Files
rar-autopass/docs/prd/prd-5.23-editor-prestatii-unificat.md
Claude Agent 56ebe1f7cb feat(editor): editor prestatii unificat in modal - un select, un rand de chips (PRD 5.23)
Inlocuieste cele 3 controale de asociere cod RAR cu un singur rand de chips
+ un singur select care adauga instant la change. Stare = un hidden chips_state
JSON versionat; post_form_chips redus la 2 actiuni (add/remove). Optgroup
Sugestii (fuzzy/k-NN) + optiune "Nu se declara la RAR" in select, cu tinta
implicita evidentiata si placeholder care o numeste. exclus persistat prin
payload_json (treapta noua de precedenta in resolve_prestatii, round-trip complet).

Siguranta: itemii exclusi sunt scosi din payload la momentul trimiterii
(worker split_prestatii_excluse inainte de build_rar_payload + filtru defensiv),
ca sa nu ajunga NICIODATA la RAR ca codPrestatie:null.

Suita: 1680 passed, 1 skipped.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-08 15:45:34 +00:00

515 lines
38 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!-- /autoplan restore point: /home/claude/.gstack/projects/romfast-rar-autopass/main-autoplan-restore-20260707-194818.md -->
# 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 `<select>` nativ; imbunatatire separata).
- NU se atinge butonul "salveaza ca regula" de la Trimiteri — ramane, doar mutat pe chip-ul nou.
## 3. Comportamentul controlului unificat (spec)
```
Prestatii — coduri RAR
┌───────────────────────────────────────────────────────────────┐
│ [⚠ OP-ITP · fara cod] [OE-1 ×] [— alege cod RAR — ▾] │
└───────────────────────────────────────────────────────────────┘
```
- **Un singur container** `.chips` cu toate elementele: chips-uri cod (× sterge), chips-uri warning
pentru operatii nemapate, si selectul unic la capat. Wrap natural doar la lipsa de spatiu —
niciodata containere/chenare separate pe zone.
- **Chip cod legat de operatie**: eticheta `COD`, titlu/tooltip cu denumirea operatiei sursa
(ex. `OE-8 — Inlocuire anvelope`). `×` scoate codul; daca operatia ramane fara cod, reapare
chip-ul warning al operatiei.
- **Chip cod liber** (fara operatie sursa): identic vizual, tooltip "cod adaugat manual".
- **Chip warning** (`⚠ OP-ITP · fara cod`): reprezinta operatia din import nemapata. Click pe el
il face **tinta activa** (evidentiat); implicit tinta = prima operatie nemapata.
- **Selectul unic**, `hx-trigger="change"``POST /form-chips` cu `chips_action=add`:
- daca exista tinta activa (operatie nemapata) → codul se leaga de acea operatie
(chip warning → chip cod); tinta trece automat la urmatoarea operatie nemapata;
- **placeholder-ul selectului numeste tinta** (decizie gate D2, 2026-07-07): cu tinta activa →
`Cod pentru OP-X — alege`; fara tinta → `— adauga cod RAR —`. Legatura cod↔operatie e
explicita la momentul alegerii, fara text suplimentar sub control;
- fara operatii nemapate → codul se adauga ca chip liber;
- dupa adaugare selectul revine la placeholder; chip-ul nou primeste evidentierea `.chip-nou`.
- **Continutul selectului**, cand tinta e o operatie nemapata:
- `optgroup "Sugestii"` sus: sugestia principala + fuzzy/k-NN top-3 pentru denumirea operatiei
(aceleasi surse ca in `_preview_import.html:89-121`);
- optiunea `"Nu se declara la RAR (exclude operatia)"` = sentinel `__NEDECLARAT__`
(`EXCLUDE_SENTINEL`, `app/mapping.py:44`) → operatia primeste chip distinct `Nedeclarat`
(stil mut, ×-ul readuce chip-ul warning), item anotat `exclus` in starea chips;
- restul nomenclatorului, ca azi.
Fara tinta (doar cod liber): fara optgroup Sugestii si fara `__NEDECLARAT__`.
- **Dedup**: acelasi `(operatie, cod)` nu se adauga de doua ori (comportament existent pastrat).
- **Fara text explicativ permanent** sub control (decizie utilizator la aprobarea mockup-ului,
2026-07-07): starea tintei se comunica DOAR vizual (stilul chip-ului warning evidentiat);
mesajele `chips_ok`/`chips_error` raman exclusiv ca feedback tranzitoriu la actiuni.
- Mockup aprobat: artifact "Mockup — Editor prestatii unificat (PRD 5.23)".
- **Modul plat** (corectii fara operatii din import) foloseste ACELASI control: zero chips warning,
selectul adauga doar coduri libere. Logica `has_r_odo`/`data-has-r-odo` (reveal odometru initial
pe R-ODO/I-ODO) si stilul `chip-warn` pe aceste coduri se pastreaza neschimbate.
### 3.1 Specificatie vizuala si de stari (din review-ul de design, 2026-07-07)
**Scara de dominanta vizuala — UN SINGUR tratament puternic simultan:**
| Element | Fundal / chenar / text | Nota |
|---|---|---|
| Chip tinta activa | fundal warn-soft, chenar SOLID 1px `--warn` + inel 2px warn-transparent | singurul element "tare" din rand |
| Chip warning (ne-tinta) | fundal warn-soft, chenar DASHED 1px `--warn` | click → devine tinta |
| Chip cod | fundal `--card2`-albastru (`--accent` 15%), text `--accent`, chenar subtil | eticheta `COD` mono |
| Chip Nedeclarat | fundal gri mut, text taiat (line-through), chenar dashed gri | NU discret pana la invizibil |
| `.chip-nou` | animatie de emfaza >=1.5s (fade), tranzitorie | nu concureaza cu tinta |
| Chip R-ODO/I-ODO | `chip-warn` existent, neschimbat | reveal odometru pastrat |
**Invariante de interactiune:**
- Ordinea chips e STABILA: operatiile in ordinea din import, codurile libere la coada; o rezolvare
nu re-ordoneaza randul (warning → cod in acelasi slot).
- Cat exista operatii nemapate, EXISTA tinta (implicit prima nemapata); click pe tinta activa =
no-op (nu se poate "deselecta" — adaugarile libere sunt posibile doar fara warnings).
- Click pe chip warning = comutare de tinta CLIENT-SIDE (toggle clasa + hidden `chips_target_index`),
fara round-trip; indexul tintei pleaca la server la urmatorul add.
- Selectul are id stabil (`chips-picker`); dupa swap `outerHTML`, focusul revine pe select.
- Optiunile selectului: `COD — denumire nomenclator`; in optgroup "Sugestii" acelasi format
(optgroup-ul e singura marca de sugestie). Eticheta chip warning: max ~18ch + ellipsis,
text complet in `title`.
**Tabel de stari (ce VEDE utilizatorul):**
| Stare | Ce se vede |
|---|---|
| Loading (add in zbor) | selectul disabled (`hx-disabled-elt="this"`) + stil `.htmx-request` pe sectiune; dedup face inofensiv un dublu-fire |
| Eroare add (400/500/retea) | mesaj `chips_error` SUB control (pattern 5.22, persista pana la urmatoarea actiune); selectul re-activat; tinta neschimbata; `hx-on::response-error` acopera esecul de retea |
| Gol (mod plat, zero chips) | doar selectul cu placeholder "— adauga cod RAR —"; container cu min-height (modalul nu salta la primul chip) |
| Succes partial | chip-ul nou cu emfaza >=1.5s; tinta avanseaza (vezi decizia de la gate) |
| Complet (zero nemapate) | mesaj tranzitoriu "Toate operatiile au cod" (`chips_ok`); optgroup Sugestii si `__NEDECLARAT__` dispar din select |
| Nedeclarat | mesajul numeste operatia: "OP-X nu se va declara la RAR" |
| Nomenclator gol | empty state existent (pastrat) |
## 4. Stories atomice
### US-001: Stare chips extinsa cu `exclus` + actiuni add/remove unificate in backend
**Ca** dezvoltator **vreau** o singura pereche de actiuni `add`/`remove` pe starea chips (cu tinta
optionala si suport sentinel `__NEDECLARAT__`) **pentru ca** azi exista 4 actiuni suprapuse
(`add`, `add_flat`, `add_extra`, `remove_flat`) care dubleaza logica.
- **Depinde de**: —
- **Fisiere**: `app/web/routes.py` (`post_form_chips` :2402-2521, `_chips_state_from_form` :3862-3918), `tests/test_web_chips.py` (~2 fisiere)
- **Test intai (RED)**: `tests/test_web_chips.py``test_add_cu_tinta_leaga_de_operatie`,
`test_add_fara_tinta_adauga_cod_liber`, `test_add_nedeclarat_marcheaza_exclus`,
`test_remove_cod_readuce_operatia_nemapata`, `test_dedup_operatie_cod`
- **Acceptance criteria**:
- [x] Starea chips serializata intr-UN SINGUR hidden `chips_state` = JSON versionat
(`{"v":1,"items":[{"cod":"","op":"","den":"","exclus":0},...]}`) — decizie gate D4:
elimina structural clasa de bug-uri de aliniere pe index (2 regresii in 2 release-uri).
`_chips_state_from_form` parseaza JSON-ul; JSON invalid → eroare vizibila, nu stare goala.
- [x] Compat tranzitorie la deploy: daca `chips_state` lipseste dar exista listele vechi
(`cod_prestatie`/`chip_op_service`/`chip_denumire`), parserul cade inapoi pe ele cu
`exclus=0` (form-uri deschise peste deploy); fallback-ul se sterge in release-ul urmator.
- [x] `chips_state` prezent mereu (lista goala = `{"v":1,"items":[]}`): golirea intentionata a
chips se persista la salvare (inlocuieste santinela `chips_present`).
- [x] `chips_action=add` + `chips_target_index=i` leaga codul de operatia i; fara target →
chip liber; cod `__NEDECLARAT__` + target → item cu `exclus=1` si fara `cod_prestatie`.
- [x] `chips_action=remove` (index unic) acopera si fostul `remove_flat`; actiunile
`add_flat`/`add_extra`/`chips_add_cod_{i}`/`chips_add_cod_flat` sunt eliminate.
- [x] Tinta implicita dupa un add reusit = urmatoarea operatie nemapata (sau nici una).
- [x] Validare tinta SEMANTICA, nu doar in-range: `chips_target_index` trebuie sa arate spre o
operatie nemapata si ne-exclusa; altfel `chips_error` (acopera POST fabricat si index
stale dupa un remove).
- [x] `chip_exclus` parsat strict ("1"→1, orice altceva→0); `__NEDECLARAT__` fara tinta valida →
`chips_error` explicit.
- [x] Testele existente pe actiunile vechi (`tests/test_web_corectie_prestatii.py`,
`tests/test_web_form_editare_slim.py`) sunt ACTUALIZATE in acelasi val (suita nu ramane
rosie intre valuri).
- [x] Emfaza chip nou pe INDEX (`chips_added_index`), nu pe valoarea codului (doua operatii pot
avea legitim acelasi cod).
- [x] `python3 -m pytest -q` trece.
- **Verificare E2E**: acoperit de US-005.
### US-002: Template unificat — un container, un select, fara butoane Adauga/+
**Ca** operator **vreau** un singur rand de chips si un singur select care adauga instant
**pentru ca** azi nu stiu care din cele 3 controale trebuie folosit.
- **Depinde de**: US-001
- **Fisiere**: `app/web/templates/_chips_prestatii.html` (rescriere), `app/web/templates/base.html`
(CSS `.chips`/`.chip*` :815-843, eliminare `.op-row`), `tests/test_web_chips.py` (~3 fisiere)
- **Test intai (RED)**: `tests/test_web_chips.py``test_render_un_singur_container_chips`,
`test_render_fara_op_row_si_fara_butoane_adauga`, `test_chip_warning_pentru_operatie_nemapata`
- **Acceptance criteria**:
- [x] Un singur container `.chips` contine: chips cod (cu tooltip operatia sursa), chips warning
operatii nemapate, chip `Nedeclarat` pentru itemi exclusi, selectul unic la capat.
- [x] Selectul (nume camp: `chips_pick`, id stabil `chips-picker`) are `hx-trigger="change"`,
`hx-post="/form-chips"`, `hx-target="#chips-section"`, `hx-swap="outerHTML"`,
`hx-include="closest form"` (transporta CSRF + starea hidden — omiterea = 403) si
`hx-disabled-elt="this"` (capcana htmx din 5.22 — NU mosteni de la form).
- [x] Selectul revine la placeholder dupa ORICE raspuns (succes SI eroare); valoarea ramasa in
`chips_pick` NU e aplicata la Salveaza pe niciuna din cai (regresia c6e29aa nu reapare
sub alt nume — codul se adauga DOAR prin actiunea add).
- [x] Pe durata unui request /form-chips, INTREAGA sectiune e inerta (`pointer-events:none` pe
`.htmx-request` sau echivalent) — click pe × / chip warning mid-flight nu pierde actiuni.
- [x] Click pe chip warning comuta tinta CLIENT-SIDE (clasa + hidden `chips_target_index`),
fara round-trip; click pe tinta activa = no-op.
- [x] Focus restaurat pe `#chips-picker` dupa swap.
- [x] Zero aparitii `op-row`, `Adauga`, `add_flat`, `chips_add_cod_` in template.
- [x] `chips_submission_id` e emis ca hidden in sectiune si ecouat de `/form-chips`, astfel
incat "salveaza ca regula" ramane disponibil si DUPA re-randari (azi dispare la prima
interactiune — bug perpetuat altfel).
- **Verificare E2E**: acoperit de US-005.
### US-003: Sugestii fuzzy/k-NN + optiunea "Nu se declara la RAR" in selectul unic
**Ca** operator **vreau** sugestiile de mapare direct in selectul din modal **pentru ca** azi ele
exista doar in panoul de mapare, iar in modal caut manual prin tot nomenclatorul.
- **Depinde de**: US-001, US-002
- **Fisiere**: `app/web/routes.py` (`post_form_chips` — construire sugestii pentru operatia tinta,
refolosind sursele din preview), `app/web/templates/_chips_prestatii.html`, `tests/test_web_chips.py` (~3 fisiere)
- **Test intai (RED)**: `tests/test_web_chips.py``test_select_are_optgroup_sugestii_pt_tinta`,
`test_select_are_nedeclarat_doar_cu_tinta`, `test_select_fara_tinta_fara_sugestii`
- **Acceptance criteria**:
- [x] Cu tinta activa: `optgroup "Sugestii"` cu sugestia principala + top-3 fuzzy/k-NN pentru
`denumire`-a operatiei; sub el optiunea `__NEDECLARAT__`; apoi nomenclatorul complet.
- [x] Fara tinta: selectul listeaza doar nomenclatorul (fara Sugestii, fara `__NEDECLARAT__`).
- [x] Sugestiile folosesc aceleasi functii ca `_preview_import.html` (fara drum nou de cod).
- [x] Degradare gratioasa: motor de sugestii rece/dezactivat → select DOAR cu nomenclatorul
(fara optgroup), fara eroare si fara blocare pe warmup (lock non-blocking, PRD 5.21).
- [x] `python3 -m pytest -q` trece.
- **Verificare E2E**: acoperit de US-005.
### US-004: Persistarea `exclus` prin salvare (import + Trimiteri) — cu round-trip complet
**Ca** operator **vreau** ca excluderea aleasa in modal sa se pastreze la salvare, sa produca
starea corecta si sa fie REVERSIBILA la redeschidere **pentru ca** altfel optiunea din select ar fi
cosmetica sau, mai rau, operatia ar fi declarata contra intentiei mele (RAR e ireversibil).
> Corectie din review (eng, CONFIRMATA in cod): pipeline-ul actual sterge `exclus` per-item
> (`resolve_prestatii` face `it.pop("exclus", None)`, `mapping.py:298`) si elimina itemii exclusi
> din `payload_json` la corectie (`routes.py:1744`). Fara fix-urile de mai jos, un item exclus din
> modal ar reveni `needs_mapping` sau — daca operatia are mapare pe cont — ar fi DECLARAT la RAR.
- **Depinde de**: US-001
- **Fisiere**: `app/mapping.py` (`resolve_prestatii` — treapta noua: item-exclus explicit
supravietuieste), `app/web/routes.py` (`web_editeaza_rand`, `post_corectie_trimitere` :1619,
`_prestatii_chips_from_payload` :1336 + toti constructorii de context chips),
`app/api/v1/import_router.py` (`apply_row_override` :319-366), `tests/test_web_editare_rand.py` (~4 fisiere)
- **Test intai (RED)**: `tests/test_web_editare_rand.py``test_salvare_pastreaza_exclus_in_override`,
`test_exclus_supravietuieste_resolve_chiar_cu_mapare_pe_cont`,
`test_redeschidere_modal_arata_chip_nedeclarat_si_x_readuce_warning`,
`test_rand_toate_operatiile_excluse_devine_excluded`, `test_salvare_trimitere_cu_exclus`
- **Acceptance criteria**:
- [x] `resolve_prestatii` pastreaza `exclus=1` venit explicit pe item (precedenta: item-exclus >
cod explicit > regula exclus cont > mapare > reguli text); restul treptelor neschimbate.
- [x] Itemii exclusi RAMAN in `payload_json`/`override_json` adnotati cu `exclus=1`;
`split_prestatii_excluse` ii scoate DOAR din payload-ul trimis la RAR si din cheia idempotenta.
- [x] Toti constructorii de chips (`_prestatii_chips_from_payload`, preview, re-render eroare)
propaga `exclus` → chip `Nedeclarat` reapare la redeschidere; `×` il readuce la warning.
- [x] Rand cu toate operatiile excluse → stare preview `excluded` ("Nedeclarat"); la Trimiteri →
`needs_data` cu motiv explicit (invariantele existente).
- [x] Validarea cod-vs-nomenclator la salvare ramane; sentinelul `__NEDECLARAT__` e RESPINS
explicit daca apare ca `cod_prestatie` in starea hidden (nu doar in select), pe ambele cai.
- [x] `python3 -m pytest -q` trece.
- **Verificare E2E**: acoperit de US-005.
### US-005: Verificare E2E browser pe ambele fluxuri
**Ca** lead **vreau** dovada in browser ca editarea e acum un singur control coerent
**pentru ca** bug-ul raportat e de UX vizual, nu doar de logica.
- **Depinde de**: US-002, US-003, US-004
- **Fisiere**: — (doar verificare; eventuale fix-uri in fisierele de mai sus)
- **Test intai (RED)**: —
- **Acceptance criteria**:
- [x] Import preview: rand cu operatie nemapata + cod liber → modalul arata UN rand de chips
(warning + cod + select), fara chenare/zone separate; screenshot atasat la raport.
- [x] Selectarea unui cod din Sugestii transforma chip-ul warning in chip cod, instant, fara "+".
- [x] Alegerea "Nu se declara la RAR" produce chip `Nedeclarat`; salvare → rand "Nedeclarat".
- [x] Trimiteri (`/trimitere/{id}` corectie): acelasi control, "salveaza ca regula" functional.
- [x] Salvarea persista chips-urile (regresie c6e29aa) si nu reapar controale duble la re-deschidere.
- **Verificare E2E**: browser HTMX pe http://localhost:8010/ (import xlsx test + o trimitere
`needs_mapping`), conform pasilor de mai sus.
## 5. Riscuri
- **`hx-disabled-elt` mostenit de la form** a mai rupt butoanele chips o data (5.22) — selectul unic
primeste explicit `hx-disabled-elt="this"`; test de template pentru atribut.
- **`change` pe select nativ** se declanseaza si la navigarea cu tastatura pe unele browsere vechi;
acceptat (comportamentul modern e la confirmare), iar chip-ul gresit se sterge cu un click.
- **Compatibilitate stare veche**: submission-uri/override-uri existente fara `chip_exclus` trebuie
citite ca `exclus=0` (default la deserializare) — altfel salvarea de pe randuri vechi crapa.
- **Doua endpoint-uri de save** (import + Trimiteri) cu acelasi contract de campuri — orice
schimbare de nume de camp se face simultan in ambele, plus `_chips_state_from_form` unic partajat.
## 6. Intrebari deschise
> Rezolvate cu utilizatorul la aprobare (2026-07-07):
- `×` pe un chip `Nedeclarat`**readuce chip-ul warning** al operatiei (operatia NU se sterge).
- Selectul unic ramane `<select>` 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) sugestiechip 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 previewcommit 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