git-subtree-dir: fiscal git-subtree-mainline:d95165fc8fgit-subtree-split:102ba84500
80 lines
5.2 KiB
Python
80 lines
5.2 KiB
Python
"""READ-ONLY dry-run of the deterministic cClassTrib resolver over real Firebird databases. Writes NOTHING to any database.
|
|
|
|
python scripts/rtc_resolution_dryrun.py --db TESTESOFICIAIS CONSTRUCAO ... [--out docs/runs/rtc_resolution_dryrun.json]
|
|
|
|
For each database, three passes over the same read-only snapshot:
|
|
AS_FOUND the ERP's own RTC switch and the company's real CRT (what the Agent would report today)
|
|
WHATIF_RTC_ON RTC forced ON (pure evaluator; PARAMETROS is never touched), real CRT
|
|
WHATIF_NORMAL_ON RTC forced ON and regime normal (CRT 3): what the rules WOULD resolve once an official orientation exists for Simples
|
|
NCM validity = the code exists in the active official NCM table exported from the Hub (docs/runs/ncm_valid_codes.txt).
|
|
"""
|
|
import argparse
|
|
import csv
|
|
import json
|
|
import sys
|
|
from collections import Counter
|
|
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 local_completion as lc # noqa: E402
|
|
from gti_fiscal_agent import firebird_reader, unified_fiscal # noqa: E402
|
|
from unified_coverage_e2e import resolved_crt # noqa: E402
|
|
|
|
NCM_OK = set((ROOT / "docs" / "runs" / "ncm_valid_codes.txt").read_text().split())
|
|
|
|
|
|
def digits(value):
|
|
return "".join(ch for ch in str(value or "") if ch.isdigit())
|
|
|
|
|
|
def one_pass(env, company, override, ncm_valid):
|
|
conn = firebird_reader._connect(env) # READ COMMITTED READ ONLY
|
|
try:
|
|
return unified_fiscal.assess_from_connection(conn, company, rtc_override=override, ncm_valid=ncm_valid)
|
|
finally:
|
|
conn.close()
|
|
|
|
|
|
def main():
|
|
ap = argparse.ArgumentParser(); ap.add_argument("--db", nargs="+", required=True); ap.add_argument("--out", default="docs/runs/rtc_resolution_dryrun.json"); ap.add_argument("--sample", type=int, default=100)
|
|
a = ap.parse_args()
|
|
report, sample = {}, []
|
|
for name in a.db:
|
|
path = lc.allowlist()[name]; env = lc.env_for(path)
|
|
crt = resolved_crt(name, path)
|
|
products = firebird_reader.read_products(env)
|
|
ncm_valid = {str(p["CODIGO"]): digits(p.get("CLASSIFICACAO_FISCAL")) in NCM_OK for p in products}
|
|
passes = {}
|
|
for label, override, company in (("AS_FOUND", None, {"crt": crt}), ("WHATIF_RTC_ON", True, {"crt": crt}), ("WHATIF_NORMAL_ON", True, {"crt": "3"})):
|
|
out = one_pass(env, company, override, ncm_valid)
|
|
summ = out["context"].get("rtc_resolution")
|
|
findings = out["findings"]
|
|
by_status = Counter((f.get("rtc_resolution") or {}).get("status", "NOT_EVALUATED") for f in findings.values())
|
|
conflicts = Counter(b["type"] for f in findings.values() for b in f["blockers"] if b["category"] == "RTC")
|
|
passes[label] = {"crt": company["crt"], "rtc_enabled": out["context"]["rtc_enabled"], "products": len(findings), "resolution_summary": summ, "by_status": dict(by_status), "rtc_blocker_types": dict(conflicts),
|
|
"AUTO_APPLY_CANDIDATES": sum(1 for f in findings.values() if (f.get("rtc_resolution") or {}).get("auto_apply_candidate") and not [b for b in f["blockers"] if b["type"] in ("CCLASSTRIB_CONFLICT",)])}
|
|
if label == "WHATIF_NORMAL_ON" and len(sample) < a.sample:
|
|
by_code = {str(p["CODIGO"]): p for p in products}
|
|
for code, f in findings.items():
|
|
res = f.get("rtc_resolution")
|
|
if res and len(sample) < a.sample and (res["auto_apply_candidate"] or res["status"] == "AMBIGUOUS") and (len([s for s in sample if s["database"] == name]) < max(1, a.sample // len(a.db))):
|
|
p = by_code.get(code, {})
|
|
sample.append({"database": name, "product": code, "ncm": digits(p.get("CLASSIFICACAO_FISCAL")), "current_cclasstrib": (p.get("CCLASSTRIB") or "").strip() if p.get("CCLASSTRIB") else "", "proposed_cclasstrib": res["resolved_cclasstrib"] or "",
|
|
"status": res["status"], "rule_id": res["rule_id"], "reason": res["reason"], "confidence_type": res["confidence_type"], "scenario": "WHATIF_NORMAL_ON (regime normal, RTC on)"})
|
|
passes[label]["context"] = {k: out["context"][k] for k in ("rtc_param", "rtc_source", "rtc_observed_at", "local_catalog_hash", "official_catalog", "catalog_integrity")}
|
|
report[name] = {"crt": crt, "total_products": len(products), "passes": passes}
|
|
print(name, "crt", crt, {k: (v["rtc_enabled"], v["by_status"], v["AUTO_APPLY_CANDIDATES"]) for k, v in passes.items()}, flush=True)
|
|
doc = {"mode": "READ_ONLY_DRY_RUN", "FIREBIRD_WRITES": 0, "WRITE_ATTEMPTS": 0, "WRITE_SUCCESSES": 0, "databases": report}
|
|
(ROOT / a.out).write_text(json.dumps(doc, ensure_ascii=False, indent=1, default=str), encoding="utf-8")
|
|
with open(ROOT / "docs" / "runs" / "rtc_resolution_audit_sample.csv", "w", newline="", encoding="utf-8-sig") as fh:
|
|
w = csv.DictWriter(fh, fieldnames=list(sample[0]) if sample else ["database"]); w.writeheader(); w.writerows(sample)
|
|
print("sample rows:", len(sample))
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|