Files
rar-autopass/docs/prd/prd-5.2-dryrun-valideaza.md
Claude Agent ae7960294f feat(api): endpoint dry-run POST /v1/prezentari/valideaza (PRD 5.2)
Valideaza payload + mapare si intoarce verdictul real (status_estimat
queued/needs_data/needs_mapping + erori [{field,message}] + coduri nemapate
+ prestatii rezolvate) FARA enqueue, fara creds, zero scriere DB. "Magical
moment" pentru integratori (ROAAUTO / soft propriu / punte VFP).

Cheia de design: helper pur partajat classify_prezentare (mapping.py) folosit
de AMBELE rute, ca dry-run-ul sa nu poata diverge de trimiterea reala
(invariant de corectitudine). create_prezentari refactorizat pe el cu
comportament identic (test_api.py verde).

Scope minim (decizie user): doar validare+mapare, fara idempotency/duplicat
(idempotency.py neatins); descoperibilitate in hub /integrare amanata.

VERIFY context curat PASS (577 teste; E2E API cu cele 3 verdicte + COUNT(*)=0
dupa dry-run). /code-review high: 0 findings.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-22 18:54:50 +00:00

11 KiB

PRD 5.2 — Endpoint dry-run POST /v1/prezentari/valideaza

Stare: inchis

Proces complet: docs/ROADMAP.md §5. Contract RAR (sursa de adevar): docs/api-rar-contract.md. Starea trece: draft → aprobat → in-executie → verify-pass → inchis (actualizata de lead).

1. Obiectiv

Un endpoint care valideaza un payload de prezentari exact ca POST /v1/prezentari, dar fara enqueue si fara efecte secundare: intoarce verdictul (queued / needs_data / needs_mapping), erorile reale [{field, message}] si codurile nemapate, ca integratorul (ROAAUTO / soft propriu / punte VFP) sa-si verifice integrarea inainte sa trimita ceva real. "Magical moment" de onboarding (Etapa 5 — ergonomie & integrare).

Invariant de corectitudine (motivul cheie de design): dry-run-ul trebuie sa produca acelasi verdict pe care l-ar produce POST /v1/prezentari pe acelasi payload. Daca cele doua cai diverg, dry-run-ul minte — mai rau decat sa nu existe. De aceea logica de clasificare se extrage intr-un helper pur partajat folosit de AMBELE endpoint-uri (nu se duplica).

2. Non-Goals (anti scope-creep)

  • NU atinge coada submissions — zero INSERT/UPDATE/DELETE; endpoint read-only (citeste doar operations_mapping pentru rezolvarea codurilor, scoped pe cont).
  • NU detectie duplicat / idempotency — nu raporteaza idempotency_key si nu cauta submission-uri identice existente (decizie utilizator 2026-06-22: doar validare + mapare). idempotency.py neatins.
  • NU stocheaza si NU foloseste creds RARrar_credentials devine optional si, daca e prezent, e ignorat (nu se cripteaza, nu se logheaza, nu se trimite nicaieri).
  • NU UI / hub — documentarea in /integrare (5.1) + snippet-uri ramane follow-up (decizie utilizator 2026-06-22). Acest PRD = strict endpoint + teste.
  • NU atinge worker, masina de stari, schema, validation.py (regulile), nomenclator.
  • NU apel live la RAR — validarea e 100% locala (replica regulilor RAR din validation.py).

3. Stories atomice

US-001: Endpoint dry-run POST /v1/prezentari/valideaza

Ca integrator (ROAAUTO / soft propriu / VFP) vreau sa trimit un payload de prezentari si sa primesc inapoi exact erorile + verdictul pe care le-ar produce trimiterea reala, fara sa enqueue ceva sau sa am nevoie de creds RAR, pentru ca sa-mi validez integrarea sigur, repetabil, inainte de prima trimitere reala.

  • Depinde de: —
  • Fisiere: app/mapping.py (helper pur nou classify_prezentare), app/api/v1/router.py (ruta + refactor create_prezentari sa foloseasca helper-ul), app/models.py (modele request/response), tests/test_validare_dryrun.py (~4 fisiere)
  • Test intai (RED): tests/test_validare_dryrun.py
    • test_payload_valid_returneaza_queued (valid → status_estimat="queued", valid=True, erori=[])
    • test_vin_invalid_returneaza_needs_data (VIN cu O/I/Q → needs_data + eroare pe field="vin")
    • test_data_viitoare_needs_data (data in viitor → needs_data)
    • test_cod_op_nemapat_returneaza_needs_mapping (cod_op_service necunoscut → needs_mapping + nemapate=[{cod_op_service, denumire}])
    • test_mapare_existenta_rezolva_codul (cu mapare salvata pe cont → cod_op_service rezolvat in prestatii_rezolvate, status_estimat="queued")
    • test_fara_creds_merge (body FARA rar_credentials → 200)
    • test_nu_scrie_in_coada (COUNT(*) submissions inainte == dupa; zero efecte secundare)
    • test_multi_prezentari_rezultate_per_index (lista [valid, invalid] → 2 rezultate, index 0/1, statusuri diferite)
    • test_shape_invalid_422 (prestatie fara cod_prestatie SI fara cod_op_service → 422 de shape, ca endpoint-ul real; raspunsul NU contine echo de input/parola)
  • Acceptance criteria:
    • POST /v1/prezentari/valideaza exista, accepta {prezentari:[...], rar_credentials?}, intoarce {results:[{index, valid, status_estimat, erori, nemapate, prestatii_rezolvate}]}.
    • status_estimat ∈ {queued, needs_data, needs_mapping} si coincide cu statusul pe care POST /v1/prezentari l-ar atribui pe acelasi payload + aceeasi mapare de cont (verificat prin helper partajat classify_prezentare, folosit de ambele rute).
    • valid == (status_estimat == "queued").
    • erori = exact lista validate_prezentare (forma [{field, message}]), goala cand e curat.
    • nemapate = [{cod_op_service, denumire}] cand exista coduri interne nerezolvate; altfel [].
    • prestatii_rezolvate arata codurile RAR rezolvate (cod_op_service mapat → cod_prestatie umplut).
    • rar_credentials optional; daca lipseste → 200; daca e prezent → ignorat (necriptat, nelogat).
    • Zero scriere in DB: COUNT(*) FROM submissions neschimbat dupa apel (read-only pe mapping).
    • Scope pe cont prin resolve_account_id (ca endpoint-ul real): maparea folosita la rezolvare e a contului cheii API (dev fara cheie → cont 1; prod → cheie obligatorie).
    • create_prezentari (endpoint-ul real) ramane cu comportament identic — toate testele existente tests/test_api.py raman verzi dupa refactor-ul catre helper-ul partajat.
    • python3 -m pytest -q verde.
  • Verificare E2E: canal API — POST /v1/prezentari/valideaza pe instanta locala cu (a) payload valid → queued, (b) VIN invalid → needs_data + mesaj real, (c) cod_op nemapat → needs_mapping; apoi COUNT(*) submissions neschimbat. Regresia de aur: POST /v1/prezentari real → worker → FINALIZATA la RAR test (neatins de endpoint-ul nou).

4. Riscuri

  • Divergenta dry-run vs. real (risc principal) → mitigat prin helper pur partajat classify_prezentare folosit de ambele rute + AC explicit + testele test_api.py care lock-uiesc calea reala dupa refactor.
  • Refactor al caii reale (create_prezentari adopta helper-ul) ar putea schimba subtil comportamentul → mitigat: testele existente test_api.py sunt contractul; raman verzi = comportament identic. Helper-ul intoarce exact aceleasi status + rar_error ca azi (queued / needs_data + erori JSON / needs_mapping + unmapped JSON / needs_mapping + auto_send note).
  • Scurgere de creds prin noul model → mitigat: rar_credentials optional, ignorat, repr=False pastrat; handler-ul global 422 din main.py deja dropeaza input/ctx (no-echo parola) — acoperit de test_shape_invalid_422.
  • Asteptare gresita: "valideaza" = duplicat-check → documentat ca Non-Goal in raspuns/docstring; fara idempotency_key in raspuns ca sa nu sugereze dedup.

5. Intrebari deschise

Rezolvate cu utilizatorul la poarta de aprobare PRD (2026-06-22).

  • Continut raspuns — REZOLVAT: doar validare + mapare (fara idempotency/duplicat). [user 2026-06-22]
  • Hub /integrare — REZOLVAT: amanat; 5.2 = endpoint + teste. [user 2026-06-22]
  • auto_send=0 pe un cod mapat: pe calea reala devine needs_mapping cu nota "review manual". Dry-run-ul raporteaza acelasi status_estimat="needs_mapping" (consistenta cu real). Confirmat ca acceptabil — fara camp separat pentru cazul auto_send (ar fi scope creep). [decizie de plan, acceptata implicit]

6. Valuri de executie (graful de dependente)

Val 1: [US-001]   ← singur story, fara dependente. Un worker (sau lead direct, livrabila mica — ROADMAP §5.5).

Livrabila mica: poate rula fara TeamCreate (un singur worker Sonnet TDD), dar VERIFY in context curat

  • writeback raman obligatorii (ROADMAP §5.5).

7. Review-uri de plan (aplicate inainte de cod — ROADMAP §5.3)

CEO (valoare/scope) — PASS. Problema corecta, calea cea mai directa (reuse pur validation.py+mapping.py, zero logica de domeniu noua). Inversiune ("ce-l face sa esueze?"): divergenta dry-run vs. real — neutralizata prin helper-ul partajat classify_prezentare + lock-ul test_api.py. Scope minim corect; singura "expansiune" (extragerea clasificatorului) e ceruta de corectitudine, nu scope creep. Risc flagat (deferare constienta): descoperibilitate — un endpoint pe care integratorii nu-l gasesc nu-si livreaza valoarea pana cand un follow-up il expune in /integrare. Acceptat de utilizator (amanat); valoarea 5.2 se realizeaza la follow-up.

Eng (fezabilitate/teste) — PASS. Fezabilitate triviala, fara atingere schema/worker/idempotenta. Lista de teste e completa (happy path + fiecare ramura de eroare + fara-creds + multi-prezentare cu index + shape-422 fara echo + aserctia critica zero-efecte COUNT(*) inainte==dupa). Singurul risc pe calea de aur = refactor-ul create_prezentari pe helper-ul partajat; contractul de blocare = suita test_api.py existenta (ramane verde = comportament identic). Read-only → fara logging nou (consistent cu GET-urile existente); in prod cere cheie prin resolve_account_id (fara suprafata noua de abuz).


Raport VERIFY

Completat de subagentul verificator (context curat) in faza VERIFY — vezi ROADMAP §5.6. PASS/FAIL per criteriu, cu dovezi (output pytest citat, E2E pe canal API). Lipseste pana la VERIFY.

Verificator independent (context curat, rol qa-only), 2026-06-22. VERDICT GLOBAL: PASS.

1. Suita — PASS. python3 -m pytest -q → 577 passed (226 warnings preexistente Starlette/templating). pytest tests/test_validare_dryrun.py tests/test_api.py -q → 14 passed (9 dry-run TDD + 5 regresia caii reale).

2. Acceptance criteria US-001 — toate PASS. Endpoint exista cu raspunsul {results:[{index,valid, status_estimat,erori,nemapate,prestatii_rezolvate}]}. status_estimat coincide cu POST /v1/prezentari prin helper-ul partajat classify_prezentare apelat de AMBELE rute (router.py create + valideaza, acelasi load_mapping_meta). valid == (status=="queued"). erori=lista validate_prezentare. nemapate= [{cod_op_service,denumire}]. rar_credentials optional + ignorat (encrypt_creds absent din ruta; parola din body NU apare in raspuns). Zero scriere DB (COUNT(*) submissions=0 dupa 3 apeluri). Scope prin resolve_account_id. create_prezentari identic (test_api.py verde).

3. E2E canal API (TestClient + DB temp, verificare directa SQLite) — PASS. (a) valid+creds → queued, valid=true, erori=[], fara leak creds; (b) VIN cu O/I/Q → needs_data + eroare reala pe vin; (c) cod_op nemapat → needs_mapping + nemapate populat. Dupa toate: GET /v1/prezentari=0, DB COUNT(*)=0.

4. Regresia de aur — PASS (live neprobat, conform asteptarii). POST /v1/prezentari enqueue-aza corect (200, queued, COUNT(*)=1) + test_api.py verde. Flux live RAR (worker→FINALIZATA pe RAR test) NEPROBAT — lipsesc secretele in mediu (AUTOPASS_CREDS_KEY nesetat, fara creds RAR test/--send). NU e FAIL al 5.2: endpoint-ul nou e read-only, nu atinge worker/coada/schema. Documentat, nu inventat.