Files
rar-autopass/docs/api-rar-contract.md
Claude Agent c9f9a1ca0e feat(5.16+5.17): tipografie/antet branded + tipuri cont, planuri si enforcement
PRD 5.16 — propagare design finalizata (system font stack, fara IBM Plex self-hostat):
- US-001/002/008: tokeni --font-ui/--font-mono (system stack) + scala --fs-*; zero
  @font-face si zero /static/fonts/; landing aliniat la acelasi stack
- US-003: RAR online = dot compact in antet + meniu burger; banda rosie DOAR pe blocat
  (invariant zero-silent-failures pastrat)
- US-010: antet "ROMFAST AUTOPASS" + nume service + /login brandeit 2 coloane + badge plan;
  meniu burger cu separatoare; gate strict pe is_authenticated
- US-011: selector tema pill icon+eticheta (reuse THEMES)
- US-004/005/006/007: bug-fix editor prestatii (picker cod+denumire, add_extra in mod
  operatii, cod ales se salveaza fara "+", Renunta inchide via closest)
- US-012/013: landing Autentificare->/login; wizard import colapsat + 4 pasi pe tokeni
- fix VERIFY E2E: contoare duplicate pe 390px (inline display:flex batea @media) -> CSS + test-lock

PRD 5.17 — tipuri de cont + trial Pro 30z + enforcement DUR:
- US-001/002/008: accounts.tier + trial_until (migrare aditiva defensiva); app/plans.py
  sursa unica (PLANS, FREE_MONTHLY_LIMIT=60, effective_tier(now injectabil), monthly_usage,
  CONSUMED_STATUSES); create_account trial Pro 30z; CLI set-tier (protejat id=1, audit)
- US-003/004/005: enforce volum 60/luna INAINTE de build_key pe ambele canale
  (PLAN_LIMITA_LUNARA, 3 niveluri + log_event); gate API Pro+ (PLAN_FARA_API 403 actionabil);
  valideaza/nomenclator raman permise; downgrade lazy; flag AUTOPASS_ENFORCE_PLANS (kill-switch)
- US-006: badge plan antet + linie burger + consum N/60 + warn>=80% + 6 stari + copy RO
  pluralizat + banner one-time trial->Gratuit + pagina Cont

Regresie: 1380 passed, 0 failed, 1 deselected (live). E2E browser pe 390/1280 confirmat.
Backend trimitere (worker/masina stari/idempotenta/contract RAR) NEATINS. Lucrul 5.18
(corpus kNN) ramane separat, necomis.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-29 06:02:40 +00:00

29 KiB

Contract RAR AUTOPASS — sursa de adevăr (verificat live)

Acesta este documentul autoritativ pentru contractul API RAR AUTOPASS. Înlocuiește presupunerile din planurile vechi acolo unde diferă. Dacă un plan contrazice acest fișier, acest fișier are dreptate.

Surse:

  • docs/api-rar-documentatie-oficiala.md — răspuns oficial de la programatorii RAR.
  • Verificare live pe endpoint-ul de test (/public/login + getNomenclatorPrestatii), 2026-06-15.
  • Cod VFP testat (rar_autopass.prg) — confirmat ca URL-uri corecte.

Endpoint-uri

Mediu Bază
TEST (integrare — doar aici se testează) https://apps.rarom.ro/test-rar-autopass
PRODUCȚIE https://apps.rarom.ro/rar-autopass
Swagger test https://apps.rarom.ro/test-rar-autopass/swagger-ui/index.html

⚠️ …/v3/api-docs, …/v2/api-docs, …/swagger-resources întorc 403 (nu se pot descărca programatic). Swagger-ul nu permite autentificare directă — testarea reală se face din cod / Postman cu credențialele de test.

Rute confirmate

Operație Metodă + cale Stare
Login POST /public/login verificat live
Nomenclator prestații GET /nomenclator/getNomenclatorPrestatii verificat live (200)
Adăugare prezentare POST /prezentari/postPrezentare din VFP testat + doc oficial

Nota: doc-ul oficial citează operationId-ul Swagger getPrestatiiNomUsingGET, dar calea reală e /nomenclator/getNomenclatorPrestatii (cea din VFP). /nomenclator/getPrestatiiNom întoarce 403 — nu o folosi. operationId ≠ path.

⚠️ Header obligatoriu pe TOATE apelurile: User-Agent

WAF-ul RAR întoarce 403 Forbidden („Request forbidden by administrative rules") la orice request fără header User-Agent de browser — inclusiv /public/login și chiar swagger-ui. curl/clienți fără UA = blocați la WAF înainte de a ajunge la aplicație. MSXML2.XMLHTTP din VFP trimite un UA implicit, de aceea VFP-ul merge.

→ Gateway-ul (app/rar_client.py) trebuie să seteze User-Agent pe fiecare apel (ex. Mozilla/5.0). Confirmat live 2026-06-15: fără UA → 403; cu UA + creds greșite → 401; cu UA + creds corecte → 200.

Autentificare

POST /public/login body {"email": "...", "password": "..."} → 200, JSON cu câmpuri:

idUser, idAgent, cui, nume, prenume, email, token, activationToken,
activ, dataActivarii, dataSfarsit, blocat, expirat, tokenExists, authorities
  • Tokenul JWT se atașează la apelurile securizate: Authorization: Bearer {token}.
  • JWT claims (decodate live): { jti: "mobileJWT", sub: <email>, authorities: ["ADMIN"], iat, exp }.
  • ⚠️ JWT TTL = 108000 secunde = 30 de ORE (exp - iat). NU e un token scurt. Vezi „Corecții față de planuri" #1 — schimbă strategia de robustețe a worker-ului.
  • idUser vine din răspunsul de login (ex. live: 6766); idAgent poate fi null la login, dar serverul atribuie idAgent pe prezentare (vezi exemplul de răspuns oficial: idAgent: 1587).

postPrezentare — payload (request)

Câmpurile marcate OPTIONAL pot lipsi; restul sunt obligatorii. tipPrestatie NU se trimite (îl generează serverul).

{
  "vin": "XXXXXXXXXXXXXXXXX",
  "nrInmatriculare": "B999GEN",
  "dataPrestatie": "2024-07-25",
  "odometruFinal": "9999999",
  "odometruInitial": null,
  "prestatii": [
    { "codPrestatie": "OE-1", "idPrezentare": null },
    { "codPrestatie": "OE-2", "idPrezentare": null }
  ],
  "sistemReparat": "null",
  "status": "FINALIZATA",
  "obs": "TEST",
  "b64Image": "UklGR...."
}
Câmp Obligatoriu Note
vin DA 17 caractere, MAJUSCULE, fără spații/caractere speciale, fără literele O, I, Q
nrInmatriculare DA max 10 caractere, litere + cifre, MAJUSCULE, fără spații/caractere speciale
dataPrestatie DA format YYYY-MM-DD; nu mai devreme de 2024-12-01, nu mai târziu de azi
odometruFinal DA indicația km la final (exemplul oficial îl trimite ca string "9999999")
odometruInitial condiționat null normal; OBLIGATORIU dacă prestatii conține R-ODO sau I-ODO (indicația dinainte de reparație/schimbare)
prestatii DA listă {codPrestatie, idPrezentare:null}; codurile din nomenclator
status DA întotdeauna "FINALIZATA" prin API
sistemReparat DA (poate fi "null") exemplul oficial trimite string-ul "null"; valori reale nedocumentate (vezi Open Q)
obs NU (OPTIONAL) text liber
b64Image NU (OPTIONAL) poza odometrului; nu mai e obligatorie; dacă se atașează, trebuie format base64 valid

postPrezentare — răspuns (success) — VERIFICAT LIVE 2026-06-15

Răspuns real pe contul nostru de test (record creat data.id=68514):

{
  "statusCode": 200,
  "message": "Prezentare adaugata cu succes",
  "data": {
    "id": 68514,
    "dataPrestatie": "2026-06-15",
    "vin": "WVWZZZ1KZAW000123",
    "odometruFinal": 123456,
    "idAgent": 40,
    "tipPrestatie": "GENERIC",
    "odometruInitial": null,
    "idUser": 6766,
    "sistemReparat": "null",
    "obs": "TEST GATEWAY",
    "nrInmatriculare": "B999TST",
    "listaPrestatii": null,
    "status": "FINALIZATA",
    "prestatii": [
      { "idPrezentare": 68514, "codPrestatie": "OE-1" },
      { "idPrezentare": 68514, "codPrestatie": "OE-2" }
    ],
    "b64Image": null
  }
}
  • data.id = ID-ul prezentării la RAR (de reținut ca idPrezentare în submission).
  • prestatii[].idPrezentare == data.id (live: ambele 68514). Exemplul vechi sugera un număr separat mai mare (599950 vs 59950) — fals, e același id.
  • idAgent = atribuit de server (login a întors null, răspunsul are 40).
  • odometruFinal trimis ca string "123456"întors ca număr 123456 (server normalizează).
  • sistemReparat:"null" acceptat; b64Image omis → null; odometruInitial:null OK.
  • Câmp extra nedocumentat în răspuns: listaPrestatii: null (prezent lângă prestatii).
  • tipPrestatie = "GENERIC"generat de server, nu input client.

postPrezentare — răspuns (eroare validare) — VERIFICAT LIVE 2026-06-15

HTTP 400. data este un ARRAY de {field, message} (NU string), un element per eroare:

{
  "statusCode": 400,
  "message": "Validare eșuată pentru cererea de prezentare.",
  "data": [
    { "field": "vin", "message": "VIN trebuie să aibă exact 17 caractere, fara spatii sau caractere speciale, litere invalide :  O, I, Q." },
    { "field": "vin", "message": "VIN conține caractere invalide, spatii, sau  O, I, Q." }
  ]
}

Mesaje exacte capturate live (de mapat în UI/loguri gateway):

Constrângere încălcată field message (exact)
VIN cu O/I/Q vin VIN trebuie să aibă exact 17 caractere, fara spatii sau caractere speciale, litere invalide : O, I, Q. + VIN conține caractere invalide, spatii, sau O, I, Q.
dataPrestatie < 2024-12-01 dataPrestatie Data prestatiei nu poate fi anterioara datei de 01.12.2024.
dataPrestatie în viitor dataPrestatie Data prestatiei nu poate fi în viitor.

→ Gateway: parsează data ca listă de erori de câmp (nu citi data.message). Pe success data e obiect; pe 400 data e array — discriminează după statusCode/HTTP code.

Reguli de validare (server-side RAR, de aplicat și în gateway)

Aplicate deja pe ambele medii (test + producție):

  1. dataPrestatie: >= 2024-12-01 și <= azi.
  2. VIN: exact 17 caractere, majuscule, fără spații/caractere speciale, fără O, I, Q.
  3. nrInmatriculare: max 10 caractere, litere + cifre, majuscule, fără spații/caractere speciale.
  4. b64Image: dacă e prezent, trebuie base64 valid.
  5. odometruInitial: obligatoriu dacă prestatii conține R-ODO sau I-ODO.

→ Acestea devin reguli Pydantic exacte în app/api. Validează la gateway înainte de enqueue (stare needs_data) ca să nu primești 4xx de la RAR.

Envelope de eroare imbogatit (PRD 5.4)

Forma unui obiect de eroare

Incepand cu PRD 5.4, fiecare obiect de eroare returnat de gateway contine 6 chei:

Cheie Tip Rol Back-compat
field string | null Campul care a generat eroarea (null daca eroarea e globala) DA — existent anterior
message string Mesajul scurt (identic cu cauza cand e disponibila, altfel problema) DA — existent anterior
cod string Identificator stabil de tip eroare (ex. VIN_FORMAT). Camp nou. NU — adaugat 5.4
problema string Ce s-a intamplat — descriere scurta, inteligibila pentru utilizator NU — adaugat 5.4
cauza string De ce a aparut eroarea — concret; pentru erorile RAR 400, mesajul exact de la RAR (passthrough) NU — adaugat 5.4
fix string Ce trebuie facut pentru remediere NU — adaugat 5.4

Exemplu JSON concret (eroare VIN invalid, returnat de POST /v1/prezentari/valideaza):

{
  "field": "vin",
  "message": "VIN trebuie sa aiba exact 17 caractere majuscule, fara spatii/caractere speciale si fara O, I, Q.",
  "cod": "VIN_FORMAT",
  "problema": "VIN invalid",
  "cauza": "VIN trebuie sa aiba exact 17 caractere majuscule, fara spatii/caractere speciale si fara O, I, Q.",
  "fix": "Verifica VIN-ul pe talon (pozitia E) sau pe caroserie: exact 17 caractere majuscule, fara spatii si fara literele O, I, Q."
}

Nota de back-compat

Cheile field si message sunt pastrate neschimbate pe toate raspunsurile. Cheile cod, problema, cauza, fix sunt aditive (camp nou in plus). Clientii care citesc doar field/message (sau error/message la import) continua sa functioneze fara modificare.

Unde apare envelope-ul imbogatit

1. POST /v1/prezentari/valideaza (dry-run)

Campul erori (array) si campul nemapate (array) din raspuns contin obiecte cu toate cele 6 chei.

2. submissions.rar_error (stocat in DB, vizibil prin GET /v1/prezentari/{id} si in dashboard)

Campul rar_error e superset al formei de mai sus si variaza cu starea submission-ului:

  • needs_data — array de obiecte {field, message, cod, problema, cauza, fix}:
    [
      {
        "field": "dataPrestatie",
        "message": "Data prestatiei nu poate fi anterioara datei de 01.12.2024.",
        "cod": "RAR_VALIDARE",
        "problema": "RAR a respins prezentarea",
        "cauza": "Data prestatiei nu poate fi anterioara datei de 01.12.2024.",
        "fix": "Corecteaza campul semnalat de RAR (vezi cauza) si reincearca; detaliile exacte sunt in mesajul tehnic RAR."
      }
    ]
    
  • needs_mapping (cod nemapat): obiect cu cheile unmapped (array), cod, problema, cauza, fix:
    {
      "unmapped": ["SCHIMB_ULEI_COMPLET"],
      "cod": "COD_NEMAPAT",
      "problema": "Lipseste codul RAR al operatiei",
      "cauza": "Operatia SCHIMB_ULEI_COMPLET nu are un cod RAR mapat.",
      "fix": "Alege codul RAR pentru aceasta operatie in tab-ul Mapari (ai sugestii automate)."
    }
    
  • needs_mapping cu auto_send oprit: obiect cu auto_send, cod: "AUTO_SEND_OPRIT", problema, cauza, fix.
  • Eroare RAR 400: array imbogatit cu cod: "RAR_VALIDARE" pe fiecare element.
  • Eroare RAR 401 (creds invalide): obiect cu cod: "RAR_CREDS_INVALIDE", problema, cauza, fix.

3. Erori de import (POST /v1/import, preview, commit)

Campul detail din raspunsurile de eroare este superset: contine cheile vechi error/message plus cod, problema, cauza, fix.

Exceptii din scope 5.4: erorile de login/signup si CSRF raman mesaje plate (fara envelope imbogatit).

Tabel cod → problema / fix (toate codurile din app/errors.CATALOG)

Validare date prestatie

Cod Problema Fix
VIN_FORMAT VIN invalid Verifica VIN-ul pe talon (pozitia E) sau pe caroserie: exact 17 caractere majuscule, fara spatii si fara literele O, I, Q.
NR_INMATRICULARE_FORMAT Numar de inmatriculare invalid Foloseste doar litere si cifre majuscule, maxim 10 caractere, fara spatii sau cratima (ex. B123ABC).
DATA_FORMAT Data prestatiei in format gresit Scrie data ca AAAA-LL-ZZ (ex. 2026-06-22).
DATA_PREA_VECHE Data prestatiei prea veche RAR accepta prestatii doar incepand cu 01.12.2024; verifica data prestatiei.
DATA_VIITOR Data prestatiei in viitor Data prestatiei nu poate fi dupa ziua de azi; corecteaza data.
ODOMETRU_FINAL_FORMAT Odometru final invalid Scrie kilometrajul final ca numar intreg, fara zecimale sau text (ex. 145000).
ODOMETRU_INITIAL_LIPSA Lipseste odometrul initial Prestatiile R-ODO / I-ODO cer kilometrajul initial; completeaza-l.
ODOMETRU_INITIAL_FORMAT Odometru initial invalid Scrie kilometrajul initial ca numar intreg, fara zecimale sau text.
ODOMETRU_INITIAL_ORDINE Odometru initial mai mare decat finalul Kilometrajul initial trebuie sa fie mai mic sau egal cu cel final; verifica cele doua valori.
PRESTATII_GOALE Nicio prestatie Adauga cel putin o prestatie cu cod RAR valid.
B64_INVALID Imaginea nu este base64 valid Trimite imaginea codata base64 corect, sau omite campul daca nu ai imagine.

Mapare operatie

Cod Problema Fix
COD_NEMAPAT Lipseste codul RAR al operatiei Alege codul RAR pentru aceasta operatie in tab-ul Mapari (ai sugestii automate).
AUTO_SEND_OPRIT Necesita confirmare manuala Codul e mapat cu trimitere automata oprita; verifica randul si pune-l manual in coada.

Erori RAR (raspuns live de la RAR)

Cod Problema Fix
RAR_VALIDARE RAR a respins prezentarea Corecteaza campul semnalat de RAR (vezi cauza) si reincearca; detaliile exacte sunt in mesajul tehnic RAR.
RAR_EROARE_SERVER RAR a esuat la inregistrarea prezentarii RAR a raspuns cu o eroare de server (vezi cauza). Trimiterea NU se reincearca automat si NU a fost confirmata — verifica datele (in special codul prestatiei) si re-trimite dupa corectare.
RAR_CREDS_INVALIDE Credentiale RAR invalide Verifica email-ul si parola contului RAR in tab-ul Cont; trimiterea nu se reincearca automat la credentiale gresite.

Clasificarea esecurilor RAR la postPrezentare (worker). Un 400 -> needs_data (validare continut). Un 500 cu corp de eroare ({statusCode,message}, ex. ORA-12899) e un esec DEFINITIV: RAR a raspuns „am esuat", deci NU e o pierdere de raspuns ambigua -> worker-ul marcheaza error (RAR_EROARE_SERVER), fara reconciliere si fara retry (altfel ar marca fals sent pe un record PARTIAL pe care RAR, ne-tranzactional, il lasa la esec). Doar erorile ambigue — timeout / TransportError / 502/503/504 / 429 / 408 — declanseaza reconcilierea anti-duplicat + retry cu backoff.

Import fisier

Cod Problema Fix
IMPORT_FISIER_PREA_MARE Fisier prea mare Imparte fisierul in bucati de maxim 5000 de randuri si incarca-le pe rand.
IMPORT_ANTET_NECLAR Antet de coloane neclar Asigura-te ca primul rand contine numele coloanelor (ex. VIN, Numar, Data).
IMPORT_ENCODING Codare de caractere nesuportata Salveaza fisierul ca CSV UTF-8 (sau xlsx) si reincarca.
IMPORT_FISIER_NERECUNOSCUT Fisier nerecunoscut Incarca un fisier .xlsx sau .csv valid.
IMPORT_MULTIPLE_SHEETS Mai multe foi in fisier Pastreaza datele intr-o singura foaie sau alege foaia de import.
IMPORT_FARA_MAPARE_COLOANE Coloanele nu sunt mapate Mapeaza intai coloanele fisierului la campurile cerute, apoi continua.
IMPORT_CONFIRMARE_GRESITA Numar confirmat gresit Numarul confirmat difera de randurile gata de trimis; verifica preview-ul si reconfirma.
IMPORT_OVERRIDE_ILIZIBIL Editarea anterioara nu se poate citi Editarea salvata este ilizibila (probabil cheia s-a schimbat); reediteaza randul.
COLOANE_FORMAT_JSON Format de coloane (JSON) invalid Verifica sintaxa JSON a maparii de coloane (ghilimele duble, acolade inchise corect).

Nomenclator prestații (18 coduri, verificat live 2026-06-15)

cod nume
OE-1 REPARAȚIE
OE-2 INTRETINERE
OE-3 REVIZIE PERIODICA
OE-4 REGLARE FUNCTIONALA
OE-5 MODIFICARE CONSTRUCTIVA
OE-6 RECONSTRUCTIE
OE-7 ACTUALIZARE SOFTWARE
OE-8 INLOCUIRE SEZONIERA A ANVELOPELOR
OE-D AVARII GRAVE LA SISTEMUL DE DIRECTIE
OE-F AVARII GRAVE LA SISTEMUL DE FRANARE
OE-C AVARII GRAVE LA STRUCTURA DE REZISTENTA A CAROSERIEI
OE-S AVARII GRAVE LA STRUCTURA DE REZISTENTA A SASIULUI
OE-R AVARII GRAVE LA UN SISTEM DE RETINERE SI PROTECTIE IN CAZ DE ACCIDENT
OE-A AVARII GRAVE LA UN SISTEM AVANSAT DE ASISTENTA A CONDUCATORULUI AUTO (ADAS)
OE-I ISTORICUL INDICATIEI ODOMETRULUI (vehicule anterior inmatriculate in alte tari)
AITLV INREGISTRARE ATELIER INSPECTIE TAHOGRAFE / LIMITATOARE DE VITEZA
R-ODO REPARATIE ODOMETRU → declanșează odometruInitial obligatoriu
I-ODO INLOCUIRE ODOMETRU → declanșează odometruInitial obligatoriu

numePrestatie redat prescurtat pentru OE-D…OE-A (în API e textul lung complet). Nomenclatorul se ia live din API; nu hard-coda — folosește acest tabel doar ca referință.

Ciclu de viață prezentare (la RAR) — IMPORTANT

  • API-ul are un singur scop: INSERT prezentări cu status FINALIZATA. Toate celelalte operațiuni CRUD se fac din interfața web.
  • Prezentările FINALIZATA NU se pot anula prin API (nici din web). Doar cele SALVATA sunt anulabile/editabile, dar API nu produce SALVATA.
    • Încercarea de anulare a unei FINALIZATA întoarce: EROARE_STATUS_ANULARE("... Id not found.").
  • Corecția datelor eronate (după FINALIZATA) = solicitare la suport.autopass@rarom.ro (pe test nu e cazul). Nu există flux API de corecție/anulare pentru records-urile noastre.

Monitorizare (citire prezentări) — VERIFICAT LIVE 2026-06-15

Rută: GET /prezentari/getAllPrezentariFinalizate (Bearer). Confirmat live. Răspuns: {statusCode, message, data: {totalCount, content: [...]}} — listă în data.content.

Fiecare item din content (live):

{
  "id": 68514, "dataPrestatie": "2026-06-15", "vin": "WVWZZZ1KZAW000123",
  "odometruFinal": 123456, "idAgent": 40, "tipPrestatie": null,
  "odometruInitial": null, "idUser": 6766, "sistemReparat": null, "obs": "...",
  "nrInmatriculare": "B999TST", "listaPrestatii": null, "status": "FINALIZATA",
  "prestatii": null, "b64Image": null
}
  • odometruFinal e NUMĂR (int) în listare (deși la postPrezentare se trimite string). Reconcilierea compară ca int.
  • Pe test: prestatii vine null (confirmă: nu te baza pe prestatii din listă — le ai local în submissions).
  • Filtrele NU funcționează pe test: ?vin=, ?search=, ?keyword=, ?dataPrestatie= sunt IGNORATE (întorc tot setul). ?page=&size= rup răspunsul (non-JSON). → fetch tot setul, filtrează client-side. Pe prod doc-ul promite filtrare keyword/dată + export Excel (de re-verificat pe prod).
  • RAR acceptă DUPLICATE: live există 2 perechi de records identice pe vin+dataPrestatie+odometruFinal (id 63622≡63625, 63623≡63626). De aceea reconcilierea pe răspuns pierdut e necesară, iar matcher-ul alege id-ul maxim când există mai multe potriviri.

Reconciliere (T2): înainte de re-send pe un rând sending, GET finalizate, match pe vin + dataPrestatie + odometruFinal(int); dacă există → marchează sent cu id-ul găsit, NU re-trimite.

Corecții față de planurile inițiale (context istoric)

  1. JWT „scurt" → de fapt 30 de ORE. Planurile (plan-design-review §„Gestiunea credențialelor", plan-eng-review §worker) presupun JWT scurt și mută durabilitatea pe re-push din ROAAUTO („coada acoperă minutele, ROAAUTO acoperă orele"). Fals. Cu 30h, worker-ul singur poate relua peste orice pană RAR realistă în fereastra tokenului. Re-push-ul din ROAAUTO devine plasă de siguranță secundară, nu mecanismul principal. Re-evaluează: poate nu mai e nevoie de re-push în treapta 1, sau credențialele se pot reține în memorie pe durata penei.
  2. b64Image (poza odometrului) NU mai e obligatorie. Planurile o tratau ca posibil gap de conformitate / posibil obligatorie. Rezolvat: opțională. → Open Question „sursa pozei în ROAAUTO" se închide (nu mai e blocantă). Dacă e atașată, doar trebuie base64 valid.
  3. tipPrestatie NU e input client — îl generează serverul ("GENERIC"). Open Question #2 (valori acceptate tipPrestatie) se închide pentru request — nu-l trimite.
  4. sistemReparat: planul presupunea că e derivabil server-side din coduri și nu input liber. Parțial fals — apare în request (exemplul oficial trimite string-ul "null"). Rămâne open ce valori reale acceptă; sigur: poți trimite "null" când nu se aplică.
  5. Anulare / corecție prin API: NU există pentru records-urile noastre (sunt FINALIZATA). Scoate din scope endpoint-urile gateway PATCH /v1/prezentari/{id}/anulare și /corectie (proxy peste markPrezentareAnulataById / patchPrezentare) — nu se aplică FINALIZATA. Maparea stărilor + „Error & rescue map" din plan-eng-review trebuie ajustate.
  6. Regula needs_data e acum deterministă: odometruInitial lipsă doar când prestatii conține R-ODO sau I-ODO. (Planul vorbea generic de „repair odometru".)
  7. URL nomenclator confirmat = /nomenclator/getNomenclatorPrestatii (nu varianta din operationId Swagger). Constatarea #5 din plan-eng-review (URL-uri din VFP, nu din spec) — confirmată.

API gateway (ROAAUTO -> gateway): mapare operatii (hibrid, 2026-06-15)

Aceasta e suprafata gateway-ului, nu RAR. Un item din prestatii la POST /v1/prezentari poate veni in doua forme (cel putin una obligatorie):

Camp item Note
cod_prestatie cod RAR direct (ex. OE-1). Validat fata de nomenclator -> validare T3 -> coada. Cod NECUNOSCUT in nomenclator e tratat ca operatie de mapat (vezi mai jos).
cod_op_service cod intern ROAAUTO. Gateway-ul il traduce in cod RAR prin operations_mapping.
denumire denumirea operatiei ROAAUTO; folosita pentru fuzzy lookup in editor.

Daca lipsesc ambele coduri -> 422 (shape). Op cu cod_op_service necunoscut (fara mapare) -> submission needs_mapping (NU se trimite la RAR), apare in editorul web. La salvarea maparii, submission-urile blocate pe acel cod se re-rezolva automat (-> queued, sau needs_data daca regula odometru/continut cere asta). Codul RAR rezolvat se scrie inapoi in payload_json, deci payload builder + worker raman code-driven.

Validare cod_prestatie la ingestie (2026-06-23). RAR accepta NUMAI coduri din nomenclator: coloana COD_PRESTATIE are max 5 caractere si un cod necunoscut intoarce HTTP 500 (ORA-12899) — confirmat live. Periculos: RAR NU e tranzactional si lasa un record partial (FINALIZATA, terminal) chiar cand apelul esueaza, iar reconcilierea worker-ului il poate marca fals sent. De aceea gateway-ul NU mai trimite un cod_prestatie care nu e in nomenclator: il promoveaza la cod_op_service (cu denumire=cod, pentru fuzzy) si il trateaza ca operatie de mapat.

Optiunea on_unmapped_error (camp boolean top-level optional pe POST /v1/prezentari si /v1/prezentari/valideaza) controleaza ce se intampla la cod necunoscut/nemapat:

  • false (default) — submission needs_mapping, apare in editor (non-distructiv);
  • true — respinge fara enqueue: SubmissionResult cu status="error", submission_id=null, erori=[COD_NEMAPAT...].

Cand campul lipseste se aplica default-ul contului (accounts.on_unmapped_error_default, implicit false/0). Override per-cerere > default cont > false.

Endpointuri noi:

  • GET /v1/mapari/pending — operatii nemapate distincte + sugestii fuzzy ({cod_prestatie, nume_prestatie, score}).
  • POST /v1/mapari {account_id?, cod_op_service, cod_prestatie, auto_send} — upsert mapare + re-rezolvare. Respinge cod_prestatie inexistent in nomenclator (422).
  • Web: GET /_fragments/mapari (editor HTMX), POST /mapari (form, salveaza + re-randeaza).

Lifecycle trimiteri blocate (PRD 5.6)

POST /v1/prezentari — camp aditiv in fiecare SubmissionResult: reactivated: bool. La resubmit cu aceeasi cheie de continut peste un rand error (ex. parola RAR corectata), randul se RE-ACTIVEAZA (re-clasificat + creds actualizate) si raspunsul poarta reactivated: true + starea noua. deduped pastreaza semantica actuala (clientii vechi care testeaza deduped nu se sparg). Pentru sent/queued/sending/needs_* -> deduped: true (neschimbat).

  • DELETE /v1/prezentari/{id} — sterge o trimitere blocata a contului cheii API. 200 + body JSON {ok, submission_id, status_anterior} (NU 204 — clienti VFP string-parse). Scope evaluat INAINTEA starii: cross-account / inexistent -> 404 (acelasi mesaj, B3); own-account sent/sending -> 409 (conflict de stare).
  • POST /v1/prezentari/{id}/repune — re-pune in coada (error -> queued, re-ruleaza classify). 200 + body JSON {ok, submission_id, status_anterior, status_nou}. Acelasi oracol scope/stare.
  • GET /v1/prezentari/{id} expune ACUM si rar_error (T9) — recovery observabil prin API (de ce a esuat); contine doar coduri/mesaje de validare RAR, niciodata creds.

Web (dashboard, scoped pe sesiune + CSRF): POST /trimitere/{id}/sterge, POST /trimitere/{id}/repune, POST /trimiteri/sterge-bulk (selectie multipla, doar blocate).

Fuzzy: rapidfuzz.token_sort_ratio pe denumire normalizata (fara diacritice, upper). Nomenclatorul se ia live din RAR (worker upsert la fiecare login); seed fallback de 18 coduri la boot (app/nomenclator_seed.py) ca editorul sa mearga offline. Auth API-key (CORE) inca neimplementat -> account_id curge ca NULL si e atribuit contului default id=1 (seed in schema); cand auth livreaza, account_id real curge natural.

Regula de scope pe cont (B8, PRD 3.2)

Orice GET nou pe /v1/* care atinge submissions sau operations_mapping PORNESTE cu account_id: int = Depends(resolve_account_id) si clauza de scope pe cont in SQL. Varianta globala (fara scope) e exceptie justificata explicit — singurul exemplu actual este GET /v1/nomenclator (cache de referinta RAR fara PII, partajat intre conturi).

Pentru submissions (account_id nullable): foloseste account_scope_clause(account_id) din app/mapping.py care produce (account_id = ? OR (account_id IS NULL AND ? = 1)). Randurile legacy cu account_id IS NULL apartin contului 1 (OV-2, back-compat).

Pentru operations_mapping (account_id NOT NULL): WHERE account_id = ? simplu.

Open questions rămase (actualizat)

  1. Sursa pozei odometruluiînchis (poză opțională).
  2. sistemReparat — ce valori reale acceptă (în afară de "null"). De probat la postPrezentare test.
  3. Un singur user RAR per agent economic sau mai mulți (idUser/idAgent — afectează filtrarea monitorizării).
  4. JWT TTLînchis: 30h. Decizie nouă: simplifică worker-ul; reconsideră necesitatea re-push ROAAUTO.
  5. Comportamentul răspunsului de eroareînchis (format data:[{field,message}], mesaje capturate mai sus).
  6. WAF cere User-Agentînchis/confirmat (vezi secțiunea de sus).

Rămas de verificat live (postPrezentare real pe test) FĂCUT 2026-06-15

Login + nomenclator + JWT TTL + postPrezentare = toate verificate live. Record de test creat: data.id = 68514 (FINALIZATA, permanent pe test). Confirmat:

  • mesajele de eroare exacte (VIN O/I/Q, dată prea veche, dată viitoare) — vezi tabelul de erori;
  • forma răspunsului success pe contul nostru + data.id;
  • sistemReparat:"null" acceptat, b64Image/odometruInitial omise OK;
  • header User-Agent obligatoriu (altfel 403 WAF).

Rămas neprobat: ce alte valori sistemReparat (în afară de "null") acceptă (Open Q #2).

Note integrare — planuri de cont (PRD 5.17)

Poți dezvolta și testa pe planul Gratuit fără niciun upgrade — POST /v1/prezentari/valideaza (dry-run) e permis pe orice plan, nu face enqueue și nu consumă cotă lunară. Primești același răspuns de validare (câmpuri, cod_prestatie, rezolvare operație) ca la trimiterea reală.

Trimiterea reală cere planul Pro (sau trial Pro activ): rutele POST /v1/prezentari, POST /v1/import și POST /v1/import/{id}/commit sunt gate-uite pe api_access=True (Pro/Premium). Un cont Free/Standard primește 403 PLAN_FARA_API. Contactează-ne pentru upgrade.

Planul Gratuit are limită de 60 prezentări/lună (indiferent de canal). La depășire: 422 PLAN_LIMITA_LUNARA. Planul Pro nu are limită de volum. GET /v1/nomenclator rămâne public pe orice plan (exploatare pre-upgrade).