- 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>
76 lines
2.2 KiB
Python
76 lines
2.2 KiB
Python
"""Modele Pydantic pentru suprafata API.
|
|
|
|
ATENTIE: validarea completa (regex VIN ^[A-HJ-NPR-Z0-9]{17}$, nrInmatriculare,
|
|
dataPrestatie ∈ [2024-12-01, azi] TZ Bucuresti, R-ODO/I-ODO -> odometruInitial
|
|
obligatoriu, odometruInitial <= odometruFinal, normalizare strip/upper) este
|
|
**T3** — aici sunt doar formele de baza. Vezi plan.md sect. 2 + roadmap T3.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from pydantic import BaseModel, Field, field_validator
|
|
|
|
|
|
class RarCredentials(BaseModel):
|
|
"""Credentiale RAR per-cerere (vin de la ROAAUTO din Oracle). NU se stocheaza."""
|
|
|
|
email: str
|
|
password: str
|
|
|
|
|
|
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.
|
|
|
|
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
|
|
odometru_final: str # string per contract
|
|
odometru_initial: str | None = None
|
|
prestatii: list[PrestatieItem]
|
|
sistem_reparat: str = "null"
|
|
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."""
|
|
|
|
rar_credentials: RarCredentials
|
|
prezentari: list[PrezentareIn] = Field(..., min_length=1)
|
|
|
|
|
|
class SubmissionResult(BaseModel):
|
|
submission_id: int
|
|
status: str
|
|
id_prezentare: int | None = None
|
|
deduped: bool = False # True daca idempotency a intors un submission existent
|
|
|
|
|
|
class PrezentariResponse(BaseModel):
|
|
results: list[SubmissionResult]
|