Files
GTI_GERENCIADOR_FISCAL/scripts/generate_multi_db_findings.py
T

73 lines
7.5 KiB
Python

"""Compose docs/MULTI_DATABASE_FINDINGS_V1.md from the read-only audit + coverage artifacts. No database access."""
import json
from pathlib import Path
ROOT = Path(__file__).resolve().parents[1]
D = ROOT / "docs"
audit = json.loads((D / "MULTI_DATABASE_SCHEMA_AUDIT_V1.json").read_text(encoding="utf-8"))
cov = json.loads((D / "MULTI_DATABASE_FISCAL_COVERAGE_V1.json").read_text(encoding="utf-8"))
compat = json.loads((D / "MULTI_DATABASE_FIELD_COMPATIBILITY_V1.json").read_text(encoding="utf-8"))
raw = {p.stem: json.loads(p.read_text(encoding="utf-8")) for p in (D / "runs" / "multi_db").glob("*.json") if not p.stem.endswith("_coverage")}
A = {a["file"]: a for a in audit["databases"]}
C = {c["file"]: c for c in cov["databases"]}
def seg(f):
return A[f]["segment_inferred"]["segment"]
def pct(a, b):
return f"{100 * a / b:.1f}%" if b else "n/a"
lines = ["# MULTI_DATABASE_FINDINGS_V1", "",
"Read-only audit of the copies in `Banco/` (no Firebird writes, no new fiscal rules, no new datasets). Segment = inferred from table population and the company name stored in C000004, never from the file name.", "",
"## Databases", "", "| file | segment (evidence) | products | company UF/CRT (C000004) | EMISSOR.EMITENTE_CRT | schema class |", "|---|---|---|---|---|---|"]
for f, a in A.items():
co = a["company"]
crt = f"{co.get('uf')}/{co.get('crt')}" if co.get("status") == "OK" else f"UNAVAILABLE ({co.get('rows')} company rows)"
lines.append(f"| {f} | {seg(f)} | {a['product_count']} | {crt} | {co.get('emissor_crt')} | {a['schema_class']} |")
lines += ["", "Company-context findings: C000004.CRT disagrees with EMISSOR.EMITENTE_CRT in 3 of the 4 copies, OPTANTE_SIMPLES=S coexists with CRT=3 (UNIAO) and OPTANTE_SIMPLES=N with CRT=2 (MMMODAS); RINCAO's C000004 holds 3 company rows (fail-closed: context ambiguous). The Agent must not guess: it needs an explicit company selection and a decided CRT source of truth.", "",
"## Fiscal coverage (Fiscal Engine V1, read-only)", "", "| metric | " + " | ".join(C) + " |", "|---|" + "---|" * len(C)]
keys = ["TOTAL_PRODUCTS", "NCM_VALID", "NCM_LEGACY", "NCM_INVALID", "NCM_MISSING", "IPI_NUMERIC", "IPI_NON_NUMERIC", "IPI_SOURCE_BLANK", "IPI_AMBIGUOUS", "IPI_NO_MATCH", "IPI_ALREADY_CORRECT", "IPI_CHANGE_REQUIRED",
"IPI_WRITE_ELIGIBLE", "CEST_CONFIRMED", "CEST_CANDIDATES", "CFOP_RESOLVED", "CFOP_CONTEXT_REQUIRED", "CSOSN_RESOLVED", "CSOSN_CANDIDATES", "CSOSN_CONTEXT_REQUIRED", "ST_CONFIRMED", "ST_CANDIDATE", "ST_UNKNOWN",
"ST_NOT_APPLICABLE", "PRODUCT_ROLE_KNOWN", "PRODUCT_ROLE_UNKNOWN", "ST_WITHHOLDING_CONFIRMED", "ST_WITHHOLDING_NOT_CONFIRMED", "ST_WITHHOLDING_UNKNOWN"]
for k in keys:
lines.append(f"| {k} | " + " | ".join(str(C[f]["TOTAL_PRODUCTS"] if k == "TOTAL_PRODUCTS" else C[f]["metrics"].get(k, 0)) for f in C) + " |")
lines += ["", "Guards: ST/MVA dataset is BA-only and CSOSN logic needs CRT=1; when the company context does not satisfy them the engine reports context-required instead of applying a rule (`guards_applied` in the coverage artifact).", "",
"## Segment comparison", "", "| indicator | " + " | ".join(f"{f} ({seg(f)})" for f in C) + " |", "|---|" + "---|" * len(C)]
q = lambda f, k: C[f]["quality"][k] # noqa: E731
n = lambda f: C[f]["TOTAL_PRODUCTS"] # noqa: E731
rows = [("NCM valid share", lambda f: pct(C[f]["metrics"].get("NCM_VALID", 0), n(f))), ("CEST filled", lambda f: pct(q(f, "cest_filled"), n(f))), ("CSOSN filled", lambda f: pct(q(f, "csosn_filled"), n(f))),
("CFOP distinct values", lambda f: q(f, "cfop_distinct")), ("CFOP top", lambda f: q(f, "cfop_top")[:3]), ("SITUACAO_TRIBUTARIA", lambda f: q(f, "situacao_tributaria")),
("ST_CANDIDATE+CONFIRMED (BA dataset)", lambda f: C[f]["metrics"].get("ST_CANDIDATE", 0) + C[f]["metrics"].get("ST_CONFIRMED", 0)), ("purchase items C000088", lambda f: q(f, "purchase_items_C000088")),
("products with purchase evidence", lambda f: q(f, "products_with_purchase_evidence")), ("recipes / composed", lambda f: f"{q(f, 'recipes')} / {q(f, 'composed_products')}"),
("role known share", lambda f: pct(C[f]["metrics"].get("PRODUCT_ROLE_KNOWN", 0), n(f))), ("IPI numeric share", lambda f: pct(C[f]["metrics"].get("IPI_NUMERIC", 0), n(f))), ("IPI already stored", lambda f: pct(q(f, "ipi_filled"), n(f)))]
for name, fn in rows:
lines.append(f"| {name} | " + " | ".join(str(fn(f)) for f in C) + " |")
# mechanical attribution of gaps
def all_zero(k):
return all(C[f]["metrics"].get(k, 0) == 0 for f in C)
erp_wide = [k for k in ("CEST_CONFIRMED", "CSOSN_RESOLVED", "ST_CONFIRMED", "ST_WITHHOLDING_CONFIRMED", "IPI_WRITE_ELIGIBLE") if all_zero(k)]
lines += ["", "## Where the gaps come from", "",
f"- **ERP-wide (0 in every database):** {', '.join(erp_wide) or 'none'}. These are limits of the SGV data model / current rules, not of one client.",
"- **Client/segment:** CEST filling varies from near-empty (apparel) to mostly filled (market/restaurant/construction) — segment-shaped (goods under ST agreements), but each segment has ONE database here, so client vs segment cannot be separated.",
"- **Company context (client-specific and ERP-wide):** CRT/regime data is inconsistent inside the same ERP (C000004 vs EMISSOR vs OPTANTE_SIMPLES); it is a data-quality/config issue every client needs to settle.",
"- **No auto-repair (oficina) database was provided** (`Banco/` holds construction, apparel, restaurant and market copies); auto-repair conclusions are NOT drawn.", "",
"## GUI map validation (21 write targets + origin)", "",
f"All {compat['write_targets_count']} write-target columns exist in all databases ({len(compat['write_targets_present_in_all'])} present everywhere); the only type variation is {json.dumps(compat['variations'], ensure_ascii=False)}. "
"Value patterns repeat across databases (CST 3-digit origin+CST, CSOSN 102/500/101, SITUACAO_TRIBUTARIA T, IAT/IPPT/INCIDENCIA constants, PIS/COFINS columns almost empty), which supports the DFM-derived map without changing it.", ""]
org = compat["fields"]["COD_ORIGEM_MERCADORIA"]
lines += ["Origin (`COD_ORIGEM_MERCADORIA`): populated counts per database " + json.dumps(org["population_per_db"]) + "; observed values " + json.dumps({k: v for k, v in org["distribution_top_per_db"].items()}, ensure_ascii=False) +
". Values stay inside the 0/1/2 domain, but so few rows are populated that this is weak correlation: the field stays **PROBABLE** until a UI diff on the radio group.", "",
"## Architecture answer", "",
"**CAN_USE_ONE_GENERIC_AGENT=true.** All four databases expose C000004 and C000025 with every write-target column; differences are optional tables, one column length (CEST VARCHAR(10) vs (7)) and company-context quality. Required capabilities: "
"`supports_ncm`, `supports_ipi` (only IPI is written today), `supports_cest`, C000025+C000004 present, an unambiguous (or explicitly configured) company context, and the write executor's live column-length/type check. "
"Detected-but-optional: `supports_csosn/cfop/cst/pis/cofins/origin/iat/incidence/ippt`, and evidence tables (C000088, C000100, RECEITA_INGREDIENTES, NFCE_ITENS) which only enrich validation. "
"No per-client Agent is needed; per-client configuration is: company selector (C000004.CODIGO), CRT source of truth, and the authorized write path/fingerprint (pilot two-layer authorization).", ""]
(D / "MULTI_DATABASE_FINDINGS_V1.md").write_text("\n".join(lines) + "\n", encoding="utf-8")
print("ok", erp_wide)