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>
48 KiB
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(commit41aa385),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
prestatiicompleta (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.
- luna asta + azi (secundar). Necesita extinderea numaratorilor cu
- D5 (Observatii = operatii service): in API-ul RAR, campul
obse DE FAPT denumirea operatiilor din service. Deciobs= text liber cu operatiile efectuate; la import, daca fisierul nu are coloana Observatii, concatenam denumirea operatiei de service inobs.obsramane inpayload_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 inobs.obse sink aditional, nu mutare; fluxul needs_mapping ramane neatins. (Rafineaza D5.) - D8 (idempotenta obs):
obse EXCLUS din cheia de idempotenta (idempotency.py:98). Deci editareaobsNU schimba cheia si NU poate crea duplicate — corecteaza AC-ul gresit din US-005.prestatiiESTE 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
prestatiisi 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):
/repuneface azip0.pop("cod_op_service")laroutes.py:1326— sterge operatia cand se seteaza un cod direct, rupand D7 si US-009. US-006 ELIMINA acelpopsi pastreazacod_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
VALIDla :22 + cinci literali paraleliCYCLE/VALID/ICONS/LABELS/NEXTla :758-765). US-001 CONSOLIDEAZA intr-o singura structura sursa-de-adevar (THEMESordonata) 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
obsse face DERIVE-ON-EMPTY (doar candobse 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-postcare re-randeaza partial-ul chips+form; reveal-ul conditionalodometruInitialrezulta 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 astase bucketeaza in timp local RO (UTC+2/+3), nu UTC —updated_atedatetime('now')UTC, decidate(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 bazanow) + 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):
vinla RAR e un singur camp (17 car., MAJUSCULE, fara O/I/Q) — cerinta "fara 2 campuri VIN" e deja respectata azi (_form_editare.htmlare un singurvin); ramane sa NU regresam.prestatiie deja lista in modelul intern (mapping.resolve_prestatii(prestatii: list[dict])) si in contractul RAR — multi-select nu cere model nou, ci editor nou.obsexista 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.
obssiprestatiitraiesc insubmissions.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.pyraman 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.htmle 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:
- 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/--line2primesc 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. - Selectorul ciclic parcurge TOATE: light -> dark -> petrol -> grafit -> cobalt -> cupru ->
hartie -> Auto, afiseaza eticheta temei curente, persistenta
localStorage(D2). - DRY (E2): config-ul de teme traieste intr-o SINGURA structura sursa-de-adevar
(
THEMESordonata, cu{id,label,icon}) din care se DERIVACYCLE/NEXT/ICONS/LABELS(azi 5 literali paraleli la base.html:758-765) SI setul anti-FOUCVALID(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). - "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). - Script anti-FOUC in
<head>seteazadata-themesincron pre-paint pentru toate starile; valoare necunoscuta -> Auto, fara blink. Valorile vechi raman valide (nu se mapeaza fortat). - Contrast AA pentru text principal in toate temele (light + hartie sunt cele luminoase).
DESIGN.mdactualizat: sectiunea cromatica + selector tema reflecta toate temele.
- Pastram temele EXISTENTE light/dark/petrol si ADAUGAM 4 teme noi grafit/cobalt/cupru/hartie,
definite prin token-urile EXISTENTE (
- 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:
.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)..lista-trimiteri-slimcu 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.- Varianta slim de camp formular: label 11px muted deasupra, input ~30px inaltime, fundal
--card2, mono pentru VIN/odometru/nr; integrata in macro-ulcampdin_macros.htmlprintr-un flag (slim=True), fara a rupe randarea actuala (default neschimbat). .chips+.chip(cu buton×de stergere) pentru prestatii multi-select; accesibil (buton real cuaria-label), stilat ca in mockup (accent 18%, font 10-11px).- 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 dinlanding.html. Cifra "De corectat" rosie = token (var(--errt)), nu#E05D5Dhardcodat, ca sa ramana AA pe temele luminoase (hartie/light). Referinta:docs/mockups/prd-5.15-mockups.html. - 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_countsextins cusent_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:
- 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. - 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).
- 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. - Cardul Trimise afiseaza trei valori temporale (D4): all-time (cifra principala) + "luna asta"
+ "azi" (sub-linie secundara).
_status_countsextins cusent_today/sent_month. Sursa de timp: NU exista coloanasent_at; folosimstatus='sent' AND date(updated_at)=.... Justificare (verificat): un randsentnu mai primeste scrieri ulterioare pana la purge-delete la +90z (purge_afterse seteaza in ACEEASI scriere care marcheazasent), deciupdated_at== momentul trimiterii pentru randurilesent-> fara migrare de coloana (respecta Non-Goal). Daca pe viitor apar scrieri post-sent, reevalueaza o coloanasent_atdedicata. Timezone (E7):updated_atedatetime('now')= UTC; bucketareaazi/luna astase face in TIMP LOCAL RO (ex.date(updated_at,'+3 hours'), aceeasi bazanow), 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 marcheazasentcuupdated_at= momentul reconcilierii, nu al inserarii RAR — pentru randurile reconciliate (rare)updated_atpoate diferi de momentul real al trimiterii. - 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. - Scoped pe cont; poll-ul existent (
/_fragments/status) randeaza noul antet fara a pierde tab-ul. - Responsive: cele 3 contoare pe un rand pe desktop, stivuite/2-pe-rand pe mobil, fara overflow.
- 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:
- 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:
- 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). - Filtre (data/vehicul/stare —
_coada.html), paginarea numerotata si bulk-delete pe randuri blocate (checkbox doar pegestionabil) raman FUNCTIONALE. - Click pe rand deschide
/_fragments/trimitere/{id}in modal (neschimbat). - Slim layout consistent desktop si <=1024px (cardurile responsive existente nu regreseaza).
- Pill-urile de stare folosesc maparea din
labels.py(zero etichete noi). Eticheta "Eroare VIN" din mockup-ul landing e DOAR ilustrativa — se folosestestare_scurtexistent (ex. "De corectat").
- Fiecare rand: stanga VIN mono scurt (
- 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:
obstraieste inpayload_json(campobsdin contractul RAR); fara coloana noua / migrare (D5).obsadaugat inEDIT_FIELDS;corecteazasiediteaza(preview) accepta si persistaobs.obsoptional (text liber, fara validare de continut, doar trim); apare inpayload_view.obsse include in payload-ul trimis la RAR (campobs).obse EXCLUS din cheia de idempotenta (idempotency.py:98) — deci editarea DOAR aobsNU schimba cheia si NU poate crea duplicat (D8). NU recalcula/forta cheia pe bazaobs. (Corecteaza formularea anterioara.)- 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 inobs; daca are coloana Observatii, se pastreaza textul ei. Format de concatenare definit (denumiri separate prin "; "). Fluxul needs_mapping ramane neatins. - Idempotent (E3): copierea operatiei in
obse DERIVE-ON-EMPTY (doar candobse gol) ca re-importul/re-editarea sa NU dubleze textul ("Schimb ulei; Schimb ulei"). Test dedicat anti-dublu-concat. - Cuplaj preview-import:
obsse adauga inEDIT_FIELDS(import_router.py:261);_merge_overrideil propaga (obs e free-text, cade pe ramura ne-canonicalizata — fara strip "0", doar trim).
- Verificare E2E:
POST /trimitere/{id}/corecteazacuobs-> persistat -> vizibil in detaliu; optional proba live RAR caobsapare 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_curentlaroutes.py:977-982+ injectia la1146-1164/1288-1324presupun 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.pye 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:
- 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 inprestatii[0](routes.py:1146-1164,1288-1324) — multi-select le rescrie ca: pastreaza itemii existenti cucod_op_service/denumire(invariant D7) si seteaza/adaugacod_prestatiepe ei.idPrezentare:nullse adauga inpayload.pyla construirea payload-ului, NU in itemul intern. E1 (CRITIC):/repuneface azip0.pop("cod_op_service", None)laroutes.py:1326— ELIMINA acelpop: cand se seteaza un cod direct,cod_op_service/denumireRAMAN pe item (altfel rupe D7 si US-009). Test de regresie obligatoriu (IRON RULE): op_service supravietuieste unui /repune cu cod. - 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 faraop_service. Aceasta pereche e ce consuma US-009 (salvare mapare op->cod). - 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). - Lista goala de coduri -> ramane
needs_mapping(nu se trimite fara cod). - 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.
- Recalcul idempotenta dupa editare (mecanism existent), cu prinderea coliziunii ca azi.
- Se pastreaza regula
odometruInitialobligatoriu cand lista contineR-ODO/I-ODO(contract §payload) — validare existenta, doar verificata pe lista.
- Handler-ele de editare accepta o LISTA de
- Verificare E2E:
POST /corecteazacu 2 coduri valide ->queuedcuprestatiide 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:
- 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").
- Observatii = textarea liber, legat de
obs(US-005). - 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 cacod_prestatiemultiplu (US-006). - Acelasi
_form_editare.htmlslujeste ambele modale (detaliu/corecteazasi preview/editeaza), fara duplicare; degradare fara JS rezonabila (chips ca lista, picker = select). - Require dinamic odometruInitial (D10c): cand lista de chips contine
R-ODOsauI-ODO, formularul DEZVALUIE si cereodometru_initial(contract §payload), previne 400 RAR si un drumneeds_data. Cand niciun chip R-ODO/I-ODO -> campul ramane optional/ascuns. - Editare keyboard-first (D10d): in picker, Enter adauga chip-ul selectat; sageti navigheaza optiunile; Esc inchide modalul; focus-ul revine logic dupa adaugare/stergere.
- Stilizare fidela mockup-ului pe toate temele; tinte 44px pe mobil; a11y (label-uri, aria, anunt de chip adaugat/sters pentru screen-reader).
- HTMX server-driven PRIMARY (E6): chips add/remove via
hx-postcare re-randeaza partial-ul chips+form; reveal-ul conditionalodometruInitialrezulta 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. - Referinta vizuala (criteriu din mockup):
docs/mockups/prd-5.15-mockups.htmldefineste 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:
pytest -q -m "not live"verde (fara regresii fata de baseline).- 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(reusesave_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:
- Cand operatia (
op_service) e cunoscuta si userul adauga un cod RAR prin chip, apare optiunea "salveaza ca regula op->cod"; la confirmare reuse EXACTsave_mapping+reresolve_account(acelasi mecanism ca maparea inline din 5.7), scoped pe cont + CSRF. - Re-rezolvarea deblocheaza si alte submission-uri
needs_mappingcu aceeasi operatie (pebatch_id). - 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).
- Cand operatia (
- 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:
- 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). - Scoped pe cont (404-before-409 ca la bulk-delete); doar randuri blocate eligibile.
- 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).
- Pe randurile blocate (checkbox existent pe
- 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:
- 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).
- Un cont nu poate enumera/citi VIN/PII al altui cont prin listare sau detaliu.
- Enforcement aliniat cu
AUTOPASS_REQUIRE_API_KEY(dev vs prod), fara a rupe contul id=1 implicit in dev. - 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.sqlSAUapp_events(reuse tabela de evenimente existenta — fara coloana noua dacaapp_eventspoarta semnalul),tests/test_device_mix.py(~3 fisiere) - Test intai (RED):
test_device_mix_inregistrat,test_device_mix_fara_pii. - Acceptance criteria:
- 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 dacaapp_eventspoarta semnalul. - Un mod simplu de citire a raportului (query/admin), suficient pentru a decide investitia mobil.
- Zero PII nou; aliniat retentiei
app_eventsexistente.
- La acces dashboard, clasifica grosier viewport/UA in desktop/mobil si inregistreaza in
- 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 / hartielight 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_countsextins. - 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
obscand 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 grafitdark / hartielight 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.pyare autor unic in Val 3: US-005 INAINTE de US-006 (ambele inpost_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
prestatiisi 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/--line2in toate (US-001, DRYTHEMES); componente slim doar cu tokeni, zero hex (US-002, ancorat peSENTINEL-COMPONENTE-SLIM); strip sanatate D6 + 3 contoare +sent_today/sent_monthbucketate timp local RO+3 hours(US-003, E7); lista slim cu filtre/paginare/bulk pastrate (US-004);obseditabil + EXCLUS din cheia idempotenta (idempotency.py:98) + concat derive-on-empty anti-dublu (US-005, D8/E3); prestatii multi-cod viagetlist+ E1 IRON RULE (cod_op_servicesupravietuieste/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 reusesave_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 reuseapp_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 prinvar(--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 grafitdark/hartielight 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 sterscod_op_service/denumire-> rupea D7 si US-009. Remediat: US-006 pastreaza itemii existenti, seteaza doarcod_prestatie; pereche operatie<->cod definita;idPrezentarese adauga inpayload.py, nu in itemul intern. - #2 MEDIUM:
sent_today/sent_monthnu aveau sursa de timp (nu existasent_at). Remediat: US-003 folosestestatus='sent' AND date(updated_at)cu justificare (randulsentnu 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/repunefacep0.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 gresitazi/lunapeste 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); cuplajEDIT_FIELDSpentru 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