Plan #7107 — Kompletny mechanizm eskalacji anomalii floty (Atrax)
Issue: radieu/p24-infra#7107
Type: New Feature (design/plan)
Status: design — dla /review-plan (użytkownik zażądał 4 iteracji, nie domyślnych 3),
następnie implementacja rozbita na PR-1…PR-4 + cross-repo issue w et-operational-platform.
Repos: radieu/p24-infra (schemat + RLS + digest Discord + kontrakt) oraz
radieu/et-operational-platform (panel UI) — wspólny projekt Supabase mwkqmgadqnkkihjdeqsi.
Ten dokument jest trwałą wersją planu z ciała issue #7107. Zakres (#0–#10) to treść JEDNEGO issue projektowego; realizacja jest podzielona na samodzielnie weryfikowalne PR-y (patrz §Sekwencja wykonania). PR persystujący ten plan nie implementuje żadnego z etapów — jest artefaktem projektowym do przeglądu.
Kontekst
W poprzednich sesjach zbudowano 4 niezależne systemy detekcji anomalii floty na danych Atrax:
| System | Tabela detekcji | Dzisiejsze raportowanie |
|---|---|---|
| Kontrola spalania + fraud (#6138/#6194/#6710) | p24_route_consumption_anomalies, p24_gps_daily_stats_native | Lane E → dev_r_alert_events → alert-triage-batch → GH issue p24-infra-nc-alert |
| Ecodriving per-trasa (#6841/#6848/#7020) | p24_ecodriving_anomalies | brak — tylko zapis do tabeli |
| Vehicle health: silnik/napięcie/AdBlue (#7043/#7044/#7046) | p24_vehicle_health_anomalies | brak — tylko zapis do tabeli |
| Tachograf compliance (#7045) | p24_tachograph_violations | bespoke Discord embed + bezpośredni gh issue create |
Decyzja użytkownika (nadpisuje pierwszą wersję tego planu): żaden z 4 systemów nie eskaluje
przez GitHub Issues. dev_r_alert_events/alert-triage-batch/p24-infra-nc-alert to mechanizm dla
infra/ops (serwery, kontenery) — anomalie floty mają innego odbiorcę (fleet managerowie)
i inny kanał: zapis do bazy + notyfikacja Discord, a rzeczywiste zarządzanie eskalacją
(przypisanie, potwierdzenie, rozwiązanie) odbywa się w panelu eskalacji w et-operational-platform
— osobnej aplikacji/repo, nie tutaj.
Zakres: wszystkie 4 systemy migrują do nowego mechanizmu (w tym już działający fuel/fraud —
zdejmujemy go z dev_r_alert_events).
Architektura
0. BLOKUJĄCY Etap 0 — sprawdzić, czy incidents (et-operational-platform) już to robi, zanim tworzymy nową tabelę
Plan-review na żądanie użytkownika (“nie generować duplikacji względem już istniejących tabel w
et-operational-platform”) wykrył: we współdzielonym projekcie Supabase (mwkqmgadqnkkihjdeqsi)
istnieje tabela incidents (bez prefiksu p24_), opisana w docs/supabase-operations.md:45
jako “Business incident tracking” — własność et-operational-platform, odrębna od
dev_r_incidents (to są infra-incydenty p24-infra, potwierdzone jako niezwiązane). Jej
kolumny/schemat nie są nigdzie zadokumentowane w tym repo — CREATE TABLE nie istnieje w
supabase/migrations/ tego repo (bo to nie jest tabela p24-infra). Nie wiadomo, czy to już jest
ogólny mechanizm zgłoszeń biznesowych, który mógł (powinien?) być rozszerzony o anomalie floty,
zamiast budować drugą, równoległą tabelę o podobnym celu.
To musi się rozstrzygnąć PRZED napisaniem migracji p24_fleet_anomaly_escalations — nie
równolegle, nie “do zweryfikowania w designie” jak inne otwarte punkty niżej, bo zmienia całą
przesłankę architektury:
- Sprawdzić schemat
incidents(read-onlyinformation_schemaquery na współdzielonym projekcie). Nie wymaga nowego sekretu — uzasadnienie todocs/supabase-operations.md:31(service_role= “Full access, bypasses RLS”) oraz linia 102 tego samego dokumentu, gdzieincidentsjest już wprost na liście grantówgrafana_readonly(GRANT SELECT). Sprawdzić kolumny, czy ma już coś w stylusource/type/status/assigned_to, czy et-operational-platform faktycznie pisze do niej z kodu aplikacji. - Jeśli
incidentsjest generyczna i rozszerzalna → rozszerzyć ją (dodać nullable kolumny fleet-specific:vehicle_id,driver_id,attribution,requires_human_review,severityjeśli jej nie ma) zamiast tworzyćp24_fleet_anomaly_escalationsod zera. To prawdopodobnie oznacza, że et-operational-platform już ma UI do zarządzania incydentami — panel z #7 mógłby być rozszerzeniem istniejącego ekranu. - Jeśli
incidentsjest za wąska/niezwiązana → zostać przy nowej, dedykowanej tabeli z jawnie zapisanym uzasadnieniem. - Ten check wykonuje
sys-admin/infra-task(read-only, żadnych nowych credentiali) jako pierwszy krok fazy design zdalnego workera — PR-1 nie może ruszyć, zanim to się nie rozstrzygnie.
1. Nowa, wspólna tabela p24_fleet_anomaly_escalations (aktualne TYLKO jeśli Etap 0 potwierdzi, że incidents się nie nadaje)
Jedna tabela, wspólna dla wszystkich 4 systemów (dyskryminator source_system):
| Kolumna | Typ | Uwagi |
|---|---|---|
id | uuid | PK |
source_system | text | 'fuel_consumption' | 'ecodriving' | 'vehicle_health' | 'tachograph' |
anomaly_type | text | np. ecodriving_deviation, engine_temperature_deviation, extended_driving |
vehicle_id | text, nullable | wymagane również dla tachografu (patrz uwaga niżej); nullable tylko gdy naprawdę nieustalalne |
driver_id / driver_name | text, nullable | brak dla vehicle_health |
occurred_at | timestamptz, nullable | dokładny czas zdarzenia (nie tylko occurred_date) |
attribution | text, nullable | 'vehicle_route' | 'driver_pattern' — niesie flagę EU AI Act (patrz #4) |
severity | text | znormalizowane do 'warning' | 'critical' |
observed_value, norm_median, modified_z | numeric, nullable | tylko systemy statystyczne |
detail | jsonb | pełny, systemowo-specyficzny payload |
occurred_date | date | stat_date/trip_date źródłowej anomalii |
status | text | 'open' | 'acknowledged' | 'resolved' | 'dismissed' — zarządzane przez ludzi w panelu, nigdy automatycznie |
requires_human_review | boolean | true gdy attribution='driver_pattern' — patrz #4 |
assigned_to, resolution_note, resolved_at | nullable | wypełniane przez panel |
created_at, updated_at | timestamptz |
Uwaga — tachograf potrzebuje pojazdu i konkretnego czasu, nie tylko dnia.
p24_atrax_drivers_daily (dzisiejsze źródło tachografu) jest kluczowane tylko (driver_id, stat_date) — nie ma tam vehicle_id ani godziny, tylko dobowe liczniki. Wiersz eskalacji
tachografu musi zawsze nieść, w jakim pojeździe i o której godzinie doszło do naruszenia:
- Etap 1 (przed implementacją migracji tachografu): sprawdzić surowy payload
/api/rtdrivers— czy Atrax w ogóle podajevehicleId/timestamp per naruszenie, czego dziś nie mapujemy dop24_atrax_drivers_daily. - Fallback, jeśli Atrax nie daje tego per-naruszenie: dociągnąć
vehicle_id/occurred_atprzez joindriver_id/first_name+last_namezp24_atrax_drivers_dailydodriver_namewp24_ride_eventsdla tego samegostat_date, biorąc np. najdłuższy/pierwszy segment DRIVING tego dnia jako reprezentatywny czas i pojazd. Ryzyko, niezweryfikowane:driver_namewp24_ride_eventsmoże mieć problem z kodowaniem polskich znaków i nie ma wspólnego stabilnego ID między tabelami — join po nazwisku jest niepewny i wymaga jawnego flagowania niskiej pewności (vehicle_id_confidence='inferred'vs'direct') albo pozostawieniaNULLz komunikatem wdetail. - Nie implementować na ślepo — jeśli Etap 1 wykaże, że żadna z dwóch ścieżek nie daje wiarygodnego wyniku, udokumentować to jako znane ograniczenie.
Idempotencja. Zweryfikowano w kodzie: wszystkie 4 skrypty dziś robią delete+reinsert całego dnia
(atrax_route_anomaly_daily.py:849-851, atrax_tachograph_compliance_daily.py:468-470,
atrax_ecodriving_anomaly_daily.py:572-574, atrax_vehicle_health_daily.py:285-292). Czysty
ON CONFLICT DO NOTHING zamroziłby observed_value/severity na zawsze po pierwszym zapisie.
Reguła: unique index na (source_system, anomaly_type, coalesce(vehicle_id,''), coalesce(driver_id,''), occurred_date) + INSERT ... ON CONFLICT (...) DO UPDATE SET observed_value=EXCLUDED.observed_value, norm_median=EXCLUDED.norm_median, modified_z=EXCLUDED.modified_z, detail=EXCLUDED.detail, severity=EXCLUDED.severity, updated_at=now() WHERE p24_fleet_anomaly_escalations.status='open'. Efekt: dopóki nikt nie zaczął triażu, rerun
odświeża dane detekcji; gdy status już nie jest open, wiersz zamraża się.
To realna zmiana logiki w każdym z 4 skryptów: każdy musi przejść z “usuń i wstaw cały dzień na nowo” na “upsert per-wiersz z klauzulą WHERE”.
Znane, jawnie zaakceptowane ograniczenie: rerun, który przeliczy że anomalia już nie zachodzi, NIE usuwa istniejącego otwartego wiersza — wymaga ręcznego odrzucenia w panelu. Nazwać to w ops docach każdego z 4 skryptów.
RLS: service_role FOR ALL, grafana_readonly SELECT, plus dostęp dla et-operational-platform
(odczyt + update status/assigned_to/resolution_note) — dokładny mechanizm grantu do
zweryfikowania w fazie design, może wymagać secret-manager/nowej polityki RLS.
2. Normalizacja severity
- Fuel/ecodriving/vehicle-health:
escalate→critical,watch→warning,baseline_drift→critical. - Tachograf:
*_exceeded/extended_driving>0/shortened_rest>0→critical;*_near→warning.
3. Notyfikacja: Discord digest (wzorem tachografu, uogólniony)
Wynieść logikę diff-tylko-NOWE do wspólnej funkcji w atrax_norm_common.py (np.
post_fleet_escalation_digest(source_system, new_rows)), używanej przez wszystkie 4 skrypty. Jeden
webhook per system. Uogólnić P24_DISCORD_COMPLIANCE_WEBHOOK_URL na
P24_DISCORD_FLEET_ESCALATIONS_WEBHOOK_URL (nowy sekret = zadanie dla secret-manager, nie zgadywać
wartości).
Ważne: dziś to dwa strukturalnie różne modele dostawy — tachograf woła Discord bezpośrednio,
fuel/fraud steguje do dev_r_alert_events (Lane E). Nowa funkcja musi jawnie zastąpić OBA modele.
Po zdjęciu Lane E: wpis 'atrax-route-anomaly' w VALID_SOURCE (scripts/alert-ingest.py,
infra-src/meta-dispatcher/src/alert-ingest.ts) staje się martwym kodem — usunąć w tym samym PR.
Sprawdzić .github/workflows/alert-source-cutover-guard.yml (pilnuje odwrotnego kierunku) —
potwierdzić że zdjęcie Lane E nie koliduje.
4. Zgodność EU AI Act — reguła niesiona w danych
Cytat z docs/eu-ai-act-compliance.md: “advisory/investigatory only, a human interprets every
finding… never wired into any automated disciplinary/performance/allocation system… wiring this
output into any automated employment decision requires a fresh conformity assessment first.”
requires_human_review=true(każdy wiersz zattribution='driver_pattern'+ KAŻDY wiersz tachografu, bo tachograf z definicji jest per-kierowca) — panel nie może udostępniać dla takich wierszy żadnej akcji masowej/automatycznego rozwiązania, i nie może zasilać żadnego innego systemu oceny pracownika bez świeżej oceny zgodności.
Rejestracja dev_r_ai_systems to osobny, brakujący krok. Zweryfikowano: rejestruje TYLKO
ecodriving-per-route-anomaly. Fuel/fraud, vehicle-health i tachograf NIE MAJĄ wpisu wcale — ten
projekt powinien uzupełnić audyt + wpis dla każdego z 4 systemów, z jawną oceną per system czy w
ogóle podlega (vehicle-health jest o zasobie, nie o osobie, prawdopodobnie NIE wymaga Annex III —
decyzja do jawnego zapisania). Dodatkowo: przejście z ad-hoc triażu GH-issue na dedykowany panel z
akcją status→resolved/dismissed to bardziej ukształtowany “decision support” — zrewidować
istniejący wpis ecodrivingu (risk_justification/has_human_oversight).
Uwaga: reguła “p24-infra nie powinno wysyłać e-mail/push w imieniu et-operational-platform” (#9)
to własne rozumowanie o granicy własności danych (PII/tożsamość użytkownika) — NIE cytat z
credential-isolation-policy.md (ten dokument dotyczy wyłącznie credentiali) — nie przypisywać tej
reguły do tego dokumentu.
5. Migracja 4 istniejących skryptów
-
atrax_ecodriving_anomaly_daily.py— dodać zapis do nowej tabeli + wywołanie digestu. -
atrax_vehicle_health_daily.py— to samo, rozszerzalne o AdBlue (#7046). -
atrax_tachograph_compliance_daily.py— usunąć_post_discord/_file_alert_issue/send_alert, zastąpić zapisem do nowej tabeli + wspólnym digestem. Istniejąca logika “tylko NOWE” zostaje.p24_tachograph_violations(audyt) zostaje bez zmian.Realny gap do naprawienia w tym samym PR-1: ten skrypt NIE jest wpięty do żadnego crona —
docs/atrax-tachograph-compliance-operations.md:103-107nazywa to “separate sys-admin handoff… until then run it manually”, i to nigdy nie zostało zrealizowane (potwierdzone: nieobecny wrun-atrax-norm-nightly.sh, brak wpisu w.github/workflows/). Bez tego migracja jest asymetryczna. PR-1 musi również wpiąćatrax_tachograph_compliance_daily.pydorun-atrax-norm-nightly.sh. -
atrax_route_anomaly_daily.py(fuel/fraud) — zdjąć Lane E (native_fraud_flag/reconcile_fraudZOSTAJĄ — to klasyfikacja, nie dostawa alertu), zastąpić zapisem do nowej tabeli- digestem.
-
Nowy
atrax_driver_utilization_compute.py(#10) — nowy komponent, niezależny od tabeli eskalacji.
dev_r_alert_events/alert-triage-batch/p24-infra-nc-alert ZOSTAJE dla genuinie
infrastrukturalnych alertów — po tej migracji żaden z 4 skryptów Atrax już go nie używa.
6. Kontrakt cross-repo z et-operational-platform
p24-infra buduje: schemat + RLS + digest Discord + dokumentację kontraktu. et-operational-platform buduje: sam panel UI.
Mechanizm: docs/playbooks/cross-project-infra-requests.md działa w ODWROTNYM kierunku
(instrukcja dla et-operational-platform jak prosić p24-infra). Właściwy mechanizm to prawdopodobnie
docs/playbooks/queue-dispatch-cross-project.md (POST /queue-issue z
repo: "radieu/et-operational-platform") — ale myślany do dispatchowania implementacji już
istniejącego issue. Potwierdzić przy wykonaniu, nie zgadywać gh issue create --repo radieu/et-operational-platform bez sprawdzenia sankcjonowanego wzorca.
Potwierdzone: oba repo współdzielą ten sam projekt Supabase mwkqmgadqnkkihjdeqsi
(docs/infrastructure-overview.md:384).
Gap do jawnego nazwania: et-operational-platform nie ma dziś żadnej rejestracji AI Act pod tego
typu panel (sprawdzone: tylko vipo-offer-parser, niezwiązany) — cross-repo issue musi to nazwać
jako wymagające ich własnej zgody/oceny.
Rozszerzony zakres
7. Panel UI w et-operational-platform — koordynowane cross-repo issue
Zakłada drugie issue w repo et-operational-platform (mechanizm per #6) opisujące: schemat
p24_fleet_anomaly_escalations, semantykę status/requires_human_review/metryk wysycenia (#9),
wymóg UI: lista + filtrowanie + przypisanie + zamykanie, z zablokowaną akcją masową/auto-rozwiązania
dla requires_human_review=true. Oba issue cross-referencują się bez keywordów zamykających.
8. Korelacja cross-systemowa per pojazd — SQL view, nie nowy ETL
Widok p24_fleet_vehicle_daily_risk grupujący p24_fleet_anomaly_escalations po (vehicle_id, occurred_date): liczba odrębnych source_system, najwyższa severity, lista anomaly_type. Widok
SQL nad już zapisywaną tabelą, zero nowej ścieżki zapisu.
9. Notyfikacje rozszerzone — Discord (ops) + Realtime dla et-operational-platform
p24-infra nie powinno trzymać tożsamości/preferencji notyfikacji fleet managerów. Zamiast p24-infra wysyłającego e-mail/push:
- Discord digest (#3) zostaje.
- Włączyć Supabase Realtime na
p24_fleet_anomaly_escalations(ustawienie konfiguracyjne) — et-operational-platform subskrybuje i sam decyduje o e-mail/push. - Udokumentować to rozgraniczenie explicite w cross-repo issue z #7.
10. Miara wysycenia godzin jazdy (utilization) — dzienna/tygodniowa/dwutygodniowa/miesięczna
Cel: obok naruszeń tachografu, pokazywać ile % legalnie dostępnego czasu jazdy kierowca faktycznie wykorzystał — sygnał operacyjny, nie compliance.
Research zakończony: Zmapowane pola z /api/rtdrivers (map_drivers w
n8n-workflows/atrax-drivers-daily-snapshot_akgnvueDkDPbLJ49.json): brak jakiegokolwiek pola
dziennego limitu — kod nigdy go nie odczytuje, brak zapisanego przykładowego surowego payloadu.
this_week_limit_min ma fallback ?? 3360 (56h). Brak stałych regulacji UE (540/600 min)
gdziekolwiek w repo. Tabela p24_atrax_drivers_daily jest młoda (dane od 2026-08-08) i nie ma
wypełniania dziur. Wzorzec architektoniczny: p24_consumption_rolling_averages (#6777) — dedykowana
tabela z window_type, osobny nocny skrypt, zarejestrowana w
infra-src/report-query-api/tables.py jako SNAPSHOT TableSpec, z endpointem
/api/v1/driver/{id}/rolling-averages. NIE SQL view, NIE agregacja w report-query-api.
Projekt (poziomy pewności):
- Tygodniowy:
this_week_driving_min / this_week_limit_min— wysoka pewność. - Dwutygodniowy:
last_2w_driving_min / (last_2w_driving_min + last_2w_remaining_min)— wysoka pewność. - Dzienny:
day_driving_min / (540 lub 600 min stała UE albo realne pole Atrax jeśli Etap 1 je znajdzie)— średnia pewność, wymaga Etap 1. - Miesięczny:
SUM(day_driving_min) za kalendarzowy miesiąc / (liczba dni z realnym wierszem, NIE dni kalendarzowych, × dzienna stała)— niższa pewność, metryka biznesowa.
Etap 1: jednorazowy live-check surowej odpowiedzi /api/rtdrivers — czy jest tam pole dziennego
limitu. Jeśli nie, hardkodować stałą UE jako jawnie oznaczone założenie.
Architektura: nowa tabela p24_driver_utilization (window_type), nowy skrypt
atrax_driver_utilization_compute.py wpięty w run-atrax-norm-nightly.sh. Uwaga:
atrax_tachograph_compliance_daily.py dziś NIE jest w tym runnerze (patrz #5.3, PR-1 to naprawia) —
kotwiczyć nowy krok po atrax_vehicle_health_daily, chyba że PR-1 już wpięło tachograf na koncu.
Sprawdzić aktualny stan runnera w chwili implementacji PR-3. Rejestracja w
report-query-api/tables.py + nowy endpoint /api/v1/driver/{driver_id}/utilization. To metryka
informacyjna/KPI, NIE trafia do p24_fleet_anomaly_escalations.
Sekwencja wykonania
Cały ten zakres (#0–10) to treść JEDNEGO issue projektowego, ale realizacja musi być podzielona, żeby każdy krok był samodzielnie weryfikowalny (panel/Realtime zależą od innego repo):
- Etap 0 (blokujący): sprawdzić schemat/użycie tabeli
incidents(#0). - PR-1 (rdzeń, samodzielnie weryfikowalny): tabela
p24_fleet_anomaly_escalations(lub rozszerzenieincidents) + idempotencjaDO UPDATE ... WHERE status='open'+ normalizacja severity + wspólnapost_fleet_escalation_digest()+ migracja logiki zapisu we wszystkich 4 skryptach + wpięcieatrax_tachograph_compliance_daily.pydorun-atrax-norm-nightly.sh+ audyt/ uzupełnieniedev_r_ai_systemsdla fuel/fraud, vehicle-health, tachografu ORAZ rewizja wpisu ecodrivingu + usunięcie martwegoVALID_SOURCEwpisu. - PR-2 (tachograf: vehicle_id/occurred_at, Etap 1 first).
- PR-3 (osobne issue — miara wysycenia, #10). Nie blokuje PR-1/2.
- PR-4 (osobne issue — widok korelacji, #8). Dorzucić dopiero gdy panel faktycznie go zapyta.
- Cross-repo issue w et-operational-platform (#6-7). Zależny od PR-1.
WAŻNE — instrukcja dla worker/design fazy: plan-review na tym issue (/review-plan) ma przejść
4 iteracje, nie domyślne 3 z CLAUDE.md — jawne żądanie użytkownika, nadpisuje domyślny limit —
przed commitem PR-1.
Weryfikacja po wdrożeniu — per PR
Etap 0 / PR-1:
incidentssprawdzona i decyzja jawnie zapisana z uzasadnieniem.- Migracja aplikuje się czysto, CI zielone.
--dry-runprzebieg wszystkich 4 skryptów na dniu ze znaną anomalią zapisuje wiersz z poprawnymsource_system/severity/requires_human_review(dla tachografuvehicle_id/occurred_atmogą być jeszcze NULL — dopełnia PR-2).- Rerun nie nadpisuje ręcznie zmienionego
status/assigned_to/resolution_note; rerun który przeliczy anomalię jako niewystępującą zostawia stary otwarty wiersz. - Discord digest wysyła się raz per system, tylko dla nowych wierszy.
- Regresja: stary bespoke webhook tachografu i Lane E fuel-joba usunięte z kodu.
dev_r_ai_systems: 3 nowe wpisy + zrewidowany wpis ecodrivingu.
PR-2: 8. Wiersze tachografu niosą vehicle_id/occurred_at.
PR-3: 9. Tabela p24_driver_utilization zapełnia się poprawnie; endpoint odpowiada.
PR-4: 10. Widok korelacji zwraca poprawny wynik.
Cross-repo: 11. Panel odczytuje z nowej tabeli i respektuje requires_human_review.
Persisted from issue #7107. Plan reviewed via /review-plan (Co-Authored-By Claude Sonnet 5).
Implementation is split into PR-1…PR-4 + a cross-repo et-operational-platform issue per the sequence
above; this document is the design artifact, not an implementation.