VERIFY PASS pentru 3.6: suita 523 teste, E2E browser pe `/` (toate US-001..007), si trimitere LIVE pe RAR test — import fara coloana data -> editarea completeaza data (override_json) -> commit -> worker login RAR test -> postPrezentare -> sent idPrezentare=68696 (confirmat independent in tools.rar_finalizate). - ROADMAP: rand 3.6 = VERIFY-PASS + "Ultima actualizare". - PRD 3.6: Stare -> verify-pass + sectiunea "## Raport VERIFY" completata (PASS per story, dovezi, cele 3 bug-uri JS prinse la E2E si reparate). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Gateway RAR AUTOPASS
Gateway web (Python / FastAPI) care preia prezentarile de service si le declara la
RAR AUTOPASS (Legea 142/2023, OM 210/2024). Inlocuieste integrarea Visual FoxPro
existenta (ROAAUTO). Sursa de adevar pentru contractul RAR este
docs/api-rar-contract.md.
Arhitectura pe scurt
Doua procese peste acelasi SQLite persistent:
| Proces | Rol | Pornire |
|---|---|---|
API (app.main:app) |
API v1 (/v1/*), dashboard web (/), /healthz, /metrics, import fisiere (/v1/import/*) |
uvicorn app.main:app |
Worker (app.worker) |
login RAR + JWT, refresh nomenclator, trimite prezentarile din coada, retry/backoff, heartbeat | python3 -m app.worker |
Worker-ul ruleaza ca proces separat (nu task in API) — un worker mort nu trebuie sa
lase containerul "sanatos". Comunicarea API <-> worker se face exclusiv prin tabela
submissions din SQLite. Send-ul catre RAR este dezactivat implicit
(AUTOPASS_WORKER_SEND_ENABLED=false) — sigur pentru probe.
Cerinte
- Python 3.12+
- Dependintele din
requirements.txt
pip3 install -r requirements.txt
(Optional, pentru deploy: Docker + Docker Compose — vezi sectiunea Docker.)
Configurare
Variabilele de mediu folosesc prefixul AUTOPASS_. Pentru dev local valorile implicite
sunt suficiente — nu ai nevoie de .env sau de credentiale RAR ca sa testezi UI-ul si
API-ul. Copiaza .env.example -> .env doar cand vrei sa rulezi end-to-end.
| Variabila | Implicit | Rol |
|---|---|---|
AUTOPASS_DB_PATH |
./data/autopass.db |
calea fisierului SQLite |
AUTOPASS_RAR_ENV |
test |
test sau prod |
AUTOPASS_REQUIRE_API_KEY |
false |
false = dev (fara cheie -> cont id=1); true = prod (cere cheie) |
AUTOPASS_CREDS_KEY |
(efemera) | cheie Fernet pt criptarea creds RAR. Trebuie partajata intre API si worker. Genereaza: python3 -c "from cryptography.fernet import Fernet; print(Fernet.generate_key().decode())" |
AUTOPASS_WORKER_SEND_ENABLED |
false |
true = trimite efectiv la RAR (proba end-to-end) |
AUTOPASS_WORKER_USE_TEST_CREDS |
false |
dev: foloseste blocul <test> din settings.xml pt login worker |
Pentru proba reala cu RAR: copiaza settings.xml.example -> settings.xml si completeaza
credentialele de test (fisierul nu se comite).
Rulare locala (dezvoltare)
1. Porneste API-ul
uvicorn app.main:app --reload --port 8010
# sau, daca uvicorn nu e pe PATH:
python3 -m uvicorn app.main:app --reload --port 8010
La prima pornire se creeaza schema SQLite si se face seed la nomenclatorul RAR (18 coduri din contract), astfel incat dashboard-ul si maparile functioneaza imediat, offline.
2. (Optional) Porneste worker-ul
Necesar doar pentru a procesa coada / a trimite la RAR. Pentru testarea UI-ului si a import-ului nu e necesar.
python3 -m app.worker
Pornire rapida cu start.sh
start.sh ambaleaza pornirea pe mediu (test / prod) si rol (api / worker / both):
./start.sh test api # API pe :8010, mediu test
./start.sh test worker --send # worker care TRIMITE la RAR test
./start.sh test both --send # API + worker impreuna (dev end-to-end, loguri in .run/)
./start.sh prod api --port 8010 # API mediu prod
./start.sh prod worker --send # worker prod (NU foloseste creds de test)
./start.sh status # stare procese + /healthz
./start.sh stop # opreste procesele pornite cu "both"
./start.sh test finalizate # ce prezentari sunt inregistrate la RAR (vezi mai jos)
Optiuni: --port N, --host H, --reload (dev), --send (activeaza trimiterea la RAR),
--test-creds / --no-test-creds (forteaza folosirea creds <test> din settings.xml).
Pe test cu --send, creds <test> se folosesc automat. Pentru productie reala foloseste
docker compose (vezi sectiunea Docker).
Doua wrappere fixeaza mediul si forwardeaza rolul + optiunile:
./start-test.sh # = start.sh test both --send (API + worker, trimite la RAR test)
./start-test.sh worker --send # = start.sh test worker --send
./start-test.sh finalizate # = start.sh test finalizate
./start-prod.sh both --send # = start.sh prod both --send
./start-prod.sh api # = start.sh prod api
Pe test, ./start-test.sh fara argumente porneste end-to-end (sandbox RAR e sigur). Pe prod,
./start-prod.sh cere rolul explicit si trimiterea trebuie ceruta cu --send (evita trimiteri
accidentale in productie).
Testare in browser
Cu API-ul pornit, deschide in browser:
| URL | Ce vezi |
|---|---|
http://localhost:8010/ |
Dashboard — stare coada, banner prezentari blocate, stare worker / ultim login RAR, editor mapari operatii, browser nomenclator, sectiune import fisier |
http://localhost:8010/docs |
Swagger UI — API v1 interactiv (incearca endpointurile direct din browser) |
http://localhost:8010/healthz |
JSON sanatate: worker viu, ultim login RAR, adancime coada |
http://localhost:8010/metrics |
metrici text (submissions pe status) |
Fluxul de import fisier (xlsx / csv) din browser
Pe dashboard, in sectiunea de import:
- Incarca un fisier
.xlsxsau.csv(drag & drop sau selectare). - Mapeaza coloanele — gateway-ul sugereaza automat (fuzzy) maparea coloana fisier -> camp canonic (VIN, data prestatie, odometru, operatie etc.). Maparea se retine pe semnatura coloanelor: la urmatorul fisier cu aceleasi coloane se aplica automat.
- Preview — fiecare rand primeste o stare:
ok,needs_mapping,needs_data,needs_review,already_sent,duplicate_in_file. - Confirma — gate dur: retastezi numarul exact de randuri
okde trimis. Randurile confirmate intra in coada (submissions), apoi le urmaresti in tabelul de jos.
Coloane recunoscute (cu sinonime): VIN, Nr inmatriculare, Data prestatie,
Odometru final, Odometru initial, Operatie, Observatii.
Genereaza un fisier de test pentru import
Repo-ul nu contine fisiere sample. Creeaza unul rapid:
python3 - <<'PY'
import openpyxl
wb = openpyxl.Workbook()
ws = wb.active
ws.append(["VIN", "Nr inmatriculare", "Data prestatie", "Odometru final", "Operatie"])
ws.append(["WAUZZZ8K0AA000001", "B123ABC", "2026-06-15", 120000, "REVIZIE PERIODICA"])
ws.append(["WAUZZZ8K0AA000002", "B456DEF", "2026-06-16", 85000, "REPARATIE"])
wb.save("sample_import.xlsx")
print("scris sample_import.xlsx")
PY
Sau un CSV echivalent:
printf 'VIN,Nr inmatriculare,Data prestatie,Odometru final,Operatie\nWAUZZZ8K0AA000001,B123ABC,2026-06-15,120000,REVIZIE PERIODICA\n' > sample_import.csv
Incarca apoi fisierul prin sectiunea de import a dashboard-ului.
Proba trimitere la RAR (mediu test) + verificare ca au ajuns
Implicit worker-ul nu trimite (AUTOPASS_WORKER_SEND_ENABLED=false). Pentru proba
end-to-end pe contul de test RAR:
-
Pune credentialele de test in
settings.xml(copiaza dinsettings.xml.example, completeaza blocul<test>). Acestea nu se comit. -
Bag-a prezentari in coada — fie prin import fisier din dashboard, fie prin API (
POST /v1/prezentari, vezi mai jos). -
Porneste worker-ul cu trimiterea activa:
./start.sh test worker --sendWorker-ul face login la RAR test, ia randurile
queued, trimite si trece fiecare rand insentcuid_prezentare(id-ul intors de RAR — dovada ca a ajuns) sau inneeds_data/errorcu motivul. -
Vizualizeaza prezentarile trimise — trei feluri:
-
Dashboard (
http://localhost:8010/) — tabelul de jos arata fiecare submission cu status (sent/error/...),id_prezentare, cod RAR si eroare. Se actualizeaza singur. -
API:
curl -s http://localhost:8010/v1/prezentari— coada locala cu statusuri. -
Direct de la RAR (confirmare independenta ca au ajuns):
./start.sh test finalizateFace login la RAR test si listeaza prezentarile inregistrate acolo (id, VIN, data, odometru). Compari
id-urile cuid_prezentaredin coada locala: daca se regasesc, prezentarea a ajuns la RAR.
-
Status
sent+id_prezentarecompletat = RAR a acceptat prezentarea. Worker-ul are si reconciliere anti-duplicat: daca raspunsul RAR se pierde, la urmatorul ciclu cauta prezentarea in finalizate si o marcheazasentfara a o re-trimite.
Import fisier pentru mai multi utilizatori (service-uri) cu formate diferite
Da — fiecare service auto poate avea propriul format de fisier (alte denumiri de coloane, alta ordine, alt format de data). Sistemul tine minte maparea per cont, deci nu o refaci la fiecare upload:
-
Cont (
account_id) — fiecare service e un cont. In productie contul se identifica prin cheia API (X-API-Key) trimisa la upload/cerere (AUTOPASS_REQUIRE_API_KEY=true). In dev, fara cheie, totul merge pe contul implicitid=1. -
Semnatura coloanelor — la upload, gateway-ul calculeaza o semnatura din lista (sortata) a denumirilor de coloane din fisier. Maparea coloana-fisier -> camp-canonic se salveaza in tabela
column_mappings, cheie unica(account_id, signature_coloane), impreuna cu formatul de data. -
Re-aplicare automata — la urmatorul fisier cu aceleasi coloane (aceeasi semnatura), pentru acelasi cont, maparea retinuta se aplica automat si sari direct la preview. Daca un service schimba formatul (alte coloane) se creeaza o semnatura noua, deci o mapare noua — fara sa o strice pe cea veche. Astfel un cont poate avea mai multe formate memorate simultan.
Pe scurt: cine = account_id (din cheia API), care format = signature_coloane
(setul de coloane al fisierului). Combinatia lor selecteaza maparea corecta.
Conturi (service-uri) si chei API
Un cont (accounts) = un service auto care foloseste gateway-ul. Cererile /v1/* se
autentifica printr-o cheie API (header X-API-Key: <cheie> sau Authorization: Bearer <cheie>) care identifica contul. Cheia e separata de credentialele RAR ale service-ului.
Enforcement-ul e controlat de AUTOPASS_REQUIRE_API_KEY:
false(dev/test, implicit): cerere fara cheie -> contul implicitid=1; o cheie prezenta dar invalida ->401.true(productie): orice/v1/*protejat cere o cheie valida, altfel401.
Auth-ul se aplica pe endpointurile care scriu/sunt legate de cont (au dependinta de cheie):
POST /v1/prezentari, POST /v1/mapari, POST|DELETE /v1/conturi/rar-creds si toate rutele
de import (POST /v1/import, .../column-mapping, .../preview, .../commit,
.../export-failed) — acestea ruleaza pe account_id-ul cheii. GET-urile de monitorizare
(/v1/prezentari, /v1/prezentari/{id}, /v1/nomenclator, /v1/mapari, /v1/audit/export)
sunt momentan neprotejate si globale (nu filtreaza pe cont). Filtrarea pe cont a listarilor
- protejarea lor raman de adaugat (vezi tabelul de mai jos).
Stare implementare
| Capabilitate | Stare | Cum |
|---|---|---|
| Emitere / rotire / revocare / listare chei API | Implementat | CLI python3 -m tools.apikey |
| Auth pe cheie (X-API-Key / Bearer) pe POST-uri + import | Implementat | app/auth.py + flag AUTOPASS_REQUIRE_API_KEY |
| Ingestie + import account-scoped (din cheie) | Implementat | POST /v1/prezentari, POST /v1/import |
| Creds RAR durabile per cont | Implementat | POST /v1/conturi/rar-creds |
| Creare cont nou (service) | De facut / manual | momentan prin INSERT SQL (vezi mai jos); nu exista tool/endpoint dedicat |
| Protejare + filtrare pe cont a GET-urilor de listare | De facut | GET /v1/prezentari, /v1/nomenclator, /v1/audit/export sunt globale acum |
| Self-onboarding web (login email+parola -> emite cheie) | De facut | docs/ROADMAP.md (Etapa 3.3) — neimplementat |
Lifecycle-ul cheilor se face DOAR din CLI, pe masina gateway-ului (admin) — nu exista suprafata HTTP de administrare de securizat. Cheia in clar se afiseaza o singura data la creare/rotire; in DB se pastreaza doar hash-ul SHA-256.
Creare cont + cheie pentru un service nou
Pana la onboarding-ul web, un cont nou se creeaza direct in DB, apoi i se emite o cheie:
# 1. Creeaza contul (numele + CUI sunt informative)
python3 -c "
from app.db import get_connection, init_db
init_db()
c = get_connection()
cur = c.execute(\"INSERT INTO accounts (name, cui) VALUES ('Service Auto SRL', 'RO12345678')\")
print('account_id nou =', cur.lastrowid); c.commit(); c.close()
"
# 2. Emite o cheie API pentru cont (afisata O SINGURA DATA)
python3 -m tools.apikey create --account 2
# -> rfak_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
# Alte operatii
python3 -m tools.apikey list # toate cheile
python3 -m tools.apikey list --account 2 # cheile unui cont
python3 -m tools.apikey rotate --account 2 # revoca cele active + emite una noua
python3 -m tools.apikey revoke --key-id 3 # revoca o cheie dupa id
Creds RAR per cont
Ca worker-ul sa poata trimite pentru un service fara ca fiecare cerere sa-i poarte parola RAR, seteaza credentialele RAR durabile pe cont (criptate Fernet at-rest):
curl -s -X POST http://localhost:8010/v1/conturi/rar-creds \
-H 'X-API-Key: rfak_...' -H 'Content-Type: application/json' \
-d '{"email": "service@exemplu.ro", "password": "parola-rar"}'
Testare prin API (curl)
Exemplele de mai jos arata atat varianta dev (fara cheie -> cont id=1), cat si varianta
service cu cheie API (header X-API-Key). Cand AUTOPASS_REQUIRE_API_KEY=true, cheia e
obligatorie.
# Sanatate (neprotejat)
curl -s http://localhost:8010/healthz | python3 -m json.tool
# Nomenclator RAR (cache local)
curl -s http://localhost:8010/v1/nomenclator
# Coada de prezentari (monitorizare; momentan globala + neprotejata, vezi nota de mai sus)
curl -s http://localhost:8010/v1/prezentari
# Trimite o prezentare -- dev (fara cheie API -> cont id=1)
curl -s -X POST http://localhost:8010/v1/prezentari \
-H 'Content-Type: application/json' \
-d '{
"rar_credentials": {"email": "test@example.ro", "password": "secret"},
"prezentari": [{
"vin": "WAUZZZ8K0AA000001",
"nr_inmatriculare": "B123ABC",
"data_prestatie": "2026-06-15",
"odometru_final": "120000",
"prestatii": [{"cod_op_service": "REVIZIE PERIODICA", "denumire": "REVIZIE PERIODICA"}]
}]
}'
# Trimite o prezentare -- service cu cheie API (account_id curge din cheie)
curl -s -X POST http://localhost:8010/v1/prezentari \
-H 'X-API-Key: rfak_...' -H 'Content-Type: application/json' \
-d '{
"rar_credentials": {"email": "service@exemplu.ro", "password": "parola-rar"},
"prezentari": [{
"vin": "WAUZZZ8K0AA000002",
"nr_inmatriculare": "B456DEF",
"data_prestatie": "2026-06-16",
"odometru_final": "85000",
"prestatii": [{"cod_op_service": "REPARATIE", "denumire": "REPARATIE"}]
}]
}'
# Import fisier prin API pentru un service (multi-tenant: contul vine din cheie)
curl -s -X POST http://localhost:8010/v1/import \
-H 'X-API-Key: rfak_...' -F 'file=@sample_import.xlsx'
Endpointurile complete sunt vizibile si testabile in /docs (Swagger UI). In Swagger,
pune cheia prin butonul "Authorize" sau adauga header-ul X-API-Key.
# Sanatate
curl -s http://localhost:8010/healthz | python3 -m json.tool
# Nomenclator RAR (cache local)
curl -s http://localhost:8010/v1/nomenclator
# Coada de prezentari
curl -s http://localhost:8010/v1/prezentari
# Trimite o prezentare (dev: fara cheie API -> cont id=1)
curl -s -X POST http://localhost:8010/v1/prezentari \
-H 'Content-Type: application/json' \
-d '{
"rar_credentials": {"email": "test@example.ro", "password": "secret"},
"prezentari": [{
"vin": "WAUZZZ8K0AA000001",
"nr_inmatriculare": "B123ABC",
"data_prestatie": "2026-06-15",
"odometru_final": "120000",
"prestatii": [{"cod_op_service": "REVIZIE PERIODICA", "denumire": "REVIZIE PERIODICA"}]
}]
}'
Endpointurile complete sunt vizibile si testabile in /docs (Swagger UI).
Rularea testelor
python3 -m pytest -q
Suita acopera fundatia, securitatea, validarea, parserul de import, masina de stari a worker-ului si fluxul UI de import (E2E cu RAR mock).
Docker / deploy
# 1. Pregateste .env (CRITIC: AUTOPASS_CREDS_KEY partajata intre api si worker)
cp .env.example .env
# completeaza AUTOPASS_CREDS_KEY (vezi comanda de generare de mai sus)
# 2. Porneste API + worker + autoheal
docker compose up --build
docker-compose.yml porneste trei containere: api (port 8010), worker si autoheal
(restarteaza worker-ul cand heartbeat-ul devine invechit). Ambele servicii folosesc acelasi
image si acelasi volum SQLite persistent.
Structura
app/
main.py # FastAPI: API v1 + dashboard + /healthz + /metrics
api/v1/ # router.py (prezentari, nomenclator, mapari) + import_router.py
web/ # routes.py (dashboard + import UI HTMX) + templates/ + static/
worker/ # proces separat: login RAR, send, retry, heartbeat
rar_client.py # client HTTP RAR (login/JWT, postPrezentare, nomenclator)
validation.py # validare continut (T3)
mapping.py # mapare operatie -> cod prestatie + fuzzy lookup
crypto.py # criptare Fernet creds RAR efemere (zero-storage at rest)
schema.sql # schema SQLite
docs/ # contract RAR (sursa de adevar) + ROADMAP (progres + proces)
tests/ # suita pytest
legacy-vfp/ # arhiva Visual FoxPro ROAAUTO (legacy, doar referinta/migrare)
Contract RAR (sursa de adevar): docs/api-rar-contract.md.
Roadmap + proces de dezvoltare: docs/ROADMAP.md.