"""Product Fiscal Defaults V1 runner: CFOP/CSOSN/CST over all C000025 products (read-only). Writes docs/PRODUCT_FISCAL_DEFAULTS_MATRIX_V1.json, ..._DRY_RUN_V1.json, FISCAL_CONTEXT_REQUIREMENTS_V2.json, NEXT_FISCAL_FIELD_IMPLEMENTATION_V2.md. No Firebird writes: the engine can only emit write_eligible=False (see central/app/product_defaults.py); this runner refuses to write anything. """ import json import os import sys from collections import Counter, defaultdict 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")) import run_fiscal_decision_v1 as base # noqa: E402 from central.app.product_defaults import PRODUCT_DEFAULTS_ENGINE_VERSION, cfop_default, csosn_default, cst_status, validate_against_history # noqa: E402 from gti_fiscal_agent.firebird_reader import _connect # noqa: E402 def main(): token = sys.argv[1] hub = "http://localhost:8000" base.load_dotenv(ROOT / ".env") conn = _connect(os.environ); cur = conn.cursor() cur.execute("select CODIGO, PRODUTO, CLASSIFICACAO_FISCAL, CFOP, CSOSN, CST from C000025 order by CODIGO") products = [dict(zip(("CODIGO", "PRODUTO", "CLASSIFICACAO_FISCAL", "CFOP", "CSOSN", "CST"), r)) for r in cur.fetchall()] cur.execute("select ICMS_CSOSN, CRT, UF from C000004"); company_default, crt, uf = [str(x).strip() for x in cur.fetchone()] conn.close() refs = base.build_refs(hub, token, products, uf) decisions = [] for i in range(0, len(products), 100): chunk = products[i:i + 100] decisions += base.http(f"{hub}/v1/classify/batch", token, {"products": [{"current_ncm": p["CLASSIFICACAO_FISCAL"], "description": p["PRODUTO"] or ""} for p in chunk]})["results"] trusted = json.loads((ROOT / "docs" / "runs" / "trusted_historical_items.json").read_text(encoding="utf-8")) hist = defaultdict(lambda: {"cfop": [], "csosn": []}) for t in trusted: h = hist[str(t["cod"]).strip()] if (t.get("cfop") or "").strip(): h["cfop"].append(t["cfop"].strip()) if (t.get("csosn") is not None) and str(t["csosn"]).strip(): h["csosn"].append(str(t["csosn"]).strip()) rows = [] for p, d in zip(products, decisions): code = str(p["CODIGO"]).strip() ncm_status = d.get("status"); mva = d.get("ba_st_mva_status") cf = cfop_default(p, ncm_status, mva, refs["cfop_valid"]) cs = csosn_default(p, ncm_status, mva, refs["csosn"], company_default) ct = cst_status(p, refs["cst_icms"], refs["origem"]) h = hist.get(code, {"cfop": [], "csosn": []}) cf["historical_validation"] = validate_against_history(cf["proposed_value"], h["cfop"]) cs["historical_validation"] = validate_against_history(cs["proposed_value"], h["csosn"]) cf_cur_share = (sum(1 for v in h["cfop"] if v == cf["current_value"]) / len(h["cfop"])) if h["cfop"] else None cs_cur_share = (sum(1 for v in h["csosn"] if v == cs["current_value"]) / len(h["csosn"])) if h["csosn"] else None rows.append({ "CODIGO": code, "DESCRIPTION": p["PRODUTO"], "NCM": p["CLASSIFICACAO_FISCAL"], "CFOP_CURRENT": cf["current_value"], "CFOP_PROPOSED": cf["proposed_value"], "CFOP_STATUS": cf["status"], "CFOP_REASON": cf["reason_code"], "CFOP_WRITE_ELIGIBLE": cf["write_eligible"], "CSOSN_CURRENT": cs["current_value"], "CSOSN_PROPOSED": cs["proposed_value"], "CSOSN_STATUS": cs["status"], "CSOSN_REASON": cs["reason_code"], "CSOSN_WRITE_ELIGIBLE": cs["write_eligible"], "CST_CURRENT": ct["current_value"], "CST_STATUS": ct["status"], "HISTORICAL_OCCURRENCES": {"trusted_cfop_items": len(h["cfop"]), "trusted_csosn_items": len(h["csosn"])}, "HISTORICAL_CONSISTENCY": {"cfop_current_equals_emitted_share": cf_cur_share, "csosn_current_equals_emitted_share": cs_cur_share, "cfop_validation": cf["historical_validation"], "csosn_validation": cs["historical_validation"]}, "PROVENANCE": {"engine": PRODUCT_DEFAULTS_ENGINE_VERSION, "cfop_dataset": d.get("dataset_versions", {}).get("cfop"), "moc": d.get("dataset_versions", {}).get("nfe_moc_tables"), "ba_st_mva": d.get("dataset_versions", {}).get("ba_st_mva"), "company": {"crt": crt, "uf": uf, "icms_csosn": company_default}}, "_cfop": cf, "_csosn": cs, }) def count(field, key): c = Counter(r[key] for r in rows) return c cf_states = Counter(r["CFOP_STATUS"] for r in rows); cs_states = Counter(r["CSOSN_STATUS"] for r in rows) cf_act = Counter(r["_cfop"]["action_class"] for r in rows); cs_act = Counter(r["_csosn"]["action_class"] for r in rows) cfv = Counter(r["HISTORICAL_CONSISTENCY"]["cfop_validation"] for r in rows if r["CFOP_PROPOSED"] is not None) csv_ = Counter(r["HISTORICAL_CONSISTENCY"]["csosn_validation"] for r in rows if r["CSOSN_PROPOSED"] is not None) cf_conflicts = [r for r in rows if r["CFOP_STATUS"] == "CFOP_DEFAULT_CANDIDATE"] conflict_hist = Counter() for r in cf_conflicts: share = r["HISTORICAL_CONSISTENCY"]["cfop_current_equals_emitted_share"] conflict_hist["no_trusted_history" if share is None else ("history_emitted_current_value" if share == 1.0 else "history_differs_from_current")] += 1 summary = { "TOTAL_PRODUCTS": len(rows), "company": {"crt": crt, "uf": uf, "icms_csosn": company_default}, "CFOP": {"RESOLVED": cf_states["CFOP_DEFAULT_RESOLVED"], "ALREADY_CORRECT": cf_act["ALREADY_CORRECT"], "CHANGE_REQUIRED": 0, "REVIEW": cf_act["REVIEW_REQUIRED"], "CONTEXT_REQUIRED": cf_states["CFOP_OPERATION_CONTEXT_REQUIRED"], "UNRESOLVED": cf_states["CFOP_UNRESOLVED"], "CANDIDATE": cf_states["CFOP_DEFAULT_CANDIDATE"], "AUTO_APPLY_SAFE": 0, "states": dict(cf_states), "historical_validation_of_proposals": dict(cfv), "conflict_candidates_vs_history": dict(conflict_hist)}, "CSOSN": {"RESOLVED": cs_states["CSOSN_DEFAULT_RESOLVED"], "ALREADY_CORRECT": 0, "CHANGE_REQUIRED": 0, "REVIEW": cs_act["REVIEW_REQUIRED"], "CONTEXT_REQUIRED": cs_states["CSOSN_OPERATION_CONTEXT_REQUIRED"], "UNRESOLVED": cs_states["CSOSN_UNRESOLVED"], "CANDIDATE": cs_states["CSOSN_DEFAULT_CANDIDATE"], "AUTO_APPLY_SAFE": 0, "states": dict(cs_states), "historical_validation_of_proposals": dict(csv_), "st_conflicts": dict(Counter(r["_csosn"].get("consistency_flag") for r in rows if r["_csosn"].get("consistency_flag")))}, "CST": dict(Counter(r["CST_STATUS"] for r in rows)), } for r in rows: r.pop("_cfop"); r.pop("_csosn") dm = {"artifact": "PRODUCT_FISCAL_DEFAULTS_MATRIX_V1", "engine": PRODUCT_DEFAULTS_ENGINE_VERSION, "products": rows} (ROOT / "docs" / "PRODUCT_FISCAL_DEFAULTS_MATRIX_V1.json").write_text(json.dumps(dm, ensure_ascii=False, indent=1, default=str), encoding="utf-8") dry = {"artifact": "PRODUCT_FISCAL_DEFAULTS_DRY_RUN_V1", "firebird_writes": 0, "write_candidates": [], **summary, "note": "No proposal is write-eligible: CFOP conflicts need ST-retention/resale context; CSOSN needs product ICMS-treatment and retention context."} (ROOT / "docs" / "PRODUCT_FISCAL_DEFAULTS_DRY_RUN_V1.json").write_text(json.dumps(dry, ensure_ascii=False, indent=1), encoding="utf-8") # context requirements V2 (exact inputs still blocking) st_unknown = sum(1 for r in rows if r["CFOP_REASON"] == "ST_APPLICABILITY_UNCONFIRMED_NEEDS_CEST_CONFIRMATION") st_subject = sum(1 for r in rows if r["CSOSN_REASON"] == "ST_RETENTION_BY_SUPPLIER_UNKNOWN") ncm_bad = sum(1 for r in rows if r["CFOP_REASON"] == "NCM_NOT_VALID_CANNOT_ASSESS_ST") csosn_notst = sum(1 for r in rows if r["CSOSN_STATUS"] == "CSOSN_DEFAULT_CANDIDATE") blank_cfop = sum(1 for r in rows if r["CFOP_REASON"] == "NO_DECLARED_RESALE_OR_PRODUCTION_FAMILY") other_family = sum(1 for r in rows if r["CFOP_REASON"] in ("NO_RULE_FOR_DECLARED_CFOP_FAMILY", "DIRECTION_OR_UF_SCOPE_NOT_INTRA_UF_SAIDA")) conflicts = len(cf_conflicts) req = [ {"INPUT": "valid_ncm", "SOURCE_EXPECTED": "product master (C000025.CLASSIFICACAO_FISCAL)", "AVAILABLE": "PARTIAL (495/521 valid)", "AFFECTED_PRODUCTS": ncm_bad, "AFFECTED_FIELD": "CFOP,CSOSN", "NEXT_ACTION": "Fix/review invalid+legacy NCMs (never auto-change)."}, {"INPUT": "st_applicability_confirmed (CEST confirmation)", "SOURCE_EXPECTED": "CEST dataset + BA Anexo 1 item", "AVAILABLE": "PARTIAL (31 resolved, 237 candidates)", "AFFECTED_PRODUCTS": st_unknown, "AFFECTED_FIELD": "CFOP,CSOSN", "NEXT_ACTION": "Confirm CEST per product (description evidence/manual review) to settle ST applicability."}, {"INPUT": "st_retention_by_supplier", "SOURCE_EXPECTED": "purchase document per product (entrada) or supplier flag", "AVAILABLE": False, "AFFECTED_PRODUCTS": st_subject, "AFFECTED_FIELD": "CSOSN(500 vs 102),CFOP(5405 vs 5102)", "NEXT_ACTION": "Locate structured retention evidence in purchase items (C000032/C000062 CST 060/CSOSN 500 history) or ask vendor."}, {"INPUT": "product_icms_treatment (taxed / exempt / not taxed)", "SOURCE_EXPECTED": "state ICMS rules per NCM (no acquired dataset)", "AVAILABLE": False, "AFFECTED_PRODUCTS": csosn_notst, "AFFECTED_FIELD": "CSOSN(102 vs 400/300/103)", "NEXT_ACTION": "Needs an official per-NCM ICMS treatment source; ERP CST=040 with CSOSN=102 coexists, so ERP alone is ambiguous."}, {"INPUT": "declared_resale_vs_production", "SOURCE_EXPECTED": "product nature/type (C000025.TIPO_ITEM unused) or company activity", "AVAILABLE": False, "AFFECTED_PRODUCTS": blank_cfop + other_family, "AFFECTED_FIELD": "CFOP", "NEXT_ACTION": "Confirm meaning of FOOD_ATRIBUICAO_VALORES/TIPO_ITEM with vendor before deriving 5101 vs 5102."}, {"INPUT": "st_conflict_resolution (which of ERP CFOP or BA Anexo 1 is wrong)", "SOURCE_EXPECTED": "human/fiscal review of conflicting products", "AVAILABLE": False, "AFFECTED_PRODUCTS": conflicts, "AFFECTED_FIELD": "CFOP", "NEXT_ACTION": "Review queue; do not auto-apply."}, {"INPUT": "destination_uf / consumer_final / direction", "SOURCE_EXPECTED": "NFC-e document model 65 + chave cUF (proven 1499/1499) and C000004.UF", "AVAILABLE": True, "AFFECTED_PRODUCTS": 0, "AFFECTED_FIELD": "CFOP", "NEXT_ACTION": "Already satisfied for NFC-e scope; NF-e needs customer data."}, ] (ROOT / "docs" / "FISCAL_CONTEXT_REQUIREMENTS_V2.json").write_text(json.dumps({"artifact": "FISCAL_CONTEXT_REQUIREMENTS_V2", "requirements": req}, ensure_ascii=False, indent=1), encoding="utf-8") # priorities V2 (mechanical) prio = [ ("CFOP", "CONFIRMED (HIGH)", "PARTIAL", "YES (ST family)", summary["CFOP"]["RESOLVED"] + conflicts, "trusted history available", "AUTO_APPLY_SAFE=0; review the ST conflicts."), ("CSOSN", "CONFIRMED (HIGH)", "NO", "NO (treatment/retention sources missing)", summary["CSOSN"]["CANDIDATE"] + summary["CSOSN"]["CONTEXT_REQUIRED"], "trusted history available", "Acquire retention/treatment evidence."), ("CST", "CONFIRMED (MEDIUM)", "YES", "n/a (reference only, CRT=1)", 521, "trusted history 100%", "No write needed."), ("CEST", "CONFIRMED (HIGH)", "YES", "PARTIAL", 15, "weak (1/15 supported)", "Description-evidence review of 15 candidates."), ("IPI", "CONFIRMED (HIGH)", "YES", "YES", 107, "n/a", "Maintained; already reconciled."), ] md = ["# NEXT_FISCAL_FIELD_IMPLEMENTATION_V2", "", "Mechanical ordering: mapping confirmed, rule available, context available, products affected, history validation, auto-apply possible (post PRODUCT DEFAULTS V1).", "", "| FIELD | ERP_MAPPING | CONTEXT_AVAILABLE | RULE_AVAILABLE | POTENTIAL_PRODUCTS | HISTORICAL_VALIDATION | NEXT_ACTION |", "|---|---|---|---|---|---|---|"] md += [f"| {a} | {b} | {c} | {d_} | {e} | {f} | {g} |" for a, b, c, d_, e, f, g in prio] md += ["", "Blocking inputs and affected counts: `docs/FISCAL_CONTEXT_REQUIREMENTS_V2.json`."] (ROOT / "docs" / "NEXT_FISCAL_FIELD_IMPLEMENTATION_V2.md").write_text("\n".join(md) + "\n", encoding="utf-8") print(json.dumps(summary, ensure_ascii=True, indent=1)) main()