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) <noreply@anthropic.com>
This commit is contained in:
415
README.md
Normal file
415
README.md
Normal file
@@ -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 `<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
|
||||
|
||||
```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 `<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:
|
||||
|
||||
```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 `<test>`). 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: <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 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).
|
||||
Reference in New Issue
Block a user