Files
rar-autopass/app/errors.py
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

236 lines
8.1 KiB
Python

"""Catalog central de erori AutoPass.
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."
),
},
# Coduri de plan (PRD 5.17)
"PLAN_LIMITA_LUNARA": {
"problema": "Ai atins limita planului Gratuit (60 prestatii/luna)",
"fix": (
"Treci pe planul Standard sau Pro, sau asteapta inceperea lunii urmatoare."
" Numarul de prestatii ramase in luna curenta e in campul cauza."
),
},
"PLAN_FARA_API": {
"problema": "Importul prin API e disponibil pe planul Pro",
"fix": (
"Planul tau curent nu include accesul la API."
" Endpoint-ul /v1/prezentari/valideaza ramane disponibil pentru testare fara upgrade."
" Contacteaza-ne pentru a face upgrade la planul Pro."
),
},
}
# ---------------------------------------------------------------------------
# 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,
}