"""Store partajat pentru sugestii (SILVER) si mapari validate de oameni (GOLD cross-account). Straturi (L14-S3, PRD 5.14): - mapping_suggestions (SILVER): sugestii LLM/embedding, citite DOAR de suggest/pending_unmapped, NICIODATA de load_mapping/resolve_prestatii (separare structurala #13). - shared_mappings (GOLD partajat): mapari validate de om din orice cont; pot pre-completa editorul (suggestion-only cross-account, F-A/#11); auto-send DOAR GOLD propriu (operations_mapping per-cont, Eng-F2). Invariante: - INSERT OR IGNORE la seed: nu suprascrie randuri existente (#2). - NUL = is_nul=1, cod_prestatie NULL; NU se promoveaza la cod RAR (#4). - source/confidence pe fiecare rand (provenienta + rollback batch model prost, #5). - Wiring in resolve_prestatii/load_mapping vine in L14-S6; modulul de fata e API pur. """ from __future__ import annotations import sqlite3 from typing import Any from .mapping import normalize_for_match def seed_suggestions( conn: sqlite3.Connection, items: list[dict[str, Any]], ) -> int: """Insereaza sugestii in mapping_suggestions (SILVER). INSERT OR IGNORE. Nu suprascrie randuri deja existente (#2): re-rularea seeder-ului e sigura. Fiecare item trebuie sa contina: - 'denumire': str — text brut (se normalizeaza intern cu normalize_for_match) - 'source': str — 'llm', 'embedding', etc. Optional: - 'cod_prestatie': str | None — ignorat cand is_nul=True - 'is_nul': bool — True pt non-operatii (supresie, #4); cod_prestatie stocat NULL - 'confidence': float — 0..1 (default 0.0) Returneaza numarul de randuri inserate efectiv (0 daca toate existau deja). """ inserted = 0 for item in items: den_norm = normalize_for_match(item.get("denumire") or "") if not den_norm: continue is_nul = 1 if item.get("is_nul") else 0 # NUL -> cod NULL obligatoriu (supresie stricta, #4) # Normalizeaza INAINTE de truthiness: un cod whitespace-only (" ") sau # ne-string trebuie sa devina NULL, nu '' (altfel rand non-NUL cu cod gol). cod = None if not is_nul: raw_cod = str(item.get("cod_prestatie") or "").strip().upper() cod = raw_cod or None source = str(item.get("source") or "llm") confidence = float(item.get("confidence") or 0.0) cur = conn.execute( """ INSERT OR IGNORE INTO mapping_suggestions (denumire_normalizata, cod_prestatie, is_nul, source, confidence) VALUES (?, ?, ?, ?, ?) """, (den_norm, cod, is_nul, source, confidence), ) inserted += cur.rowcount return inserted def lookup_suggestion( conn: sqlite3.Connection, denumire: str, ) -> sqlite3.Row | None: """Cauta sugestie SILVER dupa denumire normalizata. Returneaza randul din mapping_suggestions sau None daca nu exista. NOTA: apelantul trebuie sa verifice is_nul inainte de a folosi cod_prestatie. """ den_norm = normalize_for_match(denumire) if not den_norm: return None return conn.execute( "SELECT * FROM mapping_suggestions WHERE denumire_normalizata = ?", (den_norm,), ).fetchone() def lookup_shared_gold( conn: sqlite3.Connection, denumire: str, ) -> sqlite3.Row | None: """Cauta mapare GOLD partajata dupa denumire normalizata. Returneaza randul din shared_mappings sau None daca nu exista. NOTA (F-A/#11): acest GOLD partajat e suggestion-only cross-account; auto-send vine DOAR din operations_mapping (GOLD propriu per-cont). """ den_norm = normalize_for_match(denumire) if not den_norm: return None return conn.execute( "SELECT * FROM shared_mappings WHERE denumire_normalizata = ?", (den_norm,), ).fetchone() def record_human_validation( conn: sqlite3.Connection, denumire: str, cod_prestatie: str, *, source: str = "human", provenance: str | None = None, confidence: float = 1.0, ) -> None: """Inregistreaza o mapare validata de om in GOLD partajat (shared_mappings). Daca denumirea exista deja: incrementeaza confirmations + actualizeaza updated_at. Daca nu exista: insert nou cu confirmations=1. Apelat la confirmarea umana a unei mapari (din editorul needs_mapping). Wiring efectiv vine in L14-S6 (dupa 5.15); aceasta functie e API-ul store. NOTA: NU intra in operations_mapping (GOLD per-cont) — acela e gestionat separat de editorul existent. Ambele pot coexista. """ den_norm = normalize_for_match(denumire) if not den_norm: return cod = (cod_prestatie or "").strip().upper() if not cod: return conn.execute( """ INSERT INTO shared_mappings (denumire_normalizata, cod_prestatie, source, provenance, confidence, confirmations) VALUES (?, ?, ?, ?, ?, 1) ON CONFLICT(denumire_normalizata) DO UPDATE SET confirmations = confirmations + 1, updated_at = datetime('now') """, (den_norm, cod, source, provenance, confidence), )