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>
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 modificaworker/,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:
- 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).
- 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). - "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 dinpayload_json(text JSON simplu, nu criptat). - "Mapari" complet — trei sectiuni. (1) De rezolvat (
needs_mapping, ca acum), (2) Mapari operatii salvate —operations_mappingeditabil (schimba cod RAR / auto-send / sterge), (3) Formate de coloane salvate —column_mappingsper 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. Tabeleleoperations_mapping/column_mappingsexista deja; doar le expunem/edita prin rute web. Exceptie controlata (acceptata la CEO review): US-009 poate adauga un index pesubmissionspentru 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 inqueued(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. Randurilesent/FINALIZATAraman 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 pehttp://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_rar—2026-06-18T14:30:22(sau forma stocata inworker_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 producedd.mm.yyyy hh24:mi:ss. _status.htmlrescris 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), farafont-size:11px/12px.- Accesibilitate (design review): starea NU se distinge doar prin culoare. Glifa difera —
✓(✓) pentru activ/OK,✗(✗) 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.
- Helper pur in
- 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-sizesub 13px. python3 -m pytest -qtrece.
- Data ultimei autentificari apare ca
- 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/acasacontine formularul de upload (hx-postcatre ruta de import existenta);test_tab_import_redirect—?tab=importnu mai e tab separat (redirect laacasasau 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 dindashboard.html; pastreaza?tab=importvalid (redirect laacasa) 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=importnu 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 -qtrece.
- Pe
- 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/prezentarifoloseste 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 unpayload_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 (numarvsnumarInmatriculare) 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_jsone plaintext — vezirouter.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 -qtrece.
- 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.pypt 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_idramanecoada),app/web/routes.py(fragment_submissionsimbogatit + 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 (foloselabels.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=coadasa ramana valid). Titlu sectiune "Trimiteri catre RAR". _submissions.html: coloane =#, Stare (eticheta_staretext RO + pill culoare), Vehicul (nr + VIN scurt, din US-003), Operatie, Data prestatie, Nr. prezentare RAR (id_prezentaresau—), Actualizat, Motiv (text uman; ptneeds_data→ ex. "lipsa odometru"). Codul HTTP tehnic NU mai e coloana principala — coboara in detaliu.query-ul includepayload_json(pt US-003) pe langa campurile actuale.- Click pe rand →
hx-get="/_fragments/trimitere/{id}"→_trimitere_detaliu.htmlcu toate campurile (vehicul integral, operatie+cod, odometru, data, stare,rar_status_code,rar_errorintegral, retry, timestamps). Scoped pe contul sesiunii. - Detaliul se randeaza intr-un PANOU DEDICAT (ex.
#trimitere-detaliusub/langa tabel), NU inline in randul din tabel. Motiv (CEO review, Finding #1):_submissions.htmlarehx-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).
- Eticheta tab "Coada" → "Trimiteri" (pastreaza
- 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/errorapare in coloana Motiv (nu gol cand existarar_error). python3 -m pytest -qtrece.
- 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(queryoperations_mapping+ rute edit/delete),tests/test_web_mapari_salvate.py(~2 fisiere) - Test intai (RED):
test_lista_mapari_salvate— intoarce randurileoperations_mappingale contului cunume_prestatiejonctionat din nomenclator;test_editeaza_mapare_salvata— POST schimbacod_prestatie/auto_senddoar pe contul propriu (cross-account interzis), verificacod_prestatieexista in nomenclator (ca la/mapariactual);test_sterge_mapare_salvata— DELETE scoped pe cont. - Continut: functie de listare scoped pe cont + rute web
POST /mapari/salvate(update) siPOST /mapari/salvate/sterge(delete) cu CSRF + PRG/HTMX swap. Nu schimba logica de rezolvare dinmapping.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 acelcod_op_servicese 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_mappingpe acelcod_op_servicese deblocheaza automat (test: rand blocat → editez maparea → randul trece dinneeds_mapping). python3 -m pytest -qtrece.
- 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(querycolumn_mappings+ rute edit/delete),tests/test_web_formate_coloane.py(~2 fisiere) - Test intai (RED):
test_lista_formate_coloane— intoarce randurilecolumn_mappingsale contului cu coloanele (dinsignature_coloane/json_mapare),format_datasi un contor de utilizare (cateimport_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 schimbajson_mapare/format_datapentru 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 -qtrece.
- 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_mapariimbogatit 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/maparicontine cele trei sectiuni (De rezolvat / Mapari operatii salvate / Formate de coloane salvate);test_cont_fara_mapari—/_fragments/contnu mai contine sectiuni de mapari. - Continut:
_mapari.htmlreorganizat 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 -qtrece.
- 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 randneeds_datadin 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_reviewmotivul (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 -qtrece.
- 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_submissionsaccepta parametri de filtru),app/web/templates/_coada.html(controale filtru),app/web/templates/_submissions.html,app/schema.sql(index pesubmissions(account_id, status)daca lipseste — exista dejaidx_submissions_account_status, deci probabil zero schimbari),tests/test_web_filtrare.py(~4 fisiere) - Test intai (RED):
test_filtru_stare—?status=needs_dataintoarce doar acele randuri (scoped pe cont);test_filtru_vehicul— cautare text pe nr/VIN (case-insensitive);test_filtru_data— intervaldata_prestatie. Toate scoped pe cont, fara leak. - Continut: controale HTMX (select stare + input text vehicul + interval data) care reincarca
/_fragments/submissionscu query string; filtrarea pe vehicul/data se face dupa parsareapayload_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 -qtrece.
- 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(rutaPOST /trimitere/{id}/corecteaza),app/web/templates/_trimitere_detaliu.html(form de corectie pe randurileneeds_data/needs_mapping),tests/test_web_corectie.py(~3 fisiere) - Test intai (RED):
test_corectie_needs_data— un randneeds_datadin lipsa odometru: completez odometru → re-validare (validation.py) → statusqueued, payload actualizat, idempotency recalculata;test_corectie_sent_interzis— un randsent/FINALIZATANU 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 prinvalidation.py(fara reguli noi), reconstruiestepayload_json+ recalculeazaidempotency_key(canonicalize → build_key, ca la enqueue), seteaza statusqueuedsinext_attempt_at=now. Nu atinge worker-ul / masina de stari (doar re-enqueue). Randurilesent/FINALIZATAraman read-only (gard explicit).- Coliziune idempotency (decizie eng review): INAINTE de UPDATE, verifica daca noua
idempotency_keyexista 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 peidempotency_keyar 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).
- Coliziune idempotency (decizie eng review): INAINTE de UPDATE, verifica daca noua
- Acceptance criteria:
- Un rand
needs_datacorectat valid trece inqueuedcu payload + idempotency actualizate. - Randurile
sent/FINALIZATAnu 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 -qtrece.
- Un rand
- Verificare E2E: browser — corectez odometru pe un rand blocat, il vad trecand in asteptare;
cu worker
--sendpe 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 infragment_status/panel context, transmise la tab-bar),tests/test_web_badge.py(~3 fisiere) - Test intai (RED):
test_badge_mapari— cand exista operatiineeds_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 -qtrece.
- 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 laacasa; grep dupa toate referinteletab=import//_fragments/importinainte de stergere; test dedicat (US-002). - Citirea
payload_jsonpentru 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.htmlsi_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.htmlar 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/FINALIZATAraman 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_keyINAINTE de UPDATE si o comunica clar (rand-duplicat identificat), fara 500/duplicat. - Decizie DRY (US-003): helper partajat
app/payload_view.pyfolosit si de web si deGET /v1/prezentari— o singura sursa de extragere payload→campuri. - Aserțiune adaugata: test ca
submissions.payload_jsone 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 -q→ 483 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✓/✗+ 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.pypur/defensiv, refolosit deGET /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_mappingsscoped pe cont, re-rezolvare la edit cod. - US-007:
_fragments/mapari3 sectiuni cu empty states;_fragments/contfara 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.
- US-001:
- Regresia de aur: flux import→commit→coada + canal API
POST /v1/prezentariintacte (test_import_ui/e2e,test_api,test_web_tabs); deep-link-uri?tab=valide. - Non-Goals:
git diff --statconfirmaapp/worker/,app/idempotency.py,app/mapping.py,app/schema.sqlNEATINSE; 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 pehttp://localhost:8000/.
Findings /code-review (high) — reparate inainte de inchidere
- Corectie + needs_mapping (sever): ruta
POST /trimitere/{id}/corecteazare-punea inqueuedfara re-rezolvarea prestatiilor → un cod nemapat putea ajunge la RAR cucodPrestatie: null(FINALIZATA ireversibil). Fix: re-ruleazaresolve_prestatii+has_no_auto_send(careresolve_account); cod nemapat ramaneneeds_mapping. Test:test_corectie_needs_mapping_nu_ajunge_in_coada. - 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.
- Coliziune idempotency non-atomica (cursa TOCTOU → 500): Fix:
try/except sqlite3.IntegrityErrorin jurul UPDATE-uluiqueued, mesaj prietenos in loc de 500. - 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.