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.


Hot-fix Entries (Production Incident Response)

[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.