feat(T3): validare completa prezentari + 29 teste

- app/validation.py: reguli de continut (VIN ^[A-HJ-NPR-Z0-9]{17}$ fara O/I/Q,
  nrInm ^[A-Z0-9]{1,10}$, dataPrestatie ∈ [2024-12-01, azi] TZ Bucuresti,
  R-ODO/I-ODO -> odometruInitial obligatoriu, odometruInitial<=odometruFinal,
  odometruFinal numeric, prestatii nevide, b64Image base64 valid)
- erori structurate {field, message} (aceeasi forma ca raspunsul RAR), fara exceptii
- modele Pydantic: normalizare strip/upper pe vin/nrInm/coduri
- router /v1/prezentari: validare inainte de enqueue; esec continut -> needs_data
  (tinut, vizibil in dashboard cu motiv), NU 422; JSON malformat -> 422 (shape)
- tests/: 29 teste (per regula + rutare API + idempotenta)

Verify: pytest 29 passed.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Claude Agent
2026-06-15 13:49:20 +00:00
parent 97798a3cbc
commit 2117ab5c1e
7 changed files with 390 additions and 11 deletions

View File

@@ -8,7 +8,7 @@ obligatoriu, odometruInitial <= odometruFinal, normalizare strip/upper) este
from __future__ import annotations
from pydantic import BaseModel, Field
from pydantic import BaseModel, Field, field_validator
class RarCredentials(BaseModel):
@@ -21,13 +21,24 @@ class RarCredentials(BaseModel):
class PrestatieItem(BaseModel):
cod_prestatie: str = Field(..., description="cod din nomenclator RAR, ex. OE-1")
@field_validator("cod_prestatie")
@classmethod
def _norm_cod(cls, v: str) -> str:
return v.strip().upper()
class PrezentareIn(BaseModel):
"""O prezentare de declarat la RAR (inainte de validarea T3)."""
"""O prezentare de declarat la RAR.
Pydantic doar NORMALIZEAZA aici (strip/upper pe vin/nrInm). Validarea de
continut (regex VIN, interval data, R-ODO/I-ODO, odometru) e in
app.validation.validate_prezentare si NU resping cererea — marcheaza
`needs_data` (plan.md sect. 3).
"""
vin: str
nr_inmatriculare: str
data_prestatie: str # YYYY-MM-DD; validare interval = T3
data_prestatie: str # YYYY-MM-DD
odometru_final: str # string per contract
odometru_initial: str | None = None
prestatii: list[PrestatieItem]
@@ -35,6 +46,16 @@ class PrezentareIn(BaseModel):
obs: str | None = None
b64_image: str | None = None
@field_validator("vin", "nr_inmatriculare")
@classmethod
def _norm_upper(cls, v: str) -> str:
return v.strip().upper()
@field_validator("data_prestatie", "odometru_final")
@classmethod
def _norm_strip(cls, v: str) -> str:
return v.strip()
class PrezentareRequest(BaseModel):
"""Body pentru POST /v1/prezentari — una sau mai multe prezentari + creds RAR."""