docs: contract RAR verificat live + plan unic consolidat
Verificat contractul RAR AUTOPASS pe endpoint-ul de test si compilat sursa de adevar `docs/api-rar-contract.md`. Corectii majore fata de planurile vechi: - JWT TTL = 30h (nu scurt); worker se re-logheaza, retry neplafonat - b64Image optional; tipPrestatie generat de server (nu se trimite) - anulare/corectie prin API inexistente pentru FINALIZATA - needs_data determinist pe R-ODO/I-ODO; reguli validare exacte (VIN/data/nrInm) Rulat plan-eng-review + plan-design-review, apoi consolidat ambele intr-un singur plan executabil `docs/plans/plan.md` (design ca anexa). Outside voice a prins lost-ack double-submit (P1) -> reconciliere inainte de re-send. Re-push din ROAAUTO scos din v1 (durabilitate = SQLite persistent + restart). - mutat fisierele spec oficiale RAR in docs/ - adaugat raspunsul oficial al programatorilor RAR (api-rar-documentatie-oficiala.md) - sterse plan-eng-review.md + plan-design-review.md (consolidate in plan.md) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
321
docs/plans/plan.md
Normal file
321
docs/plans/plan.md
Normal file
@@ -0,0 +1,321 @@
|
||||
# Plan unic — Gateway RAR AUTOPASS (migrare ROAAUTO din VFP în Web API)
|
||||
|
||||
> **Planul executabil, sursă unică.** Consolidează design-ul de produs + planul de implementare,
|
||||
> aliniat la contractul verificat live. Citește-l împreună cu:
|
||||
> - `docs/api-rar-contract.md` — **contractul RAR (sursa de adevăr)**, verificat live 2026-06-15.
|
||||
> - `docs/CONTEXT.md` — continuitate între sesiuni.
|
||||
>
|
||||
> Înlocuiește fostele `plan-eng-review.md` + `plan-design-review.md` (consolidate aici).
|
||||
> Ultima actualizare: 2026-06-15.
|
||||
|
||||
## Cum reiei (sesiune nouă)
|
||||
|
||||
1. `git clone git@gitea.romfast.ro:romfast/rar-autopass.git`
|
||||
2. `cp settings.xml.example settings.xml` și completează credențialele de test RAR (blocul `<test>`). NU se comite.
|
||||
3. Citește `docs/api-rar-contract.md` (contractul) și acest plan.
|
||||
4. Pornește de la **Roadmap de execuție** (mai jos) — pasul blocant următor e **T1**.
|
||||
|
||||
---
|
||||
|
||||
## 1. Problema și rezultatul țintă
|
||||
|
||||
ROAAUTO (Visual FoxPro + Oracle, la fiecare service client) declară azi prestațiile la **RAR AUTOPASS**
|
||||
direct din clasa `RarAutoPass` (`rar_autopass.prg`), prin `MSXML2.ServerXMLHTTP`. Obligație legală
|
||||
(L.142/2023, OM 210/2024). Integrarea e testată doar pe endpoint-ul de test RAR, nepusă la clienți.
|
||||
|
||||
Problema reală e de **ISV**: nu vrei să redistribui un `.exe` VFP la fiecare corecție. Muți logica
|
||||
(mapare + login RAR + jurnal + retry) pe un **gateway central depanabil o dată pentru toți**; ROAAUTO
|
||||
rămâne client subțire. Un client real a cerut automatizarea — primul plătitor, nu ipoteză.
|
||||
|
||||
**Rezultat țintă (treapta 1):** o prezentare reală trimisă din ROAAUTO prin gateway apare `FINALIZATA`
|
||||
la RAR, vizibilă în dashboard, cu retry pe erori tranzitorii și fără a stoca parole.
|
||||
|
||||
## 2. Contract RAR — reguli care guvernează implementarea
|
||||
|
||||
Detalii complete: `docs/api-rar-contract.md`. Esențialul pentru cod:
|
||||
|
||||
- **Endpoint-uri** (verificate live): login `POST /public/login`; nomenclator `GET /nomenclator/getNomenclatorPrestatii`;
|
||||
trimitere `POST /prezentari/postPrezentare`. Bază test `https://apps.rarom.ro/test-rar-autopass`, prod `…/rar-autopass`.
|
||||
- **JWT TTL = 30h** (108000s). Worker-ul se re-loghează la expirare; retry-ul NU e plafonat la 30h.
|
||||
- **Payload** `postPrezentare`: `vin`, `nrInmatriculare`, `dataPrestatie`, `odometruFinal`, `odometruInitial`,
|
||||
`prestatii:[{codPrestatie,idPrezentare:null}]`, `sistemReparat`, `status:"FINALIZATA"`, `obs?`, `b64Image?`.
|
||||
**`tipPrestatie` NU se trimite** (generat de server, `GENERIC`).
|
||||
- **Reguli de validare (RAR le aplică; le replicăm în gateway înainte de enqueue):**
|
||||
- `vin`: 17 caractere majuscule, fără spații/special, **fără O, I, Q** → regex `^[A-HJ-NPR-Z0-9]{17}$`.
|
||||
- `nrInmatriculare`: max 10, litere+cifre, majuscule → `^[A-Z0-9]{1,10}$`.
|
||||
- `dataPrestatie`: ∈ [2024-12-01, azi], TZ `Europe/Bucharest`.
|
||||
- `b64Image`: **opțional**; dacă prezent, base64 valid.
|
||||
- `odometruInitial`: `null` normal; **obligatoriu dacă `prestatii` ∋ `R-ODO` sau `I-ODO`**; `odometruInitial <= odometruFinal`.
|
||||
- `odometruFinal`: trimis ca string (per exemplul oficial).
|
||||
- `sistemReparat`: trimis mereu; default `"null"` în v1.
|
||||
- Normalizează `vin`/`nrInmatriculare` cu `.strip().upper()` înainte de regex (DBF-urile pot avea spații).
|
||||
- **Anulare/corecție prin API: NU există.** API e strict INSERT `FINALIZATA`; `FINALIZATA` nu se anulează
|
||||
(doar `SALVATA`, pe care API nu le produce). Corecția = email `suport.autopass@rarom.ro`.
|
||||
- **Monitorizare:** pe TEST lista finalizate **nu întoarce `prestatii`** (pe prod da, cu filtrare + export Excel).
|
||||
- **Idempotency:** RAR n-are câmp nr. comandă și acceptă duplicate → dedup-ul e responsabilitatea noastră.
|
||||
|
||||
## 3. Arhitectură
|
||||
|
||||
```
|
||||
ROAAUTO (VFP, la client) GATEWAY FastAPI (central, 1 container)
|
||||
citește comanda + creds RAR din Oracle POST /v1/prezentari {comanda + RAR creds + idempotency implicit}
|
||||
──HTTPS──────────────────────────────────────▶ API
|
||||
├─ valid Pydantic (vin 17/fără O,I,Q; nrInm; dată ∈ [2024-12-01,azi] TZ Bucharest)
|
||||
├─ rezolvă op→codPrestatie (operations_mapping)
|
||||
├─ odometruInitial cerut DOAR dacă coduri ∋ R-ODO/I-ODO
|
||||
├─ NU trimite tipPrestatie; sistemReparat="null"
|
||||
├─ calc idempotency_key = hash(conținut canonic)
|
||||
├─ INSERT submission (PII criptat tranzitoriu, queued)
|
||||
◀── {submissionId, status: queued|needs_mapping|needs_data} ──┘ (UNIQUE → dedup, întoarce id existent)
|
||||
|
||||
WORKER (proces separat, restart:always, poll SQLite WAL)
|
||||
claim atomic: BEGIN IMMEDIATE; UPDATE…SET sending WHERE id=? AND status='queued'
|
||||
login RAR → JWT (30h, re-login la expirare) → reconciliere → postPrezentare → retry/backoff
|
||||
succes: scrie idPrezentare(data.id); PURJEAZĂ creds; PII criptat rămâne max 90 zile
|
||||
|
||||
Browser ─▶ Dashboard (Jinja2+HTMX): monitorizare live RAR + coadă + editor mapări + nomenclator + audit CSV
|
||||
+ BANNER alertă submission-uri blocate (singura plasă pe pene > 30h)
|
||||
(Re-push din ROAAUTO: SCOS din v1. Durabilitatea = SQLite pe volum persistent + backup + restart.)
|
||||
```
|
||||
|
||||
### Mașina de stări submission
|
||||
|
||||
```
|
||||
queued → sending → { sent | needs_mapping | needs_data | error }
|
||||
needs_mapping : operație fără codPrestatie mapat → ținut gateway-side, NU trimis incomplet
|
||||
needs_data : prestatii ∋ R-ODO/I-ODO fără odometruInitial, SAU validare RAR eșuată (VIN/dată/nrInm) → ținut
|
||||
error : login creds invalide, RAR 4xx nerecuperabil, sau pană > 30h → ALERTĂ banner (fără re-push automat)
|
||||
sent : are idPrezentare (data.id) RAR; creds purjate; PII criptat max 90 zile; terminal
|
||||
(anulare/corecție: NU există tranziție — FINALIZATA e terminal la RAR; corecția = suport RAR)
|
||||
```
|
||||
|
||||
## 4. Componente (un repo, `docker compose up`)
|
||||
|
||||
1. **API `app/api/v1`** (FastAPI):
|
||||
- `POST /v1/prezentari` (una/mai multe) → Pydantic (validările din §2), mapare, enqueue, `submissionId`.
|
||||
- `GET /v1/prezentari?status=&data=` și `/{id}` — monitorizare programatică (stare coadă + idPrezentare).
|
||||
- `GET /v1/nomenclator`, `POST /v1/nomenclator/refresh`.
|
||||
- `GET/PUT /v1/mapari` — CRUD mapare per cont, cu **sugestie fuzzy** pe denumire (cherry-pick).
|
||||
- `GET /v1/audit/export?from=&to=` — **CSV** cu ce s-a trimis (cherry-pick, leagă reținerea 90 zile).
|
||||
- Auth gateway: **API key per cont ROA** (separată de creds RAR), cu emitere/rotire/revocare.
|
||||
- **Redactare credențiale (CORE):** middleware care garantează că body-ul pe `/v1/prezentari` NU se loghează
|
||||
niciodată și `password` se scrubează din excepții/APM.
|
||||
- *(SCOS din v1: anulare/corecție prin API. Amânat: `POST /v1/import` xlsx/csv — treapta 2.)*
|
||||
2. **Client RAR `app/rar_client.py`** — portare din `rar_autopass.prg` + `rar-forms.prg`: login+JWT,
|
||||
getNomenclatorPrestatii, postPrezentare, getAllPrezentari/getAllPrezentariFinalizate. `httpx` + retry/backoff.
|
||||
**URL-uri din VFP testat** (verificate live). *(NU portăm anulare/corecție.)*
|
||||
3. **Worker `app/worker`** — proces propriu supravegheat (NU task `asyncio` în uvicorn — altfel worker mort lasă
|
||||
containerul „sănătos"), sub Docker `restart: always`. Buclă claim atomic → login → reconciliere → send → retry.
|
||||
- JWT 30h, **re-login la expirare** (retry nu e plafonat la 30h).
|
||||
- **Reconciliere înainte de re-send (anti-duplicat pe răspuns pierdut — P1):** dacă răspunsul RAR se pierde după
|
||||
ce RAR a inserat, rândul rămâne `sending`; un re-send orb ar crea duplicat (RAR acceptă duplicate). Înainte de
|
||||
re-send, worker interoghează lista RAR pe `VIN + dataPrestatie + odometruFinal`; dacă găsește → marchează `sent`
|
||||
cu `idPrezentare` găsit, NU re-trimite. **UNIQUE pe `submissions` NU acoperă acest caz.**
|
||||
- **Lease/timeout pe `sending` orfane** (worker mort mid-POST) → recuperat de reconciliere.
|
||||
- `b64Image` (opțional) mare → BLOB/path pe disc, nu RAM.
|
||||
4. **Dashboard `app/web`** — Jinja2 + HTMX (server-rendered, zero build): monitorizare live RAR + coadă + editor
|
||||
mapări (cu fuzzy) + browser nomenclator + **banner alertă submission-uri blocate**. Stări explicite:
|
||||
empty (coadă goală cu CTA), error (needs_mapping/needs_data cu motiv), **RAR indisponibil → ultima stare a cozii**.
|
||||
5. **SQLite (WAL)** — un fișier `.db` pe **volum persistent + backup**:
|
||||
- `accounts`, `api_keys`.
|
||||
- `operations_mapping` (cod_op_service → codPrestatie, `auto_send`) ← `mapare_prestatii.DBF`.
|
||||
- `nomenclator_rar` (cache {codPrestatie, numePrestatie}) ← `prestatii_rar.DBF`.
|
||||
- `submissions`: `idempotency_key` UNIQUE, status, statusCode RAR, eroare, `idPrezentare`, retry, timestamps.
|
||||
PII (`vin`, `odometru`, `dataPrestatie`, `prestatii`, `b64Image`) **criptat** + `purge_after` (sent+90z).
|
||||
- **Niciun câmp pentru parole RAR.**
|
||||
|
||||
## 5. Securitate & raza de explozie
|
||||
|
||||
Gateway-ul vede parolele RAR ale tuturor clienților în memorie/tranzit. Zero-storage reduce riscul la rest,
|
||||
dar o greșeală de logging scurge parole live, iar AGPL = codul e public. Controale **hard**:
|
||||
- Middleware de redactare — body-uri cu parole niciodată în loguri/APM/excepții.
|
||||
- HTTPS obligatoriu; recomandare TLS pinning din ROAAUTO către gateway.
|
||||
- Parola folosită doar pentru `login` în worker. Cu JWT 30h, după primul login reușit tokenul acoperă retry-urile;
|
||||
creds se șterg. Dacă login-ul eșuează (RAR jos), creds rămân în itemul (criptat) până la primul login reușit.
|
||||
|
||||
### Failure modes registry
|
||||
|
||||
| Codepath | Failure | Rescued | User vede | Logged |
|
||||
|---|---|---|---|---|
|
||||
| postPrezentare cu R-ODO/I-ODO fără odometru init | record fraud-sensibil incomplet | Y (needs_data determinist) | flag dashboard | DA |
|
||||
| dublu-send (răspuns RAR pierdut, rând `sending`) | duplicat la RAR (UNIQUE NU acoperă) | Y (reconciliere VIN+dată+odometru) | nimic (transparent) | DA |
|
||||
| dublu-enqueue (același conținut, 2 cereri API) | submission dublu | Y (idempotency UNIQUE) | „deja înregistrat" | DA |
|
||||
| VIN cu O/I/Q sau ≠17 / dată în afara intervalului | RAR respinge 4xx | Y (validare Pydantic înainte de enqueue) | flag „date invalide" | DA |
|
||||
| login 401 creds greșite | nu se poate trimite | Y (NU retry; error+motiv) | „credențiale RAR invalide" | DA |
|
||||
| pană RAR > 30h (fără re-push) | declarație legală întârziată | Parțial (alertă, NU auto-recovery) | banner + webhook | DA |
|
||||
| worker mort (proces, nu container) | coada se oprește tăcut | Y (proces propriu + /healthz → restart) | banner | DA |
|
||||
|
||||
**Regulă:** fără `except Exception` generic. Fiecare rescue: retry / degradare cu mesaj / re-raise cu context.
|
||||
|
||||
> **Risc acceptat (2026-06-15):** scoaterea re-push-ului lasă penele RAR > 30h fără recuperare automată. Acoperit de
|
||||
> alerta de submission blocat. Re-introducem re-push (timer VFP `OnAutoProcessTimer`) dacă apare în practică o pană > 30h.
|
||||
|
||||
## 6. Client ROAAUTO (VFP) — refactor minim
|
||||
|
||||
- `settings.xml` păstrează doar **URL gateway + API key** (rotește parola de test expusă acum în SVN!).
|
||||
- Creds RAR ale clientului se citesc din **Oracle** și se trimit în payload la gateway peste HTTPS.
|
||||
- `export_comenzi.prg` rămâne, dar construiește JSON și face `POST /v1/prezentari` (nu XML + apel RAR direct).
|
||||
- Dispar din VFP: `Login`, `UpdateNomenclator`, `GetCodRarPentruOperatie`, maparea, `rar_log` → în web.
|
||||
- **ROAAUTO = pur expeditor în v1.** Re-push scos; timer-ul `OnAutoProcessTimer` rămâne disponibil dacă revine.
|
||||
- **Fără atașare poză** (`b64Image` opțional) — un câmp mai puțin de construit în VFP.
|
||||
|
||||
## 7. Migrare date
|
||||
|
||||
`tools/import_dbf.py` (cu `dbfread`) — **dry-run + raport întâi**: rânduri valide, mapări orfane, coduri
|
||||
necunoscute în nomenclator. Confirmi, apoi scrie în SQLite. Surse: `mapare_prestatii.DBF`, `prestatii_rar.DBF`.
|
||||
(`rar_log.DBF` NU se migrează — jurnalul nou e `submissions` + live din RAR.)
|
||||
|
||||
## 8. Observabilitate (cherry-picks)
|
||||
|
||||
- `/healthz` — worker viu + ultimul login RAR reușit + adâncime coadă; pică → semnal de restart.
|
||||
- `/metrics` — submissions pe status, latență send, retry count, backlog needs_mapping/needs_data.
|
||||
- Alertă submission-uri blocate — banner dashboard + webhook/email peste prag (plasă de siguranță legală, acum critică).
|
||||
|
||||
## 9. Deploy
|
||||
|
||||
- Start: **LXC Proxmox + Cloudflare Tunnel** (0 €, teste). Producție: **VPS mic always-on** (~5 €/lună).
|
||||
- Un container: uvicorn (API) + worker (proces 2), `restart: always`, **volum SQLite persistent + backup**.
|
||||
Mutare = copiezi container + `.db`.
|
||||
- Open-source pe github.com/romfast, **AGPL-3.0**. ⚠️ Decide **CLA / copyright assignment din ziua 1** dacă vrei
|
||||
opțiunea de dual-license (open core + hosted comercial). Relicențierea fără CLA cere acordul tuturor contributorilor.
|
||||
- romfast.ro/hosting.com = doar landing (ASGI + worker daemon nu merg pe shared hosting).
|
||||
|
||||
---
|
||||
|
||||
## 10. Roadmap de execuție (pașii rămași)
|
||||
|
||||
Nimic din cod nu e scris încă (`app/`, `tools/` nu există). Ordine recomandată:
|
||||
|
||||
- [ ] **T1 (P1, BLOCANT) — verificare contract live.** Un singur `POST /prezentari/postPrezentare` real pe **test**
|
||||
cu o prezentare minimă validă (status FINALIZATA, sistemReparat `"null"`, fără b64Image/odometruInitial). Capturează:
|
||||
mesajele de eroare exacte pe constrângeri (VIN cu O/I/Q, dată < 2024-12-01), forma exactă a răspunsului, `data.id`,
|
||||
acceptarea `sistemReparat:"null"`. **Fă-l ÎNAINTE de a construi suprafața** — poate invalida design-ul validării/erorilor.
|
||||
Verify: 200 + record vizibil în `getAllPrezentariFinalizate`.
|
||||
- [ ] **T5 (P1) — `tools/import_dbf.py`** dry-run + raport pe `mapare_prestatii.DBF` / `prestatii_rar.DBF`, apoi import în SQLite.
|
||||
Verify: raport rânduri valide/orfane/coduri necunoscute; import idempotent.
|
||||
- [ ] **Schelet repo** — `app/api/v1`, `app/rar_client.py`, `app/worker`, `app/web`, SQLite (WAL), `docker compose up`, `/healthz` verde.
|
||||
- [ ] **T3 (P1) — validare Pydantic completă** (VIN `^[A-HJ-NPR-Z0-9]{17}$`, nrInm, dată ∈ [2024-12-01,azi] TZ Bucharest,
|
||||
R-ODO/I-ODO→odometruInitial, `odometruInitial<=odometruFinal`, normalize strip/upper). Verify: unit tests per regulă.
|
||||
- [ ] **T4 (P1) — payload builder** cu `status:"FINALIZATA"`, `sistemReparat:"null"`, fără `tipPrestatie`, `odometruFinal` string.
|
||||
Verify: snapshot payload == exemplul oficial din contract.
|
||||
- [ ] **T2 (P1) — `app/worker` reconciliere** VIN+dată+odometru înainte de re-send pe `sending` + lease/timeout orfane.
|
||||
Verify: test integration — răspuns pierdut simulat → fără duplicat la RAR.
|
||||
- [ ] **T6 (P2) — worker proces/container propriu supravegheat;** `/healthz` pică → restart. Verify: worker omorât → restart automat.
|
||||
- [ ] **T7 (P2) — deploy:** SQLite pe volum persistent numit + backup (singura copie durabilă, re-push scos).
|
||||
Verify: recreare container → coada supraviețuiește.
|
||||
- [ ] **Dashboard** (Jinja2+HTMX) cu stările empty/error/RAR-indisponibil + banner alertă. Apoi `/design-review` pe UI-ul live.
|
||||
|
||||
### De decis ulterior (urmărit, nu blocant)
|
||||
- **[P2]** Defer criptare-at-rest + purjare 90z până după primul postPrezentare real reușit? (gold-plating vs. privacy-argument-de-adopție).
|
||||
- **[P2]** Gate pe `auto_send` pentru operații nou-mapate / valori neobișnuite — un record `FINALIZATA` eronat e permanent.
|
||||
|
||||
## 11. Verificare (end-to-end)
|
||||
|
||||
1. ✅ Spike „The Assignment" (JWT 30h, b64Image opțional, tipPrestatie server-gen, R-ODO/I-ODO) — `docs/api-rar-contract.md`.
|
||||
2. T1: `postPrezentare` real pe test → 200 + `data.id`.
|
||||
3. `import_dbf.py --dry-run` raport corect; apoi import confirmat.
|
||||
4. `docker compose up`; `/healthz` verde.
|
||||
5. `POST /v1/prezentari` cu o comandă reală (test) → `submissionId`, worker trimite, apare `FINALIZATA` la RAR + dashboard.
|
||||
6. Re-trimite aceeași comandă identică → același `submissionId` (idempotency), NU dublă la RAR.
|
||||
7. Operație nemapată → `needs_mapping`; `prestatii` ∋ R-ODO/I-ODO fără `odometruInitial` → `needs_data`;
|
||||
VIN cu O/I/Q sau dată < 2024-12-01 → respins la validare, NU ajunge la RAR.
|
||||
8. Oprește RAR / forțează 5xx → submission `error`, worker reia în fereastra de re-login; pană > 30h → alertă banner.
|
||||
9. Simulează răspuns pierdut → reconcilierea previne duplicatul.
|
||||
10. SQLite n-are câmp parolă; după `sent`, PII criptat + `purge_after`; logurile n-au parole.
|
||||
11. Teste: unit (mapare, idempotency hash, validare VIN/dată/nrInm, R-ODO→odometruInitial), integration (claim atomic,
|
||||
retry/backoff, reconciliere), E2E pe endpoint test RAR.
|
||||
|
||||
## 12. NOT in scope (amânat, cu motiv)
|
||||
|
||||
- **Re-push din ROAAUTO** — scos din v1; worker + alertă acoperă. Re-introducem dacă apare pană RAR > 30h.
|
||||
- **Anulare/corecție prin API** — `FINALIZATA` nu se anulează/corectează prin API; corecția = suport RAR.
|
||||
- **Atașare poză odometru în ROAAUTO** — `b64Image` opțional, nu în treapta 1.
|
||||
- `POST /v1/import` xlsx/csv + UX mapare coloane — treapta 2 (piață non-ROA). Motor identic, fără rescriere.
|
||||
- Modelul de conturi RAR (addClient/roluri) — rămâne la RAR.
|
||||
- Outbox în Oracle (Approach C) — pentru clienți non-ROA / viitor, cere acces gateway→Oracle.
|
||||
- Agregare/produse din datele service-urilor — niciodată default; doar opt-in + anonimizare + lawyered.
|
||||
- Redis/arq/Postgres — SQLite WAL + un worker acoperă volumul (60-100 prezentări/lună/client).
|
||||
|
||||
## 13. Open questions rămase
|
||||
|
||||
1. ~~Sursa pozei odometrului~~ — închis (b64Image opțional).
|
||||
2. ~~`tipPrestatie` / JWT TTL~~ — închis (server-generated; 30h). Rămâne: ce valori reale acceptă `sistemReparat` în afară de `"null"`.
|
||||
3. Un singur user RAR per agent economic sau mai mulți (`idUser`/`idAgent` — afectează filtrarea monitorizării).
|
||||
4. Monetizare/direcție SaaS — de reluat după ce prima prezentare reală merge la primul client.
|
||||
|
||||
## 14. Decizii blocate (nu le re-deschide fără motiv)
|
||||
|
||||
- Idempotency = **hash de conținut pe server**, UNIQUE (RAR n-are câmp nr. comandă, acceptă duplicate) + **reconciliere
|
||||
înainte de re-send** pentru răspuns pierdut.
|
||||
- **Reținere temporară 90 zile** a payload-ului **criptat**, apoi purjare (defensibilitate vs privacy).
|
||||
- Odometru: **strict + `needs_data`**, declanșat determinist de `R-ODO`/`I-ODO`.
|
||||
- **Re-push scos din v1**; **anulare/corecție API scoase** (FINALIZATA).
|
||||
- URL-urile RAR: **sursa de adevăr = VFP testat / contract verificat**, NU spec-ul vechi (typo-uri).
|
||||
- Cherry-picks v1: alertă submission-uri blocate (critică), `/healthz`+`/metrics`, sugestie fuzzy mapare, export audit CSV.
|
||||
|
||||
---
|
||||
|
||||
# ANEXĂ — Rațiunea de produs & direcția SaaS
|
||||
|
||||
> Context de produs (din `/office-hours`), păstrat pentru deciziile de prioritizare. Nu blochează execuția treptei 1.
|
||||
|
||||
## Demand evidence (validat: client real + lege)
|
||||
|
||||
Un **client real a cerut automatizarea** introducerii prezentărilor în AUTOPASS — de aici a pornit proiectul.
|
||||
Status quo înlocuit: interfața web oficială AUTOPASS, unde service-urile introduc **manual, prezentare cu prezentare**.
|
||||
Obligație legală reală (L.142/2023): la fiecare prestație VIN + odometru; reparare/înlocuire odometru; operațiuni
|
||||
principale la direcție, frânare, structura caroseriei/șasiului și alte sisteme de siguranță. Amenzi: info eronate
|
||||
1.000–2.000 lei; manipulare odometru până la 5.000 lei / penal. Implică **toți** clienții ROA + **mii de service-uri
|
||||
non-ROA** cu aceeași obligație → piață dincolo de ROA pentru un canal de import (xlsx/csv) ulterior.
|
||||
|
||||
## Teza de produs
|
||||
|
||||
*Cel mai ușor mod de a băga operațiile de service în AUTOPASS* — din ROAAUTO (API), din alte aplicații, sau din
|
||||
fișiere — în loc de tastarea manuală. Câștigi prin **efort minim cerut service-ului**, nu prin features.
|
||||
Lecția GTM (demoanaf.ro): viral prin variantă **reimaginată, simplă** a unui serviciu oficial greoi.
|
||||
|
||||
## Trepte (același motor — mapare + coadă + trimitere + monitorizare — fără rescriere)
|
||||
|
||||
- **Treapta 1 (acum):** core-ul pentru clientul care a cerut + clienții ROA, prin ROAAUTO.
|
||||
- **Treapta 2 (non-ROA, web upload):** import xlsx/csv cu **mapare reținută** (map once, reuse forever) + dashboard.
|
||||
Login web, fără instalare. Freemium **pe volum** (gratis ~30-40 prezentări/lună; plată peste prag). Metrica de preț =
|
||||
prezentări/lună = unitatea obligatorie legal.
|
||||
- **Treapta 3:** integrări mai adânci + sugestii AI de mapare (eventual conector MCP).
|
||||
|
||||
**Moat:** (1) mapările reținute per service cresc costul de plecare; (2) lățimea integrării; (3) fiabilitatea de
|
||||
conformitate (retry, monitorizare din RAR); (4) **privacy** (nu reținem datele lor) — argument de adopție.
|
||||
|
||||
## Adopție & praguri (cifre reale)
|
||||
|
||||
Clienți reali cunoscuți: **60-80** și **80-100 prezentări/lună** → ~2-4 min/prezentare manual × volum = **3-6 ore/lună**
|
||||
de tastare = durerea care convertește. Prima folosire trivială (upload → mapare reținută → trimite, sub 5 min).
|
||||
Gratis la volumul lor = fără decizie de achiziție. Service-urile mici = bază virală (durere mică); volumele mari =
|
||||
cei mai dispuși să plătească.
|
||||
|
||||
## Import xlsx/csv — UX (treapta 2)
|
||||
|
||||
Două straturi de mapare, **ambele reținute per cont**: (1) mapare coloane (schema fișier → câmpuri canonice),
|
||||
(2) mapare operații (etichete service → `codPrestatie`, cu sugestie fuzzy). Flux: upload → recunoaște coloanele →
|
||||
propune maparea → **preview** (rânduri nemapate flag-uite) → „Trimite la RAR" → monitorizare.
|
||||
Spectru de integrare (același backend): API → drop fișier (folder/SFTP/email-to-import) → upload manual în browser.
|
||||
|
||||
## Opțiuni de deploy
|
||||
|
||||
| Opțiune | Cost | Când |
|
||||
|---|---|---|
|
||||
| LXC Proxmox + Cloudflare Tunnel | 0 € | Start + teste (risc: net/curent birou) |
|
||||
| VPS mic always-on (ex. Hetzner) | ~5 €/lună | Clienți reali / producție (recomandat) |
|
||||
| romfast.ro / shared hosting | inclus | ⚠️ Doar landing (ASGI + worker daemon nepotrivite pe shared) |
|
||||
|
||||
---
|
||||
|
||||
## What already exists (reuse din VFP)
|
||||
|
||||
| Sub-problemă | Reuse |
|
||||
|---|---|
|
||||
| Contract RAR (login/JWT, nomenclator, postPrezentare) | `rar_autopass.prg`, `rar-forms.prg:655,720` → `rar_client.py` |
|
||||
| Mapare op→codPrestatie + `auto_send` | `GetCodRarPentruOperatie` → `operations_mapping` |
|
||||
| Export (oglindă treapta 2) | `btnExportExcel.Click` (`rar_advanced.prg`) |
|
||||
| Migrare DBF | `import_dbf.py` citește direct cele `.DBF` |
|
||||
| Timer (dacă revine re-push) | `OnAutoProcessTimer`/`nTimerHandle` (`rar-forms.prg`) — nefolosit în v1 |
|
||||
</content>
|
||||
Reference in New Issue
Block a user