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:
@@ -31,7 +31,7 @@ def _body(**over):
|
||||
"prestatii": [{"cod_prestatie": "OE-1"}],
|
||||
}
|
||||
prez.update(over)
|
||||
return {"rar_credentials": {"email": "x@y.ro", "password": "s"}, "prezentari": [prez]}
|
||||
return {"rar_credentials": {"email": "x@y.ro", "password": "s"}, "prezentari": [prez], "raspuns": "complet"}
|
||||
|
||||
|
||||
def test_prezentare_valida_queued(client):
|
||||
@@ -117,6 +117,66 @@ def test_raspuns_nu_reflecta_parola(client):
|
||||
assert "password" not in r.text
|
||||
|
||||
|
||||
def test_rezultate_poarta_index_si_ecou_vehicul(client):
|
||||
"""Fiecare rezultat e corelabil cu cererea: index 0-based + ecou vin/nr,
|
||||
inclusiv pe randuri blocate si pe dedup."""
|
||||
body = _body()
|
||||
prez2 = dict(body["prezentari"][0])
|
||||
prez2.update({"vin": "WAUZZZ8K9BA123456", "nr_inmatriculare": "cj45xyz",
|
||||
"data_prestatie": "2099-01-01"})
|
||||
body["prezentari"].append(prez2)
|
||||
r = client.post("/v1/prezentari", json=body)
|
||||
res = r.json()["results"]
|
||||
assert [x["index"] for x in res] == [0, 1]
|
||||
assert res[0]["vin"] == "WVWZZZ1KZAW000123"
|
||||
assert res[1]["nr_inmatriculare"] == "CJ45XYZ" # ecou normalizat (upper)
|
||||
assert res[1]["status"] == "needs_data"
|
||||
|
||||
# Dedup: acelasi continut -> rezultatul pastreaza corelarea.
|
||||
r2 = client.post("/v1/prezentari", json=body)
|
||||
res2 = r2.json()["results"]
|
||||
assert res2[0]["deduped"] is True
|
||||
assert res2[0]["index"] == 0
|
||||
assert res2[0]["vin"] == "WVWZZZ1KZAW000123"
|
||||
|
||||
|
||||
def test_nemapat_primeste_sugestie(client):
|
||||
"""Cod nemapat cu denumire apropiata de nomenclator -> sugestie fuzzy in
|
||||
nemapate[].sugestie (complet) si in motiv (ambele forme)."""
|
||||
prestatii = [{"cod_op_service": "REV-PER", "denumire": "Revizie periodica"}]
|
||||
r = client.post("/v1/prezentari", json=_body(prestatii=prestatii))
|
||||
res = r.json()["results"][0]
|
||||
assert res["status"] == "needs_mapping"
|
||||
sug = res["nemapate"][0]["sugestie"]
|
||||
assert sug["cod_prestatie"] == "OE-3" # seed: REVIZIE PERIODICA
|
||||
assert sug["sursa"] == "fuzzy"
|
||||
assert "sugestie: OE-3" in res["motiv"]
|
||||
|
||||
# Forma minima: sugestia ramane vizibila in motiv.
|
||||
body = _body(prestatii=prestatii, vin="WAUZZZ8K9BA123456")
|
||||
del body["raspuns"]
|
||||
r2 = client.post("/v1/prezentari", json=body)
|
||||
assert "sugestie: OE-3" in r2.json()["results"][0]["motiv"]
|
||||
|
||||
|
||||
def test_raspuns_minim_implicit(client):
|
||||
"""Fara `raspuns` in cerere -> forma compacta cu chei fixe, fara campurile detaliate."""
|
||||
body = _body(prestatii=[{"cod_op_service": "REP-MOT", "denumire": "Reparatie motor"}])
|
||||
del body["raspuns"]
|
||||
r = client.post("/v1/prezentari", json=body)
|
||||
res = r.json()["results"][0]
|
||||
assert set(res) == {"index", "submission_id", "vehicul", "status", "motiv", "id_prezentare"}
|
||||
assert res["index"] == 0
|
||||
assert res["vehicul"] == "B999TST"
|
||||
assert res["status"] == "needs_mapping"
|
||||
assert "REP-MOT" in res["motiv"]
|
||||
|
||||
# 'minim' explicit = acelasi lucru cu default-ul.
|
||||
body["raspuns"] = "minim"
|
||||
r2 = client.post("/v1/prezentari", json=body)
|
||||
assert set(r2.json()["results"][0]) == set(res)
|
||||
|
||||
|
||||
def test_json_malformat_422(client):
|
||||
# Lipseste vin -> validare de shape Pydantic -> 422 (NU needs_data).
|
||||
bad = {"rar_credentials": {"email": "x", "password": "y"},
|
||||
|
||||
@@ -38,7 +38,7 @@ def _body(rar_env=None, **over):
|
||||
"prestatii": [{"cod_prestatie": "OE-1"}],
|
||||
}
|
||||
prez.update(over)
|
||||
body = {"rar_credentials": {"email": "x@y.ro", "password": "s"}, "prezentari": [prez]}
|
||||
body = {"rar_credentials": {"email": "x@y.ro", "password": "s"}, "prezentari": [prez], "raspuns": "complet"}
|
||||
if rar_env is not None:
|
||||
body["rar_env"] = rar_env
|
||||
return body
|
||||
|
||||
@@ -479,7 +479,7 @@ def test_retry_idempotent_nu_consuma_cota(client):
|
||||
assert usage_1 == 1, f"Dupa prima trimitere: asteptat usage=1, primit {usage_1}"
|
||||
|
||||
# Retry (acelasi payload -> acelasi idempotency_key -> dedup, fara INSERT nou)
|
||||
r2 = client.post("/v1/prezentari", json={"prezentari": [prez_unic]})
|
||||
r2 = client.post("/v1/prezentari", json={"prezentari": [prez_unic], "raspuns": "complet"})
|
||||
assert r2.status_code == 200, f"Retry trebuie sa treaca (dedup): {r2.text}"
|
||||
assert r2.json()["results"][0].get("deduped") is True, "Retry trebuia marcat deduped"
|
||||
|
||||
|
||||
@@ -161,7 +161,7 @@ def _body(**over):
|
||||
"prestatii": [{"cod_prestatie": "OE-1"}],
|
||||
}
|
||||
prez.update(over)
|
||||
return {"rar_credentials": {"email": "x@y.ro", "password": "s"}, "prezentari": [prez]}
|
||||
return {"rar_credentials": {"email": "x@y.ro", "password": "s"}, "prezentari": [prez], "raspuns": "complet"}
|
||||
|
||||
|
||||
def test_api_dedup_dupa_t9(client):
|
||||
|
||||
@@ -37,7 +37,7 @@ def _body(password="corecta", **over):
|
||||
"prestatii": [{"cod_prestatie": "OE-1"}],
|
||||
}
|
||||
prez.update(over)
|
||||
return {"rar_credentials": {"email": "x@y.ro", "password": password}, "prezentari": [prez]}
|
||||
return {"rar_credentials": {"email": "x@y.ro", "password": password}, "prezentari": [prez], "raspuns": "complet"}
|
||||
|
||||
|
||||
def _force_status(sid, status):
|
||||
|
||||
@@ -75,7 +75,7 @@ def _body(**over) -> dict:
|
||||
"prestatii": [{"cod_prestatie": "OE-1"}],
|
||||
}
|
||||
prez.update(over)
|
||||
return {"prezentari": [prez]}
|
||||
return {"prezentari": [prez], "raspuns": "complet"}
|
||||
|
||||
|
||||
# --------------------------------------------------------------------------- #
|
||||
|
||||
@@ -205,7 +205,7 @@ def _body(prestatii, **over):
|
||||
"prestatii": prestatii,
|
||||
}
|
||||
prez.update(over)
|
||||
return {"rar_credentials": {"email": "x@y.ro", "password": "s"}, "prezentari": [prez]}
|
||||
return {"rar_credentials": {"email": "x@y.ro", "password": "s"}, "prezentari": [prez], "raspuns": "complet"}
|
||||
|
||||
|
||||
def test_nomenclator_seed_la_boot(client):
|
||||
|
||||
@@ -99,7 +99,7 @@ def _body(**over):
|
||||
"prestatii": [{"cod_prestatie": "OE-1"}],
|
||||
}
|
||||
prez.update(over)
|
||||
return {"rar_credentials": {"email": "x@y.ro", "password": "HUNTER2SECRET"}, "prezentari": [prez]}
|
||||
return {"rar_credentials": {"email": "x@y.ro", "password": "HUNTER2SECRET"}, "prezentari": [prez], "raspuns": "complet"}
|
||||
|
||||
|
||||
def test_422_does_not_echo_password(env):
|
||||
|
||||
@@ -259,5 +259,5 @@ def test_mobil_pastreaza_doua_linii_cu_bulina_si_litera(client):
|
||||
html = client.get("/?tab=coada").text
|
||||
assert re.search(r'\.trimiteri-head\s*\{\s*display\s*:\s*none', html), \
|
||||
"Headerul de coloane nu e ascuns pe mobil"
|
||||
m = re.search(r'grid-template-areas\s*:\s*"check veh\s+act env status"\s*"check meta act env status"', html)
|
||||
assert m, "Grid-areas pe 2 linii (veh/meta) lipseste din CSS-ul mobil"
|
||||
m = re.search(r'grid-template-areas\s*:\s*"check id\s+veh\s+act env status"\s*"check meta meta act env status"', html)
|
||||
assert m, "Grid-areas pe 2 linii (id+veh/meta) lipseste din CSS-ul mobil"
|
||||
|
||||
Reference in New Issue
Block a user