Files
rar-autopass/docs/prd/prd-5.4-erori-3-niveluri.md
Claude Agent 14e1c463f0 feat(errors): erori pe 3 niveluri (problema+cauza+fix) pe API si UI (PRD 5.4)
Catalog central pur app/errors.py ca sursa unica cod->{problema,fix},
consumat de API+UI+worker. Aditiv (field/message pastrate la octet) +
rar_error stocat superset. Scope: fluxul de declarare; login/signup/CSRF
neatinse. labels.parse_erori degradeaza gratios; UI progresiv AA light+dark.
631 teste.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-23 10:28:09 +00:00

30 KiB

PRD 5.4 — Erori pe 3 niveluri (problema + cauza + fix) pe API si UI

Stare: inchis

Proces complet: docs/ROADMAP.md §5. Contract RAR (sursa de adevar): docs/api-rar-contract.md. Starea trece: draft → aprobat → in-executie → verify-pass → inchis (actualizata de lead).

1. Obiectiv

Fiecare eroare pe care o vede un integrator (canal API) sau un service-auto (UI web) sa raspunda la trei intrebari, in loc de una singura:

  1. Problema — ce s-a intamplat (categorie umana, scurta). "VIN invalid."
  2. Cauza — de ce, specific. "VIN-ul are 16 caractere; RAR cere exact 17."
  3. Fix — ce sa faci acum. "Verifica VIN-ul pe talon (pozitia E); 17 caractere majuscule, fara O/I/Q."

Motivul (lentila DX, Etapa 5): erorile plate ("Fisier nerecunoscut", "cheie API invalida", "VIN invalid") transfera incertitudinea catre utilizator — care fie ghiceste, fie deschide un tichet de suport. Cele trei niveluri inchid bucla la sursa: mai putine tichete, integrare self-service.

Invariant de corectitudine (motivul cheie de design): cele trei niveluri pentru un anumit cod de eroare se definesc o singura data, intr-un catalog central pur (app/errors.py), consumat de toate suprafetele (API + UI + worker). Daca textul s-ar duplica pe canale, API si UI ar putea diverge — un cod ar spune un lucru in JSON si altul in dashboard. Catalogul unic face imposibila divergenta (acelasi pattern care a facut 5.2 corect: o singura sursa partajata, nu doua copii).

2. Non-Goals (anti scope-creep)

  • NU acoperim login / signup / CSRF / auth 401 (decizie utilizator 2026-06-22: focus pe fluxul de declarare). Aceste suprafete sunt edge / dev si raman mesaje plate. auth_routes.py, csrf.py, handler-ele LoginRequired/AdminRequired/CsrfError din main.py — NEATINSE.
  • NU breaking change pe API (decizie utilizator 2026-06-22: aditiv). Pastram campurile existente (field, message, error, type/loc/msg) si ADAUGAM cod, problema, cauza, fix. Clientii vechi (ROAAUTO / soft propriu integrat la 5.1/5.2) nu se strica; cei noi pot afisa 3 niveluri.
  • NU schema nouasubmissions.rar_error e deja TEXT si stocheaza JSON; doar imbogatim continutul. Zero migrare.
  • NU apel live nou la RAR — pentru erorile RAR 400 imbracam mesajul RAR existent (passthrough ca cauza) intr-un invelis 3-niveluri; nu schimbam clasificarea transient/terminal a worker-ului.
  • NU schimbam regulile de validarevalidate_prezentare valideaza exact aceleasi conditii; doar ataseaza cod + nivelele la fiecare eroare existenta.
  • NU schimbam masina de stari / idempotenta / mapping-rezolvarea / nomenclatorul.
  • NU traducere i18n — romana, ca tot proiectul.

3. Stories atomice

US-001: Catalog central de erori (app/errors.py) — DONE (588 teste)

Ca dezvoltator al gateway-ului vreau o singura sursa de adevar care mapeaza fiecare cod de eroare la (problema, fix), cu un helper care construieste obiectul de eroare 3-niveluri, pentru ca API-ul, UI-ul si worker-ul sa nu poata diverge in ce explica utilizatorului.

  • Depinde de: —
  • Fisiere: app/errors.py (modul pur nou), tests/test_errors.py (~2 fisiere)
  • Test intai (RED): tests/test_errors.py
    • test_catalog_complet — fiecare intrare are problema + fix ne-goale (string).
    • test_eroare_construieste_3nivelurieroare("VIN_FORMAT", field="vin", cauza="...") intoarce dict cu cheile {field, cod, problema, cauza, fix, message}, cod=="VIN_FORMAT", problema/fix luate din catalog, cauza cea data.
    • test_message_back_compat — cand cauza e dat, message == cauza (alias pentru clientii vechi).
    • test_cod_necunoscut_ridicaeroare("INEXISTENT") ridica KeyError/ValueError (nu inventeaza text gol — drift prins la dezvoltare).
  • Acceptance criteria:
    • app/errors.py pur (fara import DB/HTTP), expune CATALOG: dict[str, dict] (cod → {problema, fix}) si eroare(cod, *, field=None, cauza=None) -> dict.
    • Obiectul de eroare are exact cheile {field, cod, problema, cauza, fix, message}; message (back-compat) == cauza cand cauza e dat, altfel == problema.
    • CATALOG contine codurile pentru toate suprafetele in scop (validare continut, mapare op→cod, RAR, import) — vezi lista din §4. Fiecare intrare are problema + fix ne-goale.
    • fix e specific si actionabil (finding CEO): numeste un loc/o actiune concreta (ex. "talon, pozitia E", "tab-ul Mapari", "salveaza ca .csv UTF-8"), NU boilerplate generic ("verifica datele"). Un fix generic = eroare plata mai lunga; testul de calitate al livrabilei. (Verificat la review uman.)
    • eroare cu cod absent din CATALOG ridica eroare (nu intoarce text gol).
    • python3 -m pytest -q verde.
  • Verificare E2E: unitar (modul pur) — fara canal.

US-002: Validarea de continut emite 3 niveluri (validation.py) — DONE

Ca integrator API vreau ca fiecare eroare de validare (VIN/nr/data/odometru/prestatii/b64) sa spuna problema + cauza + fix, pentru ca sa corectez payload-ul fara sa ghicesc formatul cerut.

  • Depinde de: US-001
  • Fisiere: app/validation.py, tests/test_validation.py (~2 fisiere)
  • Test intai (RED): tests/test_validation.py
    • test_vin_invalid_are_3niveluri — VIN cu O/I/Q → eroare cu cod=="VIN_FORMAT", problema, fix ne-goale, field=="vin", message pastrat (back-compat).
    • test_data_prea_veche_cod / test_data_viitor_cod / test_data_format_cod — coduri distincte.
    • test_odometru_initial_lipsa_cod / test_odometru_ordine_cod — coduri distincte.
    • test_prestatii_goale_cod, test_b64_invalid_cod.
    • test_back_compat_field_message — fiecare eroare are inca field + message (forma veche pastrata pentru clientii existenti).
    • test_toate_codurile_in_catalog — fiecare cod emis de validate_prezentare exista in CATALOG.
  • Acceptance criteria:
    • validate_prezentare intoarce erori cu {field, message, cod, problema, cauza, fix} (aditiv — field + message neschimbate la octet fata de azi).
    • Fiecare regula are un cod stabil (vezi lista §4); textul (problema/fix) vine din errors.eroare.
    • Byte-compat (finding Eng): cauza = mesajul existent verbatim (eventual + context specific precum lungimea VIN gasita); message ramane EXACT string-ul de azi → testele existente care compara message raman verzi fara modificari.
    • Toate testele existente (test_validation.py, test_api.py, test_validare_dryrun.py) raman verzi (forma veche field/message intacta).
    • python3 -m pytest -q verde.
  • Verificare E2E: canal API — POST /v1/prezentari/valideaza cu VIN invalid → erori[0] are cele 3 niveluri + field/message vechi.

US-003: Propagare 3 niveluri prin mapare + raspuns API (mapping.py, router.py) — DONE

Ca integrator API vreau ca raspunsul /valideaza si motivul stocat (rar_error) sa transporte cele 3 niveluri pentru validare SI pentru coduri nemapate / auto-send oprit, pentru ca verdictul sa fie la fel de explicit indiferent de ramura (needs_data / needs_mapping).

  • Depinde de: US-001, US-002
  • Fisiere: app/mapping.py, app/api/v1/router.py, app/models.py, tests/test_mapping.py, tests/test_validare_dryrun.py (~5 fisiere)
  • Test intai (RED):
    • test_mapping.py::test_unmapped_are_3niveluri — cod_op_service necunoscut → classify_prezentare produce needs_mapping cu cod=="COD_NEMAPAT" + problema/cauza (codul concret)/fix in structura.
    • test_mapping.py::test_auto_send_oprit_3niveluri — mapare cu auto_send=0cod=="AUTO_SEND_OPRIT"
      • 3 niveluri.
    • test_mapping.py::test_needs_data_pass_through — erorile de validare imbogatite trec neatinse prin classify_prezentare in rar_error.
    • test_validare_dryrun.py::test_erori_au_3niveluri/valideaza cu VIN invalid → erori[i] are cod/problema/cauza/fix; cu cod_op nemapat → nemapate carry 3 niveluri.
  • Acceptance criteria:
    • classify_prezentare pastreaza erorile de validare imbogatite (pass-through) in rar_error.
    • Ramura needs_mapping (cod nemapat) si nota auto_send=0 se construiesc prin errors.eroare (3 niveluri), nu string-uri ad-hoc.
    • rar_error stocat = SUPERSET al formei de azi (finding Eng critic): pastreaza structura veche (needs_data → array [{field,message,...}]; needs_mapping{unmapped:[...], ...}), ADAUGA cheile 3-niveluri. Asa labels.motiv_uman actual ramane functional intre Val 3 si Val 4 (nu se strica pana e actualizat in US-006) si nu e nevoie de migrare. Aplica principiul aditiv si la datele stocate, nu doar la API.
    • Raspunsul /valideaza (erori, nemapate) include cod/problema/cauza/fix aditiv; modelele din models.py accepta cheile noi fara a respinge (verifica: tipul erori/nemapate e permisiv sau extins; nu schema stricta care respinge chei in plus).
    • Teste subset, nu egalitate exacta (finding Eng): testele existente care comparau == un dict de eroare se actualizeaza la asertii de subset (comportament identic, doar chei aditive).
    • POST /v1/prezentari (calea reala) ramane cu comportament identic — test_api.py verde; rar_error stocat e JSON 3-niveluri pentru needs_data/needs_mapping.
    • python3 -m pytest -q verde.
  • Verificare E2E: canal API — /valideaza cu (a) VIN invalid → needs_data + 3 niveluri, (b) cod_op nemapat → needs_mapping + 3 niveluri. Regresia de aur: POST /v1/prezentari enqueue neschimbat.

US-004: Erorile RAR (400/401) imbracate pe 3 niveluri in worker (worker, rar_client.py) — DONE

Ca service-auto vreau ca o respingere de la RAR sa fie tradusa in problema + cauza (mesajul RAR exact) + fix, in loc de un JSON brut, pentru ca sa inteleg ce a respins RAR fara sa citesc JSON.

  • Depinde de: US-001
  • Fisiere: app/worker/__main__.py, app/rar_client.py (eventual), tests/test_worker_*.py (~3 fisiere)
  • Test intai (RED): tests/test_worker_rar_errors.py (nou) —
    • test_rar_400_stocheaza_3niveluriRarError(status=400, field_errors=[{field,message}]) → worker stocheaza rar_error JSON cu cod=="RAR_VALIDARE", problema, cauza continand mesajul RAR exact (passthrough), fix cu indrumare; pastreaza si field_errors originale.
    • test_rar_401_creds_3niveluriRarAuthErrorcod=="RAR_CREDS_INVALIDE" + 3 niveluri, stare error (fara retry, neschimbat).
    • test_clasificare_transient_neschimbata — 5xx/timeout raman transient (retry), comportament identic.
  • Acceptance criteria:
    • La RAR 400, rar_error stocat = SUPERSET (finding Eng): pastreaza array-ul field_errors original [{field,message}] (ca labels.py actual sa-l randeze per-camp) + ADAUGA invelisul 3-niveluri (cod=RAR_VALIDARE, cauza=mesajul RAR exact passthrough, fix=indrumare).
    • La RAR 401, rar_error = 3-niveluri (cod=RAR_CREDS_INVALIDE), stare error (fara retry).
    • Clasificarea transient vs terminal NESCHIMBATA (5xx/408/429 retry; 4xx terminal); reconcilierea anti-duplicat neatinsa.
    • Fara echo de creds in rar_error (mesajul RAR nu contine parola; verificat in test).
    • python3 -m pytest -q verde.
  • Verificare E2E: worker pe RAR test (daca exista creds) — prezentare cu VIN invalid → RAR 400 → needs_data cu rar_error 3-niveluri vizibil in dashboard. (Live optional — vezi riscuri.)

US-005: Erorile de import imbracate pe 3 niveluri (import_router.py) — DONE

Ca service-auto care incarca un fisier vreau ca erorile de upload / mapare coloane / commit sa spuna ce e gresit, de ce si cum repar, pentru ca sa pot incarca singur fara suport.

  • Depinde de: US-001
  • Fisiere: app/api/v1/import_router.py, tests/test_import_*.py (~2 fisiere)
  • Test intai (RED): tests/test_import_errors.py (nou) —
    • test_fisier_prea_mare_3niveluri — 413 → detail contine cod=="IMPORT_FISIER_PREA_MARE" + problema/cauza (nr randuri vs max)/fix; pastreaza error/message vechi.
    • test_antet_neclar_3niveluri — HeaderError → cod=="IMPORT_ANTET_NECLAR" + 3 niveluri + found.
    • test_encoding_3niveluri, test_fisier_nerecunoscut_3niveluri, test_multiple_sheets_3niveluri.
    • test_fara_mapare_coloane_3niveluri — preview fara mapare → IMPORT_FARA_MAPARE_COLOANE.
    • test_confirmare_gresita_3niveluri — commit cu n gresit → IMPORT_CONFIRMARE_GRESITA + n_ok.
    • test_override_ilizibil_3niveluri — editare cu override corupt → IMPORT_OVERRIDE_ILIZIBIL.
  • Acceptance criteria:
    • Fiecare HTTPException de import in scop are detail SUPERSET: pastreaza error/message/ campurile contextuale existente (sheets/found/n_ok) si ADAUGA cod/problema/cauza/fix.
    • Codurile vin din errors.eroare (catalog), nu string-uri ad-hoc.
    • Toate testele de import existente raman verzi (forma veche error/message intacta; asertii de subset unde comparau exact — finding Eng).
    • python3 -m pytest -q verde.
  • Verificare E2E: canal API — POST /v1/import cu fisier prea mare / antet neclar → detail 3-niveluri.

US-006: Componenta UI de eroare pe 3 niveluri + stari submission (labels.py, templates core) — DONE

Ca service-auto vreau ca dashboard-ul sa afiseze problema (bold) + cauza + fix (linie de actiune) pentru randurile needs_data / needs_mapping / error si per-camp in preview, pentru ca sa stiu ce sa fac fara sa deschid un tichet.

  • Depinde de: US-001, US-002, US-003, US-004 (codurile/structura trebuie sa existe)
  • Fisiere: app/web/labels.py, app/web/templates/_eroare.html (macro nou), app/web/templates/base.html (CSS), app/web/templates/_trimitere_detaliu.html, app/web/templates/_status.html, app/web/templates/_preview_rand.html, tests/test_web_*.py (~7 fisiere)
  • Test intai (RED): tests/test_web_erori.py (nou) —
    • test_motiv_uman_3nivelurilabels.motiv_uman pe rar_error 3-niveluri intoarce problema/cauza/fix (nu doar un string plat); fallback gratios la rar_error vechi/string/corupt.
    • test_detaliu_afiseaza_fix/_fragments/... pe submission needs_data → HTML contine textul fix.
    • test_preview_rand_per_camp_fix — preview rand needs_data → fiecare camp invalid arata fix-ul.
  • Acceptance criteria:
    • Progresiv, dashboard compact pastrat (finding Design — nu regresa 3.5/3.6): in lista/rand se vede problema (eticheta umana existenta) + fix-ul ca o singura linie de actiune; cauza + mesajul tehnic RAR integral stau in detaliu / <details> (nu 3 linii per rand → zid de text). Cele 3 niveluri complete apar in panoul de detaliu si in preview-ul de rand, nu in fiecare rand din lista.
    • Scannabil (finding Design): nivelele au tratament vizual / etichete care le fac parcurgibile fara citire integrala (ex. "Problema" / "De ce" / "Cum repari", sau ierarhie vizuala clara).
    • Macro Jinja _eroare.html randeaza consistent; mesajul tehnic RAR integral ramane in <details> (pattern existent in _trimitere_detaliu.html).
    • labels.py citeste catalogul / parseaza rar_error 3-niveluri; degradeaza gratios pe forma veche (string / [{field,message}] / JSON corupt) — fara 500 (lectia 3.6 cu decriptarea).
    • Reutilizeaza, NU inlocuieste, pattern-ul bun din _status.html (problema + subtext-hint deja ~ problema + fix); _trimitere_detaliu.html, _preview_rand.html folosesc macro-ul.
    • CSS in paleta light+dark din 5.3 — fara culori hardcodate; accentul de "fix/actiune" trece AA in AMBELE teme (lectia 5.3: --ok pica AA ca text); distinct de rosul de eroare.
    • python3 -m pytest -q verde.
  • Verificare E2E: browser HTMX pe / — rand needs_data arata problema+cauza+fix; preview rand invalid arata fix per-camp; dark + light (5.3) ambele lizibile.

US-007: 3 niveluri in import / upload / preview UI + rute web (routes.py, templates import) — DONE

Ca service-auto vreau ca erorile de la upload, mapare coloane si preview import sa apara cu cele 3 niveluri in interfata, pentru ca sa rezolv singur problemele de fisier.

  • Depinde de: US-006 (macro _eroare.html), US-005 (codurile de import)
  • Fisiere: app/web/routes.py, app/web/templates/_upload.html, app/web/templates/_mapcoloane.html, app/web/templates/_preview_import.html, tests/test_web_*.py (~5 fisiere)
  • Test intai (RED): tests/test_web_import_erori.py (nou) —
    • test_upload_eroare_3niveluri — upload fisier invalid prin ruta web → fragment contine problema+fix.
    • test_mapcoloane_format_json_3niveluri — format coloane JSON invalid → COLOANE_FORMAT_JSON 3 niveluri.
    • test_cod_rar_necunoscut_3niveluri — mapare operatie cu cod RAR inexistent → 3 niveluri + sugestie.
  • Acceptance criteria:
    • Caile web de eroare din routes.py (upload, mapare coloane, format JSON, cod RAR necunoscut, corectie) trec context 3-niveluri catre template (din catalog), nu string plat.
    • _upload.html, _mapcoloane.html, _preview_import.html folosesc macro-ul _eroare.html.
    • Forma veche (mesaj plat) inca functioneaza unde nu exista cod (fara regresie); toate testele web existente verzi.
    • python3 -m pytest -q verde.
  • Verificare E2E: browser HTMX — upload fisier prea mare → 3 niveluri; mapare coloane JSON invalid → fix.

US-008: Documentare envelope de eroare imbogatit (api-rar-contract.md) — DONE

Ca integrator nou vreau sa stiu forma exacta a erorilor (campuri vechi + cele 3 niveluri noi) si lista de coduri, pentru ca sa-mi construiesc gestionarea de erori fara reverse-engineering.

  • Depinde de: US-001, US-002, US-003, US-005 (codurile finale)
  • Fisiere: docs/api-rar-contract.md (~1 fisier)
  • Test intai (RED): — (doc; verificare manuala). Optional tests/test_errors.py::test_doc_acopera_codurile daca e fezabil ieftin (verifica ca fiecare cod din CATALOG apare in doc).
  • Acceptance criteria:
    • Sectiune noua in api-rar-contract.md: forma erorii ({field, message, cod, problema, cauza, fix}), nota de back-compat (campurile vechi raman), tabel cod → problema/fix.
    • Mentioneaza ca /valideaza si rar_error stocat folosesc aceeasi forma.
  • Verificare E2E: review uman al documentului.

4. Catalog de coduri (referinta — definit in US-001)

Domeniu Cod problema (nivel 1) unde
Validare VIN_FORMAT VIN invalid US-002
Validare NR_INMATRICULARE_FORMAT Numar de inmatriculare invalid US-002
Validare DATA_FORMAT Data prestatiei in format gresit US-002
Validare DATA_PREA_VECHE Data prestatiei prea veche US-002
Validare DATA_VIITOR Data prestatiei in viitor US-002
Validare ODOMETRU_FINAL_FORMAT Odometru final invalid US-002
Validare ODOMETRU_INITIAL_LIPSA Lipseste odometrul initial US-002
Validare ODOMETRU_INITIAL_FORMAT Odometru initial invalid US-002
Validare ODOMETRU_INITIAL_ORDINE Odometru initial > final US-002
Validare PRESTATII_GOALE Nicio prestatie US-002
Validare B64_INVALID Imaginea nu e base64 valid US-002
Mapare COD_NEMAPAT Lipseste codul RAR al operatiei US-003
Mapare AUTO_SEND_OPRIT Necesita confirmare manuala US-003
RAR RAR_VALIDARE RAR a respins prezentarea US-004
RAR RAR_CREDS_INVALIDE Credentiale RAR invalide US-004
Import IMPORT_FISIER_PREA_MARE Fisier prea mare US-005
Import IMPORT_ANTET_NECLAR Antet de coloane neclar US-005
Import IMPORT_ENCODING Codare de caractere nesuportata US-005
Import IMPORT_FISIER_NERECUNOSCUT Fisier nerecunoscut US-005
Import IMPORT_MULTIPLE_SHEETS Mai multe foi in fisier US-005
Import IMPORT_FARA_MAPARE_COLOANE Coloanele nu sunt mapate US-005
Import IMPORT_CONFIRMARE_GRESITA Numar confirmat gresit US-005
Import IMPORT_OVERRIDE_ILIZIBIL Editarea anterioara nu se poate citi US-005
Coloane COLOANE_FORMAT_JSON Format de coloane (JSON) invalid US-007

Lista finala se fixeaza in US-001 (catalog) + drift-test (test_toate_codurile_in_catalog). Codurile nu sunt parte din contractul de back-compat (campuri noi); mesajele RAR exacte raman in cauza.

4b. Intrebari deschise (rezolvate inainte de executie)

  • Latimea scope-ului — REZOLVAT: focus pe fluxul de declarare (validare continut, RAR 400, import, mapare op→cod); login/signup/CSRF/auth raman plate. [user 2026-06-22]
  • Compatibilitate API — REZOLVAT: aditiv, fara breaking change (campuri vechi pastrate, adaugam cod/problema/cauza/fix); documentat in api-rar-contract.md. [user 2026-06-22]

5. Riscuri

  • Drift catalog (cod folosit dar absent din CATALOG, sau text gol) → errors.eroare ridica pe cod necunoscut (US-001) + test_toate_codurile_in_catalog (US-002) — drift prins la dezvoltare, nu in prod.
  • fix generic, fara valoare (finding CEO) → criteriu de calitate explicit (US-001): fiecare fix numeste loc/actiune concreta; verificat la review uman + in design-review-ul UI.
  • rar_error stocat schimbat rupe labels.py intre valuri (finding Eng) → stocam SUPERSET (old keys intacte) in US-003/US-004; labels.py actual ramane functional pana la US-006; zero migrare.
  • Teste cu egalitate exacta de dict (finding Eng) → se trec la asertii de subset (acelasi comportament, chei aditive); contractul de back-compat e pe campurile vechi, nu pe absenta celor noi.
  • Breaking change accidental pe API → aditiv prin constructie + testele existente (test_api.py, test_validare_dryrun.py, test_import_*.py) sunt contractul de back-compat: raman verzi = forma veche field/message/error intacta. AC explicit in fiecare story backend.
  • 500 la afisare UI pe rar_error vechi/corupt (lectia 3.6: decriptare neprotejata) → labels.py degradeaza gratios pe forma veche (string / [{field,message}] / JSON corupt), test dedicat (US-006).
  • Scurgere de creds prin cauza (mesaj RAR passthrough) → mesajele RAR de validare nu contin parola (field/message pe campuri de prezentare); test no-echo (US-004). Handler-ul 422 din main.py deja dropeaza input/ctx.
  • Suprafata mare → 8 stories pe valuri cu fisiere disjuncte (vezi §6); backend (US-002/004/005) paralel, UI secvential dupa backend, docs paralel.
  • Verbozitate UI (3 niveluri = zgomot) → progresiv: problema + fix vizibile, mesajul tehnic RAR integral ramane in <details> (pattern existent in _trimitere_detaliu.html).
  • Conflict pe fisiere comune (lectia 5.1: clobber la worktree/merge) → mapping.py atins doar de US-003; routes.py doar de US-007; templates partitionate intre US-006 (detaliu/status/preview_rand) si US-007 (upload/mapcoloane/preview_import); macro _eroare.html creat in US-006, consumat in US-007 (dependenta de val, nu paralel).

6. Valuri de executie (graful de dependente)

Val 1: [US-001]                          backbone catalog (singur — toti depind de el)
Val 2: [US-002] [US-004] [US-005]        backend paralel, fisiere disjuncte
          validation.py  worker/rar  import_router.py
Val 3: [US-003]                          mapping.py + router.py + models.py (depinde US-002)
Val 4: [US-006] [US-008]                 UI core (labels+templates) || docs (api-rar-contract.md) — disjuncte
Val 5: [US-007]                          UI import/web (depinde US-006 pt macro)
  • Val 2: max 2-3 teammates simultan (ROADMAP §5.5). validation.py / worker+rar_client / import_router.py sunt disjuncte → 3 teammates paraleli OK.
  • Val 4: US-006 (templates+labels) si US-008 (doc) ating fisiere disjuncte → paralel.
  • Dupa fiecare val: lead-ul ruleaza python3 -m pytest -q (regresie) si bifeaza stories in PRD.

7. Review-uri de plan (aplicate inainte de cod — ROADMAP §5.3)

Obligatorii: /plan-ceo-review (valoare/scope) + /plan-eng-review (fezabilitate/teste). /plan-design-review — DA (atinge UI: US-006, US-007). Rezultatele se aplica IN acest PRD inainte de cod.

CEO (valoare/scope) — PASS. Problema corecta (DX: erorile plate transfera incertitudinea la user → tichete de suport), aliniata cu directia Etapa 5. Scope-ul (declaration flow) e cel mai direct la valoare; login/signup/CSRF taiate corect (decizie user). Inversiune ("ce-l face sa esueze?"): un fix generic ("verifica datele") face dintr-o eroare 3-niveluri doar o eroare plata mai lunga — valoarea traieste sau moare in specificitatea fix-ului. → Aplicat ca criteriu de calitate explicit (US-001 AC + risc): fiecare fix numeste loc/actiune concreta. Deferare constienta: POST /v1/prezentari real intoarce doar status, nu erori inline — integratorul afla "de ce" printr-un GET sau prin /valideaza; a adauga erori inline pe ruta reala ar fi aditiv + util, dar e scope creep peste 5.4 → notat ca oportunitate viitoare, nu in scope acum.

Eng (fezabilitate/teste) — PASS cu 3 conditii (aplicate in PRD). Catalogul pur + helper = backbone fezabil, drift prins la dev. (1) Critic — rar_error stocat trebuie SUPERSET (old keys intacte): altfel labels.motiv_uman se strica intre Val 3 (backend schimba forma) si Val 4 (UI o citeste); superset = zero migrare + degradare gratioasa (lectia 3.6). Aplicat in US-003/US-004 AC + risc. (2) Byte-compat: validarea pune mesajul existent verbatim ca cauza, message ramane identic → testele pe message raman verzi (US-002). (3) Teste subset, nu egalitate exacta de dict (chei aditive) — aplicat in US-002/003/005. models.py trebuie sa accepte chei in plus pe erori/nemapate (verificat in US-003). Worker testabil cu rar_client mock-uit (fara live RAR).

Design (UI — US-006/US-007) — PASS cu 3 conditii (aplicate in PRD). (1) Progresiv, nu regresa dashboard-ul compact din 3.5/3.6: in lista/rand → problema + fix pe o linie; cauza + tehnic RAR in detaliu/<details>; cele 3 niveluri complete doar in panoul de detaliu + preview-ul de rand. (2) Scannabil: etichete/ierarhie vizuala ("Problema"/"De ce"/"Cum repari") ca user-ul sa parcurga fara citire integrala. (3) AA in ambele teme (lectia 5.3): accentul de "fix/actiune" trece AA light+dark, fara culori hardcodate, distinct de rosul de eroare. Reutilizeaza pattern-ul bun din _status.html (problema + subtext = ~ problema + fix), nu il inlocui.


Raport VERIFY

Verificator independent (context curat, rol qa-only), 2026-06-22. VERDICT GLOBAL: PASS.

1. Suita — PASS. python3 -m pytest -q → 628 passed, 234 warnings.

2. Acceptance criteria US-001..US-008 — toate PASS. Verificate direct pe cod + probe live (TestClient + SQLite temp):

  • US-001: catalog pur, 24 coduri (MISSING:[], EMPTY:[]), eroare('INEXISTENT')→KeyError, chei {field,cod,problema,cauza,fix,message}, message==cauza/==problema corect.
  • US-002: byte-compat confirmatmessage VIN identic la octet cu git show HEAD:app/validation.py (textul vechi pus ca cauza); erori complete pe 6 chei.
  • US-003: rar_error SUPERSET confirmat — needs_mapping pastreaza unmapped; auto_send pastreaza auto_send; needs_data ramane array cu field+message; nemapate din /valideaza poarta 3 niveluri; models.py:113-114 permisiv (list[dict]).
  • US-004: RAR 400→RAR_VALIDARE (field/message pastrate, cauza=mesaj RAR passthrough), RAR 401→RAR_CREDS_INVALIDE fara retry fara echo creds; _is_transient neschimbat.
  • US-005: detalii import superset (error/message/sheets/found/n_ok + 3 niveluri).
  • US-006: parse_erori degradeaza gratios (string plat / [{field,message}] fara cod / JSON invalid / None — fara exceptie); detaliu randeaza Problema/De ce/Cum repari + <details> tehnic; CSS doar variabile paleta, AA in ambele teme (accent 5.17/5.33, err 4.83/5.06), accent ≠ rosu.
  • US-007: upload/mapcoloane prin macro; per-camp camp-fix.
  • US-008: contract documentat (forma 6 chei, back-compat, tabel cod→problema/fix complet).
  • Calitate fix (finding CEO) — PASS: specifice/actionabile ("talon pozitia E", "tab-ul Mapari", "CSV UTF-8", "maxim 5000 randuri"); niciun fix generic.
  • Non-Goal — PASS: auth_routes.py/csrf.py/main.py NEATINSE (confirmat git status).

3. E2E canal API — PASS. /valideaza: (a) VIN invalid → erori[0] cu cod/problema/cauza/fix + field/message vechi; (b) cod_op nemapat → nemapate[0] cu cod_op_service/denumire + 3 niveluri. POST /v1/prezentari real → 200 {status:queued}.

4. E2E canal web (UI, TestClient pe fragmente) — PASS. Upload invalid → _upload.html cu eroare-3n + "Cum repari" + fix; submission needs_data → _trimitere_detaliu.html cu 3 niveluri + <details> tehnic. (Browser Playwright neutilizat — fragmentele TestClient acopera criteriile.)

5. Regresia de aur — PASS (live neprobat, conform asteptarii). POST /v1/prezentari→queued + test_api.py verde. Flux LIVE RAR (worker→FINALIZATA pe RAR test) NEPROBAT — lipsesc AUTOPASS_CREDS_KEY+creds test+--send in mediu; NU e FAIL al 5.4 (endpoint-urile/UI noi nu ating trimiterea; worker-ul doar imbogateste rar_error).

Observatie minora (ne-blocanta), REPARATA la CLOSE: exemplul JSON din api-rar-contract.md avea message/cauza cu un text VIN usor diferit de cel real emis de validation.py:72. Corectat (ambele linii) sa coincida verbatim cu codul.