Files
rar-autopass/docs/prd/prd-3.5-dashboard-compact-trimiteri-mapari.md
Claude Agent d7ba1195d4 feat(web): dashboard compact — import pe Acasa, status cu bife, Trimiteri lizibile, Mapari complete (3.5)
Acasa = ecran de import (tab Import scos, ?tab=import->Acasa). Bara status
compacta pe 2 randuri cu bife accesibile (glife + text) + data formatata.
'Coada'->'Trimiteri': coloane RO, stare umana, detaliu la click in panou
dedicat. Mapari pe 3 sectiuni (de rezolvat / op salvate / formate coloane),
Cont doar cheie+creds. Filtrare Trimiteri, corectie inline needs_data cu
re-enqueue + detectie coliziune idempotency, badge contoare pe tab-uri.
Helper pur partajat payload_view.py (web + GET /v1/prezentari).
Backend trimitere (worker/idempotenta/mapping/schema) neatins. 483 teste.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-19 08:56:45 +00:00

36 KiB

PRD 3.5 — Dashboard compact: import pe prima pagina, status cu bife, Trimiteri lizibile, Mapari complete

Stare: inchis

Proces complet: docs/ROADMAP.md §5. Contractul RAR (sursa de adevar): docs/api-rar-contract.md. Starea trece: draft → aprobat → in-executie → verify-pass → inchis (actualizata de lead). Aceasta e o livrabila de UI/UX — continua 3.4 (prd-3.4-ux-dashboard-web.md). Atinge stratul web (Jinja2 + HTMX, zero build) si rute de prezentare/listare. Nu modifica worker/, mapping.py (logica de rezolvare), idempotency.py, masina de stari submissions sau contractul RAR.

1. Obiectiv

Feedback de utilizare pe interfata 3.4: e mai buna, dar (a) importul — operatia principala — e ascuns intr-un tab, (b) bara de status are fonturi mici si etichete fara bife, (c) pagina "Coada" e neintuitiva (coloane tehnice "HTTP RAR", stare in engleza "sent", "Motiv" gol, si nu se vede la ce comanda se refera — doar idPrezentare), (d) pagina "Mapari" arata doar operatiile nerezolvate — maparile deja salvate par pierdute, iar maparile de coloane nu se vad nicaieri.

Livrabila rezolva aceste patru zone, fara a schimba comportamentul backend de trimitere:

  1. Acasa = ecranul de import. Prima pagina arata direct caseta de upload (importul e operatia principala), sub o bara de status compacta; ghidul "primii pasi" si link-urile de ajutor coboara pe un singur rand discret. Tab-ul "Import" separat dispare (era acelasi flux).
  2. Bara de status compacta, font normal, cu bife. Doua randuri: sus doua bife (verde/rosu) pentru "Trimitere automata" si "Legatura RAR" + "Ultima autentificare RAR" in format dd.mm.yyyy hh24:mi:ss; jos contoarele (in asteptare / declarate / blocate).
  3. "Coada" → "Trimiteri", lizibila. Coloane umane (Stare in romana via labels.py, Vehicul, Operatie, Data prestatie, Nr. prezentare RAR, Actualizat, Motiv uman). Click pe rand → detaliu complet (toate campurile, inclusiv codul HTTP tehnic si motivul integral). Detaliile comenzii se citesc din payload_json (text JSON simplu, nu criptat).
  4. "Mapari" complet — trei sectiuni. (1) De rezolvat (needs_mapping, ca acum), (2) Mapari operatii salvate — operations_mapping editabil (schimba cod RAR / auto-send / sterge), (3) Formate de coloane salvate — column_mappings per semnatura (vezi coloanele, format data, editeaza/sterge). "Cont" ramane doar cheie API + creds RAR.

Decizii de layout confirmate cu utilizatorul (AskUserQuestion, cu preview): Acasa=Import direct; status pe doua randuri cu bife; Trimiteri cu detalii in tabel + expand; toate maparile intr-un singur loc ("Mapari").

2. Non-Goals (anti scope-creep)

  • Fara schimbari de backend de trimitere: worker, mapare op→cod (rezolvarea), idempotenta, reconciliere, masina de stari submissions raman neatinse. Doar prezentare + listare/editare web.
  • Fara endpoint-uri API noi /v1/* si fara schimbari de schema SQL de structura. Tabelele operations_mapping / column_mappings exista deja; doar le expunem/edita prin rute web. Exceptie controlata (acceptata la CEO review): US-009 poate adauga un index pe submissions pentru filtrare (nu coloane noi, nu tabele noi).
  • Fara framework JS / build step: ramane Jinja2 + HTMX + CSS in base.html. Eventualul JS e vanilla inline, minim. Fara React/Vue/Tailwind/bundler.
  • Fara rescriere a fluxului de import (parsare, mapare coloane, preview, commit raman ca logica) — doar muta upload-ul pe Acasa si imbraca rezultatul.
  • Fara redesign login/signup/admin dincolo de aplicarea acelorasi clase/etichete daca e trivial.
  • Fara i18n / tema light: texte in romana hardcodate, paleta dark din base.html.
  • Fara modificari de worker / masina de stari / reconciliere: US-010 (corectie inline) doar re-valideaza (validation.py) si re-pune randul in queued (re-enqueue), fara sa atinga worker-ul.
  • Editarea de continut e permisa DOAR pentru randuri ne-trimise blocate (needs_data/needs_mapping), prin US-010. Randurile sent/FINALIZATA raman read-only (terminal la RAR, fara anulare/corectie).

3. Stories atomice

Fiecare story: cea mai mica unitate care lasa sistemul functional. Backend + UI pentru acelasi comportament = 2 stories. Toate atingerile sunt in app/web/ (templates + routes + labels.py). Verificare E2E = browser HTMX pe http://localhost:8000/ (Playwright MCP sau /browse). Regula de aur: fluxul import → commit → worker → FINALIZATA la RAR test NU are voie sa se strice, iar deep-link-urile ?tab= raman valide.

US-001: Bara de status compacta cu bife + data formatata (backend format + UI)

Ca operator de service vreau o bara de status compacta, cu font normal, cu bife clare si data ultimei autentificari completa pentru ca sa vad starea sistemului dintr-o privire, fara sa ghicesc.

  • Depinde de: —
  • Fisiere: app/web/labels.py (helper format data), app/web/routes.py (fragment_status), app/web/templates/_status.html, tests/test_web_labels.py, tests/test_web_status.py (~5 fisiere)
  • Test intai (RED): test_format_data_rar2026-06-18T14:30:22 (sau forma stocata in worker_heartbeat.last_rar_login_ok) → "18.06.2026 14:30:22"; valoare lipsa → "—"; format invalid → fallback grijuliu (nu arunca). tests/test_web_status.py::test_status_are_bife — fragmentul randat contine bifa verde cand worker viu + RAR ok, rosie cand oprit/indisponibil.
  • Continut:
    • Helper pur in labels.py (ex. format_data_rar(raw) -> str) care produce dd.mm.yyyy hh24:mi:ss.
    • _status.html rescris pe doua randuri: rand 1 = [bifa] Trimitere automata activa + [bifa] Legatura RAR OK + Ultima autentificare RAR: <data>; rand 2 = In asteptare: N | Declarate la RAR: N | Blocate: N. Font normal (13-14px), fara font-size:11px/12px.
    • Accesibilitate (design review): starea NU se distinge doar prin culoare. Glifa difera — &#10003; (✓) pentru activ/OK, &#10007; (✗) pentru oprit/indisponibil — plus textul difera (activa/oprita, functionala/indisponibila). Culoarea (verde/rosu) e redundanta, nu singurul semnal.
    • Pastreaza avertismentul "cont in asteptare de activare" (regresia reparata in 3.4) si poll-ul 15s.
  • Acceptance criteria:
    • Data ultimei autentificari apare ca dd.mm.yyyy hh24:mi:ss (test pe helper, pur).
    • Doua stari binare au bifa verde/rosie dupa starea reala (worker viu/mort, RAR ok/indisponibil).
    • Niciun text din bara nu mai foloseste font-size sub 13px.
    • python3 -m pytest -q trece.
  • Verificare E2E: browser pe / — bara compacta, bife corecte, data formatata romaneste.

US-002: Acasa devine ecranul de import (upload inline + help compact, scoatere tab Import)

Ca operator vreau sa import direct de pe prima pagina pentru ca importul e ce fac cel mai des.

  • Depinde de: US-001 (bara status finala deasupra)
  • Fisiere: app/web/templates/dashboard.html (lista tab-uri), app/web/routes.py (_TABS_VALIDE, _render_panel, fragment_import/fragment_acasa), app/web/templates/_acasa.html, app/web/templates/_upload.html (reutilizat), tests/test_web_dashboard.py (~5 fisiere)
  • Test intai (RED): test_acasa_contine_upload — fragmentul /_fragments/acasa contine formularul de upload (hx-post catre ruta de import existenta); test_tab_import_redirect?tab=import nu mai e tab separat (redirect la acasa sau eticheta absenta din tab-bar), iar deep-link-ul nu da 404.
  • Continut:
    • _acasa.html: caseta de upload (din _upload.html) ca prim element; sub ea, "primii pasi" pe un singur rand compact (o Cont RAR o Cheie API * Import) + un rand de ajutor cu link-uri mici (Ghid / Coada / Mapari). Upload-ul porneste stepper-ul existent (target #import-section).
    • Ierarhie (design review): upload-ul e vizual DOMINANT (titlu clar + caseta mare); checklist-ul "primii pasi" si ajutorul sunt subordonate (font mai mic, sub upload), nu concureaza cu el. Prima pagina are un singur centru de greutate: importa un fisier.
    • Scoate ("import", ...) din lista de tab-uri din dashboard.html; pastreaza ?tab=import valid (redirect la acasa) pentru orice URL salvat. Stepper-ul, mapare-coloane, preview, commit raman.
    • "Incarca alt fisier" din stepper trimite inapoi la Acasa (nu la un tab inexistent).
  • Acceptance criteria:
    • Pe / (tab implicit Acasa) caseta de upload e vizibila fara click suplimentar.
    • Tab-bar-ul nu mai are "Import"; ?tab=import nu da 404 (redirect/echivalent la Acasa).
    • Fluxul upload → mapare coloane → preview → commit functioneaza neschimbat (target/csrf intacte).
    • Link-urile de ajutor + checklist incap pe randuri compacte, font normal.
    • python3 -m pytest -q trece.
  • Verificare E2E: browser — incarc un fisier de pe Acasa, parcurg stepper-ul pana la commit.

US-003: Prezentare detalii comanda din payload (backend pur, testat)

Ca operator vreau ca fiecare trimitere sa-mi spuna despre ce vehicul/operatie e vorba pentru ca acum vad doar idPrezentare si nu stiu ce comanda am trimis si ce nu.

  • Depinde de: —
  • Fisiere: app/payload_view.py (nou, helper PARTAJAT web+API), app/api/v1/router.py (refactor mic: GET /v1/prezentari foloseste acelasi helper, nu o copie), tests/test_payload_view.py (~3 fisiere). Decizie eng review (DRY): un singur modul de extragere payload→campuri afisabile, ca sa nu diverge intre canalul web si cel API (router.py:247-264 face azi extragerea sa proprie).
  • Test intai (RED): test_detalii_din_payload — dat un payload_json (text JSON: vin, numar/ numarInmatriculare, odometru_final, data_prestatie, cod_prestatie/cod_op_service/denumire), helperul intoarce un dict de prezentare {vehicul_nr, vin_scurt, operatie, data_prestatie, odometru, cod}; test_payload_partial — campuri lipsa → "—"/gol fara exceptie; test_payload_invalid → fallback grijuliu (nu arunca); test_payload_coercion_excel — odometru "123.0"/numeric si VIN non-string (coercion Excel) afisate curat (str() defensiv), chei API vs import (numar vs numarInmatriculare) ambele rezolvate.
  • Continut: functie pura care primeste randul submission (sau payload_json) si produce campurile de afisat. vin_scurt = forma trunchiata pentru tabel (VIN integral ramane in detaliu). Citeste cheile defensiv (canalele API si import pot diferi usor; payload_json e plaintext — vezi router.py).
  • Acceptance criteria:
    • Helper pur (fara DB, fara request), 100% acoperit de teste pe cazurile plin/partial/invalid.
    • Nu arunca niciodata pe payload malformat (degradeaza la ).
    • python3 -m pytest -q trece.
  • Verificare E2E: — (acoperit de US-004).

US-004: "Coada" → "Trimiteri" — tabel lizibil + detaliu la click

Ca operator vreau un tabel de trimiteri pe care il inteleg, cu detaliile comenzii pentru ca "HTTP RAR"/"sent"/"Motiv gol" nu-mi spun nimic si nu stiu la ce comanda se refera randul.

  • Depinde de: US-003 (prezentare payload), US-001 (labels.py pt stare umana)
  • Fisiere: app/web/templates/_coada.html (titlu + tab label), app/web/templates/_submissions.html, app/web/templates/dashboard.html (eticheta tab "Coada"→"Trimiteri", tab_id ramane coada), app/web/routes.py (fragment_submissions imbogatit + ruta detaliu /_fragments/trimitere/{id}), app/web/templates/_trimitere_detaliu.html (nou), tests/test_web_submissions.py (~6 fisiere)
  • Test intai (RED): test_submissions_coloane_umane — tabelul contine antete RO (Stare, Vehicul, Operatie, Data prestatie, Nr. prezentare RAR, Motiv) si nu mai contine "HTTP RAR" ca antet principal, nici status brut englezesc afisat ca atare (folose labels.eticheta_stare); test_detaliu_trimitere/_fragments/trimitere/{id} intoarce detaliul complet scoped pe cont (404 cross-account).
  • Continut:
    • Eticheta tab "Coada" → "Trimiteri" (pastreaza tab_id="coada" ca deep-link ?tab=coada sa ramana valid). Titlu sectiune "Trimiteri catre RAR".
    • _submissions.html: coloane = #, Stare (eticheta_stare text RO + pill culoare), Vehicul (nr + VIN scurt, din US-003), Operatie, Data prestatie, Nr. prezentare RAR (id_prezentare sau ), Actualizat, Motiv (text uman; pt needs_data → ex. "lipsa odometru"). Codul HTTP tehnic NU mai e coloana principala — coboara in detaliu. query-ul include payload_json (pt US-003) pe langa campurile actuale.
    • Click pe rand → hx-get="/_fragments/trimitere/{id}"_trimitere_detaliu.html cu toate campurile (vehicul integral, operatie+cod, odometru, data, stare, rar_status_code, rar_error integral, retry, timestamps). Scoped pe contul sesiunii.
    • Detaliul se randeaza intr-un PANOU DEDICAT (ex. #trimitere-detaliu sub/langa tabel), NU inline in randul din tabel. Motiv (CEO review, Finding #1): _submissions.html are hx-trigger="every 10s"; un expand inline ar fi sters de poll-ul de refresh. Panoul dedicat nu e prins de poll.
    • Vizibilitate (design review): la deschidere, panoul trebuie sa fie evident — scroll-to panou si/sau evidentiere a randului selectat in tabel. Altfel pare ca "nu s-a intamplat nimic" (panoul apare sub fold).
  • Acceptance criteria:
    • Antetele coloanelor sunt in romana; starea afisata e text uman (nu "sent").
    • Fiecare rand arata vehicul + operatie + data (din payload), nu doar idPrezentare.
    • Click pe rand deschide detaliul complet, scoped pe cont (404 la id-ul altui cont).
    • Motivul pentru needs_data/error apare in coloana Motiv (nu gol cand exista rar_error).
    • python3 -m pytest -q trece.
  • Verificare E2E: browser — tab Trimiteri, identific un rand dupa vehicul, deschid detaliul.

US-005: Listare + editare mapari operatii salvate (backend)

Ca operator vreau sa vad si sa pot schimba maparile operatie→cod pe care le-am salvat pentru ca acum, dupa ce mapez si trimit, ele dispar din ecran si par pierdute.

  • Depinde de: —
  • Fisiere: app/web/routes.py (query operations_mapping + rute edit/delete), tests/test_web_mapari_salvate.py (~2 fisiere)
  • Test intai (RED): test_lista_mapari_salvate — intoarce randurile operations_mapping ale contului cu nume_prestatie jonctionat din nomenclator; test_editeaza_mapare_salvata — POST schimba cod_prestatie/auto_send doar pe contul propriu (cross-account interzis), verifica cod_prestatie exista in nomenclator (ca la /mapari actual); test_sterge_mapare_salvata — DELETE scoped pe cont.
  • Continut: functie de listare scoped pe cont + rute web POST /mapari/salvate (update) si POST /mapari/salvate/sterge (delete) cu CSRF + PRG/HTMX swap. Nu schimba logica de rezolvare din mapping.py; doar CRUD pe tabela existenta. Re-rezolvare obligatorie (promovata din optional la CEO review): la schimbarea unui cod, submission-urile blocate (needs_mapping) pe acel cod_op_service se re-rezolva automat, reutilizand helperul de re-rezolvare existent (reresolve_account/echivalent) — fara cod nou de rezolvare. Inchide pain-ul "am mapat dar nu vad efectul".
  • Acceptance criteria:
    • Listarea e scoped pe contul sesiunii (fara leak cross-account — vezi C6 din 3.3a).
    • Editarea respinge cod inexistent in nomenclator si cont strain.
    • Stergerea afecteaza doar contul propriu.
    • La editarea unui cod, submission-urile needs_mapping pe acel cod_op_service se deblocheaza automat (test: rand blocat → editez maparea → randul trece din needs_mapping).
    • python3 -m pytest -q trece.
  • Verificare E2E: — (acoperit de US-006).

US-006: Listare + editare/stergere formate de coloane salvate (backend)

Ca operator vreau sa vad formatele de import memorate si sa le pot edita/sterge pentru ca nu stiu ce coloane sunt retinute si ce se intampla cand vin cu un fisier cu alte coloane.

  • Depinde de: —
  • Fisiere: app/web/routes.py (query column_mappings + rute edit/delete), tests/test_web_formate_coloane.py (~2 fisiere)
  • Test intai (RED): test_lista_formate_coloane — intoarce randurile column_mappings ale contului cu coloanele (din signature_coloane/json_mapare), format_data si un contor de utilizare (cate import_batches/submissions folosesc acea semnatura — best-effort, sau omis daca nu e ieftin); test_sterge_format_coloane — DELETE scoped pe cont; test_editeaza_format_coloane — POST schimba json_mapare/format_data pentru o semnatura, scoped pe cont.
  • Continut: listare scoped pe cont + rute POST /formate-coloane/... (edit/delete) cu CSRF. Comportament documentat (nu cod nou de import): un fisier cu alte coloane = semnatura noua = format nou separat (UNIQUE (account_id, signature_coloane)), nu suprascrie; acelasi antet = maparea retinuta se reaplica automat la urmatorul import (comportament existent din 2.4).
  • Acceptance criteria:
    • Listarea arata coloanele fiecarui format + format data, scoped pe cont.
    • Stergerea/editarea afecteaza doar contul propriu (fara leak cross-account).
    • python3 -m pytest -q trece.
  • Verificare E2E: — (acoperit de US-006 UI in US-007).

US-007: Pagina "Mapari" cu trei sectiuni (UI)

Ca operator vreau o singura pagina "Mapari" cu tot ce tine de mapari pentru ca sa rezolv ce e blocat si sa-mi gestionez maparile salvate (operatii + coloane) intr-un loc.

  • Depinde de: US-005, US-006
  • Fisiere: app/web/templates/_mapari.html, app/web/routes.py (_render_mapari/fragment_mapari imbogatit sa treaca cele 3 seturi de date), app/web/templates/_cont.html (ramane doar cheie API + creds RAR — fara mapari), tests/test_web_mapari_ui.py (~4 fisiere)
  • Test intai (RED): test_mapari_trei_sectiuni — fragmentul /_fragments/mapari contine cele trei sectiuni (De rezolvat / Mapari operatii salvate / Formate de coloane salvate); test_cont_fara_mapari/_fragments/cont nu mai contine sectiuni de mapari.
  • Continut:
    • _mapari.html reorganizat pe 3 sectiuni: (1) De rezolvat = pending_unmapped (ca acum), (2) Mapari operatii salvate = lista din US-005, fiecare cu select cod RAR + checkbox auto-send + buton sterge (HTMX swap pe #mapari-section), (3) Formate de coloane salvate = lista din US-006, fiecare cu coloanele afisate + format data + actiuni edit/sterge. Empty states prietenoase per sectiune.
    • _cont.html: confirma ca nu contine mapari (cheie API + creds RAR doar).
  • Acceptance criteria:
    • "Mapari" arata cele 3 sectiuni; sectiunile goale au mesaj prietenos, nu lipsesc tacit.
    • Salvarea/stergerea reincarca sectiunea via HTMX fara reload de pagina.
    • "Cont" nu mai contine mapari.
    • python3 -m pytest -q trece.
  • Verificare E2E: browser — vad maparile salvate dupa ce am mapat+trimis; editez un cod; vad formatul de coloane al fisierului importat.

US-008: Feedback clar pentru randuri respinse la import (lipsa odometru / needs_data)

Ca operator vreau sa inteleg de ce un rand nu s-a importat pentru ca am incarcat un fisier fara odometru si pur si simplu "nu s-a importat", fara explicatie.

  • Depinde de: US-002 (importul pe Acasa)
  • Fisiere: app/web/templates/_preview_import.html, app/web/routes.py (mesaj preview, daca e cazul), tests/test_web_preview_motive.py (~3 fisiere)
  • Test intai (RED): test_preview_arata_motiv_needs_data — un rand needs_data din lipsa odometru apare in preview cu motiv explicit ("lipsa odometru" / mesajul de validare), nu doar numarat la "blocate".
  • Continut: preview-ul de import (cele 6 stari deja existente din 2.5) afiseaza pentru randurile needs_data/needs_review motivul (din validare/error), ca operatorul sa stie ce sa corecteze. Reutilizeaza mesajele de validare existente (validation.py); fara reguli noi de validare.
  • Acceptance criteria:
    • Un rand fara odometru apare explicit cu motivul, nu doar in contorul "blocate".
    • Contoarele preview (ok/needs_data/...) raman corecte.
    • python3 -m pytest -q trece.
  • Verificare E2E: browser — import un fisier fara odometru, vad in preview de ce e blocat randul.

US-009: Filtrare/cautare in "Trimiteri" (stare / vehicul / data prestatie)

Ca operator cu sute de trimiteri vreau sa filtrez lista pentru ca sa gasesc rapid o comanda sau toate cele blocate, fara sa derulez tot.

  • Depinde de: US-003, US-004
  • Fisiere: app/web/routes.py (fragment_submissions accepta parametri de filtru), app/web/templates/_coada.html (controale filtru), app/web/templates/_submissions.html, app/schema.sql (index pe submissions(account_id, status) daca lipseste — exista deja idx_submissions_account_status, deci probabil zero schimbari), tests/test_web_filtrare.py (~4 fisiere)
  • Test intai (RED): test_filtru_stare?status=needs_data intoarce doar acele randuri (scoped pe cont); test_filtru_vehicul — cautare text pe nr/VIN (case-insensitive); test_filtru_data — interval data_prestatie. Toate scoped pe cont, fara leak.
  • Continut: controale HTMX (select stare + input text vehicul + interval data) care reincarca /_fragments/submissions cu query string; filtrarea pe vehicul/data se face dupa parsarea payload_json (text JSON), filtrarea pe stare in SQL (foloseste indexul existent). Pastreaza poll-ul (poll-ul re-trimite filtrul curent). Empty state "nimic pe filtrul curent" + buton „sterge filtrele" (design review) cand exista un filtru activ.
  • Acceptance criteria:
    • Filtrele combina (stare + vehicul + data) si raman aplicate la refresh-ul de 10s.
    • Filtrarea e scoped pe cont (fara leak cross-account).
    • Nu necesita coloane/tabele noi (cel mult confirma indexul existent).
    • python3 -m pytest -q trece.
  • Verificare E2E: browser — filtrez dupa "blocate" si dupa un numar de inmatriculare.

US-010: Corectie inline pentru randuri ne-trimise blocate (needs_data)

Ca operator vreau sa completez un camp lipsa (ex. odometru) direct pe randul blocat si sa-l re-trimit pentru ca acum trebuie sa refac tot fisierul de import doar pentru un camp.

  • Depinde de: US-003, US-004
  • Fisiere: app/web/routes.py (ruta POST /trimitere/{id}/corecteaza), app/web/templates/_trimitere_detaliu.html (form de corectie pe randurile needs_data/needs_mapping), tests/test_web_corectie.py (~3 fisiere)
  • Test intai (RED): test_corectie_needs_data — un rand needs_data din lipsa odometru: completez odometru → re-validare (validation.py) → status queued, payload actualizat, idempotency recalculata; test_corectie_sent_interzis — un rand sent/FINALIZATA NU poate fi editat (403/refuz, read-only); test_corectie_coliziune_idempotency — daca noua cheie coincide cu alt submission existent, corectia se opreste cu mesaj „exista deja o trimitere identica (rand #N)", fara IntegrityError/500 si fara duplicat; test_corectie_cont_strain — interzis cross-account.
  • Continut: pe panoul de detaliu (US-004), pentru randuri needs_data/needs_mapping, un mini-form cu campurile relevante; la submit re-valideaza prin validation.py (fara reguli noi), reconstruieste payload_json + recalculeaza idempotency_key (canonicalize → build_key, ca la enqueue), seteaza status queued si next_attempt_at=now. Nu atinge worker-ul / masina de stari (doar re-enqueue). Randurile sent/FINALIZATA raman read-only (gard explicit).
    • Coliziune idempotency (decizie eng review): INAINTE de UPDATE, verifica daca noua idempotency_key exista deja pe alt submission al contului; daca da, opreste corectia si afiseaza „exista deja o trimitere identica (rand #N)" cu link la acel rand. Fara 500, fara duplicat. (UNIQUE pe idempotency_key ar arunca IntegrityError altfel.)
    • Loc + eroare (design review): formul de corectie traieste IN panoul de detaliu (US-004), nu intr-o sectiune separata. Eroarea de validare se afiseaza clar pe campul invalid (nu un mesaj generic sus).
  • Acceptance criteria:
    • Un rand needs_data corectat valid trece in queued cu payload + idempotency actualizate.
    • Randurile sent/FINALIZATA nu pot fi editate (gard testat).
    • Coliziunea de idempotency e prinsa si comunicata clar (rand-duplicat identificat), fara 500.
    • Corectia respinge date inca invalide (mesaj de validare) si conturi straine.
    • python3 -m pytest -q trece.
  • Verificare E2E: browser — corectez odometru pe un rand blocat, il vad trecand in asteptare; cu worker --send pe RAR test, ajunge FINALIZATA.

US-011: Badge cu contoare pe tab-uri (atentionari)

Ca operator vreau sa vad pe eticheta tab-ului cate lucruri ma asteapta pentru ca sa stiu unde sa intervin fara sa deschid fiecare tab.

  • Depinde de: US-007 (mapari), US-004 (trimiteri)
  • Fisiere: app/web/templates/dashboard.html (badge pe eticheta tab), app/web/routes.py (contoarele deja calculate in fragment_status/panel context, transmise la tab-bar), tests/test_web_badge.py (~3 fisiere)
  • Test intai (RED): test_badge_mapari — cand exista operatii needs_mapping, eticheta "Mapari" poarta un numar; test_badge_trimiteri_blocate — cand exista randuri blocate, "Trimiteri" poarta marcaj; test_badge_zero_ascuns — fara nimic de rezolvat, niciun badge.
  • Continut: numar mic pe eticheta tab-ului, alimentat din contoarele existente (needs_mapping pt Mapari, blocate pt Trimiteri). Pur prezentare; reutilizeaza ce calculeaza deja status-ul. Accesibil (text in aria-label, nu doar culoare).
  • Acceptance criteria:
    • Badge apare doar cand contorul > 0; dispare la zero.
    • Numarul e corect si scoped pe cont.
    • aria-label-ul tab-ului include sensul badge-ului (nu doar pastila colorata).
    • python3 -m pytest -q trece.
  • Verificare E2E: browser — cu o operatie nemapata, "Mapari" arata "(1)"; dupa rezolvare, dispare.

4. Riscuri

  • Scoaterea tab-ului "Import" rupe deep-link-uri/teste (?tab=import, link-uri din _acasa.html, "Incarca alt fisier" din stepper). Mitigare: ?tab=import → redirect la acasa; grep dupa toate referintele tab=import//_fragments/import inainte de stergere; test dedicat (US-002).
  • Citirea payload_json pentru detalii depinde de forma payload-ului, care difera usor intre canalul API si import. Mitigare: helper pur defensiv cu fallback (US-003), testat pe ambele forme; nu se bazeaza pe o cheie obligatorie.
  • Leak cross-account pe noile listari/editari (mapari salvate, formate coloane, detaliu trimitere). Mitigare: toate scoped pe contul sesiunii cu regula NULL→1 (C6/3.3a), test cross-account per ruta noua, re-folosind pattern-ul account_scope_clause (3.2).
  • Afisare PII (VIN/nr) pe ecran in Trimiteri. Acceptabil: e proprietatea contului autentificat, scoped pe sesiune; nu se expune in loguri si nu apare in raspunsuri 422 (handler existent in main.py).
  • Aglomerare "Mapari" cu 3 sectiuni. Mitigare: empty states + colaps vizual cand o sectiune e goala.

5. Intrebari deschise

Se rezolva cu utilizatorul ÎNAINTE de executie (poarta de aprobare PRD).

  • Detaliul trimiterii: expand inline sau panou? REZOLVAT (CEO review): panou dedicat #trimitere-detaliu, nu inline — altfel poll-ul de 10s sterge expand-ul (vezi US-004).
  • "Editare" format de coloane: redeschidem editorul de mapare campuri (_mapcoloane.html) prefiltrat pe semnatura salvata, sau permitem doar stergere + re-import? (propunere MVP: stergere + vizualizare; edit de campuri = nice-to-have daca incape in story).
  • Contor de utilizare pe formate de coloane: il afisam (cost de query) sau il omitem in v1? (propunere: omitem daca nu e ieftin — nu e critic).

6. Valuri de executie (graful de dependente)

Val 1: [US-001] [US-003] [US-005] [US-006]   ← fara dependente, fisiere distincte → paralel (max 3-4)
Val 2: [US-002] [US-004]                      ← US-002←US-001 ; US-004←US-003+US-001
Val 3: [US-007] [US-008] [US-009] [US-010]    ← US-007←US-005+US-006 ; US-008←US-002 ;
                                                 US-009←US-003+US-004 ; US-010←US-003+US-004
Val 4: [US-011]                               ← US-011←US-007+US-004 (contoare din ambele)

Atentie la fisiere partajate intre valuri: routes.py, dashboard.html, _status.html, _submissions.html si _trimitere_detaliu.html (US-004/009/010) sunt atinse de mai multe stories — secventiaza-le sau worktree + merge de catre lead (vezi anti-pattern ROADMAP). In special US-009 si US-010 ating ambele acelasi panou de detaliu/tabel — ruleaza-le secvential, nu in paralel.

7. Review-uri de plan

CEO review (2026-06-19) — SELECTIVE EXPANSION

  • Abordare aleasa: A (cele 8 stories complete), cu expansiuni cherry-pick acceptate.
  • Expansiuni acceptate (adaugate ca stories): US-009 filtrare/cautare Trimiteri, US-010 corectie inline pentru needs_data, US-011 badge contoare pe tab-uri.
  • Finding promovat: re-rezolvarea automata la editarea unei mapari salvate, din "optional" in scope obligatoriu (US-005).
  • Finding de robustete inchis: detaliul Trimiteri merge in panou dedicat, nu inline — altfel poll-ul de 10s din _submissions.html ar sterge expand-ul (US-004 + §5).
  • Schimbare de scope fata de draft: editarea de continut e acum permisa pentru randuri ne-trimise blocate (US-010); sent/FINALIZATA raman read-only. Vezi §2 non-goals actualizat.

Eng review (2026-06-19)

  • Step 0: 11 stories e mult, dar e o livrabila UI sparta in stories atomice TDD (conventia proiectului), nu overbuild. Risc real = contentia pe fisiere partajate, nu numarul. Scope confirmat.
  • Decizie idempotency (US-010): corectia detecteaza coliziunea de idempotency_key INAINTE de UPDATE si o comunica clar (rand-duplicat identificat), fara 500/duplicat.
  • Decizie DRY (US-003): helper partajat app/payload_view.py folosit si de web si de GET /v1/prezentari — o singura sursa de extragere payload→campuri.
  • Aserțiune adaugata: test ca submissions.payload_json e plaintext (US-003) — daca vreodata se cripteaza, testul cade si stim sa adaptam.
  • Plafon perf notat (US-009): filtrarea pe vehicul/data parseaza payload in Python per rand; OK la scara actuala, json_extract() daca devine necesar. Nu blocheaza.
  • Secventiere intarita: US-004 (schelet tabel+panou) → apoi US-009 si US-010, strict secvential pe _submissions.html/_trimitere_detaliu.html/fragment_submissions. NU paraleliza valul 3 pe ele.

Design review (2026-06-19) — rating 7/10 → 9/10 dupa fixuri

Layout-urile au fost alese vizual cu utilizatorul (mockup-uri ASCII in AskUserQuestion). Patru cerinte de design adaugate ca AC:

  • Accesibilitate bife (US-001): glife distincte (✓/✗) + text, nu doar culoare (daltonism).
  • Ierarhie Acasa (US-002): upload-ul vizual dominant; checklist + ajutor subordonate.
  • Vizibilitate panou (US-004): scroll-to / evidentiere rand la deschiderea detaliului.
  • Stari de eroare (US-009/010): „sterge filtrele" + empty state; eroare de validare pe campul invalid, in panoul de detaliu (decizie utilizator).

8. Raport review-uri de plan (consolidat)

Review Data Rezultat Decizii cheie
CEO (SELECTIVE EXPANSION) 2026-06-19 Aprobat cu expansiuni Abordare A; +US-009/010/011; re-rezolvare US-005 obligatorie; detaliu in panou (nu inline)
Eng 2026-06-19 Aprobat Coliziune idempotency US-010 detectata pre-UPDATE; helper partajat payload_view.py; secventiere val 3
Design 2026-06-19 Aprobat (9/10) Bife accesibile; ierarhie Acasa; vizibilitate panou; stari de eroare

Scope final: 11 stories (US-001…US-011), in 4 valuri. Backend de trimitere (worker, masina de stari, reconciliere, idempotenta ca logica) neatins; singura mutatie de date noua = corectia US-010 (re-enqueue randuri ne-trimise) + posibil un index (US-009). DECIZII NEREZOLVATE: niciuna care sa blocheze executia — raman 2 intrebari de finete in §5 (editare format coloane: stergere+vizualizare in MVP; contor utilizare formate: omis daca nu e ieftin), ambele cu propunere si fara impact pe arhitectura.

Urmatorul pas (ROADMAP §5): **Stare**: aprobat → EXECUTE (TDD pe valuri). Poarta umana: aprobarea PRD.


Raport VERIFY

Completat de subagentul verificator (context curat, ROADMAP §5.6) — 2026-06-19.

Verdict global: PASS. Toate cele 11 stories verificate prin cod + teste. Regresia de aur intacta. Non-Goals respectate.

  • Suita: python3 -m pytest -q483 passed (de la 434 baseline 3.4; +49 teste noi). Verde.
  • PASS/FAIL per story: toate US-001…US-011 PASS. Dovezi (verificator context curat):
    • US-001: format_data_rar (dd.mm.yyyy hh24:mi:ss, lipsa→"—", invalid→fallback fara exceptie); bife accesibile cu glife &#10003;/&#10007; + text distinct + culoare redundanta; fara font-size <13px.
    • US-002: Acasa include upload (hx-post="/_import/upload"), tab Import scos, ?tab=import→Acasa (fara 404).
    • US-003: app/payload_view.py pur/defensiv, refolosit de GET /v1/prezentari (DRY), payload_json brut neexpus.
    • US-004: coloane RO, stare umana, detaliu in panou dedicat #trimitere-detaliu (nu inline), 404 cross-account.
    • US-005/006: CRUD operations_mapping/column_mappings scoped pe cont, re-rezolvare la edit cod.
    • US-007: _fragments/mapari 3 sectiuni cu empty states; _fragments/cont fara mapari.
    • US-008: preview arata mesajul de validare pentru randuri needs_data.
    • US-009: filtre stare(SQL)/vehicul/data scoped pe cont; empty state cu "sterge filtrele".
    • US-010: corectie needs_data→queued cu payload+idempotency recalculate; sent read-only (403); coliziune idempotency prinsa pre-UPDATE; cross-account 404.
    • US-011: badge Mapari(needs_mapping)/Trimiteri(blocate), ascuns la zero, scoped, aria-label cu sens.
  • Regresia de aur: flux import→commit→coada + canal API POST /v1/prezentari intacte (test_import_ui/e2e, test_api, test_web_tabs); deep-link-uri ?tab= valide.
  • Non-Goals: git diff --stat confirma app/worker/, app/idempotency.py, app/mapping.py, app/schema.sql NEATINSE; CHECK status pastreaza cele 6 stari; niciun endpoint /v1/* nou.
  • E2E live RAR: neprobat in sesiune (fara credentiale RAR live, identic cu 3.4) — recomandata probare manuala ./start.sh test both --send + browser pe http://localhost:8000/.

Findings /code-review (high) — reparate inainte de inchidere

  1. Corectie + needs_mapping (sever): ruta POST /trimitere/{id}/corecteaza re-punea in queued fara re-rezolvarea prestatiilor → un cod nemapat putea ajunge la RAR cu codPrestatie: null (FINALIZATA ireversibil). Fix: re-ruleaza resolve_prestatii + has_no_auto_send (ca reresolve_account); cod nemapat ramane needs_mapping. Test: test_corectie_needs_mapping_nu_ajunge_in_coada.
  2. Filtru dupa LIMIT 200: cautarea pe vehicul/data rata randuri mai vechi de 200. Fix: fara LIMIT in SQL cand filtrul text/data e activ, plafonare dupa filtrare.
  3. Coliziune idempotency non-atomica (cursa TOCTOU → 500): Fix: try/except sqlite3.IntegrityError in jurul UPDATE-ului queued, mesaj prietenos in loc de 500.
  4. Comparatie data non-ISO gresita la filtru: Fix: _is_iso_date — compar doar date ISO YYYY-MM-DD.

Findings de cleanup (scope-clause hand-coded in _status_counts/_get_acasa_context, _render_panel_* duplicate) sunt preexistente din 3.4, in afara scope-ului 3.5 — neatinse intentionat.