"""Constructor payload postPrezentare (T4). 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; odometruInitial ca string cand e prezent, altfel null. - sistemReparat trimis mereu (default "null"). - prestatii: [{codPrestatie, idPrezentare: null}]. - obs / b64Image optionale — se OMIT din payload daca lipsesc. Snapshot test fata de exemplul oficial: tests/test_payload.py. """ 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 (snake_case) -> payload exact pentru RAR postPrezentare.""" payload: dict[str, Any] = { "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": _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"): payload["b64Image"] = prezentare["b64_image"] return payload