Files
rar-autopass/docs/prd/prd-5.8-ux-tabel-trimiteri-reguli-text.md
Claude Agent 51dc504f1d feat(5.8): reguli mapare pe text (substring/cont) + UX tabel trimiteri (detaliu inline, fara scroll, cod RAR)
Reguli text per cont (operation_text_rules), resolve_prestatii cu param aditiv
text_rules + precedenta stricta, threadat pe toate cele 6 callsite-uri + valid_codes
+ seam classify_prezentare. UI Mapari: sectiune reguli + preview pre-salvare + overlap
+ telemetrie text_rule_hit. UX tabel: cod_rar sub operatie, pill eticheta scurta, fara
scroll orizontal (scopat .tabel-trimiteri + carduri <768px), detaliu inline expandabil
(a11y + pauza poll). code-review: reparat regula auto_send=0 care trimitea automat la RAR
in loc sa tina randul pentru review. 814 passed.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-24 12:47:37 +00:00

545 lines
37 KiB
Markdown

# PRD 5.8 — UX tabel trimiteri (detaliu inline, fara scroll, cod RAR) + reguli mapare pe text
**Stare**: verify-pass
> Proces: `docs/ROADMAP.md` §5. Contract RAR (sursa de adevar): `docs/api-rar-contract.md`.
> Starea trece: `draft → aprobat → in-executie → verify-pass → inchis`.
## 1. Obiectiv
Patru imbunatatiri UX, fara schimbarea fluxului de trimitere:
1. Panoul de detaliu al unei trimiteri apare **sub randul selectat** (rand expandabil), nu la
baza tabelului (azi `#trimitere-detaliu` traieste dupa `<table>` in `_coada.html:72`).
2. Tabelul de trimiteri **nu mai are scroll orizontal** (confirmat live: `.tablewrap`
`scrollWidth=1189 > clientWidth=1010` la 1280px). Solutie agreata: mut **Motiv** in detaliu,
stiveasc **cod RAR sub codul operatiei** in coloana Operatie, **VIN sub nr. inmatriculare**
(deja stivuit) si **scurtez etichetele de Stare**.
3. In coloana Operatie apare si **codul de operatie RAR rezolvat** (`cod_prestatie`), nu doar
codul/denumirea venita din API/import.
4. Pagina **Mapari** capata **reguli automate pe text** (substring): ex. operatie care *contine*
„verificare" → cod RAR `OE-2`. Se aplica la ingestie si la re-rezolvarea blocajelor.
## 2. Non-Goals (anti scope-creep)
- Fara `match_type` multiplu (starts_with / regex). Doar **contains** (substring), insensibil la
diacritice si majuscule. Daca apare nevoia, e alt PRD.
- Fara modificari la masina de stari, la worker, la contractul RAR sau la idempotenta.
- Fara schimbarea logicii de mapare exacta `cod_op_service → cod_prestatie` (ramane, are precedenta).
- Fara redesign general al dashboard-ului; doar tabelul de trimiteri + sectiunea noua din Mapari.
- Fara reguli text per-import sau globale: regulile sunt **per cont** (ca `operations_mapping`).
## 3. Stories atomice
> Backend + UI pentru acelasi comportament = stories separate. Regulile pe text (US-001..004) si
> UX-ul tabelului (US-005..008) sunt **independente** — se pot livra in paralel.
---
### US-001: Schema + persistenta reguli text de mapare
**Ca** dezvoltator **vreau** o tabela `operation_text_rules` **pentru ca** regulile pe substring
sa fie durabile per cont, langa `operations_mapping`.
- **Depinde de**: —
- **Fisiere**: `app/schema.sql`, `app/mapping.py`, `tests/test_mapping_text_rules.py`
- **Test intai (RED)**: `tests/test_mapping_text_rules.py`
`test_save_text_rule_persista`, `test_load_text_rules_per_cont`,
`test_delete_text_rule`, `test_unic_per_cont_pattern`
- **Acceptance criteria**:
- [x] Tabela noua: `id, account_id (NOT NULL, FK accounts ON DELETE CASCADE), pattern TEXT NOT NULL,
cod_prestatie TEXT NOT NULL, auto_send INTEGER NOT NULL DEFAULT 0, priority INTEGER NOT NULL
DEFAULT 0, created_at`, cu `UNIQUE(account_id, pattern)`.
- [x] **`auto_send` DEFAULT 0** (decizie CEO 2026-06-24, siguranta): o regula pe substring are blast
radius mai mare decat o mapare exacta (potriveste si operatii viitoare nevazute), iar
`FINALIZATA` e ireversibil la RAR. O regula noua rezolva codul dar TINE randul pentru verificare
umana (`needs_mapping`) pana cand operatorul activeaza explicit „In coada". UI-ul (US-004) reflecta
toggle-ul pe 0 implicit.
- [x] `load_text_rules(conn, account_id)` intoarce lista ordonata `[{pattern, cod_prestatie,
auto_send, priority}]` (ordine: `priority ASC, id ASC`), aplicand `account_or_default`.
- [x] `save_text_rule(conn, account_id, pattern, cod_prestatie, auto_send)` face upsert pe
`(account_id, pattern)`; `delete_text_rule(...)` sterge.
- [x] Schema e idempotenta (`CREATE TABLE IF NOT EXISTS`) si nu strica DB-uri existente.
- [x] `python3 -m pytest tests/test_mapping_text_rules.py -q` trece.
- **Verificare E2E**: —
### US-002: `resolve_prestatii` aplica reguli text (substring, dupa maparea exacta)
**Ca** integrator **vreau** ca o operatie nemapata sa primeasca cod RAR din prima regula text care
da match **pentru ca** sa nu mai intre in editor degeaba.
- **Depinde de**: US-001
- **Fisiere**: `app/mapping.py`, `tests/test_mapping.py`
- **Test intai (RED)**: `tests/test_mapping.py` —
`test_regula_text_contains_rezolva`, `test_mapare_exacta_bate_regula_text`,
`test_regula_text_insensibila_diacritice_caz`, `test_regula_text_cod_invalid_in_nomenclator_ramane_nemapat`,
`test_prima_regula_dupa_priority_castiga`
- **Acceptance criteria**:
- [x] `resolve_prestatii(prestatii, mapping, valid_codes=None, text_rules=None)` — semnatura
**aditiva** (param nou optional, default `None` = comportament actual neschimbat).
- [x] **Precedenta** stricta: `cod_prestatie` direct valid > mapare exacta `cod_op_service` >
**reguli text** > nemapat. Regula text se incearca DOAR cand nu exista cod valid si op nu e
in `mapping`.
- [x] Match = substring pe textul operatiei (`denumire` daca exista, altfel `cod_op_service`),
normalizat cu `normalize_for_match` (fara diacritice, uppercase, spatii colapsate) — pe
ambele parti (pattern si text).
- [x] Daca regula da match dar `cod_prestatie`-ul ei **nu** e in `valid_codes` → operatia ramane
**nemapata** (nu trimitem cod invalid; coerent cu regula „RAR accepta doar coduri din
nomenclator", `mapping.py:101-105`).
- [x] La match multiplu castiga prima dupa ordinea `load_text_rules` (priority, id).
- [x] `python3 -m pytest tests/test_mapping.py -q` trece (inclusiv testele vechi).
- **Verificare E2E**: —
### US-003: Reguli text active la ingestie + la re-rezolvare
**Ca** service **vreau** ca regulile text sa actioneze pe prezentari noi (API + import) **si** sa
deblocheze randurile `needs_mapping` existente cand salvez o regula noua.
- **Depinde de**: US-002
- **Fisiere**: `app/mapping.py` (`classify_prezentare` + `reresolve_account`), `app/api/v1/router.py`,
`app/api/v1/import_router.py`, `app/web/routes.py`, `tests/test_reresolve_text_rules.py`
- **Test intai (RED)**: `tests/test_reresolve_text_rules.py` —
`test_ingestie_api_aplica_regula_text`, `test_ingestie_import_aplica_regula_text`,
`test_corectie_web_aplica_regula_text`, `test_salvare_regula_rerezolva_blocate`
- **Acceptance criteria**:
- [x] **TOATE cele 6 apeluri `resolve_prestatii` primesc `text_rules` SI `valid_codes`** (decizie
eng D-eng-1): `mapping.py:276` (in `classify_prezentare`), `mapping.py:441` (reresolve_account),
`import_router.py:204`, `import_router.py:1079`, `routes.py:984` (corectie web `needs_data`),
`routes.py:2278` (import web). Cele 4 care azi paseaza `valid_codes=None` incep sa paseze
`valid_codes=load_nomenclator_codes(conn) or None` — altfel AC-ul de validare (US-002) nu se
onoreaza pe acele cai.
- [x] **`classify_prezentare` capata param `text_rules`** (A3): seam-ul partajat API + `/valideaza`
(invariant 5.2) il primeste si ambii apelanti (`create_prezentari` + ruta `/valideaza`) fac
`load_text_rules(...)` si il paseaza — altfel dry-run-ul diverge de trimiterea reala.
- [x] **`text_rules`/`valid_codes` se incarca o data per cerere/batch, NU per rand** (T2):
`routes.py:2278` (loop import) si `reresolve_account` (loop randuri) le incarca inainte de bucla.
- [x] O prezentare API/import a carei operatie da match pe o regula intra `queued` (sau respectand
`auto_send`), NU `needs_mapping`.
- [x] La salvarea unei reguli noi, `reresolve_account` re-evalueaza blocajele si deblocheaza
randurile care acum dau match (acelasi mecanism ca la `save_mapping`).
- [x] `cod_prestatie`-ul rezolvat din regula respecta validarea fata de nomenclator (US-002).
- [x] `python3 -m pytest -q` trece integral.
- **Verificare E2E**: `POST /v1/prezentari` cu operatie „Verificare X" (fara mapare exacta), regula
`contine "verificare" → OE-2` salvata in prealabil → raspuns cu `status=queued`, fara `nemapate`.
### US-004: UI Mapari — sectiune „Reguli automate (text)"
**Ca** operator **vreau** sa adaug/sterg reguli text din pagina Mapari **pentru ca** sa automatizez
maparea fara cod intern per operatie.
- **Depinde de**: US-001 (UI poate merge in paralel cu US-002/003; rezolvarea efectiva cere US-003)
- **Fisiere**: `app/web/routes.py`, `app/web/templates/_mapari.html`,
`tests/test_web_mapari_text_rules.py`
- **Test intai (RED)**: `tests/test_web_mapari_text_rules.py` —
`test_post_regula_text_salveaza_si_rerezolva`, `test_post_sterge_regula`,
`test_regula_text_scoped_pe_cont_sesiune`, `test_csrf_necesar`
- **Acceptance criteria**:
- [x] Sectiune noua in `_mapari.html` (a 4-a, langa „De rezolvat" / „Mapari salvate" / „Formate"):
tabel cu coloanele **Daca operatia contine** (text) | **Cod RAR** (select din nomenclator) |
**In coada** (toggle `auto_send`, **implicit OFF** — vezi US-001 decizia CEO) | **Actiuni**
(sterge), plus un rand de adaugare.
- [x] Formularul foloseste `auto_send`-toggle si `select` din nomenclator existente (reuse macro),
cu `csrf_token`; rutele `POST /mapari/reguli-text` si `POST /mapari/reguli-text/sterge`
sunt **account-scoped pe sesiune** (`require_login`), ca rutele Mapari actuale.
- [x] La salvare se cheama `save_text_rule` + `reresolve_account`, cu mesaj „Regula salvata.
Deblocate: N" si trigger `trimiteriChanged` (refresh lista), exact ca maparea inline (5.7).
- [x] Textul afisat e clar ca match-ul e „contine" (substring), nu egalitate.
- [x] **Empty state** (nicio regula): o linie explicativa cu exemplu — „Inca nu ai reguli.
Ex: operatia contine «verificare» → OE-2. Mapeaza automat operatii similare fara cod
intern." — urmata de randul de adaugare gata afisat ca placeholder. (Pass 2)
- [x] **Stari**: succes = mesaj „Regula salvata. Deblocate: N"; eroare cod invalid in
nomenclator = mesaj inline „Cod RAR necunoscut in nomenclator", fara salvare. (Pass 2)
- [x] `python3 -m pytest tests/test_web_mapari_text_rules.py -q` trece.
- **Verificare E2E**: gstack browser pe `/?tab=mapari` — adaug regula `verificare → OE-2`,
confirm ca apare in lista si ca un rand `De rezolvat` cu „verificare" dispare dupa salvare.
---
### US-005: `cod_rar` distinct in datele de afisare ale randului
**Ca** dezvoltator **vreau** un camp separat pentru codul RAR rezolvat **pentru ca** sa-l pot afisa
sub codul operatiei fara sa-l confund cu `cod_op_service`.
- **Depinde de**: —
- **Fisiere**: `app/payload_view.py`, `tests/test_payload_view.py`
- **Test intai (RED)**: `tests/test_payload_view.py` —
`test_cod_rar_prezent_cand_mapat`, `test_cod_rar_gol_cand_nemapat`,
`test_operatie_ramane_denumire_sau_op`
- **Acceptance criteria**:
- [x] `prezentare_din_payload` intoarce in plus `cod_rar` = `cod_prestatie` (uppercase, strip
„.0") sau `EMPTY` cand lipseste. `operatie` si `cod` raman neschimbate (compat).
- [x] Cand operatia e nemapata (`cod_prestatie` None), `cod_rar` == `EMPTY` (nu cade pe
`cod_op_service`).
- [x] `python3 -m pytest tests/test_payload_view.py -q` trece.
- **Verificare E2E**: —
### US-006: Etichete de Stare compacte (pill)
**Ca** operator **vreau** etichete scurte in coloana Stare **pentru ca** randul sa fie ingust si
lizibil.
- **Depinde de**: —
- **Fisiere**: `app/web/labels.py`, `tests/test_labels.py`
- **Test intai (RED)**: `tests/test_labels.py` —
`test_eticheta_scurta_pentru_fiecare_stare`, `test_eticheta_lunga_ramane_pentru_subtext`
- **Acceptance criteria**:
- [x] Eticheta scurta expusa ca **functie noua separata `eticheta_scurta(status) -> str`** (dict
propriu + garda `KeyError`), NU ca al 4-lea element in tuple-ul `Eticheta` (3 elemente azi,
despachetat in template-uri — l-ar rupe). `eticheta_stare` ramane neatins (text lung pentru
subtext/tooltip in detaliu). Set propus:
`queued→"In coada"`, `sending→"Se trimite"`, `sent→"Finalizat"`,
`needs_mapping→"De mapat"`, `needs_data→"Date lipsa"`, `error→"Eroare"`.
- [x] Pill-ul din tabel foloseste eticheta scurta; clasele CSS `s-*` (culorile) raman.
- [x] Stare neacoperita ridica `KeyError` (ca azi, ca sa prinda stari noi).
- [x] `python3 -m pytest tests/test_labels.py -q` trece.
- **Verificare E2E**: —
### US-007: Tabel trimiteri fara scroll orizontal (coloane re-aranjate)
**Ca** operator **vreau** sa vad tot randul fara scroll lateral **pentru ca** sa citesc starea si
operatia dintr-o privire.
- **Depinde de**: US-005, US-006
- **Fisiere**: `app/web/templates/_submissions.html`, `app/web/routes.py` (builder rand),
`app/web/templates/base.html` (CSS), `tests/test_web_submissions.py`
- **Test intai (RED)**: `tests/test_web_submissions.py` —
`test_tabel_nu_are_coloana_motiv`, `test_operatie_contine_cod_rar`,
`test_pill_eticheta_scurta`
- **Acceptance criteria**:
- [x] Coloana **Motiv** eliminata din `<thead>`/`<tbody>` (continutul ei se vede in detaliu —
`_trimitere_detaliu.html:36-39` deja afiseaza Motiv).
- [x] Coloana **Operatie**: linia 1 = `prez.operatie` (denumire/cod API), linia 2 = `prez.cod_rar`
muted (`cod RAR: XXX`); cand nemapat afiseaza „nemapat" muted.
- [x] Coloana **Vehicul**: nr. inmatriculare + VIN scurt stivuit (deja exista, pastrat).
- [x] Pill Stare = eticheta scurta (US-006).
- [x] CSS: la 1280px `.tablewrap` **nu** mai depaseste (`scrollWidth <= clientWidth`); se permite
`white-space: normal` controlat pe coloanele text si latimi rezonabile, fara a rupe alte
tabele care folosesc `.tablewrap` (scopare prin clasa, ex. `.tabel-trimiteri`).
- [x] **Pill compact pastreaza info lunga**: eticheta scurta (US-006) e in pill; textul lung
(subtext din `eticheta_stare`) ramane disponibil ca `title=` (tooltip) pe pill si in
panoul de detaliu — nu se pierde informatie. (Pass 5)
- [x] **Responsive** (Pass 6): `>=1024px` toate cele 8 coloane; `768-1024px` ascund coloana
**Actualizat** (e in detaliu) → 7 coloane fara scroll; `<768px` randurile devin **carduri**
(eticheta:valoare stivuit, pill + chevron sus), nu tabel — fara scroll orizontal.
- [x] Secundarul „cod RAR" / „nemapat" e >=12px cu contrast >=4.5:1 (coerent cu `vin_scurt`
existent); nu se transmite stare DOAR prin culoare (textul „nemapat" o spune). (Pass 6)
- [x] `python3 -m pytest tests/test_web_submissions.py -q` trece.
- **Verificare E2E**: gstack browser pe `/` la 1280px, 1024px, 900px si 375px — `scrollWidth <=
clientWidth` la fiecare (sau carduri sub 768px); coloana Operatie arata ambele coduri.
### US-008: Detaliu trimitere ca rand expandabil sub randul selectat
**Ca** operator **vreau** ca detaliul sa apara imediat sub randul pe care l-am dat click **pentru
ca** sa nu mai caut la baza tabelului.
- **Depinde de**: US-007
- **Fisiere**: `app/web/templates/_submissions.html`, `app/web/templates/_coada.html`,
`app/web/templates/_trimitere_detaliu.html`, `app/web/routes.py`,
`tests/test_web_detaliu_inline.py`
- **Test intai (RED)**: `tests/test_web_detaliu_inline.py` —
`test_fragment_detaliu_se_randeaza_in_container_pe_rand`,
`test_un_singur_detaliu_deschis`
- **Acceptance criteria**:
- [x] Fiecare rand are un rand-sibling de detaliu `<tr class="detaliu-rand">` cu
`<td colspan=N><div id="detaliu-{{r.id}}"></div></td>`, ascuns implicit.
- [x] Click pe rand face `hx-get="/_fragments/trimitere/{id}"` cu `hx-target="#detaliu-{id}"`,
`hx-swap="innerHTML"`; la deschidere se inchid celelalte detalii deschise (un singur rand
expandat o data).
- [x] **Indicator vizual** (Pass 1/4): chevron ``/`` la inceputul randului (rotit cand e
deschis) + randul deschis primeste **fundal evidentiat** (`#1d212b` dark / echivalent light).
Detaliul se leaga prin **fundal subtil + `border-top`** (stilul existent al sectiunii de
mapare inline `_trimitere_detaliu.html`), NU `border-left` accent (evita pattern AI-slop).
- [x] **Stare loading** (Pass 2): la click, pana raspunde HTMX, containerul randului arata un
placeholder discret („Se incarca…") prin `hx-indicator`, ca operatorul sa vada ca s-a
inregistrat clicul.
- [x] **Accesibilitate** (Pass 6): randul clickabil e focusabil la tastatura (`tabindex="0"`,
`role="button"`, `aria-expanded` sincronizat); Enter/Space deschid/inchid; tinta de atins
(chevron/checkbox) >=44px pe touch.
- [x] Butonul „Inchide" din `_trimitere_detaliu.html` goleste containerul randului curent (nu mai
tinteste `#trimitere-detaliu` global) si readuce focusul pe randul declansator.
- [x] `#trimitere-detaliu` global din `_coada.html:72` este eliminat sau golit de rol.
- [x] **Poll-ul de refresh (15s, `_coada.html`) se pune pe pauza cat timp un rand e expandat** si
se reia la inchidere (decizie eng D-eng-2). Fara flicker, fara pierdere de scroll/focus in
timpul citirii; lista nu se misca sub operator. (NU re-fetch dupa swap, NU excludere de rand.)
- [x] `python3 -m pytest tests/test_web_detaliu_inline.py -q` trece.
- **Verificare E2E**: gstack browser pe `/` — click pe randul #N: detaliul apare imediat sub el
(`detaliuTop` intre `rowTop` si randul urmator), nu la baza; click pe alt rand muta detaliul.
---
> **Stories de expansiune (CEO review 2026-06-24, SELECTIVE EXPANSION).** US-009..011 acceptate in scope.
> Cresc increderea si observabilitatea regulilor text; nu schimba fluxul de trimitere. Livrabile in Val 4.
### US-009: Preview pre-salvare regula text (cate operatii potriveste)
**Ca** operator **vreau** sa vad cate operatii potriveste o regula INAINTE sa o salvez **pentru ca**
sa nu salvez un pattern prea lacom (perechea naturala a `auto_send=0`).
- **Depinde de**: US-001 (reuse agregarea `pending_unmapped`); independent de US-002/003
- **Fisiere**: `app/web/routes.py`, `app/web/templates/_mapari.html`, `tests/test_web_mapari_preview_regula.py`
- **Test intai (RED)**: `tests/test_web_mapari_preview_regula.py` —
`test_preview_numara_potriviri`, `test_preview_intoarce_exemple`, `test_preview_pattern_gol`,
`test_preview_scoped_pe_cont`
- **Acceptance criteria**:
- [x] Ruta `POST /mapari/reguli-text/preview` (account-scoped pe sesiune, CSRF) primeste `pattern`,
normalizeaza cu `normalize_for_match`, numara operatiile distincte nemapate ale contului
(`needs_mapping`, reuse `pending_unmapped`) al caror text **contine** pattern-ul + intoarce
pana la 3 exemple `{cod_op_service, denumire}`. **NU salveaza nimic**.
- [x] Fragment HTMX afisat sub randul de adaugare la schimbarea pattern-ului (`hx-trigger="keyup
delay:400ms"`): „Potriveste N operatii nemapate: «...», «...»" sau „Nicio potrivire acum".
- [x] Pattern gol → fara apel/fragment gol (nu numara „tot").
- [x] `python3 -m pytest tests/test_web_mapari_preview_regula.py -q` trece.
- **Verificare E2E**: gstack browser pe `/?tab=mapari` — tastez „verificare" in pattern → vad „Potriveste
N operatii: ..." inainte de Salveaza.
### US-010: Telemetrie hit regula text in `app_events`
**Ca** operator/admin **vreau** sa stiu ce regula a rezolvat ce submission **pentru ca** sa pot
raspunde la „de ce a primit randul asta codul OE-2?".
- **Depinde de**: US-002 (rezolvare) + US-003 (active la ingestie)
- **Fisiere**: `app/mapping.py` (adnotare item rezolvat-prin-regula), `app/observ.py` (reuse `log_event`),
apelantii cu `conn` (`create_prezentari`, `reresolve_account`, import), `tests/test_text_rule_telemetry.py`
- **Test intai (RED)**: `tests/test_text_rule_telemetry.py` —
`test_hit_regula_emite_app_event`, `test_mapare_exacta_nu_emite_text_rule_hit`,
`test_event_redactat_si_scoped_pe_cont`
- **Acceptance criteria**:
- [x] `resolve_prestatii` adnoteaza itemul rezolvat-prin-regula cu pattern-ul sursa (camp aditiv pe dict,
ex. `cod_sursa="text_rule:<pattern>"`; payload-harmless — RAR citeste doar `cod_prestatie`).
- [x] Apelantii cu `conn` emit `log_event("text_rule_hit", {...})` in `app_events` cu
`{submission_id, account_id, pattern, cod_prestatie}`, redactat prin `app/security` (fara PII).
Maparea exacta NU emite `text_rule_hit`.
- [x] Vizibil in tab-ul „Jurnal" (5.6), scoped pe cont pentru non-admin (mecanismul existent).
- [x] `python3 -m pytest tests/test_text_rule_telemetry.py -q` trece.
- **Verificare E2E**: `POST /v1/prezentari` cu operatie ce da match pe regula → eveniment `text_rule_hit`
vizibil in Jurnal cu pattern-ul si codul.
### US-011: Avertizare overlap intre reguli text (neblocant)
**Ca** operator **vreau** un avertisment cand o regula noua se suprapune cu una existenta **pentru ca**
sa inteleg de ce o regula „castiga" inaintea alteia.
- **Depinde de**: US-001 (load) + US-004 (UI salvare)
- **Fisiere**: `app/mapping.py` (helper pur `text_rules_overlap`), `app/web/routes.py`,
`app/web/templates/_mapari.html`, `tests/test_mapping_overlap.py`, `tests/test_web_mapari_overlap.py`
- **Test intai (RED)**: `tests/test_mapping_overlap.py` — `test_overlap_substring_ambele_directii`,
`test_fara_overlap`, `test_overlap_normalizat_diacritice`; `tests/test_web_mapari_overlap.py` —
`test_salvare_cu_overlap_arata_avertisment_dar_salveaza`
- **Acceptance criteria**:
- [x] Helper pur `text_rules_overlap(pattern, existing_rules) -> list[dict]`: overlap = un pattern
normalizat e substring al celuilalt (oricare directie). Determinist, fara DB.
- [x] La salvare (ruta US-004), daca exista overlap, mesaj inline **neblocant**: „Se suprapune cu
regula «X» → COD; ordinea (priority, id) decide care se aplica prima." **Salvarea continua**
(avertisment, NU eroare).
- [x] `python3 -m pytest tests/test_mapping_overlap.py tests/test_web_mapari_overlap.py -q` trece.
- **Verificare E2E**: gstack browser — adaug „verificare", apoi „verificare faruri" → avertisment overlap,
ambele reguli salvate.
## 4. Riscuri
- **Poll de 15s vs. detaliu deschis (US-008)**: `_coada.html` reincarca periodic `#submissions-wrap`;
un re-render naiv ar inchide detaliul expandat. Mitigare: pastreaza id-ul randului deschis si
re-cere fragmentul dupa swap, sau exclude randul deschis din inlocuire. De decis la executie.
- **`white-space: normal` global (US-007)**: `.tablewrap`/`table` sunt partajate de mai multe
tabele (Mapari, Formate). Schimbarile CSS trebuie scopate la tabelul de trimiteri ca sa nu
strice celelalte. Verificare vizuala pe toate tab-urile.
- **Cod RAR invalid intr-o regula text (US-002)**: daca nomenclatorul nu e inca populat,
`valid_codes` poate fi gol → regula n-ar rezolva nimic. Coerent cu `load_nomenclator_codes`
(nu valida cand e gol); de confirmat ca seed-ul fallback (18 coduri) acopera cazul uzual.
- **Colizii de reguli text** (mai multe pattern-uri match): rezolvat determinist prin ordine
(priority, id); fara ordine, comportamentul ar fi nedeterminist.
## 5. Intrebari deschise
> Se rezolva cu utilizatorul INAINTE de executie (poarta de aprobare PRD).
- ~~Indiciu „rezolvat prin regula text" pe rand?~~ **REZOLVAT** (design review): suficient codul RAR;
re-evaluat post-livrare (vezi §8).
- ~~Pastram „Actualizat" in tabel la 1024px?~~ **REZOLVAT** (Pass 6): ascuns la `<=1024px`, ramane in
detaliu (vezi §7 Responsive).
- ~~Ordonarea regulilor: priority editabila?~~ **REZOLVAT**: v1 = ordine de creare, fara editare
priority (vezi §8).
## 6. Valuri de executie (graful de dependente)
```
Val 1: [US-001] [US-005] [US-006] ← fara dependente, fisiere distincte → paralel
Val 2: [US-002] [US-007] ← US-002 dep US-001; US-007 dep US-005+US-006
Val 3: [US-003] [US-004] [US-008] ← US-003 dep US-002; US-004 dep US-001; US-008 dep US-007
Val 4: [US-009] [US-010] [US-011] ← expansiuni CEO; US-009 dep US-001; US-010 dep US-002+US-003;
US-011 dep US-001+US-004
```
## 7. Decizii de design (din /plan-design-review, 2026-06-23)
### Arhitectura informatiei (tabel trimiteri)
Ierarhie pe rand, stanga→dreapta: **Stare** (pill colorat, ancora vizuala) → **Vehicul**
(nr. mare + VIN muted dedesubt) → **Operatie** (denumire/cod API + „cod RAR: X" muted). Coloana
Operatie devine purtatoarea celor doua coduri; restul (Data, Nr. RAR, Actualizat) sunt context
secundar. Motiv iese din tabel (zgomot pe randurile OK) si traieste in detaliu.
### Tabel stari interactiune (Pass 2)
```
FEATURE | LOADING | EMPTY | ERROR | SUCCESS | PARTIAL
------------------------|--------------------|------------------|------------------------|----------------------|--------------------
Celula Operatie | — | „nemapat" (red) | — | „cod RAR: X" muted | „nemapat" pana la mapare
Rand expandabil (detaliu)| placeholder „Se | n/a | fragment de eroare HTMX| detaliu sub rand, | n/a
| incarca…" hx-indic.| | in container | chevron rotit + fundal|
Sectiune reguli text | — | explicatie+exemplu| „Cod RAR necunoscut" | „Regula salvata. | —
| | + rand adaugare | (inline, fara salvare) | Deblocate: N" |
```
### Responsive (Pass 6)
- `>=1024px`: 8 coloane complete, fara scroll orizontal.
- `768-1024px`: ascunde **Actualizat** (e in detaliu) → 7 coloane, fara scroll.
- `<768px`: **card per rand** (eticheta:valoare stivuit, pill+chevron sus). Cardurile sunt
justificate aici pentru ca **cardul ESTE interactiunea** (tap → expand), nu decor.
### Accesibilitate (Pass 6)
Randul expandabil: `tabindex=0`, `role="button"`, `aria-expanded`, activare Enter/Space, focus
readus la inchidere, tinta touch >=44px. Secundarul muted >=12px, contrast >=4.5:1; starea nu se
comunica DOAR prin culoare.
### Aliniere stil (Pass 4/5) — clasificat APP UI
Tabel dens, limbaj utilitar, putine culori, fara mozaic de carduri decorative. Conectorul
detaliului = fundal subtil + `border-top` (vocabular existent), NU `border-left` accent
(pattern AI-slop). Pill-urile pastreaza clasele `s-*`; eticheta lunga ramane in `title=`/detaliu.
## 8. NU in scope (design — deferat explicit)
- `match_type` multiplu (starts_with/regex) la reguli text — substring acopera „contine". Alt PRD.
- Editare `priority` reguli in UI (drag/numar) — v1 e ordine de creare.
- Redesign vizual al celorlalte tab-uri/tabele — doar tabelul de trimiteri + sectiunea Mapari noua.
- Indicator „rezolvat prin regula text" pe rand — codul RAR e suficient (de re-evaluat post-livrare).
- Animatii de expand/collapse elaborate — un toggle simplu (rotire chevron) e destul pentru un APP UI.
## 9. Ce exista deja (de reutilizat, nu reinventat)
- Sistem de culori prin CSS variables + teme dark/light (`base.html`), clase pill `s-*`.
- Macro toggle `auto_send` + `select` nomenclator + `csrf_token` (`_mapari.html`,
`_trimitere_detaliu.html`) — refolosite la sectiunea reguli text.
- `reresolve_account` (re-rezolvare blocaje), `save_mapping` (pattern upsert+rerez), mesaj
„Deblocate: N" + trigger `trimiteriChanged` din maparea inline (5.7).
- `eticheta_stare` (labels.py) — extins cu eticheta scurta, fara a pierde textul lung.
- `prezentare_din_payload` (payload_view.py) — extins cu `cod_rar`.
## Approved Mockups
| Screen/Section | Mockup Path | Direction | Notes |
|----------------|-------------|-----------|-------|
| Tabel trimiteri (desktop + mobil) | ~/.gstack/projects/romfast-rar-autopass/designs/trimiteri-tabel-20260623/mockup-trimiteri.png | Tabel dens cu pill compact, „cod RAR" stivuit sub operatie, rand expandat inline cu chevron+fundal; card per rand sub 768px | Conectorul detaliului in implementare = fundal subtil + border-top (NU border-left accent ca in mockup); teme calibrate pe CSS vars existente |
---
## Raport VERIFY
> Completat de subagentul verificator (context curat) in faza VERIFY — vezi ROADMAP §5.6.
**VERDICT: PASS** (toate US-001..011), dupa 1 fix critic descoperit la `/code-review` (CLOSE).
### Suita
`python3 -m pytest -q` → **814 passed, 1 skipped** (skip = `tests/test_live_rar.py`, live RAR opt-in).
Toate testele numite in PRD exista si trec.
### PASS/FAIL per story (verificator independent, context curat)
- US-001..011: **PASS**, cu dovezi de cod (vezi mai jos). Subset 5.8 = 81+ teste verzi.
- US-001 PASS — `operation_text_rules` UNIQUE(account_id,pattern), `auto_send DEFAULT 0`, FK ON DELETE CASCADE, `CREATE TABLE IF NOT EXISTS`; load/save/delete corecte (ordine priority,id).
- US-002 PASS — semnatura aditiva `resolve_prestatii(...,text_rules=None)`; precedenta stricta cod valid > mapare exacta > regula text > nemapat; match substring normalizat ambele parti; cod regula invalid -> ramane nemapat; prima dupa ordine castiga.
- US-003 PASS — toate **6** callsite resolve_prestatii primesc text_rules + valid_codes; `classify_prezentare` param nou + ambii apelanti (create_prezentari + /valideaza) incarca; T2 incarcare o data per cerere/batch.
- US-004 PASS — rute `/mapari/reguli-text` (+sterge) scoped+CSRF; save+reresolve+„Deblocate: N"+trimiteriChanged; empty state; cod invalid respins inline; sectiune a 4-a cu „contine".
- US-005 PASS — `cod_rar` uppercase/strip „.0" sau EMPTY; nemapat -> EMPTY (fara fallback pe cod_op_service).
- US-006 PASS — `eticheta_scurta` functie separata + dict propriu + KeyError; tuple `Eticheta` ramane 3.
- US-007 PASS — Motiv eliminata; Operatie cu „cod RAR: X"/„nemapat"; pill scurt + title lung; CSS scopat `.tabel-trimiteri`; responsive (ascunde Actualizat ≤1024, carduri <768).
- US-008 PASS — `<tr class="detaliu-rand">` colspan=8; click hx-target=#detaliu-{id}; single-open + poll-pauza via `htmx:beforeRequest` preventDefault; Enter/Space; Inchide goleste+focus; #trimitere-detaliu global golit.
- US-009 PASS — `/mapari/reguli-text/preview` scoped+CSRF, numara distinct nemapate ce contin pattern, max 3 exemple (HTML-escaped), zero scriere; pattern gol -> fragment gol.
- US-010 PASS — `cod_sursa="text_rule:<pattern>"` doar pe hit; `_emite_text_rule_hits` din TOATE caile (API create, reresolve, import_router commit, + corectie web routes.py:1001 si import web commit routes.py:2482 — paritate adaugata post-VERIFY); maparea exacta nu emite.
- US-011 PASS — `text_rules_overlap` pur (substring ambele directii, exclude identic); mesaj neblocant, salvarea continua.
### E2E
Browser pixel-level **neprobat** (sandbox ucide serverul persistent; dashboard in spatele /login). Compensat cu **E2E functional prin HTTP (TestClient, login real)**: tab Mapari S4 + empty state; US-009 preview „Potriveste N..."; US-004 save → trimiteriChanged + „Deblocate:" + lista; US-011 overlap; cod invalid respins; fragment submissions fara Motiv + detaliu-rand colspan=8 + pill role=button + „cod RAR/nemapat". Live RAR `FINALIZATA` neprobat (lipsa creds) — backend trimitere NEATINS.
### Regresia de aur (Non-Goals)
CONFIRMAT: worker / idempotency / validation / reconcile / crypto / auth NEMODIFICATE (git). `schema.sql` = pur aditiv (tabela noua + index). Masina de stari si fluxul de trimitere neatinse.
### Fix critic la `/code-review high` (CLOSE) — 1 bug real reparat
- **Regula text cu `auto_send=0` (DEFAULT, decizia CEO) trimitea automat la RAR.** `has_no_auto_send`
inspecta DOAR `operations_mapping`, nu si regula text care a rezolvat itemul → un rand rezolvat
prin regula cu auto_send=0 trecea pe `queued` (trimis automat), incalcand AC-ul central de siguranta
US-001/US-003/US-004 (blast radius substring + `FINALIZATA` ireversibil). Repro confirmat:
`classify_prezentare(..., auto_send=0)` → `queued`. **Reparat** (`app/mapping.py`, TDD,
`tests/test_text_rule_autosend.py`): `_rezolva_din_reguli_text` intoarce si `auto_send`;
`resolve_prestatii` marcheaza itemul cu `regula_fara_autosend` cand regula are auto_send falsy
SI curata adnotarile stale (`cod_sursa`/flag) la fiecare rezolvare (repara si o telemetrie falsa
latenta la re-rezolvare prin mapare exacta); `has_no_auto_send` prinde flagul. Acum auto_send=0 →
`needs_mapping` (review), auto_send=1 → `queued`. Operatorul elibereaza randul comutand „In coada"
pe regula (US-004) → reresolve cu auto_send=1 → queued. `pytest -q` **814 passed**.
VERIFY-ul initial (context curat) a RATAT acest caz (nu testase hold-ul pe auto_send=0); prins la code-review.
---
## GSTACK REVIEW REPORT
| Review | Trigger | Why | Runs | Status | Findings |
|--------|---------|-----|------|--------|----------|
| CEO Review | `/plan-ceo-review` | Scope & strategy | 1 | clean | SELECTIVE EXPANSION: 1 fix siguranta (auto_send default 0) + 3 stories acceptate (US-009/010/011) |
| Codex Review | `/codex review` | Independent 2nd opinion | 0 | — | — |
| Eng Review | `/plan-eng-review` | Architecture & tests (required) | 1 | findings | 4 arhitectura (A1-A4) + 3 code/test (C1, T1-T2); 2 decizii deschise (D-eng-1/2) |
| Design Review | `/plan-design-review` | UI/UX gaps | 1 | clean | score: 6/10 → 9/10, 6 decisions |
| DX Review | `/plan-devex-review` | Developer experience gaps | 0 | — | — |
Pasele 1-7 evaluate cu mockup tintit (calibrat pe tema reala a app-ului). Decizii adaugate in plan:
empty state reguli text, conector detaliu (fundal+border-top, nu border-left slop), banda responsive
768-1024 (ascunde Actualizat), card per rand <768px, chevron+fundal pe rand expandat, keyboard a11y
(`role=button`/`aria-expanded`/Enter-Space) + pastrarea textului lung de stare in `title=`/detaliu.
- **VERDICT (design):** DESIGN CLEARED (9/10).
### Eng Review (2026-06-24)
Plan corect si bine descompus (8 stories atomice, 3 valuri, fisiere disjuncte). Referintele de
linie din PRD sunt exacte. Constatari, verificate fata de cod:
- **A1 (corectitudine) — US-003 numara gresit callsite-urile `resolve_prestatii`.** Sunt **6**, nu 4:
pe langa `mapping.py:276` (in `classify_prestatie`), `mapping.py:441`, `import_router.py:204`,
`import_router.py:1079`, mai exista `routes.py:984` (corectie web `needs_data`) si `routes.py:2278`
(import web, intr-un loop pe randuri). Daca nu se threadeaza `text_rules` si acolo, regulile text
NU se aplica pe caile web. → **D-eng-1**.
- **A2 — `valid_codes=None` pe 4 din 6 callsite-uri** (import_router 204/1079, routes 984/2278) intra
in conflict cu AC-ul US-003 „cod rezolvat din regula respecta validarea fata de nomenclator": pe
acele cai validarea e oprita. Pentru a onora AC-ul trebuie threadat si `valid_codes`. Nota
(pre-existent, semnalat, nereparat aici): promovarea codului direct necunoscut (garda ORA-12899)
ruleaza azi DOAR pe calea API, pentru ca import/web rezolva fara `valid_codes`.
- **A3 — face explicit seam-ul `classify_prezentare`.** `mapping.py:276` e in `classify_prezentare`
(helperul partajat care garanteaza ca `/valideaza` da acelasi verdict ca trimiterea reala —
invariant 5.2). Threading `text_rules` cere param nou pe `classify_prezentare` + incarcare in AMBII
apelanti (create_prezentari + ruta `/valideaza`), altfel dry-run diverge. PRD spune „create_prezentari"
— de corectat in US-003.
- **A4 (decizia principala) — detaliu inline (US-008) vs poll 15s.** Azi detaliul traieste in afara
`#submissions-wrap` (`_coada.html:72`) tocmai ca poll-ul `every 15s` sa nu-l stearga. Inline
reintroduce stergerea-la-poll. → **D-eng-2**.
- **C1 — US-006: nu mari tuple-ul `Eticheta`** (3 elemente, despachetat in template-uri). Adauga
`eticheta_scurta(status)->str` separat (dict propriu + garda `KeyError`).
- **T1 — adauga stories/teste pentru cele 2 callsite-uri web** (corectie + import) care aplica o regula text.
- **T2 — incarca `text_rules`/`valid_codes` o data per cerere/batch**, NU per rand (`routes.py:2278`
e in loop; la fel disciplina in `reresolve_account`).
Decizii rezolvate cu utilizatorul (2026-06-24):
- **D-eng-1 → TOATE cele 6 callsite-uri + `valid_codes`.** Incorporat in US-003 (Fisiere += `routes.py`;
AC-uri pentru cele 6 apeluri + `classify_prezentare` param + incarcare per-cerere/batch).
- **D-eng-2 → pauza poll cat e un rand deschis.** Incorporat in US-008 AC.
A2/A3/C1/T1/T2 incorporate in stories (US-003 callsite-uri+validare+per-batch, US-006 `eticheta_scurta`
separat, US-008 pauza poll, teste web pe corectie/import).
- **VERDICT (eng):** PLAN SOUND. Cele 2 decizii rezolvate, constatarile incorporate in stories.
### CEO Review (2026-06-24) — SELECTIVE EXPANSION
Premisa corecta: regulile text sunt levierul de adoptie al Etapei 5 (mai putine mapari manuale =
onboarding mai rapid pentru service-urile din VFP). Scop bine subtras, reuse puternic peste
`operations_mapping`/`reresolve_account`. Abordare confirmata (tabela separata + threading
`text_rules`, varianta A). Constatare principala (inversiune/blast-radius) + 3 cherry-pick-uri,
toate rezolvate cu utilizatorul:
- **Fix siguranta — `auto_send` DEFAULT 0** (nu 1). O regula pe substring potriveste si operatii
viitoare nevazute; `FINALIZATA` e ireversibil la RAR. Regula noua rezolva codul dar TINE randul
pentru verificare umana pana cand operatorul activeaza „In coada". Incorporat in US-001 + US-004.
- **US-009 (acceptat) — preview pre-salvare**: „aceasta regula potriveste N operatii: ..." inainte de
commit. Perechea naturala a `auto_send=0` → onboarding aproape fara risc.
- **US-010 (acceptat) — telemetrie hit regula** in `app_events`: ce pattern a rezolvat ce submission.
- **US-011 (acceptat) — avertizare overlap** neblocanta intre reguli.
Cele 3 expansiuni livrabile in Val 4 (dupa nucleul US-001..008). Plan CEO persistat in
`~/.gstack/projects/.../ceo-plans/`.
- **VERDICT (ceo):** SCOPE CLEARED. 4 decizii incorporate in stories. Ramane poarta umana de aprobare
PRD inainte de EXECUTE.
NO UNRESOLVED DECISIONS