chore: curatare agresiva comentarii — scoatere referinte US/PRD din cod si template-uri
Eliminat zgomotul de trasabilitate (US-xxx, PRD x.x, Rn, OV-x, Tn, decizii/naratiune istorica) din 41 fisiere app/ + template-uri. Pastrate comentariile care documenteaza invarianti si logica ne-evidenta (idempotenta/hash, reconciliere anti-duplicat, RAR 500 esec definitiv, creds per cont, WAF User-Agent, 422 fara echo de parola, scope NULL->1), curatate doar de tokeni. Verificare: pentru cele 27 module .py curatate, structura de cod (tokeni non-comentariu/ non-string) e IDENTICA fata de HEAD -> doar comentarii/docstring-uri schimbate. Singura schimbare de cod e in tests/test_web_responsive.py (scos 3 assert pe markeri US-006/007/008, inlocuite de asertiunile structurale alaturate). 0 tokeni US/PRD reziduali in app/. Regresie: 896 passed, 1 deselected. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,12 +1,10 @@
|
||||
"""API v1 — suprafata gateway (schelet).
|
||||
"""API v1 — suprafata gateway.
|
||||
|
||||
Endpointuri din plan.md sect. 4. In schelet:
|
||||
Endpointuri:
|
||||
- POST /v1/prezentari: enqueue cu idempotenta (dedup pe idempotency_key UNIQUE).
|
||||
- GET /v1/prezentari, /v1/prezentari/{id}: monitorizare coada.
|
||||
- GET /v1/nomenclator: cache local.
|
||||
- GET /v1/mapari: listare mapari cont.
|
||||
Validarea completa (T3), maparea op->cod, auth API-key, redactarea creds in
|
||||
middleware (CORE) si exportul CSV vin ulterior — marcate TODO unde lipsesc.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
@@ -79,7 +77,7 @@ def _classify_modal(content, mapping, mapping_meta, valid_codes, error_mode, tex
|
||||
|
||||
|
||||
def _erori_nemapate(unmapped: list[dict]) -> list[dict]:
|
||||
"""Coduri nemapate imbogatite cu 3 niveluri (COD_NEMAPAT), pentru raspuns onest."""
|
||||
"""Coduri nemapate imbogatite cu 3 niveluri (COD_NEMAPAT)."""
|
||||
return [
|
||||
{**u, **err_eroare("COD_NEMAPAT", cauza=f"cod {u.get('cod_op_service')} necunoscut/fara mapare RAR")}
|
||||
for u in unmapped
|
||||
@@ -87,7 +85,7 @@ def _erori_nemapate(unmapped: list[dict]) -> list[dict]:
|
||||
|
||||
|
||||
def _motiv_clasificare(cl: dict) -> str | None:
|
||||
"""Rezumat uman pe o linie pentru un rezultat de clasificare (PRD 5.7).
|
||||
"""Rezumat uman pe o linie pentru un rezultat de clasificare.
|
||||
|
||||
None cand status='queued'. Acopera toate ramurile de blocaj: erori de continut
|
||||
(needs_data), coduri nemapate (needs_mapping) si auto_send oprit (needs_mapping).
|
||||
@@ -107,7 +105,7 @@ def _motiv_clasificare(cl: dict) -> str | None:
|
||||
|
||||
|
||||
def _rezultat_enqueue(submission_id: int | None, cl: dict, **extra) -> SubmissionResult:
|
||||
"""SubmissionResult onest dintr-un rezultat de clasificare (PRD 5.7).
|
||||
"""SubmissionResult onest dintr-un rezultat de clasificare.
|
||||
|
||||
Populeaza erori (validare continut), nemapate (coduri fara mapare) si motiv (uman)
|
||||
pentru orice status != 'queued'. Aditiv: pe 'queued' toate raman goale/None.
|
||||
@@ -141,42 +139,40 @@ def create_prezentari(
|
||||
) -> PrezentariResponse:
|
||||
"""Enqueue una/mai multe prezentari. Idempotent: continut identic -> acelasi submission.
|
||||
|
||||
Validarea de continut (T3, app.validation) ruleaza inainte de enqueue:
|
||||
esecurile NU resping cererea, ci enqueue-aza cu status `needs_data` + motiv
|
||||
(plan.md sect. 3). JSON malformat -> 422 din Pydantic (validare de shape).
|
||||
Validarea de continut (app.validation) ruleaza inainte de enqueue: esecurile NU
|
||||
resping cererea, ci enqueue-aza cu status `needs_data` + motiv. JSON malformat ->
|
||||
422 din Pydantic (validare de shape).
|
||||
account_id vine din cheia API (resolve_account_id): cont real cu cheie,
|
||||
implicit id=1 in dev fara cheie, 401 fara cheie valida in prod.
|
||||
Nota: rar_credentials NU se persista (zero-storage) — worker-ul le va primi
|
||||
pe alt canal (T2); in schelet enqueue-ul doar stocheaza prezentarea.
|
||||
Cand rar_credentials lipseste, submission-ul intra fara creds efemere: worker-ul
|
||||
cade pe creds-urile durabile ale contului (`accounts.rar_creds_enc`).
|
||||
"""
|
||||
acct = account_or_default(account_id)
|
||||
# Creds RAR efemere: criptate si lipite de fiecare submission nou pana la
|
||||
# primul login reusit pentru cont (worker le sterge atunci). Zero-storage at
|
||||
# rest — niciodata in clar in DB/loguri (plan sect. 5). Optional: cand lipsesc,
|
||||
# rest — niciodata in clar in DB/loguri. Optional: cand lipsesc,
|
||||
# creds_enc=NULL si worker-ul foloseste creds-urile durabile ale contului.
|
||||
creds_enc = encrypt_creds(req.rar_credentials.model_dump()) if req.rar_credentials else None
|
||||
conn = get_connection()
|
||||
results: list[SubmissionResult] = []
|
||||
try:
|
||||
# T6/OV-1: load_mapping_meta include auto_send per op (gate pentru coduri noi).
|
||||
# load_mapping_meta include auto_send per op (gate pentru coduri noi).
|
||||
mapping_meta = load_mapping_meta(conn, acct)
|
||||
mapping = {op: meta["cod_prestatie"] for op, meta in mapping_meta.items()}
|
||||
# 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
|
||||
# Reguli text incarcate o data per cerere (seam partajat cu dry-run, invariant 5.2).
|
||||
# Reguli text incarcate o data per cerere (seam partajat cu dry-run).
|
||||
text_rules = load_text_rules(conn, acct)
|
||||
error_mode = _effective_on_unmapped_error(conn, acct, req.on_unmapped_error)
|
||||
for prez in req.prezentari:
|
||||
content = prez.model_dump()
|
||||
# T9/OV-2: canonicalize_row inaintea build_key (odometru strip ".0", VIN upper).
|
||||
# canonicalize_row inaintea build_key (odometru strip ".0", VIN upper).
|
||||
# build_key aplica account_or_default(account_id) inainte de hash:
|
||||
# None si 1 colapseaza la aceeasi cheie (canal API + canal import).
|
||||
canon = canonicalize_row(content)
|
||||
key = build_key(account_id, canon)
|
||||
# Aplica normalizarea si in content (odometru canonicalizat inainte de validare, §3.4bis)
|
||||
# Aplica normalizarea si in content (odometru canonicalizat inainte de validare)
|
||||
content.update({
|
||||
"vin": canon["vin"],
|
||||
"nr_inmatriculare": canon["nr_inmatriculare"],
|
||||
@@ -187,7 +183,7 @@ def create_prezentari(
|
||||
(key,),
|
||||
).fetchone()
|
||||
if existing:
|
||||
# US-012: un rand `error` (ex. creds RAR gresite) NU mai blocheaza tacit
|
||||
# Un rand `error` (ex. creds RAR gresite) NU mai blocheaza tacit
|
||||
# retrimiterea aceluiasi continut. Il RE-ACTIVAM (re-clasificam + actualizam
|
||||
# creds + reset), printr-un UPDATE compare-and-swap pe status='error'.
|
||||
if existing["status"] == "error":
|
||||
@@ -205,17 +201,16 @@ def create_prezentari(
|
||||
cl["rar_error"], creds_enc, existing["id"]),
|
||||
)
|
||||
if cur.rowcount == 1:
|
||||
# Creds noi se propaga si in canalul durabil (accounts.rar_creds_enc,
|
||||
# decizie #17) — ambele canale converg pe parola corectata.
|
||||
# Creds noi se propaga si in canalul durabil (accounts.rar_creds_enc)
|
||||
# — ambele canale converg pe parola corectata.
|
||||
if req.rar_credentials is not None:
|
||||
conn.execute(
|
||||
"UPDATE accounts SET rar_creds_enc=? WHERE id=?",
|
||||
(encrypt_creds(req.rar_credentials.model_dump()), acct),
|
||||
)
|
||||
# US-010: telemetrie pentru itemii rezolvati prin regula text.
|
||||
_emite_text_rule_hits(conn, acct, existing["id"], cl["resolved"])
|
||||
# Raspuns onest si la reactivare (PRD 5.7): daca re-clasificarea
|
||||
# cade pe needs_data/needs_mapping, expune motivul (nu doar status).
|
||||
# Raspuns onest si la reactivare: daca re-clasificarea cade pe
|
||||
# needs_data/needs_mapping, expune motivul (nu doar status).
|
||||
results.append(_rezultat_enqueue(existing["id"], cl, reactivated=True))
|
||||
continue
|
||||
# Cursa: alt POST/requeue a schimbat starea intre SELECT si UPDATE
|
||||
@@ -234,7 +229,7 @@ def create_prezentari(
|
||||
)
|
||||
continue
|
||||
|
||||
# Helper pur partajat cu dry-run (PRD 5.2): reproduce EXACT clasificarea
|
||||
# Helper pur partajat cu dry-run: reproduce EXACT clasificarea
|
||||
# (canonicalize + mapare op->cod + validare + auto_send gate).
|
||||
cl = _classify_modal(content, mapping, mapping_meta, valid_codes, error_mode, text_rules)
|
||||
if cl["blocked_error"]:
|
||||
@@ -247,13 +242,12 @@ def create_prezentari(
|
||||
(key, acct, cl["status"], json.dumps(cl["content"], ensure_ascii=False), cl["rar_error"], creds_enc),
|
||||
)
|
||||
sub_id = int(cur.lastrowid)
|
||||
# US-010: telemetrie pentru itemii rezolvati prin regula text.
|
||||
_emite_text_rule_hits(conn, acct, sub_id, cl["resolved"])
|
||||
# Raspuns onest (PRD 5.7): pe needs_data/needs_mapping expune erori/nemapate/motiv.
|
||||
# Raspuns onest: pe needs_data/needs_mapping expune erori/nemapate/motiv.
|
||||
results.append(_rezultat_enqueue(sub_id, cl))
|
||||
|
||||
# US-004: audit cerere API per cont. Doar metadate (count + distributie status),
|
||||
# NICIUN camp de payload PII integral. Reuse conn (T4 — fara contentie WAL).
|
||||
# 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] = {}
|
||||
for r in results:
|
||||
if r.reactivated:
|
||||
@@ -284,7 +278,7 @@ def valideaza_prezentari(
|
||||
|
||||
Intoarce pentru fiecare prezentare: verdictul (status_estimat), erorile de
|
||||
continut si codurile nemapate — exact ce ar obtine trimiterea reala pe acelasi
|
||||
payload + aceeasi mapare de cont. rar_credentials ignorat complet (PRD 5.2).
|
||||
payload + aceeasi mapare de cont. rar_credentials ignorat complet.
|
||||
"""
|
||||
acct = account_or_default(account_id)
|
||||
conn = get_connection()
|
||||
@@ -301,7 +295,7 @@ def valideaza_prezentari(
|
||||
res = _classify_modal(content, mapping, mapping_meta, valid_codes, error_mode, text_rules)
|
||||
if res["blocked_error"]:
|
||||
res = {**res, "status": "error"}
|
||||
# US-003: imbogatim fiecare element nemapat cu 3 niveluri COD_NEMAPAT
|
||||
# Imbogatim fiecare element nemapat cu 3 niveluri COD_NEMAPAT
|
||||
nemapate = [
|
||||
{**u, **err_eroare("COD_NEMAPAT", cauza=f"cod {u.get('cod_op_service')} fara mapare RAR")}
|
||||
for u in res["unmapped"]
|
||||
@@ -329,7 +323,7 @@ def list_prezentari(
|
||||
try:
|
||||
scope_sql, scope_params = account_scope_clause(account_id)
|
||||
# payload_json e plaintext (vezi submissions.payload_json); il citim doar ca
|
||||
# sa derivam campurile afisabile prin helper-ul partajat (US-003, DRY), nu il expunem.
|
||||
# sa derivam campurile afisabile prin helper-ul partajat, nu il expunem.
|
||||
cols = (
|
||||
"id, status, id_prezentare, rar_status_code, retry_count, "
|
||||
"created_at, updated_at, payload_json"
|
||||
@@ -357,13 +351,13 @@ def list_prezentari(
|
||||
conn.close()
|
||||
|
||||
|
||||
# Campuri expuse de GET /v1/prezentari/{id} — allowlist explicita (B4).
|
||||
# Exclude: rar_creds_enc, payload_json, idempotency_key, rar_error, sending_since.
|
||||
# Campuri expuse de GET /v1/prezentari/{id} — allowlist explicita.
|
||||
# Exclude: rar_creds_enc, payload_json, idempotency_key, sending_since.
|
||||
_PREZENTARE_FIELDS = frozenset({
|
||||
"id", "status", "id_prezentare", "rar_status_code", "retry_count",
|
||||
"next_attempt_at", "created_at", "updated_at", "account_id",
|
||||
"batch_id", "row_index", "purge_after",
|
||||
# T9: rar_error e SIGUR de expus — contine doar coduri/mesaje de validare RAR si
|
||||
# rar_error e SIGUR de expus — contine doar coduri/mesaje de validare RAR si
|
||||
# erori din catalog (niciodata creds, ex. RAR_CREDS_INVALIDE poarta doar cauza
|
||||
# "credentiale RAR invalide", fara parola). Face recovery-ul observabil prin API.
|
||||
"rar_error",
|
||||
@@ -383,7 +377,7 @@ def get_prezentare(
|
||||
[submission_id] + scope_params,
|
||||
).fetchone()
|
||||
if not row:
|
||||
# B3: acelasi mesaj indiferent daca randul exista dar apartine altui cont
|
||||
# Acelasi mesaj indiferent daca randul exista dar apartine altui cont
|
||||
# sau nu exista deloc — nu confirmam existenta.
|
||||
raise HTTPException(status_code=404, detail="submission inexistent")
|
||||
row_dict = dict(row)
|
||||
@@ -397,11 +391,11 @@ def delete_prezentare(
|
||||
submission_id: int,
|
||||
account_id: int = Depends(resolve_account_id),
|
||||
) -> dict:
|
||||
"""Sterge o trimitere blocata a contului cheii API (US-010).
|
||||
"""Sterge o trimitere blocata a contului cheii API.
|
||||
|
||||
Raspuns 200 + body JSON (NU 204 — clienti VFP fac string-parse). Scope evaluat
|
||||
INAINTEA starii (decizie /autoplan #20): cross-account / inexistent -> 404 (acelasi
|
||||
mesaj, B3); own-account `sent`/`sending` -> 409 (conflict de stare).
|
||||
INAINTEA starii: cross-account / inexistent -> 404 (acelasi mesaj);
|
||||
own-account `sent`/`sending` -> 409 (conflict de stare).
|
||||
"""
|
||||
conn = get_connection()
|
||||
try:
|
||||
@@ -424,10 +418,10 @@ def repune_prezentare(
|
||||
submission_id: int,
|
||||
account_id: int = Depends(resolve_account_id),
|
||||
) -> dict:
|
||||
"""Re-pune in coada o trimitere blocata a contului cheii API (US-010).
|
||||
"""Re-pune in coada o trimitere blocata a contului cheii API.
|
||||
|
||||
`error -> queued` (peste helper US-009), re-ruleaza classify. Acelasi oracol de
|
||||
scope/stare ca DELETE (404 cross-account/inexistent, 409 sent/sending).
|
||||
`error -> queued`, re-ruleaza classify. Acelasi oracol de scope/stare ca DELETE
|
||||
(404 cross-account/inexistent, 409 sent/sending).
|
||||
"""
|
||||
conn = get_connection()
|
||||
try:
|
||||
@@ -478,8 +472,7 @@ def _audit_rows(conn, date_from: str | None, date_to: str | None, status: str, a
|
||||
"""Randuri audit filtrate pe cont + data(updated_at) in [from, to].
|
||||
|
||||
account_id = contul cheii API (scope obligatoriu — PII in CSV). Randuri cu
|
||||
account_id IS NULL apartin contului 1 (legacy/OV-2). payload_json e text in
|
||||
schelet; b64_image NU intra in CSV.
|
||||
account_id IS NULL apartin contului 1. b64_image NU intra in CSV.
|
||||
"""
|
||||
scope_sql, scope_params = account_scope_clause(account_id)
|
||||
sql = (
|
||||
@@ -514,7 +507,7 @@ def _audit_rows(conn, date_from: str | None, date_to: str | None, status: str, a
|
||||
"submission_id": r["id"],
|
||||
"status": r["status"],
|
||||
"id_prezentare": r["id_prezentare"] or "",
|
||||
# NULL→cont 1 (OV-2): coloana reflecta invariantul de scope, nu "" ambiguu.
|
||||
# NULL→cont 1: coloana reflecta invariantul de scope, nu "" ambiguu.
|
||||
"account_id": account_or_default(r["account_id"]),
|
||||
"vin": p.get("vin") or "",
|
||||
"nr_inmatriculare": p.get("nr_inmatriculare") or "",
|
||||
@@ -539,7 +532,7 @@ def audit_export(
|
||||
|
||||
pe data(updated_at). `status` implicit `sent` (ce a ajuns efectiv la RAR);
|
||||
`status=all` exporta toata coada contului. Leaga retinerea 90 zile prin coloana
|
||||
`purge_after` (plan.md sect. 4 + 8). b64_image nu se exporta.
|
||||
`purge_after`. b64_image nu se exporta.
|
||||
"""
|
||||
conn = get_connection()
|
||||
try:
|
||||
@@ -568,7 +561,7 @@ def get_mapari(
|
||||
"""Maparile operatie->cod ale contului curent.
|
||||
|
||||
Parametrul `account_id` din query e pastrat pentru compatibilitate, dar contul
|
||||
efectiv vine MEREU din cheia API (TD-3.2). Daca e prezent si difera -> 400.
|
||||
efectiv vine MEREU din cheia API. Daca e prezent si difera -> 400.
|
||||
"""
|
||||
if account_id is not None and account_id != key_account:
|
||||
raise HTTPException(
|
||||
@@ -635,7 +628,7 @@ def create_mapare(
|
||||
|
||||
|
||||
class RarCredsIn(BaseModel):
|
||||
"""Creds RAR durabile per-cont (D4). Stocate criptate (Fernet) in accounts.rar_creds_enc."""
|
||||
"""Creds RAR durabile per-cont. Stocate criptate (Fernet) in accounts.rar_creds_enc."""
|
||||
|
||||
email: str = Field(..., min_length=1)
|
||||
password: str = Field(..., min_length=1, repr=False)
|
||||
@@ -646,7 +639,7 @@ def set_rar_creds(
|
||||
req: RarCredsIn,
|
||||
account_id: int = Depends(resolve_account_id),
|
||||
) -> dict:
|
||||
"""Seteaza creds RAR durabile per-cont (D4/T1).
|
||||
"""Seteaza creds RAR durabile per-cont.
|
||||
|
||||
Criptate Fernet in accounts.rar_creds_enc. Worker-ul le foloseste ca fallback
|
||||
cand submission-ul nu mai are creds (canal web fara re-pusher, restart worker).
|
||||
|
||||
Reference in New Issue
Block a user