Files
GTI_GERENCIADOR_FISCAL/central/app/rtc_audit.py
T
gtiandClaude Sonnet 5 9f08998800 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>
2026-09-24 21:18:34 -03:00

37 lines
2.3 KiB
Python

"""Persists the Agent's RTC auto-apply audit records (append-only) and mirrors a summary event for the panel."""
from __future__ import annotations
from .events import record_event
from .models import RtcWriteAudit
MAX_AUDITS_PER_JOB = 2000
def record_rtc_write_audits(session, *, tenant_id: int, installation, agent, job_id: int | None, rtc_auto_apply: dict | None) -> int:
"""Stores every applied/aborted write of the job. Never trusts the Agent for identity: tenant/installation/agent come from the authenticated context."""
if not isinstance(rtc_auto_apply, dict):
return 0
records = list(rtc_auto_apply.get("applied") or []) + list(rtc_auto_apply.get("aborted") or [])
stored = applied = aborted = 0
for item in records[:MAX_AUDITS_PER_JOB]:
if not isinstance(item, dict) or item.get("field") != "CCLASSTRIB":
continue
session.add(RtcWriteAudit(
tenant_id=tenant_id, installation_id=installation.id, agent_id=agent.id, job_id=job_id, client=getattr(installation, "display_name", None),
product_code=str(item.get("product_code", ""))[:64], field="CCLASSTRIB", outcome=str(item.get("outcome", ""))[:16], reason=(item.get("reason") or None) and str(item["reason"])[:96],
old_value=(None if item.get("old_value") is None else str(item["old_value"])[:64]), new_value=(None if item.get("new_value") is None else str(item["new_value"])[:64]),
rule_id=(item.get("rule_id") or None) and str(item["rule_id"])[:32], evidence=item.get("evidence") or {}, official_source_version=item.get("official_source_version") or {},
local_catalog_hash=item.get("local_catalog_hash"), written_at=item.get("timestamp")))
stored += 1
if item.get("outcome") == "APPLIED":
applied += 1
else:
aborted += 1
if stored:
record_event(session, tenant_id=tenant_id, installation_id=installation.id, agent_id=agent.id, severity="WARNING" if applied else "INFO", event_type="RTC_AUTO_APPLY", job_id=job_id,
message=f"cClassTrib preenchido em {applied} produto(s); {aborted} abortado(s)",
context={"applied": applied, "aborted": aborted, "reasons": rtc_auto_apply.get("aborted_reasons") or {}, "mode": rtc_auto_apply.get("mode")})
if stored:
session.commit()
return stored