feat(T4): payload builder finalizat + snapshot test
- app/payload.py rafinat: odometruFinal/odometruInitial string (initial gol -> null), evita capcana falsy `or ""` (pastreaza "0"), normalizare vin/nrInm/coduri, tipPrestatie niciodata trimis, obs/b64Image omise cand lipsesc - tests/test_payload.py: 10 teste, inclusiv snapshot vs exemplul oficial din contract Verify: pytest 39 passed (29 + 10). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,13 +1,14 @@
|
||||
"""Constructor payload postPrezentare (schelet — T4 il completeaza).
|
||||
"""Constructor payload postPrezentare (T4).
|
||||
|
||||
Reguli din contract (docs/api-rar-contract.md):
|
||||
Reguli din contract (docs/api-rar-contract.md), confirmate live in T1:
|
||||
- status mereu "FINALIZATA".
|
||||
- tipPrestatie NU se trimite (server-generated GENERIC).
|
||||
- odometruFinal ca string.
|
||||
- odometruFinal ca string; odometruInitial ca string cand e prezent, altfel null.
|
||||
- sistemReparat trimis mereu (default "null").
|
||||
- prestatii: [{codPrestatie, idPrezentare: null}].
|
||||
- b64Image / odometruInitial optionale (se omit daca lipsesc).
|
||||
T4 adauga snapshot-test fata de exemplul oficial din contract.
|
||||
- obs / b64Image optionale — se OMIT din payload daca lipsesc.
|
||||
|
||||
Snapshot test fata de exemplul oficial: tests/test_payload.py.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
@@ -15,25 +16,41 @@ from __future__ import annotations
|
||||
from typing import Any
|
||||
|
||||
|
||||
def _upper(value: object) -> str:
|
||||
return str(value or "").strip().upper()
|
||||
|
||||
|
||||
def _str_or_none(value: object) -> str | None:
|
||||
"""str.strip() pentru valori non-goale; None pentru None / string gol."""
|
||||
if value is None:
|
||||
return None
|
||||
s = str(value).strip()
|
||||
return s or None
|
||||
|
||||
|
||||
def _cod(p: object) -> str | None:
|
||||
cod = p.get("cod_prestatie") if isinstance(p, dict) else getattr(p, "cod_prestatie", None)
|
||||
return str(cod).strip().upper() if cod else None
|
||||
|
||||
|
||||
def build_rar_payload(prezentare: dict[str, Any]) -> dict[str, Any]:
|
||||
"""Mapeaza o prezentare interna -> payload exact pentru RAR postPrezentare."""
|
||||
prestatii = prezentare.get("prestatii") or []
|
||||
"""Mapeaza o prezentare interna (snake_case) -> payload exact pentru RAR postPrezentare."""
|
||||
payload: dict[str, Any] = {
|
||||
"vin": (prezentare.get("vin") or "").strip().upper(),
|
||||
"nrInmatriculare": (prezentare.get("nr_inmatriculare") or "").strip().upper(),
|
||||
"dataPrestatie": prezentare.get("data_prestatie"),
|
||||
"odometruFinal": str(prezentare.get("odometru_final") or "").strip(),
|
||||
"odometruInitial": prezentare.get("odometru_initial"),
|
||||
"vin": _upper(prezentare.get("vin")),
|
||||
"nrInmatriculare": _upper(prezentare.get("nr_inmatriculare")),
|
||||
"dataPrestatie": str(prezentare.get("data_prestatie") or "").strip(),
|
||||
# odometruFinal ramane string (nu folosim `or` ca sa nu pierdem "0").
|
||||
"odometruFinal": str(prezentare.get("odometru_final")).strip()
|
||||
if prezentare.get("odometru_final") is not None else "",
|
||||
"odometruInitial": _str_or_none(prezentare.get("odometru_initial")),
|
||||
"prestatii": [
|
||||
{
|
||||
"codPrestatie": (p.get("cod_prestatie") if isinstance(p, dict) else getattr(p, "cod_prestatie", None)),
|
||||
"idPrezentare": None,
|
||||
}
|
||||
for p in prestatii
|
||||
{"codPrestatie": _cod(p), "idPrezentare": None}
|
||||
for p in (prezentare.get("prestatii") or [])
|
||||
],
|
||||
"sistemReparat": prezentare.get("sistem_reparat") or "null",
|
||||
"status": "FINALIZATA",
|
||||
}
|
||||
# tipPrestatie: NICIODATA in payload (server-generated GENERIC).
|
||||
if prezentare.get("obs"):
|
||||
payload["obs"] = prezentare["obs"]
|
||||
if prezentare.get("b64_image"):
|
||||
|
||||
Reference in New Issue
Block a user