feat(5.15+5.14): CLOSE — fix-uri code-review + embeddings functional
5.15 (propagare design + dashboard editare) si 5.14 (mapare LLM distilata) inchise dupa /code-review high. 8 buguri reparate TDD: - HIGH modal nu se deschidea pe randul slim (base.html: trimitere-slim) - HIGH /repune trunchia prestatii (declaratie incompleta la RAR) -> iterare peste existing, codes pozitional - HIGH embeddings incarca model ~230MB degeaba pe corpus gol -> poarta has_corpus() - HIGH picker chips gol pe re-render eroare -> conn/account_id pe toate ramurile - MED obs re-derivat dupa stergere explicita -> _merge_override pastreaza obs='' - MED mapare salvata fara denumire poluă GOLD -> _record_gold_validation guard - MED typo nome_prestatie -> nume_prestatie in select /repune - MED bucketare timp +3h gresita iarna -> SQLite localtime + TZ=Europe/Bucharest Embeddings WIRE-uit functional (PRD #15, decizie user): ensure_embeddings_corpus construieste corpus din nomenclator, gated pe AUTOPASS_EMBEDDINGS_ENABLED (default off). Marime model corectata ~50MB->~230MB (estimare PRD gresita). Cleanup: hoist load_* din bucla bulk-fix; import re la top. Regresie: 1256 passed, 1 deselected (live), 0 failed. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
File diff suppressed because one or more lines are too long
@@ -1,6 +1,67 @@
|
||||
<!-- plan sub /autoplan -->
|
||||
# PRD 5.14 — Mapare automata operatii service prin distilare LLM
|
||||
|
||||
**Stare**: inchis (2026-06-28; CLOSE dupa `/code-review high` -> embeddings „mort dar scump" reparat + WIRE functional la decizia user: corpus din nomenclator gated pe `AUTOPASS_EMBEDDINGS_ENABLED`; marime model corectata ~50MB->~230MB; regresie 1256 passed)
|
||||
|
||||
## Stories de executie (decompozitie lead, 2026-06-28)
|
||||
|
||||
> PRD-ul a fost aprobat prin /autoplan ca DESIGN (Decision Audit Trail #11-20). Aici lead-ul
|
||||
> il sparge in stories atomice executabile (ROADMAP §5.4), FARA a re-deschide deciziile.
|
||||
> **Secventiere fata de 5.15 (D9 + cerinta user "prioritate design 5.15"):** partile DISJUNCTE
|
||||
> de fisier ruleaza in PARALEL cu 5.15 acum; integrarea in editor (`mapping.py`/`routes.py`)
|
||||
> ASTEAPTA 5.15 si se aplica PESTE designul 5.15, fara sa-l suprascrie.
|
||||
|
||||
| Story | Tip | Fisiere (disjunct?) | Depinde de |
|
||||
|-------|-----|---------------------|-----------|
|
||||
| **L14-S1** Layer 1 etichetator offline | tool | `tools/mapare-llm/or_label.py` + teste (mock OpenRouter) — DISJUNCT | — |
|
||||
| **L14-S2** Temporal holdout (GATE Premisa 1) | tool | `tools/mapare-llm/holdout.py` + raport — DISJUNCT | — |
|
||||
| **L14-S3** Schema suggestions + shared store | backend | `app/schema.sql` (aditiv), store module nou, seeder, teste — owns schema.sql | — |
|
||||
| **L14-S4** Modul embeddings in-proces | backend | `app/embeddings.py` NOU + teste — DISJUNCT (modul; fara wiring) | — |
|
||||
| **L14-S5** Set held-out eval (BLOCANT auto-send) | tool | `tools/mapare-llm/heldout_eval.py` + metodologie — DISJUNCT | — |
|
||||
| **L14-S6** Integrare Layer 2/3 in editor | backend+UI | `app/mapping.py`, `app/web/routes.py` (editor) — **DUPA 5.15** | L14-S3,S4; 5.15 US-007/US-009 |
|
||||
|
||||
**Invariante de respectat (din Decision Audit Trail):** auto-send DOAR GOLD propriu (F-A/#11);
|
||||
silver in tabela SEPARATA, niciodata in resolve_prestatii (#13); seeder INSERT OR IGNORE, nu
|
||||
clobber uman (#2); scrub PII inainte de LLM (#3); NUL = ancore negative + supresie (#4);
|
||||
provenance source/confidence (#5); embeddings doar SUGESTIE + degradare gratioasa (#16b);
|
||||
held-out etichetat de OM = blocant pt orice auto-send peste GOLD (#19); tier "Inalta" sters din v1 (#17).
|
||||
|
||||
**Rezultat GATE Premisa 1 (L14-S2, 2026-06-28) — VERDICT: SLABA.** Validarea temporala STRICTA e
|
||||
imposibila (CSV-urile `docs/operatii-service/*.csv` au doar frecvente agregate, fara timestamp). Proxy
|
||||
Zipf + leave-first-out pe 155.195 operatii: pentru 90% acoperire de volum e nevoie de **4.368 denumiri
|
||||
distincte (25.4% din total)**, nu "cateva sute"; leave-first-out (limita superioara de stationaritate)
|
||||
= **88.9% agregat, SUB 90%**. Implicatie: etichetarea offline (L14-S1) trebuie sa proceseze ordine de
|
||||
MII de denumiri per client; coada `needs_mapping` ramane semnificativa chiar dupa bootstrap. Premisa nu e
|
||||
falsa, dar randamentul auto-rezolvarii e mai mic decat estima PRD-ul. NU blocheaza build-ul (piesele sunt
|
||||
utile + auto-send ramane conservator pe GOLD), dar recalibreaza asteptarile de acoperire. Tool: `tools/mapare-llm/holdout.py`.
|
||||
|
||||
**Raport VERIFY 5.14** (subagent independent context curat, 2026-06-28) — **VERDICT: PASS, zero FAIL,
|
||||
zero regresie 5.15.** `pytest -q -m "not live"` → **1245 passed, 0 failed**. Invariante confirmate cu cod+test:
|
||||
- **F1/#11/#17 auto-send DOAR GOLD propriu**: `load_mapping` citeste EXCLUSIV `operations_mapping` al
|
||||
contului; `resolve_prestatii` nu atinge DB (primeste `mapping` dict); singura cale spre `queued` =
|
||||
GOLD propriu. SILVER/GOLD-partajat/embedding = sugestie. Teste `test_f1_*` PASS. Tier "Inalta" sters (#17).
|
||||
- **#13 separare structurala**: grep confirma — `shared_store`/`mapping_suggestions`/`shared_mappings`
|
||||
apar DOAR in `enrich_suggestions` (apelat din `pending_unmapped`), niciodata in `resolve_prestatii`/`load_mapping`.
|
||||
- **#16b degradare gratioasa**: `is_available()=False` → `suggest_nearest=[]` fara exceptie; ingestia nu se blocheaza.
|
||||
- **#2** seeder INSERT OR IGNORE (nu clobber uman); **#4** NUL nu devine cod; **#5** provenance source/confidence;
|
||||
**#3** scrub PII nr/VIN inainte de LLM (`or_common.scrub`); **#19** held-out cu `cod_gold` GOL + kill-criterion
|
||||
(`wrong_code_rate<0.5%` AND `coverage>50%`) — toate PASS cu teste.
|
||||
- **GATE Premisa 1**: verdict **SLABA** documentat onest (proxy Zipf, fara pretentie de validare temporala).
|
||||
- fastembed 0.8.0 INSTALAT; testul real de embedding trece.
|
||||
|
||||
**Riscuri reziduale (LOW, non-blocant)**: (1) fastembed 0.8.0 foloseste mean-pooling (warning) — relevant doar
|
||||
daca se persista corpusul de vectori intre versiuni (acum re-indexat la nevoie din nomenclator); (2) `record_human_validation`
|
||||
ON CONFLICT nu suprascrie `cod_prestatie` (by design — corectie = override per-cont sau DELETE explicit);
|
||||
(3) lazy-load fastembed la prima cerere `/mapari` cand `AUTOPASS_EMBEDDINGS_ENABLED=true` (~230MB, cateva
|
||||
zeci de secunde daca modelul nu e in cache — acceptat la decizia CLOSE). **CLOSE 2026-06-28: embeddings WIRE-uit
|
||||
functional** (era „mort dar scump"): `ensure_embeddings_corpus(conn)` construieste corpusul din nomenclator
|
||||
(`nume_prestatie`->`cod_prestatie`), apelat in `pending_unmapped` + `_nemapate_pentru_submission` inainte de
|
||||
bucla, gated pe `AUTOPASS_EMBEDDINGS_ENABLED` (default OFF). Re-index doar la schimbarea semnaturii
|
||||
nomenclatorului. Corpusul se construieste din nomenclator (18 coduri largi), NU per-confirmare umana — sugestia
|
||||
embedding e similaritate denumire-prezentare vs. nume_prestatie RAR.
|
||||
|
||||
---
|
||||
|
||||
## Problema
|
||||
|
||||
La ingestie (canal API si import web), o prestatie poate veni cu `cod_op_service`
|
||||
@@ -169,9 +230,13 @@ cheie pentru reviziile plan: unde fix se aseaza bara treptei „inalta".
|
||||
|
||||
- Stratul 1: tool CLI offline `tools/mapare-llm/` (exista: `or_common.py`,
|
||||
`or_modeltest.py`; de adaugat `or_label.py` cu grupare + propagare).
|
||||
- Stratul 2: `suggest_from_corpus` + similaritate embeddings in `app/mapping.py`,
|
||||
apelata in `pending_unmapped` pentru sugestia din editor. Model embedding incarcat
|
||||
la pornire / serviciu, vectori pre-calculati pe baza.
|
||||
- Stratul 2: similaritate embeddings in `app/mapping.py` (`enrich_suggestions` ->
|
||||
`suggest_nearest`), apelata in `pending_unmapped` / `_nemapate_pentru_submission`
|
||||
pentru sugestia din editor. Corpusul se construieste din nomenclator via
|
||||
`ensure_embeddings_corpus` (gated pe `AUTOPASS_EMBEDDINGS_ENABLED`, default off):
|
||||
lazy-load model fastembed/ONNX (~230MB) la prima cerere /mapari cand flagul e activ,
|
||||
re-index doar la schimbarea nomenclatorului (semnatura). Off -> no-op (cade pe
|
||||
GOLD/SILVER + fuzzy). SUGGESTION-ONLY: NU intra in resolve_prestatii/enqueue (#13).
|
||||
- Stratul 3: store partajat (tabela noua `shared_mappings` sau coloana de scope pe
|
||||
`operations_mapping`), seed la confirmare umana; override per-cont.
|
||||
- Validare `valid_codes` pe tot lantul (exista).
|
||||
@@ -208,6 +273,17 @@ cheie pentru reviziile plan: unde fix se aseaza bara treptei „inalta".
|
||||
| 8 | Eng | **SUPERSEDED:** corpus partajat cross-account (strat GOLD comun), NU per-cont izolat; override per-cont pe conflict | TASTE | P1, P2 | cerinta utilizator: validarea unui service ajuta toate; muncă compusa. Conflictul de vocabular rezolvat prin override + provenienta | (vechi: corpus strict per-cont) |
|
||||
| 9 | Eng | Furnizor etichetare = OpenRouter free, ensemble NVIDIA (super-120b + nano-9b); aruncat ultra-550b | MECHANICAL | P3 | masurat 2026-06-28: doar NVIDIA routeaza fiabil; ultra 4-5x lent fara castig | Groq (cap atins) / ultra |
|
||||
| 10 | Eng | Etichetare prioritizata pe frecventa + grupare pe similaritate (eticheteaza reprezentant, propaga) | MECHANICAL | P2 | acopera mult mai mult volum per apel; critic pe cap free ~50/zi | etichetare alfabetica |
|
||||
| 11 | CEO | **F-A: cross-account GOLD = suggestion-only**, nu auto-send cross-cont; doar GOLD PROPRIU (validat de omul contului) auto-trimite | GATE (user) | P1 | prima-intalnire cross-cont = FINALIZATA gresit ireversibil; override per-cont e post-hoc | cross-account auto-send (PRD scris) |
|
||||
| 12 | CEO | Premisa 1 (90% repeat) validata cu **temporal holdout INAINTE** de build | GATE (user) | P1 | concentrare-in-corpus != future-repeats-past; ieftin de verificat | build pe asumtie |
|
||||
| 13 | Eng | **Strat SILVER in TABELA SEPARATA** (mapping_suggestions), citita DOAR de suggest_codes/pending_unmapped; NICIODATA de load_mapping/resolve_prestatii | MECHANICAL | P5,P1 | scope-column pe operations_mapping auto-trimite silver (8+ call-site); separare structurala | scope column pe operations_mapping |
|
||||
| 14 | Eng | Shared store = tabela noua pe cheia `denumire_normalizata` (NU coloana pe operations_mapping: cheie diferita cod_op_service + UNIQUE) | MECHANICAL | P5 | spatii de chei diferite; conflict UNIQUE | scope column |
|
||||
| 15 | Eng | **Embeddings Layer 2 RAMANE in v1** (utilizatorul a respins amanarea la gate; mentine Decision #6). Recomandarea ambelor voci era amanare la v2 | USER CHALLENGE -> override user | P3,P5 | voci: 2GB pe ipoteza nemasurata, 18 clase acoperite de exact+fuzzy. User: vrea castig pe coada RO + control infra | (amanare v2) |
|
||||
| 16 | Eng | Embeddings = **IN-PROCES fastembed/ONNX** (~230MB pe disc, ONNX quantizat, fara torch; estimarea initiala de ~50MB a fost gresita — modelul multilingv `paraphrase-multilingual-MiniLM-L12-v2` are ~231MB chiar quantizat), in procesul API; model BAKED in imaginea Docker (sau volum cache) -> ZERO dependenta de retea la runtime. NU serviciu separat. Lazy-load la pornire, nu pe /healthz; worker NU incarca modelul | TASTE (user, revizuit) | P5,P3 | user: "embedding in interiorul aplicatiei, nu mai depind de alte resurse". Mai simplu + mai robust decat serviciu HTTP; ruleaza identic local si in Docker/LXC | serviciu separat Ollama/HTTP (revocat) / sentence-transformers+torch |
|
||||
| 16b | Eng | **Degradare gratioasa**: daca modelul nu se incarca -> ingestia NU se blocheaza, NU auto-trimite; cade pe exact+fuzzy, incertul -> needs_mapping. Embeddings raman doar SUGESTIE (consecinta F-A), in afara verdictului de enqueue (invariant dry-run/commit, Eng-F8) | MECHANICAL | P1 | esecul incarcarii modelului nu trebuie sa rupa coada; fara retea la runtime | block ingest pe model lipsa |
|
||||
| 17 | Eng | **Tier "Inalta" auto-send STERS din v1**; GOLD auto-trimite, restul (silver/NN/LLM-unanim) = needs_mapping 1-click | MECHANICAL | P1 | fara ground-truth; unanimitate same-family = eroare corelata, nu validitate | tier Inalta pe unanimitate LLM |
|
||||
| 18 | Eng | sklearn classifier scos din v1 | MECHANICAL | P5 | al doilea artefact antrenabil + pickle, castig marginal pe 18 clase | sklearn in v1 |
|
||||
| 19 | Eng | **Set held-out etichetat de OM = BLOCANT** pt orice tier auto-send peste GOLD propriu | MECHANICAL | P1 | "antrenare pe test" invalideaza orice precizie raportata | prag din etichete LLM |
|
||||
| 20 | CEO | OpenRouter: free OK pt bootstrap unic; credit mic ($5-20) pt drift steady-state (nu arhitecta pe cap 50/zi) | TASTE | P3 | juggling free > cost credit in timp eng | totul pe free tier |
|
||||
|
||||
## Istoric review (pre-pivot)
|
||||
|
||||
@@ -221,4 +297,92 @@ e goala momentan si se completeaza la urmatoarea rulare.
|
||||
|
||||
## GSTACK REVIEW REPORT
|
||||
|
||||
(de completat la rularea reviziilor pe versiunea pivotata — plan-ceo / plan-eng / plan-design)
|
||||
Rulat prin `/autoplan` 2026-06-28 (SELECTIVE EXPANSION). Voci: Claude subagent independent
|
||||
(CEO + Eng) + analiza orchestrator pe cod. **Codex INDISPONIBIL** (usage limit, reset 18 iul)
|
||||
-> mod single-reviewer. UI scope: NU (editorul needs_mapping exista deja). DX scope: borderline
|
||||
(CLI intern operator) -> Phase 3.5 sarit, considerente DX in Eng.
|
||||
|
||||
### Decizii GATE (confirmate de utilizator)
|
||||
- **F-A: cross-account = suggestion-only.** Maparile validate de orice cont PRE-COMPLETEAZA
|
||||
editorul needs_mapping (1-click) dar NU auto-trimit. Doar exact-match pe GOLD-ul PROPRIU
|
||||
(validat de omul contului) auto-trimite. Elimina riscul de FINALIZATA gresit cross-tenant.
|
||||
- **Premisa 1 validata cu temporal holdout INAINTE de build** (corpus primele N luni/client ->
|
||||
hit-rate exact pe lunile urmatoare). Ieftin, datele exista.
|
||||
|
||||
### Consens CEO (single-reviewer; Codex N/A)
|
||||
| Dimensiune | Claude | Verdict |
|
||||
|---|---|---|
|
||||
| Premise valide | NO (P1, P5) | flagged |
|
||||
| Problema corecta | PARTIAL | flagged |
|
||||
| Scope calibrat | NO (over-eng) | flagged |
|
||||
| Alternative explorate | NO | flagged |
|
||||
| Riscuri piata | PARTIAL | flagged |
|
||||
| Traiectorie 6 luni | AT RISK | flagged |
|
||||
|
||||
### Consens Eng (single-reviewer; Codex N/A)
|
||||
| Dimensiune | Claude | Verdict |
|
||||
|---|---|---|
|
||||
| Arhitectura | PARTIAL | flagged |
|
||||
| Acoperire teste | NO | flagged |
|
||||
| Footprint/perf | NO (2GB torch) | flagged |
|
||||
| Siguranta F1 | INTENT-OK | flagged |
|
||||
| Cai de eroare | PARTIAL | flagged |
|
||||
| Risc deploy | NO | flagged |
|
||||
|
||||
### Constatari portante (severitate)
|
||||
- **F-A / Eng-F1 (CRITIC):** auto-send DOAR pe GOLD. Strat SILVER in TABELA SEPARATA
|
||||
(`mapping_suggestions`), citita doar de suggest_codes/pending_unmapped, NICIODATA de
|
||||
load_mapping/resolve_prestatii. `auto_send` col e moarta (mapping.py:436); singura cale
|
||||
spre `queued` (auto-send, mapping.py:414) trebuie sa fie GOLD. Separarea = structurala.
|
||||
- **F-B (CRITIC):** toate masuratorile sunt ACORD (100% vs Groq, 87% unanim), nu ACURATETE
|
||||
vs ground-truth. Same-family NVIDIA = eroare corelata. Niciun tier auto-send peste GOLD
|
||||
pana nu exista set held-out etichetat de OM (esantion aleator stratificat).
|
||||
- **Eng-F2 (HIGH):** shared store pe cheia `denumire_normalizata` (NU `cod_op_service`) ->
|
||||
tabela noua obligatorie; precedenta override pinnata: account override > account GOLD >
|
||||
shared GOLD > text rules > unmapped.
|
||||
- **F-C / Eng-F3 (HIGH):** embeddings Layer 2 = over-engineering pe 18 clase Zipf-head.
|
||||
AMANAT v2. Daca se construieste: fastembed/ONNX (~230MB pe disc, ONNX quantizat;
|
||||
estimarea initiala de ~50MB a fost gresita), API-process-only, lazy, nu pe
|
||||
/healthz. NU in resolve_prestatii (altfel worker-ul ar avea nevoie de torch).
|
||||
- **Eng-F4 (HIGH):** tier "Inalta" sters din v1 (consecinta F-A + lipsa ground-truth).
|
||||
- **F-D (HIGH):** Premisa 1 nevalidata temporal -> gate (rezolvat).
|
||||
- **F-E (HIGH):** fara metrica de succes/baseline/kill-criterion -> de instrumentat
|
||||
(% linii auto-rezolvate la rata cod-gresit < 0.X%).
|
||||
- **MEDIUM:** NUL short-circuit inainte de suggest_codes + structura separata (Eng-F6);
|
||||
OpenRouter 429 resumabil + group radius conservator + provenance (Eng-F7); divergenta
|
||||
dry-run/commit (Eng-F8); credit mic vs free-tier (F-F); omisiune silentioasa NUL (F-G);
|
||||
calitate embedding RO de verificat (F-H); versionare cheie normalizare; drop sklearn v1.
|
||||
|
||||
### Teme cross-faza (semnalate independent in ambele faze)
|
||||
1. Auto-send DOAR GOLD; silver/embeddings/unanimitate-LLM = sugestie (CEO F-A/F-B + Eng F1/F4).
|
||||
2. Embeddings over-engineered pe 18 clase; amana sau fastembed (CEO F-C + Eng F3).
|
||||
3. Fara set ground-truth; masoara precizia inainte de orice tier auto-send (CEO F-B/F-E + Eng F4).
|
||||
|
||||
### NU in scope (amanat)
|
||||
- **sklearn classifier** peste embeddings (v2; embeddings raman doar NN suggestion in v1).
|
||||
- Orice tier auto-send peste exact-match GOLD propriu (pana la set held-out).
|
||||
- LLM generativ local la runtime (deja non-obiectiv PRD).
|
||||
- Tier "Inalta" calibrat (re-introdus doar cu eval cross-family + ground-truth).
|
||||
|
||||
**Embeddings Layer 2 RAMANE in v1** (override user la gate), IN-PROCES (fastembed/ONNX,
|
||||
model baked in imagine), DOAR sugestie, cu fallback gratios pe exact+fuzzy daca modelul nu
|
||||
incarca. Zero dependenta de retea la runtime. Vezi audit #15/#16/#16b.
|
||||
|
||||
### Ce exista deja (de refolosit, nu rescris)
|
||||
- `resolve_prestatii` / `classify_prezentare` / `reresolve_account` (mapping.py): precedenta
|
||||
cod direct > exact mapping > text rules > unmapped; garda valid_codes (ORA-12899).
|
||||
- `suggest_codes` (rapidfuzz token_sort) + `pending_unmapped`: punct de injectie sugestii.
|
||||
- `operation_text_rules` (substring) + `operations_mapping` (GOLD per-cont).
|
||||
- `tools/mapare-llm/` (or_common.py, or_modeltest.py) + pattern `*-partial.json` resumabil.
|
||||
- Scrub PII (F3), `normalize_for_match`, seed nomenclator (18 coduri).
|
||||
|
||||
### Artefact test plan
|
||||
`~/.gstack/projects/romfast-rar-autopass/mmarius-main-test-plan-20260628.md`
|
||||
(test F1-regression CRITIC + precedenta override + NUL + idempotenta seed + held-out eval).
|
||||
|
||||
### Stare review
|
||||
Aprobat prin `/autoplan` (vezi Decision Audit Trail #11-20 + #16b). Plan livrabil:
|
||||
v1 = Layer 1 (etichetare offline) + Layer 2 (embeddings ca SERVICIU SEPARAT configurabil,
|
||||
doar sugestie, fallback gratios) + Layer 3 (GOLD propriu auto-send + shared suggestion-only)
|
||||
+ exact/fuzzy existent + temporal holdout + metrica de succes + set held-out (blocant pt
|
||||
orice auto-send peste GOLD). v2 = sklearn classifier (dupa masurare).
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# PRD 5.15 — Propagare design landing in aplicatie (dashboard compact + editare slim, VIN unic, prestatii multi-select)
|
||||
|
||||
**Stare**: draft
|
||||
**Stare**: inchis (2026-06-28; CLOSE dupa `/code-review high` -> 8 buguri reparate TDD; regresie 1256 passed, 1 deselected live; E2E browser real ramane OPEN — mediu sandbox fara Playwright)
|
||||
|
||||
> Proces complet: `docs/ROADMAP.md` §5. Contract RAR (sursa de adevar): `docs/api-rar-contract.md`.
|
||||
> Sistemul de design al landing-ului: `app/web/templates/landing.html` (commit 41aa385), `DESIGN.md`.
|
||||
@@ -124,24 +124,24 @@ acelasi produs, coerent vizual.
|
||||
- **Fisiere**: `app/web/templates/base.html`, `DESIGN.md`, `tests/test_tema.py` (~3 fisiere)
|
||||
- **Test intai (RED)**: `tests/test_tema.py` — `test_cele_4_teme_definite`, `test_tokeni_card2_line2_in_toate_temele`, `test_anti_fouc_4_stari`, `test_migrare_localStorage_legacy`
|
||||
- **Acceptance criteria**:
|
||||
- [ ] Pastram temele EXISTENTE light/dark/petrol si ADAUGAM 4 teme noi grafit/cobalt/cupru/hartie,
|
||||
- [x] Pastram temele EXISTENTE light/dark/petrol si ADAUGAM 4 teme noi grafit/cobalt/cupru/hartie,
|
||||
definite prin token-urile EXISTENTE (`--bg/--card/--ink/--muted/--line/--ok/--warn/--err/--accent`)
|
||||
+ DOUA noi `--card2` (fundal input/contor) si `--line2` (separator subtire). `--card2`/`--line2`
|
||||
primesc valori si in light/dark/petrol (fallback rezonabil). Maparea landing->app pentru cele 4
|
||||
noi: `--text->--ink`, `--sub->--muted`, `--okt->--ok`, `--errt->--err`, `--infot->--accent`.
|
||||
- [ ] Selectorul ciclic parcurge TOATE: light -> dark -> petrol -> grafit -> cobalt -> cupru ->
|
||||
- [x] Selectorul ciclic parcurge TOATE: light -> dark -> petrol -> grafit -> cobalt -> cupru ->
|
||||
hartie -> Auto, afiseaza eticheta temei curente, persistenta `localStorage` (D2).
|
||||
- [ ] **DRY (E2)**: config-ul de teme traieste intr-o SINGURA structura sursa-de-adevar
|
||||
- [x] **DRY (E2)**: config-ul de teme traieste intr-o SINGURA structura sursa-de-adevar
|
||||
(`THEMES` ordonata, cu `{id,label,icon}`) din care se DERIVA `CYCLE`/`NEXT`/`ICONS`/`LABELS`
|
||||
(azi 5 literali paraleli la base.html:758-765) SI setul anti-FOUC `VALID` (azi separat la
|
||||
base.html:22). Adaugarea unei teme noi = o singura intrare; test ca derivatele acopera
|
||||
toate temele (prinde o intrare ICONS/LABELS lipsa, nu doar token CSS lipsa).
|
||||
- [ ] "Auto" pastrat: urmeaza `prefers-color-scheme`, rezolva la dark/grafit sau light/hartie
|
||||
- [x] "Auto" pastrat: urmeaza `prefers-color-scheme`, rezolva la dark/grafit sau light/hartie
|
||||
(decizie minora: Auto -> dark + hartie pentru light, sau dark/grafit — aliniaza cu I2).
|
||||
- [ ] Script anti-FOUC in `<head>` seteaza `data-theme` sincron pre-paint pentru toate starile;
|
||||
- [x] Script anti-FOUC in `<head>` seteaza `data-theme` sincron pre-paint pentru toate starile;
|
||||
valoare necunoscuta -> Auto, fara blink. Valorile vechi raman valide (nu se mapeaza fortat).
|
||||
- [ ] Contrast AA pentru text principal in toate temele (light + hartie sunt cele luminoase).
|
||||
- [ ] `DESIGN.md` actualizat: sectiunea cromatica + selector tema reflecta toate temele.
|
||||
- [x] Contrast AA pentru text principal in toate temele (light + hartie sunt cele luminoase).
|
||||
- [x] `DESIGN.md` actualizat: sectiunea cromatica + selector tema reflecta toate temele.
|
||||
- **Verificare E2E**: browser pe `/` (dashboard logat) — ciclare prin toate temele, persistenta la
|
||||
refresh, fara FOUC; toate temele selectabile.
|
||||
|
||||
@@ -153,24 +153,24 @@ chips **pentru ca** dashboard-ul si formularul sa le consume DRY, identic cu moc
|
||||
- **Fisiere**: `app/web/templates/base.html`, `DESIGN.md`, `tests/test_web_responsive.py` (~3 fisiere)
|
||||
- **Test intai (RED)**: `tests/test_web_responsive.py` — `test_clasa_contor_card`, `test_clasa_lista_slim`, `test_clasa_camp_slim`, `test_clasa_chips`
|
||||
- **Acceptance criteria**:
|
||||
- [ ] `.contor-card` (sau nume aliniat conventiei): cifra mare bold + eticheta mica muted, fundal
|
||||
- [x] `.contor-card` (sau nume aliniat conventiei): cifra mare bold + eticheta mica muted, fundal
|
||||
`--card2`, bordura `--line`, radius 8px, padding 10-12px; variante de culoare a cifrei prin
|
||||
`.s-*` existente (verde/accent/rosu).
|
||||
- [ ] `.lista-trimiteri-slim` cu rand `.trimitere-slim`: stanga = VIN mono (linia 1) + operatie·ora
|
||||
- [x] `.lista-trimiteri-slim` cu rand `.trimitere-slim`: stanga = VIN mono (linia 1) + operatie·ora
|
||||
muted (linia 2, 11px); dreapta = pill de stare; separator `--line2`; padding 10-14px.
|
||||
Randul ramane clickabil (rol button) si pastreaza tinta 44px pe mobil.
|
||||
- [ ] Varianta slim de camp formular: label 11px muted deasupra, input ~30px inaltime, fundal
|
||||
- [x] Varianta slim de camp formular: label 11px muted deasupra, input ~30px inaltime, fundal
|
||||
`--card2`, mono pentru VIN/odometru/nr; integrata in macro-ul `camp` din `_macros.html`
|
||||
printr-un flag (`slim=True`), fara a rupe randarea actuala (default neschimbat).
|
||||
- [ ] `.chips` + `.chip` (cu buton `×` de stergere) pentru prestatii multi-select; accesibil
|
||||
- [x] `.chips` + `.chip` (cu buton `×` de stergere) pentru prestatii multi-select; accesibil
|
||||
(buton real cu `aria-label`), stilat ca in mockup (accent 18%, font 10-11px).
|
||||
- [ ] **Doar tokeni, fara hex hardcodat (criteriu din mockup)**: toate culorile componentelor noi
|
||||
- [x] **Doar tokeni, fara hex hardcodat (criteriu din mockup)**: toate culorile componentelor noi
|
||||
(contor, lista slim, chips, strip, picker) folosesc EXCLUSIV variabile CSS
|
||||
(`var(--errt)`/`var(--okt)`/`var(--accent)`/`var(--card2)`/`var(--line2)` etc.), NU hex literal
|
||||
si NU inline-styles copiate ca-atare din `landing.html`. Cifra "De corectat" rosie = token
|
||||
(`var(--errt)`), nu `#E05D5D` hardcodat, ca sa ramana AA pe temele luminoase (hartie/light).
|
||||
Referinta: `docs/mockups/prd-5.15-mockups.html`.
|
||||
- [ ] Zero regresie vizuala pe componentele existente (`.card/.pill/.act/.tabel-trimiteri`).
|
||||
- [x] Zero regresie vizuala pe componentele existente (`.card/.pill/.act/.tabel-trimiteri`).
|
||||
- **Verificare E2E**: pagina de proba/sandbox sau direct in US-003/004/007; vizual pe un esantion de teme + 390/1280.
|
||||
|
||||
### US-003: Dashboard Acasa — carduri-contor inlocuiesc bara de status
|
||||
@@ -183,7 +183,7 @@ chips **pentru ca** dashboard-ul si formularul sa le consume DRY, identic cu moc
|
||||
`tests/test_web_dashboard.py` (~5 fisiere)
|
||||
- **Test intai (RED)**: `tests/test_web_status.py` — `test_strip_sanatate_mereu_vizibil`, `test_strip_rosu_worker_oprit`, `test_trei_contoare_card`, `test_trimise_all_time_luna_azi`, `test_fara_bara_veche`
|
||||
- **Acceptance criteria**:
|
||||
- [ ] **Strip de sanatate mereu-vizibil, DEASUPRA contoarelor** (D6): o linie compacta colorata —
|
||||
- [x] **Strip de sanatate mereu-vizibil, DEASUPRA contoarelor** (D6): o linie compacta colorata —
|
||||
verde "declaratiile curg" cand worker viu + RAR ok; **rosu** + text explicit cand worker
|
||||
oprit SAU RAR inaccesibil ("Blocat: worker oprit" / "Blocat: RAR inaccesibil"), cu ultima
|
||||
autentificare RAR. Glife accesibile ✓/✗ (nu doar culoare). Invariant zero-silent-failures:
|
||||
@@ -192,14 +192,14 @@ chips **pentru ca** dashboard-ul si formularul sa le consume DRY, identic cu moc
|
||||
stanga, "Ultima autentificare RAR: ..." mono muted la dreapta. Copy: rosu "Blocat: worker oprit
|
||||
— declaratiile NU pleaca" (sau "... RAR inaccesibil"), verde "Declaratiile curg normal".
|
||||
Referinta: `docs/mockups/prd-5.15-mockups.html`.
|
||||
- [ ] Sub strip: card "Trimiteri RAR AUTOPASS" cu 3 contoare slim: **In coada** (queued, accent),
|
||||
- [x] Sub strip: card "Trimiteri RAR AUTOPASS" cu 3 contoare slim: **In coada** (queued, accent),
|
||||
**Trimise** (sent, verde), **De corectat** (blocate = needs_data + needs_mapping + error, rosu).
|
||||
- [ ] **Stari goale + ierarhie contor (criteriu din mockup)**: cifra principala a contorului "Trimise"
|
||||
- [x] **Stari goale + ierarhie contor (criteriu din mockup)**: cifra principala a contorului "Trimise"
|
||||
e **all-time** (cifra mare bold), iar "luna asta"/"azi" sunt o sub-linie mono secundara
|
||||
(`luna {n} · azi {n}`) — NU "luna asta" ca cifra principala (corecteaza framing-ul din mockup-ul
|
||||
landing). Contorul "De corectat" la 0 se afiseaza **muted, nu rosu** (rosu doar cand exista
|
||||
blocate — pastreaza pattern-ul `_status.html:47`). Referinta: `docs/mockups/prd-5.15-mockups.html`.
|
||||
- [ ] Cardul **Trimise** afiseaza trei valori temporale (D4): all-time (cifra principala) + "luna asta"
|
||||
- [x] Cardul **Trimise** afiseaza trei valori temporale (D4): all-time (cifra principala) + "luna asta"
|
||||
+ "azi" (sub-linie secundara). `_status_counts` extins cu `sent_today`/`sent_month`.
|
||||
**Sursa de timp**: NU exista coloana `sent_at`; folosim `status='sent' AND date(updated_at)=...`.
|
||||
Justificare (verificat): un rand `sent` nu mai primeste scrieri ulterioare pana la purge-delete
|
||||
@@ -213,11 +213,11 @@ chips **pentru ca** dashboard-ul si formularul sa le consume DRY, identic cu moc
|
||||
**Caveat reconcile (E6 outside-voice)**: pe reconciliere (raspuns pierdut) worker-ul
|
||||
marcheaza `sent` cu `updated_at` = momentul reconcilierii, nu al inserarii RAR — pentru
|
||||
randurile reconciliate (rare) `updated_at` poate diferi de momentul real al trimiterii.
|
||||
- [ ] Navigarea existenta (Trimiteri/Mapari + badge needs_mapping) se pastreaza. Click pe contorul
|
||||
- [x] Navigarea existenta (Trimiteri/Mapari + badge needs_mapping) se pastreaza. Click pe contorul
|
||||
**De corectat** deep-link-eaza in lista filtrata pe blocate (`?status=` existent din 5.x),
|
||||
nu intr-o pagina noua.
|
||||
- [ ] Scoped pe cont; poll-ul existent (`/_fragments/status`) randeaza noul antet fara a pierde tab-ul.
|
||||
- [ ] Responsive: cele 3 contoare pe un rand pe desktop, stivuite/2-pe-rand pe mobil, fara overflow.
|
||||
- [x] Scoped pe cont; poll-ul existent (`/_fragments/status`) randeaza noul antet fara a pierde tab-ul.
|
||||
- [x] Responsive: cele 3 contoare pe un rand pe desktop, stivuite/2-pe-rand pe mobil, fara overflow.
|
||||
- **Verificare E2E**: browser pe `/` — contoare corecte vs date din DB, sanatate worker mort/viu,
|
||||
poll pastreaza starea.
|
||||
|
||||
@@ -230,14 +230,14 @@ si mai usor de scanat, pastrand filtrele si actiunile.
|
||||
`tests/test_web_submissions.py`, `tests/test_web_responsive.py` (~4 fisiere)
|
||||
- **Test intai (RED)**: `tests/test_web_submissions.py` — `test_rand_slim_vin_operatie_pill`, `test_filtre_paginare_pastrate`, `test_bulk_doar_blocate`, `test_click_deschide_detaliu`
|
||||
- **Acceptance criteria**:
|
||||
- [ ] Fiecare rand: stanga VIN mono scurt (`vin_scurt`) linia 1 + operatie + ora/data muted linia 2;
|
||||
- [x] Fiecare rand: stanga VIN mono scurt (`vin_scurt`) linia 1 + operatie + ora/data muted linia 2;
|
||||
dreapta pill de stare (`stare_css`/`stare_scurt`). Nr. inmatriculare, data completa si nr.
|
||||
prezentare RAR raman accesibile (linie meta discreta si/sau in modalul de detaliu).
|
||||
- [ ] Filtre (data/vehicul/stare — `_coada.html`), paginarea numerotata si bulk-delete pe randuri
|
||||
- [x] Filtre (data/vehicul/stare — `_coada.html`), paginarea numerotata si bulk-delete pe randuri
|
||||
blocate (checkbox doar pe `gestionabil`) raman FUNCTIONALE.
|
||||
- [ ] Click pe rand deschide `/_fragments/trimitere/{id}` in modal (neschimbat).
|
||||
- [ ] Slim layout consistent desktop si <=1024px (cardurile responsive existente nu regreseaza).
|
||||
- [ ] Pill-urile de stare folosesc maparea din `labels.py` (zero etichete noi). Eticheta "Eroare VIN"
|
||||
- [x] Click pe rand deschide `/_fragments/trimitere/{id}` in modal (neschimbat).
|
||||
- [x] Slim layout consistent desktop si <=1024px (cardurile responsive existente nu regreseaza).
|
||||
- [x] Pill-urile de stare folosesc maparea din `labels.py` (zero etichete noi). Eticheta "Eroare VIN"
|
||||
din mockup-ul landing e DOAR ilustrativa — se foloseste `stare_scurt` existent (ex. "De corectat").
|
||||
- **Verificare E2E**: browser — filtrare + paginare + click detaliu + bulk pe blocate, pe 4 teme,
|
||||
pe 390/820/1280.
|
||||
@@ -253,20 +253,20 @@ sa corectez/completez ce s-a facut, separat de codurile RAR.
|
||||
`tests/test_web_corectie*.py`, `tests/test_import_review.py` (~6 fisiere)
|
||||
- **Test intai (RED)**: `tests/test_web_corectie_obs.py` — `test_obs_editabil_persistat_corecteaza`, `test_obs_persistat_preview_editeaza`, `test_obs_optional_gol_ok`, `test_import_concateneaza_operatie_in_obs`
|
||||
- **Acceptance criteria**:
|
||||
- [ ] `obs` traieste in `payload_json` (camp `obs` din contractul RAR); fara coloana noua / migrare (D5).
|
||||
- [ ] `obs` adaugat in `EDIT_FIELDS`; `corecteaza` si `editeaza` (preview) accepta si persista `obs`.
|
||||
- [ ] `obs` optional (text liber, fara validare de continut, doar trim); apare in `payload_view`.
|
||||
- [ ] `obs` se include in payload-ul trimis la RAR (camp `obs`). **`obs` e EXCLUS din cheia de
|
||||
- [x] `obs` traieste in `payload_json` (camp `obs` din contractul RAR); fara coloana noua / migrare (D5).
|
||||
- [x] `obs` adaugat in `EDIT_FIELDS`; `corecteaza` si `editeaza` (preview) accepta si persista `obs`.
|
||||
- [x] `obs` optional (text liber, fara validare de continut, doar trim); apare in `payload_view`.
|
||||
- [x] `obs` se include in payload-ul trimis la RAR (camp `obs`). **`obs` e EXCLUS din cheia de
|
||||
idempotenta** (`idempotency.py:98`) — deci editarea DOAR a `obs` NU schimba cheia si NU poate
|
||||
crea duplicat (D8). NU recalcula/forta cheia pe baza `obs`. (Corecteaza formularea anterioara.)
|
||||
- [ ] **La import** (D7): denumirea operatiei RAMANE in `op_service` (sursa pentru maparea op->cod);
|
||||
- [x] **La import** (D7): denumirea operatiei RAMANE in `op_service` (sursa pentru maparea op->cod);
|
||||
daca fisierul NU are coloana Observatii, denumirea operatiei se **COPIAZA** (nu se muta) si in
|
||||
`obs`; daca are coloana Observatii, se pastreaza textul ei. Format de concatenare definit
|
||||
(denumiri separate prin "; "). Fluxul needs_mapping ramane neatins.
|
||||
- [ ] **Idempotent (E3)**: copierea operatiei in `obs` e DERIVE-ON-EMPTY (doar cand `obs` e gol)
|
||||
- [x] **Idempotent (E3)**: copierea operatiei in `obs` e DERIVE-ON-EMPTY (doar cand `obs` e gol)
|
||||
ca re-importul/re-editarea sa NU dubleze textul ("Schimb ulei; Schimb ulei"). Test dedicat
|
||||
anti-dublu-concat.
|
||||
- [ ] **Cuplaj preview-import**: `obs` se adauga in `EDIT_FIELDS` (`import_router.py:261`); `_merge_override`
|
||||
- [x] **Cuplaj preview-import**: `obs` se adauga in `EDIT_FIELDS` (`import_router.py:261`); `_merge_override`
|
||||
il propaga (obs e free-text, cade pe ramura ne-canonicalizata — fara strip "0", doar trim).
|
||||
- **Verificare E2E**: `POST /trimitere/{id}/corecteaza` cu `obs` -> persistat -> vizibil in detaliu;
|
||||
optional proba live RAR ca `obs` apare in FINALIZATA.
|
||||
@@ -285,7 +285,7 @@ comanda poate avea mai multe prestatii, asa cum accepta RAR.
|
||||
handler-e cer un rewrite real (nu "fara schimbare de logica").
|
||||
- **Test intai (RED)**: `tests/test_web_corectie_prestatii.py` — `test_mai_multe_coduri_acceptate`, `test_cod_invalid_respins`, `test_lista_goala_needs_mapping`, `test_idempotency_recalculat`, `test_odometru_initial_conditionat_R_ODO`
|
||||
- **Acceptance criteria**:
|
||||
- [ ] Handler-ele de editare accepta o LISTA de `cod_prestatie`, inlocuind selectul unic. **NU
|
||||
- [x] Handler-ele de editare accepta o LISTA de `cod_prestatie`, inlocuind selectul unic. **NU
|
||||
reconstrui lista cu itemi goi**: handler-ele de azi injecteaza codul DOAR in `prestatii[0]`
|
||||
(`routes.py:1146-1164`, `1288-1324`) — multi-select le rescrie ca: pastreaza itemii existenti
|
||||
cu `cod_op_service`/`denumire` (invariant D7) si seteaza/adauga `cod_prestatie` pe ei.
|
||||
@@ -294,18 +294,18 @@ comanda poate avea mai multe prestatii, asa cum accepta RAR.
|
||||
ELIMINA acel `pop`: cand se seteaza un cod direct, `cod_op_service`/`denumire` RAMAN pe item
|
||||
(altfel rupe D7 si US-009). **Test de regresie obligatoriu** (IRON RULE): op_service
|
||||
supravietuieste unui /repune cu cod.
|
||||
- [ ] **Pereche operatie<->cod definita**: cand exista operatii (`cod_op_service`), fiecare cod-chip
|
||||
- [x] **Pereche operatie<->cod definita**: cand exista operatii (`cod_op_service`), fiecare cod-chip
|
||||
se ataseaza unei operatii (1 operatie -> 1 cod, ca azi, dar acum N operatii -> N coduri);
|
||||
cand NU exista operatie (cod direct, ex. corectie pura), chip-urile sunt coduri libere intr-o
|
||||
lista fara `op_service`. Aceasta pereche e ce consuma US-009 (salvare mapare op->cod).
|
||||
- [ ] Fiecare cod e validat fata de nomenclator (`valid_codes`); cod necunoscut -> respins cu
|
||||
- [x] Fiecare cod e validat fata de nomenclator (`valid_codes`); cod necunoscut -> respins cu
|
||||
mesaj (NU se trimite raw — invariant ORA-12899 din CLAUDE.md/contract).
|
||||
- [ ] Lista goala de coduri -> ramane `needs_mapping` (nu se trimite fara cod).
|
||||
- [ ] **Coduri duplicate** -> dedupare **PER-ITEM, nu "dupa cod"** (E4): doua operatii distincte
|
||||
- [x] Lista goala de coduri -> ramane `needs_mapping` (nu se trimite fara cod).
|
||||
- [x] **Coduri duplicate** -> dedupare **PER-ITEM, nu "dupa cod"** (E4): doua operatii distincte
|
||||
pot mapa legitim la acelasi cod RAR; deduparea naiva dupa cod ar sterge o operatie reala si
|
||||
ar distruge contextul op->cod cerut de US-009. Dedup = acelasi (op, cod) de 2x, nu acelasi cod.
|
||||
- [ ] Recalcul idempotenta dupa editare (mecanism existent), cu prinderea coliziunii ca azi.
|
||||
- [ ] Se pastreaza regula `odometruInitial` obligatoriu cand lista contine `R-ODO`/`I-ODO`
|
||||
- [x] Recalcul idempotenta dupa editare (mecanism existent), cu prinderea coliziunii ca azi.
|
||||
- [x] Se pastreaza regula `odometruInitial` obligatoriu cand lista contine `R-ODO`/`I-ODO`
|
||||
(contract §payload) — validare existenta, doar verificata pe lista.
|
||||
- **Verificare E2E**: `POST /corecteaza` cu 2 coduri valide -> `queued` cu `prestatii` de lungime 2;
|
||||
cu un cod invalid -> respins; optional live RAR cu 2 prestatii -> FINALIZATA.
|
||||
@@ -320,28 +320,28 @@ e compact si imi arata clar codurile RAR si observatiile, ca in mockup.
|
||||
`tests/test_web_preview_edit.py`, `tests/test_web_detaliu*.py` (~6 fisiere)
|
||||
- **Test intai (RED)**: `tests/test_web_form_editare_slim.py` — `test_un_singur_vin`, `test_camp_observatii_prezent`, `test_chips_multi_select_prestatii`, `test_adauga_sterge_chip`, `test_form_slim_in_ambele_modale`
|
||||
- **Acceptance criteria**:
|
||||
- [ ] Formularul foloseste varianta slim de camp (US-002): VIN, Data prestatiei, Nr. inmatriculare,
|
||||
- [x] Formularul foloseste varianta slim de camp (US-002): VIN, Data prestatiei, Nr. inmatriculare,
|
||||
Observatii (textarea), prestatii (chips), Odometru — un SINGUR camp VIN (fara "Confirma VIN").
|
||||
- [ ] Observatii = textarea liber, legat de `obs` (US-005).
|
||||
- [ ] Prestatii = chips multi-select. **Binding op<->cod (E4)**: cand exista operatii
|
||||
- [x] Observatii = textarea liber, legat de `obs` (US-005).
|
||||
- [x] Prestatii = chips multi-select. **Binding op<->cod (E4)**: cand exista operatii
|
||||
(`cod_op_service`), UN picker PE operatie (eticheta op + chip-ul ei de cod), pastrand
|
||||
perechea per-item; lista plata de coduri libere DOAR pentru cazul fara operatie (corectie
|
||||
pura). Fiecare cod ca chip cu `×`; lista se trimite ca `cod_prestatie` multiplu (US-006).
|
||||
- [ ] Acelasi `_form_editare.html` slujeste ambele modale (detaliu `/corecteaza` si preview
|
||||
- [x] Acelasi `_form_editare.html` slujeste ambele modale (detaliu `/corecteaza` si preview
|
||||
`/editeaza`), fara duplicare; degradare fara JS rezonabila (chips ca lista, picker = select).
|
||||
- [ ] **Require dinamic odometruInitial** (D10c): cand lista de chips contine `R-ODO` sau `I-ODO`,
|
||||
- [x] **Require dinamic odometruInitial** (D10c): cand lista de chips contine `R-ODO` sau `I-ODO`,
|
||||
formularul DEZVALUIE si cere `odometru_initial` (contract §payload), previne 400 RAR si un
|
||||
drum `needs_data`. Cand niciun chip R-ODO/I-ODO -> campul ramane optional/ascuns.
|
||||
- [ ] **Editare keyboard-first** (D10d): in picker, Enter adauga chip-ul selectat; sageti
|
||||
- [x] **Editare keyboard-first** (D10d): in picker, Enter adauga chip-ul selectat; sageti
|
||||
navigheaza optiunile; Esc inchide modalul; focus-ul revine logic dupa adaugare/stergere.
|
||||
- [ ] Stilizare fidela mockup-ului pe toate temele; tinte 44px pe mobil; a11y (label-uri, aria,
|
||||
- [x] Stilizare fidela mockup-ului pe toate temele; tinte 44px pe mobil; a11y (label-uri, aria,
|
||||
anunt de chip adaugat/sters pentru screen-reader).
|
||||
- [ ] **HTMX server-driven PRIMARY (E6)**: chips add/remove via `hx-post` care re-randeaza
|
||||
- [x] **HTMX server-driven PRIMARY (E6)**: chips add/remove via `hx-post` care re-randeaza
|
||||
partial-ul chips+form; reveal-ul conditional `odometruInitial` rezulta GRATIS din re-randarea
|
||||
server (server computeaza din lista de chips, fara ramura JS); navigare tastatura =
|
||||
`<select>`/`<datalist>` nativ. JS custom DOAR ca progressive enhancement (snappiness), nu
|
||||
calea principala. Elimina path-ul dublu JS/no-JS pe care formularea anterioara il cerea.
|
||||
- [ ] **Referinta vizuala (criteriu din mockup)**: `docs/mockups/prd-5.15-mockups.html` defineste
|
||||
- [x] **Referinta vizuala (criteriu din mockup)**: `docs/mockups/prd-5.15-mockups.html` defineste
|
||||
aspectul-tinta — VIN unic (FARA al doilea camp "Confirma VIN" din mockup-ul landing); Observatii
|
||||
ca textarea slim; picker PE operatie cu DOUA stari vizuale: (a) operatie mapata = chip cod cu `×`
|
||||
+ "+ alt cod" + link "salveaza regula op->cod" (US-009); (b) operatie ne-mapata = picker galben
|
||||
@@ -361,8 +361,8 @@ fisiere fierbinti (base.html) si nu vreau regresii pe teme/liste/formular.
|
||||
(~3 fisiere)
|
||||
- **Test intai (RED)**: completare scenarii lipsa (componente noi pe TOATE temele; slim list desktop+mobil)
|
||||
- **Acceptance criteria**:
|
||||
- [ ] `pytest -q -m "not live"` verde (fara regresii fata de baseline).
|
||||
- [ ] **Test de tema robust, nu esantion**: un test parametrizat verifica fiecare token critic
|
||||
- [x] `pytest -q -m "not live"` verde (fara regresii fata de baseline).
|
||||
- [x] **Test de tema robust, nu esantion**: un test parametrizat verifica fiecare token critic
|
||||
(`--card2`, `--line2`, `--accent`, `--ok`, `--err`) e DEFINIT in TOATE cele 7+1 stari
|
||||
(light/dark/petrol/grafit/cobalt/cupru/hartie/Auto). Ancorare pe SENTINEL CSS (nu felii
|
||||
fixe `[idx:idx+N]`) — vezi regresia false-green din ROADMAP 5.13.
|
||||
@@ -379,11 +379,11 @@ fisiere fierbinti (base.html) si nu vreau regresii pe teme/liste/formular.
|
||||
`reresolve_account` — fara logica noua), `tests/test_web_mapare_din_chip.py` (~3 fisiere)
|
||||
- **Test intai (RED)**: `tests/test_web_mapare_din_chip.py` — `test_salveaza_regula_din_chip`, `test_reresolve_deblocheaza_frate`, `test_optional_nu_forteaza`
|
||||
- **Acceptance criteria**:
|
||||
- [ ] Cand operatia (`op_service`) e cunoscuta si userul adauga un cod RAR prin chip, apare optiunea
|
||||
- [x] Cand operatia (`op_service`) e cunoscuta si userul adauga un cod RAR prin chip, apare optiunea
|
||||
"salveaza ca regula op->cod"; la confirmare reuse EXACT `save_mapping` + `reresolve_account`
|
||||
(acelasi mecanism ca maparea inline din 5.7), scoped pe cont + CSRF.
|
||||
- [ ] Re-rezolvarea deblocheaza si alte submission-uri `needs_mapping` cu aceeasi operatie (pe `batch_id`).
|
||||
- [ ] Optional: daca userul nu vrea sa salveze, editarea ramane one-off (fara regula). Se compune
|
||||
- [x] Re-rezolvarea deblocheaza si alte submission-uri `needs_mapping` cu aceeasi operatie (pe `batch_id`).
|
||||
- [x] Optional: daca userul nu vrea sa salveze, editarea ramane one-off (fara regula). Se compune
|
||||
cu 5.14 (auto-maparea umple, salvarea din chip ramane fallback-ul uman).
|
||||
- **Verificare E2E**: adaug cod la operatie nemapata + salveaza regula -> al doilea rand cu aceeasi
|
||||
operatie se rezolva automat.
|
||||
@@ -397,10 +397,10 @@ de corectat/zi nu vreau sa intru in fiecare individual.
|
||||
existenta din `_submissions` + `submissions_admin`), `tests/test_web_bulk_fix.py` (~3 fisiere)
|
||||
- **Test intai (RED)**: `tests/test_web_bulk_fix.py` — `test_bulk_remapeaza_selectie`, `test_bulk_doar_blocate`, `test_bulk_scoped_cont`
|
||||
- **Acceptance criteria**:
|
||||
- [ ] Pe randurile blocate (checkbox existent pe `gestionabil`), o actiune bulk noua: aplica un cod
|
||||
- [x] Pe randurile blocate (checkbox existent pe `gestionabil`), o actiune bulk noua: aplica un cod
|
||||
RAR / o remapare la toata selectia intr-o singura cerere (reuse forma `#bulk-trimiteri`).
|
||||
- [ ] Scoped pe cont (404-before-409 ca la bulk-delete); doar randuri blocate eligibile.
|
||||
- [ ] Fiecare rand re-validat + idempotenta recalculata individual (un cod invalid pe un rand nu
|
||||
- [x] Scoped pe cont (404-before-409 ca la bulk-delete); doar randuri blocate eligibile.
|
||||
- [x] Fiecare rand re-validat + idempotenta recalculata individual (un cod invalid pe un rand nu
|
||||
pica tot lotul — sumar "N reusite, M esuate" ca la salvarea mapcoloane D#12).
|
||||
- **Verificare E2E**: selectez 3 randuri needs_mapping + aplic un cod -> toate 3 -> `queued`.
|
||||
- **Verificare E2E**: rulare completa documentata in Raportul VERIFY.
|
||||
@@ -416,12 +416,12 @@ azi GET-urile de listare sunt globale + neprotejate (scurgere VIN/PII cross-cont
|
||||
- **Test intai (RED)**: `test_get_listare_scoped_cont` — un cont NU vede randuri ale altui cont;
|
||||
`test_get_listare_neautentificat_401`; `test_get_detaliu_scoped` (404-before-leak pe id strain).
|
||||
- **Acceptance criteria**:
|
||||
- [ ] GET-urile de listare (trimiteri + orice listare globala) devin account-scoped, refolosind
|
||||
- [x] GET-urile de listare (trimiteri + orice listare globala) devin account-scoped, refolosind
|
||||
mecanismul de scope existent (ca POST-urile + bulk-delete: 404-before-409 pe id strain).
|
||||
- [ ] Un cont nu poate enumera/citi VIN/PII al altui cont prin listare sau detaliu.
|
||||
- [ ] Enforcement aliniat cu `AUTOPASS_REQUIRE_API_KEY` (dev vs prod), fara a rupe contul id=1
|
||||
- [x] Un cont nu poate enumera/citi VIN/PII al altui cont prin listare sau detaliu.
|
||||
- [x] Enforcement aliniat cu `AUTOPASS_REQUIRE_API_KEY` (dev vs prod), fara a rupe contul id=1
|
||||
implicit in dev.
|
||||
- [ ] Actualizeaza nota din CLAUDE.md ("GET-urile de listare ... de remediat") cand e inchis.
|
||||
- [x] Actualizeaza nota din CLAUDE.md ("GET-urile de listare ... de remediat") cand e inchis.
|
||||
- **Verificare E2E**: doua conturi cu trimiteri; contul A nu vede niciun rand al contului B in
|
||||
listare, filtre, paginare sau detaliu.
|
||||
|
||||
@@ -435,11 +435,11 @@ rafinarile mobil (390px) viitoare merita efortul (premisa nevalidata din TODOS 5
|
||||
`tests/test_device_mix.py` (~3 fisiere)
|
||||
- **Test intai (RED)**: `test_device_mix_inregistrat`, `test_device_mix_fara_pii`.
|
||||
- **Acceptance criteria**:
|
||||
- [ ] La acces dashboard, clasifica grosier viewport/UA in desktop/mobil si inregistreaza in
|
||||
- [x] La acces dashboard, clasifica grosier viewport/UA in desktop/mobil si inregistreaza in
|
||||
`app_events` (semnal agregat, FARA PII suplimentar). Reuse tabela existenta — fara migrare
|
||||
daca `app_events` poarta semnalul.
|
||||
- [ ] Un mod simplu de citire a raportului (query/admin), suficient pentru a decide investitia mobil.
|
||||
- [ ] Zero PII nou; aliniat retentiei `app_events` existente.
|
||||
- [x] Un mod simplu de citire a raportului (query/admin), suficient pentru a decide investitia mobil.
|
||||
- [x] Zero PII nou; aliniat retentiei `app_events` existente.
|
||||
- **Verificare E2E**: acces dashboard de pe doua viewport-uri -> doua evenimente clasificate corect.
|
||||
|
||||
## 4. Riscuri
|
||||
@@ -504,8 +504,36 @@ Val 6: [US-008] regresie + E2E final (dupa toate)
|
||||
|
||||
## Raport VERIFY
|
||||
|
||||
> Completat de subagentul verificator (context curat) in faza VERIFY — vezi ROADMAP §5.6.
|
||||
> PASS/FAIL per criteriu, cu dovezi (output pytest citat, E2E pe RAR test). Lipseste pana la VERIFY.
|
||||
Verificator independent (context curat, subagent Sonnet) — 2026-06-28. **VERDICT: PASS** (12/12 stories),
|
||||
cu 1 FAIL documentar remediat de lead + 1 OPEN limitat de mediu.
|
||||
|
||||
- **Suita completa**: `python3 -m pytest -q -m "not live"` → **1230 passed, 1 deselected, 0 failed** (118s).
|
||||
Baseline initial 992 → +238 teste, zero regresii.
|
||||
- **AC per story (US-001..US-012)**: toate PASS cu dovezi (fisier:linie + test care le acopera).
|
||||
Puncte verificate explicit: 7+1 teme cu `--card2`/`--line2` in toate (US-001, DRY `THEMES`);
|
||||
componente slim doar cu tokeni, zero hex (US-002, ancorat pe `SENTINEL-COMPONENTE-SLIM`);
|
||||
strip sanatate D6 + 3 contoare + `sent_today`/`sent_month` bucketate timp local RO `+3 hours` (US-003, E7);
|
||||
lista slim cu filtre/paginare/bulk pastrate (US-004); `obs` editabil + EXCLUS din cheia idempotenta
|
||||
(`idempotency.py:98`) + concat derive-on-empty anti-dublu (US-005, D8/E3); prestatii multi-cod via
|
||||
`getlist` + **E1 IRON RULE** (`cod_op_service` supravietuieste `/repune` — test dedicat) + dedup per-item
|
||||
(US-006, E4); form slim VIN unic + picker chips pe operatie + reveal odo server-driven + select vechi
|
||||
redundant ELIMINAT (US-007/cleanup B); test tema parametrizat 5 tokeni x 7 teme ancorat pe selectori
|
||||
`[data-theme]` (US-008, anti false-green); salvare mapare din chip reuse `save_mapping`+`reresolve_account`
|
||||
(US-009); bulk-fix sumar "N reusite/M esuate" scoped (US-010); account-scope GET-listari 404-before-leak
|
||||
(US-011); device-mix fara PII reuse `app_events` (US-012).
|
||||
- **Fidelitate mockup** (`docs/mockups/prd-5.15-mockups.html`, cod-level): D6 strip, contoare D4,
|
||||
picker E4 cu 2 stari (mapata=chip+×+salveaza / nemapata=select galben "lipsa cod"), reveal odo
|
||||
border-left warn — toate conforme; toate culorile prin `var(--token)`, fara hex.
|
||||
- **Regresia de aur**: testele `POST /v1/prezentari` + worker + import→commit raman verzi in suita;
|
||||
E1 confirmat cu test. Live RAR real (`FINALIZATA`) = opt-in, indisponibil fara creds in sandbox (documentat).
|
||||
|
||||
**FAIL 1 (remediat de lead)**: nota CLAUDE.md "GET-urile de listare globale + neprotejate (de remediat)"
|
||||
nu fusese actualizata (teammates instruiti sa NU atinga CLAUDE.md). **Remediat**: `CLAUDE.md:70` actualizat
|
||||
sa reflecte scope-ul implementat de US-011.
|
||||
|
||||
**OPEN (mediu)**: E2E Playwright pe 390/820/1280 (grafit/hartie/petrol) — browserul MCP a returnat
|
||||
"already in use" in sandbox (ca la livrabilele anterioare). Serverul porneste OK (`/healthz` ok),
|
||||
ACs acoperite functional de pytest (`test_web_responsive.py`). Recomandat: rulat de operator cu browser real.
|
||||
|
||||
---
|
||||
|
||||
|
||||
Reference in New Issue
Block a user