From 9106983dd56436667b01557eb5ead88b8a9fa2cc Mon Sep 17 00:00:00 2001 From: Claude Agent Date: Wed, 17 Jun 2026 06:51:34 +0000 Subject: [PATCH] docs: README cu ghid de testare (browser, API, RAR, conturi/chei API) Adauga README.md: arhitectura, configurare, rulare locala si cu start.sh, testare in browser (dashboard, Swagger, import xlsx/csv), proba trimitere la RAR test + verificare, formate de fisier per cont (signature_coloane), conturi si chei API (CLI tools.apikey, exemple curl cu X-API-Key) si starea reala a auth-ului (POST/import protejate; GET-uri de listare inca globale). Co-Authored-By: Claude Opus 4.8 (1M context) --- README.md | 415 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 415 insertions(+) create mode 100644 README.md diff --git a/README.md b/README.md new file mode 100644 index 0000000..17f11e1 --- /dev/null +++ b/README.md @@ -0,0 +1,415 @@ +# 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`](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` + +```bash +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 `` 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 + +```bash +uvicorn app.main:app --reload --port 8000 +# sau, daca uvicorn nu e pe PATH: +python3 -m uvicorn app.main:app --reload --port 8000 +``` + +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. + +```bash +python3 -m app.worker +``` + +### Pornire rapida cu `start.sh` + +`start.sh` ambaleaza pornirea pe mediu (`test` / `prod`) si rol (`api` / `worker` / `both`): + +```bash +./start.sh test api # API pe :8000, 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 8000 # 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 `` din `settings.xml`). +Pe `test` cu `--send`, creds `` se folosesc automat. Pentru productie reala foloseste +`docker compose` (vezi sectiunea Docker). + +Doua wrappere fixeaza mediul si forwardeaza rolul + optiunile: + +```bash +./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:8000/` | **Dashboard** — stare coada, banner prezentari blocate, stare worker / ultim login RAR, editor mapari operatii, browser nomenclator, sectiune **import fisier** | +| `http://localhost:8000/docs` | **Swagger UI** — API v1 interactiv (incearca endpointurile direct din browser) | +| `http://localhost:8000/healthz` | JSON sanatate: worker viu, ultim login RAR, adancime coada | +| `http://localhost:8000/metrics` | metrici text (submissions pe status) | + +### Fluxul de import fisier (xlsx / csv) din browser + +Pe dashboard, in sectiunea de import: + +1. **Incarca** un fisier `.xlsx` sau `.csv` (drag & drop sau selectare). +2. **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. +3. **Preview** — fiecare rand primeste o stare: `ok`, `needs_mapping`, `needs_data`, + `needs_review`, `already_sent`, `duplicate_in_file`. +4. **Confirma** — gate dur: retastezi numarul exact de randuri `ok` de 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: + +```bash +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: + +```bash +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: + +1. Pune credentialele de test in `settings.xml` (copiaza din `settings.xml.example`, + completeaza blocul ``). Acestea **nu** se comit. + +2. Bag-a prezentari in coada — fie prin import fisier din dashboard, fie prin API + (`POST /v1/prezentari`, vezi mai jos). + +3. Porneste worker-ul cu trimiterea activa: + + ```bash + ./start.sh test worker --send + ``` + + Worker-ul face login la RAR test, ia randurile `queued`, trimite si trece fiecare rand + in `sent` cu `id_prezentare` (id-ul intors de RAR — dovada ca a ajuns) sau in + `needs_data` / `error` cu motivul. + +4. **Vizualizeaza prezentarile trimise** — trei feluri: + + - **Dashboard** (`http://localhost:8000/`) — tabelul de jos arata fiecare submission cu + status (`sent`/`error`/...), `id_prezentare`, cod RAR si eroare. Se actualizeaza singur. + - **API**: `curl -s http://localhost:8000/v1/prezentari` — coada locala cu statusuri. + - **Direct de la RAR** (confirmare independenta ca au ajuns): + + ```bash + ./start.sh test finalizate + ``` + + Face login la RAR test si listeaza prezentarile inregistrate acolo (id, VIN, data, + odometru). Compari `id`-urile cu `id_prezentare` din coada locala: daca se regasesc, + prezentarea a ajuns la RAR. + +> Status `sent` + `id_prezentare` completat = 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 marcheaza `sent` fara 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 implicit `id=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: ` sau `Authorization: Bearer +`) 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 implicit `id=1`; o cheie prezenta + dar invalida -> `401`. +- `true` (productie): orice `/v1/*` **protejat** cere o cheie valida, altfel `401`. + +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) | **In plan** | `docs/plans/plan-treapta2.md` (sect. login web) — 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: + +```bash +# 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): + +```bash +curl -s -X POST http://localhost:8000/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. + +```bash +# Sanatate (neprotejat) +curl -s http://localhost:8000/healthz | python3 -m json.tool + +# Nomenclator RAR (cache local) +curl -s http://localhost:8000/v1/nomenclator + +# Coada de prezentari (monitorizare; momentan globala + neprotejata, vezi nota de mai sus) +curl -s http://localhost:8000/v1/prezentari + +# Trimite o prezentare -- dev (fara cheie API -> cont id=1) +curl -s -X POST http://localhost:8000/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:8000/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:8000/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`. + +```bash +# Sanatate +curl -s http://localhost:8000/healthz | python3 -m json.tool + +# Nomenclator RAR (cache local) +curl -s http://localhost:8000/v1/nomenclator + +# Coada de prezentari +curl -s http://localhost:8000/v1/prezentari + +# Trimite o prezentare (dev: fara cheie API -> cont id=1) +curl -s -X POST http://localhost:8000/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 + +```bash +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 + +```bash +# 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 8000), `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) + planuri + context +tests/ # suita pytest +legacy-vfp/ # arhiva Visual FoxPro ROAAUTO (legacy, doar referinta/migrare) +``` + +Detalii de continuitate intre sesiuni: [`docs/CONTEXT.md`](docs/CONTEXT.md). +Plan executabil: [`docs/plans/plan.md`](docs/plans/plan.md).