# 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 / `<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 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 + `<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.
