Files
rar-autopass/app/errors.py
Claude Agent 6bad6bc01e feat(api): validare cod_prestatie la nomenclator + optiune on_unmapped_error
Cod_prestatie necunoscut in nomenclator nu se mai trimite raw la RAR (HTTP 500
ORA-12899 + record partial FINALIZATA pe care reconcilierea il marca fals sent):
e promovat la cod_op_service si tratat ca operatie de mapat.

Optiune top-level boolean on_unmapped_error pe POST /v1/prezentari + /valideaza:
  - false (default) -> submission needs_mapping (intra in editor)
  - true            -> respinge fara enqueue (status error, submission_id=null, erori)
  - None            -> default per-cont accounts.on_unmapped_error_default (implicit 0)
Inlocuieste enum-ul anterior on_unmapped (needs_mapping/error) cu un boolean mai
simplu; coloana de cont migrata aditiv la INTEGER on_unmapped_error_default.

Izolare teste de .env-ul de dezvoltare: tests/conftest.py fixeaza default sigur
pe AUTOPASS_REQUIRE_API_KEY / AUTOPASS_WORKER_USE_TEST_CREDS (precedenta peste
.env in pydantic-settings) + fixturile env din test_creds_delivery/test_t1 pineaza
explicit aceste flag-uri, ca fallback-ul creds pe cont sa fie atins.

Teste: 752 passed (fara flag pe CLI).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-23 19:35:47 +00:00

220 lines
7.4 KiB
Python

"""Catalog central de erori AutoPass (PRD 5.4).
Singura sursa de adevar care mapeaza fiecare cod de eroare la (problema, fix),
cu un helper care construieste obiectul de eroare pe 3 niveluri:
- nivel 1 (tehnic): `cod` + `cauza` — ce s-a intamplat exact
- nivel 2 (utilizator): `problema` — descriere scurta, inteligibila
- nivel 3 (actiune): `fix` — ce trebuie facut pentru a remedia
Modul PUR — fara import DB sau HTTP.
"""
from __future__ import annotations
# ---------------------------------------------------------------------------
# CATALOG
# cheie = cod (string), valoare = {"problema": str, "fix": str}
# ---------------------------------------------------------------------------
CATALOG: dict[str, dict[str, str]] = {
"VIN_FORMAT": {
"problema": "VIN invalid",
"fix": (
"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": {
"problema": "Numar de inmatriculare invalid",
"fix": (
"Foloseste doar litere si cifre majuscule, maxim 10 caractere, fara spatii"
" sau cratima (ex. B123ABC)."
),
},
"DATA_FORMAT": {
"problema": "Data prestatiei in format gresit",
"fix": "Scrie data ca AAAA-LL-ZZ (ex. 2026-06-22).",
},
"DATA_PREA_VECHE": {
"problema": "Data prestatiei prea veche",
"fix": (
"RAR accepta prestatii doar incepand cu 01.12.2024;"
" verifica data prestatiei."
),
},
"DATA_VIITOR": {
"problema": "Data prestatiei in viitor",
"fix": "Data prestatiei nu poate fi dupa ziua de azi; corecteaza data.",
},
"ODOMETRU_FINAL_FORMAT": {
"problema": "Odometru final invalid",
"fix": (
"Scrie kilometrajul final ca numar intreg, fara zecimale sau text"
" (ex. 145000)."
),
},
"ODOMETRU_INITIAL_LIPSA": {
"problema": "Lipseste odometrul initial",
"fix": (
"Prestatiile R-ODO / I-ODO cer kilometrajul initial; completeaza-l."
),
},
"ODOMETRU_INITIAL_FORMAT": {
"problema": "Odometru initial invalid",
"fix": (
"Scrie kilometrajul initial ca numar intreg, fara zecimale sau text."
),
},
"ODOMETRU_INITIAL_ORDINE": {
"problema": "Odometru initial mai mare decat finalul",
"fix": (
"Kilometrajul initial trebuie sa fie mai mic sau egal cu cel final;"
" verifica cele doua valori."
),
},
"PRESTATII_GOALE": {
"problema": "Nicio prestatie",
"fix": "Adauga cel putin o prestatie cu cod RAR valid.",
},
"B64_INVALID": {
"problema": "Imaginea nu este base64 valid",
"fix": (
"Trimite imaginea codata base64 corect, sau omite campul daca nu ai imagine."
),
},
"COD_NEMAPAT": {
"problema": "Lipseste codul RAR al operatiei",
"fix": (
"Alege codul RAR pentru aceasta operatie in tab-ul Mapari"
" (ai sugestii automate)."
),
},
"AUTO_SEND_OPRIT": {
"problema": "Necesita confirmare manuala",
"fix": (
"Codul e mapat cu trimitere automata oprita; verifica randul si"
" pune-l manual in coada."
),
},
"RAR_VALIDARE": {
"problema": "RAR a respins prezentarea",
"fix": (
"Corecteaza campul semnalat de RAR (vezi cauza) si reincearca;"
" detaliile exacte sunt in mesajul tehnic RAR."
),
},
"RAR_EROARE_SERVER": {
"problema": "RAR a esuat la inregistrarea prezentarii",
"fix": (
"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": {
"problema": "Credentiale RAR invalide",
"fix": (
"Verifica email-ul si parola contului RAR in tab-ul Cont;"
" trimiterea nu se reincearca automat la credentiale gresite."
),
},
"IMPORT_FISIER_PREA_MARE": {
"problema": "Fisier prea mare",
"fix": (
"Imparte fisierul in bucati de maxim 5000 de randuri si incarca-le pe rand."
),
},
"IMPORT_ANTET_NECLAR": {
"problema": "Antet de coloane neclar",
"fix": (
"Asigura-te ca primul rand contine numele coloanelor"
" (ex. VIN, Numar, Data)."
),
},
"IMPORT_ENCODING": {
"problema": "Codare de caractere nesuportata",
"fix": "Salveaza fisierul ca CSV UTF-8 (sau xlsx) si reincarca.",
},
"IMPORT_FISIER_NERECUNOSCUT": {
"problema": "Fisier nerecunoscut",
"fix": "Incarca un fisier .xlsx sau .csv valid.",
},
"IMPORT_MULTIPLE_SHEETS": {
"problema": "Mai multe foi in fisier",
"fix": "Pastreaza datele intr-o singura foaie sau alege foaia de import.",
},
"IMPORT_FARA_MAPARE_COLOANE": {
"problema": "Coloanele nu sunt mapate",
"fix": (
"Mapeaza intai coloanele fisierului la campurile cerute, apoi continua."
),
},
"IMPORT_CONFIRMARE_GRESITA": {
"problema": "Numar confirmat gresit",
"fix": (
"Numarul confirmat difera de randurile gata de trimis;"
" verifica preview-ul si reconfirma."
),
},
"IMPORT_OVERRIDE_ILIZIBIL": {
"problema": "Editarea anterioara nu se poate citi",
"fix": (
"Editarea salvata este ilizibila (probabil cheia s-a schimbat);"
" reediteaza randul."
),
},
"COLOANE_FORMAT_JSON": {
"problema": "Format de coloane (JSON) invalid",
"fix": (
"Verifica sintaxa JSON a maparii de coloane"
" (ghilimele duble, acolade inchise corect)."
),
},
"EROARE_INTERNA": {
"problema": "Eroare interna a gateway-ului",
"fix": (
"Nu e o problema de date trimise de tine. Reincearca peste cateva"
" momente; daca persista, contacteaza administratorul cu identificatorul"
" cererii (request_id) afisat."
),
},
}
# ---------------------------------------------------------------------------
# eroare()
# ---------------------------------------------------------------------------
def eroare(
cod: str,
*,
field: str | None = None,
cauza: str | None = None,
) -> dict:
"""Construieste un obiect de eroare pe 3 niveluri din CATALOG.
Parametri
---------
cod: Codul de eroare (cheie in CATALOG). Ridica KeyError daca absent.
field: Campul care a generat eroarea (optional, pentru context).
cauza: Descrierea tehnica a erorii concrete (optional).
Daca lipseste, `cauza` si `message` preiau valoarea `problema` din catalog.
Returneaza
----------
dict cu exact cheile: field, cod, problema, cauza, fix, message.
"""
entry = CATALOG[cod] # ridica KeyError daca cod absent
problema = entry["problema"]
fix = entry["fix"]
cauza_efectiva = cauza if cauza is not None else problema
message = cauza if cauza is not None else problema
return {
"field": field,
"cod": cod,
"problema": problema,
"cauza": cauza_efectiva,
"fix": fix,
"message": message,
}