Add 'fiscal/' from commit '102ba84500dbd9655a0471288c3e278652593ca9'

git-subtree-dir: fiscal
git-subtree-mainline: d95165fc8f
git-subtree-split: 102ba84500
This commit is contained in:
gti
2026-09-29 14:43:23 -03:00
868 changed files with 566394 additions and 0 deletions
+115
View File
@@ -0,0 +1,115 @@
"""Fiscal Engine V1 coverage over every database in ./Banco — READ-ONLY (no Firebird writes, no new rules).
python scripts/run_multi_db_coverage.py <AGENT_TOKEN> [DIR]
Reuses the existing pure engines: fiscal_decision (NCM/IPI/EX/CEST/MVA), product_defaults (CFOP), icms_context (ST, role,
withholding, CSOSN V2). Only GUARDS are added for company context: the BA Anexo 1 dataset is applied to UF=BA companies only, and
CSOSN logic only under CRT=1; otherwise the engines are told the context is unavailable (no fiscal rule invented).
"""
import json
import os
import sys
from collections import Counter, defaultdict
from concurrent.futures import ThreadPoolExecutor
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
import fdb # noqa: E402
from central.app.fiscal_decision import decide_product # noqa: E402
from central.app.icms_context import csosn_v2, product_role, st_status, withheld_status # noqa: E402
from central.app.product_defaults import cfop_default # noqa: E402
from gti_fiscal_agent.firebird_reader import parse_company_context # noqa: E402
HUB = "http://localhost:8000"
s = lambda v: None if v is None else str(v).strip() # noqa: E731
dg = lambda v: "".join(c for c in str(v or "") if c.isdigit()) # noqa: E731
def connect(path):
return fdb.connect(database=str(path), user=os.environ["FB_USER"], password=os.environ["FB_PASSWORD"], fb_library_name=os.environ.get("FIREBIRD_CLIENT_LIBRARY"),
isolation_level=fdb.ISOLATION_LEVEL_READ_COMMITED_RO)
def table_cols(cur):
cur.execute("SELECT TRIM(RDB$RELATION_NAME), TRIM(RDB$FIELD_NAME) FROM RDB$RELATION_FIELDS")
m = defaultdict(set)
for t, f in cur.fetchall():
m[t].add(f)
return m
def classify_all(token, products):
"""Classify every distinct (ncm, description) with the production Classifier inside the hub container (read-only on PostgreSQL,
memoized dataset lookups; identical code path to /v1/classify, verified against the HTTP API in tests/verification)."""
import subprocess
import tempfile
pairs = sorted({(s(p["CLASSIFICACAO_FISCAL"]) or "", s(p["PRODUTO"]) or "") for p in products})
with tempfile.TemporaryDirectory() as tmp:
fin, fout = Path(tmp) / "in.json", Path(tmp) / "out.json"
fin.write_text(json.dumps(pairs, ensure_ascii=False), encoding="utf-8")
subprocess.run(["docker", "cp", str(ROOT / "scripts" / "bulk_classify_in_container.py"), "projeto-fiscal-hub-1:/tmp/bulk.py"], check=True)
subprocess.run(["docker", "cp", str(fin), "projeto-fiscal-hub-1:/tmp/in.json"], check=True)
subprocess.run(["docker", "exec", "projeto-fiscal-hub-1", "sh", "-c", "cd /app && python /tmp/bulk.py /tmp/in.json /tmp/out.json"], check=True, capture_output=True)
subprocess.run(["docker", "cp", "projeto-fiscal-hub-1:/tmp/out.json", str(fout)], check=True)
results = json.loads(fout.read_text(encoding="utf-8"))
print(f" classify: {len(pairs)} distinct (ncm, description) pairs for {len(products)} products", flush=True)
return dict(zip(pairs, results))
def analyze(path, token):
conn = connect(path); cur = conn.cursor()
tc = table_cols(cur)
have = lambda t, *cols: t in tc and all(c in tc[t] for c in cols) # noqa: E731
c25 = tc["C000025"]
cols = [c for c in ("CODIGO", "PRODUTO", "CLASSIFICACAO_FISCAL", "CFOP", "CSOSN", "CST", "CEST", "IPI", "EX_TIPI", "TIPO", "PROD_COZINHA", "PRODUTO_SERVICO", "SITUACAO_TRIBUTARIA") if c in c25]
cur.execute(f"SELECT {','.join(cols)} FROM C000025")
products = [dict(zip(cols, r)) for r in cur.fetchall()]
for p in products:
for c in ("CFOP", "CSOSN", "CST", "CEST", "IPI", "EX_TIPI", "TIPO", "PROD_COZINHA", "PRODUTO_SERVICO", "SITUACAO_TRIBUTARIA", "PRODUTO", "CLASSIFICACAO_FISCAL"):
p.setdefault(c, None)
# company (fail closed; guards below)
try:
cur.execute("SELECT UF, CRT FROM C000004"); company = {"status": "OK", **parse_company_context(cur.fetchall())}
except Exception as e: # noqa: BLE001
company = {"status": "UNAVAILABLE", "error": str(e)[:120], "uf": None, "crt": None}
company_default = None
if have("C000004", "ICMS_CSOSN"):
cur.execute("SELECT FIRST 1 ICMS_CSOSN FROM C000004 ORDER BY CODIGO"); company_default = s(cur.fetchone()[0])
# context evidence (only where the tables exist in this database)
recipes = set(); composed = set(); purchases = defaultdict(list); stock_entry = {}
if have("RECEITA_INGREDIENTES", "CODRECEITA"):
cur.execute("SELECT CODRECEITA FROM RECEITA_INGREDIENTES WHERE CODRECEITA IS NOT NULL"); recipes = {str(r[0]).zfill(6) for r in cur.fetchall()}
if have("C000032", "CODPRODUTO", "TIPO"):
cur.execute("SELECT DISTINCT CODPRODUTO FROM C000032 WHERE TIPO = 'COMPOSICAO'"); composed = {s(r[0]) for r in cur.fetchall()}
if have("C000088", "CODPRODUTO", "ICMS_RETIDO", "ICMS_VALORRETIDO", "SUB_VALOR"):
cur.execute("SELECT CODPRODUTO, ICMS_RETIDO, ICMS_VALORRETIDO, SUB_VALOR FROM C000088")
for cod, ret, vr, sv in cur.fetchall():
purchases[s(cod)].append({"icms_retido": s(ret), "valor_retido": vr, "sub_valor": sv})
if have("C000100", "CODPRODUTO", "ULTIMA_ENTRADA", "NOTA_FISCAL"):
cur.execute("SELECT CODPRODUTO, ULTIMA_ENTRADA, NOTA_FISCAL FROM C000100"); stock_entry = {s(r[0]): (r[1] is not None or s(r[2]) not in (None, "", "000000")) for r in cur.fetchall()}
conn.close()
refs = base.build_refs(HUB, token, products, company["uf"] or "BA")
decisions = classify_all(token, products)
from gti_fiscal_agent.coverage import compute_coverage
evidence = {"recipes": recipes, "composed": composed, "purchases": dict(purchases), "stock_entry": stock_entry}
report = compute_coverage(products, [decisions[(s(p["CLASSIFICACAO_FISCAL"]) or "", s(p["PRODUTO"]) or "")] for p in products], company, company_default, refs, evidence)
return {"file": path.name, "company": {k: company.get(k) for k in ("status", "uf", "crt", "error")}, **report}
def main():
base.load_dotenv(ROOT / ".env")
token = sys.argv[1]
directory = Path(sys.argv[2]) if len(sys.argv) > 2 else ROOT / "Banco"
out = []
for p in sorted(directory.glob("*.FDB")):
print("analyzing", p.name, flush=True)
out.append(analyze(p, token))
(ROOT / "docs" / "runs" / "multi_db" / f"{p.stem}_coverage.json").write_text(json.dumps(out[-1], ensure_ascii=False, indent=1, default=str), encoding="utf-8")
print(json.dumps({k: out[-1][k] for k in ("file", "TOTAL_PRODUCTS", "company")}, ensure_ascii=False), flush=True)
(ROOT / "docs" / "MULTI_DATABASE_FISCAL_COVERAGE_V1.json").write_text(json.dumps({"artifact": "MULTI_DATABASE_FISCAL_COVERAGE_V1", "firebird_writes": 0, "databases": out}, ensure_ascii=False, indent=1, default=str), encoding="utf-8")
main()