Arhiva clasei RarAutoPass (VFP) care declara prestatiile la RAR AUTOPASS, ca baza pentru rescrierea ca gateway central Python/FastAPI. Include: - sursa VFP (.prg) + datele necesare migrarii (mapare_prestatii, prestatii_rar) - spec-ul oficial RAR (txt) - docs/plans/: plan-design-review + plan-eng-review - docs/CONTEXT.md: handoff pentru continuarea in alta sesiune - .gitignore specific Visual FoxPro (ignora artefacte compilate + credentiale) settings.xml (cu parola de test in clar) EXCLUS; vezi settings.xml.example. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
20 KiB
Design: Gateway RAR AUTOPASS (migrare ROAAUTO din VFP în web)
Generat de /office-hours pe 2026-06-14 Mod: Startup (proiect intern / intrapreneurship ISV) Status: DRAFT
Problem Statement
ROA (ERP) are nevoie să declare la RAR AUTOPASS prestațiile de service ale clienților săi
(service-uri auto care rulează ROAAUTO, Visual FoxPro + Oracle), conform Legii 142/2023 și
OM 210/2024. Integrarea există azi în VFP (clasa RarAutoPass), dar nu e încă pusă la clienți —
doar testată pe endpoint-ul de test RAR.
Problema reală a lui Mihai NU e a unui service, ci a unui ISV: nu vrea să redistribuie un .exe VFP
la fiecare client la fiecare corecție. Vrea logica pe un server central, depanabilă central, cu ROAAUTO
ca simplu client subțire care trimite comenzile.
Demand Evidence (validat: client real + lege)
Cel mai tare semnal: un client real a cerut automatizarea introducerii prezentărilor în AUTOPASS — de aici a pornit tot proiectul. Primul plătitor a cerut-o, nu e ipoteză. Status quo înlocuit: interfața web oficială AUTOPASS, unde service-urile introduc manual, prezentare cu prezentare, operația principală — tedios.
Obligație legală reală, nu ipotetică — Legea 142/2023 (registrul electronic al istoricului vehiculelor) obligă operatorii economici autorizați să transmită la RAR:
- la fiecare prestație: VIN + indicația odometrului;
- repararea/înlocuirea odometrului;
- operațiunile principale de reparație la direcție, frânare, structura caroseriei/șasiului și alte sisteme de siguranță.
- Amenzi: informații eronate de la service 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 au aceeași obligație → există piață dincolo de ROA pentru un canal de import (xlsx/csv) ulterior.
Status Quo
Integrare VFP funcțională dar nedistribuită: RarAutoPass (rar_autopass.prg) vorbește direct cu RAR prin
MSXML2.ServerXMLHTTP; mapare în DBF (mapare_prestatii), nomenclator în prestatii_rar, jurnal în
rar_log; UI desktop cu tab-uri. Credențiale RAR în settings.xml pe fiecare stație (text clar).
Constraints
- Stack: Python / FastAPI (ales). Hosting: hibrid — instanță centrală always-on operată de ROA +
Proxmox LXC pentru dev/staging.
romfast.ro/hosting.com (doar PHP) nu găzduiește core-ul. - Open-source pe github.com/romfast (licență recomandată AGPL-3.0).
- ROAAUTO rămâne client subțire (refolosim pattern-ul
MSXML2.ServerXMLHTTP).
Premises (confirmate)
- Migrarea în web se justifică prin nevoia ISV: deploy central, fără redistribuire de exe-uri. ✅
- Cererea e reală și legală (L.142/2023) — clienți ROA + service-uri non-ROA. ✅
- Maparea operație→
codPrestatiee în core (API-ul cereprestatiiînpostPrezentare). ✅ (corectat de utilizator pe baza spec-ului) - ROAAUTO = client subțire; mapare + retry + jurnal pe server. ✅
- Topologie: gateway central, pass-through credențiale, ZERO stocare de parole. ✅
Contract API RAR AUTOPASS (din spec oficial v0.0.1, baza /rar-autopass)
- Auth:
POST /public/login{email, password} →GetUtilizatoriDTO{ token, idUser, ... }. Token JWT atașat caAuthorization: Bearer {token}la toate apelurile securizate. - Nomenclator:
GET /nomenclator/getNomenclatorPrestatii→ listă{codPrestatie, numePrestatie};GET /nomenclator/getPrestatieByCodPrestatie/{cod}. - Prezentări:
POST /prezentari/postPrezentare(payloadPrezentari);GET /prezentari/getAllPrezentari;GET /prezentari/getPrezentare/{id};PATCH /prezentari/markPrezentareAnulataById/{id};PATCH /prezentari/patchPrezentare/{id}. Răspuns:CustomResponseForMapping{ data, message, statusCode }. - Payload
Prezentari:vin,nrInmatriculare,dataPrestatie(date),odometruInitial,odometruFinal,obs,b64Image,sistemReparat,tipPrestatie,status∈{SALVATA,FINALIZATA,ANULATA,UNDEFINED},prestatii: [{codPrestatie, idPrezentare}]. - Cont/roluri (per agent economic): ADMIN creează CLIENT/ADMIN_CLIENT. Nu replicăm asta în gateway.
Recommended Approach — B: gateway central + coadă + token JWT scurt
Flux
ROAAUTO (VFP) ──POST /v1/prezentari {comanda + RAR creds + idempotency_key}──▶ Gateway FastAPI
(citește creds din Oracle clientului) │
├─ rezolvă maparea op→codPrestatie
├─ INSERT submission (PII TRANZITORIU, status=queued)
◀── răspuns imediat: {submissionId, status:queued|needs_mapping} ───────────┘ (dedup pe idempotency_key)
Worker (daemon/task fundal, poll SQLite) ── login RAR → JWT ──▶ postPrezentare ──▶ RAR
│ retry cu backoff în fereastra JWT
└─ la succes: PURJEAZĂ PII, reține doar hash+status+idPrezentare
Browser ──▶ Dashboard ── monitorizare CITITĂ LIVE din RAR (getAllPrezentari), nu din PII local ── + mapări, nomenclator
Gestiunea credențialelor (cheia deciziei #5)
- ROAAUTO trimite
email+passwordRAR la fiecare apel (din Oracle-ul clientului, peste HTTPS). Creds-urile trăiesc doar în itemul de coadă (în memorie/rândul de lucru), folosite de worker pentrulogin, apoi șterse. Parola nu se persistă și se scrubează din loguri ȘI din capturile de excepție/APM. - Worker-ul face
login(nu API-ul) → POST-ul răspunde imediat fără latența RAR. Worker: login → JWT → postPrezentare, retry cu backoff în fereastra JWT-ului. - Onestitate despre robustețe: coada NU aduce reziliență la indisponibilitate RAR de durată — JWT-ul e scurt și
nu ținem parola ca să reluăm peste expirare. Ce aduce coada: răspuns asincron rapid pentru ROAAUTO + jurnal central +
retry pe erori tranzitorii scurte. Durabilitatea reală pe pene lungi stă în ROAAUTO, prin job-ul periodic de
re-push al submission-urilor rămase
error/pending(retrimite cu creds proaspete). Coada acoperă minutele, ROAAUTO acoperă orele. (Dacă măsurarea TTL-ului arată JWT lung, reevaluezi — vezi „The Assignment".)
Idempotență (critic — record legal, fără dubluri)
postPrezentare NU e idempotent, iar avem două bucle de retry (worker + re-push ROAAUTO) → risc de prezentări
duplicate la RAR pentru un record urmărit legal. Soluție:
- ROAAUTO trimite un
idempotency_key= hash(cont + VIN + dataPrestatie + set(codPrestatie)). - Gateway:
UNIQUE(idempotency_key)pesubmissions. Re-trimiterea aceleiași chei NU creează submission nou. - Worker, înainte de a retrimite: dacă submission-ul are deja
idPrezentare(răspuns RAR) → marcheazăsent, nu reapelează.
Mașina de stări a unui submission
queued → sending → { sent | needs_mapping | error }
needs_mapping: operație fărăcodPrestatiemapat → se ține gateway-side, NU se trimite incomplet (API-ul cereprestatii); după ce mapezi, trece înqueued. (≠ VFP-ul de azi care o arunca silențios.)error: eligibil pentru re-push din ROAAUTO (GET /v1/prezentari?status=error).sent: areidPrezentarede la RAR; terminal.
Privacy-first / stateless (mentenanță & răspundere minime)
Decizie: gateway-ul e pur tranzit + interfață cu RAR, NU depozit de date.
- PII-ul prezentării (VIN, km, date) trăiește în SQLite doar tranzitoriu cât e în coadă; la
sentse purjează, rămân doaridempotency_key(hash ireversibil) + status +idPrezentare. Nu se poate reconstrui VIN-ul din hash. - Monitorizarea se citește LIVE din RAR (
getAllPrezentari/getPrezentare) — RAR e sursa de adevăr, nu un jurnal local. - Durabilitatea pe pene lungi stă la margine (Oracle ROAAUTO / fișierul încărcat), nu la tine → re-push din client.
- Fără agregare de date. Datele service-urilor NU se folosesc pentru alte produse. (Eventual, în viitor, doar produs separat cu opt-in explicit + anonimizare, lawyered — niciodată default.) Privacy = argument de adopție, nu doar conformitate.
Componente (un repo, docker compose up)
- API (
app/api/v1) — FastAPI:POST /v1/prezentari(una/mai multe) → validare Pydantic, enqueue, răspuns cusubmissionId.GET /v1/prezentari?status=&data=și/{id}— monitorizare programatică pentru ROAAUTO + re-push.GET /v1/nomenclator,POST /v1/nomenclator/refresh.GET/PUT /v1/mapari— CRUD mapare per cont.PATCH /v1/prezentari/{id}/anulare,/corectie— proxy peste markPrezentareAnulataById / patchPrezentare.- Auth gateway: API key per cont ROA (separată de credențialele RAR ale clientului); cu emitere/rotire/revocare.
- (Amânat, NU în v1:
POST /v1/importxlsx/csv — strat 2 / piață non-ROA.)
- Client RAR (
app/rar_client.py) — portare dinrar_autopass.prg: login+JWT, getNomenclatorPrestatii, postPrezentare, getAllPrezentari, getPrezentare, markPrezentareAnulataById, patchPrezentare.httpx+ retry/backoff. - Worker (daemon / task de fundal) + coadă pe SQLite (
app/worker) — proces pornit non-stop (sau taskasyncioîn aplicația FastAPI), sub Dockerrestart: always. Buclă: ia rândurilestatus='queued', revendică atomic (BEGIN IMMEDIATE; UPDATE … SET status='sending' WHERE id=? AND status='queued'), login RAR, trimite, retry cu backoff, scrie status +idPrezentare. Reacție instant, fără întârziere. Fără Redis, fără arq, fără Postgres. ROAAUTO oferă durabilitatea pe pene lungi (re-push). Atențieb64Image: poate fi mare → stocat ca BLOB sau path pe disc, nu în RAM. - Dashboard (
app/web) — Jinja2 + HTMX (server-rendered, zero build): Monitorizare citită live din RAR (getAllPrezentari) + starea cozii curente (dinsubmissions), Editor mapări, Browser nomenclator. API-first. - SQLite (mod WAL) — înlocuiește DBF, un singur fișier
.db:accounts,api_keys(conturi ROA + chei gateway).operations_mapping(cod_op_service → codPrestatie,auto_send=trimite automat dacă e mapat) ←mapare_prestatii.nomenclator_rar(cache {codPrestatie, numePrestatie}) ←prestatii_rar.submissions(coadă + dedup):idempotency_keyUNIQUE, status, statusCode RAR, eroare,idPrezentare, retry, timestamps. Câmpurile PII (vin, km, dataPrestatie, prestatii) sunt tranzitorii — populate cât equeued/sending, purjate lasent. (≠rar_logcare era jurnal permanent.)- Notă: niciun câmp pentru parole RAR; niciun PII reținut după trimitere. (
import_jobs— doar la xlsx/csv, amânat.)
Client ROAAUTO (VFP) — refactor minim
settings.xmlpăstrează doar URL gateway + API key (nu mai ține mapări/nomenclator).- Credențialele RAR ale clientului se citesc din Oracle (ROA) și se trimit în payload la gateway.
export_comenzi.prgrămâne (citeștecomenzi_service/operatii), dar construiește JSON și facePOST /v1/prezentariîn loc de XML + apel direct RAR.- Dispar din VFP:
Login,UpdateNomenclator,GetCodRarPentruOperatie, maparea,rar_log→ trec în web. - Se adaugă un job periodic „re-push pending" (timer existent din
rar-forms.prgse poate reutiliza).
Approaches Considered
- A — sincron, fără coadă (S, risc mic): ships rapid, dar fără retry autonom. Bun ca prim pas, respins ca țintă.
- B — coadă + token JWT scurt (M, recomandat ✅): robust la indisponibilitate RAR, nu pierzi prestații obligatorii legal.
- C — outbox în Oracle (M/L): cel mai decuplat, dar cere acces gateway→Oracle client (VPN/rețea). Reținut ca opțiune pentru clienți non-ROA / viitor.
Open Questions
- Durata reală a JWT-ului RAR (decide fereastra de retry autonom). De măsurat pe endpoint-ul de test.
sistemReparat/tipPrestatie— valori acceptate (enum nedocumentat în spec). De clarificat cu RAR.- Modelul de cont RAR per client: un singur user RAR per agent economic sau mai mulți (afectează cum mapezi
idUser). - Monetizare/direcție (nedecisă): vezi mai jos — de reluat după ce A→B merge la primul client.
Arhitectura în mare (modelul mental)
Azi VFP face 3 treburi pe FIECARE PC client: (a) ia comanda, (b) mapează + se loghează la RAR, (c) ține jurnalul → de-aia trebuie redistribuit la fiecare corecție. Migrarea = muți (b)+(c) pe un server central al tău:
- ROAAUTO (VFP, la client) = expeditorul — citește comanda + creds RAR din Oracle și le trimite la gateway. Atât.
- Gateway (Python, central) = creierul — primește, mapează, login RAR, trimite, retry, jurnal. Aici faci corecțiile o dată, pentru toți.
- Dashboard web = panoul de control — vezi ce s-a trimis/eșuat, editezi maparea.
Opțiuni de deploy (unde rulează gateway-ul)
| Opțiune | Cum | Cost | Când |
|---|---|---|---|
| LXC Proxmox + Cloudflare Tunnel | container la birou, expus public HTTPS fără IP static / porturi deschise | 0 € | Start + teste (risc: netul/curentul biroului) |
| VPS mic always-on (ex. Hetzner) | același container, mașină care nu cade | ~5 €/lună | Clienți reali / producție (recomandat) |
| romfast.ro / hosting.com | Python via cPanel/Passenger (WSGI) | inclus | ⚠️ FastAPI e ASGI + worker-ul e daemon → shared hosting nepotrivit (shim fragil, fără procese persistente). Doar landing |
Recomandare: start pe LXC + Cloudflare Tunnel (cost 0), mutare pe VPS la clienți. Mutarea = copiezi containerul + fișierul .db.
romfast.ro rămâne doar pentru landing/prezentare, nu țintă de producție (ASGI + worker daemon nu merg pe shared hosting).
- Cod: open-source pe github.com/romfast, AGPL-3.0. Deploy = un container cu FastAPI (uvicorn) + worker-ul ca task de fundal/al doilea proces, sub Docker
restart: always+ un volum SQLite. - Dev/staging: LXC Proxmox. Migrare date:
tools/import_dbf.py(mapare_prestatii.DBF+prestatii_rar.DBFcudbfread).
Teza de produs & direcție SaaS
Teza: 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ă din interfața oficială. Câștigi prin efort minim cerut service-ului, nu prin features. (Funnel-ul „read public gratuit" — RESPINS: nimeni nu vrea să citească nomenclatorul; valoarea e 100% în trimitere.)
Lecția GTM de la demoanaf.ro: a devenit viral nu prin model plătit, ci oferind o variantă reimaginată, simplă, plăcută a unui serviciu oficial greoi/instabil; s-a răspândit în grupurile de Facebook. Echivalentul tău: o cale dramatic mai simplă decât tastarea manuală AUTOPASS, construită rapid cu AI dar pe arhitectură solidă.
Wedge validat: automatizezi tastarea manuală AUTOPASS, începând cu clientul care a cerut-o (cale ROAAUTO/API).
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. Îți rezolvi nevoia.
- Treapta 2 (non-ROA, web upload): import xlsx/csv cu mapare reținută (vezi mai jos) + dashboard. Login web, fără instalare. Primul venit — freemium pe volum (gratis sub N prezentări/lună pt. service mici, plată peste; metrica de preț = prezentări/lună = fix unitatea obligatorie legal).
- Treapta 3 (diferențiere): 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 (mergi indiferent ce software are service-ul); (3) fiabilitatea de conformitate (retry, monitorizare din RAR — nu pierzi o declarație legală); (4) privacy (nu reținem datele lor) — el însuși argument de adopție. Saltul fără rescriere: API key + mapare per cont, zero parole stocate, zero PII reținut.
Adopție în masă & praguri (calibrat pe cifre reale)
Declanșatorul trecerii de la tastarea manuală în RAR la upload = timp salvat / efort de trecere: ~2-4 min/prezentare manual × volum lunar. Clienți reali cunoscuți: 60-80 și 80-100 prezentări/lună → 3-6 ore/lună de tastare = durerea care îi convertește.
- Prima folosire trebuie trivială (upload Excel existent → mapare reținută → trimite, sub 5 min).
- Gratis la volumul lor = fără decizie de achiziție, doar încearcă.
- Freemium pe volum: gratis ~30-40 prezentări/lună (service mici = bază virală, cost ~0 la tine); plată peste prag (banda 1 ≈ 50-150/lună prinde fix clienții actuali — primii bani de la cine a cerut serviciul). Metrica de preț = prezentări/lună = unitatea obligatorie legal.
- Ironie de reținut: service-urile mici au cea mai mică durere (greu de convins, dar virale); volumele mari au cea mai mare durere (cei mai dispuși să plătească). Gratis = achiziție; venit = volume mari.
Import xlsx/csv — UX (stratul SaaS, treapta 2)
Două straturi de mapare, ambele reținute per cont (cheia produsului — „map once, reuse forever"):
- Mapare coloane (schema fișierului → câmpuri canonice): ex. „«Serie șasiu»→VIN, «Index km»→odometruFinal". Reluată automat dacă headerele se repetă.
- Mapare operații (etichetele/codurile service-ului →
codPrestatieAUTOPASS), cu sugestie fuzzy pe denumire.
Flux: upload → recunoaște coloanele (reia maparea) → propune maparea operațiilor (reținută + sugestii) → preview (ce se trimite, rânduri nemapate flag-uite) → „Trimite la RAR" → monitorizare. A 2-a oară: upload → preview → trimite.
Spectru de integrare (același backend): API (POST prezentări, ca ROAAUTO) → drop fișier programat (folder/SFTP/email-to-import) → upload manual în browser (zero instalare). Cine poate, integrează API; cine nu, dă fișier.
Success Criteria
- O prezentare reală trimisă din ROAAUTO prin gateway apare
FINALIZATAla RAR (test), vizibilă în dashboard. - Paritate cu VFP: același
codPrestatierezultat din mapare pe aceleași comenzi. - Reziliență: RAR indisponibil → submission
queued/errorcu retry, ROAAUTO nu se blochează; re-push recuperează. - Securitate: niciun credențial RAR în client/
settings.xmlși niciun câmp de parolă în SQLite. - Privacy: după
sent, în SQLite nu rămâne PII de vehicul (doar hash+status+idPrezentare); monitorizarea vine din RAR.
The Assignment (următorul pas concret)
Pe endpoint-ul de test RAR, măsoară durata de viață a JWT-ului întors de /public/login (fă un login,
apoi postPrezentare la intervale crescătoare până la 401). Numărul ăsta dimensionează fereastra de retry
autonom din worker și decide dacă ai nevoie de job-ul de re-push în ROAAUTO. E o oră de muncă și deblochează
toată decizia de robustețe din B.
What I noticed about how you think
- Ai corectat insight-ul meu „wedge = doar VIN+km" cu „API-ul cere și operațiile de manopelă" — și aveai dreptate, ai citit contractul, nu l-ai presupus. Asta e exact instinctul care face diferența: sursa, nu pitch-ul.
- Ai pus singur problema de custodie a parolelor („nu vreau să salvez parole") înainte să ți-o ridic eu — gândești în termeni de răspundere, nu doar de „merge/nu merge".
- Vezi proiectul ca ISV, nu ca utilizator final: „nu vreau să redistribui la fiecare corecție" e fix raționamentul care justifică web-ul. Mulți ar fi rescris fără să poată articula de ce.