# 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 noua** — `submissions.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 validare** — `validate_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_3niveluri` — `eroare("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_ridica` — `eroare("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=0` → `cod=="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_3niveluri` — `RarError(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_3niveluri` — `RarAuthError` → `cod=="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_3niveluri` — `labels.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 / `
` (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 `
` (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 `
` (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/`
`; 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 confirmat** — `message` 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 + `
` 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 + `
` 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.