"""Acceptance rehearsal of the production scenario on DEV (real HTTP Hub + real Agent worker + Firebird COPY): product WITHOUT EAN, NCM=00000000, cClassTrib typed by the operator -> visible status (no silent failure) -> an ADMIN decides the NCM of the conflicting GTIN in the KB -> the flagged re-evaluation finds the unique verified product by NAME -> the Agent writes the NCM (typed write) -> read-back + audit -> second run writes 0. Also: DRY_RUN shows the candidate without writing; EAN path (KB hit, zero provider requests) with latency; human override is preserved. docker compose up -d --build fiscal-hub && python scripts/enrichment_acceptance_dev.py The ADMIN decision is applied through the same code as the panel route (kb.attest + mark_for_reevaluation), with operator name 'E2E-OPERATOR'. Writes go only to the disposable COPY. """ import json import sys import time from pathlib import Path ROOT = Path(__file__).resolve().parents[1] sys.path.insert(0, str(ROOT)); sys.path.insert(0, str(ROOT / "scripts")); sys.path.insert(0, str(ROOT / "agent" / "src")) from dotenv import load_dotenv # noqa: E402 load_dotenv(ROOT / ".env") import enrichment_kb_e2e as ke # noqa: E402 import enrichment_local_hub_e2e as lh # noqa: E402 from gti_fiscal_agent.client import HubClient # noqa: E402 base, ce = ke.base, ke.ce GTIN = "7894900010015" def insert(code, name, ncm, ean, cclasstrib): conn = base.connect(True); cur = conn.cursor() cur.execute("INSERT INTO C000025 (CODIGO, PRODUTO, CODBARRA, CLASSIFICACAO_FISCAL, CCLASSTRIB) VALUES (?, ?, ?, ?, ?)", (code, name, ean, ncm, cclasstrib)) conn.commit(); conn.close() def row(code): conn = base.connect(False); cur = conn.cursor() cur.execute("SELECT CLASSIFICACAO_FISCAL, CEST, IPI, CCLASSTRIB, PRODUTO FROM C000025 WHERE CODIGO=?", (code,)) r = cur.fetchone(); conn.close() return tuple(None if v is None else (v if isinstance(v, float) else str(v).strip()) for v in r) if r else None def ui(code, inst): return lh.sql(f"select ui_status||'|'||coalesce(reason_code,'') from product_enrichments where product_code='{code}' and installation_id=(select id from installations where installation_id='{inst}')") def main(): checks, info = {}, {} base.restore_copy() c1 = HubClient(lh.HUB, lh.TOKEN) inst = c1.identity()["installation_id"] lh.set_features(inst, "DISABLED") lh.sql(f"delete from product_field_audits where installation_id=(select id from installations where installation_id='{inst}')") lh.sql(f"delete from product_enrichments where installation_id=(select id from installations where installation_id='{inst}')") # start from the real situation: the KB has a CONFLICT for the Coca-Cola can (RINCAO x UNIAO), nothing verified state = ke.exec_hub(f''' from sqlalchemy import select from central.app.db import SessionLocal from central.app.enrichment import kb from central.app.models import KbKnowledge, KbProduct with SessionLocal() as s: p = s.scalar(select(KbProduct).where(KbProduct.gtin == "0{GTIN}")) rows = s.scalars(select(KbKnowledge).where(KbKnowledge.kb_product_id == p.id, KbKnowledge.knowledge_key == "ncm")).all() for r in rows: # reset any earlier rehearsal: drop attestation ev = dict(r.evidence or {{}}); fam = dict(ev.get("families") or {{}}); fam.pop("OPERATOR_ATTESTATION", None); ev["families"] = fam; r.evidence = ev; r.status = "OBSERVED"; r.validated_at = None s.commit() print("ROWS", sorted((r.value, r.status) for r in rows)) ''') info["kb_before"] = state w, stop = ke.worker_for(c1, "acc_dev") lh.set_features(inst, "DRY_RUN"); w._features_at = -999; time.sleep(6) # 1) DRY_RUN: product without EAN, placeholder NCM insert("AC001", "COCA-COLA ORIGINAL LATA 350 ML", "00000000", None, "200004") time.sleep(12) info["status_before_decision"] = ui("AC001", inst) checks["SILENT_FAILURE_FIXED_STATUS_VISIBLE"] = info["status_before_decision"].startswith("EAN_AUSENTE") checks["NCM_UNCHANGED_UNTIL_DECISION"] = row("AC001")[0] == "00000000" # 2) ADMIN decision in the KB (same code path as the panel route) -> flagged re-evaluation, still DRY_RUN out = ke.exec_hub(f''' from central.app.db import SessionLocal from central.app.enrichment import history, kb, status from central.app.models import Installation from sqlalchemy import select with SessionLocal() as s: cur = history.official_sets(s) r = kb.attest(s, "0{GTIN}", "22021000", "E2E-OPERATOR", lambda v: v in cur, note="E2E rehearsal") n = 0 for i in s.scalars(select(Installation).where(Installation.enrichment_mode != "DISABLED", Installation.features_configured.is_(True))): n += status.mark_for_reevaluation(s, i.id) s.commit() print("ATTEST", r["status"], r["families"], "flagged", n) ''') info["attest"] = out checks["ATTESTED_TO_VERIFIED_KB"] = "VERIFIED_KB" in out t0 = time.monotonic() while time.monotonic() - t0 < 40 and not ui("AC001", inst).startswith("SIMULADO"): time.sleep(1) info["status_dry_run_after_decision"] = ui("AC001", inst) checks["DRY_RUN_SHOWS_CANDIDATE_ZERO_WRITES"] = ui("AC001", inst).startswith("SIMULADO") and row("AC001")[0] == "00000000" # 3) AUTOMATIC: reevaluate (like the panel button) -> written by the Agent lh.set_features(inst, "ENABLED"); w._features_at = -999; w._auth_cache = (None, 0.0); time.sleep(3) lh.sql(f"update product_enrichments set reevaluate=true where product_code='AC001' and installation_id=(select id from installations where installation_id='{inst}')") t0 = time.monotonic() while time.monotonic() - t0 < 45 and row("AC001")[0] == "00000000": time.sleep(0.5) time.sleep(4) r = row("AC001") info["AC001_after"] = r checks["NAME_ONLY_NCM_WRITTEN_BY_AGENT"] = r[0] == "22021000" checks["CCLASSTRIB_PRESERVED"] = r[3] == "200004" audit = lh.sql(f"select field||':'||coalesce(old_value,'')||'>'||coalesce(new_value,'')||':'||outcome||':'||coalesce(kb_status,'') from product_field_audits where product_code='AC001' and outcome='APPLIED' order by id") info["audit"] = audit checks["AUDIT_OLD_PLACEHOLDER_NEW_NCM"] = "CLASSIFICACAO_FISCAL:00000000>22021000:APPLIED" in audit checks["FINAL_STATUS_ENRIQUECIDO"] = ui("AC001", inst).startswith("ENRIQUECIDO") # 4) second run writes 0, zero provider requests before_req, before_writes = ke.requests_total(), int(lh.sql("select count(*) from product_field_audits where outcome='APPLIED'")) lh.sql(f"update product_enrichments set reevaluate=true where product_code='AC001' and installation_id=(select id from installations where installation_id='{inst}')") time.sleep(25) checks["SECOND_RUN_WRITES_0"] = int(lh.sql("select count(*) from product_field_audits where outcome='APPLIED'")) == before_writes and row("AC001")[0] == "22021000" checks["ZERO_PROVIDER_REQUESTS_ON_KB_RESOLUTION"] = ke.requests_total() == before_req # 5) EAN path on the now-verified GTIN: latency + zero provider requests t0 = time.monotonic() insert("AC002", "COCA-COLA ORIGINAL LATA 350 ML", "00000000", GTIN, "200004") while time.monotonic() - t0 < 25 and row("AC002")[0] == "00000000": time.sleep(0.2) info["ean_latency_s"] = round(time.monotonic() - t0, 2) checks["EAN_PATH_KB_HIT_WRITE"] = row("AC002")[0] == "22021000" and ke.requests_total() == before_req # 6) real NCM preserved + human override preserved insert("AC003", "COCA-COLA ORIGINAL LATA 350 ML", "19059090", GTIN, "200004"); time.sleep(10) checks["EXISTING_REAL_NCM_PRESERVED"] = row("AC003")[0] == "19059090" conn = base.connect(True); cur = conn.cursor(); cur.execute("UPDATE C000025 SET CLASSIFICACAO_FISCAL='19059090' WHERE CODIGO='AC001'"); conn.commit(); conn.close() time.sleep(E_OVERRIDE_WAIT := 28) lh.sql(f"update product_enrichments set reevaluate=true where product_code='AC001' and installation_id=(select id from installations where installation_id='{inst}')") time.sleep(20) checks["HUMAN_OVERRIDE_PRESERVED"] = row("AC001")[0] == "19059090" stop.stop = True result = {"dev_rehearsal": True, "info": info, "checks": checks, "ALL_PASS": all(checks.values())} (ROOT / "docs" / "runs" / "enrichment_acceptance_dev.json").write_text(json.dumps(result, indent=1, default=str), encoding="utf-8") print(json.dumps(result, indent=1, default=str)) sys.exit(0 if result["ALL_PASS"] else 1) if __name__ == "__main__": main()