feat: unified fiscal coverage - PIS/COFINS applicability + conditional RTC in one definition, one queue, one panel

- central/app/unified_fiscal_coverage.py: pure rules (PIS/COFINS NOT_APPLICABLE/REQUIRED/OPTIONAL/NEEDS_REVIEW, RTC only when the ERP switch is on)
- agent: read-only loader (unified_fiscal.py) replaces the legacy/expanded module; findings feed the single operational state, coverage and blocker queue
- hub: BLOCKED_FISCAL_DATA blockers (PIS/COFINS/RTC) share the queue; stale ones close on the next full pass; dashboard breakdown gains CEST/PIS/COFINS/RTC
- panel: legacy/expanded coverage block removed; discreet 'Reforma Tributaria: Ativa/Desativada'
- docs: unified_fiscal_completeness, pis_cofins_applicability, rtc_pis_cofins_model + official source manifests; read-only E2E script

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
gti
2026-09-24 16:39:53 -03:00
co-authored by Claude Sonnet 5
parent d897cceebb
commit b15dbea213
32 changed files with 3979 additions and 517 deletions
+37 -16
View File
@@ -30,8 +30,8 @@ sys.path.insert(0, str(AGENT_ROOT / "src"))
sys.path.insert(0, str(PROJECT_ROOT)) # ships central/app engine modules read-only (central.app.*)
from gti_fiscal_agent.client import HubClient # noqa: E402
from gti_fiscal_agent.firebird_reader import _connect as _open_read_only, check_connection, read_products # noqa: E402
from gti_fiscal_agent.rtc_pis_cofins import assess_from_connection # noqa: E402
from gti_fiscal_agent import firebird_reader as _firebird_reader, unified_fiscal as _unified # noqa: E402
from gti_fiscal_agent.firebird_reader import check_connection, read_products # noqa: E402
from gti_fiscal_agent.pipeline import run_pipeline # noqa: E402
from gti_fiscal_agent.runner import REFERENCE_SCHEMA_V1, product_fingerprint, to_hub_product # noqa: E402
from central.app.operational_state import classify_product_state, summarize_operational_states # noqa: E402
@@ -187,16 +187,27 @@ def open_write_connection(env, database_path, firebird_writes=True):
)
def _expanded_scope(env, legacy_states):
"""RTC + PIS/COFINS assessment (read-only, SELECT only). Isolated: a failure here never fails the job or changes legacy coverage."""
def _unified_fiscal(env, company):
"""PIS/COFINS applicability + RTC (only when the ERP has the reform on), read-only (SELECT only). Fail-closed: if it cannot be assessed the job fails
instead of reporting a coverage that ignores these fields."""
connection = _firebird_reader._connect(env)
try:
connection = _open_read_only(env)
try:
return assess_from_connection(connection, legacy_states)
finally:
connection.close()
except Exception as error: # noqa: BLE001
return {"scope_version": "expanded-v1", "unavailable": type(error).__name__}
return _unified.assess_from_connection(connection, company)
finally:
connection.close()
def _fiscal_blocked_products(products, findings):
"""One queue entry per (product, blocker). Same shape as every other blocker; `field` is the dashboard category (PIS/COFINS/RTC)."""
out = []
for product in products:
code = str(product.get("CODIGO") or "")
found = findings.get(code) or {}
for blocker in found.get("blockers") or []:
out.append({"company": "", "product_code": code, "gtin": product.get("GTIN") or product.get("EAN_GTIN"), "description": product.get("PRODUTO"), "blocker_type": "BLOCKED_FISCAL_DATA",
"field": blocker["category"], "reason_code": blocker["type"], "evidence_summary": {"reason": blocker["reason"], "applicability": found.get("applicability")},
"input_fingerprint": found.get("input_fingerprint", "")})
return out
def execute_job(job, config, executor, state, env):
@@ -238,7 +249,8 @@ def execute_job(job, config, executor, state, env):
if write_connection is not None:
write_connection.close()
operational = coverage = expanded_scope = None
operational = coverage = fiscal_context = None
fiscal = None
if sync_kind:
if cache:
for product, decision in zip(to_process, decisions):
@@ -247,14 +259,23 @@ def execute_job(job, config, executor, state, env):
all_decisions = [state.cached_decision(p["CODIGO"]) or {} for p in products]
else:
all_decisions = decisions
product_states = [classify_product_state(p, d) for p, d in zip(products, all_decisions)]
operational = summarize_operational_states(product_states)
coverage = build_job_coverage(env, client, products, all_decisions)
expanded_scope = _expanded_scope(env, {s["product_code"]: s["state"] for s in product_states})
company = {"crt": ((coverage or {}).get("company_context") or {}).get("company_crt")}
fiscal = _unified_fiscal(env, company)
findings = fiscal["findings"]
product_states = [classify_product_state(p, d, fiscal=findings.get(str(p["CODIGO"]))) for p, d in zip(products, all_decisions)]
operational = summarize_operational_states(product_states)
fiscal_context = {**fiscal["context"], "evaluated_all": kind != "REPROCESS_PRODUCTS"}
else:
fiscal = _unified_fiscal(env, None)
fiscal_blocked = _fiscal_blocked_products(products, fiscal["findings"])
fiscal_codes = {b["product_code"] for b in fiscal_blocked}
summary["blocked_products"] = summary["blocked_products"] + fiscal_blocked
summary["resolved_products"] = [c for c in summary["resolved_products"] if str(c) not in fiscal_codes]
result_summary = {
"coverage": coverage,
"operational": operational,
"expanded_scope": expanded_scope,
"fiscal_context": fiscal_context,
"fiscal_mode": getattr(config, "fiscal_mode", None),
"release_version": env.get("AGENT_VERSION") or "1.1.7",
"products_total": len(products),