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>
545 lines
37 KiB
Markdown
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
|