10 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). - Admin bootstrap fara hardcodare: la
/signup, primul user din TOATA baza (count_admins()==0, verificat in tranzactie) primesteis_admin=1automat; orice signup ulterior e neadmin.is_adminda acces la/admin(panou global, toate conturile) — verificat peaccount_id-ul sesiunii, dar neconditionat DE careaccount_ida primit userul (semnul de "cine e admin" nu are legatura cu contulid=1). Contulid=1e doar bucket-ul de fallback pentru trafic API neautentificat in dev (AUTOPASS_REQUIRE_API_KEY=false) — separat de conceptul de admin. Fix manual daca primul signup s-a consumat deja gresit:python3 -m tools.account set-admin --account N. - 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.
Skill routing
When the user's request matches an available skill, invoke it via the Skill tool. When in doubt, invoke the skill.
Key routing rules:
- Product ideas/brainstorming → invoke /office-hours
- Strategy/scope → invoke /plan-ceo-review
- Architecture → invoke /plan-eng-review
- Design system/plan review → invoke /design-consultation or /plan-design-review
- Full review pipeline → invoke /autoplan
- Bugs/errors → invoke /investigate
- QA/testing site behavior → invoke /qa or /qa-only
- Code review/diff check → invoke /review
- Visual polish → invoke /design-review
- Ship/deploy/PR → invoke /ship or /land-and-deploy
- Save progress → invoke /context-save
- Resume context → invoke /context-restore
- Author a backlog-ready spec/issue → invoke /spec