Files
rar-autopass/CLAUDE.md
2026-07-03 13:11:56 +00:00

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 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).
  • Admin bootstrap fara hardcodare: la /signup, primul user din TOATA baza (count_admins()==0, verificat in tranzactie) primeste is_admin=1 automat; orice signup ulterior e neadmin. is_admin da acces la /admin (panou global, toate conturile) — verificat pe account_id-ul sesiunii, dar neconditionat DE care account_id a primit userul (semnul de "cine e admin" nu are legatura cu contul id=1). Contul id=1 e 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) 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.

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