feat(T2): reconciliere anti-duplicat + retry/backoff + recuperare orfane
Inchide bucla de trimitere (plan.md sect. 4 worker, failure registry).
- app/reconcile.py: match_finalizata pe vin+dataPrestatie+odometruFinal (int),
alege id maxim la duplicate (RAR accepta duplicate, confirmat live)
- app/rar_client.get_finalizate: parseaza data.content (descoperit live ca
ruta = GET /prezentari/getAllPrezentariFinalizate; filtrele nu merg pe test)
- app/worker rescris:
- recuperare orfane (rand 'sending' peste lease = worker mort mid-POST)
- pe eroare tranzitorie/timeout: reconciliere INAINTE de re-send (anti-duplicat);
daca recordul exista la RAR -> sent fara re-POST
- retry/backoff exponential; peste worker_max_retries -> error + banner
- re-login la token expirat (JWT 30h)
- schema: coloana next_attempt_at (backoff) + migrare aditiva in init_db
- config: worker_sending_lease_s, worker_retry_base_s/max_s, worker_max_retries
- contract: documentata ruta+forma getAllPrezentariFinalizate (verificat live)
Verify: pytest 54 passed (15 noi T2) + validare live (reconciliere record 68514).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -110,8 +110,10 @@ class RarClient:
|
||||
def get_finalizate(self, token: str) -> list[dict]:
|
||||
"""Lista prezentarilor finalizate (pentru reconciliere — T2).
|
||||
|
||||
Atentie: pe mediul TEST raspunsul NU contine `prestatii` (vezi contract).
|
||||
Portare din rar-forms.prg:720 / getAllPrezentariFinalizate.
|
||||
GET /prezentari/getAllPrezentariFinalizate -> data.content (listă).
|
||||
Verificat live: filtrele/paginarea NU functioneaza pe test (vezi contract),
|
||||
deci interogam fara parametri si filtram client-side. Pe test `prestatii`
|
||||
vine null in fiecare item — match-ul se face pe vin+dataPrestatie+odometruFinal.
|
||||
"""
|
||||
resp = self._client.get(
|
||||
"/prezentari/getAllPrezentariFinalizate",
|
||||
@@ -119,8 +121,14 @@ class RarClient:
|
||||
)
|
||||
if resp.status_code != 200:
|
||||
raise RarError(f"getFinalizate esuat (HTTP {resp.status_code})", status_code=resp.status_code)
|
||||
data = _safe_json(resp)
|
||||
return data.get("data", data) if isinstance(data, dict) else data
|
||||
body = _safe_json(resp)
|
||||
if isinstance(body, dict):
|
||||
data = body.get("data")
|
||||
if isinstance(data, dict) and isinstance(data.get("content"), list):
|
||||
return data["content"]
|
||||
if isinstance(data, list):
|
||||
return data
|
||||
return []
|
||||
|
||||
|
||||
def _safe_json(resp: httpx.Response) -> Any:
|
||||
|
||||
Reference in New Issue
Block a user