# PRD 5.23 — Editor prestatii unificat in modalul "Editare rand" (un select, un rand de chips) **Stare**: inchis (2026-07-08; executie echipa agenti Sonnet TDD, VERIFY context curat PASS, code-review CLOSE = 1 blocker de siguranta prins + reparat. Vezi Anexa /autoplan, GSTACK REVIEW REPORT, Raport VERIFY; mockup final aprobat: select cu placeholder-tinta) > Proces: `docs/ROADMAP.md` §5. Contract RAR: `docs/api-rar-contract.md`. > Context vizual: screenshot `2026-07-07-003.jpg` (modal "Editare rand 4", badge "Cod RAR lipsa"). ## 1. Obiectiv Modalul "Editare rand" (import preview + corectie Trimiteri) are azi TREI controale pentru aceeasi sarcina — asocierea codurilor RAR la prestatii: (1) rand per operatie cu select "— alege cod RAR —" + buton "Adauga", (2) container separat de chips pentru coduri libere (ex. `OE-1 ×`), (3) select "+ Adauga alta operatie / cod RAR" cu buton "+". Utilizatorul nu stie care control face ce, iar chips-urile apar in containere diferite, pe randuri diferite. Livrabila: **un singur control** — un rand unic de chips (toate codurile, legate de operatie sau libere) + **un singur select** care adauga chip instant la selectie (fara butoane "Adauga"/"+"). Selectul primeste sugestii fuzzy/k-NN pentru operatia nemapata si optiunea "Nu se declara la RAR". ## 2. Non-Goals (anti scope-creep) - NU se schimba panoul de mapare (`_mapari.html`, `_preview_import.html`). EXCEPTIE confirmata in review (eng, 2026-07-07): `resolve_prestatii` primeste O SINGURA treapta noua de precedenta — `exclus=1` explicit pe item (din override/corectie) supravietuieste re-rezolvarii (azi `it.pop("exclus", None)` la `mapping.py:298` il sterge, ceea ce face US-004 imposibil altfel). Restul precedentei ramane neatins. - NU se schimba contractul API `POST /v1/prezentari`. `override_json["prestatii"]` castiga campul `exclus` pe item; itemii exclusi RAMAN in `payload_json` (adnotati), nu mai sunt eliminati la salvare — `split_prestatii_excluse` ii scoate doar din payload-ul trimis la RAR. - NU se schimba validarea codului fata de nomenclator la salvare (invariant ORA-12899) — ramane. - NU se adauga cautare/autocomplete text in select (ramane `` nativ cu tot nomenclatorul — **acceptat**; autocomplete ramane non-goal. ## 7. Valuri de executie (graful de dependente) ``` Val 1: [US-001] ← fara dependente Val 2: [US-002] [US-004] ← deblocate de US-001, fisiere distincte → paralel Val 3: [US-003] ← deblocat de US-002 Val 4: [US-005] ← verificare finala E2E ``` --- ## Anexa /autoplan — Review CEO (Faza 1, 2026-07-07, mod SELECTIVE EXPANSION, voci: subagent-only) ### NOT in scope (decizii din review) - Autocomplete/cautare in picker — non-goal confirmat de utilizator. - Schimbarea panoului de mapare / precedentei `resolve_prestatii` — non-goal PRD. - Badge-uri sursa sugestie (GOLD/SILVER) in select — respins (P5): optgroup "Sugestii" e suficient. - "Bulk: aplica maparea la N randuri identice" in preview — deferat TODOS (in afara blast radius). - Metrici de baza (deschideri modal / % randuri mapate manual) — deferat TODOS. ### What already exists (refolosit, nu reconstruit) - Stare chips in hidden inputs + `/form-chips` stateless (`_chips_prestatii.html`, `routes.py:2402`). - Validare cod vs nomenclator la add si la save (c6e29aa); mesaje `chips_error`/`chips_ok` (5.22). - Sugestii: `suggest_codes` + `sugestie_principala` (`mapping.py:117,868`) — aceleasi surse ca preview. - `EXCLUDE_SENTINEL`/`split_prestatii_excluse` (0bc6187); `apply_row_override` (c6e29aa). - Empty state nomenclator lipsa; `has_r_odo`/reveal odometru; buton "salveaza ca regula" (Trimiteri). ### Dream state delta Planul muta editorul de la 3 controale/4 actiuni la 1 control/2 actiuni si aduce sugestiile in modal — pas direct spre idealul 12 luni (o singura componenta de mapare peste tot). Ramase pe traiectorie: autocomplete (daca nomenclatorul creste), componenta partajata cu panoul de mapare. ### Arhitectura (Sectiunea 1) ``` [modal Editare rand]──include──▶[_form_editare]──include──▶[_chips_prestatii (REWRITE)] │ │ hx-post change/× ▼ ▼ POST editeaza (import) ─┐ POST /form-chips (add|remove) POST corecteaza (Trim.) ─┴─▶ _chips_state_from_form ──▶ apply_row_override(prestatii+exclus) │ (commit) suggest_codes/mapping.py ◀──────┘ resolve + split_prestatii_excluse ``` Cuplaj NOU: /form-chips → motorul de sugestii (pana acum doar preview-ul) — justificat (reuse), cu conditia caii NE-blocante pe request (lock embeddings non-blocking, PRD 5.21). Fara migrare DB (`exclus` traieste in override_json). Rollback: git revert simplu. Fereastra de deploy: un modal deschis peste deploy trimite campuri vechi (`chips_add_cod_{i}`) pe care handlerul nou le ignora — utilizatorul redeschide editarea; acceptat (sesiune de editare scurta). ### Registru erori & rescue (Sectiunea 2) ``` CODEPATH | CE POATE ESUA | TRATARE | USER VEDE --------------------------|------------------------------------|--------------------------------|----------- /form-chips add | cod gol / necunoscut | chips_error (exista) | mesaj rosu /form-chips add | index tinta iesit din forma | chips_error (exista) | mesaj rosu /form-chips add NED | __NEDECLARAT__ fara tinta | GAP -> chips_error nou | mesaj rosu /form-chips render | motor sugestii rece/dezactivat | GAP -> degradare la nomenclator| select fara optgroup /form-chips | nomenclator gol | empty state (exista) | avertisment save (ambele) | cod necunoscut in stare | re-render cu eroare (exista) | modal + eroare save (ambele) | stare veche fara chip_exclus | default exclus=0 (PRD risc) | nimic (compat) save (ambele) | CSRF invalid/expirat | 403 (existent) | eroare form enqueue | toate operatiile excluse | excluded / needs_data (exista) | stare explicita ``` GAP-uri de inchis in implementare: NED-fara-tinta si degradarea sugestiilor (ambele = criterii noi). ### Securitate (Sectiunea 3) Fara endpoint nou. Input nou `chip_exclus`: parsare stricta ("1" -> 1, orice altceva -> 0). Sentinelul `__NEDECLARAT__` nu ajunge NICIODATA in payload RAR (split inainte de enqueue — invariant existent; test explicit). XSS: `denumire` in options/title — autoescape Jinja (exista). Sugestii cross-account: politica 5.14 (suggestion-only) neschimbata. CSRF pe /form-chips: exista. Nicio amenintare High nemitigata. ### Edge cases date/interactiune (Sectiunea 4) - Dublu `change` in zbor: `hx-disabled-elt="this"` pe select (criteriu US-002). - Focus pierdut dupa swap `outerHTML`: focusul revine pe select dupa add (criteriu nou US-002). - Navigare cu tastatura prin select declanseaza change pe unele browsere: acceptat (PRD risc), chip-ul gresit se scoate cu un click. - Iesire din modal mid-edit: stare pierduta (comportament existent, form-local) — neschimbat. - Dedup (operatie,cod) si cod liber duplicat: exista, se pastreaza. - Mod plat + `has_r_odo`: pastrate explicit (spec actualizat). ### Calitate (Sectiunea 5) 4 actiuni -> 2 reduce ramificarea din `post_form_chips` (~120 linii azi). `_chips_state_from_form` ramane UNICUL parser, partajat de ambele endpoint-uri de save (risc PRD; test comun). Alternativa "un singur camp hidden JSON" in loc de 4 liste paralele — decizie de gust (vezi gate). ### Teste (Sectiunea 6) — completari la RED-urile din PRD - `test_add_nedeclarat_fara_tinta_da_eroare` (US-001) - `test_sugestii_indisponibile_degradare_fara_optgroup` (US-003) - `test_mod_plat_fara_operatii_select_doar_coduri_libere` (US-002) - `test_has_r_odo_pastrat_dupa_actiuni` (US-002) - `test_sentinel_nedeclarat_nu_ajunge_in_payload` (US-004) - Template: `test_select_are_hx_disabled_elt_this` (capcana 5.22) ### Performanta (Sectiunea 7) Sugestiile se calculeaza la FIECARE re-render /form-chips pentru operatia tinta — bounded (matvec numpy 5.21, corpus 17k, <100ms tipic); calea request NU asteapta warmup (lock non-blocking). 18 optiuni in select — trivial. Fara probleme. ### Observabilitate (Sectiunea 8) Starea randurilor e deja vizibila in dashboard (needs_mapping/excluded). Gap acceptat si deferat TODOS: metrica "deschideri modal / sugestie acceptata" (leaga de decizia de produs, nu de 5.23). ### Deploy (Sectiunea 9) Fara migrare, fara feature flag (UI inlocuit atomic in acelasi template). Worker neatins. Post-deploy: smoke = deschide un rand needs_mapping, adauga un cod, salveaza. ### Traiectorie (Sectiunea 10) Reversibilitate 4/5 (revert curat; starile cu exclus persistate raman valide — split exista independent). Datorie REDUSA (actiuni duplicate eliminate). Comentariul de header din `_chips_prestatii.html` se rescrie (diagrama noua a starii — parte din US-002). ### Design/UX la nivel CEO (Sectiunea 11) Ierarhie: chips (starea) inaintea selectului (actiunea) — corect. Stari acoperite: gol (hint), nomenclator lipsa, eroare, succes, exclus. Mobil: rand cu wrap; latimea selectului pe 390px = criteriu in faza Design. Accesibilitate: aria-labels existente se pastreaza; focus dupa swap. Riscul de mis-binding (tinta implicita + fara text permanent) — ridicat de vocea externa, decizie la gate. Detaliu complet in Faza 2 (plan-design-review). ### Decision Audit Trail (/autoplan) | # | Faza | Decizie | Clasificare | Principiu | Rationament | Respins | |---|------|---------|-------------|-----------|-------------|---------| | 1 | CEO | Abordare A (rescriere template + unificare actiuni) | Mecanica | P1,P5 | B pastreaza confuzia; C incalca non-goal aprobat | B, C | | 2 | CEO | A11y tastatura + focus dupa swap → criterii US-002 | Mecanica | P1 | in blast radius, 2 fisiere | — | | 3 | CEO | Badge sursa sugestie in select → respins | Mecanica | P5 | optgroup "Sugestii" spune deja asta | badge-uri | | 4 | CEO | Bulk-apply mapare pe randuri identice → TODOS | Mecanica | P2,P3 | in afara blast radius (panou preview = non-goal) | — | | 5 | CEO | Metrici deschideri-modal/sugestii → TODOS | Mecanica | P3 | produs, nu 5.23 | — | | 6 | CEO | GAP-uri erori (NED-fara-tinta, degradare sugestii) → criterii noi | Mecanica | P1 | zero silent failures | — | | 7 | CEO | Auto-advance tinta + fara eticheta pe select | TASTE (gate) | — | voce externa: risc mis-binding pe declaratii ireversibile | — | | 8 | CEO | Paleta click 18 coduri vs select nativ | TASTE (gate) | — | premisa "tot nomenclatorul" e falsa (18 coduri fixe) | — | | 9 | CEO | Stare chips: 4 liste hidden vs un camp JSON | TASTE (gate) | — | a 3-a regresie in zona; JSON = sursa unica | — | | 10 | CEO | Test cu operator real in US-005 | Mecanica | P1 | verifica direct riscul de mis-binding; cost ~0 | — | | 11 | Design | Scara de dominanta vizuala (tabel §3.1) | Mecanica | P1,P5 | 3 tratamente concurente fara ierarhie = confuzia initiala reambalata | — | | 12 | Design | Stari loading/eroare/gol/complet specificate (tabel §3.1) | Mecanica | P1 | eroarea de retea lasa selectul disabled permanent altfel | — | | 13 | Design | Tinta invarianta (exista cat sunt nemapate; click pe tinta = no-op) | Mecanica | P5 | interzice starea "cod liber cu warnings inca pe rand" tacuta | — | | 14 | Design | Comutare tinta CLIENT-SIDE (fara round-trip) | Mecanica | P3 | round-trip pe click de highlight = lent + race cu add-ul in zbor | server-side | | 15 | Design | Ordine chips stabila + focus pe select dupa swap | Mecanica | P1 | reflow-ul + focusul pierdut rup fluxul de tastatura pe 10 randuri | — | | 16 | Design | Optiuni `COD — denumire`; chip warning ellipsis 18ch | Mecanica | P5 | 18 coduri criptice fara denumire = memorare fortata | — | | 17 | Design | Nedeclarat vizibil (line-through, nu mut) + mesaj cu numele operatiei | Mecanica | P1 | cea mai riscanta actiune legala nu poate fi cea mai discreta | stil mut | | 18 | Design | Mesaje chips_ok/err raman pattern 5.22 (persista pana la actiune) | Mecanica | P4 | timer de auto-dismiss = JS nou fara valoare | timer 4s | | 19 | Design | Auto-advance tinta: pastrat cu emfaza vs eliminat | TASTE (gate) | — | CEO-voice: elimina (mis-binding); Design-voice: pastreaza (momentum) + emfaza | — | | 20 | Design | Touch targets × pe chips sub 44px (UI compact 11px existent) | Mecanica | P4 | consecvent cu design-ul compact existent; nota in TODOS a11y | 44px | | 21 | Eng | `exclus` supravietuieste `resolve_prestatii` (treapta noua, non-goal amendat) | Mecanica | P1 | P1 confirmat: `it.pop("exclus")` mapping.py:298 face US-004 imposibil; alternativa (regula pe cont) schimba semantica aprobata | regula per-cont; taierea US-004 | | 22 | Eng | Itemii exclusi raman in payload_json + `exclus` in toti constructorii de chips | Mecanica | P1 | P1 confirmat: routes.py:1744 ii elimina → × pe Nedeclarat imposibil dupa salvare | — | | 23 | Eng | `chips_submission_id` hidden + ecou in /form-chips ("salveaza ca regula" persistent) | Mecanica | P1,P3 | butonul dispare azi la prima re-randare; fluxul nou trece TOT prin /form-chips | — | | 24 | Eng | `chips_pick` reset dupa orice raspuns; valoarea NEaplicata la Salveaza | Mecanica | P5 | clasa de bug c6e29aa; sentinel NED pozitional pe calea corectie | aplicare implicita | | 25 | Eng | Validare semantica tinta + chip_exclus strict + sectiune inerta in request | Mecanica | P1 | POST fabricat / index stale / race mid-flight leaga codul de alt item | — | | 26 | Eng | `chips_present=1` — golirea listei se persista (nu no-op) | Mecanica | P1 | `_chips_state_from_form` intoarce None pe form fara chips → golire imposibila | no-op actual | | 27 | Eng | Actualizarea testelor existente pe actiunile vechi in acelasi val | Mecanica | P6 | altfel suita e rosie intre Val 1 si Val 2 | — | | 28 | Eng | Emfaza chip nou pe index, nu pe cod | Mecanica | P5 | doua operatii cu acelasi cod = dubla evidentiere falsa | — | | 29 | Eng | Fara memoizare sugestii per re-render | Mecanica | P3 | rapidfuzz pe 18 randuri + k-NN gardat = bounded; memoizarea = complexitate fara castig | memoizare | | D2 | Gate | Auto-advance pastrat + placeholder-ul selectului numeste tinta | USER | — | leaga explicit codul de operatie la momentul alegerii, fara text sub control | doar emfaza; fara auto-advance | | D3 | Gate | Select nativ pastrat (mockup v1); paleta v2 vazuta si RESPINSA de utilizator | USER | — | directia utilizatorului; paleta ramane alternativa daca selectul se dovedeste lent (US-005 cu operator) | paleta 18 coduri | | D4 | Gate | Stare chips = UN hidden `chips_state` JSON versionat + fallback tranzitoriu liste vechi | USER | — | elimina clasa de regresii de aliniere pe index | 4 liste paralele | | D5 | Gate | Plan revizuit APROBAT de utilizator | USER | — | — | — | ### Anexa Eng (Faza 3) — diagrama de acoperire teste ``` CODE PATHS USER FLOWS [+] post_form_chips (rescris, 2 actiuni) [+] Corectie rand cu N operatii nemapate ├── add cu tinta valida [RED planificat] ├── sugestie→chip instant [RED + E2E US-005] ├── add fara tinta (liber) [RED planificat] ├── comutare tinta client [E2E US-005] ├── add NED cu/fara tinta [RED planificat] ├── × pe cod → warning [RED planificat] ├── tinta semantica invalida [RED planificat] ├── NED → Nedeclarat → × [RED planificat] ├── remove index/liber [RED planificat] ├── eroare retea la add [→E2E] [GAP inchis §3.1] └── dedup + cod necunoscut [exista, se muta] └── golire totala + salvare [RED chips_present] [+] resolve_prestatii treapta item-exclus [RED planificat — REGRESIUNE potentiala: IRON RULE, test obligatoriu ca exclus NU e sters si ca restul precedentei ramane identic] [+] _chips_state_from_form (4 liste + compat vechi + liste scurte) [RED planificat] [+] save x2 (import/corectie): exclus round-trip, NED respins, chips_pick ignorat [RED planificat] [+] template: hx-atribute, focus, sectiune inerta, mod plat, has_r_odo [RED planificat] COVERAGE planificata: 100% din ramurile noi au RED numit in stories. Fara eval-uri LLM (nu se ating prompturi). Regresiune-cheie: precedenta resolve_prestatii → test dedicat blocant. ``` ENG DUAL VOICES — CONSENS (subagent-only): ``` Dimensiune Claude Codex Consens 1. Arhitectura solida? DA* N/A OK (directia 4→2 actiuni corecta; * dupa fix P1-uri) 2. Acoperire teste suficienta? NU→DA N/A FLAGGED→INCHIS (6 teste noi adaugate in stories) 3. Riscuri de performanta? NU N/A OK (18 coduri, k-NN gardat, bounded) 4. Securitate acoperita? PARTIAL→DA N/A INCHIS (validare semantica tinta, chip_exclus strict, NED respins la save) 5. Drumuri de eroare tratate? PARTIAL→DA N/A INCHIS (§3.1 + criterii noi) 6. Risc de deploy gestionabil? DA N/A OK (fara migrare; fereastra modal-peste-deploy acceptata) ``` --- ## Raport VERIFY Verificator independent (subagent context curat, doar PRD + §5.6), 2026-07-08. **VERDICT: PASS** pe toate criteriile testabile; regresia de aur RAR live = NEPROBAT (contul QA fara creds RAR). - **Suita**: `python3 -m pytest -q` → verde. La VERIFY: 1672 passed, 1 skipped. Dupa fix-urile de code-review CLOSE: **1680 passed, 1 skipped** (+45 teste noi fata de baseline 1635). - **US-001..US-004** PASS cu dovezi de cod + teste (chips_state JSON unic, 2 actiuni add/remove, exclus supravietuieste resolve_prestatii cu test de regresie blocant pe precedenta, sentinel respins pe ambele cai). - **US-005 E2E browser** (import preview + Trimiteri corectie, Playwright): PASS pe (a) un singur rand de chips cu tinta evidentiata + placeholder-tinta + optgroup Sugestii + optiune "Nu se declara la RAR" la deschiderea initiala; (b) sugestie→chip cod instant fara "+"; (c) Nedeclarat → salvare → rand "Nedeclarat", redeschidere arata chipul, × readuce warning; (d) acelasi control la Trimiteri, "salveaza ca regula" persistent; (e) fara controale duble, zero erori JS de flux. - **Gap prins in executie** (dupa US-003, reparat inainte de VERIFY): tinta implicita + sugestiile nu se calculau la randarea initiala a modalului — helper partajat `_chips_target_si_sugestii` in toate caile de randare. ### Code-review CLOSE (high, 8 unghiuri + verify) — 1 BLOCKER de siguranta + 2 findings, toate reparate - **BLOCKER (CONFIRMED, reparat)**: itemii exclusi (`exclus=1`, cod null) ramaneau in `payload_json` pe calea de corectie, iar worker-ul ii trimitea la RAR ca `{codPrestatie: null}` → risc ORA-12899 / FINALIZATA partial ireversibil. Nimic nu facea `split_prestatii_excluse` la MOMENTUL trimiterii. Fix la chokepoint unic (worker, inainte de `build_rar_payload`) — acopera toate canalele (API/import/corectie) — + filtru defensiv in `build_rar_payload` (sare itemii exclusi / fara cod) + teste care dovedesc ca itemul exclus NU apare in payload-ul RAR. - **Consistenta round-trip (reparat)**: `commit_import`/`web_confirma_import` pastreaza acum prestatiile complete in `payload_json` (cheia idempotentei = doar declarabile, identica cu preview); bonus: bug latent de idempotenta preview↔commit in `_web_compute_preview` reparat. - **Conventii CLAUDE.md (reparat)**: referinte PRD/US-xxx/decizii scoase din comentariile noi. - **Robustete tinta (reparat)**: un item fara `cod_op_service` nu mai poate deveni tinta invizibila; sugestiile intorc gol pe denumire goala. ## GSTACK REVIEW REPORT | Review | Trigger | Why | Runs | Status | Findings | |--------|---------|-----|------|--------|----------| | CEO Review | `/plan-ceo-review` | Scope & strategy | 1 | CLEAR (PLAN via /autoplan) | 5 proposals, 2 accepted, 3 deferred; 8 constatari voce externa | | Codex Review | `/codex review` | Independent 2nd opinion | 0 | UNAVAILABLE (usage limit pana 2026-07-18) | voci = subagent-only | | Eng Review | `/plan-eng-review` | Architecture & tests (required) | 1 | CLEAR (PLAN via /autoplan) | 8 issues (4×P1 confirmate in cod), 0 critical gaps ramase | | Design Review | `/plan-design-review` | UI/UX gaps | 1 | CLEAR (FULL via /autoplan) | score: 6/10 → 9/10, 10 decizii in §3.1 | | DX Review | `/plan-devex-review` | Developer experience gaps | 0 | SKIPPED | fara scope developer-facing | - **CROSS-MODEL:** indisponibil (Codex la limita de utilizare); toate vocile externe = subagenti Claude independenti (context curat, fara review-ul anterior). - **VERDICT:** CEO + ENG + DESIGN CLEARED — gata de implementare. Decizii gate aplicate: D2 placeholder-tinta, D3 select (paleta respinsa), D4 stare JSON, D5 aprobat. NO UNRESOLVED DECISIONS