Files
rar-autopass/CLAUDE.md
Claude Agent 3fc53534e2 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>
2026-06-28 20:48:34 +00:00

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 randuri sending orfane, 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/prezentari pentru 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_KEY trebuie 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 both genereaza 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_key normalizeaza INTOTDEAUNA account_id la account_or_default (None == 1) INAINTE de hash — altfel acelasi rand logic primeste chei diferite pe canalele API vs import (OV-2). canonicalize_row normeaza VIN/nr/odometru (strip ".0" din coercion Excel) inainte de validare si de cheie.
  • FINALIZATA e terminal la RAR — fara anulare/corectie prin API. De aceea reconcilierea anti-duplicat: pe eroare ambigua (timeout / TransportError / 502/503/504 / 429 / 408) sau rand sending orfan, worker-ul cauta in finalizate (match pe vin+dataPrestatie+odometruFinal) si marcheaza sent fara 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, marcheaza error cu mesajul RAR (RAR_EROARE_SERVER). Altfel ar marca fals sent pe 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 in submissions.rar_creds_enc (canal API, sterse dupa primul login reusit). Worker incearca submission-ul intai, apoi fallback la cont. Purjarea sterge DOAR submissions.rar_creds_enc, NU accounts.rar_creds_enc.
  • Auth API-key (auth.py): identifica CONTUL ROAAUTO, separat de credentialele RAR. Stocam doar SHA-256 al cheii. Enforcement prin AUTOPASS_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-versiune sub require_login + scope, 404-before-leak pe id strain; GET /v1/prezentari(/{id})//v1/mapari//v1/audit/export filtrate pe cont). GET /v1/nomenclator ramane 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) sau cod_op_service (cod intern) + denumire. Nerezolvat → submission needs_mapping (nu se trimite), apare in editorul web cu sugestie fuzzy; la salvarea maparii se re-rezolva automat submission-urile blocate.
  • cod_prestatie e VALIDAT fata de nomenclator la ingestie (resolve_prestatii(..., valid_codes)): un cod direct NECUNOSCUT in nomenclator NU se mai trimite raw — e promovat la cod_op_service (denumire=cod) si tratat ca operatie de mapat. Motiv (confirmat live 2026-06-23): RAR accepta NUMAI coduri din nomenclator (coloana COD_PRESTATIE max 5 car.); un cod necunoscut da HTTP 500 (ORA-12899), iar RAR NU e tranzactional → lasa un record PARTIAL FINALIZATA (terminal) chiar pe esec, pe care reconcilierea worker-ului l-ar marca fals sent. Comportamentul la cod necunoscut/nemapat: on_unmapped_error (camp boolean top-level pe POST /v1/prezentari + /valideaza) = false (intra in editor, needs_mapping) sau true (respinge fara enqueue → submission_id=null + erori). Default = accounts.on_unmapped_error_default (implicit false/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.py pastreaza type/loc/msg dar DROP-a input/ctx (altfel ar reflecta rar_credentials.password).
  • Retentie: submissions sent + import_batches primesc purge_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.