# 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 `` 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 ``/`` (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 `` cu ``, 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:"`; 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 — `` 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:"` 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