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ü:Otherkodu gelen iki seans da normal bitmişti; tek gerçek kesintide gelenPowerLossise ş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).PowerLossiç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) ve91,35 ₺. İngilizce bildirimlerde18.4 kWh/91.35 TRYkorunur. - 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övdeis_charge_watcheralanı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.defaultsatı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:- 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),
- "Bu kart numarası için filo geneli bir yetki kaydı var" hatasını çözmek (çakışan kaydın kaldırılabildiği tek ekran),
- 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:
| İşlem | Koruma çalışır mı? |
|---|---|
| Yeni kart (seçili cihazlar, aktif) | Evet |
| Kapsam değiştirme / daraltma | Evet |
| Kartı yeniden aktifleştirme | Evet |
| "Engelli" / "Kayıp" yapma | Hayır — her zaman geçer |
| Geçerlilik süresini kısaltma | Hayır — her zaman geçer |
| Kartı silme | Hayı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.
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_statusalanı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.scopealanı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,5551234567gibi hangi yazımı girerseniz girin kayıt+905551234567olarak 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 haneli5…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(.xlsve 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ı
**** **** 1234biç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-toaststore kaldırıldı; tüm bildirimler uygulamadaki tek renderer olan Sonner üzerinden veriliyor (bkz. Frontend Genel Bakış — Bildirim: Sonner). Görsel eşleme: eskidestructive→toast.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-1565—RemoteStartRequest:id_tag: CiString20 | None(opsiyonel, varsayılanNone) — geri uyumluluk.parent_id_tag: CiString20 | None(opsiyonel, varsayılanNone) — Issue #502.@model_validator(mode="after") _mutex_id_or_parent— XOR: tam olarak biri verilmelidir, aksi halde 422VALIDATION_ERROR.model_config = ConfigDict(json_schema_extra={"examples": [...]})— OpenAPI Swagger UI'da iki geçerli payload formu (parent_id_tagveid_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 logocpp.remote_start.parent_resolution(PII mask). - 2+ sonuç →
HTTPException(500, error_code="OCPP_PARENT_TAG_DATA_INTEGRITY")+ audit logocpp.remote_start.parent_resolution.invariant_violation+ structured log error. - PII koruma: log/audit payload'larda
parent_id_tagveresolved_id_tagmask_id_tag()(son 4 karakter + yıldız) ile maskelenir.
- Filtre:
backend/app/features/ocpp/commands.py:772-829—remote_start():payload.parent_id_tag is not Noneise_resolve_parent_to_id_tagçağrılır.- Aksi halde
payload.id_tagdoğ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-680—remote_start_endpoint:responsesdict 404/422/500 içincontent.application/json.examplesiç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).
- 404:
- 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.ts—RemoteStartRequesttipi:id_tagveparent_id_tagher ikisi opsiyonel (TypeScript tarafında XOR runtime'da backend mutex enforce eder; frontend defaultparent_id_tagkullanı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_entriestablosuna 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 iseRuntimeErrorraise 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
0055idi; PR #5040055_cleanup_double_prefix_ocpp_heartbeataynı parent0054'ten dallandığı için single-head ihlali doğurdu; bu PR0056'ya alındı,down_revision = "0055_cleanup_double_prefix_ocpp_heartbeat"zincire takıldı.
Yeni hata kodları
| Kod | HTTP | Tetiklenme | Eylem |
|---|---|---|---|
OCPP_PARENT_TAG_NO_ACTIVE_CARD | 404 | parent_id_tag için tenant altında aktif kart yok | Operator → "RFID Kartları" sayfasından kart ekle veya status='Accepted'/expiry_date güncelle |
OCPP_PARENT_TAG_DATA_INTEGRITY | 500 | 1: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 dolu | Client → 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'tir —
WHERE parent_id_tag IS NOT NULL AND charger_id IS NULLkapsamı 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_tagpayload formu çalışmaya devam eder. Yeniparent_id_tagformu 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/(veyadocs-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.jsoperatör kılavuzu kategorisi eklenir. - EN-mode browser smoke (Mayıs 2026 i18n bundle olayı sonrası zorunlu): Deploy sonrası
?lang=enile 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 Taxonomy —
OCPP_PARENT_TAG_NO_ACTIVE_CARDveOCPP_PARENT_TAG_DATA_INTEGRITYeklendi. - 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:
- Bug 1 — Double prefix
alarm_type:engine.record_ocpp_incidentcode.startswith("ocpp_")orijinal string'i (UPPER) kontrol ediyordu,elsebranchf"ocpp_{code.lower()}"ile prefix eklerken"OCPP_HEARTBEAT_TIMEOUT"→"ocpp_ocpp_heartbeat_timeout"üretiyordu. TümOCPP_*errorCode'ları için aynı bug (GroundFailure, OverCurrentFailure, vb. eğer tetiklenmiş olsaydı). - Bug 2 — Close path eksik: Legacy
heartbeat_watchdogCelery task ile açılan alarmlar için hiçbir reconnect senaryosunda close çağrısı yoktu (resolve_charger_offline_if_onlinesadeceocpp_charger_offlinetipini 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.py—code_lower = code.lower()lokal var üzerindenstartswithkontrolü. TümOCPP_*(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_asyncsilindi).backend/app/core/celery/celery_config.py—ocpp_heartbeat_watchdogbeat schedule entry kaldırıldı.- Migration 0055 (
cleanup_double_prefix_ocpp_heartbeat) — mevcut açıkocpp_ocpp_heartbeat_timeoutalarmları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ı)
- Mevcut açık
e22f12ab-...alarmstatus='closed', AlarmEvent('closed') yazıldı. - Celery beat schedule artık
heartbeat_watchdogtetiklemez; PR-A1/A2 domain task'ları aktif. - Yeni OCPP error code path'ı (örn.
OCPP_GroundFailuretest scenario) artık single-prefixocpp_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-184—DeviceResponse.subregion_id: UUID | None(opsiyonel, defaultNone). Description'da semantik netleştirildi: OCPP charger içinOcppCharger.subregion_idLEFT JOIN ile doldurulur; gateway tabanlı cihazlarda şu anNonedöner (gateway resolution bu endpoint'te uygulanmadı).backend/app/features/devices/service.py:244-310—get_devices()veget_device()OcppCharger'a LEFT OUTER JOIN; charger olmayan cihazlardaNonekalır. ORMDevicemodeline kolon eklemeden runtime attribute olarak set edilir → DB migration yok.
Doküman (bu PR)
docs-site/docs/api-reference/endpoints.md—GET /api/devicesörnek response'asubregion_idalanı 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_idPydantic'teOptional[UUID]. - DB schema değişmedi —
devicestablosuna 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çindekiDeviceinterface'inesubregion_id?: string | nulleklenecek. 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/MQTTGatewayile gateway.subregion_id fallback. Şu an açık değil; gerekirse ayrı PR.
Doküman
- API Endpoint Listesi → Devices →
subregion_idalanı - Cross-reference: PR-E2 — Region/Subregion charger entegrasyonu (OcppChargerListItem.subregion_id; bu kez
DeviceResponseortak yüzeyine taşındı).
[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.phasekolonu:nullable=True→primary_key=True, nullable=False, server_default=''. NULLphasesatırlar migration içinde''sentinel'ine backfill edildi. ORM modelbackend/app/core/database/models.py:2532-2537ile hizalandı.- Sentinel
''semantiği: OCPP spec'indephaseopsiyonel; vendor atlarsa backend''literal'i yazar (DB NOT NULL koruması). Backend response layer (backend/app/features/ocpp/service.py:982raw path +:1023downsample path) bu sentinel'i frontend'eNone/nullolarak 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. IntegrityErrorayrı dallanma —ocpp_meter_values_persist_conflictwarning event'i (vendor replay / NTP drift sinyali).- Generic
Exception→ocpp_meter_values_persist_failederror 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-samplesresponse'undaphasealanı tipistring | nullaynı (PydanticMeterSampleResponse.phase: str | None). - Frontend
LiveMeterChartpreferL1 ['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
- OCPP MeterValues Persist/Broadcast Failure Runbook
- Structured logging spec:
docs/observability/structured-logging.md - Prometheus alert dosyası:
prometheus/rules/ocpp_meter_values.yml - Grafana dashboard:
grafana/dashboards/ocpp-meter-values.json
[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.ts—extractErrorMessage(payload)helper. Envelopemessagefield'ı varsa onu döner; yoksaerror_codeveya HTTP status fallback. PII guard:id_tag/password/tokengibi alanlardetailsiç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/telemetryhandler — catch-allexcept Exceptionöncesiexcept WebSocketDisconnectpropagate.await ws.close(code=1011, reason="...")gerçek hatalarda gönderiliyor; client1006yerine1011gö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-ocppTypeError→ outer retry loop genericexcept→ 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) — aktiflisten_commandstask sayısı.ocpp_dispatcher_loop_errors_total{error_kind}(Counter) —error_kindwhitelist (5 değer:redis_connection,os_error,timeout,cancelled,unknown).
- Alert rules (3 yeni): dispatcher restart loop, listening task yokluk anomalisi,
cancellederror_kind sızıntısı (BUG sinyali). - Grafana dashboard panel'leri:
OCPP Charger Fleet > PR-E6 Dispatcher Canaryrow. - 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.py—pubsub.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— Reservationstatusquery param PydanticLiteral["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ıştauseStatereset (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,statusfilteractive|used|cancelled|expired, defaultactive).
- Frontend:
CommandLogsPanel.tsx(yeni component, charger detayda son 20 komut audit trail),CommandResultAlert.tsx(inline result block), 6 dialog'da inline result,useCommandResultStatehook. - Provisioning kit PDF (PR-E5 B): PDF'te yeni satırlar —
TLS: ENABLEDveWSS 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_DUPLICATE409).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'laraSendLocalListdifferential 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üzenleprimary button +⋯ Daha FazlaDropdownMenu altındaRotatePasswordveDelete(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_namefield'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'daregistry.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_*.py—OcppCharger.registration_statusenum case mismatch fix (eskiPending/Accepted/Rejected→ lowercasepending/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.py—chk_device_sourcewhitelist'ine'ocpp'ekleyen migration. NOT VALID + VALIDATE pattern (production lock minimize). Defansif_constraint_exists()helper. Convention adıck_devices_chk_device_source+ legacy adchk_device_sourcedefansif 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:244—MIGRATION_TARGET="${MIGRATION_TARGET:-merge_sofar_mqtt}"→head. Önceden 0037+ revisionlar otomatik deploy'a giremiyordu (workflow_dispatchoverride veya manuel SSH apply gerektiriyordu). Production drift kapatıldı. Detay: CI/CD Pipeline.backend/app/core/database/models.py:360-364—device_sourceyorumu 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.ts—getWsUrl()host-relativewss://${window.location.host}/wsüretiyor;NEXT_PUBLIC_WS_URLenv override sadece protokol uyumluysa kabul (HTTPS sayfadaws://env reddediliyor + console.warn).frontend/src/lib/websocket/client.ts—connect()öncesi mixed-content guard (https:+ws://→setState("error")early return) +new WebSocket()try/catch.frontend/src/lib/websocket/types.ts—WebSocketStatetipine"error"eklendi.frontend/src/lib/websocket/context.tsx—WebSocketProvidertry/catch+client = nullfallback (subscribe no-op).frontend/src/lib/websocket/WebSocketErrorBoundary.tsx— YENİ class component.componentDidCatchile sync exception yakalanırsa fallback children render.frontend/src/components/providers.tsx— BoundaryWebSocketProvider'ı sarıyor; fallback<>{children}</>.frontend/Dockerfile—ARG NEXT_PUBLIC_WS_URLveENV NEXT_PUBLIC_WS_URLsatırları silindi.docker-compose.yml— frontend service'inbuild.args+environmentblokları temizlendi.
Backend (eklendi/değişti)
backend/app/core/websocket/security.py— YENİ.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şındaOriginheader doğrulama. Uyumsuzsaawait websocket.close(code=4003, reason="Forbidden origin")+ structured logwebsocket_origin_rejected. Dev fallback:cors_origins_listboşsa Origin guard skip.backend/app/core/config/settings.py:65-78—cors_origins_listproperty: whitespace + trailing slash normalize.docker-compose.yml:213—CORS_ORIGINSraw IP origin'lerinden temizlendi.- TODO Faz 2 (router.py:97-101): Token query string yerine
Sec-WebSocket-Protocolsubprotocol 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_URLbuild-arg + env injection kaldırıldı. frontend-testjob 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.accesslog,set_real_ip_fromprivate 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.exampleOCPP 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 headstep).
Eklendi (PR #273 — release gate handoff)
/healthzminimal endpoint (k8s convention liveness probe, OpenAPI gizli).MQTT_ENABLED+CELERY_ALWAYS_EAGERstartup bypass flag'leri (CI test için MQTT broker bağlantı denemesi yapılmaz).zeus_ocpp_chargers_totalPrometheus Gauge (fleet ratio alarm paydası,tenant_idlabel, 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: falsekaldırıldı).
Güvenlik
- 15 maddelik
code-audit-sentinelsecurity audit (11 PASS / 2 PARTIAL / 1 FAIL bcrypt karar — canary öncesi accept). - Nginx Authorization passthrough log leak yok (default
mainlog_format$http_authorizationiç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— shadcnSheet+ 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 (daydate_trunc,charger,id_tag,tariffLEFT JOIN); tenant filter; Wh→kWh ve sec→min dönüşümü SQL içinde. GET /api/reports/charging-sessions(READ_CHARGERpermission,?format=csvvariant).- 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.py—device_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. DeviceHierarchySldCanvas—useOcppLiveStreamileocpp.meter|status|session_*aboneliği,setNodes+setEdgescanlı 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ğı swapmove(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— shadcnAlertDialogonaylı 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). useOcppLiveStreamhook'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/wsUrlzorunlu (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)
- CRUD:
- 33 Pydantic schema (
backend/app/features/ocpp/schemas.py). - Idempotency middleware (
backend/app/features/ocpp/idempotency.py):Idempotency-Keyheader — 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, max200. - 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 — handshake1011close).
- python-ocpp
ZeusChargePoint16handler (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 +
Deprecationheader.
Tüm breaking değişiklikler bu changelog'ta DEPRECATED etiketiyle önceden duyurulur.