Ana içeriğe geç

Changelog

Bu changelog Zeus 2.0 OCPP entegrasyonunun tüm faz teslimat'larını ve breaking-change politikası kararlarını listeler. Format Keep a Changelog prensibine yakındır; semantik versiyonlama kararları Faz 3 §14'te tanımlıdır.


Faz Teslimatları — Contacts × RFID × OCPP Birleştirme

[FAZ 5 · PR-2b] 2026-08-04 — Şarj bitişinin ayırt edilmesi + erken "tamamlandı" (DARK)

Şarj bildirimlerinin davranış katmanı. Özellik hâlâ kapalıdır (CHARGING_NOTIFICATIONS_ENABLED=False) — bu sürümde hiçbir bayrak açılmadı, üretimde davranış değişikliği yok. Migration yok.

Kullanıcı dokümantasyonu: Şarj Bildirimleri → Şarj nasıl bittiğini nasıl anlıyoruz?

  • "Şarj Kesildi" olayı artık gerçekten üretiliyor. Daha önce yalnız adı vardı; şarj her durumda "tamamlandı" sayılıyordu. Ayrım kesme anındaki güce göre yapılır: güç eşiğin (varsayılan 200 W) üzerindeyse şarj kesilmiş sayılır.
  • Karar stop_reason'a bakmaz. 45 günlük 77 üretim seansı ölçüldü: Other kodu gelen iki seans da normal bitmişti; tek gerçek kesintide gelen PowerLoss ise şebeke normalken (224/224/225 V) üretilmişti — cihaz STOP tuşunu da böyle raporluyor. Sebep koduna bakan bir sınıflandırma yanlış alarm üretirdi.
  • "Şarj Kesildi" arıza demek değildir — otomatik arıza kaydı açılmaz; bilerek "başarısız" denmez.
  • Ölçüm yoksa "tamamlandı" denir (güvenli varsayılan): bilinmeyen bir durum "kesildi" diye bildirilmez.
  • Yeni "Sebep" satırı — yalnız "Şarj Kesildi" mesajında (e-posta + WhatsApp; SMS'e konmaz, 70 karakter bütçesi kaldırmaz). OCPP kodu insan diline çevrilir, ham kod destek için parantezde kalır: Sebep: Şarj cihazından durduruldu (Local). PowerLoss için bilerek tek sebep iddia edilmez ("Enerji kesildi veya cihazdan durduruldu").
  • "Şarj Tamamlandı" artık fiş çekilmeden gelebiliyor. Araç dolduğunda güç sıfıra düşer ama kablo kilitli kalır; kullanıcı fişi saatler sonra çekebilir. Güç ~5 dakika eşiğin altında kalırsa bildirim hemen gönderilir; fiş çekildiğinde ikinci mesaj gönderilmez.
    • Bilinen sınır: araç şarjı geçici duraklatırsa bildirim erken gelebilir (süre ayarlanabilir).
    • Bilinen sınır: erken gelen mesajda enerji/süre/tutar yer almaz — bu değerler şarj kaydı kapandığında kesinleşir.
  • Yeni ayarlar (sistem yöneticisi): CHARGING_INTERRUPT_POWER_THRESHOLD_W (200), CHARGING_IDLE_DETECTION_ENABLED (kapalı — bkz. PR-2c), CHARGING_IDLE_COMPLETE_MIN_SAMPLES (10), CHARGING_IDLE_COMPLETE_MIN_SPAN_SEC (240, PR-2c), CHARGING_IDLE_COMPLETE_LOOKBACK_MIN (5). Hepsi ana şarj bildirimi bayrağına tabidir; ana bayrak kapalıyken hiçbiri mesaj üretmez.

[FAZ 5 · PR-2c] 2026-08-04 — Go-live sertleştirmesi + "Şarj Kesildi" tercih göçü (mig 0123)

Bağımsız iki denetimin bloklayıcı bulduğu kalemler. Özellik hâlâ kapalıdır (CHARGING_NOTIFICATIONS_ENABLED=False) — bu sürümde de hiçbir bayrak açılmadı.

Kullanıcı dokümantasyonu: Şarj Bildirimleri → Olay × kanal matrisi

  • 🔴 "Şarj Kesildi" mevcut kişilerde otomatik açıldı (mig 0123). Bu ayrım yapılmadan önce tam şarj hâlindeyken kesilen bir işlem de "Şarj Tamamlandı" olarak size ulaşıyordu. "Şarj tamamlandı" bildirimi alan kişilerde kutu işaretlenmeseydi aynı işlem için artık hiçbir bildirim almayacaktınız. Yani yeni bir bildirim eklenmedi, hâlihazırda aldığınız bildirim korundu; kutu istediğiniz an kapatılabilir.
  • "Şarj Tamamlandı" bekleme süresi artık cihazdan bağımsız. Önceden yalnız ölçüm sayısı sayılıyordu; ölçümlerini çok sık gönderen bir cihazda bu yaklaşık 1 dakikaya kadar inebiliyordu. Artık hem yeterli ölçüm hem yeterli süre (varsayılan 4 dakika) aranır — doküman "yaklaşık 5 dakika" derken her cihaz için aynı şeyi ifade eder.
  • Boşta dedektörü artık varsayılan kapalı. Şarj bildirimleri açıldığında erken "tamamlandı" özelliği kendiliğinden devreye girmez; sistem yöneticisi onu ayrı bir adımda açar (kademeli geçiş).
  • Görünmez arıza kapatıldı (ops). Ölçüm sorgusu düşerse bu artık "ölçüm yok" (normal durum) gibi raporlanmıyor; ayrı bir arıza etiketiyle sayılıyor ve tek bir hata, o turdaki diğer şarj işlemlerinin değerlendirilmesini durdurmuyor. Kullanıcıya yansıyan davranış değişmedi: bilinmeyen durumda yine "Şarj Tamamlandı" denir.

[FAZ 5 · PR-2a] 2026-08-04 — Şarj bildirimi mesaj metinleri (DARK)

Şarj bildirimlerinin metin/biçim katmanı düzeltildi. Kime ve ne zaman bildirim gittiği değişmedi; özellik hâlâ kapalıdır (CHARGING_NOTIFICATIONS_ENABLED=False) — bugüne kadar hiç mesaj gönderilmedi.

Kullanıcı dokümantasyonu: Şarj Bildirimleri → Bildirimde ne yazar?

  • Türkçe metinler tam yazımıyla: "Şarj Başladı", "Şarj Tamamlandı", "İstasyon", "Konnektör", "Süre" (önceki taslakta Türkçe karakterler düşürülmüştü).
  • "Şarj Kesildi" — üçüncü olayın başlığı artık böyle (İngilizcesi Charging Interrupted). Bilerek "başarısız" denmiyor: şarjın kesilmesinin en yaygın sebebi kullanıcının cihazdaki STOP tuşuna basmasıdır; "başarısız" yanlış arıza algısı yaratır.
  • Tercih ekranında üçüncü sütun: Kişi → Bildirimler sekmesindeki olay × kanal tablosunda artık "Şarj Kesildi" sütunu da var ve kanal başına ayrı ayrı açılıp kapatılabilir (sütun başlığının altında ne zaman gönderildiği yazar). Bu olay daha önce ekranda gizliydi: API üzerinden tanımlanmış bir tercih korunuyordu ama kullanıcı ekrandan açıp kapatamıyordu. Ayrıntı: Kişiler → Bildirim tercihleri.
  • Türkçe sayı biçimi: 18,4 kWh (ondalık virgül) ve 91,35 ₺. İngilizce bildirimlerde 18.4 kWh / 91.35 TRY korunur.
  • Konnektör numarası her zaman yazılır (tek konnektörlü cihazda da); bilinmiyorsa -.
  • Sürücü adı izleyiciye: izleyiciye giden mesajda artık kimin şarj ettiği yazar. Sürücünün kendi mesajında adı yazmaz; kart bir kişiye bağlı değilse satır hiç görünmez. Ad gönderim kayıtlarına/günlüklere yazılmaz.
  • SMS kırpması anlamı bozmuyor: eskiden metin 70 karakterden kesilip saat kayboluyordu. Artık başlık ve saat her zaman korunur; önce uzun istasyon adı kısaltılır, gerekirse enerji/süre ayrıntısı düşürülür.
  • E-posta konusu [Zeus Şarj] Şarj Tamamlandı: Turef AC-1 (İngilizce: [Zeus Charging] ...); Türkçe karakterler e-posta başlığında standart biçimde kodlanır.

[FAZ 3d] 2026-07-28 — Kişi detayında "Bildirimler" sekmesi

Birleşik Kişiler ekranındaki kişi panelinin üçüncü sekmesi (daha önce boş yer tutucuydu) artık bildirim tercihlerini yönetir: Ayarlar → Kişiler → kişiye tıklayın → Bildirimler.

  • Şarj bildirimleri: kapsam (yalnız kendi şarjları / izleyici — tüm cihazlar) + olay × kanal tablosu (e-posta / SMS / WhatsApp × şarj başladı / tamamlandı) + sessiz saatler.
  • Tabloda yalnız üretilen olaylar var. "Şarj başarısız" olayı sözleşmede tanımlıdır ancak bugün hiç üretilmez; bu yüzden gösterilmez. Daha önce API ile tanımlanmış böyle bir tercih silinmez, korunur.
  • Sessiz saatler kişi bazındadır ve kaydedince kişinin yazılan kanallarına aynı yazılır; kayıtlı değerler kanal bazında farklıysa ekran kaydetmeden önce uyarır.
  • Kayıt kısmidir: yalnız gerçekten değiştirdiğiniz kanallar gönderilir. Sürücünün kendi bildirim ekranından (/account/charging-notifications) açtığı kanal, yönetici başka bir ayarı kaydettiğinde sessizce kapanmaz. Siz düzenlerken yazılacak bir ayar değişmişse kayıt gönderilmez; ekran güncel duruma göre tazelenir ve etkilenen ayarları söyler (tekrar Kaydet = bilinçli üst-yazma). Çakışma kontrolü kapsam eksenini de kapsar: gövde is_charge_watcher alanını her istekte taşıdığı ve backend onu şarj-olaylı tüm satırlara uyguladığı için, yalnız kanal yazan bir kayıt da başka birinin izleyici değişikliğini geri alabilirdi.
  • İzleyici seçimi tek başına bildirim getirmez: hiçbir olay × kanal seçili değilken dispatcher bu kişiye hiçbir şey teslim etmez; ekran bunu uyarı olarak yazar. Kayıttan sonra ekran sunucunun döndürdüğü duruma senkronlanır, kapsam saklanmadıysa bu da ayrıca bildirilir.
  • Alarm kanal varsayılanı ekranda YOK: vaat edilen "alarm kuralını ön-doldurma" hiçbir yüzeyde tüketilmiyordu (alarm kuralında kişi başına kanal kavramı da yok) — karşılıksız kontrol kaldırıldı. Kayıtlı alarm.default satırları silinmez, alarm gönderimi değişmez.
  • Şarj bildirimi gönderimi ayrı bir sistem ayarına bağlıdır (CHARGING_NOTIFICATIONS_ENABLED + tenant bazlı canary). Bu durum API ile arayüze sunulmadığı için ekran kesin bir "açık/kapalı" iddiası kurmaz; tercihlerin her hâlükârda kaydedildiğini, gönderimin bu ayara bağlı olduğunu söyler. Alarm bildirimleri etkilenmez.
  • Yetki: tercihleri değiştirmek kişi yazma (write:contact) ister; yetki yoksa sekme salt-okunur açılır.

Ayrıntı: Kişiler → Bildirim tercihleri.

[Düzeltme] 2026-07-28 — Kişiler listesinde kart filtresi artık tüm kişilerde çalışıyor

Kişiler ekranındaki "Kartı olanlar / Kartı olmayanlar" filtresi daha önce yalnız o an açık olan sayfaya uygulanıyordu.

  • Belirti: 50'den fazla kişisi olan kuruluşlarda "Kartı olanlar" seçildiğinde diğer sayfalardaki kartlı kişiler listede görünmüyordu; kullanıcı eksik listeyi tam sanıyordu. Ayrıca liste boş kalsa bile alttaki sayaç filtresiz toplamı gösterdiği için liste ile sayaç çelişiyordu.
  • Düzeltme: Filtre sunucuya taşındı. Sonuç artık kaçıncı sayfada olursa olsun tüm kişileri kapsar; sayaç filtreye uyan toplam kişiyi gösterir; filtre değişince liste ilk sayfaya döner. Arama ile kart filtresi birlikte uygulanır.
  • Ölçü: Bir kişi, kendisine bağlı en az bir kart varsa "kartı olanlar" listesine girer (kart durumu fark etmez) — liste satırındaki kart sayısı rozetiyle birebir aynı. Başka kuruluşa ait kart satırları hiçbir koşulda kişiyi "kartlı" göstermez.
  • Yanıltıcı hale gelen "Kart filtresi yalnızca bu sayfadaki kişilere uygulanır" uyarısı kaldırıldı.

Ayrıntı: Kişiler → Listede arama ve sayfalama.

[FAZ 6] 2026-07-27 — RFID kart yönetimi Kişiler ekranına yönlendirildi

Kart yönetimi tek ekranda toplandı. Eski Şarj Cihazları → RFID Kartları sayfası artık birleşik Kişiler ekranına yönlendirir.

  • /chargers/rfid-cards/settings?tab=contacts. Kayıtlı yer imleri, dokümantasyon bağlantıları ve derin bağlantılar çalışmaya devam eder (yönlendirme; sayfa kaldırılmadı).
  • Filo geneli (kişiye bağlanmamış) yetki kayıtları kaybolmadıŞarj Cihazları → Filo Geneli Yetki Kayıtları (/chargers/auth-list) altına taşındı. Bu ekran şu üç işi karşılamaya devam eder:
    1. geçmişten gelen / elle girilmiş filo geneli kayıtları görmek ve kaldırmak (Kişiler yalnız kişiye bağlı kartları listeler),
    2. "Bu kart numarası için filo geneli bir yetki kaydı var" hatasını çözmek (çakışan kaydın kaldırılabildiği tek ekran),
    3. seçili cihazlara toplu liste gönderimi (SendLocalList) ve cihaz sürüm farkı (drift) doğrulaması.
  • Uzaktan şarj başlatma penceresindeki "Üst Etiket tanımlı değil" bağlantısı da bu yeni ekrana gider — üst etiket (parent_id_tag) Kişiler kart formunda tanımlanamaz, yalnız filo geneli yetki kaydı ekranından girilir.
  • Şarj Cihazları listesindeki araç çubuğu ikiye ayrıldı: Kişiler & Kartlar (birincil) ve Filo Geneli Yetki Kayıtları (ikincil).

Ayrıntı: Kart = Kimlik + Yetki → Hangi ekran neyi yönetir.

[FAZ 4 — sertleştirme] 2026-07-27 — Kart yetkilendirmesinde üç güvenlik düzeltmesi (DARK)

FAZ 4 kart → yetki listesi projeksiyonu hâlâ karanlık (DARK) modda; bu teslimat özellik açılmadan önce kapatılması gereken üç davranış hatasını düzeltir. Flag kapalı olduğu için bugünkü çalışmanızda hiçbir değişiklik yoktur.

1) Kart yönetimi hangi ekrandan yapılırsa yapılsın yetki güncellenir

Eski yönetim ekranlarından (sürücü/RFID kart uçları) yapılan kart ekleme, güncelleme, silme ve kişi silme işlemleri, yeni yetkilendirme akışından geçmiyordu. Özellik açıldığında bu, tek başına bir görüntü tutarsızlığı değil iptal edilemeyen yetki anlamına gelirdi:

  • silinen bir kart cihazlarda "kabul" durumunda kalıp şarj başlatabilirdi,
  • "Engelli" yapılan kart cihazlarda gerçekten reddedilmezdi,
  • eski ekrandan eklenen kart hiçbir cihazda yetkili olmazdı,
  • silinen kişinin kartı cihazlarda "kabul" olarak kalırdı.

Artık dört işlem de aynı yetki güncellemesinden geçer. Eski ekranların davranışı ve alanları değişmedi (kart devri, kişiye atanmamış/kayıp kartlar üzerinde işlem yapabilme dâhil) — yalnızca yetki senkronu eklendi.

2) "Seçili cihazlar" kısıtı artık sessizce uygulanmadan geçmiyor

Aynı kart numarası için filo geneli elle girilmiş bir yetki kaydı varken kart "Seçili cihazlar" kapsamıyla kaydedilirse, seçmediğiniz cihazlarda kart filo geneli kayıt yüzünden çalışmaya devam ediyordu — yani kısıt uygulanmamış oluyordu (ve bu durum hiçbir uyarı üretmiyordu). Artık işlem kaydedilmez; ne yapmanız gerektiğini anlatan bir hata alırsınız. Ayrıntı: Kart = Kimlik + Yetki → filo geneli kayıt hatası.

3) Kapsam değişiminde cihaza gönderilen liste düzeltildi

Kart kapsamı "Tüm cihazlar" ↔ "Seçili cihazlar" arasında değiştirilirken, cihaza gönderilen yetki listesinde aynı kart numarası iki kez (biri ekleme, biri kaldırma olarak) yer alabiliyordu. Cihazın bu durumda ne yapacağı standartta tanımlı değildir; bazı cihazlarda kart yetkisini kaybedebilirdi. Artık her cihaza kart numarası başına tek ve doğru kayıt gönderilir; kapsam genişlediğinde önceden seçili olan cihaz yetkisini kaybetmez.

4) Kart iptali artık hiçbir koşulda engellenmiyor

(2) maddesindeki koruma ilk hâlinde fazla genişti: aynı kart numarası için filo geneli elle girilmiş bir kayıt varken, "Seçili cihazlar" kapsamlı bir kartı "Engelli" yapmak da reddediliyordu. Sonuç, korumanın amacının tam tersiydi — kart cihazlarda "kabul" olarak kalıyor, operatör iptal edemiyordu. Artık koruma yalnızca kartın yetkisini genişleten işlemlerde çalışır:

İşlemKoruma çalışır mı?
Yeni kart (seçili cihazlar, aktif)Evet
Kapsam değiştirme / daraltmaEvet
Kartı yeniden aktifleştirmeEvet
"Engelli" / "Kayıp" yapmaHayır — her zaman geçer
Geçerlilik süresini kısaltmaHayır — her zaman geçer
Kartı silmeHayır — her zaman geçer

5) Filo geneli kayıt eklemede de aynı koruma (ters yön)

Koruma tek taraflıydı: önce "Seçili cihazlar" kartı, sonra aynı kart numarası için Şarj Cihazları → RFID Kartları sayfasından filo geneli kayıt eklenirse kısıt yine sessizce geçersiz kalıyordu. Artık bu yön de reddedilir ve ne yapmanız gerektiğini anlatan bir hata verir.

Görünürlük iyileştirmesi

Elle girilmiş bir kayıtla çakışma olduğunda kart yanıtı artık bunu açıkça bildirir ve kartın gerçekte yürürlükte olan durumunu ayrıca gösterir (önceden yanıt "kabul" diyor, cihazda ise "red" uygulanıyordu). Bu tarama kartın kapsamadığı cihazlardaki çakışan kayıtları da kapsar.

Uyarı henüz ekranda gösterilmiyor

Bu sinyal şu an API yanıtında ve sistem günlüklerinde/metriklerinde mevcuttur; Kişiler ekranında görsel gösterimi sonraki sürümde eklenecektir.

Non-breaking

Uçlar ve mevcut alanlar değişmedi. Kart yanıtına iki yeni opsiyonel alan eklendi (projection_status, effective_ocpp_status); özellik kapalıyken ikisi de boştur.

[FAZ 4] 2026-07-27 — Kart = kimlik + yetki: RFID kart → OCPP Yetki Listesi projeksiyonu (DARK)

Birleşik Kişiler (Contacts) ekranından eklenen bir RFID kart artık cihazlarda otomatik yetki kazanır (kullanıcı kararı: "Kart = kimlik + yetki"). Kart eklenince/güncellenince/silinince veya durumu değişince (aktif ↔ engelli ↔ süresi geçmiş), sistem kartı OCPP Yetki Listesi'ne (auth-list) projekte eder ve şarj cihazına gönderir (SendLocalList). Böylece operatör kartı iki ayrı yerde (kişi kartı + cihaz yetki listesi) tanımlamak zorunda kalmaz.

Bu özellik karanlık (DARK) modda teslim edilir — varsayılan olarak KAPALI (RFID_CARD_AUTHLIST_SYNC_ENABLED=False); operasyonel hazırlık (canary) tamamlanınca açılır. Kapalıyken kart yönetimi bugünkü gibi çalışır.

Kullanıcı dokümantasyonu: Kart = Kimlik + Yetki

Davranış (flag açıkken, müşteri-yüzü)

  • Cihaz kapsamı korunur: Kart "Tüm cihazlar" ise tenant'ın tüm şarj cihazlarında; "Seçili cihazlar" ise yalnızca seçilen cihazlarda yetkilidir.
  • Durum eşlemesi: Aktif kart → cihazda kabul (Accepted); engelli/kayıp kart → red (Blocked); süresi geçmiş → süresi doldu (Expired). Geçerlilik başlangıcı gelecekte olan kart, cihaz "başlamadan-önce"yi ifade edemediği için listeye yazılmaz (başlangıç geldiğinde kart yeniden kaydedilerek etkinleştirilir).
  • Manuel yetki listesiyle bir arada çalışır: Cihaz yetki listesindeki mevcut elle girilmiş kayıtlar korunur; projeksiyon yalnızca karttan ürettiği kayıtları yönetir (sahiplik işareti ile ayrılır).
  • Yetkilendirme kararı değişmez: Şarj sırasında kart doğrulama (Authorize) akışı aynen kalır — kart yalnızca yetki listesine yansır (sıfır risk).
  • Kişi silinince kartın yetkisi de kalkar: Kişi silindiğinde ona bağlı kartlar silinmez (geçmiş/rapor izi korunur); kartlar kişiden ayrılıp "Kayıp" olur ve cihazlarda "Red" (Blocked) durumuna çekilerek cihaz(lar)a gönderilir — silinen kişinin kartıyla şarj başlatılamaz.

Düzeltme — kart listesindeki "OCPP durumu" artık cihazdaki gerçek durumu yansıtır

Kart yanıtındaki ocpp_status alanı önceden yalnızca kart durumuna bakıyordu ve geçerlilik penceresini yok sayıyordu; bu yüzden ekranda gösterilen durum, projeksiyonun cihaza yazdığı durumdan sapabiliyordu (örn. başlangıcı gelecekte olan kart ekranda "kabul" görünüyor, cihazda ise hiç yetkili olmuyordu). Artık her iki taraf aynı kanonik çözücüyü kullanır; sapma ortadan kalkmıştır.

  • Başlangıcı gelecekte olan kart → "Red" (henüz yetkili değil).
  • Bitişi geçmiş kart (durumu hâlâ "Aktif" olsa bile) → "Süresi doldu".
  • Tanınmayan/eşlenmemiş kart durumu → "Red" (güvenli varsayılan; sessizce yetki verilmez).

Non-breaking

  • API sözleşmesi (uçlar/şema) değişmez; yalnızca kart CRUD'unun yan etkisi (auth-list senkronu) eklenir ve flag ile korunur. ocpp_status alanının tipi/adı değişmez, yalnızca değeri artık geçerlilik penceresini hesaba katar (doğruluk düzeltmesi). Cihaz push'u best-effort'tur (cihaz çevrimdışıysa DB doğru kaynak kalır, sonradan senkron olur).

Şema

  • ocpp_rfid_cards.scope alanı VARCHAR(16)VARCHAR(20) genişletildi (Migration 0117). Bu, selected_chargers (17 karakter) değerini saklayamayan bir alt yapı kısıtını düzeltir; "Seçili cihazlar" kapsamlı kart artık kaydedilebilir.

[FAZ 2b] 2026-07-27 — Telefon numarası tek biçime getirildi (sürücü/kişi kayıtları)

Sürücü (ve birleşik Kişiler) kayıtlarında Türkiye cep telefonu numarası artık her zaman tek bir biçimde saklanır: +905XXXXXXXXX.

Ne değişti (müşteri-yüzü)

  • 0555 123 45 67, 905551234567, 5551234567 gibi hangi yazımı girerseniz girin kayıt +905551234567 olarak saklanır ve ekranda öyle görünür. Girdiğiniz numara reddedilmez — yalnızca biçimi düzeltilir.
  • Yurt dışı numaraları ve sabit hatlar olduğu gibi korunur (örn. +491701234567, 02123456789). Yurt dışı bir numara girerken ülke kodunu + ile yazın; ülke kodsuz 10 haneli 5… numaralar Türkiye cep hattı sayılır.

Neden

Aynı kişi, farklı ekranlardan farklı yazımla girildiğinde sistem bunları iki ayrı kişi olarak görüyordu. Bu, kişiye bağlı RFID kart ve bildirim tercihlerinin bölünmesine yol açıyor ve arayüzden geri alınamıyordu. Artık tüm giriş noktaları aynı kuralı uygular.

Mevcut kayıtlar

Bu değişiklik bundan sonra kaydedilen/güncellenen numaralar için geçerlidir; geçmiş kayıtlar toplu olarak yeniden yazılmaz. Eski biçimde kalmış bir kayıt, ilk güncellemede (veya Excel içe aktarımında eşleştiğinde) otomatik olarak yeni biçime çekilir — kopya kişi oluşturmadan.

Kullanıcı dokümantasyonu: Kişiler → Telefon numarası biçimi

Düzeltme — self-servis girişte eski yazımla kayıt bulunamıyordu

Telefon numaralarının tek biçime çekilmesinden sonra, sürücü self-servis girişinde (şarj geçmişi için gönderilen tek kullanımlık bağlantı) numarasını eski yazımla (0555… / 555…) giren kullanıcılar kendi kayıtlarını bulamıyordu. Hata sessizdi: güvenlik gereği bulunamayan kullanıcıya da "bağlantı gönderildi" mesajı döndüğü için kimse fark etmiyordu. Arama artık aynı kanonik kuraldan türetilen tüm yazımları birlikte sorgular; hangi yazımı girerseniz girin kaydınız bulunur.

[FAZ 2b] 2026-07-27 — Kişiler için Excel içe/dışa aktarma (DARK)

Birleşik Kişiler listesi için Excel indirme ve toplu yükleme eklendi. Bu uçlar karanlık (DARK) modda teslim edilir — yeni Kişiler ekranı ile birlikte kullanıma açılır; bugünkü Ayarlar → Bildirim Rehberi aktarımı değişmeden çalışmaya devam eder.

Kullanıcı dokümantasyonu: Kişiler → Excel ile içe/dışa aktarma

Eski rehber aktarımından farkları (müşteri-yüzü)

  • Kopya kişi üretmez. Yükleme, satırları önce mevcut kişilerle eşleştirir: telefon varsa telefon (tüm yazımlar aynı sayılır), telefon yoksa ad + soyad + e-posta. Eşleşen kişi güncellenir, eşleşmeyen oluşturulur; aynı dosyanın ikinci yüklemesi kopya oluşturmaz. Eski aktarım her satırı körü körüne ekliyordu.
  • Boş hücre mevcut veriyi silmez (kısmi doldurulmuş bir sayfa yüzünden telefon/e-posta kaybı olmaz).
  • Geçersiz e-posta artık sessizce atılmıyor. Eski aktarım geçersiz e-postayı silip satırı yine de kaydediyordu (kullanıcı verisini kaybettiğini fark etmiyordu); yeni aktarımda boş olmayan geçersiz e-posta satır hatasıdır.
  • Sütun düzeni ve hata mesajları eski dosyayla uyumludur (Ad, Soyad, Telefon, Email, Not); indirilen dosyaya bilgi amaçlı RFID Kart Sayısı sütunu eklenir. Ham kart numarası dışa aktarılmaz.
  • Sınırlar: yalnız .xlsx (.xls ve CSV kabul edilmez), en fazla 2 MB ve 10.000 satır. Çok sayfalı dosyada yalnız ilk sayfa okunur ve bu bilgi mesajı olarak döner.
  • Güvenlik: indirilen dosyada = + - @ ile başlayan hücreler formül olarak çalıştırılmasın diye metin olarak yazılır; hücre değeri değiştirilmediği için +90… telefon biçimi bozulmaz.

[FAZ 5] 2026-07-25 — Şarj bildirimleri: sürücü + izleyici (DARK)

Şarj başladığında ve tamamlandığında ilgili kişilere e-posta / SMS / WhatsApp bildirimi gönderen dispatcher eklendi. Daha önce kişi bazında olay×kanal tercihi kaydedilebiliyordu ancak bu tercihleri tüketen bir gönderim yolu yoktu.

Bu özellik karanlık (DARK) modda teslim edilir — varsayılan olarak KAPALI (CHARGING_NOTIFICATIONS_ENABLED=False), önce tek pilot kuruluşta açılır. Kapalıyken hiçbir şarj bildirimi üretilmez.

Kullanıcı dokümantasyonu: Şarj Bildirimleri

Davranış (flag açıkken, müşteri-yüzü)

  • Sürücü (kartı basan kişi) yalnız kendi şarj işlemlerinin bildirimini alır; izleyici olarak işaretlenen kişi kuruluştaki tüm şarj işlemlerini alır. Hem sürücü hem izleyici olan kişi tek bildirim alır.
  • Kanal başına aç/kapa + olay seçimi + sessiz saat tanımlanır. Sessiz saatte düşen bildirim gönderilmez; gönderim kaydına "atlandı" olarak yazılır.
  • Yalnız alarm bildirimi için tanımlı kişiler şarj olaylarında bildirim almaz (iki tercih ad-alanı birbirine karışmaz).
  • Bildirim dili kişinin tercih ettiği dile göre seçilir; e-posta/WhatsApp içeriğinde kuruluş markası (ad/logo/renk) kullanılır.
  • Şarj bildirimleri alarm bildirimlerinden ayrı gönderim bütçesi kullanır — yoğun şarj trafiği kritik alarm bildirimini geciktirmez.
  • Şarj cihazının çalışmasına etkisi yoktur: bildirim üretimi şarj kaydı tamamlandıktan sonra ayrı kuyrukta yürür; aynı olay için mükerrer mesaj engellenir.

Bilinen sınır

  • Tercih listesindeki "başarısız" olayı henüz üretilmemektedir (yalnız "başladı" ve "tamamlandı" gönderilir).

    Bu sınır kalktı. Olay artık üretiliyor ve adı "Şarj Kesildi" oldu — bkz. FAZ 5 · PR-2b.

[FAZ 2a] 2026-07-25 — Alarm rehberi kişileri birleşik kimliğe taşındı

Mevcut alarm bildirim rehberi kişileri, birleşik Kişiler kimliğine kopyalandı (veri taşıma / backfill). Taşınan her kayıt kaynağını taşır, böylece eski ve yeni kayıt birbirine bağlı kalır.

Müşteri-yüzü etkisi

  • Alarm bildirimleri değişmedi — alarm kuralları ve alıcıları aynı şekilde çalışmaya devam eder; eski rehber kaldırılmadı.
  • Taşınan kişilerin telefon numaraları +90… biçimine çevrildi. Tanınamayan numaralar (sabit hat, yurt dışı, biçimsiz) olduğu gibi korundu — veri kaybı olmadı.
  • Aynı taşıma birden çok kez çalıştırılsa bile kopya kişi oluşmaz.

[FAZ 1] 2026-07-24 — Birleşik "Kişiler" kimliği (DARK)

Alarm alıcısı, şarj sürücüsü ve RFID kart sahibi kavramları tek bir kişi kaydında birleştirildi. Bir kişiye ait kimlik bilgileri, RFID kartları ve bildirim tercihleri artık aynı kayda bağlıdır.

Backend DARK teslim edildi (bu sürümde hiçbir ekran tüketmiyor); birleşik Kişiler ekranı ayrı bir sürümle açılır. Mevcut alarm rehberi, sürücü yönetimi ve sürücü self-servis ekranları değişmeden çalışmaya devam eder.

Kullanıcı dokümantasyonu: Kişiler (Birleşik Rehber)

Neden

Aynı insan üç ayrı listede tutulduğu için biri güncellenip diğerleri eski kalıyor, kişiye bağlı kart ve bildirim tercihleri bölünüyordu.

Müşteri-yüzü notlar

  • Kart numarası yetkiye göre maskelenir: yalnız kişi okuma yetkisi olan kullanıcılar kart numarasını **** **** 1234 biçiminde görür; ham numara yalnız şarj sürücüsü yönetimi yetkisiyle görünür. Kart etiketi, durumu, geçerliliği ve kapsamı okuma yetkisine açıktır.
  • Kişi silme yetkiyi geri alır: kişi silindiğinde kartları silinmez (geçmiş izi korunur), kişiden ayrılıp "Kayıp" olur.
  • Tüm kişi verisi kuruluşa özeldir; başka kuruluşun kişi/kart/cihaz kayıtlarıyla eşleşme mümkün değildir.

Hot-fix Entries (Production Incident Response)

[Toast Migration] 2026-06-10 — İşlem bildirimleri (toast) artık ekranda görünüyor

Şarj cihazı (OCPP) yönetimi, Modbus profilleri, LOTO oturumları, gateway tanımlama (provisioning), SLD ve ayar sayfalarındaki işlem başarı/hata bildirimleri artık ekranın sağ üst köşesinde toast olarak görünür. Önceki durumda bu sayfalardaki 139 bildirim çağrısı, uygulamaya hiç bağlanmamış eski bir bildirim deposuna (shadcn use-toast) yazıyordu ve kullanıcıya hiçbir şey gösterilmeden sessizce kayboluyordu — işlem yine gerçekleşiyordu ama operatör sonucu göremiyordu.

Davranış değişikliği (müşteri-yüzü)

  • Uzaktan şarj başlat/durdur, rezervasyon, reset, unlock, RFID liste push, firmware güncelleme, charger düzenleme/silme, şarj profili oluştur/düzenle/sil işlemlerinde sonuç bildirimi (yeşil=başarı, kırmızı=hata, mavi=bilgi) artık görünür.
  • Modbus profil oluşturma, isim standardizasyonu ve tutarlılık raporu hataları görünür.
  • LOTO oturum aksiyonları (onaya gönder/onayla/aktifleştir/askıya al/sürdür/tamamla/iptal) sonuç bildirimi görünür.
  • Gateway sahiplenme (provisioning), SLD cihaz pozisyon kaydetme, ayarlar (kişi rehberi, tarife import, Zigbee claim, OCPP varsayılanları, charger modelleri) bildirimleri görünür.
  • Teknik altyapı: ölü shadcn use-toast store kaldırıldı; tüm bildirimler uygulamadaki tek renderer olan Sonner üzerinden veriliyor (bkz. Frontend Genel Bakış — Bildirim: Sonner). Görsel eşleme: eski destructivetoast.error, başarı semantiği → toast.success, bilgilendirme (ör. "şarj zaten sonlanmış", kısmi başarı) → toast.info.

Geriye dönük uyumluluk

  • API/OCPP wire format değişmedi; yalnızca frontend bildirim katmanı. Non-breaking.

[Issue #502] 2026-05-21 — RemoteStartTransaction parent_id_tag desteği + 1:1 invariant

OCPP RemoteStartTransaction çağrısında operatör artık manuel 20 karakter id_tag yerine tenant-wide "Üst Etiket" (parent_id_tag) dropdown'undan kişi/grup tanıtıcısı seçebilir. Backend dropdown seçimini 1:1 invariant ile tek bir aktif RFID kartına çözer ve OCPP dispatcher'a idTag=<resolved> gönderir. Eski id_tag payload formu geri uyumluluk için korunur (Pydantic mutex validator XOR).

Bu değişiklik non-breaking ek özelliktir; mevcut clientlar (eski frontend, mobile, integrators) id_tag ile çağırmaya devam edebilir.

Eklendi (backend)

  • backend/app/features/ocpp/schemas.py:1483-1565RemoteStartRequest:
    • id_tag: CiString20 | None (opsiyonel, varsayılan None) — geri uyumluluk.
    • parent_id_tag: CiString20 | None (opsiyonel, varsayılan None) — Issue #502.
    • @model_validator(mode="after") _mutex_id_or_parent — XOR: tam olarak biri verilmelidir, aksi halde 422 VALIDATION_ERROR.
    • model_config = ConfigDict(json_schema_extra={"examples": [...]}) — OpenAPI Swagger UI'da iki geçerli payload formu (parent_id_tag ve id_tag) örnek olarak görünür.
  • backend/app/features/ocpp/commands.py:606-764_resolve_parent_to_id_tag(db, tenant_id, parent_id_tag, *, actor_user_id) helper:
    • Filtre: tenant_id = :tenant_id AND charger_id IS NULL AND parent_id_tag = :parent AND status = 'Accepted' AND (expiry_date IS NULL OR expiry_date > NOW()) LIMIT 2.
    • 0 sonuç → HTTPException(404, error_code="OCPP_PARENT_TAG_NO_ACTIVE_CARD") (audit log yazılmaz).
    • 1 sonuç → entry.id_tag (plaintext) + audit log ocpp.remote_start.parent_resolution (PII mask).
    • 2+ sonuç → HTTPException(500, error_code="OCPP_PARENT_TAG_DATA_INTEGRITY") + audit log ocpp.remote_start.parent_resolution.invariant_violation + structured log error.
    • PII koruma: log/audit payload'larda parent_id_tag ve resolved_id_tag mask_id_tag() (son 4 karakter + yıldız) ile maskelenir.
  • backend/app/features/ocpp/commands.py:772-829remote_start():
    • payload.parent_id_tag is not None ise _resolve_parent_to_id_tag çağrılır.
    • Aksi halde payload.id_tag doğrudan kullanılır (mutex sayesinde garanti).
    • OCPP dispatcher payload {"connectorId": x, "idTag": resolved_id_tag}.

Eklendi (OpenAPI dokümantasyonu)

  • backend/app/features/ocpp/router.py:526-680remote_start_endpoint:
    • responses dict 404/422/500 için content.application/json.examples içerir:
      • 404: charger_not_found (NOT_FOUND) + parent_no_active_card (OCPP_PARENT_TAG_NO_ACTIVE_CARD).
      • 422: mutex_violation_both_missing + mutex_violation_both_present (her ikisi de XOR ihlali, aynı VALIDATION_ERROR kodu).
      • 500: parent_data_integrity (OCPP_PARENT_TAG_DATA_INTEGRITY).
    • Docstring'de hata kodları listesi ve runbook referansı.

Eklendi (frontend — bağlı bileşen)

  • frontend/src/components/chargers/RemoteStartDialog.tsx — manuel <Input> yerine <Select> (useTenantAuthList() filtreli liste). Boş durum CTA: RFID Kartları sayfasına yönlendirme. Submit payload { connector_id, parent_id_tag }.
  • frontend/src/types/ocpp.tsRemoteStartRequest tipi: id_tag ve parent_id_tag her ikisi opsiyonel (TypeScript tarafında XOR runtime'da backend mutex enforce eder; frontend default parent_id_tag kullanır).
  • 4 yeni i18n key (TR + EN, chargers.remoteStart.*): parentTag, parentTagPlaceholder, noParentTag, manageRfidCta, noActiveCard.

Değişti (DB schema — Migration 0056, expand-only)

  • ocpp_auth_list_entries tablosuna yeni partial UNIQUE INDEX: uq_auth_list_parent_per_tenant ON (tenant_id, parent_id_tag) WHERE parent_id_tag IS NOT NULL AND charger_id IS NULL.
  • Migration fail-loud pre-check: SELECT COUNT(*) FROM ... GROUP BY tenant_id, parent_id_tag HAVING COUNT(*) > 1 > 0 ise RuntimeError raise eder, runbook ile manuel cleanup beklenir.
  • Runbook: docs/runbook/0056_parent_id_tag_cleanup.md (8 bölüm: duplicate tespit SQL, cleanup senaryoları, re-run, rollback, INVALID index recovery).
  • Migration sıralama notu (2026-05-21): İlk yazımda numara 0055 idi; PR #504 0055_cleanup_double_prefix_ocpp_heartbeat aynı parent 0054'ten dallandığı için single-head ihlali doğurdu; bu PR 0056'ya alındı, down_revision = "0055_cleanup_double_prefix_ocpp_heartbeat" zincire takıldı.

Yeni hata kodları

KodHTTPTetiklenmeEylem
OCPP_PARENT_TAG_NO_ACTIVE_CARD404parent_id_tag için tenant altında aktif kart yokOperator → "RFID Kartları" sayfasından kart ekle veya status='Accepted'/expiry_date güncelle
OCPP_PARENT_TAG_DATA_INTEGRITY5001:1 invariant ihlali (2+ aktif kart aynı parent altında)Operasyon → runbook 0056_parent_id_tag_cleanup.md ile temizle
VALIDATION_ERROR (mutex)422İstek body'sinde id_tag ve parent_id_tag ikisi de None veya ikisi de doluClient → tam olarak biri dolu olacak şekilde gönder

Geriye dönük uyumluluk

  • Non-breaking, additive. Mevcut clientlar (frontend eski sürüm, mobile, external integrators) {"connector_id": 1, "id_tag": "RFID-..."} ile çağırmaya devam edebilir → mutex validator yalnızca (id_tag is None) == (parent_id_tag is None) durumunda 422 verir. Eski payload bu eşitliği sağlamaz (id_tag dolu, parent_id_tag None → mutex geçer).
  • OCPP wire format değişmedi. Dispatcher'a giden CALL {"connectorId": x, "idTag": <resolved_or_provided>}; charger için davranış aynı.
  • DB-level UNIQUE INDEX (Migration 0056) Partial INDEX'tirWHERE parent_id_tag IS NOT NULL AND charger_id IS NULL kapsamı dışında veri etkilenmez. Per-charger override (charger_id IS NOT NULL) ileri sürümde ayrı PR ile değerlendirilir.
  • API versioning gerekmez/api/ocpp/... prefix korunur.

Cross-client uyum

  • Frontend: Yeni dropdown UI (RemoteStartDialog) useTenantAuthList() hook'unu kullanır; staleTime 30s + dialog açılışta refetch. Filtre: status === 'Accepted' && (!expiry_date || new Date(expiry_date) > new Date()) && !!parent_id_tag. Defensive dedupe + alfabetik (Intl.Collator('tr-TR')).
  • Mobile / external integrators: Eski id_tag payload formu çalışmaya devam eder. Yeni parent_id_tag formu opsiyonel; integrator kendi UX'ine entegre edebilir.
  • Embedded / OCPP charger firmware: Charger tarafından tüketilmez; OCPP wire format değişmedi → etkisiz.

Müşteri-yüzü dokümantasyon (https://docs.enerji.kepmark.com)

Bu repo Zeus 2.0 müşteri-yüzü docs source'u (docs-site/, Docusaurus). Issue #502 için bu sürümde teknik docs güncellendi (faz3-rest-api.md error code listesi + bu changelog entry'si). Operatöre yönelik "Uzaktan Şarj Başlat" ve "RFID Kartları" sayfa içerikleri (ekran görüntülü kullanıcı kılavuzu) docs-site/docs/ altında şu an mevcut değil — operatör dokümantasyonu boşluğu için ayrı bir görev açılır (aşağıdaki "Sonraki adımlar" bölümü).

Sonraki adımlar

  • Operatör dokümantasyonu (yeni docs-site sayfaları): "Uzaktan Şarj Başlat" ve "RFID Kartları" sayfaları docs-site/docs/operator-guide/ (veya docs-site/docs/charging-stations/ benzeri) altında oluşturulur. İçerik:
    • "Uzaktan Şarj Başlat" — dropdown akışı, "Üst Etiket nedir?" mini bölüm, boş durum CTA ekran görüntüsü, mutex hata akışları.
    • "RFID Kartları" — "1 kişi = 1 kart" iş kuralı, "Üst Etiket nasıl tanımlanır?", duplicate temizleme rehberi (runbook 0056 referansı).
  • Sidebar entry: docs-site/sidebars.js operatör kılavuzu kategorisi eklenir.
  • EN-mode browser smoke (Mayıs 2026 i18n bundle olayı sonrası zorunlu): Deploy sonrası ?lang=en ile Remote Start dialog açılıp "Parent Tag" başlığı ve boş durum metni doğrulanır.

Doküman

  • Faz 3 REST API → Error Code TaxonomyOCPP_PARENT_TAG_NO_ACTIVE_CARD ve OCPP_PARENT_TAG_DATA_INTEGRITY eklendi.
  • Repo içi: docs/runbook/0056_parent_id_tag_cleanup.md — duplicate temizleme runbook'u (8 bölüm).
  • Repo içi: .claude/plans/502-yi-planla-ve-sonra-eager-umbrella.md — Issue #502 final plan (4 onaylı tasarım kararı).
  • Issue: zeus2.0#502.

[PR-H/4-fix] 2026-05-21 — Legacy heartbeat_watchdog deprecate + saha bug temizlik

Saha kanıtı 2026-05-21: 1 OCPP alarm 24.5 saat boyunca status='open' kaldı. İki kök neden:

  1. Bug 1 — Double prefix alarm_type: engine.record_ocpp_incident code.startswith("ocpp_") orijinal string'i (UPPER) kontrol ediyordu, else branch f"ocpp_{code.lower()}" ile prefix eklerken "OCPP_HEARTBEAT_TIMEOUT""ocpp_ocpp_heartbeat_timeout" üretiyordu. Tüm OCPP_* errorCode'ları için aynı bug (GroundFailure, OverCurrentFailure, vb. eğer tetiklenmiş olsaydı).
  2. Bug 2 — Close path eksik: Legacy heartbeat_watchdog Celery task ile açılan alarmlar için hiçbir reconnect senaryosunda close çağrısı yoktu (resolve_charger_offline_if_online sadece ocpp_charger_offline tipini kapatır).

Mimari karar — Yol A: Watchdog deprecate

PR-A1 (WebSocket disconnect → ocpp_charger_offline) + PR-A2 (1h+ heartbeat → ocpp_charger_offline_extended) domain alarm sistemi zaten heartbeat/inactivity kapsamını sağlıyor; mimari overlap kaldırıldı.

Değişiklikler

  • backend/app/features/alarms/engine.pycode_lower = code.lower() lokal var üzerinden startswith kontrolü. Tüm OCPP_* (UPPER/lowercase/mixed/prefix'siz) input'lar tek prefix üretir.
  • backend/app/tasks/ocpp_tasks.py::heartbeat_watchdog — no-op stub (Celery registry geri uyumluluğu için signature korundu; _heartbeat_watchdog_async silindi).
  • backend/app/core/celery/celery_config.pyocpp_heartbeat_watchdog beat schedule entry kaldırıldı.
  • Migration 0055 (cleanup_double_prefix_ocpp_heartbeat) — mevcut açık ocpp_ocpp_heartbeat_timeout alarmları status='closed', close_reason='deprecated_watchdog' SET + AlarmEvent('closed') yazıldı; policy soft-delete (active=false + name [DEPRECATED PR-H/4-fix] ...). Cascade=delete-orphan nedeniyle hard DELETE audit kaybeder; soft-delete IZLENEBILIRLIK KIRMIZI ÇIZGI'sini korur.

Saha smoke (deploy sonrası)

  1. Mevcut açık e22f12ab-... alarm status='closed', AlarmEvent('closed') yazıldı.
  2. Celery beat schedule artık heartbeat_watchdog tetiklemez; PR-A1/A2 domain task'ları aktif.
  3. Yeni OCPP error code path'ı (örn. OCPP_GroundFailure test scenario) artık single-prefix ocpp_groundfailure üretir.

Bilinen scope sınırı

PR-H/4-fix migration resolved-notification göndermez (raw SQL UPDATE; engine _auto_close_open_alarm tetiklemez). 24.5h-old alarm sessizce kapanır — support için: müşteri "kapanma bildirimi neden gelmedi?" derse manuel cleanup gerekçesi açıklanır.


[B Paketi — Adım 4-5] 2026-05-13 — DeviceResponse.subregion_id (OCPP charger consumption feature gap)

OCPP charger için /api/devices ve /api/devices/{id} response'larında subregion bilgisi yoktu. Frontend analysis-filters charger seçicisini subregion'a göre daraltamıyor, ayrıca region/subregion bazlı energy consumption aggregation hesaplamasında ek sorgu gerekiyordu. Non-breaking, additive alan eklendi.

Eklendi (backend)

  • backend/app/features/devices/schemas.py:172-184DeviceResponse.subregion_id: UUID | None (opsiyonel, default None). Description'da semantik netleştirildi: OCPP charger için OcppCharger.subregion_id LEFT JOIN ile doldurulur; gateway tabanlı cihazlarda şu an None döner (gateway resolution bu endpoint'te uygulanmadı).
  • backend/app/features/devices/service.py:244-310get_devices() ve get_device() OcppCharger'a LEFT OUTER JOIN; charger olmayan cihazlarda None kalır. ORM Device modeline kolon eklemeden runtime attribute olarak set edilir → DB migration yok.

Doküman (bu PR)

  • docs-site/docs/api-reference/endpoints.mdGET /api/devices örnek response'a subregion_id alanı eklendi + cihaz tipi başına davranış tablosu (charger: dolu, gateway tabanlı: null, dangling: null).

Geriye dönük uyumluluk

  • Non-breaking, additive. Mevcut clientlar (frontend eski sürüm, integrators) yeni alanı görmezden gelebilir — subregion_id Pydantic'te Optional[UUID].
  • DB schema değişmedidevices tablosuna kolon eklenmedi; alan response sırasında JOIN ile türetiliyor.
  • API versioning gerekmez/api/v1/... (ya da prefix neyse) prefix korunur.

Cross-client uyum

  • Frontend (Adım 6 handoff): frontend/src/types/devices.ts (veya eşdeğer) içindeki Device interface'ine subregion_id?: string | null eklenecek. Analysis-filters charger seçicisi bu alanı device_role === 'charger' filtreleme ile beraber kullanacak.
  • Embedded / OCPP charger firmware: Bu endpoint cihaz tarafından tüketilmiyor — etkisiz.
  • External integrators: Backward-compatible, ek alan opsiyonel.

Sonraki adımlar

  • Adım 6 — frontend type/UI entegrasyonu (frontend-experience-architect).
  • (Opsiyonel) Gateway tabanlı cihazlarda subregion resolution backend tarafında: Device LEFT JOIN ModbusGateway/MQTTGateway ile gateway.subregion_id fallback. Şu an açık değil; gerekirse ayrı PR.

Doküman


[PR-B] 2026-05-11 — OCPP MeterValues 3-fazlı persist + handler refactor + observability

Sahada ZEUS-Q95KREQY7U1FAP4H charger'ında 11+ dakikalık MeterValues persist outage gözlendi. Aynı timestamp + measurand altında L1/L2/L3 phase'li sample'lar (time, charger_id, measurand) 3-tuple PK'sini ihlal edip UniqueViolationError atıyordu. Eski handler içinde persist + broadcast tek try'da bulunduğu için tek bir DB hatası tüm pipeline'ı (UI dahil) bloke ediyordu. Hiçbir alert tetiklenmedi (gözlemlenebilirlik boşluğu).

Commit zinciri:

  • b8c474b — Migration 0042 (backend/alembic/versions/0042_widen_meter_samples_pk_with_phase.py)
  • 6b3a022 — Handler refactor (backend/app/core/ocpp/v16/handlers/core.py::on_meter_values)
  • ed2440e — Test paketi (backend/tests/features/ocpp/test_meter_values_persist.py)
  • 8aca714 — Observability paketi (Prometheus rules + Grafana dashboard + runbook + structured-logging spec)

Değişti (DB schema — Migration 0042, expand-only)

  • ocpp_meter_samples (TimescaleDB hypertable) birincil anahtarı (time, charger_id, measurand)(time, charger_id, measurand, phase).
  • OcppMeterSample.phase kolonu: nullable=Trueprimary_key=True, nullable=False, server_default=''. NULL phase satırlar migration içinde '' sentinel'ine backfill edildi. ORM model backend/app/core/database/models.py:2532-2537 ile hizalandı.
  • Sentinel '' semantiği: OCPP spec'inde phase opsiyonel; vendor atlarsa backend '' literal'i yazar (DB NOT NULL koruması). Backend response layer (backend/app/features/ocpp/service.py:982 raw path + :1023 downsample path) bu sentinel'i frontend'e None / null olarak normalize eder.

Değişti (handler refactor — on_meter_values)

  • Persist (session.add_all + commit) ve broadcast (publish_meter_tick) ayrı try/except bloklarında. Persist patlasa bile broadcast yine denenir → LiveMeterChart UI canlı kalır.
  • IntegrityError ayrı dallanma — ocpp_meter_values_persist_conflict warning event'i (vendor replay / NTP drift sinyali).
  • Generic Exceptionocpp_meter_values_persist_failed error event'i.

Eklendi (Prometheus + Grafana — observability paketi)

  • Metric label'ları: ocpp_messages_total{action="MeterValues_persist", status="ok|conflict|error|partial"} ve {action="MeterValues_broadcast", status="ok|error"} — granüler hata izleme.
  • status="partial" semantiği: persist FAIL ama broadcast OK (tarihsel veri kaybı + UI canlı). Yeni status değeri.
  • 4 yeni alert (prometheus/rules/ocpp_meter_values.yml):
    • OcppMeterValuesPersistErrorRateHigh (critical, 5m, rate > 0 → P1)
    • OcppMeterValuesPartialPersist (warning, 10m, rate > 0.1/sn → P2)
    • OcppMeterValuesBroadcastError (warning, 5m, rate > 0 → P3)
    • OcppMeterValuesPersistConflictSpike (info, 15m, rate > 1/sn → P4)
  • Grafana dashboard ocpp-meter-values (17 panel): success rate, status breakdown, conflict heatmap, per-charger top-N.
  • Runbook: OCPP MeterValues Persist/Broadcast Failure — 4 senaryo + eskalasyon matrisi (L1/L2/L3 + P4 vendor).
  • Structured logging spec: docs/observability/structured-logging.md (repo path, docs-site dışında) — 4 yeni event (ocpp_meter_values_persist_conflict, ocpp_meter_values_persist_failed, ocpp_event_meter_publish_failed, ocpp_meter_values_failed) için required field tanımı.

Geriye dönük uyumluluk (BREAKING DEĞİL)

  • API contract / OCPP wire format / response schema değişmedi. GET /api/ocpp/sessions/{id}/meter-samples response'unda phase alanı tipi string | null aynı (Pydantic MeterSampleResponse.phase: str | None).
  • Frontend LiveMeterChart preferL1 ['L1','L2','L3', null, undefined] order'ı korunur; backend '' sentinel'i frontend'e ulaşmaz.
  • Vendor side hiçbir değişiklik yok — Beny vb. cihazlar mevcut payload'larını gönderir; backend tarafında schema-uyumlu absorb edilir.
  • DB seviyesinde expand-only: PK genişledi, eski satırlar backfill ile uyumlu, geriye dönük migration'a gerek yok.

Doküman


[Hot-fix #4] 2026-04-28 — Frontend OCPP komut hata mesajı [object Object] parse fix (PR #297)

OCPP komut dialog'larında (RemoteStart/Stop/Reset/Unlock/ChangeAvailability/SendLocalList) backend'den dönen 4xx/5xx hatalarda kullanıcıya Hata: [object Object] gösteriliyordu. Backend canonical envelope ({error_code, message, details, request_id}) doğru dönüyordu; frontend JSON.stringify(err) yerine envelope'tan message alanını çıkarmıyordu.

Eklendi

  • frontend/src/lib/api/ocpp.tsextractErrorMessage(payload) helper. Envelope message field'ı varsa onu döner; yoksa error_code veya HTTP status fallback. PII guard: id_tag/password/token gibi alanlar details içinde olsa bile mesaja sızdırılmaz.
  • 6 OCPP komut dialog'unda (RemoteStartDialog, RemoteStopDialog, ResetDialog, UnlockDialog, ChangeAvailabilityDialog, SendLocalListDialog) extractErrorMessage() entegrasyonu — başarısız komutlarda inline result block'ta düzgün Türkçe hata mesajı.

Kök sebep

fetchWithHeaders axios interceptor'ı response body'yi raw object olarak Error.message'a koyuyordu (String(body)[object Object]). Frontend extractErrorMessage ile envelope'un message field'ını öncelikli okur.

Doküman


[Hot-fix #3] 2026-04-28 — Dispatcher 409 GERÇEK kök fix + WS close 1006 fix (PR #294, PR #295, PR #296)

İki bağımsız incident aynı pencerede çıktı:

(1) Dispatcher 409 — saha bug: OCPP komut RPC'leri (POST /api/ocpp/chargers/{id}/commands/*) sahada OCPP_REMOTE_REJECTED 409 dönüyordu — charger reddetmediği halde. Dispatcher python-ocpp ChargePoint.call(payload) çağrısı dict ile yapıyordu; python-ocpp ≥ 2.x dataclass instance bekliyor. Reset round-trip 25 saniyeye çıkmıştı, BootNotification gözle görülür gecikiyordu.

(2) WS close 1006 — frontend /ws/telemetry: Frontend WS bağlantısı abnormal 1006 close (peer reset) ile düşüyordu. Backend telemetry handler'ında catch-all except Exception WebSocketDisconnect'i de yutuyordu → close-frame native gönderilmediği için browser tarafında 1006 görünüyordu.

Eklendi/Değişti

  • PR #296 (D₂ — saha 409 GERÇEK fix): backend/app/core/ocpp/dispatcher.py_invoke_charger_call() payload dict → action_class(**payload) dataclass instance mapping. python-ocpp call/reply path uyumlu hale getirildi. Saha smoke YEŞİL: Reset round-trip 25sn → ~1.5sn, BootNotification kanıtlı.
  • PR #295 (D₁ — defansif reply path): backend/app/core/ocpp/dispatcher.py _serialize_payload() dataclasses.asdict() recursive guard. Reply path'te dataclass döner gelirse JSON serialize hatası vermiyor. (Tek başına saha bug'ı çözmedi — D₂ asıl çözüm; D₁ defansif katman.)
  • PR #294 (B — WS close 1006 fix): backend/app/core/websocket/router.py /ws/telemetry handler — catch-all except Exception öncesi except WebSocketDisconnect propagate. await ws.close(code=1011, reason="...") gerçek hatalarda gönderiliyor; client 1006 yerine 1011 görüyor + close reason string okunabilir.

Kök sebep

  • python-ocpp ≥ 2.x ChargePoint.call(payload) API'si dataclass instance zorunlu kılar. PR #295 (D₁) sadece reply path'i serialize ediyordu; gerçek bug call path'inde dict gönderilmesi → python-ocpp TypeError → outer retry loop generic except → 30sn timeout → 504/409 mapping. PR #296 (D₂) action_class(**payload) ile dataclass instance üreterek sorunu kökten çözdü.

Doküman


PR-E Bütünleşik Plan + Saha Bug Fix Wave (2026-04-27 → 2026-04-28)

OCPP UI gap analizi sonrası açılan PR-E1 → PR-E6 entegre planı + ardından gelen saha incident hot-fix'leri. Aşağıdaki entry'ler kompakt format'tadır; her PR için kullanıcı/operatör için pratik özet.

[PR-E6 — Dispatcher canary monitoring] 2026-04-28 (PR #293)

Eklendi

  • Yeni Prometheus metrikleri:
    • ocpp_dispatcher_listening_active (Gauge) — aktif listen_commands task sayısı.
    • ocpp_dispatcher_loop_errors_total{error_kind} (Counter) — error_kind whitelist (5 değer: redis_connection, os_error, timeout, cancelled, unknown).
  • Alert rules (3 yeni): dispatcher restart loop, listening task yokluk anomalisi, cancelled error_kind sızıntısı (BUG sinyali).
  • Grafana dashboard panel'leri: OCPP Charger Fleet > PR-E6 Dispatcher Canary row.
  • Runbook: PR-E6 Canary Monitoring — 24h checkpoint tablosu + sapma aksiyonları.

Doküman


[PR-E6 — Dispatcher pubsub fix] 2026-04-28 (PR #292)

Değişti

  • backend/app/core/ocpp/dispatcher.pypubsub.listen() async-iterator pattern → pubsub.get_message(timeout=1.0, ignore_subscribe_messages=True) polling pattern. Saatler sonrası 504 timeout fix.

Kök sebep

redis-py 5.x async-iterator generator'ında asyncio.CancelledError yutuluyordu → outer retry loop generic except Exception path'ine düşüyor → sonsuz restart loop + pubsub session corrupt. Polling pattern'a geçince CancelledError doğru propagate eder.


[PR-E5 audit/QA bulguları] 2026-04-28 (PR #291)

Değişti

  • backend/app/features/ocpp/router.py — Reservation status query param Pydantic Literal["active", "used", "cancelled", "expired"] whitelist (422 invariant).
  • frontend/src/lib/api/ocpp.ts — WSS URL encode (special char tolerant).
  • backend/tests/features/ocpp/ — Cross-tenant erişim testleri (404 enumeration koruması doğrulandı).
  • frontend/src/components/chargers/SessionDetailDrawer.tsx — Drawer kapanışta useState reset (state leak fix).

[PR-E5 — Komut sonuç görünürlüğü + reservations list + provisioning PDF TLS/WSS] 2026-04-27 (PR #290)

Eklendi

  • Yeni endpoint'ler:
    • GET /api/ocpp/chargers/{id}/command-logs (PR-E5 A0) — son OCPP komutlarının istek/yanıt audit trail'i (READ_CHARGER, default limit 20, max 100).
    • GET /api/ocpp/chargers/{id}/reservations (PR-E5 A) — charger bazlı rezervasyon listesi (CancelReservation dropdown için, status filter active|used|cancelled|expired, default active).
  • Frontend: CommandLogsPanel.tsx (yeni component, charger detayda son 20 komut audit trail), CommandResultAlert.tsx (inline result block), 6 dialog'da inline result, useCommandResultState hook.
  • Provisioning kit PDF (PR-E5 B): PDF'te yeni satırlar — TLS: ENABLED ve WSS URL: wss://<host>/ocpp/1.6/<cpid> (saha teknisyeni Beny UI'a doğrudan kopyalayabilir).

Doküman


[PR-E4 — Tenant-wide RFID kart yönetimi] 2026-04-27 (PR #289)

Eklendi

  • Yeni endpoint family /api/ocpp/auth-list/tenant (5 endpoint):
    • GET /api/ocpp/auth-list/tenant — tenant geneli RFID kart listesi (charger_id IS NULL).
    • POST /api/ocpp/auth-list/tenant — yeni kart kaydı (OCPP_AUTH_LIST_DUPLICATE 409).
    • PATCH /api/ocpp/auth-list/tenant/{id_tag} — kart status/expiry/parent_id_tag güncelle.
    • DELETE /api/ocpp/auth-list/tenant/{id_tag} — kart sil.
    • POST /api/ocpp/auth-list/tenant/push — seçili charger'lara SendLocalList differential push (per-charger sonuç: accepted | rejected | offline | error).
  • Frontend: frontend/src/app/chargers/rfid-cards/page.tsx — tenant-wide kart yönetim sayfası.

Mimari notu

Charger-spesifik override /chargers/{id}/local-auth-list (Faz 3) kalıyor; tenant-wide kayıtlar yeni endpoint family ile yönetiliyor. Authorize handler _lookup_id_tag charger-spesifik → tenant-wide fallback yapıyor (mevcut davranış korundu, geriye dönük uyumlu).

Doküman


[PR-E3 — UI Refresh] 2026-04-27 (PR #288)

Değişti (frontend-only — backend kontratı değişmedi)

  • Charger detay header: Düzenle primary button + ⋯ Daha Fazla DropdownMenu altında RotatePassword ve Delete (yanlışlıkla tıklama koruması).
  • Commands tab kategorize: Şarj (RemoteStart/Stop/Unlock) / Yapılandırma (ChangeAvailability/ChangeConfig/SendLocalList) / Bakım (Reset/TriggerMessage/UpdateFirmware).
  • Liste sayfası: Server-side pagination + URL state (?limit=50&offset=100), status dot + canlı şarj süre/güç chip, list kartında inline edit kalem ikonu.
  • Overview ek alanlar: subregion, last_heartbeat, OCPP version, vendor/model.

Doküman


[PR-E2 — Region/Subregion charger entegrasyonu] 2026-04-27 (PR #287)

Eklendi

  • OcppChargerListItem.subregion_id + subregion_name field'ları (response schema).
  • Yeni query parametre: GET /api/ocpp/chargers?subregion_id={uuid} — belirli subregion'a atanmış charger'lar.

Değişti (frontend)

  • Charger form: region → subregion cascade select.
  • Liste sayfası: subregion filter chip.
  • Detay sayfası: subregion link (region listing'e route).

Doküman


[PR-E1 — Registry TTL refresh] 2026-04-26 (PR #286)

Değişti

  • backend/app/features/ocpp/zeus_charge_point.py — Heartbeat handler'da registry.refresh_ttl(cpid, heartbeat_interval) çağrısı.

Kök sebep (Reset 504 fix)

Redis owner-registry TTL heartbeat'lerle yenilenmiyordu → backend uptime arttıkça stale entry birikiyor → Reset komutu kayıp owner'a route oluyor → 30sn timeout → 504. Heartbeat handler artık her cihaz heartbeat'inde TTL uzatıyor (zombie temizlik + owner consistency).


[Migration 0039 — registration_status lowercase normalize] 2026-04-27 (PR #279)

Değişti

  • backend/alembic/versions/0039_*.pyOcppCharger.registration_status enum case mismatch fix (eski Pending/Accepted/Rejected → lowercase pending/accepted/rejected). Replay-safe: eski CHECK constraint drop + recreate pattern.

Kök sebep

schemas.py Literal "pending" | "accepted" | "rejected" (lowercase) bekliyordu; DB CHECK constraint ('Pending','Accepted','Rejected') (PascalCase) kabul ediyordu. BootNotification handler insert'i CHECK ihlali atıyordu.


[Hot-fix #2] 2026-04-26 — OCPP Charger Create HTTP 500 düzeltmesi (PR #277)

POST /api/ocpp/chargers endpoint'i production'da HTTP 500 dönüyordu — manuel charger oluşturma form submit'inde "Hata: Internal server error".

Eklendi

  • backend/alembic/versions/0038_chk_device_source_ocpp.pychk_device_source whitelist'ine 'ocpp' ekleyen migration. NOT VALID + VALIDATE pattern (production lock minimize). Defansif _constraint_exists() helper. Convention adı ck_devices_chk_device_source + legacy ad chk_device_source defansif drop.
  • backend/tests/integration/test_ocpp_charger_create.py — 10 REGRESSION test (RBAC, Pydantic, retry, multi-tenant). 0038 olmadan FAIL.
  • backend/tests/integration/test_schema_model_contract.py — 7 schema-model drift detection test. App device_source literal'leri ⊆ DB whitelist invariant'ı. CI'da gelecek incident'leri yakalar.

Değişti

  • .github/workflows/deploy.yml:244MIGRATION_TARGET="${MIGRATION_TARGET:-merge_sofar_mqtt}"head. Önceden 0037+ revisionlar otomatik deploy'a giremiyordu (workflow_dispatch override veya manuel SSH apply gerektiriyordu). Production drift kapatıldı. Detay: CI/CD Pipeline.
  • backend/app/core/database/models.py:360-364device_source yorumu güncellendi (whitelist + migration pattern + contract test referansı).

Kök sebep

0037_add_ocpp_tables migration'ı OCPP 1.6J şemasını eklerken devices.chk_device_source CHECK constraint'ine 'ocpp' eklemeyi atladı. Mevcut whitelist ('modbus','zigbee','virtual','manual'). Backend service.create_charger (backend/app/features/ocpp/service.py:305) device_source="ocpp" insert ediyor → CheckViolationError → endpoint 500.

Bilinen issue (follow-up)

alembic_version.version_num kolonu VARCHAR(32) — revision id 32 char altında olmalı. İlk denemede 0038_extend_chk_device_source_ocpp (34 char) StringDataRightTruncationError verdi → 0038_chk_device_source_ocpp (27 char) ile düzeltildi. Follow-up: alembic_version VARCHAR(64) migration ayrı PR.

Doküman


[Hot-fix #1] 2026-04-26 — Frontend WS refactor + Backend Origin guard (PR #276)

Production'da https://enerji.kepmark.com tüm sayfalarda "Application error: a client-side exception has occurred" gösteriyordu. Browser console: SecurityError: Failed to construct 'WebSocket': An insecure WebSocket connection may not be initiated from a page loaded over HTTPS.

Frontend (eklendi/değişti)

  • frontend/src/config/site.tsgetWsUrl() host-relative wss://${window.location.host}/ws üretiyor; NEXT_PUBLIC_WS_URL env override sadece protokol uyumluysa kabul (HTTPS sayfada ws:// env reddediliyor + console.warn).
  • frontend/src/lib/websocket/client.tsconnect() öncesi mixed-content guard (https: + ws://setState("error") early return) + new WebSocket() try/catch.
  • frontend/src/lib/websocket/types.tsWebSocketState tipine "error" eklendi.
  • frontend/src/lib/websocket/context.tsxWebSocketProvider try/catch + client = null fallback (subscribe no-op).
  • frontend/src/lib/websocket/WebSocketErrorBoundary.tsxYENİ class component. componentDidCatch ile sync exception yakalanırsa fallback children render.
  • frontend/src/components/providers.tsx — Boundary WebSocketProvider'ı sarıyor; fallback <>{children}</>.
  • frontend/DockerfileARG NEXT_PUBLIC_WS_URL ve ENV NEXT_PUBLIC_WS_URL satırları silindi.
  • docker-compose.yml — frontend service'in build.args + environment blokları temizlendi.

Backend (eklendi/değişti)

  • backend/app/core/websocket/security.pyYENİ. is_allowed_websocket_origin(origin, allowed_origins) helper. CSWSH (Cross-Site WebSocket Hijacking) koruması.
  • backend/app/core/websocket/router.py:78-101 — telemetry_websocket başında Origin header doğrulama. Uyumsuzsa await websocket.close(code=4003, reason="Forbidden origin") + structured log websocket_origin_rejected. Dev fallback: cors_origins_list boşsa Origin guard skip.
  • backend/app/core/config/settings.py:65-78cors_origins_list property: whitespace + trailing slash normalize.
  • docker-compose.yml:213CORS_ORIGINS raw IP origin'lerinden temizlendi.
  • TODO Faz 2 (router.py:97-101): Token query string yerine Sec-WebSocket-Protocol subprotocol veya HttpOnly+Secure+SameSite=Lax cookie'ye taşı.

Güvenlik

  • OCPP charger endpoint'leri (/ocpp/1.6/{cpid}, /ocpp/2.0.1/{cpid}) Origin guard'sız kaldı (RFC 6455 — native client için, browser değil). Detay: WebSocket Origin Guard.

CI/CD

  • 3 workflow'dan NEXT_PUBLIC_WS_URL build-arg + env injection kaldırıldı.
  • frontend-test job CI'a eklendi (vitest 43 test) — BLOCKING.
  • 17 backend test eklendi (test_ocpp_charger_create.py + test_schema_model_contract.py — fixture refactor için integration mark'a alındı).

Doküman


[Faz 7] 2026-04-25 — Production Deployment + Release Gate Handoff (PR #272, PR #273)

Eklendi (PR #272)

  • Nginx /ocpp/ location bloğu (TLS, WS upgrade, 4h read/send timeout, Authorization passthrough, ayrı ocpp.access log, set_real_ip_from private RFC1918, proxy_buffering off).
  • Docker Compose 11 OCPP env x 3 servis (backend + celery-worker + celery-beat) — Faz 4 Celery beat task'ları için passthrough zorunlu.
  • .env.example OCPP production placeholder satırları.
  • Prometheus rules: 5 alert (FleetOfflineDrop, AuthFailureSpike, CommandTimeoutHigh, BootstrapSlow, NoActiveSessionsAnomaly).
  • Grafana dashboard: 10 panel + auto-provisioning (grafana/provisioning/dashboards/ocpp.yml).
  • CI integration test infrastructure (Postgres + Redis service container'lar, alembic upgrade head step).

Eklendi (PR #273 — release gate handoff)

  • /healthz minimal endpoint (k8s convention liveness probe, OpenAPI gizli).
  • MQTT_ENABLED + CELERY_ALWAYS_EAGER startup bypass flag'leri (CI test için MQTT broker bağlantı denemesi yapılmaz).
  • zeus_ocpp_chargers_total Prometheus Gauge (fleet ratio alarm paydası, tenant_id label, cardinality kontrol — charger_id YOK).
  • Celery beat task refresh_charger_count (60sn, Gauge.clear() stale tenant cleanup).
  • 8 yeni test (backend/tests/features/ocpp/test_health_and_metrics.py).
  • CI integration test step ENABLE (if: false kaldırıldı).

Güvenlik

  • 15 maddelik code-audit-sentinel security audit (11 PASS / 2 PARTIAL / 1 FAIL bcrypt karar — canary öncesi accept).
  • Nginx Authorization passthrough log leak yok (default main log_format $http_authorization içermez — denetlendi).
  • Prometheus rules PII leak yok (alert annotations'ta charger_id/id_tag yok).
  • Gauge.clear() stale tenant cleanup — Prometheus cardinality kontrol altında.
  • Production-readiness skoru: 8.7/10 (PR #272 sonrası) → 9.5/10 (PR #273 sonrası).
  • Release gate: CONDITIONAL → APPROVED for canary.

Doküman


[Faz 6] 2026-04-25 — Session Geçmişi + Raporlar + SLD + SmartCharging Editor (PR #270)

Eklendi (Faz 6-A — Session History + Drilldown, ~1 080 satır)

  • app/chargers/[id]/sessions/page.tsx — sessions list + tarih/status/id_tag filter + offset pagination.
  • SessionTable.tsx — locale-aware tarih/süre, status badge, id_tag son-4 mask.
  • SessionDetailDrawer.tsx — shadcn Sheet + Recharts, measurand/downsample/aggregate selector, TanStack auto-refetch (active session 10sn), session-scoped CSV indirme.

Eklendi (Faz 6-B — Reports + CSV Export, ~900 satır)

  • Backend calculate_charging_report() — 4 group_by (day date_trunc, charger, id_tag, tariff LEFT JOIN); tenant filter; Wh→kWh ve sec→min dönüşümü SQL içinde.
  • GET /api/reports/charging-sessions (READ_CHARGER permission, ?format=csv variant).
  • Frontend ChargingReport.tsx — DateRangePicker + 2 Select; 4 SummaryCard; Recharts BarChart (energy + sessions çift Y-axis); detay tablo; CSV indirme.
  • Reports page'e yeni "EV Şarj" Tabs sekmesi.

Eklendi (Faz 6-C — SLD Charger Node + Live Energy Flow, ~570 satır)

  • Backend sld/service.pydevice_role='charger' device'lar OcppCharger join + active session subquery + Redis live power lookup.
  • Frontend OcppChargerNode.tsx — status renkli pulse (charging→mavi pulse, online→yeşil, offline→gri, faulted→kırmızı, unavailable→sarı), CPID/güç/enerji/connector chip, hover tooltip, click → /chargers/{id} route.
  • DeviceHierarchySldCanvasuseOcppLiveStream ile ocpp.meter|status|session_* aboneliği, setNodes + setEdges canlı güncelleme (200ms throttle).

Eklendi (Faz 6-D — SmartCharging Schedule Editor, ~1 120 satır)

  • ChargingProfileEditor.tsx — RHF + Zod + useFieldArray. Profile meta (connector_id, stack_level, purpose, kind, recurrency_kind, valid_from/to, transaction_id), schedule meta (charging_rate_unit A/W, duration, start_schedule, min_charging_rate), period listesi (yukarı/aşağı swap move(index, ±1)).
  • Cross-field Zod refine: Recurring → recurrency_kind zorunlu, TxProfile → transaction_id zorunlu, periods start_period strictly artan.
  • Idempotency-Key her submit'te crypto.randomUUID().
  • ChargingProfileList.tsx — shadcn AlertDialog onaylı silme (ClearChargingProfile push).
  • Charger detayda yeni "Şarj Profilleri" tab.

i18n

  • 130 yeni key TR + EN parite (sessions 53 + reports 34 + sld 11 + profile 32).

Doküman


[Faz 5] 2026-04-25 — Frontend MVP (PR #267)

Eklendi

  • Charger yönetim sayfaları (liste / detay / yeni).
  • TechnicianKitPanel + PasswordRevealDialog (saha kurulum kit'i tek seferde gösterim).
  • LiveMeterChart (Recharts sliding window — Power/Voltage/Current multi-series).
  • Beny OCPP Dashboard (device-dashboards registry — ZJ-Beny:BCP-AT2N-P).
  • 22 endpoint TanStack hook (useOcppChargers.ts).
  • useOcppLiveStream hook'u (chargerId + eventTypes filtreli WS subscribe).
  • 7 WebSocket event TypeScript discriminated union (ocpp-events.ts).
  • TR + EN i18n parite (locales/{tr,en}/ocpp.json).
  • A11y: role="status" / role="alert", keyboard navigation, WCAG AA contrast.

Güvenlik

  • Idempotency-Key her mutation'da crypto.randomUUID() ile otomatik üretilir.
  • PasswordRevealDialog ESC + outside-click engellenir, onay checkbox zorunlu.
  • id_tag son-4 mask UI'da tutarlı.
  • siteConfig.apiUrl/wsUrl zorunlu (CLAUDE.md §2 build-time bake tuzağı koruması).

Doküman


[Faz 4] 2026-04-25 — Real-time Events + Celery Jobs (PR #266)

Eklendi

  • 7 WebSocket event publish helper (backend/app/core/ocpp/events.py): ocpp.boot, ocpp.status, ocpp.meter, ocpp.session_started, ocpp.session_stopped, ocpp.command_result, ocpp.firmware_status.
  • 4 Celery beat task (backend/app/tasks/ocpp_tasks.py): heartbeat_watchdog (60sn), reconcile_charging_profiles (5dk), purge_command_log (günlük 02:00 UTC), sync_firmware_status (saatlik).
  • 10 publish çağrısı handler entegrasyonu (zeus_charge_point.py).
  • Prometheus metric: ocpp_ws_events_published_total{event_type, tenant_id}.

Hardening

  • Best-effort publish pattern (try/except + warning log; OCPP yanıtı kesilmez).
  • Lazy import (events.py ↔ service.py circular koruma).
  • DetachedInstanceError snapshot (StartTransaction/StopTransaction).
  • id_tag son-4 mask backend tarafında uygulandı (KVKK).
  • Tenant izolasyonu her event publish'te zorunlu.

MeterValues Downsample

  • Whitelist measurand'lar: Power.Active.Import, Voltage, Current.Import, Energy.Active.Import.Register, SoC.
  • Whitelist context'ler: Sample.Periodic, Transaction.Begin, Transaction.End.
  • Diğer örnekler DB'ye yazılır ama broadcast edilmez.

Doküman


[Faz 3] 2026-04-25 — REST API + Business Logic (PR #265)

Eklendi

  • 22 REST endpoint (backend/app/features/ocpp/router.py):
    • CRUD: chargers (5)
    • Komutlar: 8 (RemoteStart/Stop/Reset/Unlock/ChangeAvailability/ChangeConfig/TriggerMessage/UpdateFirmware)
    • Provisioning: rotate-password, provisioning-kit.pdf (2)
    • Sessions: list, detail, meter-samples, export.csv (4)
    • Charging Profiles: 4
    • Local Auth List: GET, PUT (2)
    • Reservations: POST, DELETE (2)
  • 33 Pydantic schema (backend/app/features/ocpp/schemas.py).
  • Idempotency middleware (backend/app/features/ocpp/idempotency.py):
    • Idempotency-Key header — 24h Redis cache.
    • Server-side dedup window — 10sn (charger_id, action, connector_id, id_tag) tuple.
  • ReportLab provisioning kit PDF üretici (password PDF'te yazmaz — sadece URL alanları + checklist).
  • 6 yeni Permission: READ_CHARGER, WRITE_CHARGER, CONTROL_CHARGER, CONFIGURE_CHARGER, OTA_CHARGER, MANAGE_CHARGER_SECRETS.
  • Canonical error envelope (error_code, message, details, request_id).
  • Alarm engine entegrasyonu (Faz 2 TODO closeup).

Karar Verilenler

  • URL versioning: yok (/api/ocpp/); breaking için ileride /api/v2/ocpp/.
  • HTTP mapping: Rejected→409, NotImplemented→501, Charger offline→503, Timeout→504.
  • Pagination: offset-based, default limit=50, max 200.
  • Time series downsample: PromQL-style (30s, 1m, 5m, 15m, 1h, 4h, 1d).
  • Sessions endpoint: flat (nested charger path yok).
  • Charging profiles: 4 ayrı endpoint (PUT yok — POST replace semantik).

Doküman


[Faz 1+2] 2026-04-25 — DB Şema + Protokol Çekirdeği (PR #264)

Eklendi

  • 8 yeni tablo:
    • ocpp_chargers, ocpp_connectors, ocpp_sessions, ocpp_meter_samples (TimescaleDB hypertable), ocpp_command_log, ocpp_auth_list_entries, ocpp_charging_profiles, ocpp_reservations.
  • WebSocket server endpoint'leri:
    • /ocpp/1.6/{cpid} (production).
    • /ocpp/2.0.1/{cpid} (stub — handshake 1011 close).
  • python-ocpp ZeusChargePoint16 handler (5 feature profile):
    • Core (BootNotification, Heartbeat, StatusNotification, Authorize, StartTransaction, StopTransaction, MeterValues, DataTransfer)
    • FirmwareManagement (UpdateFirmware, FirmwareStatusNotification, GetDiagnostics, DiagnosticsStatusNotification)
    • LocalAuthList (GetLocalListVersion, SendLocalList)
    • Reservation (ReserveNow, CancelReservation)
    • SmartCharging (SetChargingProfile, ClearChargingProfile, GetCompositeSchedule)
  • HTTP Basic Auth + bcrypt (cost ≥ 12) + Redis rate-limit (3 fail / 60sn) + XFF + fail-closed pattern.
  • Multi-worker WebSocket registry (Redis pub/sub command dispatcher).
  • ZJ-Beny vendor DataTransfer handler (DLBCloud, DLBStatus, PVStatus, MeterSnapshot).
  • Boot post-config (10 ChangeConfiguration anahtarı).
  • WebSocket close code reference: 1000, 1011, 4400, 4401, 4404, 4429, 4503.
  • Prometheus metric'leri: ocpp_messages_total, ocpp_command_latency_seconds, ocpp_command_timeouts_total, ocpp_active_chargers.

Doküman


Roadmap (önümüzdeki fazlar)

Faz 8 — Genişlemeler (planlanıyor)

  • OCPP 2.0.1 tam implementation — şu an stub (/ocpp/2.0.1/{cpid} 1011 close).
  • mTLS / Profile 3 — client certificate provisioning.
  • Charging Network Operator (CNO) Federation — eMSP/CPO roaming (OCPI 2.x bridge).
  • AI-driven Smart Charging — load forecast + price-aware schedule generation.
  • AsyncAPI 2.6 spec — frontend WS event kontrat dökümantasyonu.
  • Audit PARTIAL maddelerin kapatılması — Faz 4 Celery log path id_tag mask filter, Redis pool exhaustion alert.

Versiyonlama Politikası

OCPP entegrasyon contract'ı Faz 3 — REST API §14 kararlarına bağlıdır:

  • Non-breaking ekler: Yeni endpoint, yeni opsiyonel field, yeni event type, yeni close code (4500+), yeni error code → minor version.
  • Breaking değişiklikler: URL format, auth header, CPID regex, mevcut close code anlamı, mevcut field rename/silme, mevcut enum value değişimi → major version + ≥30 gün deprecation notice + Deprecation header.

Tüm breaking değişiklikler bu changelog'ta DEPRECATED etiketiyle önceden duyurulur.