feat: deterministic cClassTrib resolver + gated CCLASSTRIB auto-apply (default OFF), RTC panel summary; 1.1.9

- resolver (rules RTC-R0..R4/A1..A3/G1..G2) over the official cClassTrib table 2026-06-22 and LC 214/2025 annexes; local vs official catalog comparison
- ONE root-cause RTC blocker per product (missing/unresolved/ambiguous/conflict/official mismatch/catalog outdated/evidence missing)
- auto-apply: C000025.CCLASSTRIB only, three switches (FIREBIRD_WRITES + RTC_AUTO_APPLY_ENABLED + Hub authorization), per-product re-read and re-decision, never overwrites, rowcount 1, audit table rtc_write_audits (migration 0027)
- copy-only write E2E (280/280 applied, only CCLASSTRIB changed, 2nd run 0 writes, stale decision 0 writes); real databases untouched
- docs: cclasstrib_resolution, rtc_auto_apply, unified_fiscal_completeness

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
gti
2026-09-24 21:18:34 -03:00
co-authored by Claude Sonnet 5
parent 88f4318072
commit 9f08998800
51 changed files with 16461 additions and 47 deletions
+32 -2
View File
@@ -161,7 +161,7 @@ def priority_label(value):
return _human_label(value, {"CRITICAL": "Crítica", "HIGH": "Alta", "NORMAL": "Média", "MEDIUM": "Média", "LOW": "Baixa"})
def rtc_status_view(summary):
def rtc_status_view(summary, authorized=False):
"""Discreet Reforma Tributaria status from the last completed job's `fiscal_context`, exactly as the Agent OBSERVED it in that installation's own database.
None when the Agent did not report it (older Agents)."""
context = (summary or {}).get("fiscal_context") if isinstance(summary, dict) else None
@@ -174,6 +174,36 @@ def rtc_status_view(summary):
except ValueError:
observed = None
source = context.get("rtc_source") or "PARAMETROS.USAR_REFORMA_TRIBUTARIA"
return {"enabled": bool(context["rtc_enabled"]), "label": "Ativa" if context["rtc_enabled"] else "Desativada", "supported": bool(context.get("rtc_supported")),
products = _rtc_products_view(context.get("rtc_resolution"), bool(context["rtc_enabled"]))
return {"products": products, "apply": _rtc_apply_view(summary.get("rtc_auto_apply"), authorized), "enabled": bool(context["rtc_enabled"]), "label": "Ativa" if context["rtc_enabled"] else "Desativada", "supported": bool(context.get("rtc_supported")),
"catalog_valid": bool(context.get("rtc_catalog_valid")), "observed_at": observed, "conflict": bool(context.get("rtc_param_conflict")),
"title": f"Lido no banco do cliente ({source})" + (f" em {observed}" if observed else "") + (" - linhas de PARAMETROS divergentes: RTC tratada como desativada" if context.get("rtc_param_conflict") else "")}
def _rtc_products_view(resolution, enabled):
"""Compact 'Produtos RTC' summary. None when RTC is off (RTC never shows up as a problem) or the Agent did not report a resolution."""
if not enabled or not isinstance(resolution, dict) or not resolution.get("total"):
return None
total = int(resolution["total"])
valid = int(resolution.get("valid_existing", 0))
ready = int(resolution.get("ready_to_apply", 0))
blocked = total - valid - ready
pending = total - valid
return {"total": total, "valid": valid, "ready": ready, "blocked": blocked, "pending": pending,
"headline": f"Reforma Tributária ativa — cClassTrib pendente em {pending} produto{'s' if pending != 1 else ''}" if pending else "Reforma Tributária ativa — todos os produtos com cClassTrib válido",
"catalog": (resolution.get("catalog_compatibility") or {}).get("compatibility")}
def _rtc_apply_view(report, authorized):
"""State of the (default OFF) automatic fill, for the operator. Never claims a write happened unless the Agent reported it."""
gate = (report or {}).get("gate") or {}
applied = len((report or {}).get("applied") or [])
missing = gate.get("missing") or []
labels = {"FIREBIRD_WRITES": "escrita no Firebird desligada no Agent", "RTC_AUTO_APPLY_ENABLED": "chave RTC_AUTO_APPLY_ENABLED desligada no Agent", "HUB_AUTHORIZATION": "sem autorização no Hub"}
if gate.get("allowed"):
state = "Ativo"
elif authorized:
state = "Autorizado no Hub, aguardando o Agent (" + "; ".join(labels.get(m, m) for m in missing if m != "HUB_AUTHORIZATION") + ")" if any(m != "HUB_AUTHORIZATION" for m in missing) else "Autorizado no Hub"
else:
state = "Desativado"
return {"authorized": bool(authorized), "state": state, "applied_last_job": applied, "aborted_last_job": len((report or {}).get("aborted") or []), "mode": (report or {}).get("mode")}