Files
rar-autopass/docs/prd/prd-5.15-propagare-design-dashboard-editare.md
Claude Agent 4caf055c53 docs(prd): 5.15 revizuit prin /plan-ceo-review (SELECTIVE EXPANSION)
Review CEO + spec-review independent (scor 7/10). Scope 8 -> 10 stories / 6 valuri.

Decizii incorporate (D6-D10):
- D6 strip sanatate mereu-vizibil deasupra contoarelor (zero-silent-failures)
- D7 operatia ramane in op_service + copiata in obs (nu se muta)
- D8 obs EXCLUS din idempotenta (idempotency.py:98) - AC US-005 corectat
- D9 secventiere 5.15 inainte de 5.14
- D10 4 extinderi acceptate: US-009 salvare mapare din chip, US-010 bulk-fix,
  require dinamic odometruInitial + keyboard-first (US-007)

Remedieri din spec-review independent:
- #1 contradictie prestatii: itemii pastreaza op_service/denumire, idPrezentare
  in payload.py (rupea D7/US-009 in forma initiala)
- #2 sent_today/month: status='sent' AND date(updated_at), fara migrare
- #3 US-006 numeste liniile de rewrite din handler-e (nu "fara schimbare logica")
- #5/#6 nota suprafata JS + click target "De corectat"

TODOS += premisa mobil nevalidata, dedup teme grafit~dark, optiune PRD separat US-009/010.

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

32 KiB
Raw Blame History

PRD 5.15 — Propagare design landing in aplicatie (dashboard compact + editare slim, VIN unic, prestatii multi-select)

Stare: draft

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. 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).

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.pytest_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/--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.
    • Selectorul ciclic parcurge TOATE: light -> dark -> petrol -> grafit -> cobalt -> cupru -> hartie -> Auto, afiseaza eticheta temei curente, persistenta localStorage (D2).
    • "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> seteaza data-theme sincron 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.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.pytest_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-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.
    • 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).
    • .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).
    • 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.pytest_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.
    • 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).
    • 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.
    • 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.
  • 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.pytest_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 pe gestionabil) 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).
  • 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.pytest_obs_editabil_persistat_corecteaza, test_obs_persistat_preview_editeaza, test_obs_optional_gol_ok, test_import_concateneaza_operatie_in_obs
  • Acceptance criteria:
    • obs traieste in payload_json (camp obs din contractul RAR); fara coloana noua / migrare (D5).
    • obs adaugat in EDIT_FIELDS; corecteaza si editeaza (preview) accepta si persista obs.
    • obs optional (text liber, fara validare de continut, doar trim); apare in payload_view.
    • 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.)
    • 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.
  • 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, /repunerescrie 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.pytest_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 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.
    • 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).
    • 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 (acelasi cod adaugat de 2x) -> deduplicate inainte de persistare (cheia sorteaza deja dupa identitate, dar lista persistata nu trebuie sa contina duplicate).
    • Recalcul idempotenta dupa editare (mecanism existent), cu prinderea coliziunii ca azi.
    • 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.pytest_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: fiecare cod ca chip cu ×; un picker (dropdown din nomenclator) adauga un cod nou; lista se trimite ca cod_prestatie multiplu (US-006).
    • Acelasi _form_editare.html slujeste ambele modale (detaliu /corecteaza si preview /editeaza), fara duplicare; degradare fara JS rezonabila (chips ca lista, picker = select).
    • 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.
    • 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).
    • Suprafata JS reala (nota de efort): chips add/remove client-side + picker navigabil cu tastatura + management focus + reveal conditional odometruInitial = JS ne-trivial intr-un app HTMX/minimal-JS. Fallback fara JS: picker = <select> simplu (server valideaza R-ODO->odo, deci no-JS da needs_data corect, nu date gresite) — multi-select se degradeaza la un cod/submit.
  • 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 (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.pytest_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 EXACT save_mapping + reresolve_account (acelasi mecanism ca maparea inline din 5.7), scoped pe cont + CSRF.
    • Re-rezolvarea deblocheaza si alte submission-uri needs_mapping cu aceeasi operatie (pe batch_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).
  • 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.pytest_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).
  • Verificare E2E: selectez 3 randuri needs_mapping + aplic un cod -> toate 3 -> queued.
  • Verificare E2E: rulare completa documentata in Raportul VERIFY.

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 grafitdark / 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_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 grafitdark / hartielight in eticheta selectorului.

6. Valuri de executie (graful de dependente)

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 + prestatii (fisiere backend; in paralel cu UI)
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)
Val 6: [US-008]                 regresie + E2E final (dupa toate)

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

Completat de subagentul verificator (context curat) in faza VERIFY — vezi ROADMAP §5.6. PASS/FAIL per criteriu, cu dovezi (output pytest citat, E2E pe RAR test). Lipseste pana la VERIFY.


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 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.

VERDICT: APROBAT cu modificari incorporate (+remedieri spec-review). Scope: 10 stories / 6 valuri. Fara CODEX/cross-model in aceasta rulare (review uni-model). Gata de executie dupa confirmarea userului.

NO UNRESOLVED DECISIONS