5.15 (propagare design + dashboard editare) si 5.14 (mapare LLM distilata) inchise dupa /code-review high. 8 buguri reparate TDD: - HIGH modal nu se deschidea pe randul slim (base.html: trimitere-slim) - HIGH /repune trunchia prestatii (declaratie incompleta la RAR) -> iterare peste existing, codes pozitional - HIGH embeddings incarca model ~230MB degeaba pe corpus gol -> poarta has_corpus() - HIGH picker chips gol pe re-render eroare -> conn/account_id pe toate ramurile - MED obs re-derivat dupa stergere explicita -> _merge_override pastreaza obs='' - MED mapare salvata fara denumire poluă GOLD -> _record_gold_validation guard - MED typo nome_prestatie -> nume_prestatie in select /repune - MED bucketare timp +3h gresita iarna -> SQLite localtime + TZ=Europe/Bucharest Embeddings WIRE-uit functional (PRD #15, decizie user): ensure_embeddings_corpus construieste corpus din nomenclator, gated pe AUTOPASS_EMBEDDINGS_ENABLED (default off). Marime model corectata ~50MB->~230MB (estimare PRD gresita). Cleanup: hoist load_* din bucla bulk-fix; import re la top. Regresie: 1256 passed, 1 deselected (live), 0 failed. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
612 lines
48 KiB
Markdown
612 lines
48 KiB
Markdown
# PRD 5.15 — Propagare design landing in aplicatie (dashboard compact + editare slim, VIN unic, prestatii multi-select)
|
||
|
||
**Stare**: inchis (2026-06-28; CLOSE dupa `/code-review high` -> 8 buguri reparate TDD; regresie 1256 passed, 1 deselected live; E2E browser real ramane OPEN — mediu sandbox fara Playwright)
|
||
|
||
> Proces complet: `docs/ROADMAP.md` §5. Contract RAR (sursa de adevar): `docs/api-rar-contract.md`.
|
||
> Sistemul de design al landing-ului: `app/web/templates/landing.html` (commit 41aa385), `DESIGN.md`.
|
||
> Mockup-uri piese fara design (REFERINTA VIZUALA OBLIGATORIE): `docs/mockups/prd-5.15-mockups.html`
|
||
> — strip sanatate D6 (stari rosu/verde), picker prestatii E4 (op<->cod), reveal odometru initial.
|
||
> Acopera exact piesele pe care mockup-urile landing nu le aratau si corecteaza contradictiile
|
||
> mockup<->PRD (VIN unic, contor all-time, culori prin tokeni).
|
||
> Starea trece: `draft -> aprobat -> in-executie -> verify-pass -> inchis`.
|
||
|
||
## 1. Obiectiv
|
||
|
||
Propagam sistemul de design al landing-ului comercial (carduri/liste/formulare compacte,
|
||
slim, si cele 4 teme grafit/cobalt/cupru/hartie) in aplicatia reala. Concret: dashboard-ul
|
||
Acasa primeste cardurile-contor + lista de trimiteri slim din mockup-ul hero, iar formularul
|
||
de editare trimitere primeste designul compact din mockup-ul "prestatie noua", cu **un singur
|
||
camp VIN**, **Observatii** ca text liber pentru operatiile de service si **prestatii ca chips
|
||
multi-select** de coduri RAR. Userul a cerut explicit replicarea acestor doua mockup-uri pentru
|
||
ca ii place cat de compacte/slim sunt.
|
||
|
||
Decizii de produs confirmate cu userul (poarta de aprobare a acestui PRD):
|
||
- **D1**: cardurile-contor INLOCUIESC bara de status actuala (`_status.html`); pastram doar
|
||
indicatorii de sanatate worker/RAR intr-o forma compacta.
|
||
- **D2**: temele sunt ADITIVE — pastram light/dark/petrol + Auto SI adaugam cele 4 din landing
|
||
(grafit/cobalt/cupru/hartie). Selectorul ciclic le parcurge pe toate. (grafit ~ dark si
|
||
hartie ~ light raman optiuni separate, la cererea userului.)
|
||
- **D3**: prestatiile sunt chips reale multi-select — utilizatorul poate adauga mai multe coduri
|
||
din nomenclatorul RAR si poate sterge oricare; se trimite lista `prestatii` completa (RAR
|
||
accepta lista `{codPrestatie, idPrezentare:null}` — `docs/api-rar-contract.md` §payload).
|
||
- **D4** (contor Trimise): cardul "Trimise" arata trei valori temporale — **all-time** (principal)
|
||
+ **luna asta** + **azi** (secundar). Necesita extinderea numaratorilor cu `sent_today`/`sent_month`.
|
||
- **D5** (Observatii = operatii service): in API-ul RAR, campul `obs` e DE FAPT denumirea operatiilor
|
||
din service. Deci `obs` = text liber cu operatiile efectuate; la import, daca fisierul nu are
|
||
coloana Observatii, **concatenam denumirea operatiei de service in `obs`**. `obs` ramane in
|
||
`payload_json` (camp din contractul RAR), fara coloana noua.
|
||
|
||
Decizii din /plan-ceo-review (2026-06-28, mod SELECTIVE EXPANSION):
|
||
- **D6** (sanatate mereu-vizibila): cardurile-contor inlocuiesc bara de status, DAR sanatatea
|
||
(worker viu? RAR accesibil? ultima autentificare) ramane intr-un **strip mereu-vizibil, colorat,
|
||
deasupra contoarelor** (verde "declaratiile curg" / rosu "blocat: worker oprit / RAR inaccesibil").
|
||
Invariant: zero-silent-failures — semnalul critic NU se ingroapa sub volum. (Rafineaza D1.)
|
||
- **D7** (operatie -> obs, fara regresie de mapare): la import, denumirea operatiei RAMANE in
|
||
`op_service` (sursa pentru maparea op->cod) SI se COPIAZA in `obs`. `obs` e sink aditional, nu
|
||
mutare; fluxul needs_mapping ramane neatins. (Rafineaza D5.)
|
||
- **D8** (idempotenta obs): `obs` e EXCLUS din cheia de idempotenta (`idempotency.py:98`). Deci
|
||
editarea `obs` NU schimba cheia si NU poate crea duplicate — corecteaza AC-ul gresit din US-005.
|
||
`prestatii` ESTE in cheie (sortat dupa cod) — multi-select re-cheieaza randul (US-006).
|
||
- **D9** (secventiere): 5.15 INAINTE de 5.14 (mapare LLM). Editorul manual defineste forma listei
|
||
`prestatii` si UX-ul de confirmare; 5.14 umple codurile peste aceeasi forma.
|
||
- **D10** (extinderi acceptate, SELECTIVE EXPANSION): toate 4 intra in scope — (a) salvare mapare
|
||
din chip (US-009), (b) bulk-fix din lista (US-010), (c) require dinamic odometruInitial la chip
|
||
R-ODO/I-ODO (US-007), (d) editare keyboard-first in form slim (US-007).
|
||
|
||
Decizii din /plan-eng-review (2026-06-28, model claude/opus; outside-voice = Claude subagent,
|
||
Codex a atins usage-limit). Fiecare confirmata cu userul:
|
||
- **E1** (ARCH, /repune nu mai sterge operatia): `/repune` face azi `p0.pop("cod_op_service")`
|
||
la `routes.py:1326` — sterge operatia cand se seteaza un cod direct, rupand D7 si US-009.
|
||
US-006 ELIMINA acel `pop` si pastreaza `cod_op_service`; test de regresie obligatoriu
|
||
(op_service supravietuieste unui /repune cu cod). (Rafineaza US-006.)
|
||
- **E2** (DRY teme, fisier fierbinte): config-ul de teme e duplicat in ~7 locuri in base.html
|
||
(anti-FOUC `VALID` la :22 + cinci literali paraleli `CYCLE`/`VALID`/`ICONS`/`LABELS`/`NEXT`
|
||
la :758-765). US-001 CONSOLIDEAZA intr-o singura structura sursa-de-adevar (`THEMES`
|
||
ordonata) din care se DERIVA ciclul/etichetele/iconitele + setul anti-FOUC. Adaugarea unei
|
||
teme = o intrare. (Rafineaza US-001.)
|
||
- **E3** (obs concat idempotent): la import, copierea denumirii operatiei in `obs` se face
|
||
DERIVE-ON-EMPTY (doar cand `obs` e gol) ca sa fie idempotenta la re-import/re-editare. Test
|
||
dedicat anti-dublu-concat ("Schimb ulei; Schimb ulei"). (Rafineaza US-005.)
|
||
- **E4** (binding operatie<->cod in chips — HIGH): chip-urile NU sunt o lista plata de coduri.
|
||
Cand exista operatii (`cod_op_service`), UI-ul randeaza UN picker PE operatie (eticheta op +
|
||
chip-ul ei de cod), pastrand perechea per-item pe care modelul o are deja; lista plata de
|
||
coduri libere DOAR pentru cazul fara operatie (corectie pura). Astfel US-009 citeste perechea
|
||
direct, iar deduparea e PER-ITEM (nu "dupa cod" — doua operatii distincte pot mapa legitim la
|
||
acelasi cod RAR). (Rafineaza US-006 AC2 + US-007 AC3 + US-009.)
|
||
- **E5** (serializare Val 3 pe routes.py): US-005 si US-006 rescriu ACEEASI functie
|
||
`post_corecteaza` (`routes.py:1120-1262`). Regula "un singur autor pe fisier fierbinte" se
|
||
EXTINDE la routes.py in Val 3: US-005 INAINTE de US-006 (secvential, nu paralel). (Rafineaza §6.)
|
||
- **E6** (US-007 HTMX server-driven PRIMARY): inversam abordarea — chips add/remove via `hx-post`
|
||
care re-randeaza partial-ul chips+form; reveal-ul conditional `odometruInitial` rezulta GRATIS
|
||
din re-randarea server; navigare tastatura = `<select>`/`<datalist>` nativ. JS custom DOAR ca
|
||
progressive enhancement (snappiness), nu calea principala. Elimina path-ul dublu JS/no-JS.
|
||
(Rafineaza US-007.)
|
||
- **E7** (contoare in timp local RO): `azi`/`luna asta` se bucketeaza in timp local RO (UTC+2/+3),
|
||
nu UTC — `updated_at` e `datetime('now')` UTC, deci `date(updated_at)` pur ar numara gresit
|
||
trimiterile dintre miezul noptii local si ~03:00. Folosim offset RO (ex. `date(updated_at,'+3 hours')`
|
||
cu aceeasi baza `now`) + test la granita de miez de noapte local. (Rafineaza US-003.)
|
||
- **E8** (interleave fix authz GET-listari — securitate): CLAUDE.md noteaza scurgere cross-cont
|
||
deschisa ("GET-urile de listare sunt globale + neprotejate"). Userul a ales sa INTERLEAVE
|
||
remedierea in 5.15 -> story noua **US-011** (account-scope pe GET-urile de listare + teste),
|
||
nu queue dupa polish-ul de teme.
|
||
|
||
Fapte verificate care fundamenteaza scope-ul (nu presupuneri):
|
||
- `vin` la RAR e **un singur camp** (17 car., MAJUSCULE, fara O/I/Q) — cerinta "fara 2 campuri
|
||
VIN" e deja respectata azi (`_form_editare.html` are un singur `vin`); ramane sa NU regresam.
|
||
- `prestatii` e deja **lista** in modelul intern (`mapping.resolve_prestatii(prestatii: list[dict])`)
|
||
si in contractul RAR — multi-select nu cere model nou, ci editor nou.
|
||
- `obs` exista deja ca alias de coloana la import (`import_router.py:71` — Observatii/Obs/Mentiuni/Note)
|
||
si ca text liber optional in contractul RAR (`obs`); azi NU e editabil in formular.
|
||
|
||
## 2. Non-Goals (anti scope-creep)
|
||
|
||
- Fara modificari pe backend-ul de trimitere: worker, masina de stari, idempotenta-logica
|
||
(`build_key`), reconciliere, contract RAR. Recalcularea idempotentei la editare foloseste
|
||
mecanismul EXISTENT (ca la 3.5/5.10), nu unul nou.
|
||
- Fara migrare de schema decat daca strict necesar. `obs` si `prestatii` traiesc in
|
||
`submissions.payload_json` (de confirmat la US-005) — fara coloane noi daca payload-ul le poarta.
|
||
- Fara stergerea functionalitatii listei de trimiteri: filtre (data/vehicul/stare), paginare,
|
||
bulk-delete pe randuri blocate, click->detaliu raman; se schimba DOAR aspectul randului (slim).
|
||
- Fara schimbarea regulilor de mapare operatie->cod sau a validarii nomenclatorului RAR
|
||
(`mapping.py`, `validation.py` raman ca atare; doar callsite-urile de editare le folosesc cu lista).
|
||
- Fara redesign al landing-ului (deja livrat in 5.x); aici doar IMPORTAM stilul lui in app.
|
||
|
||
## 3. Stories atomice
|
||
|
||
> Backend + UI pentru acelasi comportament = 2 stories. `base.html` e fisier FIERBINTE
|
||
> (serializat intre valuri — un singur autor pe val). Toate UI verificate pe un esantion de teme (o tema luminoasa + una intunecata).
|
||
|
||
### US-001: Teme aditive (light/dark/petrol + grafit/cobalt/cupru/hartie) + tokeni `--card2`/`--line2`
|
||
**Ca** operator de service **vreau** aceleasi teme ca pe landing **pentru ca** aplicatia sa para
|
||
acelasi produs, coerent vizual.
|
||
|
||
- **Depinde de**: —
|
||
- **Fisiere**: `app/web/templates/base.html`, `DESIGN.md`, `tests/test_tema.py` (~3 fisiere)
|
||
- **Test intai (RED)**: `tests/test_tema.py` — `test_cele_4_teme_definite`, `test_tokeni_card2_line2_in_toate_temele`, `test_anti_fouc_4_stari`, `test_migrare_localStorage_legacy`
|
||
- **Acceptance criteria**:
|
||
- [x] Pastram temele EXISTENTE light/dark/petrol si ADAUGAM 4 teme noi grafit/cobalt/cupru/hartie,
|
||
definite prin token-urile EXISTENTE (`--bg/--card/--ink/--muted/--line/--ok/--warn/--err/--accent`)
|
||
+ DOUA noi `--card2` (fundal input/contor) si `--line2` (separator subtire). `--card2`/`--line2`
|
||
primesc valori si in light/dark/petrol (fallback rezonabil). Maparea landing->app pentru cele 4
|
||
noi: `--text->--ink`, `--sub->--muted`, `--okt->--ok`, `--errt->--err`, `--infot->--accent`.
|
||
- [x] Selectorul ciclic parcurge TOATE: light -> dark -> petrol -> grafit -> cobalt -> cupru ->
|
||
hartie -> Auto, afiseaza eticheta temei curente, persistenta `localStorage` (D2).
|
||
- [x] **DRY (E2)**: config-ul de teme traieste intr-o SINGURA structura sursa-de-adevar
|
||
(`THEMES` ordonata, cu `{id,label,icon}`) din care se DERIVA `CYCLE`/`NEXT`/`ICONS`/`LABELS`
|
||
(azi 5 literali paraleli la base.html:758-765) SI setul anti-FOUC `VALID` (azi separat la
|
||
base.html:22). Adaugarea unei teme noi = o singura intrare; test ca derivatele acopera
|
||
toate temele (prinde o intrare ICONS/LABELS lipsa, nu doar token CSS lipsa).
|
||
- [x] "Auto" pastrat: urmeaza `prefers-color-scheme`, rezolva la dark/grafit sau light/hartie
|
||
(decizie minora: Auto -> dark + hartie pentru light, sau dark/grafit — aliniaza cu I2).
|
||
- [x] Script anti-FOUC in `<head>` seteaza `data-theme` sincron pre-paint pentru toate starile;
|
||
valoare necunoscuta -> Auto, fara blink. Valorile vechi raman valide (nu se mapeaza fortat).
|
||
- [x] Contrast AA pentru text principal in toate temele (light + hartie sunt cele luminoase).
|
||
- [x] `DESIGN.md` actualizat: sectiunea cromatica + selector tema reflecta toate temele.
|
||
- **Verificare E2E**: browser pe `/` (dashboard logat) — ciclare prin toate temele, persistenta la
|
||
refresh, fara FOUC; toate temele selectabile.
|
||
|
||
### US-002: Componente de design slim in `base.html` (CSS, fara consumatori inca)
|
||
**Ca** dezvoltator **vreau** clase reutilizabile pentru carduri-contor, lista slim, campuri slim si
|
||
chips **pentru ca** dashboard-ul si formularul sa le consume DRY, identic cu mockup-ul.
|
||
|
||
- **Depinde de**: US-001 (foloseste `--card2`/`--line2`)
|
||
- **Fisiere**: `app/web/templates/base.html`, `DESIGN.md`, `tests/test_web_responsive.py` (~3 fisiere)
|
||
- **Test intai (RED)**: `tests/test_web_responsive.py` — `test_clasa_contor_card`, `test_clasa_lista_slim`, `test_clasa_camp_slim`, `test_clasa_chips`
|
||
- **Acceptance criteria**:
|
||
- [x] `.contor-card` (sau nume aliniat conventiei): cifra mare bold + eticheta mica muted, fundal
|
||
`--card2`, bordura `--line`, radius 8px, padding 10-12px; variante de culoare a cifrei prin
|
||
`.s-*` existente (verde/accent/rosu).
|
||
- [x] `.lista-trimiteri-slim` cu rand `.trimitere-slim`: stanga = VIN mono (linia 1) + operatie·ora
|
||
muted (linia 2, 11px); dreapta = pill de stare; separator `--line2`; padding 10-14px.
|
||
Randul ramane clickabil (rol button) si pastreaza tinta 44px pe mobil.
|
||
- [x] Varianta slim de camp formular: label 11px muted deasupra, input ~30px inaltime, fundal
|
||
`--card2`, mono pentru VIN/odometru/nr; integrata in macro-ul `camp` din `_macros.html`
|
||
printr-un flag (`slim=True`), fara a rupe randarea actuala (default neschimbat).
|
||
- [x] `.chips` + `.chip` (cu buton `×` de stergere) pentru prestatii multi-select; accesibil
|
||
(buton real cu `aria-label`), stilat ca in mockup (accent 18%, font 10-11px).
|
||
- [x] **Doar tokeni, fara hex hardcodat (criteriu din mockup)**: toate culorile componentelor noi
|
||
(contor, lista slim, chips, strip, picker) folosesc EXCLUSIV variabile CSS
|
||
(`var(--errt)`/`var(--okt)`/`var(--accent)`/`var(--card2)`/`var(--line2)` etc.), NU hex literal
|
||
si NU inline-styles copiate ca-atare din `landing.html`. Cifra "De corectat" rosie = token
|
||
(`var(--errt)`), nu `#E05D5D` hardcodat, ca sa ramana AA pe temele luminoase (hartie/light).
|
||
Referinta: `docs/mockups/prd-5.15-mockups.html`.
|
||
- [x] Zero regresie vizuala pe componentele existente (`.card/.pill/.act/.tabel-trimiteri`).
|
||
- **Verificare E2E**: pagina de proba/sandbox sau direct in US-003/004/007; vizual pe un esantion de teme + 390/1280.
|
||
|
||
### US-003: Dashboard Acasa — carduri-contor inlocuiesc bara de status
|
||
**Ca** operator **vreau** cele 3 carduri-contor compacte (Trimise / In coada / De corectat)
|
||
**pentru ca** sa vad starea dintr-o privire, ca in mockup.
|
||
|
||
- **Depinde de**: US-002
|
||
- **Fisiere**: `app/web/templates/_status.html`, `app/web/templates/_acasa.html`,
|
||
`app/web/routes.py` (`_status_counts` extins cu `sent_today`/`sent_month`), `tests/test_web_status.py`,
|
||
`tests/test_web_dashboard.py` (~5 fisiere)
|
||
- **Test intai (RED)**: `tests/test_web_status.py` — `test_strip_sanatate_mereu_vizibil`, `test_strip_rosu_worker_oprit`, `test_trei_contoare_card`, `test_trimise_all_time_luna_azi`, `test_fara_bara_veche`
|
||
- **Acceptance criteria**:
|
||
- [x] **Strip de sanatate mereu-vizibil, DEASUPRA contoarelor** (D6): o linie compacta colorata —
|
||
verde "declaratiile curg" cand worker viu + RAR ok; **rosu** + text explicit cand worker
|
||
oprit SAU RAR inaccesibil ("Blocat: worker oprit" / "Blocat: RAR inaccesibil"), cu ultima
|
||
autentificare RAR. Glife accesibile ✓/✗ (nu doar culoare). Invariant zero-silent-failures:
|
||
semnalul "declaratiile NU pleaca" e imposibil de ratat, NU ingropat sub volum.
|
||
**Layout exact (din mockup)**: strip full-width sus; glifa (✗ rosu / ✓ verde) + text bold la
|
||
stanga, "Ultima autentificare RAR: ..." mono muted la dreapta. Copy: rosu "Blocat: worker oprit
|
||
— declaratiile NU pleaca" (sau "... RAR inaccesibil"), verde "Declaratiile curg normal".
|
||
Referinta: `docs/mockups/prd-5.15-mockups.html`.
|
||
- [x] Sub strip: card "Trimiteri RAR AUTOPASS" cu 3 contoare slim: **In coada** (queued, accent),
|
||
**Trimise** (sent, verde), **De corectat** (blocate = needs_data + needs_mapping + error, rosu).
|
||
- [x] **Stari goale + ierarhie contor (criteriu din mockup)**: cifra principala a contorului "Trimise"
|
||
e **all-time** (cifra mare bold), iar "luna asta"/"azi" sunt o sub-linie mono secundara
|
||
(`luna {n} · azi {n}`) — NU "luna asta" ca cifra principala (corecteaza framing-ul din mockup-ul
|
||
landing). Contorul "De corectat" la 0 se afiseaza **muted, nu rosu** (rosu doar cand exista
|
||
blocate — pastreaza pattern-ul `_status.html:47`). Referinta: `docs/mockups/prd-5.15-mockups.html`.
|
||
- [x] Cardul **Trimise** afiseaza trei valori temporale (D4): all-time (cifra principala) + "luna asta"
|
||
+ "azi" (sub-linie secundara). `_status_counts` extins cu `sent_today`/`sent_month`.
|
||
**Sursa de timp**: NU exista coloana `sent_at`; folosim `status='sent' AND date(updated_at)=...`.
|
||
Justificare (verificat): un rand `sent` nu mai primeste scrieri ulterioare pana la purge-delete
|
||
la +90z (`purge_after` se seteaza in ACEEASI scriere care marcheaza `sent`), deci `updated_at`
|
||
== momentul trimiterii pentru randurile `sent` -> fara migrare de coloana (respecta Non-Goal).
|
||
Daca pe viitor apar scrieri post-`sent`, reevalueaza o coloana `sent_at` dedicata.
|
||
**Timezone (E7)**: `updated_at` e `datetime('now')` = UTC; bucketarea `azi`/`luna asta`
|
||
se face in TIMP LOCAL RO (ex. `date(updated_at,'+3 hours')`, aceeasi baza `now`), altfel
|
||
trimiterile dintre miezul noptii local si ~03:00 cad pe ziua precedenta si "luna asta" e
|
||
gresita in primele ore ale zilei de 1. Test la granita de miez de noapte local.
|
||
**Caveat reconcile (E6 outside-voice)**: pe reconciliere (raspuns pierdut) worker-ul
|
||
marcheaza `sent` cu `updated_at` = momentul reconcilierii, nu al inserarii RAR — pentru
|
||
randurile reconciliate (rare) `updated_at` poate diferi de momentul real al trimiterii.
|
||
- [x] Navigarea existenta (Trimiteri/Mapari + badge needs_mapping) se pastreaza. Click pe contorul
|
||
**De corectat** deep-link-eaza in lista filtrata pe blocate (`?status=` existent din 5.x),
|
||
nu intr-o pagina noua.
|
||
- [x] Scoped pe cont; poll-ul existent (`/_fragments/status`) randeaza noul antet fara a pierde tab-ul.
|
||
- [x] Responsive: cele 3 contoare pe un rand pe desktop, stivuite/2-pe-rand pe mobil, fara overflow.
|
||
- **Verificare E2E**: browser pe `/` — contoare corecte vs date din DB, sanatate worker mort/viu,
|
||
poll pastreaza starea.
|
||
|
||
### US-004: Lista de trimiteri — rand slim (VIN + operatie·ora + pill)
|
||
**Ca** operator **vreau** lista de trimiteri in stil slim ca in mockup **pentru ca** e mai compacta
|
||
si mai usor de scanat, pastrand filtrele si actiunile.
|
||
|
||
- **Depinde de**: US-002
|
||
- **Fisiere**: `app/web/templates/_submissions.html`, `app/web/templates/_coada.html` (filtre raman),
|
||
`tests/test_web_submissions.py`, `tests/test_web_responsive.py` (~4 fisiere)
|
||
- **Test intai (RED)**: `tests/test_web_submissions.py` — `test_rand_slim_vin_operatie_pill`, `test_filtre_paginare_pastrate`, `test_bulk_doar_blocate`, `test_click_deschide_detaliu`
|
||
- **Acceptance criteria**:
|
||
- [x] Fiecare rand: stanga VIN mono scurt (`vin_scurt`) linia 1 + operatie + ora/data muted linia 2;
|
||
dreapta pill de stare (`stare_css`/`stare_scurt`). Nr. inmatriculare, data completa si nr.
|
||
prezentare RAR raman accesibile (linie meta discreta si/sau in modalul de detaliu).
|
||
- [x] Filtre (data/vehicul/stare — `_coada.html`), paginarea numerotata si bulk-delete pe randuri
|
||
blocate (checkbox doar pe `gestionabil`) raman FUNCTIONALE.
|
||
- [x] Click pe rand deschide `/_fragments/trimitere/{id}` in modal (neschimbat).
|
||
- [x] Slim layout consistent desktop si <=1024px (cardurile responsive existente nu regreseaza).
|
||
- [x] Pill-urile de stare folosesc maparea din `labels.py` (zero etichete noi). Eticheta "Eroare VIN"
|
||
din mockup-ul landing e DOAR ilustrativa — se foloseste `stare_scurt` existent (ex. "De corectat").
|
||
- **Verificare E2E**: browser — filtrare + paginare + click detaliu + bulk pe blocate, pe 4 teme,
|
||
pe 390/820/1280.
|
||
|
||
### US-005: Backend — `obs` (Observatii) editabil si persistat
|
||
**Ca** operator **vreau** sa editez Observatiile (operatiile de service in text liber) **pentru ca**
|
||
sa corectez/completez ce s-a facut, separat de codurile RAR.
|
||
|
||
- **Depinde de**: —
|
||
- **Fisiere**: `app/web/routes.py` (`/trimitere/{id}/corecteaza`),
|
||
`app/api/v1/import_router.py` (`/_import/{id}/rand/{row}/editeaza`, `EDIT_FIELDS`),
|
||
`app/validation.py` (obs optional), `app/payload_view.py` (echo obs),
|
||
`tests/test_web_corectie*.py`, `tests/test_import_review.py` (~6 fisiere)
|
||
- **Test intai (RED)**: `tests/test_web_corectie_obs.py` — `test_obs_editabil_persistat_corecteaza`, `test_obs_persistat_preview_editeaza`, `test_obs_optional_gol_ok`, `test_import_concateneaza_operatie_in_obs`
|
||
- **Acceptance criteria**:
|
||
- [x] `obs` traieste in `payload_json` (camp `obs` din contractul RAR); fara coloana noua / migrare (D5).
|
||
- [x] `obs` adaugat in `EDIT_FIELDS`; `corecteaza` si `editeaza` (preview) accepta si persista `obs`.
|
||
- [x] `obs` optional (text liber, fara validare de continut, doar trim); apare in `payload_view`.
|
||
- [x] `obs` se include in payload-ul trimis la RAR (camp `obs`). **`obs` e EXCLUS din cheia de
|
||
idempotenta** (`idempotency.py:98`) — deci editarea DOAR a `obs` NU schimba cheia si NU poate
|
||
crea duplicat (D8). NU recalcula/forta cheia pe baza `obs`. (Corecteaza formularea anterioara.)
|
||
- [x] **La import** (D7): denumirea operatiei RAMANE in `op_service` (sursa pentru maparea op->cod);
|
||
daca fisierul NU are coloana Observatii, denumirea operatiei se **COPIAZA** (nu se muta) si in
|
||
`obs`; daca are coloana Observatii, se pastreaza textul ei. Format de concatenare definit
|
||
(denumiri separate prin "; "). Fluxul needs_mapping ramane neatins.
|
||
- [x] **Idempotent (E3)**: copierea operatiei in `obs` e DERIVE-ON-EMPTY (doar cand `obs` e gol)
|
||
ca re-importul/re-editarea sa NU dubleze textul ("Schimb ulei; Schimb ulei"). Test dedicat
|
||
anti-dublu-concat.
|
||
- [x] **Cuplaj preview-import**: `obs` se adauga in `EDIT_FIELDS` (`import_router.py:261`); `_merge_override`
|
||
il propaga (obs e free-text, cade pe ramura ne-canonicalizata — fara strip "0", doar trim).
|
||
- **Verificare E2E**: `POST /trimitere/{id}/corecteaza` cu `obs` -> persistat -> vizibil in detaliu;
|
||
optional proba live RAR ca `obs` apare in FINALIZATA.
|
||
|
||
### US-006: Backend — prestatii multi-cod (lista) la editare/corectie
|
||
**Ca** operator **vreau** sa adaug/sterg mai multe coduri RAR pe o trimitere **pentru ca** o
|
||
comanda poate avea mai multe prestatii, asa cum accepta RAR.
|
||
|
||
- **Depinde de**: —
|
||
- **Fisiere**: `app/web/routes.py` (`/corecteaza`, `/repune` — **rescrie logica single-`prestatii[0]`**
|
||
de azi: `cod_prestatie_curent` la `routes.py:977-982` + injectia la `1146-1164`/`1288-1324`
|
||
presupun UN cod; multi-select cere pre-fill din lista intreaga + scriere pe toti itemii),
|
||
`app/api/v1/import_router.py` (`/editeaza`, idem), `app/mapping.py` (NEATINS — deja accepta lista),
|
||
`app/validation.py` (fiecare cod in nomenclator), `tests/test_web_corectie*.py`,
|
||
`tests/test_mapping*.py` (~6 fisiere). Nota: `mapping.py` e neatins, dar call-site-urile din
|
||
handler-e cer un rewrite real (nu "fara schimbare de logica").
|
||
- **Test intai (RED)**: `tests/test_web_corectie_prestatii.py` — `test_mai_multe_coduri_acceptate`, `test_cod_invalid_respins`, `test_lista_goala_needs_mapping`, `test_idempotency_recalculat`, `test_odometru_initial_conditionat_R_ODO`
|
||
- **Acceptance criteria**:
|
||
- [x] Handler-ele de editare accepta o LISTA de `cod_prestatie`, inlocuind selectul unic. **NU
|
||
reconstrui lista cu itemi goi**: handler-ele de azi injecteaza codul DOAR in `prestatii[0]`
|
||
(`routes.py:1146-1164`, `1288-1324`) — multi-select le rescrie ca: pastreaza itemii existenti
|
||
cu `cod_op_service`/`denumire` (invariant D7) si seteaza/adauga `cod_prestatie` pe ei.
|
||
`idPrezentare:null` se adauga in `payload.py` la construirea payload-ului, NU in itemul intern.
|
||
**E1 (CRITIC)**: `/repune` face azi `p0.pop("cod_op_service", None)` la `routes.py:1326` —
|
||
ELIMINA acel `pop`: cand se seteaza un cod direct, `cod_op_service`/`denumire` RAMAN pe item
|
||
(altfel rupe D7 si US-009). **Test de regresie obligatoriu** (IRON RULE): op_service
|
||
supravietuieste unui /repune cu cod.
|
||
- [x] **Pereche operatie<->cod definita**: cand exista operatii (`cod_op_service`), fiecare cod-chip
|
||
se ataseaza unei operatii (1 operatie -> 1 cod, ca azi, dar acum N operatii -> N coduri);
|
||
cand NU exista operatie (cod direct, ex. corectie pura), chip-urile sunt coduri libere intr-o
|
||
lista fara `op_service`. Aceasta pereche e ce consuma US-009 (salvare mapare op->cod).
|
||
- [x] Fiecare cod e validat fata de nomenclator (`valid_codes`); cod necunoscut -> respins cu
|
||
mesaj (NU se trimite raw — invariant ORA-12899 din CLAUDE.md/contract).
|
||
- [x] Lista goala de coduri -> ramane `needs_mapping` (nu se trimite fara cod).
|
||
- [x] **Coduri duplicate** -> dedupare **PER-ITEM, nu "dupa cod"** (E4): doua operatii distincte
|
||
pot mapa legitim la acelasi cod RAR; deduparea naiva dupa cod ar sterge o operatie reala si
|
||
ar distruge contextul op->cod cerut de US-009. Dedup = acelasi (op, cod) de 2x, nu acelasi cod.
|
||
- [x] Recalcul idempotenta dupa editare (mecanism existent), cu prinderea coliziunii ca azi.
|
||
- [x] Se pastreaza regula `odometruInitial` obligatoriu cand lista contine `R-ODO`/`I-ODO`
|
||
(contract §payload) — validare existenta, doar verificata pe lista.
|
||
- **Verificare E2E**: `POST /corecteaza` cu 2 coduri valide -> `queued` cu `prestatii` de lungime 2;
|
||
cu un cod invalid -> respins; optional live RAR cu 2 prestatii -> FINALIZATA.
|
||
|
||
### US-007: UI — formular editare slim (VIN unic, Observatii, chips prestatii)
|
||
**Ca** operator **vreau** formularul de editare in design slim cu chips de prestatii **pentru ca**
|
||
e compact si imi arata clar codurile RAR si observatiile, ca in mockup.
|
||
|
||
- **Depinde de**: US-002, US-005, US-006
|
||
- **Fisiere**: `app/web/templates/_form_editare.html`, `app/web/templates/_macros.html`,
|
||
`app/web/templates/_trimitere_detaliu.html`, `app/web/templates/_editare_preview_modal.html`,
|
||
`tests/test_web_preview_edit.py`, `tests/test_web_detaliu*.py` (~6 fisiere)
|
||
- **Test intai (RED)**: `tests/test_web_form_editare_slim.py` — `test_un_singur_vin`, `test_camp_observatii_prezent`, `test_chips_multi_select_prestatii`, `test_adauga_sterge_chip`, `test_form_slim_in_ambele_modale`
|
||
- **Acceptance criteria**:
|
||
- [x] Formularul foloseste varianta slim de camp (US-002): VIN, Data prestatiei, Nr. inmatriculare,
|
||
Observatii (textarea), prestatii (chips), Odometru — un SINGUR camp VIN (fara "Confirma VIN").
|
||
- [x] Observatii = textarea liber, legat de `obs` (US-005).
|
||
- [x] Prestatii = chips multi-select. **Binding op<->cod (E4)**: cand exista operatii
|
||
(`cod_op_service`), UN picker PE operatie (eticheta op + chip-ul ei de cod), pastrand
|
||
perechea per-item; lista plata de coduri libere DOAR pentru cazul fara operatie (corectie
|
||
pura). Fiecare cod ca chip cu `×`; lista se trimite ca `cod_prestatie` multiplu (US-006).
|
||
- [x] Acelasi `_form_editare.html` slujeste ambele modale (detaliu `/corecteaza` si preview
|
||
`/editeaza`), fara duplicare; degradare fara JS rezonabila (chips ca lista, picker = select).
|
||
- [x] **Require dinamic odometruInitial** (D10c): cand lista de chips contine `R-ODO` sau `I-ODO`,
|
||
formularul DEZVALUIE si cere `odometru_initial` (contract §payload), previne 400 RAR si un
|
||
drum `needs_data`. Cand niciun chip R-ODO/I-ODO -> campul ramane optional/ascuns.
|
||
- [x] **Editare keyboard-first** (D10d): in picker, Enter adauga chip-ul selectat; sageti
|
||
navigheaza optiunile; Esc inchide modalul; focus-ul revine logic dupa adaugare/stergere.
|
||
- [x] Stilizare fidela mockup-ului pe toate temele; tinte 44px pe mobil; a11y (label-uri, aria,
|
||
anunt de chip adaugat/sters pentru screen-reader).
|
||
- [x] **HTMX server-driven PRIMARY (E6)**: chips add/remove via `hx-post` care re-randeaza
|
||
partial-ul chips+form; reveal-ul conditional `odometruInitial` rezulta GRATIS din re-randarea
|
||
server (server computeaza din lista de chips, fara ramura JS); navigare tastatura =
|
||
`<select>`/`<datalist>` nativ. JS custom DOAR ca progressive enhancement (snappiness), nu
|
||
calea principala. Elimina path-ul dublu JS/no-JS pe care formularea anterioara il cerea.
|
||
- [x] **Referinta vizuala (criteriu din mockup)**: `docs/mockups/prd-5.15-mockups.html` defineste
|
||
aspectul-tinta — VIN unic (FARA al doilea camp "Confirma VIN" din mockup-ul landing); Observatii
|
||
ca textarea slim; picker PE operatie cu DOUA stari vizuale: (a) operatie mapata = chip cod cu `×`
|
||
+ "+ alt cod" + link "salveaza regula op->cod" (US-009); (b) operatie ne-mapata = picker galben
|
||
"alege cod RAR" cu eticheta "lipsa cod". OdometruInitial: ascuns implicit (doar hint discret
|
||
"se cere doar pentru R-ODO/I-ODO") si DEZVALUIT cu bordura-stanga galbena + label "necesar pentru
|
||
R-ODO" cand lista de chips contine R-ODO/I-ODO.
|
||
- **Verificare E2E**: browser — editare trimitere needs_data: schimb VIN + scriu Observatii + adaug
|
||
2 coduri RAR (chips, cu tastatura) + adaug R-ODO (apare odometruInitial) + sterg un chip -> salvare
|
||
-> persistat; identic in preview import.
|
||
|
||
### US-008: Teste de regresie + E2E final pe cele 4 teme
|
||
**Ca** dezvoltator **vreau** acoperire si o trecere E2E completa **pentru ca** redesign-ul atinge
|
||
fisiere fierbinti (base.html) si nu vreau regresii pe teme/liste/formular.
|
||
|
||
- **Depinde de**: US-003, US-004, US-007
|
||
- **Fisiere**: `tests/test_web_responsive.py`, `tests/test_tema.py`, `tests/test_web_submissions.py`
|
||
(~3 fisiere)
|
||
- **Test intai (RED)**: completare scenarii lipsa (componente noi pe TOATE temele; slim list desktop+mobil)
|
||
- **Acceptance criteria**:
|
||
- [x] `pytest -q -m "not live"` verde (fara regresii fata de baseline).
|
||
- [x] **Test de tema robust, nu esantion**: un test parametrizat verifica fiecare token critic
|
||
(`--card2`, `--line2`, `--accent`, `--ok`, `--err`) e DEFINIT in TOATE cele 7+1 stari
|
||
(light/dark/petrol/grafit/cobalt/cupru/hartie/Auto). Ancorare pe SENTINEL CSS (nu felii
|
||
fixe `[idx:idx+N]`) — vezi regresia false-green din ROADMAP 5.13.
|
||
- [ ] E2E Playwright pe 390/820/1280, pe un dark (grafit) + un light (hartie) + petrol (verificare
|
||
ca temele vechi nu au regresat): strip sanatate, dashboard contoare, lista slim cu
|
||
filtre/paginare/bulk, formular slim cu chips, fara overflow orizontal.
|
||
|
||
### US-009: Salvare mapare din chip (compounding cu fluxul de mapare)
|
||
**Ca** operator **vreau** ca atunci cand adaug un cod RAR la o operatie sa-l pot salva ca regula
|
||
**pentru ca** data viitoare operatia sa se auto-rezolve, fara sa re-mapez manual.
|
||
|
||
- **Depinde de**: US-006, US-007
|
||
- **Fisiere**: `app/web/templates/_form_editare.html`, `app/web/routes.py` (reuse `save_mapping` +
|
||
`reresolve_account` — fara logica noua), `tests/test_web_mapare_din_chip.py` (~3 fisiere)
|
||
- **Test intai (RED)**: `tests/test_web_mapare_din_chip.py` — `test_salveaza_regula_din_chip`, `test_reresolve_deblocheaza_frate`, `test_optional_nu_forteaza`
|
||
- **Acceptance criteria**:
|
||
- [x] Cand operatia (`op_service`) e cunoscuta si userul adauga un cod RAR prin chip, apare optiunea
|
||
"salveaza ca regula op->cod"; la confirmare reuse EXACT `save_mapping` + `reresolve_account`
|
||
(acelasi mecanism ca maparea inline din 5.7), scoped pe cont + CSRF.
|
||
- [x] Re-rezolvarea deblocheaza si alte submission-uri `needs_mapping` cu aceeasi operatie (pe `batch_id`).
|
||
- [x] Optional: daca userul nu vrea sa salveze, editarea ramane one-off (fara regula). Se compune
|
||
cu 5.14 (auto-maparea umple, salvarea din chip ramane fallback-ul uman).
|
||
- **Verificare E2E**: adaug cod la operatie nemapata + salveaza regula -> al doilea rand cu aceeasi
|
||
operatie se rezolva automat.
|
||
|
||
### US-010: Bulk-fix din lista (selectie multipla -> actiune unica)
|
||
**Ca** operator **vreau** sa corectez mai multe randuri blocate dintr-o data **pentru ca** la 2-20
|
||
de corectat/zi nu vreau sa intru in fiecare individual.
|
||
|
||
- **Depinde de**: US-004, US-006
|
||
- **Fisiere**: `app/web/templates/_submissions.html`, `app/web/routes.py` (reuse infra bulk
|
||
existenta din `_submissions` + `submissions_admin`), `tests/test_web_bulk_fix.py` (~3 fisiere)
|
||
- **Test intai (RED)**: `tests/test_web_bulk_fix.py` — `test_bulk_remapeaza_selectie`, `test_bulk_doar_blocate`, `test_bulk_scoped_cont`
|
||
- **Acceptance criteria**:
|
||
- [x] Pe randurile blocate (checkbox existent pe `gestionabil`), o actiune bulk noua: aplica un cod
|
||
RAR / o remapare la toata selectia intr-o singura cerere (reuse forma `#bulk-trimiteri`).
|
||
- [x] Scoped pe cont (404-before-409 ca la bulk-delete); doar randuri blocate eligibile.
|
||
- [x] Fiecare rand re-validat + idempotenta recalculata individual (un cod invalid pe un rand nu
|
||
pica tot lotul — sumar "N reusite, M esuate" ca la salvarea mapcoloane D#12).
|
||
- **Verificare E2E**: selectez 3 randuri needs_mapping + aplic un cod -> toate 3 -> `queued`.
|
||
- **Verificare E2E**: rulare completa documentata in Raportul VERIFY.
|
||
|
||
### US-011: Securitate — account-scope pe GET-urile de listare (interleave, E8)
|
||
**Ca** operator **vreau** ca listarile sa-mi arate DOAR trimiterile contului meu **pentru ca**
|
||
azi GET-urile de listare sunt globale + neprotejate (scurgere VIN/PII cross-cont, notata in CLAUDE.md).
|
||
|
||
- **Depinde de**: — (backend pur, independent de UI; ruleaza in paralel cu valurile de design)
|
||
- **Fisiere**: `app/web/routes.py` (GET-urile de listare trimiteri), `app/api/v1/router.py`
|
||
(GET-urile API de listare daca sunt globale), `app/auth.py` (refolosire scope existent),
|
||
`tests/test_web_scope.py`, `tests/test_api_scope.py` (~5 fisiere)
|
||
- **Test intai (RED)**: `test_get_listare_scoped_cont` — un cont NU vede randuri ale altui cont;
|
||
`test_get_listare_neautentificat_401`; `test_get_detaliu_scoped` (404-before-leak pe id strain).
|
||
- **Acceptance criteria**:
|
||
- [x] GET-urile de listare (trimiteri + orice listare globala) devin account-scoped, refolosind
|
||
mecanismul de scope existent (ca POST-urile + bulk-delete: 404-before-409 pe id strain).
|
||
- [x] Un cont nu poate enumera/citi VIN/PII al altui cont prin listare sau detaliu.
|
||
- [x] Enforcement aliniat cu `AUTOPASS_REQUIRE_API_KEY` (dev vs prod), fara a rupe contul id=1
|
||
implicit in dev.
|
||
- [x] Actualizeaza nota din CLAUDE.md ("GET-urile de listare ... de remediat") cand e inchis.
|
||
- **Verificare E2E**: doua conturi cu trimiteri; contul A nu vede niciun rand al contului B in
|
||
listare, filtre, paginare sau detaliu.
|
||
|
||
### US-012: Analytics device-mix (validare premisa mobil, in-PR)
|
||
**Ca** owner **vreau** sa stiu raportul desktop/mobil al operatorilor **pentru ca** sa decid daca
|
||
rafinarile mobil (390px) viitoare merita efortul (premisa nevalidata din TODOS 5.13/CEO-F1).
|
||
|
||
- **Depinde de**: — (instrumentare backend, independenta de UI)
|
||
- **Fisiere**: `app/web/routes.py` (sau middleware existent), `app/schema.sql` SAU `app_events`
|
||
(reuse tabela de evenimente existenta — fara coloana noua daca `app_events` poarta semnalul),
|
||
`tests/test_device_mix.py` (~3 fisiere)
|
||
- **Test intai (RED)**: `test_device_mix_inregistrat`, `test_device_mix_fara_pii`.
|
||
- **Acceptance criteria**:
|
||
- [x] La acces dashboard, clasifica grosier viewport/UA in desktop/mobil si inregistreaza in
|
||
`app_events` (semnal agregat, FARA PII suplimentar). Reuse tabela existenta — fara migrare
|
||
daca `app_events` poarta semnalul.
|
||
- [x] Un mod simplu de citire a raportului (query/admin), suficient pentru a decide investitia mobil.
|
||
- [x] Zero PII nou; aliniat retentiei `app_events` existente.
|
||
- **Verificare E2E**: acces dashboard de pe doua viewport-uri -> doua evenimente clasificate corect.
|
||
|
||
## 4. Riscuri
|
||
|
||
- **base.html fisier fierbinte**: US-001/US-002 il ating amandoua + US-003/004/007 il citesc.
|
||
Serializeaza pe valuri (un singur autor pe val pe base.html), ca la 5.12/5.13.
|
||
- **Migrare teme legacy**: useri cu `localStorage.theme` = light/dark/petrol. Mitigare: maparea
|
||
grafioasa din US-001 (light->hartie, dark->grafit, petrol->grafit) + test dedicat.
|
||
- **Restyle lista = pierdere de functii**: filtre/paginare/bulk pot fi sparte de schimbarea de
|
||
markup. Mitigare: US-004 are AC explicite pentru pastrarea lor + teste lock.
|
||
- **Idempotenta la prestatii multiple**: schimbarea listei schimba cheia canonica. Mitigare:
|
||
refolosim mecanismul existent de recalcul + prindere coliziune (3.5/5.10), zero logica noua.
|
||
- **Densitate vizuala pe mobil**: randul slim cu 2 linii + pill poate aglomera. Mitigare: tinte
|
||
44px + verificare 390px in US-004/008.
|
||
- **Premisa mobil nevalidata** (din TODOS 5.13, CEO F1): valoarea slim/compact pe mobil presupune
|
||
utilizare reala pe mobil. Daca device-mix-ul e ~95% desktop, partea responsive e efort irosit.
|
||
Mitigare: nu blocheaza (designul e bun si pe desktop), dar confirma analytics inainte de a investi
|
||
in rafinari mobil viitoare.
|
||
- **7 teme = suprafata de test/intretinere** pe fisierul cel mai fierbinte: fiecare componenta noua
|
||
trebuie corecta in 7+1 stari. Istoricul (5.13) arata ca testele de tema au dat false-green o data.
|
||
Mitigare: US-008 cere test parametrizat ancorat pe SENTINEL (nu felii fixe); deduparea
|
||
grafit~dark / hartie~light ramana optiune de simplificare (reziduala, non-blocanta).
|
||
- **Secventiere cu 5.14** (D9): 5.15 defineste forma listei `prestatii`; daca 5.14 (mapare LLM)
|
||
porneste in paralel, sincronizeaza forma listei. Mitigare: 5.15 INAINTE de 5.14.
|
||
|
||
## 5. Intrebari deschise
|
||
|
||
> Toate intrebarile au fost REZOLVATE cu userul (vezi D1-D5 §1). Pastrate aici ca istoric al deciziei.
|
||
|
||
- **I1 — contor Trimise** [REZOLVAT]: arata all-time + luna asta + azi (D4). `_status_counts` extins.
|
||
- **I2 — teme** [REZOLVAT]: aditiv — light/dark/petrol + Auto + grafit/cobalt/cupru/hartie (D2).
|
||
- **I3 — stocare obs** [REZOLVAT]: in `payload_json`, fara coloana noua (D5).
|
||
- **I4 — operatii la import -> obs** [REZOLVAT]: concatenam denumirea operatiei in `obs` cand
|
||
fisierul nu are coloana Observatii (D5).
|
||
- Reziduale minore (de decis la executie, non-blocante): formatul exact de concatenare a denumirilor
|
||
in `obs`; rezolvarea "Auto" la light vs hartie; eventuala deduplicare grafit~dark / hartie~light
|
||
in eticheta selectorului.
|
||
|
||
## 6. Valuri de executie (graful de dependente)
|
||
|
||
```
|
||
Val 0: [US-011] authz GET-listari (backend pur; ruleaza in paralel cu orice val) ||
|
||
Val 1: [US-001] base.html teme + tokeni (autor unic pe base.html)
|
||
Val 2: [US-002] base.html componente (dupa US-001, autor unic pe base.html)
|
||
Val 3: [US-003] [US-004] dashboard + strip sanatate + lista (consuma US-002; disjuncte) ||
|
||
[US-005] -> [US-006] backend obs APOI prestatii — SECVENTIAL (E5): ambele rescriu
|
||
ACEEASI functie post_corecteaza (routes.py:1120-1262), autor unic
|
||
Val 4: [US-007] formular slim cu chips (dupa US-002+US-005+US-006)
|
||
Val 5: [US-009] [US-010] salvare mapare din chip || bulk-fix (dupa US-006/007 resp. US-004)
|
||
— disjuncte la nivel de template (_form_editare vs _submissions)
|
||
Val 6: [US-008] regresie + E2E final (dupa toate)
|
||
```
|
||
|
||
> **Regula autor-unic extinsa (E5)**: pe langa base.html, `routes.py` are autor unic in Val 3:
|
||
> US-005 INAINTE de US-006 (ambele in `post_corecteaza`). US-009/US-010 in Val 5 sunt disjuncte
|
||
> la nivel de template; adauga rute noi separate in routes.py (regiuni diferite, mergeabile).
|
||
|
||
> Secventiere fata de alte PRD-uri (D9): **5.15 INAINTE de 5.14** (mapare LLM) — 5.15 fixeaza forma
|
||
> listei `prestatii` si UX-ul de confirmare; 5.14 umple codurile peste aceeasi forma.
|
||
|
||
---
|
||
|
||
## Raport VERIFY
|
||
|
||
Verificator independent (context curat, subagent Sonnet) — 2026-06-28. **VERDICT: PASS** (12/12 stories),
|
||
cu 1 FAIL documentar remediat de lead + 1 OPEN limitat de mediu.
|
||
|
||
- **Suita completa**: `python3 -m pytest -q -m "not live"` → **1230 passed, 1 deselected, 0 failed** (118s).
|
||
Baseline initial 992 → +238 teste, zero regresii.
|
||
- **AC per story (US-001..US-012)**: toate PASS cu dovezi (fisier:linie + test care le acopera).
|
||
Puncte verificate explicit: 7+1 teme cu `--card2`/`--line2` in toate (US-001, DRY `THEMES`);
|
||
componente slim doar cu tokeni, zero hex (US-002, ancorat pe `SENTINEL-COMPONENTE-SLIM`);
|
||
strip sanatate D6 + 3 contoare + `sent_today`/`sent_month` bucketate timp local RO `+3 hours` (US-003, E7);
|
||
lista slim cu filtre/paginare/bulk pastrate (US-004); `obs` editabil + EXCLUS din cheia idempotenta
|
||
(`idempotency.py:98`) + concat derive-on-empty anti-dublu (US-005, D8/E3); prestatii multi-cod via
|
||
`getlist` + **E1 IRON RULE** (`cod_op_service` supravietuieste `/repune` — test dedicat) + dedup per-item
|
||
(US-006, E4); form slim VIN unic + picker chips pe operatie + reveal odo server-driven + select vechi
|
||
redundant ELIMINAT (US-007/cleanup B); test tema parametrizat 5 tokeni x 7 teme ancorat pe selectori
|
||
`[data-theme]` (US-008, anti false-green); salvare mapare din chip reuse `save_mapping`+`reresolve_account`
|
||
(US-009); bulk-fix sumar "N reusite/M esuate" scoped (US-010); account-scope GET-listari 404-before-leak
|
||
(US-011); device-mix fara PII reuse `app_events` (US-012).
|
||
- **Fidelitate mockup** (`docs/mockups/prd-5.15-mockups.html`, cod-level): D6 strip, contoare D4,
|
||
picker E4 cu 2 stari (mapata=chip+×+salveaza / nemapata=select galben "lipsa cod"), reveal odo
|
||
border-left warn — toate conforme; toate culorile prin `var(--token)`, fara hex.
|
||
- **Regresia de aur**: testele `POST /v1/prezentari` + worker + import→commit raman verzi in suita;
|
||
E1 confirmat cu test. Live RAR real (`FINALIZATA`) = opt-in, indisponibil fara creds in sandbox (documentat).
|
||
|
||
**FAIL 1 (remediat de lead)**: nota CLAUDE.md "GET-urile de listare globale + neprotejate (de remediat)"
|
||
nu fusese actualizata (teammates instruiti sa NU atinga CLAUDE.md). **Remediat**: `CLAUDE.md:70` actualizat
|
||
sa reflecte scope-ul implementat de US-011.
|
||
|
||
**OPEN (mediu)**: E2E Playwright pe 390/820/1280 (grafit/hartie/petrol) — browserul MCP a returnat
|
||
"already in use" in sandbox (ca la livrabilele anterioare). Serverul porneste OK (`/healthz` ok),
|
||
ACs acoperite functional de pytest (`test_web_responsive.py`). Recomandat: rulat de operator cu browser real.
|
||
|
||
---
|
||
|
||
## GSTACK REVIEW REPORT
|
||
|
||
Review: `/plan-ceo-review` — 2026-06-28. Mod: **SELECTIVE EXPANSION**. Model: claude (opus).
|
||
Abordare aleasa de user: tot PRD-ul (8 stories) + 4 extinderi acceptate -> **10 stories**.
|
||
|
||
| Pasaj | Status | Constatari materiale |
|
||
|-------|--------|----------------------|
|
||
| Audit sistem | OK | base.html cel mai fierbinte fisier (31x/30z); 5.15 = a 5-a iteratie pe acelasi UI (smell recurent); 5.14 in flight pe acelasi seam |
|
||
| S1 Arhitectura | OK | Fara componente noi; fara migrare; rollback = revert template. Concentrare de risc pe base.html, nu coupling |
|
||
| S2 Eroare/Rescue | 2 GAP | (a) coduri duplicate in chips nedefinit -> US-006 dedupe; (b) cod necunoscut: invariant ORA-12899 pastrat |
|
||
| S4 Edge cases | 1 GAP HIGH | R-ODO/I-ODO cere odometruInitial; formularul nu il forta -> US-007 require dinamic (D10c) |
|
||
| S2/Idempotenta | 1 FIX | `obs` EXCLUS din cheie (`idempotency.py:98`) -> AC US-005 corectat (D8); `prestatii` in cheie -> re-cheiere OK |
|
||
| S6 Test | 1 GAP | 7 teme x componente pe fisier fierbinte; "esantion" prea lax -> US-008 test parametrizat ancorat pe SENTINEL |
|
||
| S8/S11 Trust | 1 HIGH | carduri-contor ascundeau sanatatea -> strip mereu-vizibil deasupra contoarelor (D6) |
|
||
| S9 Deploy | OK | Fara migrare; doar sent_today/sent_month (scoped). Rollback ieftin |
|
||
| S10 Trajectorie | 1 DECIZIE | secventiere 5.15 inainte de 5.14 (D9) |
|
||
| S11 Design/UX | OK + 4 EXT | strip trust; extinderi: salvare mapare din chip, bulk-fix, require dinamic odo, keyboard-first |
|
||
|
||
**Decizii incorporate (D6-D10):** strip sanatate mereu-vizibil (D6); operatie ramane in op_service +
|
||
copiata in obs (D7); obs exclus din idempotenta, AC corectat (D8); 5.15 inainte de 5.14 (D9); cele 4
|
||
extinderi acceptate (D10) -> US-007 imbogatit + US-009 (salvare mapare din chip) + US-010 (bulk-fix).
|
||
|
||
**Risc rezidual notat (non-blocant):** premisa "utilizare mobil reala" nevalidata (TODOS 5.13 F1);
|
||
7 teme = suprafata de test pe fisier fierbinte (deduparea grafit~dark/hartie~light ramana optiune).
|
||
|
||
**Spec-review loop (reviewer independent, context curat) — scor 7/10, verdict ISSUES -> remediat:**
|
||
- #1 HIGH (contradictie): US-006 spunea "reconstruieste prestatii ca itemi goi `{cod_prestatie}`",
|
||
ceea ce ar fi sters `cod_op_service`/`denumire` -> rupea D7 si US-009. **Remediat**: US-006 pastreaza
|
||
itemii existenti, seteaza doar `cod_prestatie`; pereche operatie<->cod definita; `idPrezentare` se
|
||
adauga in `payload.py`, nu in itemul intern.
|
||
- #2 MEDIUM: `sent_today`/`sent_month` nu aveau sursa de timp (nu exista `sent_at`). **Remediat**:
|
||
US-003 foloseste `status='sent' AND date(updated_at)` cu justificare (randul `sent` nu mai e scris
|
||
pana la purge la +90z) -> fara migrare.
|
||
- #3 MEDIUM: US-006 subestima rewrite-ul handler-elor (logica single-`prestatii[0]`). **Remediat**:
|
||
Fisierele US-006 numesc liniile exacte de rescris.
|
||
- #5/#6 LOW: suprafata JS reala (US-007) + tinta de click "De corectat" (US-003). **Remediat** (note adaugate).
|
||
- #4 LOW (scope): US-009/US-010 sunt adiacente FUNCTIONALE (din SELECTIVE EXPANSION), dincolo de
|
||
obiectivul pur de propagare design. **Acceptat constient** (alegerea userului); ramane optiunea de
|
||
a le scoate intr-un PRD separat daca propagarea design e ce e urgent.
|
||
|
||
### /plan-eng-review — 2026-06-28 (model claude/opus; outside-voice = Claude subagent, Codex usage-limit)
|
||
|
||
| Review | Trigger | Why | Runs | Status | Findings |
|
||
|--------|---------|-----|------|--------|----------|
|
||
| CEO Review | `/plan-ceo-review` | Scope & strategy | 1 | issues_open->remediat | 10 stories, 4 ext acceptate, spec-review remediat |
|
||
| Eng Review | `/plan-eng-review` | Architecture & tests (required) | 1 | issues_open->remediat | 7 issues (2 HIGH), 1 regresie IRON-RULE, +2 stories noi |
|
||
| Outside Voice | Claude subagent | Independent 2nd opinion | 1 | issues_found | 10 findings; 2 HIGH absorbite, restul foldate |
|
||
|
||
**Step 0 scope:** acceptat ca-atare (10 stories). Gate de complexitate = breadth, nu depth (zero clase/servicii noi). User a confirmat pastrarea US-009/US-010.
|
||
|
||
**Constatari eng-review (toate confirmate cu userul si foldate in AC):**
|
||
- **E1 (ARCH, HIGH, conf 9/10)** `routes.py:1326` `/repune` face `p0.pop("cod_op_service")` — sterge operatia, rupe D7+US-009. US-006: elimina pop + test regresie (IRON RULE).
|
||
- **E2 (Code-quality, conf 9/10)** config teme duplicat ~7 locuri pe base.html (anti-FOUC + 5 literali). US-001: o singura structura `THEMES`, restul derivat.
|
||
- **E3 (Test, conf 7/10)** obs concat la import poate dubla textul la re-import. US-005: derive-on-empty + test anti-dublu.
|
||
- **E7 (Perf/Correctness, conf 8/10 — outside-voice)** `date(updated_at)` UTC numara gresit `azi`/`luna` peste granita local RO. US-003: bucketare timp local + test granita.
|
||
|
||
**Outside-voice (Claude subagent) — material absorbit:**
|
||
- **OV/E4 (HIGH, conf 9/10)** chip-uri lista plata fara binding op<->cod -> rupe US-009; dedup-dupa-cod sterge operatie legala. US-006/007: picker PE operatie cand exista op; flat doar fara op; dedup per-item.
|
||
- **OV/E5 (HIGH, conf 9/10)** Val 3 conflict same-function: US-005+US-006 rescriu `post_corecteaza`. §6: serializare US-005 -> US-006 pe routes.py.
|
||
- **OV/E6 (MED, conf 8/10)** US-007 supraestimeaza JS custom intr-un app HTMX. US-007: hx-post server-driven primary; reveal odo gratis; select/datalist nativ.
|
||
- **OV/E8 (MED securitate, conf 7/10)** GET-uri de listare globale neprotejate (scurgere VIN/PII cross-cont, CLAUDE.md). User a ales INTERLEAVE -> **US-011** (account-scope + teste).
|
||
- **OV minore foldate:** reconcile drift pe `updated_at` (caveat US-003); cost poll non-sargabil (notat, non-blocant); cuplaj `EDIT_FIELDS` pentru obs preview (US-005 AC).
|
||
|
||
**TODO (decizie user):** premisa mobil nevalidata -> user a ales BUILD-IN-PR -> **US-012** (analytics device-mix, fara PII, reuse `app_events`).
|
||
|
||
**Scope actualizat:** 10 -> **12 stories** (+US-011 authz, +US-012 analytics). Fara migrare de schema. Outside-voice a confirmat "no migration" TRUE.
|
||
|
||
**Failure modes — gap critic:** niciun gap critic ramas silent. Cel mai aproape: E1 (regresie tacuta op_service pe /repune) — acum acoperit de test obligatoriu. E7 (off-by-a-day tacut) — acum cu test de granita.
|
||
|
||
**VERDICT:** CEO + ENG CLEARED — gata de executie. 12 stories. Outside-voice absorbit (2 HIGH foldate). Fara migrare de schema.
|
||
|
||
NO UNRESOLVED DECISIONS
|