Faz 6 — Advanced Features
PR #270 ile geldi. Frontend MVP'nin (Faz 5) üzerine session drilldown, raporlama, SLD canlı entegrasyonu ve SmartCharging schedule editor eklendi. Toplam ~4 305 satır kod, 4 paralel agent (A/B/C/D), 130 yeni i18n key (TR + EN parite).
Durum: Tamamlandı (PR #270 merged, prod canary öncesi).
1. Genel Bakış
| Bileşen | Lines | Sorumluluk |
|---|---|---|
| Faz 6-A — Session History + Drilldown | ~1 080 | Sessions liste + filter + Recharts drawer |
| Faz 6-B — Reports + CSV Export | ~900 | Backend calculate_charging_report + frontend BarChart |
| Faz 6-C — SLD Charger Node | ~570 | React Flow custom node + WS canlı update |
| Faz 6-D — SmartCharging Editor | ~1 120 | RHF + Zod cross-field refine, period swap |
| i18n parite | 130 key | sessions 53 + reports 34 + sld 11 + profile 32 |
Faz 6 tüm bileşenlerinde:
siteConfig.apiUrl/wsUrlzorunlu (CLAUDE.md §2 build-time bake tuzağı koruması).- Idempotency-Key her mutation'da
crypto.randomUUID(). - Tenant scope her query'de zorunlu.
- TR + EN i18n parite (eksik key build'te fail).
2. Faz 6-A — Session History + Drilldown
2.1 Sayfa: app/chargers/[id]/sessions/page.tsx
Bir charger'a özel session geçmişi:
- Filter: tarih aralığı (DateRangePicker), status (active/completed/aborted), id_tag son-4 mask.
- Pagination: offset-based, default
limit=50, max200(Faz 3 contract). - Routing: session satırına click → drawer açılır (URL query param
session_idile shareable).
2.2 Component: SessionTable.tsx
| Kolon | Detay |
|---|---|
| Başlangıç / Bitiş | Locale-aware (tr-TR / en-US) tarih + saat |
| Süre | duration_seconds → H:mm:ss formatlı |
| id_tag | Son-4 mask (KVKK; UI hiçbir zaman plaintext göstermez) |
| Connector | Connector ID + status pill |
| Enerji (kWh) | Wh→kWh dönüşüm UI'da; backend Wh dönmez (Faz 6-B) |
| Status | Badge: completed (yeşil), active (mavi pulse), aborted (kırmızı) |
2.3 Component: SessionDetailDrawer.tsx
shadcn Sheet + Recharts entegrasyonu:
- Selectors: measurand (Power.Active.Import / Voltage / Current.Import / Energy.Active.Import.Register / SoC), downsample (
30s/1m/5m/15m/1h/4h/1d), aggregate (avg/min/max/p95). - TanStack auto-refetch: active session ise
refetchInterval=10s; completed ise stale. - CSV Export: session-scoped — drawer içinden
GET /api/ocpp/sessions/{id}/export.csv(Faz 3 endpoint). - Performans: Recharts
<LineChart>+React.memo, sample array referans-stabil.
3. Faz 6-B — Reports + CSV Export
3.1 Backend: calculate_charging_report()
backend/app/features/reports/service.py içinde — 4 group_by stratejisi:
| group_by | Detay |
|---|---|
day | date_trunc('day', started_at) — günlük breakdown |
charger | ocpp_charger_id GROUP BY — charger ranking |
id_tag | id_tag (son-4 mask UI'a kadar taşınır) — kullanıcı ranking |
tariff | LEFT JOIN tariffs ON sessions.tariff_id — tariff eşlemesi (NULL'lar "tariff_unknown" bucket'ında) |
Önemli karar:
- Wh → kWh ve sec → min dönüşümü SQL içinde yapılır (
(SUM(energy_wh) / 1000.0) AS energy_kwh,(SUM(duration_sec) / 60.0) AS duration_min). Frontend dönüşüm yapmaz; precision drift riskini yok eder. - Tenant filter:
WHERE charger.tenant_id = :tenant_idzorunlu. - Date range filter:
started_at BETWEEN :from AND :to(UTC ISO-8601).
3.2 Endpoint: GET /api/reports/charging-sessions
- Permission:
READ_CHARGER(Faz 3 RBAC matrix). - Query params:
from(ISO-8601),to(ISO-8601),group_by(day/charger/id_tag/tariff),charger_id(opsiyonel filter). - Response: rows + summary aggregates (total_energy_kwh, total_sessions, total_duration_min, avg_session_kwh).
- CSV variant:
?format=csvquery param →Content-Type: text/csv; charset=utf-8.
3.3 Frontend: ChargingReport.tsx
app/reports/page.tsx içine yeni "EV Şarj" <Tabs> sekmesi olarak yerleşir.
- Filter card: DateRangePicker + 2
<Select>(group_by, charger filter). - 4 SummaryCard: toplam enerji, toplam oturum, toplam süre, ortalama oturum kWh.
- Recharts BarChart: çift Y-axis (sol: enerji kWh, sağ: oturum sayısı). Bar + Line composed.
- Detay tablo: group_by'a göre dinamik kolon (
day/charger/id_tag/tariff). - CSV indirme: "İndir" butonu
?format=csvquery'sini çağırır,Content-Dispositionfilename respect edilir.
4. Faz 6-C — SLD Charger Node + Live Energy Flow
4.1 Backend: sld/service.py
device_role='charger' olan device'lar SLD payload'ında özel olarak işlenir:
JOIN ocpp_chargers ON device.external_id = ocpp_charger.idile metadata (vendor, model, connector_count) eklenir.- Active session bilgisi: subquery
WHERE charger_id = ? AND status = 'active' LIMIT 1. - Live power lookup: Redis
GET ocpp:live_power:{charger_id}(Faz 4 meter publisher) — son 30sn'lik kW snapshot. NULL ise0.0. - Tenant scope: cross-tenant SLD probe → 404.
4.2 Frontend: OcppChargerNode.tsx
React Flow custom node:
| Status | Görünüm |
|---|---|
charging | Mavi pulse animation (charging→aktif enerji akışı) |
online (idle) | Yeşil sabit |
offline | Gri sabit |
faulted | Kırmızı pulse |
unavailable | Sarı sabit |
İçerik:
- CPID kısaltılmış (8 char +
...). - Güç chip (kW, 1 ondalık).
- Toplam enerji chip (kWh, lifetime).
- Connector chip (occupied/total).
- Hover tooltip: vendor, model, firmware version, son boot zamanı.
- Click →
/chargers/{id}route'a yönlendirir (Next.jsuseRouter).
4.3 SLD Canvas Live Update
DeviceHierarchySldCanvas Faz 6-C için genişletildi:
useOcppLiveStream({ eventTypes: ['ocpp.meter', 'ocpp.status', 'ocpp.session_started', 'ocpp.session_stopped'] })ile event subscribe.- Event geldikçe
setNodes(charger node data güncelle) +setEdges(charger→bus edge animation hızı kW'a göre değişir). - Performans: event throttle 200ms; üst üste birden fazla meter event olursa yalnızca son value uygulanır.
- Subscription cleanup: component unmount'ta WS unsubscribe + memo'lu callback'ler dispose edilir.
5. Faz 6-D — SmartCharging Schedule Editor
5.1 Component: ChargingProfileEditor.tsx
React Hook Form (useForm + useFieldArray) + Zod cross-field refine:
Profile metadata alanları:
connector_id(number, 0 = whole charger)stack_level(number, 0-127)purpose(ChargePointMaxProfile/TxDefaultProfile/TxProfile)kind(Absolute/Recurring/Relative)recurrency_kind(Daily/Weekly) — yalnızcakind=Recurring'devalid_from/valid_to(ISO-8601 timestamp)transaction_id— yalnızcapurpose=TxProfile'da
Schedule metadata:
charging_rate_unit(A/W)duration(saniye)start_schedule(ISO-8601)min_charging_rate(number)
Period listesi (useFieldArray):
- Yukarı/aşağı swap:
move(index, index ± 1). - Add/remove butonları.
- Her period:
start_period(saniye),limit(rate),number_phases(1/3).
Cross-field Zod refine:
.refine((data) => {
if (data.kind === 'Recurring' && !data.recurrency_kind) {
return false; // recurrency_kind zorunlu
}
if (data.purpose === 'TxProfile' && !data.transaction_id) {
return false; // transaction_id zorunlu
}
// periods start_period artan
for (let i = 1; i < data.schedule.periods.length; i++) {
if (data.schedule.periods[i].start_period <= data.schedule.periods[i-1].start_period) {
return false;
}
}
return true;
})
Idempotency-Key: her submit'te crypto.randomUUID() üretilir; React Query retry'ları aynı key ile gönderir (dedup window 24h).
5.2 Component: ChargingProfileList.tsx
- Charger detay sayfasında yeni "Şarj Profilleri" tab.
- Liste: profile_id, purpose, kind, valid_from/to, period count, push status.
- Silme: shadcn
AlertDialogonaylı — kullanıcı "ClearChargingProfile push edilecek, geri alınamaz" uyarısını onaylar →DELETE /api/ocpp/chargers/{id}/charging-profiles/{profile_id}.
5.3 Permission Gating
WRITE_CHARGER: profile create/update.CONFIGURE_CHARGER: period editor erişimi.- Eksik permission'da editor read-only mode (form disabled + "Yetkiniz yok" uyarısı).
6. i18n Parite
130 yeni key, TR + EN parite zorunlu (eksik key build'te fail):
| Modül | Key sayısı |
|---|---|
| Sessions (drawer + tablo + filter) | 53 |
| Reports (filter + summary + tablo) | 34 |
| SLD (charger node + tooltip) | 11 |
| Profile editor (form + AlertDialog) | 32 |
Locale dosyaları:
frontend/src/locales/tr/ocpp.jsonfrontend/src/locales/en/ocpp.json
7. Faz 6 Çıktı Listesi (tamamlandı — PR #270)
| # | İş | Dosya |
|---|---|---|
| 1 | Sessions sayfası + tablo | app/chargers/[id]/sessions/page.tsx, components/chargers/SessionTable.tsx |
| 2 | Session drilldown drawer | components/chargers/SessionDetailDrawer.tsx |
| 3 | Reports backend service | backend/app/features/reports/service.py (calculate_charging_report) |
| 4 | Reports endpoint | backend/app/features/reports/router.py (GET /api/reports/charging-sessions) |
| 5 | Charging report UI | components/reports/ChargingReport.tsx |
| 6 | SLD backend join | backend/app/features/sld/service.py |
| 7 | SLD charger node | components/sld/OcppChargerNode.tsx |
| 8 | SLD canvas live | components/sld/DeviceHierarchySldCanvas.tsx |
| 9 | SmartCharging editor | components/chargers/ChargingProfileEditor.tsx |
| 10 | Profile list | components/chargers/ChargingProfileList.tsx |
| 11 | i18n | locales/{tr,en}/ocpp.json (130 key) |
8. Sonraki Adımlar
Faz 7 — Production Deployment:
- Nginx
/ocpp/location bloğu (TLS, WS upgrade, 4h timeout). - Docker Compose 11 OCPP env x 3 servis (backend + celery-worker + celery-beat).
- Prometheus 5 alert + Grafana 10 panel.
- CI integration test (mock charger e2e).
- Security audit + release gate.
Detaylar: Faz 7 — Production Deployment.