Compare commits

...

2 Commits

Author SHA1 Message Date
Claude Agent
ead63245da prd 3.6 2026-06-19 09:58:24 +00:00
Claude Agent
c8a19e2f06 chore: muta portul implicit 8000 -> 8010 (evita coliziunea cu roa2web)
start.sh, docker-compose.yml, README.md, CLAUDE.md aliniate la 8010
pentru a nu se suprapune cu backend-ul roa2web pe masina de test.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-19 09:11:37 +00:00
6 changed files with 576 additions and 30 deletions

View File

@@ -19,7 +19,7 @@ Limba proiectului este **romana**: cod, comentarii, commit-uri, documentatie. Fa
pip3 install -r requirements.txt # Python 3.12+ pip3 install -r requirements.txt # Python 3.12+
# Rulare locala (dev): API + worker sunt PROCESE SEPARATE # Rulare locala (dev): API + worker sunt PROCESE SEPARATE
uvicorn app.main:app --reload --port 8000 # API: dashboard /, Swagger /docs, /healthz, /metrics uvicorn app.main:app --reload --port 8010 # API: dashboard /, Swagger /docs, /healthz, /metrics
python3 -m app.worker # worker (necesar doar pentru a procesa coada) python3 -m app.worker # worker (necesar doar pentru a procesa coada)
# Wrapper-ul start.sh ambaleaza mediu (test/prod) + rol (api/worker/both/finalizate) # Wrapper-ul start.sh ambaleaza mediu (test/prod) + rol (api/worker/both/finalizate)

View File

@@ -53,9 +53,9 @@ credentialele de test (fisierul **nu** se comite).
### 1. Porneste API-ul ### 1. Porneste API-ul
```bash ```bash
uvicorn app.main:app --reload --port 8000 uvicorn app.main:app --reload --port 8010
# sau, daca uvicorn nu e pe PATH: # sau, daca uvicorn nu e pe PATH:
python3 -m uvicorn app.main:app --reload --port 8000 python3 -m uvicorn app.main:app --reload --port 8010
``` ```
La prima pornire se creeaza schema SQLite si se face seed la nomenclatorul RAR (18 coduri La prima pornire se creeaza schema SQLite si se face seed la nomenclatorul RAR (18 coduri
@@ -75,10 +75,10 @@ python3 -m app.worker
`start.sh` ambaleaza pornirea pe mediu (`test` / `prod`) si rol (`api` / `worker` / `both`): `start.sh` ambaleaza pornirea pe mediu (`test` / `prod`) si rol (`api` / `worker` / `both`):
```bash ```bash
./start.sh test api # API pe :8000, mediu test ./start.sh test api # API pe :8010, mediu test
./start.sh test worker --send # worker care TRIMITE la RAR test ./start.sh test worker --send # worker care TRIMITE la RAR test
./start.sh test both --send # API + worker impreuna (dev end-to-end, loguri in .run/) ./start.sh test both --send # API + worker impreuna (dev end-to-end, loguri in .run/)
./start.sh prod api --port 8000 # API mediu prod ./start.sh prod api --port 8010 # API mediu prod
./start.sh prod worker --send # worker prod (NU foloseste creds de test) ./start.sh prod worker --send # worker prod (NU foloseste creds de test)
./start.sh status # stare procese + /healthz ./start.sh status # stare procese + /healthz
./start.sh stop # opreste procesele pornite cu "both" ./start.sh stop # opreste procesele pornite cu "both"
@@ -110,10 +110,10 @@ Cu API-ul pornit, deschide in browser:
| URL | Ce vezi | | URL | Ce vezi |
|-----|---------| |-----|---------|
| `http://localhost:8000/` | **Dashboard** — stare coada, banner prezentari blocate, stare worker / ultim login RAR, editor mapari operatii, browser nomenclator, sectiune **import fisier** | | `http://localhost:8010/` | **Dashboard** — stare coada, banner prezentari blocate, stare worker / ultim login RAR, editor mapari operatii, browser nomenclator, sectiune **import fisier** |
| `http://localhost:8000/docs` | **Swagger UI** — API v1 interactiv (incearca endpointurile direct din browser) | | `http://localhost:8010/docs` | **Swagger UI** — API v1 interactiv (incearca endpointurile direct din browser) |
| `http://localhost:8000/healthz` | JSON sanatate: worker viu, ultim login RAR, adancime coada | | `http://localhost:8010/healthz` | JSON sanatate: worker viu, ultim login RAR, adancime coada |
| `http://localhost:8000/metrics` | metrici text (submissions pe status) | | `http://localhost:8010/metrics` | metrici text (submissions pe status) |
### Fluxul de import fisier (xlsx / csv) din browser ### Fluxul de import fisier (xlsx / csv) din browser
@@ -179,9 +179,9 @@ end-to-end pe contul de test RAR:
4. **Vizualizeaza prezentarile trimise** — trei feluri: 4. **Vizualizeaza prezentarile trimise** — trei feluri:
- **Dashboard** (`http://localhost:8000/`) — tabelul de jos arata fiecare submission cu - **Dashboard** (`http://localhost:8010/`) — tabelul de jos arata fiecare submission cu
status (`sent`/`error`/...), `id_prezentare`, cod RAR si eroare. Se actualizeaza singur. status (`sent`/`error`/...), `id_prezentare`, cod RAR si eroare. Se actualizeaza singur.
- **API**: `curl -s http://localhost:8000/v1/prezentari` — coada locala cu statusuri. - **API**: `curl -s http://localhost:8010/v1/prezentari` — coada locala cu statusuri.
- **Direct de la RAR** (confirmare independenta ca au ajuns): - **Direct de la RAR** (confirmare independenta ca au ajuns):
```bash ```bash
@@ -285,7 +285,7 @@ Ca worker-ul sa poata trimite pentru un service fara ca fiecare cerere sa-i poar
RAR, seteaza credentialele RAR durabile pe cont (criptate Fernet at-rest): RAR, seteaza credentialele RAR durabile pe cont (criptate Fernet at-rest):
```bash ```bash
curl -s -X POST http://localhost:8000/v1/conturi/rar-creds \ curl -s -X POST http://localhost:8010/v1/conturi/rar-creds \
-H 'X-API-Key: rfak_...' -H 'Content-Type: application/json' \ -H 'X-API-Key: rfak_...' -H 'Content-Type: application/json' \
-d '{"email": "service@exemplu.ro", "password": "parola-rar"}' -d '{"email": "service@exemplu.ro", "password": "parola-rar"}'
``` ```
@@ -298,16 +298,16 @@ obligatorie.
```bash ```bash
# Sanatate (neprotejat) # Sanatate (neprotejat)
curl -s http://localhost:8000/healthz | python3 -m json.tool curl -s http://localhost:8010/healthz | python3 -m json.tool
# Nomenclator RAR (cache local) # Nomenclator RAR (cache local)
curl -s http://localhost:8000/v1/nomenclator curl -s http://localhost:8010/v1/nomenclator
# Coada de prezentari (monitorizare; momentan globala + neprotejata, vezi nota de mai sus) # Coada de prezentari (monitorizare; momentan globala + neprotejata, vezi nota de mai sus)
curl -s http://localhost:8000/v1/prezentari curl -s http://localhost:8010/v1/prezentari
# Trimite o prezentare -- dev (fara cheie API -> cont id=1) # Trimite o prezentare -- dev (fara cheie API -> cont id=1)
curl -s -X POST http://localhost:8000/v1/prezentari \ curl -s -X POST http://localhost:8010/v1/prezentari \
-H 'Content-Type: application/json' \ -H 'Content-Type: application/json' \
-d '{ -d '{
"rar_credentials": {"email": "test@example.ro", "password": "secret"}, "rar_credentials": {"email": "test@example.ro", "password": "secret"},
@@ -321,7 +321,7 @@ curl -s -X POST http://localhost:8000/v1/prezentari \
}' }'
# Trimite o prezentare -- service cu cheie API (account_id curge din cheie) # Trimite o prezentare -- service cu cheie API (account_id curge din cheie)
curl -s -X POST http://localhost:8000/v1/prezentari \ curl -s -X POST http://localhost:8010/v1/prezentari \
-H 'X-API-Key: rfak_...' -H 'Content-Type: application/json' \ -H 'X-API-Key: rfak_...' -H 'Content-Type: application/json' \
-d '{ -d '{
"rar_credentials": {"email": "service@exemplu.ro", "password": "parola-rar"}, "rar_credentials": {"email": "service@exemplu.ro", "password": "parola-rar"},
@@ -335,7 +335,7 @@ curl -s -X POST http://localhost:8000/v1/prezentari \
}' }'
# Import fisier prin API pentru un service (multi-tenant: contul vine din cheie) # Import fisier prin API pentru un service (multi-tenant: contul vine din cheie)
curl -s -X POST http://localhost:8000/v1/import \ curl -s -X POST http://localhost:8010/v1/import \
-H 'X-API-Key: rfak_...' -F 'file=@sample_import.xlsx' -H 'X-API-Key: rfak_...' -F 'file=@sample_import.xlsx'
``` ```
@@ -344,16 +344,16 @@ pune cheia prin butonul "Authorize" sau adauga header-ul `X-API-Key`.
```bash ```bash
# Sanatate # Sanatate
curl -s http://localhost:8000/healthz | python3 -m json.tool curl -s http://localhost:8010/healthz | python3 -m json.tool
# Nomenclator RAR (cache local) # Nomenclator RAR (cache local)
curl -s http://localhost:8000/v1/nomenclator curl -s http://localhost:8010/v1/nomenclator
# Coada de prezentari # Coada de prezentari
curl -s http://localhost:8000/v1/prezentari curl -s http://localhost:8010/v1/prezentari
# Trimite o prezentare (dev: fara cheie API -> cont id=1) # Trimite o prezentare (dev: fara cheie API -> cont id=1)
curl -s -X POST http://localhost:8000/v1/prezentari \ curl -s -X POST http://localhost:8010/v1/prezentari \
-H 'Content-Type: application/json' \ -H 'Content-Type: application/json' \
-d '{ -d '{
"rar_credentials": {"email": "test@example.ro", "password": "secret"}, "rar_credentials": {"email": "test@example.ro", "password": "secret"},
@@ -389,7 +389,7 @@ cp .env.example .env
docker compose up --build docker compose up --build
``` ```
`docker-compose.yml` porneste trei containere: `api` (port 8000), `worker` si `autoheal` `docker-compose.yml` porneste trei containere: `api` (port 8010), `worker` si `autoheal`
(restarteaza worker-ul cand heartbeat-ul devine invechit). Ambele servicii folosesc acelasi (restarteaza worker-ul cand heartbeat-ul devine invechit). Ambele servicii folosesc acelasi
image si acelasi volum SQLite persistent. image si acelasi volum SQLite persistent.

View File

@@ -8,9 +8,9 @@
services: services:
api: api:
build: . build: .
command: uvicorn app.main:app --host 0.0.0.0 --port 8000 command: uvicorn app.main:app --host 0.0.0.0 --port 8010
ports: ports:
- "8000:8000" - "8010:8010"
volumes: volumes:
- autopass-data:/data - autopass-data:/data
environment: environment:
@@ -20,7 +20,7 @@ services:
AUTOPASS_REQUIRE_API_KEY: ${AUTOPASS_REQUIRE_API_KEY:-false} AUTOPASS_REQUIRE_API_KEY: ${AUTOPASS_REQUIRE_API_KEY:-false}
restart: always restart: always
healthcheck: healthcheck:
test: ["CMD", "python", "-c", "import urllib.request,sys; sys.exit(0 if urllib.request.urlopen('http://localhost:8000/healthz').status==200 else 1)"] test: ["CMD", "python", "-c", "import urllib.request,sys; sys.exit(0 if urllib.request.urlopen('http://localhost:8010/healthz').status==200 else 1)"]
interval: 30s interval: 30s
timeout: 5s timeout: 5s
retries: 3 retries: 3

View File

@@ -0,0 +1,543 @@
<!-- /autoplan restore point: /home/claude/.gstack/projects/romfast-rar-autopass/main-autoplan-restore-20260619-093652.md -->
# PRD 3.6 — Editare celule in preview + Acasa unificata (Trimiteri inline, upload slim, Mapari tabelar)
**Stare**: aprobat (post-autoplan 2026-06-19 — raport de review la finalul fisierului; stories revizuite cu fix-urile aplicate)
> Proces complet: `docs/ROADMAP.md` §5. Contract RAR (sursa de adevar): `docs/api-rar-contract.md`.
> Starea trece: `draft → aprobat → in-executie → verify-pass → inchis` (actualizata de lead).
> Continua 3.5 ([prd-3.5](prd-3.5-dashboard-compact-trimiteri-mapari.md)). **Backend trimitere (worker,
> masina stari, idempotenta-logica, mapping-rezolvare) NU se atinge** — doar canalul de import si stratul web.
> Exceptie schema decisa la poarta autoplan: UNA coloana nullable `import_rows.override_json` (Approach B), cu
> migrare defensiva `_migrate` ca la 3.3b/3.5. Worker/idempotenta/mapare raman neatinse.
## 1. Obiectiv
Dupa upload, utilizatorul putea citi randurile cu probleme dar nu le putea **corecta inainte de trimitere**
singura editare de continut era post-confirmare, in alt tab. Livram: (a) editare de celule direct in preview
(buton "Editeaza" pe rand), si (b) reunificarea fluxului pe pagina **Acasa** — Trimiterile devin o sectiune
permanenta sub upload (tab-ul "Trimiteri" dispare), zona de upload se comprima la o bara slim, iar "Mapari"
trece pe format tabelar compact cu eticheta auto-send reformulata ca un comutator Automat/Manual.
Motivatie: microcopia spune "vezi mai jos trimiterile", dar trimiterile erau intr-un alt tab; cazul real de
utilizare (fisier cu o data/VIN lipsa sau gresit) cerea re-upload in loc de o corectie pe loc.
## 2. Non-Goals (anti scope-creep)
- **Fara editare a operatiei/codului RAR in celula** — codul de operatie ramane rezolvat prin panoul de mapare
existent (`needs_mapping`). Editarea de celule acopera campurile de continut: VIN, nr. inmatriculare,
data prestatie, odometru initial/final. (Operatia se schimba din panoul de mapare, nu din tabel.)
- **Fara editare in bloc / multi-rand** — un rand pe rand, mod editare explicit.
- **Schema: o singura coloana noua** — `import_rows.override_json` (nullable, criptat Fernet), patch CANONIC peste
randul mapat (Approach B). NU se modifica `raw_json` (ramane cheiat pe anteturile fisierului). Restul schemei neatins.
- **Fara atingerea logicii de idempotenta, validare, mapare sau a worker-ului** — editarea = mutatie pura de stocare;
recalculul de stare merge OBLIGATORIU prin `_resolve_row_for_preview` (un singur clasificator, fara drift).
- **Fara reorganizarea tab-urilor ramase** (Mapari/Cont/Nomenclator) dincolo de scoaterea tab-ului Trimiteri.
- **Fara paginare noua pe Trimiteri** — sectiunea de pe Acasa refoloseste filtrarea existenta (US-009 din 3.5).
## 3. Stories atomice
> Backend + UI pentru acelasi comportament = stories separate. Toate rutele web noi sunt sub `require_login`
> si **scoped pe contul din sesiune** (gard cross-account 404, identic cu rutele existente). CSRF pe toate POST-urile.
### US-001: Backend — persista editarea unui rand de preview
**Ca** utilizator care vede un rand cu date gresite/lipsa in preview **vreau** sa salvez valori corectate
pentru acel rand **pentru ca** sa-l trimit fara re-upload de fisier.
- **Depinde de**: —
- **Fisiere**: `app/schema.sql` (coloana `override_json` + `_migrate`), `app/api/v1/import_router.py`
(`_resolve_row_for_preview` + `commit_import` aplica override; ruta noua), `tests/test_import_edit_row.py`,
`tests/fixtures/import_antet_necanonic.csv` (NOU), `tests/fixtures/import_lipsa_coloana.csv` (NOU) (~5 fisiere)
- **Stocare (Approach B, decis la poarta autoplan):** editarea scrie un dict CANONIC in `import_rows.override_json`
(nullable, criptat Fernet). `_resolve_row_for_preview` si `commit_import` aplica `mapped.update(override_json)`
ULTIMUL, dupa maparea `json_mapare`. Astfel se poate completa si un camp a carui coloana LIPSESTE din fisier
(cazul "completez informatii lipsa"), iar `raw_json`/idempotency raman neatinse.
- **Test intai (RED)**: `tests/test_import_edit_row.py`
`test_editeaza_rand_antet_necanonic_devine_ok` (fixture cu antet `Serie sasiu`/`Data` — prinde bug-ul de stocare),
`test_editeaza_completeaza_coloana_absenta` (fisier fara coloana data → editarea adauga data → ok),
`test_editeaza_status_identic_cu_GET_preview` (ruta editare NU re-deriva status; egal cu `GET /preview`),
`test_editeaza_rand_scoped_alt_cont_404`, `test_editeaza_batch_inexistent_404`,
`test_editeaza_row_index_invalid_pe_batch_valid_404`,
`test_editeaza_pastreaza_campuri_neatinse` (operatie/prestatii raman),
`test_editeaza_batch_committed_409` (guard post-commit),
`test_editeaza_raw_corupt_no_op` (decrypt fail → 422/no-op, fara crash),
`test_editeaza_empty_input_sterge_campul` (semantica empty = CLEAR, pentru cazul "corectez o valoare gresita").
- **Acceptance criteria**:
- [ ] Migrare: `import_rows.override_json TEXT` (nullable), `_migrate` defensiv (idempotent, ca `is_admin` in 3.3b).
- [ ] Ruta `POST /v1/import/{import_id}/rand/{row_index}/editeaza` (`resolve_account_id`) + alias web
`POST /_import/{import_id}/rand/{row_index}/editeaza` (`require_login`). Campuri: `vin`, `nr_inmatriculare`,
`data_prestatie`, `odometru_initial`, `odometru_final`.
- [ ] **Mutatie pura**: decripteaza `override_json` curent (sau {}), aplica campurile (vezi semantica empty), re-cripteaza,
`UPDATE`. NU recalculeaza statusul in ruta — preview-ul il rederiva via `_resolve_row_for_preview`.
- [ ] **Semantica empty**: input gol = STERGE cheia din override (revine la valoarea din fisier daca exista). Documentat + testat.
- [ ] **Scoping intr-o singura interogare**: `import_rows r JOIN import_batches b ON b.id=r.batch_id
WHERE b.id=? AND b.account_id=? AND r.row_index=?` → 404 pe gol (acopera alt cont, batch inexistent, row_index invalid).
- [ ] **Guard committed**: batch cu `status='committed'` → 409 (editarea n-ar avea efect downstream).
- [ ] `decrypt_creds` → None/exceptie → 422/no-op defensiv (ca import_router.py:602-606), niciodata scriere goala.
- [ ] Coercion: nu se afirma `canonicalize_row` pe `odometru_initial` (normeaza doar `_final`); validarea (`_parse_int`)
tolereaza ".0" — testul verifica prin validare, nu prin canonicalize.
- [ ] NU atinge `submissions`.
- **Verificare E2E**: TestClient — (a) fixture cu antet ne-canonic, rand needs_data → editeaza → preview = `ok`;
(b) fixture fara coloana data → editeaza data → `ok`.
### US-002: UI — buton "Editeaza" pe rand in tabelul de preview
**Ca** utilizator **vreau** sa pun un rand in mod editare si sa-i corectez celulele pe loc **pentru ca**
sa nu reincarc tot fisierul pentru o singura valoare.
- **Depinde de**: US-001
- **Fisiere**: `app/web/templates/_preview_import.html`, `app/web/templates/_preview_rand.html` (NOU — fragment rand),
`app/web/routes.py` (handler fragment rand), `tests/test_preview_edit_ui.py`
- **Test intai (RED)**: `tests/test_preview_edit_ui.py` —
`test_preview_are_buton_editeaza_pe_rand`,
`test_editeaza_intra_in_mod_editare_form_propriu` (randul devine FORM separat, NU in `#confirm-form`),
`test_salveaza_reda_doar_randul` (raspuns = fragment rand + OOB contoare, NU tot `#import-section`),
`test_enter_in_camp_editare_nu_declanseaza_confirm`,
`test_eroare_validare_pastreaza_valorile_introduse` (data invalida → ramane in editare, mesaj pe camp).
- **Acceptance criteria**:
- [ ] Fiecare rand are un buton "Editeaza" (coloana de actiuni la final).
- [ ] **Swap pe rand, NU pe sectiune (D-3.1):** Editeaza/Salveaza tintesc randul `<tr>` (`hx-target` pe rand,
`hx-swap="outerHTML"`), iar rezumatul + bara de confirmare se actualizeaza prin **OOB swap**. NU se re-randeaza
`#import-section` (altfel se pierd bifele `reviewed_rows`, `n_confirmat`, filtrul activ, alte randuri in editare).
- [ ] **Form propriu (D-3.3):** input-urile de editare stau intr-un `<form>` separat (sau `form=` attribute),
NU in `#confirm-form`. Butonul "Trimite la RAR" e DEZACTIVAT cat un rand e in mod editare. Enter intr-un camp
de editare salveaza randul, niciodata nu declanseaza confirmarea (ireversibila).
- [ ] **Refolosire `_trimitere_detaliu.html` (DRY, rezolva mobil+eroare):** modul de editare foloseste aceeasi grila
responsiva `repeat(auto-fit, minmax(200px,1fr))` + error-map + scroll-to-row ca formul de corectie existent.
Pe viewport ingust randul devine card stacked (label deasupra input), nu celule in scroll orizontal.
- [ ] **Stari (D-2.1/D-2.2):** `hx-indicator` "se salveaza…" pe rand + butoane dezactivate in timpul cererii;
`hx-on::response-error` pastreaza randul + valorile introduse + banner ne-distructiv la 500/CSRF/timeout.
- [ ] **Mutual-exclusion (D-3.2/D-3.6):** cat un rand e in editare, butoanele Editeaza ale celorlalte randuri sunt
dezactivate; "Incarca alt fisier" / schimbarea de tab cu editare dirty cer confirmare (sau auto-cancel cu toast).
- [ ] La eroare de validare a valorii, randul ramane in editare cu mesajul pe campul vinovat (tipar `corectie_errors`).
- [ ] Dupa Salveaza: scroll la + evidentiaza randul editat (reuse scriptul de outline din `_trimitere_detaliu.html`).
- [ ] "Anuleaza" inchide editarea fara scriere. Accesibil: butoane min 44px, `aria-label` per camp cu nr. rand + VIN.
- **Verificare E2E**: browser HTMX pe `/` (Playwright MCP) — upload fixture cu un rand fara data → Editeaza →
completeaza data → Salveaza → DOAR randul se schimba pe `ok`, contorul "gata de trimis" creste, bifele altui rand raman.
### US-003: Acasa unificata — Trimiteri ca sectiune permanenta, fara tab "Trimiteri"
**Ca** utilizator **vreau** sa vad trimiterile pe aceeasi pagina cu upload-ul **pentru ca** "vezi mai jos
trimiterile" sa fie adevarat si sa nu navighez intre tab-uri.
- **Depinde de**: —
- **Fisiere**: `app/web/templates/dashboard.html`, `app/web/templates/_acasa.html`, `app/web/routes.py`,
`tests/test_acasa_trimiteri.py`
- **Test intai (RED)**: `tests/test_acasa_trimiteri.py` —
`test_tab_bar_fara_trimiteri`,
`test_acasa_contine_sectiunea_trimiteri` (tabel + filtre),
`test_tab_coada_redirect_la_acasa` (`?tab=coada` nu da 404, serveste Acasa),
`test_acasa_fara_linkuri_ajutor` (randul de linkuri catre tab-uri eliminat),
`test_badge_trimiteri_scoped_pe_acasa`.
- **Test intai (RED)** (in plus): `test_fragment_coada_serveste_acasa` (fragmentul, nu doar pagina),
`test_sectiune_trimiteri_are_heading` ("Trimiterile tale"), `test_acasa_pastreaza_wayfinding_mapari_coduri`.
- **Acceptance criteria**:
- [ ] Tab-ul "Trimiteri" (`coada`) eliminat din `tab-bar`; raman Acasa·Mapari·Cont·Nomenclator.
- [ ] `_acasa.html` randeaza, sub upload + primii pasi, sectiunea Trimiteri completa cu heading **"Trimiterile tale"**
(divizor vizual fata de upload): filtrele (US-009), tabelul (`_submissions.html`), panoul `#trimitere-detaliu`.
- [ ] **Un singur sticky bar pe ecran (D-1.2):** cat un preview de import e activ (`#import-section` randat),
sectiunea Trimiteri e ascunsa/colapsata; dupa commit, se reveleaza si scroll la ea. Nu coexista doua bare sticky.
- [ ] **Wayfinding pastrat (D-5.3):** se scoate DOAR linkul redundant "Trimiteri"; "Mapari" si "Coduri RAR" raman
ca o linie de ajutor discreta (utile pentru operatori non-tehnici).
- [ ] `GET /?tab=coada` **si** `GET /_fragments/coada` nu dau 404 — ambele servesc continutul Acasa (fragmentul nu mai
randeaza `_coada.html` orfan; `_coada.html` sters/repurposat).
- [ ] **Anti dublu-poll (M5):** poll-ul de trimiteri din sectiune are `hx-trigger` gated pe
`document.visibilityState==='visible'` (sau aliniat la 15s ca status-ul) — nu doua timere perpetue pe pagina mereu deschisa.
- [ ] Contorul de atentie (badge) se reflecta in heading-ul sectiunii, nu pe un tab disparut.
- [ ] Microcopia post-confirmare tinteste sectiunea "Trimiterile tale" de pe Acasa.
- **Verificare E2E**: browser pe `/` — dupa confirmarea unui import, trimiterile apar in aceeasi pagina sub upload, cu heading;
click pe rand blocat deschide corectia inline; `?tab=coada` si `/_fragments/coada` servesc Acasa.
### US-004: UI — zona de upload comprimata la o bara slim
**Ca** utilizator **vreau** un upload care ocupa putin spatiu **pentru ca** trimiterile de sub el sa fie vizibile fara scroll.
- **Depinde de**: US-003 (pentru a avea ce sta sub bara)
- **Fisiere**: `app/web/templates/_upload.html`, `tests/test_upload_slim.py`
- **Test intai (RED)**: `tests/test_upload_slim.py` —
`test_upload_slim_pe_un_rand` (markup compact, fara caseta mare drop-zone dominanta),
`test_upload_pastreaza_drag_drop_si_input` (input file + handler drag-drop raman),
`test_upload_pastreaza_select_foaie` (cazul multi-sheet inca functioneaza).
- **Acceptance criteria**:
- [ ] Upload-ul devine o bara pe un rand: eticheta "Importa:" + buton "Alege fisier (xlsx/csv)" + zona "sau trage aici",
microcopy scurt ("NU se trimite nimic pana confirmi" pastrat, dar discret).
- [ ] **Slim DAR accentuat (D-1.1/D-5.2):** bara pastreaza un tratament distinct (border/fundal de accent) ca sa ramana
punctul de intrare evident chiar cu un tabel lung dedesubt.
- [ ] **First-run pastreaza hero (D-5.1):** daca `not are_trimiteri`, bara ramane usor mai inalta cu copy-ul
"Primul fisier? Trage-l aici."; se colapseaza la slim doar dupa ce contul are trimiteri. Empty-state-ul redundant
al tabelului Trimiteri se suprima cand sunt zero trimiteri (bara de upload acopera deja CTA-ul).
- [ ] Drag-drop pe bara si `input[type=file]` ascuns raman functionale (JS-ul existent refolosit).
- [ ] Cazul multi-sheet (select foaie) inca apare cand fisierul are mai multe foi.
- **Verificare E2E**: browser pe `/` — bara de upload ocupa ~1 rand cand exista trimiteri; first-run pastreaza hero-ul.
### US-005: UI — Mapari "De rezolvat" + "Operatii salvate" ca tabel
**Ca** utilizator cu multe mapari **vreau** sa le vad tabelar **pentru ca** stiva de carduri/forme ocupa prea mult loc.
- **Depinde de**: —
- **Fisiere**: `app/web/templates/_mapari.html`, `tests/test_mapari_tabel.py`
- **Test intai (RED)**: `tests/test_mapari_tabel.py` —
`test_mapari_de_rezolvat_in_tabel`,
`test_mapari_salvate_in_tabel`,
`test_mapari_salvare_si_stergere_inca_functioneaza` (POST-urile `/mapari`, `/mapari/salvate`, `/mapari/salvate/sterge` neschimbate).
- **Acceptance criteria**:
- [ ] Sectiunea "De rezolvat" (operatii `needs_mapping`) randata ca tabel: coloane operatie/denumire + nr. blocate,
sugestii, select cod RAR, comutator (US-007), actiune Salveaza — un rand de tabel per operatie.
- [ ] Sectiunea "Mapari operatii salvate" randata ca tabel cu aceleasi coloane + Sterge.
- [ ] **Starea stocata redata (H4):** comutatorul (US-007) din tabelul salvate reflecta valoarea `auto_send` STOCATA
per mapare (din `_load_saved_op_mappings`, routes.py:738), nu un default hard "Automat".
- [ ] Comportamentul existent neschimbat: re-rezolvare automata a blocatelor la salvare/edit cod; endpoints identice; csrf.
- [ ] Tabelele folosesc `.tablewrap` (scroll orizontal pe mobil) ca Trimiteri.
- **Verificare E2E**: browser pe tab Mapari — operatiile nemapate si cele salvate apar ca tabele; salvare + stergere OK.
### US-006: UI — Formate de coloane ca tabel
**Ca** utilizator **vreau** formatele de coloane salvate ca tabel **pentru ca** sa le compar dintr-o privire.
- **Depinde de**: —
- **Fisiere**: `app/web/templates/_mapari.html`, `tests/test_formate_tabel.py`
- **Test intai (RED)**: `tests/test_formate_tabel.py` —
`test_formate_coloane_in_tabel`,
`test_formate_editare_data_si_stergere_inca_functioneaza`.
- **Acceptance criteria**:
- [ ] Sectiunea "Formate de coloane salvate" randata ca tabel: nr. coloane / maparile col→camp / format data (editabil) / Sterge.
- [ ] POST-urile `/formate-coloane/editeaza` si `/formate-coloane/sterge` neschimbate; csrf pastrat.
- [ ] `.tablewrap` pentru consistenta cu celelalte tabele.
- **Verificare E2E**: browser pe tab Mapari — formatele apar ca tabel; editarea formatului de data + stergerea functioneaza.
### US-007: UI — comutator pe COADA in loc de bifa "auto-send" (framing sigur, nu "Manual/trimitere")
**Ca** operator **vreau** sa stiu clar ce se intampla cu o operatie la fisierele viitoare **pentru ca**
"auto-send" e jargon, iar "Manual/Automat" suna ca si cum sistemul ar trimite singur la RAR (fals — periculos).
> **Decis la poarta autoplan (UC-A):** framing pe **punerea in coada**, NU pe trimitere. Toti reviewerii (CEO/Eng/Design):
> "Automat/Manual" citit global peste declaratii ireversibile = risc de send-safety. Etichetele poarta singure sensul.
- **Depinde de**: US-005 (acelasi markup de mapari)
- **Fisiere**: `app/web/templates/_mapari.html`, `app/web/templates/_preview_import.html`, `tests/test_autosend_toggle.py`
- **Test intai (RED)**: `tests/test_autosend_toggle.py` —
`test_comutator_coada_prezent` (textul contine "in coada" / "verificare", NU "trimite"/"Manual" gol),
`test_eticheta_scoped_pe_operatie` (microcopy contine "aceasta operatie"),
`test_pune_automat_mapeaza_auto_send_true`,
`test_tine_pentru_verificare_mapeaza_auto_send_false`,
`test_default_pune_automat` (comportament identic cu `checked` de azi).
- **Acceptance criteria**:
- [ ] Bifa `auto_send` inlocuita cu un comutator cu doua stari, etichetat pe COADA, in: panoul de mapare din preview
(`_preview_import.html`) si ambele locuri din `_mapari.html`. Antet: **"La fisierele viitoare cu aceasta operatie:"**
Optiuni: **"Pune automat in coada"** / **"Tine pentru verificare"**.
- [ ] Microcopy scoped pe operatie (NU global): "...doar pentru aceasta operatie; nimic nu pleaca la RAR pana confirmi."
Niciun cuvant "Manual"/"trimite" izolat care sa implice bypass al confirmarii RAR. Caption prezent si in preview
(azi checkbox-ul din preview nu are caption).
- [ ] Maparea valoare→backend pastreaza semantica `auto_send` existenta ("Pune automat"=true, "Tine"=false);
default **"Pune automat in coada"** (mirror la `checked` de azi).
- [ ] **`name="auto_send"` pastrat** cu `value`-uri ce produc bool corect — zero atingere backend (cale aleasa).
- **Verificare E2E**: browser — salvez o operatie pe "Tine pentru verificare" → la urmatorul import randul cu acea operatie
ramane blocat (nu intra automat in coada); pe "Pune automat in coada" → intra in coada (tot cu gate-ul de confirmare la trimitere).
## 4. Riscuri
- **Stocare editare (REZOLVAT prin Approach B)**: `raw_json` e cheiat pe anteturile fisierului; o editare pe cheie
canonica ar fi ignorata pe fisiere cu antet ne-canonic, iar un camp fara coloana-sursa n-ar putea fi completat.
Mitigare: `override_json` (patch canonic aplicat ultimul). Test obligatoriu cu fixture antet ne-canonic + coloana absenta.
- **Swap pe rand vs sectiune (REZOLVAT)**: editarea tinteste randul + OOB contoare, NU `#import-section`; form propriu;
confirm dezactivat la editare. Previne pierderea starii si Enter→trimitere ireversibila.
- **Dublu-poll pe Acasa**: status 15s + trimiteri 10s pe pagina mereu deschisa. Mitigare: poll-ul de trimiteri gated pe
`document.visibilityState` (sau 15s). Nu doua timere perpetue.
- **`?tab=coada` + `/_fragments/coada` vechi**: fallback la continutul Acasa (nu 404, nu fragment orfan).
- **Export "randuri cu probleme"**: trebuie sa reflecte valorile editate. Recalculul prin `_resolve_row_for_preview`
(cu override aplicat) => export consistent automat.
- **Recalcul preview pe fisier mare**: cu swap pe rand, doar randul + contoarele se re-randeaza (nu toate 5000).
## 5. Intrebari deschise
> Rezolvate cu utilizatorul inainte de executie (poarta de aprobare PRD). Toate cele de mai jos sunt **inchise**
> prin sesiunea de planificare (AskUserQuestion 2026-06-19):
- Editare preview: **buton "Editeaza" pe rand** (mod editare explicit pe rand), nu click-pe-celula. [INCHIS]
- Trimiteri pe Acasa: **mutare completa**, tab-ul "Trimiteri" eliminat (linkul redundant "Trimiteri" scos;
wayfinding "Mapari"/"Coduri RAR" pastrat). [INCHIS]
- Upload: **bara slim pe un rand**, accentuata + hero pastrat la first-run. [INCHIS]
- auto-send: framing pe **coada** ("Pune automat in coada" / "Tine pentru verificare"), NU "Automat/Manual" global. [INCHIS — poarta autoplan UC-A]
- Stocare editare: **Approach B** (`override_json`), relaxeaza Non-Goal-ul de schema. [INCHIS — poarta autoplan]
- Structura: **un singur PRD 3.6, valuri secventiate** (Acasa intai, editare dupa storage). [INCHIS — poarta autoplan]
## 6. Valuri de executie (secventiate post-autoplan)
```
Val 1 (Acasa unificata — livreaza 80% din valoare): [US-003] [US-004]
Val 2 (Editare preview — dupa storage redesign B): [US-001] → [US-002]
Val 3 (Cosmetic mapari + toggle sigur): [US-005] → [US-006] [US-007]
```
> Secventiere ceruta la poarta autoplan: US-003 (+US-004 ca sub-task, ii face loc) intai — fixeaza microcopia care
> minte azi. US-001/002 dupa ce `override_json` (Approach B) e in loc. US-005 si US-007 ating `_mapari.html`/`_preview_import.html`
> impreuna — acelasi worker (sectiuni distincte). Regula de aur: la fiecare val, regresia `pytest -q` verde + bifare in PRD.
---
## 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.
---
# AUTOPLAN REVIEW — APROBAT (2026-06-19)
> Pipeline /autoplan: CEO -> Design -> Eng. Mod: SELECTIVE EXPANSION. Vocile: codex `[indisponibil — usage limit]`
> + subagent Claude independent per faza. 12 corecturi auto-decise (audit trail mai jos) + 3 decizii la poarta,
> toate aprobate de user: (UC-A) US-007 framing pe coada; (poarta) Approach B `override_json`; (poarta) un PRD,
> valuri secventiate. **Stories revizuite cu toate fix-urile** (sectiunile 2-6 de mai sus reflecta decizia finala).
## FAZA 1 — CEO (strategie & scope)
### 0A. Premise Challenge
- **Premisa centrala** ("operatorii trebuie sa corecteze valori in preview, inainte de trimitere"):
VALIDA si confirmata de durerea reala — azi un VIN/data lipsa cere re-upload de fisier intreg.
- **Premisa de implementare** ("editarea = suprascriere `import_rows.raw_json`"): SUSPECTA.
`raw_json` e cheiat pe **numele coloanelor din fisier** (antet original), nu pe campuri canonice;
`_resolve_row_for_preview` (import_router.py:138-140) si commit-ul (import_router.py:922-945) re-aplica
`json_mapare` peste raw_json. O editare care scrie cheia canonica `vin` e ignorata pe orice fisier al
carui antet difera de numele canonic. Fixture-ul `tests/fixtures/test_data_mapping.csv` are din intamplare
antet = nume canonice -> testele ar trece, realul ar esua tacit. (vezi Eng pentru fix.)
- **Premisa "completez informatiile lipsa"**: cazul in care campul lipsa e o **coloana inexistenta in fisier**
nu are `col_f` in care sa scrie -> raw_json nu poate exprima valoarea. Premisa promisa userului depaseste
ce poate face stocarea actuala fara un strat de override. (escaladata la poarta — vezi UC-1.)
- **Premisa "muta Trimiteri pe Acasa"**: VALIDA — microcopia "vezi mai jos trimiterile" deja minte azi.
- **Premisa de impachetare** (4 schimbari intr-un PRD): cele 4 NU sunt cuplate tehnic. Singura legatura e
"incap pe ecranul Acasa". Risc de scope: editarea celulelor (cu fix-ul de stocare) e de departe cea mai
riscanta; restul sunt cosmetice. (vezi 0F + decizie de gust DG-1: split.)
### 0B. Existing Code Leverage (ce exista deja)
| Sub-problema | Cod existent care o rezolva partial/total |
|---|---|
| Editare campuri de continut + re-validare + re-enqueue | **US-010 corectie inline** (`routes.py:583` `post_corectie_trimitere`, `_trimitere_detaliu.html:44-84`) — exact aceleasi campuri (vin/nr/data/odo), validare, detectie coliziune idempotency. **US-001/002 trebuie sa REFOLOSEASCA acest tipar, nu sa-l reconstruiasca.** |
| Tabele compacte | `.tablewrap` + `_submissions.html` (deja tabel cu scroll orizontal) |
| Sectiunea Trimiteri pe Acasa | `_submissions.html` + filtre US-009 + `#trimitere-detaliu` — se includ ca atare |
| Upload drag-drop | JS din `_upload.html:74-107` — se pastreaza, doar markup-ul se comprima |
| Re-randare preview cu stare recalculata | `web_preview_import` + `_resolve_row_for_preview` — editarea doar schimba sursa |
Concluzie: PRD-ul e in mare parte re-cablare a unor fluxuri existente. Singurul cod nou real e ruta de
editare (US-001) + modul-editare pe rand (US-002). Bun pentru DRY, dar US-001 NU trebuie sa-si scrie propria
logica de validare/idempotency — o are in `corectie`.
### 0C. Dream State
```
CURRENT THIS PLAN 12-MONTH IDEAL
Import = ecran read-only; Editezi randul in preview; Import = foaie editabila live,
Trimiteri in alt tab; --> Trimiteri sub upload pe Acasa; --> mapare AI (Etapa 4.1),
corectie doar post-trimitere; un singur ecran operational o singura suprafata, zero re-upload
```
Planul muta sistemul CATRE idealul "o singura suprafata operationala". Aliniat.
### 0C-bis. Implementation Alternatives (pentru editarea de celule — nucleul riscant)
```
APPROACH A: Suprascriere raw_json cu reverse-map canonical->col_fisier
Summary: ruta editare mapeaza campul canonic inapoi la coloana de fisier via json_mapare, scrie raw_json.
Effort: M Risk: High
Pros: fara schema noua (respecta Non-Goal); o singura sursa de adevar
Cons: NU poate completa un camp a carui coloana lipseste din fisier (cazul "informatii lipsa" real);
reverse-map ambiguu daca doua coloane mapeaza pe acelasi camp; fragil
APPROACH B: Coloana noua import_rows.override_json (patch canonic), aplicata ultima in resolver+commit
Summary: editarea scrie un dict canonic de override; resolver si commit il suprapun peste mapped.
Effort: M Risk: Low
Pros: exprima ORICE camp, inclusiv cele absente din fisier; nu atinge semantica raw_json/idempotency;
migrare defensiva _migrate ca la PRD-urile 3.3b/3.5
Cons: incalca Non-Goal "fara modificari schema" (1 ALTER, mic); 2 puncte unde se aplica override-ul
APPROACH C: Fara editare in preview — extinde US-010 corectie sa fie accesibila si pre-commit
Summary: confirma intai, apoi corecteaza in Trimiteri (fluxul existent), fara editare in preview.
Effort: S Risk: Low
Cons: contrazice decizia explicita a userului (editare IN preview); pune o trimitere "rea" in coada intai
```
**RECOMMENDATION: Approach B** — singurul care onoreaza promisiunea "completez informatii lipsa" (campuri
absente din fisier), cu risc mic si fara sa atinga idempotency/worker. Costul: relaxarea Non-Goal-ului de schema
(1 coloana nullable + migrare defensiva). -> poarta finala UC-1.
### 0D. Selective Expansion — complexity check
- 7 stories, ~8 fisiere atinse, 0 clase/servicii noi. Sub pragul de smell (8 fisiere / 2 clase). OK.
- Minimum care livreaza valoare: US-001+US-002 (editarea) SAU US-003 (Acasa unificata) — sunt independente.
- Expansiuni candidate (cherry-pick, auto-decise spre DEFER per P3/P6): buton "repara toate needs_review",
editare bulk, undo editare. Toate -> TODOS (nu in blast radius imediat).
### 0E. Temporal Interrogation
- HOUR 1: userul importa, vede un rand needs_data, da Editeaza, completeaza data, Salveaza -> ok. Functioneaza
DOAR daca stocarea editarii e corecta (Approach B). Cu Approach A + fisier cu antet ne-canonic: editarea pare
ca se salveaza dar randul ramane needs_data -> incredere distrusa.
- HOUR 6+: zeci de fisiere cu formate diverse de antet. Approach A esueaza pe majoritatea; Approach B tine.
### 0F. Mode Selection: **SELECTIVE EXPANSION** confirmat. Scope-ul de baza tinut; o singura expansiune ceruta
(override_json), restul deferite. Decizie de structura (split 4-in-1) -> DG-1 la poarta.
### Step 0.5 — Voci duale CEO
- **Codex (CLI)**: `[codex-unavailable: usage limit]` — a citit contextul dar a esuat la analiza pe limita de cont.
- **Subagent Claude (independent)**: a confirmat din cod premisa centrala (`web_confirma_import` -> `_upload.html`
cu "urmareste coada de mai jos", dar coada e in alt tab => microcopia minte azi). Findings principale:
US-001 dubleaza motorul existent `post_corectie_trimitere` (routes.py:583-717); US-007 "Automat/Manual"
risca interpretare gresita de send-safety (auto_send e per-operatie, nu global, peste declaratii ireversibile);
US-004 e consecinta US-003, nu peer; Acasa ajunge sa ruleze DOUA poll-uri (status 15s + trimiteri ~10s);
recomanda split (groful de valuri din PRD demonstreaza independenta).
```
CEO DUAL VOICES — CONSENSUS TABLE (Codex N/A — subagent-only)
═══════════════════════════════════════════════════════════════
Dimensiune Claude Codex Consensus
─────────────────────────────────── ─────── ─────── ─────────
1. Premise valide? partial N/A DG: editare-in-staging vs reuse corectie
2. Problema corecta? da N/A CONFIRMAT (US-003 e valoarea reala)
3. Scope calibrat? nu N/A FLAG: 4-in-1, recomandare split (DG-1)
4. Alternative explorate? nu N/A FLAG: reuse post_corectie_trimitere ne-analizat
5. Riscuri (send-safety)? da N/A FLAG CRITIC: US-007 Automat/Manual (UC-2)
6. Traiectorie 6 luni sound? partial N/A US-003 da; US-001 = tax de mentenanta daca dubleaza
═══════════════════════════════════════════════════════════════
Single critical din subagent (US-007 send-safety, US-001 DRY) = flagged regardless.
```
### CEO Sections 1-10 (rezumat per sectiune — examinat, nu doar numit)
1. **Strategic fit**: US-003 aliniat la idealul "o suprafata operationala"; restul cosmetic. OK.
2. **Error & Rescue**: editarea poate introduce valori care re-blocheaza randul (data invalida) — trebuie eroare
pe camp, nu pierdere de input (acoperit US-002 AC). Nicio cale de eroare tacuta noua daca se reda preview-ul.
3. **DRY (CRITIC)**: US-001 vs `post_corectie_trimitere` — aceeasi validare/canonicalizare/idempotency. Reuse obligatoriu.
4. **Send-safety (CRITIC)**: US-007 — "Manual" suna global peste un sistem cu declaratii ireversibile. Reformulare ceruta.
5. **Observability**: editarea ar trebui sa lase un log (cine/cand a editat un rand de staging) — minor, GDPR-friendly.
6. **Scope/impachetare**: cele 4 sunt independente (graf valuri). Recomandare split (DG-1).
7. **Reuse UI**: `.tablewrap`, `_submissions.html`, filtre US-009, upload JS — reutilizate corect.
8. **Page-weight**: dublu-poll pe Acasa (status 15s + trimiteri 10s) — de consolidat/confirmat (Eng#4).
9. **Migrare deep-link**: `?tab=coada` trebuie sa nu dea 404 (acoperit US-003).
10. **6-month**: US-001 fara reuse = doua locuri de intretinut; US-005/006 cosmetice = risc minim.
### Mandatory outputs — FAZA 1
**NOT in scope (deferite la TODOS):** buton "repara toate needs_review", editare bulk multi-rand, undo editare,
log de audit pe editare de staging. **Ce exista deja (refolosit):** `post_corectie_trimitere`, `.tablewrap`,
`_submissions.html`+filtre US-009, upload JS, `_resolve_row_for_preview`. **Dream delta:** planul muta catre
"o singura suprafata"; gap ramas = mapare AI (Etapa 4.1). **Failure modes:** (a) editare ignorata tacit pe fisier
cu antet ne-canonic [CRITIC, Eng]; (b) camp lipsa fara coloana sursa nu poate fi completat [CRITIC]; (c) "Manual"
interpretat global [CRITIC]; (d) dublu-poll pe pagina mereu deschisa [mediu].
> **FAZA 1 COMPLETE.** Codex: indisponibil (usage limit). Subagent Claude: 6 findings (2 critice).
> Consensus: 1/6 confirmat clar, 4 flag-uri -> poarta. Trec la Faza 2 (Design).
## FAZA 3 — Eng (arhitectura, teste, securitate)
> Codex `[codex-unavailable: usage limit]`. Subagent Claude independent (context curat).
### Step 0.5 — Eng consensus
```
ENG DUAL VOICES — CONSENSUS TABLE (Codex N/A — subagent-only)
═══════════════════════════════════════════════════════════════
Dimensiune Claude Codex Consensus
─────────────────────────────────── ─────── ─────── ─────────
1. Arhitectura sound? nu N/A FLAG: US-001 stocare gresita (C1)
2. Acoperire teste suficienta? nu N/A FLAG: fixture mascheaza bug; lipsesc anteturi ne-canonice
3. Riscuri performanta? partial N/A FLAG: dublu-poll Acasa (M5)
4. Securitate (scoping)? da N/A OK cu nota: query scoped JOIN (M7)
5. Cai de eroare? nu N/A FLAG: decrypt fail, empty edit, batch committed (L9-L11)
6. Risc deploy? da N/A OK: 1 ALTER defensiv (override_json) ca 3.3b/3.5
═══════════════════════════════════════════════════════════════
```
### Findings Eng (cu dovezi cod)
- **C1 [CRITIC] — US-001 stocare gresita.** `raw_json` cheiat pe anteturi fisier; resolver+commit re-aplica
`json_mapare` (import_router.py:138-140, 928-930). Scrierea cheii canonice e ignorata pe antet ne-canonic;
camp fara coloana-sursa nu poate fi exprimat deloc. Fix = **Approach B** (`import_rows.override_json` nullable,
Fernet, aplicat ULTIMUL in resolver + commit). Cere rescrierea AC US-001 (liniile actuale incoded Approach A)
+ relaxare Non-Goal schema (1 ALTER + `_migrate` defensiv). -> poarta UC-1.
- **H2 [HIGH] — DRY.** US-001 nu trebuie sa-si re-deriveze statusul; recalcul OBLIGATORIU prin `_resolve_row_for_preview`
(altfel drift de clasificare vs preview, iar clasificarea controleaza send-ul). Ruta editare = mutatie pura de stocare.
- **H3 [HIGH] — US-007 eticheta.** Scopeaza la operatie: "Aceasta operatie: Automat / Manual" + microcopy "pentru
aceasta operatie". `name="auto_send"` pastrat (zero backend) e corect.
- **H4 [MED] — US-005/007.** Tabelul "mapari salvate" trebuie sa randeze starea `auto_send` STOCATA (routes.py:738),
nu sa hard-defaulteze Automat.
- **M5 [MED] — dublu-poll Acasa.** status 15s (dashboard.html:14-17) + submissions 10s (_coada.html:43-45) pe pagina
mereu deschisa. Fix: aliniaza submissions la 15s SAU `hx-trigger` gated pe `document.visibilityState==='visible'`.
- **M6 [MED] — `/_fragments/coada`.** `?tab=coada` cade deja pe Acasa (routes.py:266), dar fragmentul `/_fragments/coada`
(routes.py:309-313) inca randeaza `_coada.html` orfan. Fix: fragmentul redirectioneaza la continutul Acasa; test dedicat.
- **M7 [MED] — scoping editare.** O singura interogare scoped JOIN `import_rows r JOIN import_batches b ... WHERE
b.account_id=? AND r.row_index=?` -> 404 pe gol. Web alias sub `require_login`, `/v1/...` sub `resolve_account_id`.
- **M8 [MED] — coercion.** `canonicalize_row` normeaza DOAR `odometru_final` (idempotency.py:44), nu `_initial`.
Testul de coercion sa nu afirme strip prin canonicalize pe `odometru_initial`.
- **L9-L12 [LOW/MED]:** test decrypt-fail (raw corupt -> 422/no-op); semantica empty-input (ignore vs clear — defineste
explicit, cazul "completez" poate cere clear); guard editare pe batch `committed` (409, ca import_router.py:822-823);
setup test panel preview (seed unmapped_op).
### Mandatory outputs — FAZA 3
**Diagrama arhitectura (override_json, Approach B):**
```
upload ─▶ import_rows(raw_json[antet fisier], override_json[canonic]=NULL)
editare rand ─────────┘ scrie override_json[camp_canonic]=valoare (mutatie pura)
GET preview ─▶ _resolve_row_for_preview:
mapped = json_mapare(raw_json) ◀── pas existent
mapped.update(override_json) ◀── PAS NOU (aplicat ultimul)
─▶ validate ─▶ status (ok/needs_data/...)
commit ─▶ acelasi merge override peste mapped ─▶ submissions
```
**Test diagram (coverage):** randuri noi de acoperit — (a) fisier antet ne-canonic editat -> ok [LIPSESTE azi];
(b) camp fara coloana-sursa completat -> ok [LIPSESTE]; (c) edit == GET preview status [LIPSESTE]; (d) decrypt
fail; (e) empty-input semantics; (f) batch committed -> 409; (g) scoped 404 alt cont; (h) `/_fragments/coada` fallback.
**NOT in scope:** undo, bulk. **Ce exista:** `_resolve_row_for_preview`, `post_corectie_trimitere`, `_migrate`.
**Failure modes critice:** C1 (acoperit de Approach B); fixture verde-dar-stricat (acoperit de fixture antet ne-canonic).
> **FAZA 3 COMPLETE.** Subagent: 1 critic, 3 high, 4 medium, 4 low. Trec la sinteza + poarta.
## FAZA 2 — Design (ierarhie, stari, interactiune)
> Codex `[codex-unavailable]`. Subagent Claude independent (a citit toate cele 8 template-uri).
### Design litmus scorecard (0-10)
| Dimensiune | Scor | Nota |
|---|---|---|
| Ierarhie informatie | 5 | upload demotat la slim + tabel sub el => monitorizarea domina actiunea principala |
| Stari (loading/error/partial) | 4 | lipsesc spinner save, eroare server, scroll-to-row, empty unificat |
| Interactiune edit-mode | 3 | cea mai slaba zona — swap pe toata sectiunea distruge starea (CRITIC) |
| Copy Automat/Manual | 4 | "Manual" citit global => contrazice promisiunea de siguranta |
| First-run vs returning | 5 | first-run pierde hero "Primul fisier?", 3 nudge-uri redundante |
### Findings Design (severitate)
- **D-3.1 [CRITIC] — swap distructiv.** `hx-target="#import-section"` + `outerHTML` la editare re-randeaza tot;
pierde bife `reviewed_rows`, `n_confirmat`, filtrul activ, alte randuri in editare. Fix: swap DOAR randul `<tr>`
+ OOB pe rezumat/contoare.
- **D-3.3 [HIGH] — Enter = trimitere ireversibila.** Input-urile de editare in acelasi `#confirm-form` cu butonul
"Trimite la RAR". Fix: randul de editare = FORM PROPRIU; dezactiveaza confirm cat un rand e in editare. Test:
Enter in camp editare NU declanseaza confirm.
- **D-2.1 [HIGH] — fara stare "se salveaza".** Re-randare completa fara `hx-indicator`/buton dezactivat. Fix: spinner pe rand.
- **D-2.2 [HIGH] — eroare server = pierdere tacuta de date.** `outerHTML` pe sectiune la 500/CSRF/timeout poate goli
`#import-section`. Fix: `hx-on::response-error` pastreaza randul + banner; test dedicat.
- **D-3.4 [HIGH] — integritate N la editare.** Re-randarea reseteaza `n_confirmat`. Fix: blocheaza confirm la editare + recalcul autoritar.
- **D-3.5 [HIGH] — mobil inutilizabil.** ~10 coloane cu input-uri in container cu scroll orizontal. Fix: pe viewport ingust,
randul de editare = card stacked (refoloseste grila din `_trimitere_detaliu.html`).
- **D-4.1 [HIGH] — "Automat/Manual" contrazice "nu se trimite nimic pana confirmi".** Fix: framing pe COADA, nu pe trimitere:
"La fisierele viitoare cu aceasta operatie: [Pune automat in coada] / [Tine pentru verificare]". -> poarta UC-2.
- **D-1.1/1.2 [HIGH/MED] — upload demotat + coexistenta preview vs Trimiteri.** Fix: upload slim DAR accentuat/distinct;
ascunde Trimiteri cat un preview de import e activ (un singur sticky bar pe ecran); heading "Trimiterile tale".
- **D-3.2/3.6/5.1/5.3 [MED/LOW]:** mutual-exclusion + debounce pe Editeaza; warn pe navigare cu editare dirty;
first-run pastreaza hero "Primul fisier?" cand nu exista trimiteri (suprima empty-state redundant); pastreaza wayfinding Mapari/Coduri.
- **Lauda:** refolosirea literala a markup-ului din `_trimitere_detaliu.html` (grila responsiva + error-map + scroll-to-row)
rezolva D-2.2, D-3.5, D-2.5 gratis. -> AC HARD pe US-002.
```
DESIGN CONSENSUS (Codex N/A — subagent-only): 1 critic, 7 high, 5 med, 3 low.
Gate de design: D-3.1 + D-3.3 (swap distructiv + Enter-trimite) = problema de corectitudine/siguranta, nu rafinament.
```
> **FAZA 2 COMPLETE.** Trec la sinteza cross-faza + poarta finala.
## SINTEZA CROSS-FAZA
**Teme care apar in 2+ faze (semnal de incredere mare):**
1. **Refolosire `post_corectie_trimitere` / `_trimitere_detaliu.html`** — CEO (0B), Eng (H2), Design (lauda). Reuse = AC hard pe US-001/US-002.
2. **US-007 "Automat/Manual" periculos** — CEO (UC-2), Eng (H3), Design (D-4.1). Reformulare ceruta. -> poarta UC-A.
3. **Stocare editare gresita (C1)** — CEO (0A), Eng (C1). Approach B (override_json) + fixture cu antet ne-canonic.
## DECISION AUDIT TRAIL (auto-decise pe cele 6 principii)
| # | Faza | Decizie | Clasificare | Principiu | Rationament |
|---|---|---|---|---|---|
| 1 | Eng | C1: adopta Approach B (override_json) in loc de raw_json | Auto | P1,P5 | Approach A e stricat (ignora editari pe antet ne-canonic; nu poate completa coloane absente) |
| 2 | Eng | US-001 = mutatie pura; status recalculat prin `_resolve_row_for_preview` | Auto | P4 DRY | evita drift de clasificare care controleaza send-ul |
| 3 | Design | Edit swap pe rand `<tr>` + OOB contoare, NU pe `#import-section` | Auto | P1,P5 | swap pe sectiune distruge starea neclickata |
| 4 | Design | Rand editare = FORM propriu + confirm dezactivat la editare | Auto | P1 | previne Enter->trimitere ireversibila |
| 5 | Design | Refolosire grila `_trimitere_detaliu.html` (responsiv + error-map + scroll) | Auto | P4 DRY | rezolva mobil+eroare+scroll gratis |
| 6 | Design | Spinner save + `hx-on::response-error` pastreaza randul | Auto | P1 | fara pierdere tacuta de date |
| 7 | Eng | M5: poll trimiteri gated pe `document.visibilityState` (sau 15s) | Auto | P3 | reduce load perpetuu pe pagina mereu deschisa |
| 8 | Eng | M6: `/_fragments/coada` -> continut Acasa (nu doar `?tab=coada`) | Auto | P1 | evita fragment orfan din bookmark vechi |
| 9 | Eng | M7: query scoped JOIN import_rows×import_batches -> 404 | Auto | P1 sec | fara scope-leak/TOCTOU |
| 10 | Eng | M8: scoate afirmatia canonicalize pe odometru_initial | Auto | P5 | `canonicalize_row` normeaza doar `_final` |
| 11 | Eng | L11: guard editare pe batch `committed` -> 409 | Auto | P1 | editare post-commit nu are efect downstream |
| 12 | Design | First-run pastreaza hero "Primul fisier?"; slim accentuat; pastreaza wayfinding Mapari/Coduri | Auto | P1 | discoverability first-run |
| UC-A | CEO/Design | US-007 reformulare labels (user a ales explicit "Automat/Manual") | USER CHALLENGE | — | toti reviewerii: risc send-safety. User decide. |
| DG-1 | CEO | Split: ship US-003(+004) intai, US-001/002 dupa reuse, US-005/006 batch, US-007 redesign | Taste | P6 | cele 4 sunt independente (graf valuri) |

View File

@@ -2,10 +2,10 @@
# start.sh — pornire gateway RAR AUTOPASS (api / worker) pe mediu test sau prod. # start.sh — pornire gateway RAR AUTOPASS (api / worker) pe mediu test sau prod.
# #
# Exemple: # Exemple:
# ./start.sh test api # API pe :8000, mediu test # ./start.sh test api # API pe :8010, mediu test
# ./start.sh test worker --send # worker care TRIMITE la RAR test (creds <test> din settings.xml) # ./start.sh test worker --send # worker care TRIMITE la RAR test (creds <test> din settings.xml)
# ./start.sh test both --send # API + worker impreuna (dev end-to-end) # ./start.sh test both --send # API + worker impreuna (dev end-to-end)
# ./start.sh prod api --port 8000 # API mediu prod # ./start.sh prod api --port 8010 # API mediu prod
# ./start.sh prod worker --send # worker prod (NU foloseste creds de test) # ./start.sh prod worker --send # worker prod (NU foloseste creds de test)
# ./start.sh status # stare procese + /healthz # ./start.sh status # stare procese + /healthz
# ./start.sh stop # opreste procesele pornite cu "both" # ./start.sh stop # opreste procesele pornite cu "both"
@@ -18,7 +18,7 @@ set -euo pipefail
cd "$(dirname "$0")" cd "$(dirname "$0")"
# --- valori implicite --- # --- valori implicite ---
PORT=8000 PORT=8010
HOST=0.0.0.0 HOST=0.0.0.0
RELOAD=0 RELOAD=0
SEND=0 SEND=0

View File

@@ -1,6 +1,9 @@
vin,nr_inmatriculare,data_prestatie,odometru_final,cod_op_service,denumire vin,nr_inmatriculare,data_prestatie,odometru_final,cod_op_service,denumire,odometru_initial
1G1FB1S52D1234567,AB123CD,2026-06-10,45000,OP-MOTOR,Reparatie Motor 1G1FB1S52D1234567,AB123CD,2026-06-10,45000,OP-MOTOR,Reparatie Motor
1G1FB1S52D1234567,AB123CD,2026-06-10,45000,OP-REV,Revizie
1G1FB1S52D1234567,AB123CD,2026-06-10,45000,OP-FRANE,Schimbare placute frane
WVWZZZ3CZ9E123456,TM789BC,2026-05-15,82500,OP-ITP,Inspecție Tehnică Periodică WVWZZZ3CZ9E123456,TM789BC,2026-05-15,82500,OP-ITP,Inspecție Tehnică Periodică
2B1FB1S39C2345678,CJ456DE,2026-04-20,125000,OP-TURBO,Reparatie Turbo 2B1FB1S39C2345678,CJ456DE,2026-04-20,125000,OP-TURBO,Reparatie Turbo
JTHBP5C20D5123456,OT567FG,2026-03-08,38000,OP-ULEI,Schimb Ulei JTHBP5C20D5123456,OT567FG,2026-03-08,38000,OP-ULEI,Schimb Ulei
5TDJZRFH2LS123456,VN678HI,2026-02-14,156000,OP-PNEU,Inlocuire Anvelope 5TDJZRFH2LS123456,VN678HI,2026-02-14,156000,OP-PNEU,Inlocuire Anvelope
5TDJZRFH2LS123456,VN678HI,2026-02-14,156000,OP-ODO-NOU,Schimbare odometru
1 vin vin,nr_inmatriculare,data_prestatie,odometru_final,cod_op_service,denumire,odometru_initial nr_inmatriculare data_prestatie odometru_final cod_op_service denumire
2 1G1FB1S52D1234567 1G1FB1S52D1234567,AB123CD,2026-06-10,45000,OP-MOTOR,Reparatie Motor AB123CD 2026-06-10 45000 OP-MOTOR Reparatie Motor
3 1G1FB1S52D1234567,AB123CD,2026-06-10,45000,OP-REV,Revizie
4 1G1FB1S52D1234567,AB123CD,2026-06-10,45000,OP-FRANE,Schimbare placute frane
5 WVWZZZ3CZ9E123456 WVWZZZ3CZ9E123456,TM789BC,2026-05-15,82500,OP-ITP,Inspecție Tehnică Periodică TM789BC 2026-05-15 82500 OP-ITP Inspecție Tehnică Periodică
6 2B1FB1S39C2345678 2B1FB1S39C2345678,CJ456DE,2026-04-20,125000,OP-TURBO,Reparatie Turbo CJ456DE 2026-04-20 125000 OP-TURBO Reparatie Turbo
7 JTHBP5C20D5123456 JTHBP5C20D5123456,OT567FG,2026-03-08,38000,OP-ULEI,Schimb Ulei OT567FG 2026-03-08 38000 OP-ULEI Schimb Ulei
8 5TDJZRFH2LS123456 5TDJZRFH2LS123456,VN678HI,2026-02-14,156000,OP-PNEU,Inlocuire Anvelope VN678HI 2026-02-14 156000 OP-PNEU Inlocuire Anvelope
9 5TDJZRFH2LS123456,VN678HI,2026-02-14,156000,OP-ODO-NOU,Schimbare odometru