Adauga tests/test_live_rar.py: reproduce automat proba live a maparii inline (needs_mapping -> mapare inline web cu sesiune+CSRF -> queued -> worker real login RAR + postPrezentare -> sent -> verificare in finalizate RAR). Skip implicit (marker `live`), opt-in cu AUTOPASS_LIVE_RAR=1 + creds <test>. - conftest.py: inregistreaza markerul `live` (excludere -m "not live") - ROADMAP/CLAUDE.md: 5.7 NEPROBAT -> PROBAT (manual idPrezentare=68827, automatizat idPrezentare=68828) + comenzi rulare test live pytest -q: 765 passed, 1 skipped (live). Test live verde pe RAR test. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
8.6 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 momentan globale + neprotejate (de remediat — vezi ROADMAP). - 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.