diff --git a/docs/runs/BLOCOB_PIS_COFINS_SOURCE_V1.json b/docs/runs/BLOCOB_PIS_COFINS_SOURCE_V1.json new file mode 100644 index 0000000..543f911 --- /dev/null +++ b/docs/runs/BLOCOB_PIS_COFINS_SOURCE_V1.json @@ -0,0 +1,63 @@ +{ + "GTI": { + "name": "GTI", + "items_total": 34946, + "items_cst_filled": 3336, + "determinism_NCM": { + "distinct_keys": 10, + "items_covered": 3336, + "items_with_single_cst_for_key": 3336, + "reproduction_rate_pct": 100.0 + }, + "determinism_CODGRUPO": { + "distinct_keys": 13, + "items_covered": 3336, + "items_with_single_cst_for_key": 3336, + "reproduction_rate_pct": 100.0 + }, + "determinism_CFOP": { + "distinct_keys": 2, + "items_covered": 3336, + "items_with_single_cst_for_key": 3336, + "reproduction_rate_pct": 100.0 + }, + "determinism_CSOSN": { + "distinct_keys": 2, + "items_covered": 3336, + "items_with_single_cst_for_key": 3336, + "reproduction_rate_pct": 100.0 + }, + "determinism_GRUPO_ICMS": { + "distinct_keys": 1, + "items_covered": 3336, + "items_with_single_cst_for_key": 3336, + "reproduction_rate_pct": 100.0 + }, + "aliquota_filled_but_cst_blank": 1332, + "cst_filled_but_aliquota_null": 0 + }, + "CONSTRUCAO": { + "name": "CONSTRUCAO", + "items_total": 28934, + "items_cst_filled": 0, + "conclusion": "NAO_APLICAVEL_SEM_DADOS_PARA_CORRELACIONAR" + }, + "MMMODAS": { + "name": "MMMODAS", + "items_total": 26067, + "items_cst_filled": 0, + "conclusion": "NAO_APLICAVEL_SEM_DADOS_PARA_CORRELACIONAR" + }, + "RINCAO": { + "name": "RINCAO", + "items_total": 224355, + "items_cst_filled": 0, + "conclusion": "NAO_APLICAVEL_SEM_DADOS_PARA_CORRELACIONAR" + }, + "UNIAO": { + "name": "UNIAO", + "items_total": 121266, + "items_cst_filled": 0, + "conclusion": "NAO_APLICAVEL_SEM_DADOS_PARA_CORRELACIONAR" + } +} \ No newline at end of file diff --git a/scripts/blocoB_pis_cofins_source.py b/scripts/blocoB_pis_cofins_source.py new file mode 100644 index 0000000..429990f --- /dev/null +++ b/scripts/blocoB_pis_cofins_source.py @@ -0,0 +1,81 @@ +"""Missão 3 / Bloco B — de onde vem o CST/alíquota de PIS/COFINS emitido, somente leitura. +Testa correlação e determinismo por NCM, CODGRUPO (via C000025), CFOP, CSOSN/CST_ICMS, +GRUPO_ICMS. Não decide regra fiscal - só mede taxa de reprodução. +""" +import json +import os +import sys +from pathlib import Path +from collections import Counter, defaultdict + +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 fdb # noqa: E402 +import run_fiscal_decision_v1 as base # noqa: E402 + +DBS = { + "GTI": r"D:\PROJETOS\GTISISTEMAS\Banco\GTI.FDB", + "CONSTRUCAO": str(ROOT / "Banco" / "CONSTRUCAO.FDB"), + "MMMODAS": str(ROOT / "Banco" / "MMMODAS.FDB"), + "RINCAO": str(ROOT / "Banco" / "RINCAO.FDB"), + "UNIAO": str(ROOT / "Banco" / "UNIAO.FDB"), +} + + +def connect(path): + return fdb.connect(database=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 analyze(name, path): + conn = connect(path); cur = conn.cursor() + cur.execute("SELECT COUNT(*), COUNT(NULLIF(TRIM(CST_PIS_COFINS),'')) FROM NFCE_ITENS") + tot, filled = cur.fetchone() + out = {"name": name, "items_total": tot, "items_cst_filled": filled} + if filled == 0: + out["conclusion"] = "NAO_APLICAVEL_SEM_DADOS_PARA_CORRELACIONAR" + conn.close() + return out + # candidate determinants, only over the filled subset + for dim, expr in (("NCM", "i.NCM"), ("CODGRUPO", "p.CODGRUPO"), ("CFOP", "i.CFOP"), ("CSOSN", "i.CSOSN"), ("GRUPO_ICMS", "i.GRUPO_ICMS")): + try: + cur.execute(f""" + SELECT {expr}, TRIM(i.CST_PIS_COFINS), COUNT(*) + FROM NFCE_ITENS i LEFT JOIN C000025 p ON p.CODIGO = i.CODPRODUTO + WHERE COALESCE(TRIM(i.CST_PIS_COFINS),'') != '' + GROUP BY 1, 2 + """) + rows = cur.fetchall() + by_key = defaultdict(Counter) + for k, cst, n in rows: + by_key[k][cst] += n + deterministic = sum(sum(c.values()) for c in by_key.values() if len(c) == 1) + total_filled_dim = sum(sum(c.values()) for c in by_key.values()) + out[f"determinism_{dim}"] = {"distinct_keys": len(by_key), "items_covered": total_filled_dim, + "items_with_single_cst_for_key": deterministic, + "reproduction_rate_pct": round(100.0 * deterministic / total_filled_dim, 2) if total_filled_dim else None} + except Exception as e: # noqa: BLE001 + out[f"determinism_{dim}_error"] = str(e)[:150] + # aliquota_pis fill vs cst fill: same items or different? + cur.execute("SELECT COUNT(*) FROM NFCE_ITENS WHERE ALIQUOTA_PIS IS NOT NULL AND COALESCE(TRIM(CST_PIS_COFINS),'')=''") + out["aliquota_filled_but_cst_blank"] = cur.fetchone()[0] + cur.execute("SELECT COUNT(*) FROM NFCE_ITENS WHERE ALIQUOTA_PIS IS NULL AND COALESCE(TRIM(CST_PIS_COFINS),'')!=''") + out["cst_filled_but_aliquota_null"] = cur.fetchone()[0] + conn.close() + return out + + +def main(): + base.load_dotenv(ROOT / ".env") + results = {} + for name, path in DBS.items(): + print(f"== {name}", flush=True) + results[name] = analyze(name, path) + print(json.dumps(results[name], ensure_ascii=False, indent=1, default=str), flush=True) + out_path = ROOT / "docs" / "runs" / "BLOCOB_PIS_COFINS_SOURCE_V1.json" + out_path.write_text(json.dumps(results, ensure_ascii=False, indent=1, default=str), encoding="utf-8") + print(f"-> {out_path}") + + +if __name__ == "__main__": + main()