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>
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 doaroperations_mappingpentru rezolvarea codurilor, scoped pe cont). - NU detectie duplicat / idempotency — nu raporteaza
idempotency_keysi nu cauta submission-uri identice existente (decizie utilizator 2026-06-22: doar validare + mapare).idempotency.pyneatins. - NU stocheaza si NU foloseste creds RAR —
rar_credentialsdevine 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 nouclassify_prezentare),app/api/v1/router.py(ruta + refactorcreate_prezentarisa 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 pefield="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 inprestatii_rezolvate,status_estimat="queued")test_fara_creds_merge(body FARArar_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,index0/1, statusuri diferite)test_shape_invalid_422(prestatie faracod_prestatieSI faracod_op_service→ 422 de shape, ca endpoint-ul real; raspunsul NU contine echo deinput/parola)
- Acceptance criteria:
POST /v1/prezentari/valideazaexista, 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 carePOST /v1/prezentaril-ar atribui pe acelasi payload + aceeasi mapare de cont (verificat prin helper partajatclassify_prezentare, folosit de ambele rute).valid == (status_estimat == "queued").erori= exact listavalidate_prezentare(forma[{field, message}]), goala cand e curat.nemapate=[{cod_op_service, denumire}]cand exista coduri interne nerezolvate; altfel[].prestatii_rezolvatearata codurile RAR rezolvate (cod_op_service mapat → cod_prestatie umplut).rar_credentialsoptional; daca lipseste → 200; daca e prezent → ignorat (necriptat, nelogat).- Zero scriere in DB:
COUNT(*) FROM submissionsneschimbat 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 existentetests/test_api.pyraman verzi dupa refactor-ul catre helper-ul partajat.python3 -m pytest -qverde.
- Verificare E2E: canal API —
POST /v1/prezentari/valideazape instanta locala cu (a) payload valid →queued, (b) VIN invalid →needs_data+ mesaj real, (c) cod_op nemapat →needs_mapping; apoiCOUNT(*)submissions neschimbat. Regresia de aur:POST /v1/prezentarireal → worker →FINALIZATAla RAR test (neatins de endpoint-ul nou).
4. Riscuri
- Divergenta dry-run vs. real (risc principal) → mitigat prin helper pur partajat
classify_prezentarefolosit de ambele rute + AC explicit + testeletest_api.pycare lock-uiesc calea reala dupa refactor. - Refactor al caii reale (
create_prezentariadopta helper-ul) ar putea schimba subtil comportamentul → mitigat: testele existentetest_api.pysunt contractul; raman verzi = comportament identic. Helper-ul intoarce exact aceleasistatus+rar_errorca azi (queued / needs_data + erori JSON / needs_mapping + unmapped JSON / needs_mapping + auto_send note). - Scurgere de creds prin noul model → mitigat:
rar_credentialsoptional, ignorat,repr=Falsepastrat; handler-ul global 422 dinmain.pydeja dropeazainput/ctx(no-echo parola) — acoperit detest_shape_invalid_422. - Asteptare gresita: "valideaza" = duplicat-check → documentat ca Non-Goal in raspuns/docstring;
fara
idempotency_keyin 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=0pe un cod mapat: pe calea reala devineneeds_mappingcu nota "review manual". Dry-run-ul raporteaza acelasistatus_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.