feat(api): raspuns minim implicit + corelare cerere-raspuns + sugestie cod in raspuns; coloana # in lista trimiteri
- POST /v1/prezentari: camp optional 'raspuns' (minim implicit / complet); forma minima = 6 chei fixe (index, submission_id, vehicul, status, motiv, id_prezentare), formatul detaliat ramane la cerere - corelare rezultat<->cerere: index 0-based + ecou vin/nr_inmatriculare - sugestia principala de cod RAR pe nemapate (GOLD partajat > SILVER > embeddings > fuzzy peste prag), in motiv si in nemapate[].sugestie (si pe dry-run /valideaza); suggestion-only, nu se aplica automat - lista Trimiteri: coloana # cu id-ul trimiterii (desktop + mobil) - README: sectiune 'Mapari si sugestii de cod RAR'; contract actualizat Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -33,16 +33,19 @@ from ...mapping import (
|
||||
classify_prezentare,
|
||||
load_excluded_ops,
|
||||
load_mapping_meta,
|
||||
load_nomenclator,
|
||||
load_nomenclator_codes,
|
||||
load_text_rules,
|
||||
pending_unmapped,
|
||||
reresolve_account,
|
||||
save_mapping,
|
||||
sugestie_pentru_op,
|
||||
)
|
||||
from ...models import (
|
||||
PrezentareRequest,
|
||||
PrezentariResponse,
|
||||
SubmissionResult,
|
||||
SubmissionResultMinim,
|
||||
ValidarePrezentariRequest,
|
||||
ValidareResponse,
|
||||
ValidareResult,
|
||||
@@ -89,6 +92,16 @@ def _erori_nemapate(unmapped: list[dict]) -> list[dict]:
|
||||
]
|
||||
|
||||
|
||||
def _ataseaza_sugestii(conn, cl: dict, nomenclator: list[dict]) -> None:
|
||||
"""Adauga `sugestie` ({cod_prestatie, nume_prestatie, sursa} | None) pe fiecare
|
||||
cod nemapat din clasificare. Suggestion-only: informeaza raspunsul API, nu
|
||||
schimba statusul si nu se aplica automat."""
|
||||
for u in cl.get("unmapped") or []:
|
||||
u["sugestie"] = sugestie_pentru_op(
|
||||
conn, u.get("denumire") or u.get("cod_op_service"), nomenclator
|
||||
)
|
||||
|
||||
|
||||
# Mesaj uman pentru un rand tinut manual (queued AND held=1).
|
||||
# Reutilizeaza vocabularul existent AUTO_SEND_OPRIT (errors.py) — nu inventam al treilea
|
||||
# vocabular "auto_send". Onestitate: raspunsul NU trebuie sa arate un "queued" curat fals.
|
||||
@@ -117,8 +130,16 @@ def _motiv_clasificare(cl: dict) -> str | None:
|
||||
(e.get("problema") or e.get("message") or "") for e in cl["errors"]
|
||||
).strip("; ") or "Date incomplete (respinse de RAR)."
|
||||
if cl["unmapped"]:
|
||||
coduri = ", ".join((u.get("cod_op_service") or "") for u in cl["unmapped"])
|
||||
return f"Coduri fara mapare RAR: {coduri}"
|
||||
parti = []
|
||||
for u in cl["unmapped"]:
|
||||
op = u.get("cod_op_service") or ""
|
||||
s = u.get("sugestie")
|
||||
if s and s.get("cod_prestatie"):
|
||||
nume = f" {s['nume_prestatie']}" if s.get("nume_prestatie") else ""
|
||||
parti.append(f"{op} (sugestie: {s['cod_prestatie']}{nume})")
|
||||
else:
|
||||
parti.append(op)
|
||||
return f"Coduri fara mapare RAR: {', '.join(parti)}"
|
||||
return None
|
||||
|
||||
|
||||
@@ -185,6 +206,8 @@ def create_prezentari(
|
||||
# Validare cod_prestatie fata de nomenclator + modul la cod necunoscut/nemapat.
|
||||
# valid_codes gol (nomenclator nepopulat) -> None (nu validam, ca sa nu blocam tot).
|
||||
valid_codes = load_nomenclator_codes(conn) or None
|
||||
# Nomenclator complet (cod+nume) pentru sugestiile atasate codurilor nemapate.
|
||||
nomenclator = load_nomenclator(conn)
|
||||
# Reguli text incarcate o data per cerere (seam partajat cu dry-run).
|
||||
text_rules = load_text_rules(conn, acct)
|
||||
excluded_ops = load_excluded_ops(conn, acct)
|
||||
@@ -288,6 +311,7 @@ def create_prezentari(
|
||||
# creds + reset), printr-un UPDATE compare-and-swap pe status='error'.
|
||||
if existing["status"] == "error":
|
||||
cl = _classify_modal(content, mapping, mapping_meta, valid_codes, error_mode, text_rules, excluded_ops)
|
||||
_ataseaza_sugestii(conn, cl, nomenclator)
|
||||
if cl["blocked_error"]:
|
||||
# on_unmapped_error=True: nu reactivam; randul ramane 'error'.
|
||||
results.append(_rezultat_respins(existing["id"], cl, rar_env=env))
|
||||
@@ -340,6 +364,7 @@ def create_prezentari(
|
||||
# Helper pur partajat cu dry-run: reproduce EXACT clasificarea
|
||||
# (canonicalize + mapare op->cod + validare; fara gate auto_send).
|
||||
cl = _classify_modal(content, mapping, mapping_meta, valid_codes, error_mode, text_rules, excluded_ops)
|
||||
_ataseaza_sugestii(conn, cl, nomenclator)
|
||||
if cl["blocked_error"]:
|
||||
# on_unmapped_error=True: respinge fara enqueue (cod necunoscut/nemapat).
|
||||
results.append(_rezultat_respins(None, cl, rar_env=env))
|
||||
@@ -356,6 +381,14 @@ def create_prezentari(
|
||||
# pe queued tinut manual (held=1) expune held=true + motiv.
|
||||
results.append(_rezultat_enqueue(sub_id, cl, held=held_val, rar_env=env))
|
||||
|
||||
# Corelare raspuns<->cerere: fiecare prezentare produce EXACT un rezultat,
|
||||
# in ordine, deci pozitia din lista e si indexul cererii. Ecoul vehiculului
|
||||
# foloseste valorile normalizate de Pydantic (upper/strip).
|
||||
for i, (rez, prez) in enumerate(zip(results, req.prezentari)):
|
||||
rez.index = i
|
||||
rez.vin = prez.vin
|
||||
rez.nr_inmatriculare = prez.nr_inmatriculare
|
||||
|
||||
# Audit cerere API per cont. Doar metadate (count + distributie status),
|
||||
# NICIUN camp de payload PII integral. Reuse conn (fara contentie WAL).
|
||||
dist: dict[str, int] = {}
|
||||
@@ -376,7 +409,21 @@ def create_prezentari(
|
||||
)
|
||||
finally:
|
||||
conn.close()
|
||||
return PrezentariResponse(results=results)
|
||||
# Forma implicita = minim (6 chei fixe per rezultat); formatul complet
|
||||
# (erori/nemapate/deduped/held/...) doar la cerere explicita.
|
||||
if req.raspuns == "complet":
|
||||
return PrezentariResponse(results=list(results))
|
||||
return PrezentariResponse(results=[
|
||||
SubmissionResultMinim(
|
||||
index=r.index if r.index is not None else 0,
|
||||
submission_id=r.submission_id,
|
||||
vehicul=r.nr_inmatriculare or "",
|
||||
status=r.status,
|
||||
motiv=r.motiv,
|
||||
id_prezentare=r.id_prezentare,
|
||||
)
|
||||
for r in results
|
||||
])
|
||||
|
||||
|
||||
@router.post("/prezentari/valideaza", response_model=ValidareResponse)
|
||||
@@ -397,6 +444,7 @@ def valideaza_prezentari(
|
||||
mapping_meta = load_mapping_meta(conn, acct)
|
||||
mapping = {op: meta["cod_prestatie"] for op, meta in mapping_meta.items()}
|
||||
valid_codes = load_nomenclator_codes(conn) or None
|
||||
nomenclator = load_nomenclator(conn)
|
||||
# Acelasi seam ca trimiterea reala: dry-run trebuie sa vada aceleasi reguli text.
|
||||
text_rules = load_text_rules(conn, acct)
|
||||
excluded_ops = load_excluded_ops(conn, acct)
|
||||
@@ -433,6 +481,7 @@ def valideaza_prezentari(
|
||||
for i, prez in enumerate(req.prezentari):
|
||||
content = prez.model_dump()
|
||||
res = _classify_modal(content, mapping, mapping_meta, valid_codes, error_mode, text_rules, excluded_ops)
|
||||
_ataseaza_sugestii(conn, res, nomenclator)
|
||||
if res["blocked_error"]:
|
||||
res = {**res, "status": "error"}
|
||||
# Imbogatim fiecare element nemapat cu 3 niveluri COD_NEMAPAT
|
||||
|
||||
Reference in New Issue
Block a user