Files
GTI_GERENCIADOR_FISCAL/scripts/rtc_copy_write_e2e.py
T

141 lines
9.0 KiB
Python

"""COPY-ONLY write E2E for the RTC auto-apply (C000025.CCLASSTRIB).
python scripts/rtc_copy_write_e2e.py
Runs ONLY against scratch_rtc_e2e/TESTESOFICIAIS_RTC_COPY.FDB (a gbak restore of TESTESOFICIAIS). It refuses any other path, and refuses every real database.
Scenario (all through the Agent's own code path `run_agent_service._rtc_auto_apply`):
1. the copy's company is switched to regime normal (C000004.CRT='3'): a disposable experiment, so the rules can act (the real company is Simples => 0 candidates);
2. assess -> candidates; 3. apply with all three gates open; 4. re-read, FK/CST checks, coverage; 5. ONLY CCLASSTRIB changed (whole-table diff);
6. second run: 0 candidates, 0 writes; 7. stale-decision abort (RTC switched off on the copy between decision and write) writes nothing.
Writes go to the copy and nowhere else.
"""
import importlib.util
import json
import os
import sys
from datetime import date
from pathlib import Path
from types import SimpleNamespace
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
import fdb # noqa: E402
import local_completion as lc # noqa: E402
from central.app.operational_state import classify_product_state, summarize_operational_states # noqa: E402
from gti_fiscal_agent import firebird_reader, unified_fiscal # noqa: E402
COPY = str((ROOT / "scratch_rtc_e2e" / "TESTESOFICIAIS_RTC_COPY.FDB").resolve())
NCM_OK = set((ROOT / "docs" / "runs" / "ncm_valid_codes.txt").read_text().split())
def guard(path):
canon = os.path.normcase(os.path.abspath(path))
if canon != os.path.normcase(COPY):
raise SystemExit(f"REFUSED: {path} is not the disposable copy")
real = {os.path.normcase(os.path.abspath(p)) for p in lc.allowlist().values()}
if canon in real or "scratch_rtc_e2e" not in canon:
raise SystemExit("REFUSED: real database")
def connect(rw):
kw = dict(database=COPY, user=os.environ["FB_USER"], password=os.environ["FB_PASSWORD"], fb_library_name=os.environ.get("FIREBIRD_CLIENT_LIBRARY"))
if not rw:
kw["isolation_level"] = fdb.ISOLATION_LEVEL_READ_COMMITED_RO
return fdb.connect(**kw)
def load_service():
spec = importlib.util.spec_from_file_location("run_agent_service", ROOT / "agent" / "scripts" / "run_agent_service.py")
mod = importlib.util.module_from_spec(spec)
sys.modules[spec.name] = mod
spec.loader.exec_module(mod)
return mod
def digits(v):
return "".join(ch for ch in str(v or "") if ch.isdigit())
def snapshot_table(cur, table, key, skip_cols=()):
cur.execute(f"SELECT * FROM {table}")
cols = [d[0] for d in cur.description]
ki = cols.index(key)
return cols, {r[ki]: {c: v for c, v in zip(cols, r) if c not in skip_cols} for r in cur.fetchall()}
def coverage(env, company, ncm_valid, override=None):
conn = firebird_reader._connect(env)
try:
out = unified_fiscal.assess_from_connection(conn, company, ncm_valid=ncm_valid, rtc_override=override)
finally:
conn.close()
products = firebird_reader.read_products(env)
states = [classify_product_state(p, {"status": "EXACT_NCM"}, fiscal=out["findings"].get(str(p["CODIGO"]))) for p in products]
s = summarize_operational_states(states)
return out, {"total": s["TOTAL_PRODUCTS"], "complete": s["COMPLETE_PRODUCTS"], "blocked": s["SAFE_BLOCKED"], "unclassified": s["UNCLASSIFIED"], "coverage_percent": round(100 * s["COMPLETE_PRODUCTS"] / s["TOTAL_PRODUCTS"], 2)}
def main():
load_dotenv(ROOT / ".env") # credentials only when the script runs, never as an import side effect
guard(COPY)
service = load_service()
env = {"FIREBIRD_DATABASE_PATH": COPY, "FB_USER": os.environ["FB_USER"], "FB_PASSWORD": os.environ["FB_PASSWORD"], "FIREBIRD_CLIENT_LIBRARY": os.environ.get("FIREBIRD_CLIENT_LIBRARY")}
report = {"database": "scratch_rtc_e2e/TESTESOFICIAIS_RTC_COPY.FDB (gbak restore of TESTESOFICIAIS)", "real_database_writes": 0, "real_database_write_attempts": 0}
# ---- 1. company -> regime normal on the COPY (documented experiment)
rw = connect(True); cur = rw.cursor()
cur.execute("SELECT DISTINCT TRIM(CRT) FROM C000004"); report["copy_original_crt"] = [r[0] for r in cur.fetchall()]
cur.execute("UPDATE C000004 SET CRT = '3'"); rw.commit()
cur.execute("SELECT DISTINCT TRIM(CRT) FROM C000004"); company = {"crt": cur.fetchone()[0]}
report["copy_crt_for_experiment"] = company["crt"]
_, before_c25 = snapshot_table(cur, "C000025", "CODIGO")
cur.execute("SELECT USAR_REFORMA_TRIBUTARIA FROM PARAMETROS"); report["copy_rtc_param"] = cur.fetchone()[0]
rw.commit(); rw.close()
ncm_valid = {str(c).strip(): digits(v["CLASSIFICACAO_FISCAL"]) in NCM_OK for c, v in before_c25.items()}
# ---- 2. assess (as found on the copy)
out, cov_before = coverage(env, company, ncm_valid)
report["coverage_before"] = cov_before
report["resolution_before"] = {k: v for k, v in out["context"]["rtc_resolution"].items() if k != "by_reason"}
products = firebird_reader.read_products(env)
config = SimpleNamespace(firebird_writes=True, rtc_auto_apply_enabled=True, rtc_auto_apply_max_per_job=500, database_path=COPY)
job = {"job_id": 424242, "authorizations": {"rtc_auto_apply": True}}
# ---- 7 (first, on purpose): stale decision - RTC switched off on the copy between decision and write => nothing is written
rw = connect(True); c = rw.cursor(); c.execute("UPDATE PARAMETROS SET USAR_REFORMA_TRIBUTARIA = 0"); rw.commit(); rw.close()
stale = service._rtc_auto_apply(env, config, job, products, out, company)
report["stale_decision_run"] = {"write_attempts": stale["write_attempts"], "write_successes": stale["write_successes"], "aborted_reasons": stale["aborted_reasons"]}
rw = connect(True); c = rw.cursor(); c.execute("UPDATE PARAMETROS SET USAR_REFORMA_TRIBUTARIA = 1"); rw.commit(); rw.close()
# ---- 3. apply
first = service._rtc_auto_apply(env, config, job, products, out, company)
report["first_run"] = {"mode": first["mode"], "ready_total": first["ready_total"], "write_attempts": first["write_attempts"], "write_successes": first["write_successes"], "aborted_reasons": first["aborted_reasons"],
"audit_sample": first["applied"][:3]}
# ---- 4/5. re-read, FK, whole-table diff
ro = connect(False); cur = ro.cursor()
cols, after_c25 = snapshot_table(cur, "C000025", "CODIGO")
changed = {code: [c for c in cols if before_c25[code][c] != row[c]] for code, row in after_c25.items() if any(before_c25[code][c] != row[c] for c in cols)}
report["rows_changed"] = len(changed)
report["columns_changed"] = sorted({c for v in changed.values() for c in v})
cur.execute("SELECT COUNT(*) FROM C000025 p WHERE p.CCLASSTRIB IS NOT NULL AND NOT EXISTS (SELECT 1 FROM RTC_CCLASSTRIB c WHERE c.CCLASSTRIB = p.CCLASSTRIB)"); report["fk_orphans"] = cur.fetchone()[0]
cur.execute("SELECT COUNT(*) FROM C000025 p JOIN RTC_CCLASSTRIB c ON c.CCLASSTRIB = p.CCLASSTRIB WHERE NOT EXISTS (SELECT 1 FROM RTC_CST s WHERE s.CSTIBSCBS = c.CSTIBSCBS)"); report["cst_orphans"] = cur.fetchone()[0]
cur.execute("SELECT CCLASSTRIB, COUNT(*) FROM C000025 WHERE CCLASSTRIB IS NOT NULL GROUP BY 1"); report["written_by_class"] = {r[0].strip(): r[1] for r in cur.fetchall()}
cur.execute("SELECT COUNT(*) FROM RTC_CCLASSTRIB"); report["catalog_rows_unchanged"] = cur.fetchone()[0]
ro.close()
out2, cov_after = coverage(env, company, ncm_valid)
report["coverage_after"] = cov_after
report["resolution_after"] = {k: v for k, v in out2["context"]["rtc_resolution"].items() if k != "by_reason"}
# ---- 6. second run
products2 = firebird_reader.read_products(env)
second = service._rtc_auto_apply(env, config, job, products2, out2, company)
report["second_run"] = {"ready_total": second["ready_total"], "write_attempts": second["write_attempts"], "write_successes": second["write_successes"]}
report["IDEMPOTENT"] = second["write_attempts"] == 0 and second["write_successes"] == 0
report["ONLY_CCLASSTRIB_CHANGED"] = report["columns_changed"] in ([], ["CCLASSTRIB"])
report["PASS"] = bool(report["IDEMPOTENT"] and report["ONLY_CCLASSTRIB_CHANGED"] and report["fk_orphans"] == 0 and report["cst_orphans"] == 0 and first["write_successes"] == first["write_attempts"] > 0
and report["stale_decision_run"]["write_successes"] == 0 and report["rows_changed"] == first["write_successes"])
(ROOT / "docs" / "runs" / "rtc_copy_write_e2e.json").write_text(json.dumps(report, ensure_ascii=False, indent=1, default=str), encoding="utf-8")
print(json.dumps({k: report[k] for k in ("copy_original_crt", "copy_rtc_param", "coverage_before", "coverage_after", "first_run", "stale_decision_run", "rows_changed", "columns_changed", "fk_orphans", "cst_orphans", "second_run", "IDEMPOTENT", "ONLY_CCLASSTRIB_CHANGED", "PASS")},
ensure_ascii=False, default=str)[:3000])
if __name__ == "__main__":
main()