feat(5.8): reguli mapare pe text (substring/cont) + UX tabel trimiteri (detaliu inline, fara scroll, cod RAR)
Reguli text per cont (operation_text_rules), resolve_prestatii cu param aditiv text_rules + precedenta stricta, threadat pe toate cele 6 callsite-uri + valid_codes + seam classify_prezentare. UI Mapari: sectiune reguli + preview pre-salvare + overlap + telemetrie text_rule_hit. UX tabel: cod_rar sub operatie, pill eticheta scurta, fara scroll orizontal (scopat .tabel-trimiteri + carduri <768px), detaliu inline expandabil (a11y + pauza poll). code-review: reparat regula auto_send=0 care trimitea automat la RAR in loc sa tina randul pentru review. 814 passed. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -44,9 +44,12 @@ from ...import_parse import (
|
||||
parse_file,
|
||||
)
|
||||
from ...mapping import (
|
||||
_emite_text_rule_hits,
|
||||
account_or_default,
|
||||
has_no_auto_send,
|
||||
load_mapping_meta,
|
||||
load_nomenclator_codes,
|
||||
load_text_rules,
|
||||
normalize_for_match,
|
||||
resolve_prestatii,
|
||||
)
|
||||
@@ -126,6 +129,8 @@ def _resolve_row_for_preview(
|
||||
mapping_meta: dict[str, dict],
|
||||
formula_columns: list[str],
|
||||
override: dict[str, Any] | None = None,
|
||||
valid_codes: set[str] | None = None,
|
||||
text_rules: list[dict] | None = None,
|
||||
) -> dict[str, Any]:
|
||||
"""Rezolva un rand din import pentru preview: aplica mapare coloane + validare.
|
||||
|
||||
@@ -201,7 +206,7 @@ def _resolve_row_for_preview(
|
||||
|
||||
# Rezolvare prestatii
|
||||
prestatii = mapped.get("prestatii") or []
|
||||
resolved, unmapped = resolve_prestatii(prestatii, mapping)
|
||||
resolved, unmapped = resolve_prestatii(prestatii, mapping, valid_codes, text_rules)
|
||||
mapped["prestatii"] = resolved
|
||||
|
||||
# Determinare stare
|
||||
@@ -745,6 +750,9 @@ def preview_import(
|
||||
# Incarca maparea de operatii o singura data (Eng#5: load_mapping o singura data)
|
||||
mapping_meta = load_mapping_meta(conn, acct)
|
||||
mapping = {op: meta["cod_prestatie"] for op, meta in mapping_meta.items()}
|
||||
# T2: validare nomenclator + reguli text incarcate O DATA, inainte de bucla pe randuri.
|
||||
valid_codes = load_nomenclator_codes(conn) or None
|
||||
text_rules = load_text_rules(conn, acct)
|
||||
|
||||
# Reconstruieste parsed info (coercion_flags si date_col_format) din datele stocate
|
||||
# Nota: import_rows stocheaza raw_json dupa coercion (din parse_file)
|
||||
@@ -797,6 +805,8 @@ def preview_import(
|
||||
mapping_meta=mapping_meta,
|
||||
formula_columns=formula_columns,
|
||||
override=overrides[i] or None,
|
||||
valid_codes=valid_codes,
|
||||
text_rules=text_rules,
|
||||
)
|
||||
|
||||
# Calculeaza cheia de idempotenta pentru randurile ok/needs_review
|
||||
@@ -1030,6 +1040,9 @@ def commit_import(
|
||||
# Incarca maparea de operatii
|
||||
mapping_meta = load_mapping_meta(conn, acct)
|
||||
mapping = {op: meta["cod_prestatie"] for op, meta in mapping_meta.items()}
|
||||
# T2: validare nomenclator + reguli text incarcate O DATA, inainte de bucla pe randuri.
|
||||
valid_codes = load_nomenclator_codes(conn) or None
|
||||
text_rules = load_text_rules(conn, acct)
|
||||
|
||||
# Construieste payload-urile submissions
|
||||
enqueued: list[dict] = []
|
||||
@@ -1076,7 +1089,7 @@ def commit_import(
|
||||
|
||||
# Rezolva prestatii INAINTE de canonicalizare (altfel cheia difera de cea din preview)
|
||||
prestatii = mapped.get("prestatii") or []
|
||||
resolved, _ = resolve_prestatii(prestatii, mapping)
|
||||
resolved, _ = resolve_prestatii(prestatii, mapping, valid_codes, text_rules)
|
||||
mapped["prestatii"] = resolved
|
||||
|
||||
# Canonicalizare (dupa rezolvare prestatii -> cod_prestatie inclus in cheie)
|
||||
@@ -1127,6 +1140,8 @@ def commit_import(
|
||||
toctou_collisions.append(row_index)
|
||||
else:
|
||||
sub_id = cur.lastrowid
|
||||
# US-010: telemetrie pentru itemii rezolvati prin regula text.
|
||||
_emite_text_rule_hits(conn, acct, int(sub_id), resolved)
|
||||
enqueued.append({
|
||||
"submission_id": sub_id,
|
||||
"row_index": row_index,
|
||||
|
||||
@@ -25,11 +25,13 @@ from ...db import get_connection
|
||||
from ...errors import eroare as err_eroare
|
||||
from ...idempotency import build_key, canonicalize_row
|
||||
from ...mapping import (
|
||||
_emite_text_rule_hits,
|
||||
account_or_default,
|
||||
account_scope_clause,
|
||||
classify_prezentare,
|
||||
load_mapping_meta,
|
||||
load_nomenclator_codes,
|
||||
load_text_rules,
|
||||
pending_unmapped,
|
||||
reresolve_account,
|
||||
save_mapping,
|
||||
@@ -65,13 +67,13 @@ def _effective_on_unmapped_error(conn, acct: int, req_value: bool | None) -> boo
|
||||
return bool(row["on_unmapped_error_default"]) if row else False
|
||||
|
||||
|
||||
def _classify_modal(content, mapping, mapping_meta, valid_codes, error_mode) -> dict:
|
||||
def _classify_modal(content, mapping, mapping_meta, valid_codes, error_mode, text_rules=None) -> dict:
|
||||
"""classify_prezentare + aplicarea modului on_unmapped_error.
|
||||
|
||||
Cand exista coduri nemapate si error_mode=True, marcheaza outcome-ul ca respingere
|
||||
(blocked_error=True): rutele NU mai fac enqueue, ci intorc o eroare per-element.
|
||||
"""
|
||||
cl = classify_prezentare(content, mapping, mapping_meta, valid_codes)
|
||||
cl = classify_prezentare(content, mapping, mapping_meta, valid_codes, text_rules)
|
||||
cl["blocked_error"] = bool(cl["unmapped"]) and error_mode
|
||||
return cl
|
||||
|
||||
@@ -164,6 +166,8 @@ def create_prezentari(
|
||||
# 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).
|
||||
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()
|
||||
@@ -187,7 +191,7 @@ def create_prezentari(
|
||||
# retrimiterea aceluiasi continut. Il RE-ACTIVAM (re-clasificam + actualizam
|
||||
# creds + reset), printr-un UPDATE compare-and-swap pe status='error'.
|
||||
if existing["status"] == "error":
|
||||
cl = _classify_modal(content, mapping, mapping_meta, valid_codes, error_mode)
|
||||
cl = _classify_modal(content, mapping, mapping_meta, valid_codes, error_mode, text_rules)
|
||||
if cl["blocked_error"]:
|
||||
# on_unmapped_error=True: nu reactivam; randul ramane 'error'.
|
||||
results.append(_rezultat_respins(existing["id"], cl))
|
||||
@@ -208,6 +212,8 @@ def create_prezentari(
|
||||
"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).
|
||||
results.append(_rezultat_enqueue(existing["id"], cl, reactivated=True))
|
||||
@@ -230,7 +236,7 @@ def create_prezentari(
|
||||
|
||||
# Helper pur partajat cu dry-run (PRD 5.2): reproduce EXACT clasificarea
|
||||
# (canonicalize + mapare op->cod + validare + auto_send gate).
|
||||
cl = _classify_modal(content, mapping, mapping_meta, valid_codes, error_mode)
|
||||
cl = _classify_modal(content, mapping, mapping_meta, valid_codes, error_mode, text_rules)
|
||||
if cl["blocked_error"]:
|
||||
# on_unmapped_error=True: respinge fara enqueue (cod necunoscut/nemapat).
|
||||
results.append(_rezultat_respins(None, cl))
|
||||
@@ -240,8 +246,11 @@ def create_prezentari(
|
||||
"VALUES (?, ?, ?, ?, ?, ?)",
|
||||
(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.
|
||||
results.append(_rezultat_enqueue(int(cur.lastrowid), cl))
|
||||
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).
|
||||
@@ -284,10 +293,12 @@ def valideaza_prezentari(
|
||||
mapping_meta = load_mapping_meta(conn, acct)
|
||||
mapping = {op: meta["cod_prestatie"] for op, meta in mapping_meta.items()}
|
||||
valid_codes = load_nomenclator_codes(conn) or None
|
||||
# Acelasi seam ca trimiterea reala: dry-run trebuie sa vada aceleasi reguli text.
|
||||
text_rules = load_text_rules(conn, acct)
|
||||
error_mode = _effective_on_unmapped_error(conn, acct, req.on_unmapped_error)
|
||||
for i, prez in enumerate(req.prezentari):
|
||||
content = prez.model_dump()
|
||||
res = _classify_modal(content, mapping, mapping_meta, valid_codes, error_mode)
|
||||
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
|
||||
|
||||
Reference in New Issue
Block a user