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>
8.8 KiB
CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
Ce este
Gateway web (Python / FastAPI) care preia prezentari de service-auto si le declara la RAR AUTOPASS (Legea 142/2023, OM 210/2024). Inlocuieste integrarea Visual FoxPro RarAutoPass (ROAAUTO), arhivata in legacy-vfp/ (doar referinta).
Limba proiectului este romana: cod, comentarii, commit-uri, documentatie. Fara emoji in raspunsuri sau fisiere noi (preferinta proiect).
Surse de adevar (citeste-le inainte sa modifici comportament)
docs/api-rar-contract.md— contractul RAR. Unde un plan/cod difera de contract, contractul are dreptate.docs/ROADMAP.md— singura sursa de progres + procesul de lucru. O sesiune noua porneste de aici. Doar sectiunea "Stadiu Implementare" se modifica pe parcurs; detaliile stau in PRD-uri (docs/prd/).
Comenzi
pip3 install -r requirements.txt # Python 3.12+
# Rulare locala (dev): API + worker sunt PROCESE SEPARATE
uvicorn app.main:app --reload --port 8010 # API: dashboard /, Swagger /docs, /healthz, /metrics
python3 -m app.worker # worker (necesar doar pentru a procesa coada)
# Wrapper-ul start.sh ambaleaza mediu (test/prod) + rol (api/worker/both/finalizate)
./start.sh test both --send # API + worker, trimite efectiv la RAR test (loguri in .run/)
./start.sh test finalizate # listeaza prezentarile inregistrate la RAR (verificare independenta)
./start.sh status # stare procese + /healthz
./start.sh stop # opreste procesele pornite cu "both"
./start-test.sh / ./start-prod.sh # fixeaza mediul, forwardeaza rolul
# Teste (pytest; folosesc FastAPI TestClient + SQLite temporar). Testele live RAR sunt
# skip implicit (marker `live`) — `pytest -q` nu atinge endpointul real.
python3 -m pytest -q
python3 -m pytest tests/test_worker_reconcile.py -q # un fisier
python3 -m pytest tests/test_worker_reconcile.py::test_x -q # un singur test
python3 -m pytest -q -m "not live" # exclude explicit testele live
# Test LIVE pe RAR test (opt-in, skip implicit; atinge endpointul real -> creeaza FINALIZATA):
# reproduce lantul mapare inline -> queued -> worker -> sent -> verificare in finalizate.
AUTOPASS_LIVE_RAR=1 python3 -m pytest tests/test_live_rar.py -q # necesita settings.xml cu creds <test>
# Lifecycle chei API (admin, doar din CLI — nu exista suprafata HTTP)
python3 -m tools.apikey create --account 2 # cheie afisata O SINGURA DATA (rfak_...)
python3 -m tools.apikey list|rotate|revoke
# Docker (deploy): api + worker + autoheal, acelasi image + volum SQLite
docker compose up --build
Arhitectura
Doua procese peste acelasi SQLite (WAL) persistent, care comunica EXCLUSIV prin tabela submissions:
- API (
app.main:app) — API v1 (app/api/v1/router.py+import_router.py), dashboard web HTMX (app/web/routes.py+templates/),/healthz,/metrics. Worker-ul nu ruleaza ca task aici: un worker mort nu trebuie sa lase containerul "sanatos". - Worker (
app/worker/__main__.py) — bucla: heartbeat → recupereaza orfane → claim atomic (BEGIN IMMEDIATE) → login RAR (per cont) →postPrezentare→ update. Retry/backoff exponential, reconciliere anti-duplicat, lease pe randurisendingorfane, re-login la JWT expirat (TTL 30h). Send DEZACTIVAT implicit (AUTOPASS_WORKER_SEND_ENABLED=false) — sigur pentru probe.
Doua canale de intrare convergent in coada submissions:
- Canal API (Treapta 1):
POST /v1/prezentaripentru ROAAUTO / soft propriu. - Import web (Treapta 2): upload xlsx/csv → mapare coloane → preview → commit (
app/import_parse.py,import_router.py, dashboard).
Flux: validare (validation.py) → mapare operatie→cod (mapping.py) → enqueue cu PII criptat → worker trimite → dashboard monitorizeaza live.
Invariante critice (usor de stricat)
AUTOPASS_CREDS_KEYtrebuie sa fie ACEEASI intre API si worker. API cripteaza creds RAR (Fernet), worker le decripteaza. Chei diferite → worker nu poate decripta → trimiterile esueaza.start.sh bothgenereaza o cheie efemera partajata; pentru prod pune una persistenta in.env. (crypto.py)- Idempotenta = hash de continut canonic server-side (
idempotency.py), pentru ca RAR accepta duplicate si nu are nr. comanda.build_keynormalizeaza INTOTDEAUNAaccount_idlaaccount_or_default(None == 1) INAINTE de hash — altfel acelasi rand logic primeste chei diferite pe canalele API vs import (OV-2).canonicalize_rownormeaza VIN/nr/odometru (strip ".0" din coercion Excel) inainte de validare si de cheie. FINALIZATAe terminal la RAR — fara anulare/corectie prin API. De aceea reconcilierea anti-duplicat: pe eroare ambigua (timeout / TransportError / 502/503/504 / 429 / 408) sau randsendingorfan, worker-ul cauta in finalizate (match pe vin+dataPrestatie+odometruFinal) si marcheazasentfara a re-trimite (reconcile.py). EXCEPTIE: un RAR 500 cu mesaj (RarError.rar_message, ex.ORA-12899) e un esec DEFINITIV (RAR a raspuns „am esuat", nu o pierdere de raspuns) → worker-ul NU reconciliaza si NU reincearca, marcheazaerrorcu mesajul RAR (RAR_EROARE_SERVER). Altfel ar marca falssentpe un record PARTIAL pe care RAR (ne-tranzactional) il lasa la esec.- Creds RAR per cont: durabile in
accounts.rar_creds_enc(canal web, fallback re-login) SAU efemere insubmissions.rar_creds_enc(canal API, sterse dupa primul login reusit). Worker incearca submission-ul intai, apoi fallback la cont. Purjarea sterge DOARsubmissions.rar_creds_enc, NUaccounts.rar_creds_enc. - Auth API-key (
auth.py): identifica CONTUL ROAAUTO, separat de credentialele RAR. Stocam doar SHA-256 al cheii. Enforcement prinAUTOPASS_REQUIRE_API_KEY:false(dev) → fara cheie merge pe cont id=1, cheie invalida → 401;true(prod) → cheie obligatorie pe/v1/*protejat. POST-urile + rutele de import sunt account-scoped; GET-urile de listare sunt si ele account-scoped (5.15/US-011: fragmentele_fragments/submissions|trimitere|mapari|status|jurnal|nomenclator|trimiteri-versiunesubrequire_login+ scope, 404-before-leak pe id strain;GET /v1/prezentari(/{id})//v1/mapari//v1/audit/exportfiltrate pe cont).GET /v1/nomenclatorramane public intentionat (coduri RAR publice, fara PII). - Mapare coloane retinuta per
(account_id, signature_coloane)(column_mappings): la urmatorul fisier cu aceleasi coloane, pentru acelasi cont, maparea se reaplica automat. Un cont poate avea mai multe formate memorate simultan. - Mapare operatie→cod: prestatie poate veni cu
cod_prestatie(cod RAR direct) saucod_op_service(cod intern) +denumire. Nerezolvat → submissionneeds_mapping(nu se trimite), apare in editorul web cu sugestie fuzzy; la salvarea maparii se re-rezolva automat submission-urile blocate. cod_prestatiee VALIDAT fata de nomenclator la ingestie (resolve_prestatii(..., valid_codes)): un cod direct NECUNOSCUT in nomenclator NU se mai trimite raw — e promovat lacod_op_service(denumire=cod) si tratat ca operatie de mapat. Motiv (confirmat live 2026-06-23): RAR accepta NUMAI coduri din nomenclator (coloanaCOD_PRESTATIEmax 5 car.); un cod necunoscut da HTTP 500 (ORA-12899), iar RAR NU e tranzactional → lasa un record PARTIALFINALIZATA(terminal) chiar pe esec, pe care reconcilierea worker-ului l-ar marca falssent. Comportamentul la cod necunoscut/nemapat:on_unmapped_error(camp boolean top-level pePOST /v1/prezentari+/valideaza) =false(intra in editor,needs_mapping) sautrue(respinge fara enqueue →submission_id=null+erori). Default =accounts.on_unmapped_error_default(implicitfalse/0); precedenta cerere > cont >false.- WAF RAR da 403 fara User-Agent de browser — toate apelurile httpx trimit
User-Agent: Mozilla/5.0(config.py, confirmat live). - 422 fara echo de credentiale: handler-ul global de validare in
main.pypastreaza type/loc/msg dar DROP-ainput/ctx(altfel ar reflectarar_credentials.password). - Retentie:
submissionssent +import_batchesprimescpurge_after = now + 90 zile; worker-ul purjeaza odata pe ora (T16, GDPR/L.142).
Masina de stari submissions
queued → sending → sent (succes, cu id_prezentare de la RAR). Ramuri: needs_mapping (cod nerezolvat), needs_data (RAR 400, validare continut), error (max retries / 4xx nerecuperabil / RAR 500 cu mesaj — esec definitiv / creds invalide / login 401 — NU se face retry pe creds gresite). Backoff: next_attempt_at = now + base*2^retry, plafonat. Schema completa: app/schema.sql.
Mod non-interactiv
Vezi /workspace/CLAUDE.md (workspace-level): cand esti lansat cu claude -p, creeaza fisiere noi DOAR in /workspace/.claude-work/<task>/, nu in /workspace. Modificarile la fisiere existente se fac in locatia originala.