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
+1
View File
@@ -45,3 +45,4 @@ scripts/_*
*.pkl
.pytmp/
sources_cache/legal/
scratch_rtc_e2e/
+1 -1
View File
@@ -1,6 +1,6 @@
[project]
name = "gti-fiscal-agent"
version = "1.1.8"
version = "1.1.9"
requires-python = ">=3.10"
dependencies = ["httpx>=0.27,<1"]
+40 -10
View File
@@ -30,7 +30,7 @@ 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 import firebird_reader as _firebird_reader, unified_fiscal as _unified # noqa: E402
from gti_fiscal_agent import firebird_reader as _firebird_reader, rtc_auto_apply as _rtc_apply, 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
@@ -104,7 +104,10 @@ class Config:
# (A legacy AGENT_JOBS_ENABLED line in an old agent.env is deliberately ignored.)
self.agent_jobs_enabled = True
self.poll_interval_seconds = float(env.get("POLL_INTERVAL_SECONDS", "5"))
self.agent_version = env.get("AGENT_VERSION", "1.1.8")
# RTC auto-apply is a SEPARATE switch (default OFF): FIREBIRD_WRITES alone never lets the Agent fill cClassTrib.
self.rtc_auto_apply_enabled = env.get("RTC_AUTO_APPLY_ENABLED", "0").strip() == "1"
self.rtc_auto_apply_max_per_job = int(env.get("RTC_AUTO_APPLY_MAX_PER_JOB", str(_rtc_apply.DEFAULT_MAX_PER_JOB)))
self.agent_version = env.get("AGENT_VERSION", "1.1.9")
self.schema_fingerprint = env.get("SCHEMA_FINGERPRINT", REFERENCE_SCHEMA_V1)
self.state_db = env.get("AGENT_STATE_DB", str(AGENT_ROOT / "agent_state.sqlite"))
# Pilot write authorization: both must be explicitly set by the installer for
@@ -187,12 +190,12 @@ def open_write_connection(env, database_path, firebird_writes=True):
)
def _unified_fiscal(env, company):
def _unified_fiscal(env, company, ncm_valid=None):
"""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:
return _unified.assess_from_connection(connection, company)
return _unified.assess_from_connection(connection, company, ncm_valid=ncm_valid)
finally:
connection.close()
@@ -210,6 +213,28 @@ def _fiscal_blocked_products(products, findings):
return out
def _rtc_auto_apply(env, config, job, products, fiscal, company):
"""Report (always) and, only when all three gates are open, fill CCLASSTRIB for products whose class is PROVEN by a deterministic rule.
Returns the summary stored in the job result; never raises for a single product."""
gate = _rtc_apply.write_gate(firebird_writes=bool(getattr(config, "firebird_writes", False)), agent_enabled=bool(getattr(config, "rtc_auto_apply_enabled", False)),
hub_authorized=bool((job.get("authorizations") or {}).get("rtc_auto_apply")))
limit = int(getattr(config, "rtc_auto_apply_max_per_job", _rtc_apply.DEFAULT_MAX_PER_JOB))
candidates, ready_total = _rtc_apply.select_candidates(fiscal["findings"], products, limit=limit)
summary = {"gate": gate, "mode": "AUTO_APPLY" if gate["allowed"] else "REPORT_ONLY", "ready_total": ready_total, "max_per_job": limit, "write_attempts": 0, "write_successes": 0, "applied": [], "aborted": [], "aborted_reasons": {},
"allowed_fields": list(_rtc_apply.ALLOWED_RTC_WRITE_FIELDS)}
if not (gate["allowed"] and candidates and fiscal["context"].get("rtc_enabled")):
return summary
from datetime import datetime, timezone
connection = open_write_connection(env, config.database_path)
try:
outcome = _rtc_apply.apply_candidates(connection, candidates, company=company or {}, decision={"local_catalog_hash": fiscal["context"].get("local_catalog_hash")},
now=datetime.now(timezone.utc).isoformat(timespec="seconds"), job_id=job.get("job_id"))
finally:
connection.close()
summary.update({k: outcome[k] for k in ("applied", "aborted", "aborted_reasons", "write_attempts", "write_successes")})
return summary
def execute_job(job, config, executor, state, env):
"""Run the pipeline for one claimed job. Never raises for job-domain
failures (bad Firebird read, classify error) — returns (result_summary, error)."""
@@ -249,7 +274,7 @@ def execute_job(job, config, executor, state, env):
if write_connection is not None:
write_connection.close()
operational = coverage = fiscal_context = None
operational = coverage = fiscal_context = rtc_apply = None
fiscal = None
if sync_kind:
if cache:
@@ -261,13 +286,17 @@ def execute_job(job, config, executor, state, env):
all_decisions = decisions
coverage = build_job_coverage(env, client, products, all_decisions)
company = {"crt": ((coverage or {}).get("company_context") or {}).get("company_crt")}
fiscal = _unified_fiscal(env, company)
ncm_ok = {str(p["CODIGO"]): (d or {}).get("status") == "EXACT_NCM" for p, d in zip(products, all_decisions)}
fiscal = _unified_fiscal(env, company, ncm_ok)
rtc_apply = _rtc_auto_apply(env, config, job, products, fiscal, company)
if rtc_apply["write_successes"]:
fiscal = _unified_fiscal(env, company, ncm_ok) # coverage must reflect the fields that were just filled
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 = _unified_fiscal(env, None, {str(p["CODIGO"]): (d or {}).get("status") == "EXACT_NCM" for p, d in zip(to_process, decisions)})
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
@@ -276,8 +305,9 @@ def execute_job(job, config, executor, state, env):
"coverage": coverage,
"operational": operational,
"fiscal_context": fiscal_context,
"rtc_auto_apply": rtc_apply,
"fiscal_mode": getattr(config, "fiscal_mode", None),
"release_version": env.get("AGENT_VERSION") or "1.1.8",
"release_version": env.get("AGENT_VERSION") or "1.1.9",
"products_total": len(products),
"products_changed": len(to_process),
"products_processed": summary["processed"],
@@ -365,7 +395,7 @@ def run(config, env, *, max_iterations=None, stopper=None):
"agent_version": config.agent_version, "last_job_id": last_job_id, "display_name": env.get("AGENT_DISPLAY_NAME", ""),
"customer_code": env.get("CUSTOMER_CODE"), "hostname": env.get("AGENT_HOSTNAME") or os.environ.get("COMPUTERNAME"),
"windows_version": env.get("WINDOWS_VERSION"), "company_code": config.company_id, "company_uf": config.company_uf,
"database_path": config.database_path, "capabilities": {"heartbeat": True, "jobs": True, "local_logs": True},
"database_path": config.database_path, "capabilities": {"heartbeat": True, "jobs": True, "local_logs": True, "rtc_auto_apply": True, "rtc_auto_apply_enabled": bool(getattr(config, "rtc_auto_apply_enabled", False))},
"fiscal_engine_version": env.get("FISCAL_ENGINE_VERSION", config.agent_version), "dataset_versions": {},
}
if hasattr(client, "heartbeat_metadata"):
@@ -435,7 +465,7 @@ def run(config, env, *, max_iterations=None, stopper=None):
def enforce_startup_preflight(env):
"""Full read-only preflight at startup (Hub, auth, Firebird, capabilities, company context, guards). Fail closed."""
from gti_fiscal_agent.preflight import run_preflight
report = run_preflight(env, agent_version=env.get("AGENT_VERSION") or "1.1.8")
report = run_preflight(env, agent_version=env.get("AGENT_VERSION") or "1.1.9")
logger.info("startup preflight: %s", json.dumps({k: report[k] for k in ("PREFLIGHT", "blockers", "fiscal_mode", "effective_firebird_writes")}, ensure_ascii=False))
print(json.dumps({"PREFLIGHT": report["PREFLIGHT"], "blockers": report["blockers"]}, ensure_ascii=False))
if report["PREFLIGHT"] != "PASS":
+1 -1
View File
@@ -9,7 +9,7 @@ import os
import sqlite3
from pathlib import Path
AGENT_VERSION_DEFAULT = "1.1.8"
AGENT_VERSION_DEFAULT = "1.1.9"
REQUIRED_ENV = ("GTI_HUB_URL", "AGENT_TOKEN", "FIREBIRD_DATABASE_PATH", "FB_USER", "FB_PASSWORD")
REQUIRED_CAPABILITIES = ("supports_ncm", "supports_ipi", "supports_cest")
FISCAL_MODES = ("DRY_RUN", "AUTO_APPLY")
@@ -0,0 +1,136 @@
"""RTC auto-apply: fills C000025.CCLASSTRIB, and NOTHING else, when a deterministic rule proves the class.
Safety model (every gate must hold, default is OFF everywhere):
FIREBIRD_WRITES effective (FISCAL_MODE=AUTO_APPLY + path/schema authorization) AND RTC_AUTO_APPLY_ENABLED (this Agent) AND Hub authorization (per installation)
Write allowlist: ALLOWED_RTC_WRITE_FIELDS = ["CCLASSTRIB"]. One short transaction per product. Immediately before the UPDATE the product is re-read and
the whole decision is re-derived from fresh data; if ANYTHING changed (RTC switch, catalog, NCM, current value, decision) that product is ABORTED.
An existing value is never overwritten. Every applied change returns an audit record with the old value, so it can be reverted manually and auditably.
"""
from __future__ import annotations
import re
from datetime import date
from central.app.cclasstrib_resolver import compare_catalogs, local_catalog_hash, resolve_cclasstrib
from central.app.unified_fiscal_coverage import rtc_enabled
from .unified_fiscal import detect_rtc_capabilities, load_rtc_context
ALLOWED_RTC_WRITE_FIELDS = ("CCLASSTRIB",)
DEFAULT_MAX_PER_JOB = 500
class RtcApplyAborted(Exception):
"""The product is left untouched; `reason` is reported in the audit."""
def __init__(self, reason: str):
super().__init__(reason)
self.reason = reason
def _text(value) -> str:
return "" if value is None else str(value).strip()
def _digits(value) -> str:
return re.sub(r"\D", "", _text(value))
def write_gate(*, firebird_writes: bool, agent_enabled: bool, hub_authorized: bool) -> dict:
"""{"allowed": bool, "missing": [...]}: writes are possible only when all three switches are on."""
missing = [name for name, on in (("FIREBIRD_WRITES", firebird_writes), ("RTC_AUTO_APPLY_ENABLED", agent_enabled), ("HUB_AUTHORIZATION", hub_authorized)) if not on]
return {"allowed": not missing, "missing": missing, "firebird_writes": bool(firebird_writes), "agent_enabled": bool(agent_enabled), "hub_authorized": bool(hub_authorized)}
def select_candidates(findings: dict, products: list[dict], *, limit: int = DEFAULT_MAX_PER_JOB) -> tuple[list[dict], int]:
"""Products READY_TO_APPLY: empty CCLASSTRIB + a deterministic resolution, no other RTC problem. Returns (candidates up to `limit`, total ready)."""
by_code = {str(p.get("CODIGO") or ""): p for p in products}
ready = []
for code, found in findings.items():
res = found.get("rtc_resolution") or {}
if not res.get("auto_apply_candidate"):
continue
rtc_types = {b["type"] for b in found.get("blockers", []) if b["category"] == "RTC"}
if rtc_types != {"CCLASSTRIB_MISSING"}:
continue
p = by_code.get(code, {})
if _text(p.get("CCLASSTRIB")):
continue
ready.append({"product_code": code, "proposed": res["resolved_cclasstrib"], "cst": res["resolved_cst_ibscbs"], "rule_id": res["rule_id"], "ncm": _digits(p.get("CLASSIFICACAO_FISCAL")), "confidence_type": res["confidence_type"]})
return ready[:limit], len(ready)
def apply_candidate(conn, candidate: dict, *, company: dict, decision: dict, now: str, job_id=None, today: date | None = None) -> dict:
"""One product, one short transaction. `decision` = {"local_catalog_hash": ...} captured when the candidate was chosen. Raises RtcApplyAborted (nothing written)."""
field = "CCLASSTRIB"
assert field in ALLOWED_RTC_WRITE_FIELDS
today = today or date.today()
code, proposed = candidate["product_code"], candidate["proposed"]
cur = conn.cursor()
try:
schema, catalog, extras = load_rtc_context(cur) # fresh: the ERP is asked again, nothing cached
caps = detect_rtc_capabilities(schema, param_value=extras["param_value"], catalog=catalog, physical_fk=extras["physical_fk"])
if extras["param_conflict"] or not rtc_enabled(caps):
raise RtcApplyAborted("RTC_NO_LONGER_ENABLED")
if not caps["RTC_CATALOG_VALID"]:
raise RtcApplyAborted("RTC_CATALOG_INVALID")
local_rows = (catalog or {}).get("rows") or {}
fresh_hash = local_catalog_hash(local_rows)
if decision.get("local_catalog_hash") and fresh_hash != decision["local_catalog_hash"]:
raise RtcApplyAborted("LOCAL_CATALOG_CHANGED_SINCE_DECISION")
cur.execute(f"SELECT {field}, CLASSIFICACAO_FISCAL FROM C000025 WHERE CODIGO = ?", (code,))
rows = cur.fetchall()
if len(rows) != 1:
raise RtcApplyAborted("PRODUCT_ROW_NOT_UNIQUE" if rows else "PRODUCT_NOT_FOUND")
current, ncm = _text(rows[0][0]), _digits(rows[0][1])
if current:
raise RtcApplyAborted("VALUE_ALREADY_SET_NEVER_OVERWRITTEN" if current != proposed else "NO_WRITE_REQUIRED")
if ncm != candidate["ncm"]:
raise RtcApplyAborted("PRODUCT_CHANGED_NCM")
compat = compare_catalogs(local_rows, today)
resolution = resolve_cclasstrib({"CODIGO": code, "CLASSIFICACAO_FISCAL": ncm, "ncm_valid": True}, company, {"rtc_enabled": True, "local_catalog_valid": True}, local_rows, compat=compat, today=today)
if not resolution.auto_apply_candidate or resolution.resolved_cclasstrib != proposed or resolution.rule_id != candidate["rule_id"]:
raise RtcApplyAborted("DECISION_CHANGED")
cur.execute(f"UPDATE C000025 SET {field} = ? WHERE CODIGO = ? AND ({field} IS NULL OR TRIM({field}) = '')", (proposed, code))
if cur.rowcount != 1:
conn.rollback()
raise RtcApplyAborted("UPDATE_ROWCOUNT_NOT_ONE")
conn.commit()
cur = conn.cursor()
cur.execute(f"SELECT {field} FROM C000025 WHERE CODIGO = ?", (code,))
after = _text(cur.fetchone()[0])
conn.commit()
if after != proposed:
raise RuntimeError(f"post-write verification failed for {code}: {after!r} != {proposed!r}")
official = compat["official_catalog"]
return {"outcome": "APPLIED", "product_code": code, "field": field, "old_value": None, "new_value": proposed, "cst_ibscbs": resolution.resolved_cst_ibscbs, "rule_id": resolution.rule_id,
"confidence_type": resolution.confidence_type, "evidence": {"inputs_used": resolution.inputs_used, "evidence": resolution.evidence[:5], "reason": resolution.reason},
"official_source_version": {"cclasstrib_table": official["publication"], "cclasstrib_sha256": official["sha256"], "lc214_sha256": resolution.source_references[0]["sha256"]},
"local_catalog_hash": fresh_hash, "timestamp": now, "job_id": job_id, "rowcount": 1, "verified_after": True}
except RtcApplyAborted:
try:
conn.rollback()
except Exception: # noqa: BLE001
pass
raise
except Exception:
try:
conn.rollback()
except Exception: # noqa: BLE001
pass
raise
def apply_candidates(conn, candidates: list[dict], *, company: dict, decision: dict, now: str, job_id=None, today: date | None = None) -> dict:
"""Applies each candidate independently; a failure or an abort never stops the rest and never leaves a partial change."""
applied, aborted, reasons = [], [], {}
for candidate in candidates:
try:
applied.append(apply_candidate(conn, candidate, company=company, decision=decision, now=now, job_id=job_id, today=today))
except RtcApplyAborted as stop:
aborted.append({"outcome": "ABORTED", "product_code": candidate["product_code"], "field": "CCLASSTRIB", "reason": stop.reason, "rule_id": candidate["rule_id"], "job_id": job_id, "timestamp": now})
reasons[stop.reason] = reasons.get(stop.reason, 0) + 1
except Exception as error: # noqa: BLE001 - technical failure of ONE product must not abort the batch
aborted.append({"outcome": "FAILED", "product_code": candidate["product_code"], "field": "CCLASSTRIB", "reason": f"{type(error).__name__}", "rule_id": candidate["rule_id"], "job_id": job_id, "timestamp": now})
reasons["TECHNICAL_FAILURE"] = reasons.get("TECHNICAL_FAILURE", 0) + 1
return {"applied": applied, "aborted": aborted, "aborted_reasons": reasons, "write_attempts": len(candidates), "write_successes": len(applied)}
+26 -4
View File
@@ -11,12 +11,14 @@ import json
from collections import Counter
from datetime import date, datetime, timezone
from central.app.cclasstrib_resolver import compare_catalogs, local_catalog_hash, resolve_cclasstrib
from central.app.rtc_findings import refine_rtc_finding, summarize_rtc
from central.app.unified_fiscal_coverage import PIS_COFINS_FIELDS, catalog_integrity, evaluate_product, legal_effective_context, rtc_enabled
PRODUCT_TABLE = "C000025"
RTC_CCLASSTRIB_TABLE = "RTC_CCLASSTRIB"
RTC_CST_TABLE = "RTC_CST"
PRODUCT_COLUMNS = ("CODIGO", "CCLASSTRIB") + PIS_COFINS_FIELDS + ("PISCOFINS", "INCIDENCIA_PISCOFINS")
PRODUCT_COLUMNS = ("CODIGO", "CCLASSTRIB", "CLASSIFICACAO_FISCAL") + PIS_COFINS_FIELDS + ("PISCOFINS", "INCIDENCIA_PISCOFINS")
TRUTHY = {"1", "S", "T", "TRUE"}
@@ -67,13 +69,23 @@ def load_rtc_context(cursor) -> tuple[dict, dict | None, dict]:
class_cols = schema.get(RTC_CCLASSTRIB_TABLE, set())
if {"CCLASSTRIB", "CSTIBSCBS"} <= class_cols and "CSTIBSCBS" in schema.get(RTC_CST_TABLE, set()):
validity = "INICIO_VIGENCIA" in class_cols and "FIM_VIGENCIA" in class_cols
cursor.execute("SELECT TRIM(CCLASSTRIB), TRIM(CSTIBSCBS)" + (", INICIO_VIGENCIA, FIM_VIGENCIA" if validity else "") + " FROM RTC_CCLASSTRIB")
extra = [c for c in ("TIPO_ALIQUOTA", "PRED_IBS", "PRED_CBS", "IND_NFE", "IND_NFCE") if c in class_cols]
cursor.execute("SELECT TRIM(CCLASSTRIB), TRIM(CSTIBSCBS)" + (", INICIO_VIGENCIA, FIM_VIGENCIA" if validity else "") + "".join(f", {c}" for c in extra) + " FROM RTC_CCLASSTRIB")
rows = cursor.fetchall()
classes = {r[0]: (r[1] or None) for r in rows}
cursor.execute("SELECT TRIM(CSTIBSCBS) FROM RTC_CST")
catalog = {"classes": classes, "cst": {r[0] for r in cursor.fetchall()}, "duplicates": len(rows) - len(classes)}
if validity:
catalog["validity"] = {r[0]: (r[2], r[3]) for r in rows}
offset = 4 if validity else 2
catalog["rows"] = {r[0]: {"cst": r[1], "start": (r[2] if validity else None), "end": (r[3] if validity else None), **{c: r[offset + i] for i, c in enumerate(extra)}} for r in rows}
for value in catalog["rows"].values():
for key in ("TIPO_ALIQUOTA", "PRED_IBS", "PRED_CBS", "IND_NFE", "IND_NFCE"):
if key in value:
value[key.lower()] = value.pop(key)
for flag in ("ind_nfe", "ind_nfce"):
if flag in value:
value[flag] = str(value[flag]).strip() in ("1", "True", "true")
return schema, catalog, extras
@@ -87,9 +99,10 @@ def read_products(cursor, schema: dict) -> list[dict]:
return [dict(zip(columns, row)) for row in cursor.fetchall()]
def assess_from_connection(conn, company: dict | None, *, today: date | None = None, rtc_override: bool | None = None) -> dict:
def assess_from_connection(conn, company: dict | None, *, today: date | None = None, rtc_override: bool | None = None, ncm_valid: dict | None = None) -> dict:
"""One read-only pass. Returns {"context": {...}, "findings": {product_code: evaluate_product(...)}}.
rtc_override forces RTC on/off for simulations WITHOUT touching the ERP parameter (tests / what-if only)."""
rtc_override forces RTC on/off for simulations WITHOUT touching the ERP parameter (tests / what-if only).
ncm_valid: {product_code: bool} from the decision engine (EXACT_NCM); a product whose NCM is not confirmed never gets a cClassTrib proposal."""
cursor = conn.cursor()
schema, catalog, extras = load_rtc_context(cursor)
caps = detect_rtc_capabilities(schema, param_value=extras["param_value"], catalog=catalog, physical_fk=extras["physical_fk"])
@@ -102,8 +115,15 @@ def assess_from_connection(conn, company: dict | None, *, today: date | None = N
warn_products: set = set()
samples: list = []
applicability: Counter = Counter()
local_rows = (catalog or {}).get("rows") or {}
compat = compare_catalogs(local_rows, today) if (enabled and local_rows) else None
resolver_ctx = {"rtc_enabled": enabled, "local_catalog_valid": bool(caps["RTC_CATALOG_VALID"])}
for p in products:
found = evaluate_product(p, company, rtc, today)
if enabled:
code0 = str(p.get("CODIGO") or "")
resolution = resolve_cclasstrib({**p, "ncm_valid": bool((ncm_valid or {}).get(code0, False))}, company, resolver_ctx, local_rows, compat=compat, today=today)
refine_rtc_finding(found, p, resolution, compat["per_code"] if compat else None)
found["input_fingerprint"] = hashlib.sha256(json.dumps({k: (None if v is None else str(v).strip()) for k, v in p.items()} | {"rtc": enabled}, sort_keys=True).encode()).hexdigest()
code = str(p.get("CODIGO") or "")
findings[code] = found
@@ -119,5 +139,7 @@ def assess_from_connection(conn, company: dict | None, *, today: date | None = N
"rtc_observed_at": datetime.now(timezone.utc).isoformat(timespec="seconds"), "legal_effective_context": legal_effective_context((company or {}).get("crt")),
"catalog_integrity": integrity, "crt": (company or {}).get("crt"), "capabilities": caps, "products_evaluated": len(products),
"pis_cofins_applicability": dict(applicability),
"rtc_resolution": summarize_rtc(findings, compat) if enabled else None, "local_catalog_hash": local_catalog_hash(local_rows) if local_rows else None,
"official_catalog": (compat or {}).get("official_catalog"),
"non_canonical_warnings": {"products": len(warn_products), "by_type": dict(sorted(warn_by_type.items())), "samples": samples}},
"findings": findings}
+297
View File
@@ -0,0 +1,297 @@
import re
from datetime import date
import pytest
from central.app import rtc_reference_data as ref
from central.app.cclasstrib_resolver import local_catalog_hash
from gti_fiscal_agent import rtc_auto_apply as apply_mod
from gti_fiscal_agent import unified_fiscal as uf
pytestmark = pytest.mark.real_unified_fiscal
TODAY = date(2026, 9, 24)
NORMAL = {"crt": "3"}
CLASS_COLS = ("CCLASSTRIB", "CSTIBSCBS", "INICIO_VIGENCIA", "FIM_VIGENCIA", "TIPO_ALIQUOTA", "PRED_IBS", "PRED_CBS", "IND_NFE", "IND_NFCE")
SCHEMA = ([("C000025", c) for c in ("CODIGO", "CCLASSTRIB", "CLASSIFICACAO_FISCAL", "PISCOFINS", "INCIDENCIA_PISCOFINS")] + [("RTC_CCLASSTRIB", c) for c in CLASS_COLS] + [("RTC_CST", "CSTIBSCBS"), ("PARAMETROS", "USAR_REFORMA_TRIBUTARIA")])
class FakeFirebird:
"""A tiny SGV: PARAMETROS, the RTC catalog (from the official table) and C000025. Every statement is recorded; only SELECT and the single allowed UPDATE are understood."""
def __init__(self, products, param=(1,), catalog_tweak=None, skip=()):
self.products = {c: dict(v) for c, v in products.items()} # code -> {"CCLASSTRIB":..., "CLASSIFICACAO_FISCAL":...}
self.param = list(param)
self.catalog = {r["code"]: {"cst": r["cst"], "start": r["start"], "end": r["end"], "tipo": r["tipo_aliquota"], "pred_ibs": r["p_red_ibs"], "pred_cbs": r["p_red_cbs"], "ind_nfe": r["ind_nfe"], "ind_nfce": r["ind_nfce"]}
for r in ref.OFFICIAL_CCLASSTRIB if r["code"] not in skip and not r["end"]}
for code, change in (catalog_tweak or {}).items():
self.catalog[code] = {**self.catalog[code], **change}
self.cst_table = {r["cst"] for r in ref.OFFICIAL_CCLASSTRIB} # RTC_CST is its own table: a class may point to a CST that is not in it
self.statements, self.commits, self.rollbacks = [], 0, 0
self._pending = {}
def cursor(self):
return FakeCursor(self)
def commit(self):
for code, value in self._pending.items():
self.products[code]["CCLASSTRIB"] = value
self._pending = {}
self.commits += 1
def rollback(self):
self._pending = {}
self.rollbacks += 1
class FakeCursor:
def __init__(self, db):
self.db, self._rows, self.rowcount = db, [], -1
def execute(self, sql, params=()):
db = self.db
db.statements.append((sql, tuple(params)))
text = " ".join(sql.split())
up = text.upper()
if up.startswith("SELECT TRIM(RF.RDB$RELATION_NAME)"):
self._rows = list(SCHEMA)
elif up.startswith("SELECT FIRST 20 USAR_REFORMA_TRIBUTARIA"):
self._rows = [(v,) for v in db.param]
elif "RDB$RELATION_CONSTRAINTS" in up:
self._rows = [(1,)]
elif up.startswith("SELECT TRIM(CCLASSTRIB)") and "FROM RTC_CCLASSTRIB" in up:
cols = [c.strip() for c in text[len("SELECT "):up.index(" FROM ")].split(",")]
rows = []
for code, r in db.catalog.items():
mapping = {"CSTIBSCBS": r["cst"], "INICIO_VIGENCIA": date.fromisoformat(r["start"]) if r["start"] else None, "FIM_VIGENCIA": None, "TIPO_ALIQUOTA": r["tipo"], "PRED_IBS": r["pred_ibs"],
"PRED_CBS": r["pred_cbs"], "IND_NFE": "1" if r["ind_nfe"] else "0", "IND_NFCE": "1" if r["ind_nfce"] else "0"}
rows.append(tuple(code if c.startswith("TRIM(CCLASSTRIB") else mapping[re.sub(r"TRIM\((\w+)\)", r"\1", c)] for c in cols))
self._rows = rows
elif up.startswith("SELECT TRIM(CSTIBSCBS) FROM RTC_CST"):
self._rows = [(c,) for c in sorted(db.cst_table)]
elif up.startswith("SELECT CODIGO, CCLASSTRIB, CLASSIFICACAO_FISCAL"):
self._rows = [(c, v["CCLASSTRIB"], v["CLASSIFICACAO_FISCAL"], "N", "NENHUMA") for c, v in sorted(db.products.items())]
elif up.startswith("SELECT CCLASSTRIB, CLASSIFICACAO_FISCAL FROM C000025"):
code = params[0]
self._rows = [(db.products[code]["CCLASSTRIB"], db.products[code]["CLASSIFICACAO_FISCAL"])] if code in db.products else []
if code in db.products and db.products[code].get("DUP"):
self._rows = self._rows * 2
elif up.startswith("SELECT CCLASSTRIB FROM C000025"):
code = params[0]
self._rows = [(db.products[code]["CCLASSTRIB"],)]
elif up.startswith("UPDATE C000025 SET CCLASSTRIB = ? WHERE CODIGO = ?"):
value, code = params
product = db.products.get(code)
hit = product is not None and not (product["CCLASSTRIB"] or "").strip()
self.rowcount = (2 if product and product.get("DUP") else 1) if hit else 0
if hit and self.rowcount == 1:
db._pending[code] = value
else:
raise AssertionError(f"unexpected statement: {sql}")
def fetchall(self):
return self._rows
def fetchone(self):
return self._rows[0] if self._rows else None
def product(ncm, value=None, **extra):
return {"CCLASSTRIB": value, "CLASSIFICACAO_FISCAL": ncm, **extra}
def assess(db, ncm_ok=None, company=NORMAL):
return uf.assess_from_connection(db, company, today=TODAY, ncm_valid=ncm_ok if ncm_ok is not None else {c: True for c in db.products})
def candidates_of(db, limit=500):
out = assess(db)
products = [{"CODIGO": c, "CCLASSTRIB": v["CCLASSTRIB"], "CLASSIFICACAO_FISCAL": v["CLASSIFICACAO_FISCAL"]} for c, v in db.products.items()]
cands, total = apply_mod.select_candidates(out["findings"], products, limit=limit)
return out, cands, total
def run(db, cands, out, **kw):
return apply_mod.apply_candidates(db, cands, company=NORMAL, decision={"local_catalog_hash": out["context"]["local_catalog_hash"]}, now="2026-09-24T20:00:00+00:00", job_id=7, today=TODAY, **kw)
PRODUCTS = {"000001": product("10063021"), "000002": product("39235000"), "000003": product("15171000"), "000004": product("30049099"), "000005": product("39235000", "000001")}
def test_gate_needs_all_three_switches_and_defaults_to_closed():
assert apply_mod.write_gate(firebird_writes=True, agent_enabled=True, hub_authorized=True)["allowed"] is True
for combo in ((False, True, True), (True, False, True), (True, True, False), (False, False, False)):
g = apply_mod.write_gate(firebird_writes=combo[0], agent_enabled=combo[1], hub_authorized=combo[2])
assert g["allowed"] is False and g["missing"]
assert apply_mod.ALLOWED_RTC_WRITE_FIELDS == ("CCLASSTRIB",)
def test_only_proven_products_become_candidates():
db = FakeFirebird(PRODUCTS)
out, cands, total = candidates_of(db)
assert sorted((c["product_code"], c["proposed"], c["rule_id"]) for c in cands) == [("000001", "200003", "RTC-R3"), ("000002", "000001", "RTC-R4"), ("000003", "200003", "RTC-R3")]
assert total == 3 and out["context"]["rtc_resolution"]["ready_to_apply"] == 3 and out["context"]["rtc_resolution"]["ambiguous"] == 1 # medicine chapter is ambiguous
assert out["context"]["rtc_resolution"]["valid_existing"] == 1 # 000005 already has the right class
assert "000004" not in {c["product_code"] for c in cands}
def test_apply_writes_only_cclasstrib_with_audit_and_second_run_writes_nothing():
db = FakeFirebird(PRODUCTS)
out, cands, _ = candidates_of(db)
result = run(db, cands, out)
assert result["write_successes"] == 3 and result["write_attempts"] == 3 and result["aborted"] == []
assert {c: db.products[c]["CCLASSTRIB"] for c in ("000001", "000002", "000003")} == {"000001": "200003", "000002": "000001", "000003": "200003"}
updates = [s for s, _ in db.statements if s.upper().startswith("UPDATE")]
assert len(updates) == 3 and all(re.fullmatch(r"UPDATE C000025 SET CCLASSTRIB = \? WHERE CODIGO = \? AND \(CCLASSTRIB IS NULL OR TRIM\(CCLASSTRIB\) = ''\)", u) for u in updates)
assert not [s for s, _ in db.statements if re.match(r"(INSERT|DELETE|ALTER|DROP)", s.strip(), re.I)]
audit = result["applied"][0]
assert {"product_code", "field", "old_value", "new_value", "rule_id", "evidence", "official_source_version", "local_catalog_hash", "timestamp", "job_id", "outcome"} <= set(audit)
assert audit["old_value"] is None and audit["field"] == "CCLASSTRIB" and audit["official_source_version"]["cclasstrib_sha256"] == ref.OFFICIAL_CATALOG["sha256"] and audit["job_id"] == 7
out2, cands2, total2 = candidates_of(db) # idempotent: nothing left to write
assert cands2 == [] and total2 == 0 and out2["context"]["rtc_resolution"]["valid_existing"] == 4
before = len(db.statements)
assert run(db, cands2, out2)["write_attempts"] == 0 and len(db.statements) == before
def test_rtc_switched_off_between_decision_and_write_aborts_every_product():
db = FakeFirebird(PRODUCTS)
out, cands, _ = candidates_of(db)
db.param = [0]
result = run(db, cands, out)
assert result["write_successes"] == 0 and result["aborted_reasons"] == {"RTC_NO_LONGER_ENABLED": 3} and all(p["CCLASSTRIB"] in (None, "000001") for p in db.products.values())
def test_conflicting_parameter_rows_abort():
db = FakeFirebird(PRODUCTS)
out, cands, _ = candidates_of(db)
db.param = [1, 0]
assert run(db, cands, out)["aborted_reasons"] == {"RTC_NO_LONGER_ENABLED": 3}
def test_catalog_changed_or_invalid_between_decision_and_write_aborts():
db = FakeFirebird(PRODUCTS)
out, cands, _ = candidates_of(db)
db.catalog["200003"]["pred_ibs"] = "60"
assert run(db, cands, out)["aborted_reasons"] == {"LOCAL_CATALOG_CHANGED_SINCE_DECISION": 3}
db2 = FakeFirebird(PRODUCTS)
out2, cands2, _ = candidates_of(db2)
db2.catalog["999999"] = {**db2.catalog["000001"], "cst": "555"} # class pointing to a CST that does not exist -> invalid catalog
invalid = apply_mod.apply_candidates(db2, cands2, company=NORMAL, decision={}, now="2026-09-24T20:00:00+00:00", job_id=7, today=TODAY) # no hash pinned: integrity check alone must stop it
assert invalid["aborted_reasons"] == {"RTC_CATALOG_INVALID": 3}
def test_product_changed_between_decision_and_write_is_left_alone():
db = FakeFirebird(PRODUCTS)
out, cands, _ = candidates_of(db)
db.products["000001"]["CLASSIFICACAO_FISCAL"] = "30049099" # NCM edited in the ERP meanwhile
db.products["000002"]["CCLASSTRIB"] = "000001" # operator filled it meanwhile (same value)
db.products["000003"]["CCLASSTRIB"] = "200034" # ... or with a DIFFERENT value: never overwritten
result = run(db, cands, out)
assert result["aborted_reasons"] == {"PRODUCT_CHANGED_NCM": 1, "NO_WRITE_REQUIRED": 1, "VALUE_ALREADY_SET_NEVER_OVERWRITTEN": 1} and result["write_successes"] == 0
assert db.products["000003"]["CCLASSTRIB"] == "200034" and not [s for s, _ in db.statements if s.upper().startswith("UPDATE")]
def test_duplicate_code_rowcount_protection_and_isolation_of_failures():
products = {**PRODUCTS, "000002": product("39235000", DUP=True)}
db = FakeFirebird(products)
out, cands, _ = candidates_of(db)
result = run(db, cands, out)
assert result["aborted_reasons"] == {"PRODUCT_ROW_NOT_UNIQUE": 1} and result["write_successes"] == 2 # the ambiguous row is skipped, the rest still applied
assert db.products["000002"]["CCLASSTRIB"] is None
def test_existing_different_valid_class_is_a_conflict_blocker_never_a_write():
db = FakeFirebird({"000001": product("10063021", "000001")}) # rice already classified as fully taxed: differs from the proven Anexo I class
out = assess(db)
types = {b["type"] for b in out["findings"]["000001"]["blockers"]}
assert types == {"CCLASSTRIB_CONFLICT"} and out["context"]["rtc_resolution"]["conflicts"] == 1
_, cands, _ = candidates_of(db)
assert cands == []
def test_official_mismatch_of_an_existing_class_is_reported():
db = FakeFirebird({"000001": product("39235000", "000001")}, catalog_tweak={"000001": {"ind_nfe": False, "ind_nfce": False}})
types = {b["type"] for b in assess(db)["findings"]["000001"]["blockers"]}
assert types == {"CCLASSTRIB_OFFICIAL_MISMATCH"}
def test_batch_limit_bounds_the_blast_radius():
db = FakeFirebird({f"{i:06d}": product("39235000") for i in range(1, 8)})
out, cands, total = candidates_of(db, limit=3)
assert len(cands) == 3 and total == 7
def test_simples_never_produces_candidates_even_with_rtc_on():
db = FakeFirebird(PRODUCTS)
out = uf.assess_from_connection(db, {"crt": "1"}, today=TODAY, ncm_valid={c: True for c in PRODUCTS})
assert out["context"]["rtc_resolution"]["ready_to_apply"] == 0
kinds = {b["type"] for f in out["findings"].values() for b in f["blockers"]}
assert "CCLASSTRIB_MISSING_UNRESOLVED" in kinds and "CCLASSTRIB_MISSING" not in kinds
def test_rtc_off_has_no_resolution_and_no_candidates():
db = FakeFirebird(PRODUCTS, param=(0,))
out, cands, total = candidates_of(db)
assert cands == [] and total == 0 and out["context"]["rtc_resolution"] is None
def test_catalog_hash_is_recorded_for_audit():
db = FakeFirebird(PRODUCTS)
out = assess(db)
schema, catalog, _ = uf.load_rtc_context(db.cursor())
assert out["context"]["local_catalog_hash"] == local_catalog_hash(catalog["rows"]) and re.fullmatch(r"[0-9a-f]{64}", out["context"]["local_catalog_hash"])
# ---------------------------------------------------------------- job integration (gates)
def _service():
from test_run_agent_service import _config, service
return service, _config
def _fiscal_for(db):
return assess(db)
def test_job_reports_ready_products_but_writes_nothing_with_default_switches(monkeypatch):
service, _config = _service()
db = FakeFirebird(PRODUCTS)
fiscal = _fiscal_for(db)
monkeypatch.setattr(service, "open_write_connection", lambda *a, **k: (_ for _ in ()).throw(AssertionError("no write connection may be opened")))
products = [{"CODIGO": c, "CCLASSTRIB": v["CCLASSTRIB"], "CLASSIFICACAO_FISCAL": v["CLASSIFICACAO_FISCAL"]} for c, v in db.products.items()]
for cfg_kwargs, auth in (({}, False), ({"firebird_writes": True}, False), ({"firebird_writes": True, "rtc_auto_apply_enabled": True}, False), ({"rtc_auto_apply_enabled": True}, True), ({"firebird_writes": True}, True)):
config = _config(**cfg_kwargs)
summary = service._rtc_auto_apply({}, config, {"job_id": 1, "authorizations": {"rtc_auto_apply": auth}}, products, fiscal, NORMAL)
assert summary["mode"] == "REPORT_ONLY" and summary["gate"]["allowed"] is False and summary["ready_total"] == 3 and summary["write_attempts"] == 0 and summary["applied"] == []
def test_job_applies_only_when_all_three_switches_are_open(monkeypatch):
service, _config = _service()
db = FakeFirebird(PRODUCTS)
fiscal = _fiscal_for(db)
monkeypatch.setattr(service, "open_write_connection", lambda env, path: _Closeable(db))
products = [{"CODIGO": c, "CCLASSTRIB": v["CCLASSTRIB"], "CLASSIFICACAO_FISCAL": v["CLASSIFICACAO_FISCAL"]} for c, v in db.products.items()]
config = _config(firebird_writes=True, rtc_auto_apply_enabled=True)
summary = service._rtc_auto_apply({}, config, {"job_id": 9, "authorizations": {"rtc_auto_apply": True}}, products, fiscal, NORMAL)
assert summary["mode"] == "AUTO_APPLY" and summary["write_successes"] == 3 and summary["allowed_fields"] == ["CCLASSTRIB"] and all(a["job_id"] == 9 for a in summary["applied"])
assert [db.products[c]["CCLASSTRIB"] for c in ("000001", "000002", "000003")] == ["200003", "000001", "200003"]
def test_job_never_writes_when_the_erp_says_rtc_is_off_even_with_all_switches_open(monkeypatch):
service, _config = _service()
db = FakeFirebird(PRODUCTS, param=(0,))
fiscal = _fiscal_for(db)
monkeypatch.setattr(service, "open_write_connection", lambda *a, **k: (_ for _ in ()).throw(AssertionError("no write connection may be opened")))
products = [{"CODIGO": c, "CCLASSTRIB": None, "CLASSIFICACAO_FISCAL": "39235000"} for c in db.products]
summary = service._rtc_auto_apply({}, _config(firebird_writes=True, rtc_auto_apply_enabled=True), {"job_id": 1, "authorizations": {"rtc_auto_apply": True}}, products, fiscal, NORMAL)
assert summary["write_attempts"] == 0 and summary["ready_total"] == 0
class _Closeable:
def __init__(self, db):
self._db = db
def __getattr__(self, name):
return getattr(self._db, name)
def close(self):
pass
+4 -4
View File
@@ -69,14 +69,14 @@ def test_rtc_off_in_the_erp_is_out_of_scope_and_never_blocks():
def test_rtc_on_in_the_erp_brings_cclasstrib_into_the_same_findings():
out = uf.assess_from_connection(FakeConn(param=1), {"crt": "1"}, today=date(2026, 9, 24))
assert out["context"]["rtc_enabled"] is True
assert kinds(out["findings"]["1"]) == ["CCLASSTRIB_MISSING"] and kinds(out["findings"]["2"]) == []
assert kinds(out["findings"]["3"]) == ["CCLASSTRIB_MISSING"] # one queue, one verdict per product
assert kinds(out["findings"]["1"]) == ["CCLASSTRIB_MISSING_UNRESOLVED"] and kinds(out["findings"]["2"]) == []
assert kinds(out["findings"]["3"]) == ["CCLASSTRIB_MISSING_UNRESOLVED"] # one queue, one verdict per product
def test_simulation_flag_never_touches_the_erp_parameter():
conn = FakeConn(param=0)
on = uf.assess_from_connection(conn, {"crt": "1"}, rtc_override=True)
assert on["context"]["rtc_enabled"] is True and kinds(on["findings"]["1"]) == ["CCLASSTRIB_MISSING"]
assert on["context"]["rtc_enabled"] is True and kinds(on["findings"]["1"]) == ["CCLASSTRIB_MISSING_UNRESOLVED"]
assert on["context"]["rtc_param"] == "0" and on["context"]["rtc_source"] == "SIMULATION" and not any("UPDATE" in s.upper() for s in conn.cur.statements)
@@ -124,7 +124,7 @@ def test_toggle_between_syncs_is_followed_in_both_directions_without_any_cache()
assert first["context"]["rtc_enabled"] is False and not [b for f in first["findings"].values() for b in f["blockers"] if b["category"] == "RTC"]
conn.cur.param = 1 # the operator switches the reform on in the ERP between two syncs
second = uf.assess_from_connection(conn, {"crt": "1"})
assert second["context"]["rtc_enabled"] is True and kinds(second["findings"]["1"]) == ["CCLASSTRIB_MISSING"]
assert second["context"]["rtc_enabled"] is True and kinds(second["findings"]["1"]) == ["CCLASSTRIB_MISSING_UNRESOLVED"]
assert second["findings"]["1"]["input_fingerprint"] != first["findings"]["1"]["input_fingerprint"] # a new blocker fingerprint: the old (RTC off) state is not reused
conn.cur.param = 0 # and off again
third = uf.assess_from_connection(conn, {"crt": "1"})
@@ -0,0 +1,42 @@
"""RTC auto-apply: per-installation authorization (default OFF) + immutable write audit.
Revision ID: 0027_rtc_auto_apply
Revises: 0026_central_processing_control
"""
from alembic import op
import sqlalchemy as sa
revision = "0027_rtc_auto_apply"
down_revision = "0026_central_processing_control"
branch_labels = None
depends_on = None
def upgrade():
op.add_column("installations", sa.Column("rtc_auto_apply_authorized", sa.Boolean(), nullable=False, server_default="false"))
op.create_table(
"rtc_write_audits",
sa.Column("id", sa.Integer(), primary_key=True),
sa.Column("tenant_id", sa.Integer(), sa.ForeignKey("tenants.id"), nullable=False, index=True),
sa.Column("installation_id", sa.Integer(), sa.ForeignKey("installations.id"), nullable=False, index=True),
sa.Column("agent_id", sa.Integer(), sa.ForeignKey("agents.id"), nullable=False, index=True),
sa.Column("job_id", sa.Integer(), nullable=True, index=True),
sa.Column("client", sa.String(255), nullable=True),
sa.Column("product_code", sa.String(64), nullable=False, index=True),
sa.Column("field", sa.String(32), nullable=False),
sa.Column("outcome", sa.String(16), nullable=False, index=True),
sa.Column("reason", sa.String(96), nullable=True),
sa.Column("old_value", sa.String(64), nullable=True),
sa.Column("new_value", sa.String(64), nullable=True),
sa.Column("rule_id", sa.String(32), nullable=True),
sa.Column("evidence", sa.JSON(), nullable=False, server_default="{}"),
sa.Column("official_source_version", sa.JSON(), nullable=False, server_default="{}"),
sa.Column("local_catalog_hash", sa.String(64), nullable=True),
sa.Column("written_at", sa.String(40), nullable=True),
sa.Column("recorded_at", sa.DateTime(timezone=True), server_default=sa.func.now(), index=True),
)
def downgrade():
op.drop_table("rtc_write_audits")
op.drop_column("installations", "rtc_auto_apply_authorized")
+6 -1
View File
@@ -11,6 +11,7 @@ from .classification import ENGINE_VERSION, Classifier
from .config import settings
from .datasets.service import DatasetService
from .jobs import SYNC_KINDS, claim_next_job, complete_job, create_job, fail_job, schedule_automatic_sync
from .rtc_audit import record_rtc_write_audits
from .blocked_products import list_blockers, resolve_blocker, resolve_product_blockers, resolve_stale_fiscal_blockers, upsert_blocker
from .context_keys import validate_entry
from .models import AgentSnapshot, BlockedProduct, FiscalContextEntry, ClassificationCache, ClassificationRequest, ClassificationResult, Dataset, Installation, Job
@@ -393,7 +394,9 @@ def router(session_factory, bootstrap_token):
record_event(session, tenant_id=agent.tenant_id, installation_id=installation.id, agent_id=agent.id, severity="INFO", event_type="JOB_STARTED", message=f"Job {job.kind} started", context={"kind": job.kind}, job_id=job.id)
session.commit()
logger.info("job claimed job_id=%s agent_id=%s kind=%s", job.id, agent.id, job.kind)
return _job_view(job)
view = _job_view(job)
view["authorizations"] = {"rtc_auto_apply": bool(installation is not None and installation.rtc_auto_apply_authorized)} # decided centrally, per installation, default OFF
return view
finally: session.close()
@routes.get("/jobs/global")
@@ -427,6 +430,8 @@ def router(session_factory, bootstrap_token):
upsert_blocker(session, agent.tenant_id, blocker, installation_id=installation.id if installation else None)
for product_code in result_summary.get("resolved_products", []):
resolve_product_blockers(session, agent.tenant_id, product_code, installation_id=installation.id if installation else None)
if installation is not None:
record_rtc_write_audits(session, tenant_id=agent.tenant_id, installation=installation, agent=agent, job_id=job_id, rtc_auto_apply=result_summary.get("rtc_auto_apply"))
if installation is not None and (result_summary.get("fiscal_context") or {}).get("evaluated_all"):
resolve_stale_fiscal_blockers(session, agent.tenant_id, installation.id, pass_started)
if not complete_job(session, job, result_summary): raise HTTPException(409, "job is not RUNNING")
+233
View File
@@ -0,0 +1,233 @@
"""Deterministic, auditable cClassTrib resolver (pure: no Firebird, no network, no probabilities).
resolve_cclasstrib(product, company, ctx, local_catalog) -> Resolution
Nothing here guesses. A class is proposed only when an EXPLICIT rule, backed by the official texts in `rtc_reference_data`
(cClassTrib table 2026-06-22 and LC 214/2025 compiled through LC 227/2026), proves it. Everything else is AMBIGUOUS or INSUFFICIENT_EVIDENCE.
Operation assumption (documented, product-level default): an ordinary domestic sale of the good. Operation-dependent classes (buyer is a public body,
exports, ZFM/ALC, credit presumed, ...) cannot be carried by a product default; when the annex offers such alternatives the answer is AMBIGUOUS.
Rules
RTC-R0 RTC disabled in the ERP -> NOT_APPLICABLE
RTC-R1 Simples family (CRT 1/2/4) -> INSUFFICIENT_EVIDENCE (NT 2025.002 v1.51: orientation for these taxpayers pending)
RTC-R2 no valid 8-digit NCM (decision engine did not confirm) -> INSUFFICIENT_EVIDENCE
RTC-A1 NCM listed in a REVOKED annex or an annex without a unique product-default class, or under a conditional item text -> AMBIGUOUS
RTC-A2 NCM listed in more than one annex -> AMBIGUOUS
RTC-A3 NCM in a chapter whose special regime depends on the nature/operation of the good, not on a code list -> AMBIGUOUS
RTC-R3 NCM listed in exactly one annex whose product-default class is unique, item text has no condition -> RESOLVED_WITH_EXPLICIT_RULE
RTC-R4 NCM listed in NO annex and in no nature-based chapter -> RESOLVED_WITH_EXPLICIT_RULE 000001 (integral taxation, LC 214 art. 4 general rule)
A proposed class must additionally be present in the local ERP catalog, MATCH the official table, be in force today, be usable in NF-e/NFC-e (the SGV's own
lookup filter) and carry a CST that exists; otherwise the answer degrades to INSUFFICIENT_EVIDENCE with the exact reason.
"""
from __future__ import annotations
import hashlib
import json
import re
from dataclasses import dataclass, field
from datetime import date
from . import rtc_reference_data as ref
SIMPLES_FAMILY = frozenset({"1", "2", "4"})
RESOLVED_STATUSES = ("RESOLVED_DETERMINISTIC", "RESOLVED_WITH_EXPLICIT_RULE")
STATUSES = RESOLVED_STATUSES + ("AMBIGUOUS", "INSUFFICIENT_EVIDENCE", "NOT_APPLICABLE")
# Goods annexes whose product-default class is unique in the official table (verified by `verify_annex_class_map`).
ANNEX_DEFAULT_CLASS = {"I": "200003", "VII": "200034", "VIII": "200035", "XV": "200014"} # IX is out: 515001 (deferral with the same inputs) is a sibling class
# Chapters where a reduced/special regime is defined by the NATURE of the good (not by a code list): in natura agriculture (art. 137), medicines (arts. 133/146),
# fuels and biofuels (art. 172 ff), medical/accessibility devices and vehicles (arts. 131-132, 143-145, 149), books/periodicals (constitutional immunity),
# gold and other special-regime goods kept out of the residual rule on purpose (conservative).
SENSITIVE_CHAPTERS = frozenset([f"{n:02d}" for n in range(1, 15)] + ["27", "30", "49", "71", "87", "88", "89", "90"]) # 71 gold (financial-asset regime), 88/89 aircraft and vessels
SPECIAL_PREFIXES = ("9619", "2207", "3826", "8703") # art. 147 (menstrual health), biofuel inputs, art. 149 (PcD cars)
@dataclass
class Resolution:
status: str
rule_id: str
reason: str
resolved_cclasstrib: str | None = None
resolved_cst_ibscbs: str | None = None
confidence_type: str = "NONE"
inputs_used: dict = field(default_factory=dict)
evidence: list = field(default_factory=list)
source_references: list = field(default_factory=list)
@property
def auto_apply_candidate(self) -> bool:
return self.status in RESOLVED_STATUSES and bool(self.resolved_cclasstrib)
def as_dict(self) -> dict:
return {"status": self.status, "rule_id": self.rule_id, "reason": self.reason, "resolved_cclasstrib": self.resolved_cclasstrib, "resolved_cst_ibscbs": self.resolved_cst_ibscbs,
"confidence_type": self.confidence_type, "inputs_used": self.inputs_used, "evidence": self.evidence, "source_references": self.source_references,
"auto_apply_candidate": self.auto_apply_candidate}
def _digits(value) -> str:
return re.sub(r"\D", "", "" if value is None else str(value))
def _official_by_code() -> dict:
return {row["code"]: row for row in ref.OFFICIAL_CCLASSTRIB}
def _num(value):
try:
return float(str(value).replace(",", "."))
except (TypeError, ValueError):
return None
# ------------------------------------------------------------------------------------------------- catalog comparison
def compare_catalogs(local_rows: dict, today: date | None = None) -> dict:
"""local_rows: {code: {cst, tipo_aliquota, p_red_ibs, p_red_cbs, ind_nfe, ind_nfce, start, end}} read from the ERP. Never writes anywhere.
Per code: MATCH | LOCAL_ONLY | OFFICIAL_ONLY | MISMATCH | EXPIRED (official table is the reference)."""
today = today or date.today()
official = _official_by_code()
result, details = {}, {}
for code in sorted(set(local_rows) | set(official)):
loc, off = local_rows.get(code), official.get(code)
if off is None:
result[code] = "LOCAL_ONLY"
elif off.get("end") and date.fromisoformat(off["end"]) < today:
result[code] = "EXPIRED"
details[code] = ["OFFICIAL_END_PASSED"] + (["PRESENT_LOCALLY"] if loc is not None else [])
elif loc is None:
result[code] = "OFFICIAL_ONLY"
else:
problems = []
if str(loc.get("cst") or "").strip() != off["cst"]:
problems.append("CST")
if "p_red_ibs" in loc and "p_red_cbs" in loc and (_num(loc.get("p_red_ibs")) != _num(off["p_red_ibs"]) or _num(loc.get("p_red_cbs")) != _num(off["p_red_cbs"])):
problems.append("REDUCTION") # only attributes the local table actually carries are compared
if "ind_nfe" in loc and "ind_nfce" in loc and (bool(loc.get("ind_nfe")) != off["ind_nfe"] or bool(loc.get("ind_nfce")) != off["ind_nfce"]):
problems.append("DOCUMENT_FLAGS")
result[code] = "MISMATCH" if problems else "MATCH"
if problems:
details[code] = problems
counts = {k: sum(1 for v in result.values() if v == k) for k in ("MATCH", "LOCAL_ONLY", "OFFICIAL_ONLY", "MISMATCH", "EXPIRED")}
material = counts["MISMATCH"] + counts["LOCAL_ONLY"] # a local class the official table contradicts or does not know
expired_present = sum(1 for c, d in details.items() if "PRESENT_LOCALLY" in d)
compat = "FULL" if counts["OFFICIAL_ONLY"] == 0 and expired_present == 0 and material == 0 else ("PARTIAL_LOCAL_OUTDATED" if material == 0 else "MATERIAL_MISMATCH")
return {"per_code": result, "details": details, "counts": counts, "expired_present_locally": expired_present, "compatibility": compat,
"official_catalog": {"sha256": ref.OFFICIAL_CATALOG["sha256"], "publication": ref.OFFICIAL_CATALOG["publication"], "file": ref.OFFICIAL_CATALOG["file"]}}
def local_catalog_hash(local_rows: dict) -> str:
canon = json.dumps({c: {k: (None if v is None else str(v)) for k, v in sorted(r.items())} for c, r in sorted(local_rows.items())}, sort_keys=True)
return hashlib.sha256(canon.encode()).hexdigest()
def verify_annex_class_map() -> dict:
"""Self-check of ANNEX_DEFAULT_CLASS against the official table: the class must exist, be a goods class in force, and be the ONLY official class for that annex
usable in NF-e/NFC-e as the ordinary-sale default. Returns {annex: [problems]} (empty lists = verified)."""
official = _official_by_code()
numbers = {"I": "1", "VII": "7", "VIII": "8", "XV": "15"}
out = {}
for annex, code in ANNEX_DEFAULT_CLASS.items():
row, problems = official.get(code), []
if row is None:
problems.append("CLASS_NOT_IN_OFFICIAL_TABLE")
else:
if str(row.get("anexo")) != numbers[annex]:
problems.append("ANNEX_FIELD_DIFFERS")
if not (row["ind_nfe"] or row["ind_nfce"]):
problems.append("NOT_USABLE_IN_NFE_NFCE")
siblings = [r["code"] for r in ref.OFFICIAL_CCLASSTRIB if str(r.get("anexo")) == numbers[annex] and r["code"] != code and (r["ind_nfe"] or r["ind_nfce"])]
if siblings:
problems.append("SIBLING_CLASSES:" + ",".join(siblings))
out[annex] = problems
return out
# ------------------------------------------------------------------------------------------------- annex lookup
def annex_matches(ncm: str) -> list[dict]:
"""Every annex item (any annex, revoked included) whose code list covers the NCM, with the item text and whether it is a clean (condition-free) item."""
hits = []
for roman, annex in ref.LC214_ANNEXES.items():
for item in annex["items"]:
for prefix in item["prefixes"]:
if ncm.startswith(prefix):
hits.append({"annex": roman, "item": item["item"], "prefix": prefix, "clean": item["clean"], "revoked": annex["revoked"], "text": item["description"][:160]})
break
return hits
def _references(extra=None) -> list:
refs = [{"document": ref.LC214_SOURCE["document"], "url": ref.LC214_SOURCE["url"], "sha256": ref.LC214_SOURCE["sha256"]},
{"document": "cClassTrib (Portal Nacional NF-e)", "publication": ref.OFFICIAL_CATALOG["publication"], "sha256": ref.OFFICIAL_CATALOG["sha256"]}]
return refs + (extra or [])
def _validate_class(code: str, local_rows: dict, compat: dict, today: date):
"""None when the class may be proposed, else the exact reason it may not."""
official = _official_by_code().get(code)
if official is None:
return "CLASS_NOT_IN_OFFICIAL_TABLE"
loc = local_rows.get(code)
if loc is None:
return "CLASS_NOT_IN_LOCAL_CATALOG"
state = compat["per_code"].get(code)
if state != "MATCH":
return f"LOCAL_OFFICIAL_{state}"
if official["start"] and date.fromisoformat(official["start"]) > today:
return "CLASS_NOT_YET_IN_FORCE"
if not (official["ind_nfe"] or official["ind_nfce"]):
return "CLASS_NOT_USABLE_IN_NFE_NFCE"
return None
# ------------------------------------------------------------------------------------------------- resolver
def resolve_cclasstrib(product: dict, company: dict | None, ctx: dict, local_rows: dict | None, *, compat: dict | None = None, today: date | None = None) -> Resolution:
"""product: {"CODIGO","CLASSIFICACAO_FISCAL","ncm_valid"(bool)}; company: {"crt"}; ctx: {"rtc_enabled": bool, "local_catalog_valid": bool}."""
today = today or date.today()
ncm = _digits(product.get("CLASSIFICACAO_FISCAL"))
crt = str((company or {}).get("crt") or "").strip()
used = {"ncm": ncm or None, "crt": crt or None, "rtc_enabled": bool(ctx.get("rtc_enabled"))}
if not ctx.get("rtc_enabled"):
return Resolution("NOT_APPLICABLE", "RTC-R0", "RTC_DISABLED_IN_ERP", inputs_used=used)
if not ctx.get("local_catalog_valid"):
return Resolution("INSUFFICIENT_EVIDENCE", "RTC-G1", "LOCAL_RTC_CATALOG_INVALID", inputs_used=used)
if crt in SIMPLES_FAMILY:
return Resolution("INSUFFICIENT_EVIDENCE", "RTC-R1", "SIMPLES_ORIENTATION_PENDING_OFFICIAL_NT", inputs_used=used,
source_references=_references([{"document": "NT 2025.002-RTC v1.51 (Portal NF-e, 04/08/2026)", "note": "IBS/CBS orientations for CRT 1/2/4 pending; taxation from 2027 (LC 214 art. 348)"}]))
if not crt:
return Resolution("INSUFFICIENT_EVIDENCE", "RTC-R1", "COMPANY_REGIME_UNKNOWN", inputs_used=used)
if len(ncm) != 8 or product.get("ncm_valid") is not True:
return Resolution("INSUFFICIENT_EVIDENCE", "RTC-R2", "NO_VALID_NCM", inputs_used=used)
local_rows = local_rows or {}
compat = compat or compare_catalogs(local_rows, today)
hits = annex_matches(ncm)
annexes = sorted({h["annex"] for h in hits})
if hits:
if any(h["revoked"] for h in hits) and len(annexes) == 1:
return Resolution("AMBIGUOUS", "RTC-A1", "LISTED_ONLY_IN_REVOKED_ANNEX", inputs_used=used, evidence=hits, source_references=_references())
live = [h for h in hits if not h["revoked"]]
live_annexes = sorted({h["annex"] for h in live})
if len(live_annexes) > 1 or (len(live_annexes) == 1 and len(annexes) > 1):
return Resolution("AMBIGUOUS", "RTC-A2", "LISTED_IN_MORE_THAN_ONE_ANNEX", inputs_used=used, evidence=hits, source_references=_references())
annex = live_annexes[0] if live_annexes else annexes[0]
clean = [h for h in live if h["clean"]]
cls = ANNEX_DEFAULT_CLASS.get(annex)
if not cls or not clean or len(clean) != len(live):
return Resolution("AMBIGUOUS", "RTC-A1", f"ANNEX_{annex}_NEEDS_OPERATION_OR_CONDITION", inputs_used=used, evidence=hits, source_references=_references())
problem = _validate_class(cls, local_rows, compat, today)
if problem:
return Resolution("INSUFFICIENT_EVIDENCE", "RTC-G2", problem, inputs_used=used, evidence=hits, source_references=_references())
official = _official_by_code()[cls]
return Resolution("RESOLVED_WITH_EXPLICIT_RULE", "RTC-R3", f"ANNEX_{annex}_CLEAN_ITEM_UNIQUE_DEFAULT_CLASS", cls, official["cst"], "ANNEX_TEXT_MATCH", used, hits, _references())
if ncm[:2] in SENSITIVE_CHAPTERS or ncm.startswith(SPECIAL_PREFIXES):
return Resolution("AMBIGUOUS", "RTC-A3", "SPECIAL_REGIME_DEPENDS_ON_NATURE_OR_OPERATION_OF_THE_GOOD", inputs_used=used, evidence=[{"chapter": ncm[:2]}], source_references=_references())
problem = _validate_class("000001", local_rows, compat, today)
if problem:
return Resolution("INSUFFICIENT_EVIDENCE", "RTC-G2", problem, inputs_used=used, source_references=_references())
return Resolution("RESOLVED_WITH_EXPLICIT_RULE", "RTC-R4", "NCM_IN_NO_ANNEX_AND_NO_NATURE_BASED_CHAPTER", "000001", "000", "EXCLUSION_OF_ALL_SPECIAL_REGIMES", used,
[{"annex_hits": 0, "chapter": ncm[:2]}], _references())
+1 -1
View File
@@ -15,7 +15,7 @@ EVENT_TYPES = {
"AGENT_STARTED", "AGENT_STOPPED", "HEARTBEAT", "PREFLIGHT_PASS", "PREFLIGHT_FAIL", "JOB_CLAIMED", "JOB_STARTED",
"JOB_COMPLETED", "JOB_FAILED", "PRODUCT_BLOCKED", "BLOCKER_RESOLVED", "DATASET_CHANGED", "COMPANY_CONTEXT_CHANGED",
"SCHEMA_CHANGED", "WRITE_PLANNED", "WRITE_COMPLETED", "WRITE_FAILED", "CONNECTION_ERROR", "RETRY", "VERSION_CHANGED",
"WINDOWS_INSTALL_COMPLETED", "WINDOWS_UNINSTALL_COMPLETED", "ADMIN_ACTION",
"WINDOWS_INSTALL_COMPLETED", "WINDOWS_UNINSTALL_COMPLETED", "ADMIN_ACTION", "RTC_AUTO_APPLY",
}
SEVERITIES = {"INFO", "WARNING", "ERROR", "CRITICAL"}
SECRET_RE = re.compile(r"(?i)(password|fb_password|token|api[_-]?key|secret|authorization|connection[_-]?string)\s*[:=]\s*[^,;\s]+")
+25
View File
@@ -282,6 +282,8 @@ class Installation(Base):
sync_interval_seconds: Mapped[int] = mapped_column(Integer, default=1800, server_default="1800")
last_sync_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True)
last_sync_changed: Mapped[int | None] = mapped_column(Integer, nullable=True)
# RTC auto-apply is authorized centrally per installation and is OFF until an operator explicitly turns it on (the Agent has its own second switch).
rtc_auto_apply_authorized: Mapped[bool] = mapped_column(Boolean, default=False, server_default="false")
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now())
updated_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now(), onupdate=func.now())
@@ -731,3 +733,26 @@ class ExternalProductCache(Base):
normalized_data: Mapped[dict] = mapped_column(JSON, default=dict)
cache_allowed: Mapped[bool] = mapped_column(Boolean, default=False)
redistribution_allowed: Mapped[bool] = mapped_column(Boolean, default=False)
class RtcWriteAudit(Base):
"""Append-only record of every attempted C000025.CCLASSTRIB write (applied or aborted). Holds the old value, so each change is revertible by hand."""
__tablename__ = "rtc_write_audits"
id: Mapped[int] = mapped_column(primary_key=True)
tenant_id: Mapped[int] = mapped_column(ForeignKey("tenants.id"), index=True)
installation_id: Mapped[int] = mapped_column(ForeignKey("installations.id"), index=True)
agent_id: Mapped[int] = mapped_column(ForeignKey("agents.id"), index=True)
job_id: Mapped[int | None] = mapped_column(Integer, nullable=True, index=True)
client: Mapped[str | None] = mapped_column(String(255), nullable=True)
product_code: Mapped[str] = mapped_column(String(64), index=True)
field: Mapped[str] = mapped_column(String(32))
outcome: Mapped[str] = mapped_column(String(16), index=True)
reason: Mapped[str | None] = mapped_column(String(96), nullable=True)
old_value: Mapped[str | None] = mapped_column(String(64), nullable=True)
new_value: Mapped[str | None] = mapped_column(String(64), nullable=True)
rule_id: Mapped[str | None] = mapped_column(String(32), nullable=True)
evidence: Mapped[dict] = mapped_column(JSON, default=dict)
official_source_version: Mapped[dict] = mapped_column(JSON, default=dict)
local_catalog_hash: Mapped[str | None] = mapped_column(String(64), nullable=True)
written_at: Mapped[str | None] = mapped_column(String(40), nullable=True)
recorded_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now(), index=True)
+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")}
+36
View File
@@ -0,0 +1,36 @@
"""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
+90
View File
@@ -0,0 +1,90 @@
"""Turns the deterministic cClassTrib resolution into the ONE root-cause RTC finding per product (pure).
`evaluate_product` (unified_fiscal_coverage) says WHETHER a product violates the RTC rule; this module says WHY and whether the gap is closable:
the single RTC blocker of a product is refined to the cause that matters, never several blockers for the same root cause.
"""
from __future__ import annotations
from .cclasstrib_resolver import Resolution
RTC_CATALOG_REASONS = ("LOCAL_OFFICIAL_", "CLASS_NOT_IN_LOCAL_CATALOG", "CLASS_NOT_IN_OFFICIAL_TABLE", "CLASS_NOT_YET_IN_FORCE", "CLASS_NOT_USABLE_IN_NFE_NFCE", "LOCAL_RTC_CATALOG_INVALID")
EVIDENCE_REASONS = ("NO_VALID_NCM", "COMPANY_REGIME_UNKNOWN")
RTC_TYPES = ("CCLASSTRIB_MISSING", "CCLASSTRIB_MISSING_UNRESOLVED", "CCLASSTRIB_AMBIGUOUS", "CCLASSTRIB_CONFLICT", "CCLASSTRIB_OFFICIAL_MISMATCH", "RTC_LOCAL_CATALOG_OUTDATED",
"RTC_RULE_EVIDENCE_MISSING", "CCLASSTRIB_INVALID_FORMAT", "CCLASSTRIB_NOT_IN_CATALOG", "RTC_CST_MISSING", "RTC_CATALOG_INVALID")
def _text(value) -> str:
return "" if value is None else str(value).strip()
def slim(resolution: Resolution) -> dict:
"""Small, audit-grade view kept in the job result (full evidence stays in the resolver output)."""
return {"status": resolution.status, "rule_id": resolution.rule_id, "reason": resolution.reason, "resolved_cclasstrib": resolution.resolved_cclasstrib,
"resolved_cst_ibscbs": resolution.resolved_cst_ibscbs, "confidence_type": resolution.confidence_type, "auto_apply_candidate": resolution.auto_apply_candidate}
def refine_rtc_finding(found: dict, product: dict, resolution: Resolution | None, compat_state: dict | None) -> dict:
"""Mutates and returns `found` (an `evaluate_product` result). Only acts when RTC is REQUIRED for the product."""
if found.get("applicability", {}).get("RTC") != "REQUIRED" or resolution is None:
return found
value = _text(product.get("CCLASSTRIB"))
rtc_blockers = [b for b in found["blockers"] if b["category"] == "RTC"]
kinds = {b["type"] for b in rtc_blockers}
found["rtc_resolution"] = slim(resolution)
if "RTC_CATALOG_INVALID" in kinds:
return found # nothing can be proposed or compared against an invalid catalog
others = [b for b in found["blockers"] if b["category"] != "RTC"]
if "CCLASSTRIB_MISSING" in kinds:
if resolution.auto_apply_candidate:
new = ("CCLASSTRIB_MISSING", f"READY_TO_APPLY:{resolution.resolved_cclasstrib}:{resolution.rule_id}")
elif resolution.status == "AMBIGUOUS":
new = ("CCLASSTRIB_AMBIGUOUS", f"{resolution.rule_id}:{resolution.reason}")
elif resolution.reason.startswith(RTC_CATALOG_REASONS):
new = ("RTC_LOCAL_CATALOG_OUTDATED", resolution.reason)
elif resolution.reason in EVIDENCE_REASONS:
new = ("RTC_RULE_EVIDENCE_MISSING", resolution.reason)
else:
new = ("CCLASSTRIB_MISSING_UNRESOLVED", f"{resolution.rule_id}:{resolution.reason}")
found["blockers"] = others + [{"type": new[0], "category": "RTC", "reason": new[1]}]
return found
if not rtc_blockers and value: # a valid existing class: compare, never overwrite
state = (compat_state or {}).get(value)
if state in ("MISMATCH", "EXPIRED", "LOCAL_ONLY"):
found["blockers"] = others + [{"type": "CCLASSTRIB_OFFICIAL_MISMATCH", "category": "RTC", "reason": f"LOCAL_VS_OFFICIAL:{state}"}]
elif resolution.auto_apply_candidate and resolution.resolved_cclasstrib != value:
found["blockers"] = others + [{"type": "CCLASSTRIB_CONFLICT", "category": "RTC", "reason": f"HAS:{value};RULE:{resolution.rule_id}:{resolution.resolved_cclasstrib}"}]
else:
found["rtc_resolution"]["no_write_required"] = resolution.auto_apply_candidate and resolution.resolved_cclasstrib == value
return found
def summarize_rtc(findings: dict, compat: dict | None = None) -> dict:
"""Counts for the panel and the audit: how many products already have a valid class, how many are ready to be filled, how many are blocked and why."""
out = {"total": 0, "valid_existing": 0, "ready_to_apply": 0, "ambiguous": 0, "insufficient": 0, "conflicts": 0, "official_mismatch": 0, "not_applicable": 0, "other_blocked": 0, "by_rule": {}, "by_reason": {}}
for found in findings.values():
out["total"] += 1
if found.get("applicability", {}).get("RTC") != "REQUIRED":
out["not_applicable"] += 1
continue
types = {b["type"] for b in found["blockers"] if b["category"] == "RTC"}
res = found.get("rtc_resolution") or {}
if res:
out["by_rule"][res["rule_id"]] = out["by_rule"].get(res["rule_id"], 0) + 1
out["by_reason"][res["reason"]] = out["by_reason"].get(res["reason"], 0) + 1
if not types:
out["valid_existing"] += 1
elif "CCLASSTRIB_CONFLICT" in types:
out["conflicts"] += 1
elif "CCLASSTRIB_OFFICIAL_MISMATCH" in types:
out["official_mismatch"] += 1
elif res.get("auto_apply_candidate") and "CCLASSTRIB_MISSING" in types:
out["ready_to_apply"] += 1
elif "CCLASSTRIB_AMBIGUOUS" in types:
out["ambiguous"] += 1
elif types & {"CCLASSTRIB_MISSING_UNRESOLVED", "RTC_RULE_EVIDENCE_MISSING", "RTC_LOCAL_CATALOG_OUTDATED", "CCLASSTRIB_MISSING"}:
out["insufficient"] += 1
else:
out["other_blocked"] += 1
if compat:
out["catalog_compatibility"] = {"compatibility": compat["compatibility"], "counts": compat["counts"], "expired_present_locally": compat.get("expired_present_locally", 0)}
return out
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -4,5 +4,5 @@ kept identical to this) and what an AgentRelease row's `version` column holds
once published — never a second, independent version number. Fiscal semantics
are versioned separately (ENGINE_VERSION/DECISION_VERSION in classification.py,
DECISION_ENGINE_VERSION etc.) and are NOT changed by a release bump."""
GTI_FISCAL_VERSION = "1.1.8"
GTI_FISCAL_VERSION = "1.1.9"
API_COMPAT_MAJOR_MINOR = "1.0" # an Agent 1.0.x/1.1.x talks to any Hub 1.0.x
+27 -1
View File
@@ -296,7 +296,7 @@ def router(session_factory):
if installation.processing_enabled and last_sync is not None:
next_sync = last_sync + timedelta(seconds=installation.sync_interval_seconds)
last_summary = session.scalar(select(Job.result_summary).where(Job.agent_id == agent.id, Job.status == "COMPLETED", Job.kind.in_(("INITIAL_SYNC", "SYNC_INCREMENTAL", "REPROCESS_ALL"))).order_by(Job.id.desc()).limit(1))
return {"rtc": rtc_status_view(last_summary), "enabled": bool(installation.processing_enabled), "interval_minutes": installation.sync_interval_seconds // 60,
return {"rtc": rtc_status_view(last_summary, bool(installation.rtc_auto_apply_authorized)), "rtc_authorized": bool(installation.rtc_auto_apply_authorized), "enabled": bool(installation.processing_enabled), "interval_minutes": installation.sync_interval_seconds // 60,
"last_sync": format_ui_datetime(last_sync) if last_sync else None, "next_sync": format_ui_datetime(next_sync) if next_sync else None,
"last_changed": installation.last_sync_changed, "last_job": {"kind": last_job.kind, "status": last_job.status, "id": last_job.id, "kind_label": job_kind_label(last_job.kind), "status_label": job_status_label(last_job.status)} if last_job else None}
@@ -319,6 +319,32 @@ def router(session_factory):
return RedirectResponse(f"/agents/{agent_id}?tab=jobs", status_code=303)
finally: session.close()
@routes.post("/agents/{agent_id}/rtc-auto-apply")
async def rtc_auto_apply_authorization(request: Request, agent_id: int):
"""Central authorization of the automatic cClassTrib fill (default OFF). Enabling requires an explicit typed confirmation; every change is audited."""
session, auth_data, redirect = page_auth(request)
if redirect: return redirect
try:
values = await form_values(request); web_session, admin = auth_data
require_csrf(web_session, values.get("csrf_token"))
agent = session.get(Agent, agent_id)
installation = session.scalar(select(Installation).where(Installation.agent_id == agent_id)) if agent else None
if not agent or not installation: return HTMLResponse("Agent not found", status_code=404)
decision = values.get("decision")
if decision == "authorize":
if (values.get("confirm") or "").strip().upper() != "PREENCHER":
return HTMLResponse("Digite PREENCHER para confirmar a autorização", status_code=422)
installation.rtc_auto_apply_authorized = True
elif decision == "revoke":
installation.rtc_auto_apply_authorized = False
else:
return HTMLResponse("decisão inválida", status_code=422)
record_audit(session, admin_user_id=admin.id, tenant_id=installation.tenant_id, action=f"RTC_AUTO_APPLY_{decision.upper()}", target_type="installation", target_id=installation.installation_id,
context={"authorized": installation.rtc_auto_apply_authorized}, severity="WARNING")
session.commit()
return RedirectResponse(f"/agents/{agent_id}", status_code=303)
finally: session.close()
@routes.post("/agents/{agent_id}/sync-now")
async def sync_now(request: Request, agent_id: int):
return await _installation_action(request, agent_id, "sync-now")
+1 -1
View File
@@ -1,6 +1,6 @@
[project]
name = "gti-fiscal-hub"
version = "1.1.8"
version = "1.1.9"
requires-python = ">=3.10"
dependencies = [
"fastapi>=0.115,<1",
+3 -1
View File
@@ -8,7 +8,9 @@
<form method="post" action="/agents/{{ detail.agent_id }}/sync-now" style="display:inline"><input type="hidden" name="csrf_token" value="{{ csrf_token }}"><button type="submit" class="primary-action" {% if not ops.enabled %}disabled title="Processamento pausado: retome para sincronizar"{% else %}title="Verifica apenas as alterações desde a última sincronização"{% endif %}>Sincronizar agora</button></form>
<a class="botao secondary" href="/agents/{{ detail.agent_id }}/reprocess-confirmation" title="Recalcula todos os produtos (pede confirmação)">Reprocessar tudo</a>
{% if ops.enabled %}<form method="post" action="/agents/{{ detail.agent_id }}/pause" style="display:inline"><input type="hidden" name="csrf_token" value="{{ csrf_token }}"><button type="submit" class="caution" title="Mantém o heartbeat, mas não executa jobs">Pausar processamento</button></form>{% else %}<form method="post" action="/agents/{{ detail.agent_id }}/resume" style="display:inline"><input type="hidden" name="csrf_token" value="{{ csrf_token }}"><button type="submit" class="primary-action" title="Volta a executar jobs">Retomar processamento</button></form>{% endif %}
</div></div><dl class="facts"><dt>Processamento</dt><dd><span class="badge {{ '' if ops.enabled else 'critical' }}">{{ "Ativo" if ops.enabled else "Pausado" }}</span> · sincronização automática a cada {{ ops.interval_minutes }} min</dd><dt>Última sincronização</dt><dd>{{ ops.last_sync or "ainda não sincronizado" }}{% if ops.last_changed is not none %} · {{ ops.last_changed }} produto(s) alterado(s){% endif %}</dd><dt>Sincronização agendada</dt><dd>{{ ops.next_sync or ("aguardando o primeiro sync" if ops.enabled else "pausado") }}</dd>{% if ops.rtc %}<dt>Reforma Tributária</dt><dd><span class="badge {{ "" if ops.rtc.enabled else "muted" }}" title="{{ ops.rtc.title }}">{{ ops.rtc.label }}</span></dd>{% endif %}<dt>Último job</dt><dd>{% if ops.last_job %}<a href="/jobs?scope=all&amp;job_id={{ ops.last_job.id }}">{{ ops.last_job.kind_label }}</a> · {{ ops.last_job.status_label }}{% else %}nenhum{% endif %}</dd></dl></section>
</div></div><dl class="facts"><dt>Processamento</dt><dd><span class="badge {{ '' if ops.enabled else 'critical' }}">{{ "Ativo" if ops.enabled else "Pausado" }}</span> · sincronização automática a cada {{ ops.interval_minutes }} min</dd><dt>Última sincronização</dt><dd>{{ ops.last_sync or "ainda não sincronizado" }}{% if ops.last_changed is not none %} · {{ ops.last_changed }} produto(s) alterado(s){% endif %}</dd><dt>Sincronização agendada</dt><dd>{{ ops.next_sync or ("aguardando o primeiro sync" if ops.enabled else "pausado") }}</dd>{% if ops.rtc %}<dt>Reforma Tributária</dt><dd><span class="badge {{ "" if ops.rtc.enabled else "muted" }}" title="{{ ops.rtc.title }}">{{ ops.rtc.label }}</span></dd>{% endif %}{% if ops.rtc and ops.rtc.enabled %}{% if ops.rtc.products %}<dt>Produtos RTC</dt><dd><b>{{ ops.rtc.products.headline }}</b><br><small>Total {{ ops.rtc.products.total }} · com cClassTrib válido {{ ops.rtc.products.valid }} · prontos para preenchimento {{ ops.rtc.products.ready }} · ambíguos/bloqueados {{ ops.rtc.products.blocked }}</small></dd>{% endif %}
<dt>Preenchimento automático</dt><dd>{{ ops.rtc.apply.state }}{% if ops.rtc.apply.applied_last_job %} · {{ ops.rtc.apply.applied_last_job }} preenchido(s) no último job{% endif %}
<form method="post" action="/agents/{{ detail.agent_id }}/rtc-auto-apply" style="display:inline"><input type="hidden" name="csrf_token" value="{{ csrf_token }}">{% if ops.rtc_authorized %}<input type="hidden" name="decision" value="revoke"><button type="submit" class="caution">Revogar autorização</button>{% else %}<input type="hidden" name="decision" value="authorize"><input type="text" name="confirm" placeholder="digite PREENCHER" aria-label="Confirmar autorização" size="14"><button type="submit" class="secondary" title="Autoriza o Hub; o Agent ainda precisa ter a escrita e a chave RTC_AUTO_APPLY_ENABLED ligadas">Autorizar</button>{% endif %}</form></dd>{% endif %}<dt>Último job</dt><dd>{% if ops.last_job %}<a href="/jobs?scope=all&amp;job_id={{ ops.last_job.id }}">{{ ops.last_job.kind_label }}</a> · {{ ops.last_job.status_label }}{% else %}nenhum{% endif %}</dd></dl></section>
<nav class="tabs" aria-label="Seções da instalação">{% for name,label in [("overview","Resumo"),("fiscal","Fiscal"),("blockers","Blockers"),("jobs","Jobs"),("events","Eventos")] %}<a class="{% if tab==name %}active{% endif %}" href="?tab={{ name }}">{{ label }}</a>{% endfor %}</nav>
<section class="split"><article class="panel"><div class="panel-title"><h2>Contexto da empresa</h2></div><dl class="facts"><dt>Cliente</dt><dd>{{ detail.client or "Não informado" }}</dd><dt>Nome de exibição</dt><dd>{{ detail.display_name or "Não informado" }}</dd><dt>Hostname</dt><dd>{{ detail.hostname or "Não informado" }}</dd><dt>Empresa</dt><dd>{{ detail.company_name or detail.company_code or "Não informado" }}</dd><dt>UF</dt><dd>{{ detail.company_uf or "Não informado" }}</dd><dt>CRT</dt><dd>{{ detail.company_crt or "Não informado" }}</dd><dt>Versão do agent</dt><dd>{{ detail.agent_version_display }}</dd><dt>Último heartbeat</dt><dd title="{{ detail.last_seen_at }}">{{ detail.last_seen_display }} · {{ detail.last_seen_age }}</dd><dt>Firebird</dt><dd>{{ detail.firebird_version or "Não informado" }} · ODS {{ detail.firebird_ods or "Não informado" }}</dd><dt>IDs técnicos</dt><dd>Agent {{ detail.technical_ids.agent_id }} · instalação {{ detail.technical_ids.installation_db_id }}</dd></dl></article>
<article class="panel"><div class="panel-title"><h2>Eventos recentes</h2><a href="#eventos">Ver eventos</a></div>{% for event in events[:8] %}<div class="event-row"><span class="event-time">{{ event.occurred_display }}</span><span><b>{{ event.event_type }}</b><small>{{ event.message }}{% if event.job_id %} · <a href="/jobs?job_id={{ event.job_id }}">Job {{ event.job_id }}</a>{% endif %}{% if event.product_code %} · <a href="/blockers?agent_id={{ detail.agent_id }}&amp;product_code={{ event.product_code|urlencode }}">Produto {{ event.product_code }}</a>{% endif %}</small></span><em class="severity {{ event.severity|lower }}">Severidade: {{ event.severity }}</em></div>{% else %}<p class="empty-state">Nenhum evento.</p>{% endfor %}</article></section>
+129
View File
@@ -0,0 +1,129 @@
from datetime import date
from central.app import rtc_reference_data as ref
from central.app.cclasstrib_resolver import (ANNEX_DEFAULT_CLASS, annex_matches, compare_catalogs, local_catalog_hash, resolve_cclasstrib, verify_annex_class_map)
TODAY = date(2026, 9, 24)
NORMAL, SIMPLES = {"crt": "3"}, {"crt": "1"}
ON = {"rtc_enabled": True, "local_catalog_valid": True}
def local_from_official(skip=(), tweak=None):
rows = {}
for r in ref.OFFICIAL_CCLASSTRIB:
if r["code"] in skip:
continue
rows[r["code"]] = {"cst": r["cst"], "tipo_aliquota": r["tipo_aliquota"], "p_red_ibs": r["p_red_ibs"], "p_red_cbs": r["p_red_cbs"], "ind_nfe": r["ind_nfe"], "ind_nfce": r["ind_nfce"], "start": r["start"], "end": r["end"]}
for code, change in (tweak or {}).items():
rows[code] = {**rows[code], **change}
return rows
def product(ncm, valid=True, code="1"):
return {"CODIGO": code, "CLASSIFICACAO_FISCAL": ncm, "ncm_valid": valid}
def resolve(ncm, company=NORMAL, ctx=ON, local=None, valid=True):
return resolve_cclasstrib(product(ncm, valid), company, ctx, local if local is not None else local_from_official(), today=TODAY)
def test_annex_default_class_map_is_verified_against_the_official_table():
assert verify_annex_class_map() == {annex: [] for annex in ANNEX_DEFAULT_CLASS}
def test_reference_data_carries_provenance_and_the_expected_shape():
assert len(ref.OFFICIAL_CCLASSTRIB) == 164 and ref.OFFICIAL_CATALOG["sha256"] and ref.LC214_SOURCE["sha256"] and ref.LC214_SOURCE["accessed_at"]
assert {"I", "VII", "VIII", "IX", "XV"} <= set(ref.LC214_ANNEXES) and ref.LC214_ANNEXES["XIV"]["revoked"] is True
assert len(ref.LC214_ANNEXES["I"]["items"]) == 26 and len(ref.LC214_ANNEXES["XV"]["items"]) == 6
def test_rtc_off_is_not_applicable_and_never_a_candidate():
r = resolve("10063021", ctx={"rtc_enabled": False, "local_catalog_valid": True})
assert r.status == "NOT_APPLICABLE" and r.rule_id == "RTC-R0" and not r.auto_apply_candidate
def test_simples_is_insufficient_until_the_official_orientation_exists():
r = resolve("10063021", company=SIMPLES)
assert r.status == "INSUFFICIENT_EVIDENCE" and r.rule_id == "RTC-R1" and not r.auto_apply_candidate and r.source_references[-1]["document"].startswith("NT 2025.002")
assert resolve("10063021", company={"crt": None}).reason == "COMPANY_REGIME_UNKNOWN"
def test_invalid_or_unconfirmed_ncm_is_insufficient():
assert resolve("1234", valid=True).reason == "NO_VALID_NCM"
assert resolve("10063021", valid=False).reason == "NO_VALID_NCM"
def test_clean_annex_item_resolves_to_the_annex_default_class_with_evidence():
r = resolve("10063021") # rice: Anexo I item 1 (subposicao 1006.30)
assert r.status == "RESOLVED_WITH_EXPLICIT_RULE" and r.resolved_cclasstrib == "200003" and r.resolved_cst_ibscbs == "200" and r.rule_id == "RTC-R3" and r.auto_apply_candidate
assert r.evidence[0]["annex"] == "I" and r.evidence[0]["clean"] is True and r.confidence_type == "ANNEX_TEXT_MATCH"
assert r.source_references[0]["sha256"] == ref.LC214_SOURCE["sha256"] and r.inputs_used["ncm"] == "10063021"
def test_conditional_annex_item_is_ambiguous_not_guessed():
milk = next(i for i in ref.LC214_ANNEXES["I"]["items"] if not i["clean"] and i["prefixes"])
ncm = milk["prefixes"][0].ljust(8, "0")
r = resolve(ncm)
assert r.status == "AMBIGUOUS" and r.rule_id == "RTC-A1" and not r.auto_apply_candidate and r.evidence[0]["clean"] is False
def test_annex_without_a_unique_default_class_is_ambiguous():
item = next(i for i in ref.LC214_ANNEXES["IV"]["items"] if i["clean"]) # medical devices: 60% class vs zero-rated public-buyer class
r = resolve(item["prefixes"][0].ljust(8, "1"))
assert r.status == "AMBIGUOUS" and "NEEDS_OPERATION_OR_CONDITION" in r.reason
def test_revoked_annex_only_is_ambiguous():
prefix = next(p for i in ref.LC214_ANNEXES["XIV"]["items"] for p in i["prefixes"] if len(p) == 8 and not annex_matches_other_than_xiv(p))
assert resolve(prefix).rule_id == "RTC-A1" and resolve(prefix).reason == "LISTED_ONLY_IN_REVOKED_ANNEX"
def annex_matches_other_than_xiv(prefix):
return [h for h in annex_matches(prefix) if h["annex"] != "XIV"]
def test_nature_based_chapters_are_ambiguous():
for ncm in ("03028990", "30049099", "27101259", "49019900", "87032100", "90183100"):
r = resolve(ncm)
assert r.status == "AMBIGUOUS" and r.rule_id in ("RTC-A3", "RTC-A1", "RTC-A2"), ncm
def test_residual_ncm_outside_every_annex_and_sensitive_chapter_is_integral_taxation():
r = resolve("39235000") # plastic closures: no annex, no nature-based regime
assert r.status == "RESOLVED_WITH_EXPLICIT_RULE" and r.resolved_cclasstrib == "000001" and r.resolved_cst_ibscbs == "000" and r.rule_id == "RTC-R4" and r.confidence_type == "EXCLUSION_OF_ALL_SPECIAL_REGIMES"
assert r.auto_apply_candidate and annex_matches("39235000") == []
def test_a_class_missing_or_different_in_the_local_catalog_is_never_proposed():
r = resolve("39235000", local=local_from_official(skip=("000001",)))
assert r.status == "INSUFFICIENT_EVIDENCE" and r.reason == "CLASS_NOT_IN_LOCAL_CATALOG" and not r.auto_apply_candidate
r2 = resolve("39235000", local=local_from_official(tweak={"000001": {"cst": "010"}}))
assert r2.reason == "LOCAL_OFFICIAL_MISMATCH" and not r2.auto_apply_candidate
r3 = resolve("10063021", local=local_from_official(tweak={"200003": {"ind_nfe": False, "ind_nfce": False}}))
assert r3.reason == "LOCAL_OFFICIAL_MISMATCH"
def test_invalid_local_catalog_blocks_every_proposal():
assert resolve("39235000", ctx={"rtc_enabled": True, "local_catalog_valid": False}).reason == "LOCAL_RTC_CATALOG_INVALID"
def test_catalog_comparison_classifies_every_code_and_flags_material_problems():
full = compare_catalogs(local_from_official(), TODAY)
assert full["counts"]["MATCH"] == 161 and full["counts"]["EXPIRED"] == 3 and full["compatibility"] == "PARTIAL_LOCAL_OUTDATED" # 220001-3 ended on 2026-01-01
assert compare_catalogs(local_from_official(skip=("220001", "220002", "220003")), TODAY)["compatibility"] == "FULL"
outdated = compare_catalogs(local_from_official(skip=("000005", "200053")), TODAY)
assert outdated["counts"]["OFFICIAL_ONLY"] == 2 and outdated["compatibility"] == "PARTIAL_LOCAL_OUTDATED" and outdated["counts"]["MISMATCH"] == 0
local = local_from_official(tweak={"200003": {"p_red_ibs": "60"}})
local["999999"] = {"cst": "000", "p_red_ibs": "0", "p_red_cbs": "0", "ind_nfe": True, "ind_nfce": True}
bad = compare_catalogs(local, TODAY)
assert bad["per_code"]["200003"] == "MISMATCH" and bad["per_code"]["999999"] == "LOCAL_ONLY" and bad["compatibility"] == "MATERIAL_MISMATCH"
def test_local_catalog_hash_is_stable_and_sensitive():
a, b = local_from_official(), local_from_official(tweak={"200003": {"p_red_ibs": "60"}})
assert local_catalog_hash(a) == local_catalog_hash(local_from_official()) and local_catalog_hash(a) != local_catalog_hash(b)
def test_resolution_is_deterministic_and_auditable():
a, b = resolve("10063021"), resolve("10063021")
assert a.as_dict() == b.as_dict() and {"rule_id", "inputs_used", "source_references", "evidence", "reason"} <= set(a.as_dict())
+42
View File
@@ -587,3 +587,45 @@ def test_rtc_status_shows_where_and_when_the_agent_observed_it():
assert "linhas de PARAMETROS divergentes" in conflict and ">Desativada<" in conflict
for legacy in ("Escopo anterior", "Escopo ampliado", "Legacy coverage", "Expanded coverage"):
assert legacy not in conflict
# ---------------------------------------------------------------- RTC auto-apply authorization (central, default OFF)
def _installation(client, agent_id):
from central.app.models import Installation as Inst
with client._session_factory() as session:
row = session.scalar(select(Inst).where(Inst.agent_id == agent_id))
return bool(row.rtc_auto_apply_authorized)
def test_rtc_auto_apply_authorization_is_off_by_default_and_needs_csrf_and_a_typed_confirmation():
client, agent_id, _ = _panel_client()
assert _installation(client, agent_id) is False
csrf = client.cookies.get("gti_csrf")
url = f"/agents/{agent_id}/rtc-auto-apply"
assert client.post(url, data={"csrf_token": "wrong", "decision": "authorize", "confirm": "PREENCHER"}).status_code == 403
assert client.post(url, data={"csrf_token": csrf, "decision": "authorize"}).status_code == 422
assert client.post(url, data={"csrf_token": csrf, "decision": "authorize", "confirm": "sim"}).status_code == 422
assert _installation(client, agent_id) is False
assert client.post(url, data={"csrf_token": csrf, "decision": "authorize", "confirm": "preencher"}, follow_redirects=False).status_code == 303
assert _installation(client, agent_id) is True
assert client.post(url, data={"csrf_token": csrf, "decision": "revoke"}, follow_redirects=False).status_code == 303
assert _installation(client, agent_id) is False
assert client.post(url, data={"csrf_token": csrf, "decision": "other"}).status_code == 422
from central.app.models import AuditEvent
with client._session_factory() as session:
actions = [a.action for a in session.scalars(select(AuditEvent).order_by(AuditEvent.id)).all() if a.action.startswith("RTC_AUTO_APPLY")]
assert actions == ["RTC_AUTO_APPLY_AUTHORIZE", "RTC_AUTO_APPLY_REVOKE"]
def test_panel_shows_rtc_products_summary_and_the_pending_message_only_when_rtc_is_on():
client, agent_id, _ = _panel_client()
_complete_sync(client, agent_id, {"rtc_enabled": False, "rtc_supported": True, "rtc_resolution": None})
off = client.get(f"/agents/{agent_id}").text
assert "Produtos RTC" not in off and "cClassTrib pendente" not in off and "Preenchimento automático" not in off
resolution = {"total": 521, "valid_existing": 0, "ready_to_apply": 280, "ambiguous": 215, "insufficient": 26, "catalog_compatibility": {"compatibility": "PARTIAL_LOCAL_OUTDATED"}}
_complete_sync(client, agent_id, {"rtc_enabled": True, "rtc_supported": True, "rtc_catalog_valid": True, "rtc_resolution": resolution})
on = client.get(f"/agents/{agent_id}").text
assert "Reforma Tributária ativa — cClassTrib pendente em 521 produtos" in on and "prontos para preenchimento 280" in on and "ambíguos/bloqueados 241" in on
assert "Preenchimento automático" in on and "Desativado" in on and "digite PREENCHER" in on
for legacy in ("Escopo anterior", "Escopo ampliado"):
assert legacy not in on
+1 -1
View File
@@ -17,7 +17,7 @@ def test_health_is_unavailable_when_database_is_down():
def test_version_endpoint_reports_release_and_fiscal_versions():
body = TestClient(create_app(database_probe=lambda: True)).get("/version").json()
assert body["version"] == "1.1.8" and body["api_compat"] == "1.0" and body["engine_version"] and body["decision_version"]
assert body["version"] == "1.1.9" and body["api_compat"] == "1.0" and body["engine_version"] and body["decision_version"]
def test_production_refuses_the_dev_token(monkeypatch):
+43
View File
@@ -264,3 +264,46 @@ def test_rtc_on_then_off_and_pis_fix_leave_no_stale_open_blockers_and_never_dupl
with Session(engine) as s:
rows = s.scalars(select(BlockedProduct)).all()
assert len(rows) == 2 and [(r.product_code, r.resolved_at is None) for r in rows] == [("20", True), ("21", False)]
def test_job_carries_the_central_rtc_authorization_default_off_and_follows_the_installation():
client, engine = _client()
hb(client)
job = client.get("/v1/jobs/next", headers=H).json()
assert job["authorizations"] == {"rtc_auto_apply": False} # never on by default
client.post(f"/v1/jobs/{job['job_id']}/complete", json={"result_summary": {}}, headers=H)
with Session(engine) as s:
inst = s.scalars(select(Installation)).one()
inst.rtc_auto_apply_authorized = True
s.commit()
age_last_sync(engine, 4000)
hb(client)
assert client.get("/v1/jobs/next", headers=H).json()["authorizations"] == {"rtc_auto_apply": True}
def test_rtc_write_audits_are_persisted_with_server_side_identity_and_never_duplicated_per_job():
from central.app.models import AgentEvent, RtcWriteAudit
client, engine = _client()
hb(client)
applied = {"outcome": "APPLIED", "product_code": "10", "field": "CCLASSTRIB", "old_value": None, "new_value": "200003", "rule_id": "RTC-R3", "evidence": {"reason": "ANNEX_I_CLEAN_ITEM_UNIQUE_DEFAULT_CLASS"},
"official_source_version": {"cclasstrib_sha256": "ab" * 32}, "local_catalog_hash": "cd" * 32, "timestamp": "2026-09-24T20:00:00+00:00", "job_id": 999, "tenant_id": 12345, "agent_id": 777}
aborted = {"outcome": "ABORTED", "product_code": "11", "field": "CCLASSTRIB", "reason": "RTC_NO_LONGER_ENABLED", "rule_id": "RTC-R4", "timestamp": "2026-09-24T20:00:01+00:00"}
other_field = {"outcome": "APPLIED", "product_code": "12", "field": "IPI", "new_value": "1.0"} # anything but CCLASSTRIB is refused
finish(client, engine, {"operational": OPERATIONAL, "blocked_products": [], "resolved_products": [], "rtc_auto_apply": {"mode": "AUTO_APPLY", "applied": [applied, other_field], "aborted": [aborted], "aborted_reasons": {"RTC_NO_LONGER_ENABLED": 1}}})
with Session(engine) as s:
rows = s.scalars(select(RtcWriteAudit).order_by(RtcWriteAudit.id)).all()
assert [(r.product_code, r.outcome, r.old_value, r.new_value, r.rule_id) for r in rows] == [("10", "APPLIED", None, "200003", "RTC-R3"), ("11", "ABORTED", None, None, "RTC-R4")]
inst = s.scalars(select(Installation)).one()
assert all(r.installation_id == inst.id and r.agent_id == inst.agent_id and r.tenant_id == inst.tenant_id for r in rows) # identity comes from the authenticated context, not the payload
assert rows[0].local_catalog_hash == "cd" * 32 and rows[0].official_source_version["cclasstrib_sha256"] == "ab" * 32 and rows[0].job_id != 999
events = [e for e in s.scalars(select(AgentEvent)).all() if e.event_type == "RTC_AUTO_APPLY"]
assert len(events) == 1 and events[0].structured_context["applied"] == 1 and events[0].severity == "WARNING"
def test_report_only_jobs_leave_no_audit_rows():
from central.app.models import RtcWriteAudit
client, engine = _client()
hb(client)
finish(client, engine, {"operational": OPERATIONAL, "blocked_products": [], "resolved_products": [], "rtc_auto_apply": {"mode": "REPORT_ONLY", "applied": [], "aborted": [], "ready_total": 280}})
with Session(engine) as s:
assert s.scalars(select(RtcWriteAudit)).all() == []
+1 -1
View File
@@ -19,4 +19,4 @@ FB_PASSWORD=
FISCAL_MODE=DRY_RUN
FIREBIRD_WRITES=0
POLL_INTERVAL_SECONDS=5
AGENT_VERSION=1.1.8
AGENT_VERSION=1.1.9
+1 -1
View File
@@ -164,7 +164,7 @@ switch ($Command) {
"GTI_HUB_URL=$hub", "AGENT_TOKEN=$token", "AGENT_ID=$agentId",
"COMPANY_CODE=$code", "COMPANY_CNPJ=$cnpj", "COMPANY_CRT_SOURCE=$crtSource",
"FIREBIRD_DATABASE_PATH=$db", "FB_USER=$fbUser", "FB_PASSWORD=$fbPass",
'FISCAL_MODE=DRY_RUN', 'FIREBIRD_WRITES=0', 'POLL_INTERVAL_SECONDS=5', 'AGENT_VERSION=1.1.8'
'FISCAL_MODE=DRY_RUN', 'FIREBIRD_WRITES=0', 'POLL_INTERVAL_SECONDS=5', 'AGENT_VERSION=1.1.9'
)
Set-Content -Path $EnvFile -Value $lines -Encoding UTF8
}
@@ -19,4 +19,4 @@ FB_PASSWORD=
FISCAL_MODE=DRY_RUN
FIREBIRD_WRITES=0
POLL_INTERVAL_SECONDS=5
AGENT_VERSION=1.1.8
AGENT_VERSION=1.1.9
@@ -19,4 +19,4 @@ FB_PASSWORD=
FISCAL_MODE=DRY_RUN
FIREBIRD_WRITES=0
POLL_INTERVAL_SECONDS=5
AGENT_VERSION=1.1.8
AGENT_VERSION=1.1.9
@@ -19,4 +19,4 @@ FB_PASSWORD=
FISCAL_MODE=DRY_RUN
FIREBIRD_WRITES=0
POLL_INTERVAL_SECONDS=5
AGENT_VERSION=1.1.8
AGENT_VERSION=1.1.9
@@ -19,4 +19,4 @@ FB_PASSWORD=
FISCAL_MODE=DRY_RUN
FIREBIRD_WRITES=0
POLL_INTERVAL_SECONDS=5
AGENT_VERSION=1.1.8
AGENT_VERSION=1.1.9
+1 -1
View File
@@ -1,5 +1,5 @@
# GTI Fiscal Hub — production environment (copy to .env, never commit .env)
GTI_FISCAL_VERSION=1.1.8
GTI_FISCAL_VERSION=1.1.9
# REQUIRED. URL-safe secret (used inside a connection URL): openssl rand -hex 24
POSTGRES_PASSWORD=
+1 -1
View File
@@ -36,7 +36,7 @@ services:
build:
context: ../..
dockerfile: central/Dockerfile
image: gti-fiscal-hub:${GTI_FISCAL_VERSION:-1.1.8}
image: gti-fiscal-hub:${GTI_FISCAL_VERSION:-1.1.9}
restart: unless-stopped
depends_on:
postgres:
+1 -1
View File
@@ -16,7 +16,7 @@ A variável de ambiente `GTI_SSH_PASSWORD`, se existir, tem prioridade. A senha
## Deploy
```
powershell -ExecutionPolicy Bypass -File .\scripts\deploy_hub.ps1
powershell -ExecutionPolicy Bypass -File .\scripts\deploy_hub.ps1 -PublishAgent -Version 1.1.8
powershell -ExecutionPolicy Bypass -File .\scripts\deploy_hub.ps1 -PublishAgent -Version 1.1.9
```
Parâmetros: `-Version` (padrão `release/VERSION`), `-PublishAgent`, `-SkipTests`, `-DryRun`.
+67
View File
@@ -0,0 +1,67 @@
# Determinação segura do cClassTrib (resolver determinístico)
`central/app/cclasstrib_resolver.py` (puro, sem Firebird, sem rede, sem probabilidade) + `central/app/rtc_reference_data.py` (gerado por `scripts/build_rtc_reference_data.py`, com hash).
`ACCESSED_AT`: **2026-09-24/25**. Nada aqui é chute: uma classe só é proposta quando uma regra **explícita**, ancorada nos textos oficiais, a prova.
## Fontes oficiais (SOURCE / DOCUMENT / VERSION / PUBLICATION / SHA256)
| Fonte | Documento | Versão / publicação | SHA256 |
|---|---|---|---|
| Portal Nacional NF-e (ENCAT) | Tabela **cClassTrib** (164 classes, 18 CST) — `cClassTrib 2026-06-22.xlsx` | publicada 22/06/2026; no repo em `sources/raw/bootstrap_v1/` e no Hub (dataset `RTC_CCLASSTRIB` ACTIVE) | `1448cb63a41bdb67…` |
| Planalto (cópia do Internet Archive; planalto.gov.br recusa conexão direta) | **LC 214/2025 compilada** (com LC 227/2026: Anexo XIV revogado) — arts. 125–152 (reduções), 172 (combustíveis), 348 (2026); Anexos I–XV | acessado 2026-09-24 | `5c7d59b3c49e5b7c…` |
| Portal Nacional NF-e | **NT 2025.002-RTC v1.51** (04/08/2026) — para CRT 1/2/4 as orientações de IBS/CBS "serão publicadas em NT futura"; tributação a partir de 2027 (LC 214 art. 348) | `https://www.nfe.fazenda.gov.br/portal/exibirArquivo.aspx?conteudo=AKD/muSmiIY=` | ver `docs/sources/SOURCES_MANIFEST_UNIFIED_FISCAL_V1.json` |
| SGV 5.1.0.4 (executável) | tela de produto: `cClassTrib` = `TRzDBButtonEdit` `DataField=CCLASSTRIB`; consulta `select * from RTC_CCLASSTRIB where … (IND_NFE=1 or IND_NFCE=1 or IND_CTE=1)`; junção do documento `C000025.CCLASSTRIB → RTC_CCLASSTRIB → RTC_CST` | binário local | — |
O portal NF-e foi rastreado (Documentos, Diversos, Atos): não expõe tabela cClassTrib mais nova que 22/06/2026. Não há endpoint oficial NCM→cClassTrib: o vínculo vem dos **Anexos da LC 214** (listas de NCM) + tabela oficial.
## O que o SGV faz (item 5) — DOCUMENT_CCLASSTRIB_SOURCE
O SGV **não tem** regra produto→cClassTrib: o operador escolhe a classe pelo botão de busca (filtrada por `IND_NFE/IND_NFCE/IND_CTE=1`) e o documento usa `C000025.CCLASSTRIB` diretamente, juntando ao catálogo local e ao CST. Nenhuma trigger/procedure referencia RTC; `USAR_REFORMA_TRIBUTARIA` só liga/desliga. Consequência: qualquer resolução automática é regra **nossa**, portanto tem de ser provada e auditável. A classe proposta tem de passar no mesmo filtro do SGV (usável em NF-e/NFC-e).
## Catálogo local × oficial (item 4) — RTC_LOCAL_OFFICIAL_COMPATIBILITY
Comparação por código (CST, reduções IBS/CBS, indicadores NF-e/NFC-e, vigência): `MATCH | LOCAL_ONLY | OFFICIAL_ONLY | MISMATCH | EXPIRED`. Nada é escrito no catálogo local.
| | TESTESOFICIAIS (= todos os bancos com RTC, mesmo catálogo) |
|---|---|
| MATCH | 131 |
| OFFICIAL_ONLY (local desatualizado) | 19 (`000005, 200053, 200054, 221002-4, 400002, 410032-7, 550022-5, 620007, 820009`) |
| MISMATCH | 11 — só `DOCUMENT_FLAGS` (IND_NFE/IND_NFCE): `200001, 200044, 410027, 410028, 410031, 550002, 620001-5` |
| EXPIRED presentes no local | 3 (`220001-3`, fim 01/01/2026) |
| LOCAL_ONLY | 0 |
Conclusão: **LOCAL_CATALOG_HAS_ENOUGH_METADATA_TO_RESOLVE = false**. O catálogo local (33 colunas: CST, tipo de alíquota, reduções, indicadores de documento, monofasia, `ANEXO`, `LINK`) descreve *a classe*, não contém NCM nem critério de produto: só o `ANEXO` liga classe a anexo. A determinação usa o catálogo local **para validar** e os Anexos da LC 214 **para decidir**. Incompatibilidade material ⇒ só a classe afetada é bloqueada (`RTC_LOCAL_CATALOG_OUTDATED` / `CCLASSTRIB_OFFICIAL_MISMATCH`); as classes que as regras usam (`000001, 200003, 200014, 200034, 200035`) são `MATCH`.
## Modelo de resolução e regras
Premissa documentada: **venda ordinária no mercado interno** (default de produto). Classes que dependem do adquirente/operação (órgão público, exportação, ZFM/ALC, crédito presumido, diferimento…) não cabem num default de produto ⇒ `AMBIGUOUS`.
| Regra | Condição | Status |
|---|---|---|
| RTC-R0 | RTC desligada no ERP | `NOT_APPLICABLE` |
| RTC-G1 | catálogo RTC local com falha de integridade | `INSUFFICIENT_EVIDENCE` |
| RTC-R1 | Simples (CRT 1/2/4) ou regime desconhecido | `INSUFFICIENT_EVIDENCE` (NT 2025.002: orientação pendente) |
| RTC-R2 | NCM sem confirmação de 8 dígitos válidos | `INSUFFICIENT_EVIDENCE` |
| RTC-A1 | NCM em anexo revogado, em anexo sem classe-padrão única (IV, V, VI, IX, X, XI, XII, XIII…) ou sob texto condicional ("exceto", "em conformidade com…", "ressalvados…") | `AMBIGUOUS` |
| RTC-A2 | NCM listado em mais de um anexo | `AMBIGUOUS` |
| RTC-A3 | capítulos 01–14, 27, 30, 49, 71, 87, 88, 89, 90 e posições 9619, 2207, 3826, 8703: regime definido pela natureza/operação do bem | `AMBIGUOUS` |
| RTC-R3 | NCM em **exatamente um** anexo com classe-padrão única (I→`200003`, VII→`200034`, VIII→`200035`, XV→`200014`) e item sem condição | `RESOLVED_WITH_EXPLICIT_RULE` |
| RTC-R4 | NCM em **nenhum** anexo e fora dos capítulos acima ⇒ tributação integral (regra geral, LC 214 art. 4) | `RESOLVED_WITH_EXPLICIT_RULE` → `000001` |
| RTC-G2 | classe proposta ausente/diferente no catálogo local, fora de vigência ou inutilizável em NF-e/NFC-e | `INSUFFICIENT_EVIDENCE` |
`RESOLVED_DETERMINISTIC` (regra sem premissa de operação) não é usado: toda resolução carrega a premissa "venda ordinária" e por isso é `RESOLVED_WITH_EXPLICIT_RULE`.
Saída auditável: `status, resolved_cclasstrib, resolved_cst_ibscbs, reason, rule_id, confidence_type` (`ANNEX_TEXT_MATCH` | `EXCLUSION_OF_ALL_SPECIAL_REGIMES`; sem score), `inputs_used`, `evidence` (item do anexo, prefixo, texto), `source_references` (sha256).
O anexo é lido do texto oficial por `scripts/build_rtc_reference_data.py` (item a item; `clean=False` quando há condição). `verify_annex_class_map()` prova contra a tabela oficial que cada classe-padrão existe, é a única classe do anexo usável em NF-e/NFC-e e tem o `ANEXO` esperado (o Anexo IX ficou de fora: a classe `515001` é irmã).
## Blockers RTC (uma causa raiz, um blocker por produto)
`CCLASSTRIB_MISSING` (com `READY_TO_APPLY:<classe>:<regra>` quando a regra prova a classe) · `CCLASSTRIB_MISSING_UNRESOLVED` (cClassTrib não determinado) · `CCLASSTRIB_AMBIGUOUS` (cClassTrib ambíguo) · `RTC_RULE_EVIDENCE_MISSING` (evidência fiscal insuficiente) · `RTC_LOCAL_CATALOG_OUTDATED` (catálogo RTC local incompatível) · `CCLASSTRIB_OFFICIAL_MISMATCH` (classe existente diverge do oficial) · `CCLASSTRIB_CONFLICT` (valor existente ≠ regra determinística) + os de validação (`INVALID_FORMAT`, `NOT_IN_CATALOG`, `RTC_CST_MISSING`, `RTC_CATALOG_INVALID`). Todos vão para a mesma fila (categoria `RTC`).
## Dry-run real (somente leitura; `scripts/rtc_resolution_dryrun.py` → `docs/runs/rtc_resolution_dryrun.json`, amostra de 100 em `docs/runs/rtc_resolution_audit_sample.csv`)
Estado como está hoje: TESTESOFICIAIS e CONSTRUCAO têm RTC ligada e regime Simples ⇒ **0 candidatos** (RTC-R1). Simulação "regime normal + RTC ligada" (o que as regras resolveriam quando existir orientação oficial para Simples):
| Banco | Produtos | Prontos (R3+R4) | Ambíguos | Insuficientes (NCM inválido) |
|---|---|---|---|---|
| TESTESOFICIAIS | 521 | 280 | 215 | 26 |
| CONSTRUCAO | 3.712 | 2.725 | 973 | 14 |
| MMMODAS | 20.996 | 20.831 | 49 | 116 |
| RINCAO (CRT 3, RTC off no banco) | 894 | 530 | 364 | 0 |
| UNIAO | 8.942 | 7.419 | 1.432 | 91 |
## Limitações
Simples fica bloqueado até haver orientação oficial (ou decisão explícita de empresa, não implementada); operações especiais não são cobertas pelo default de produto; a extração dos anexos é textual (itens condicionais viram ambíguos de propósito); o catálogo oficial usado é o de 22/06/2026; sem autorização, nada é escrito (ver `docs/rtc_auto_apply.md`).
+32
View File
@@ -0,0 +1,32 @@
# Preenchimento automático do cClassTrib (RTC auto-apply)
Código: `agent/src/gti_fiscal_agent/rtc_auto_apply.py` · integração `agent/scripts/run_agent_service.py::_rtc_auto_apply` · Hub `central/app/rtc_audit.py`, migração `0027_rtc_auto_apply`, rota `POST /agents/{id}/rtc-auto-apply`.
**Estado de entrega: mecanismo pronto, DESLIGADO em todo lugar. Nenhuma escrita em banco real foi feita ou é possível sem as três chaves abaixo.**
## Escopo de escrita
Uma coluna: `C000025.CCLASSTRIB`. `ALLOWED_RTC_WRITE_FIELDS = ["CCLASSTRIB"]`; o SQL é uma constante (`UPDATE C000025 SET CCLASSTRIB = ? WHERE CODIGO = ? AND (CCLASSTRIB IS NULL OR TRIM(CCLASSTRIB) = '')`). Não há write genérico de produto, nem escrita em NFCE/itens/catálogo RTC.
Só candidatos `RESOLVED_*` (ver `docs/cclasstrib_resolution.md`) com `CCLASSTRIB` vazio e nenhum outro problema RTC. Nunca sobrescreve: valor igual ⇒ `NO_WRITE_REQUIRED`; valor diferente ⇒ blocker `CCLASSTRIB_CONFLICT` (a política de correção precisa de aprovação futura).
## Três chaves (todas necessárias; padrão = fechadas)
1. `FIREBIRD_WRITES` efetivo no Agent (`FISCAL_MODE=AUTO_APPLY` + caminho/esquema autorizados — as travas já existentes);
2. `RTC_AUTO_APPLY_ENABLED=1` no `agent.env` (chave própria; `RTC_AUTO_APPLY_MAX_PER_JOB`, padrão 500, limita o raio de ação);
3. autorização do **Hub** por instalação (`installations.rtc_auto_apply_authorized`, padrão `false`), entregue com cada job (`authorizations.rtc_auto_apply`) e alterada só pelo painel: botão "Autorizar" exige CSRF e digitar `PREENCHER`; toda mudança gera `audit_events` (`RTC_AUTO_APPLY_AUTHORIZE/REVOKE`).
Sem as três: modo `REPORT_ONLY` (conta candidatos, abre nenhuma conexão de escrita). O Agent reporta a capability `rtc_auto_apply` no heartbeat.
## Proteção contra decisão velha (por produto, uma transação curta)
Imediatamente antes do UPDATE o Agent **relê tudo do banco**: parâmetro `USAR_REFORMA_TRIBUTARIA` (todas as linhas; divergência ⇒ aborta), integridade do catálogo, hash do catálogo (deve ser o da decisão), a linha do produto (única; NCM igual ao da decisão; `CCLASSTRIB` ainda vazio) e **re-deriva** a resolução com dados novos (mesma classe e mesma regra). Qualquer diferença ⇒ `ABORTED` daquele produto (`RTC_NO_LONGER_ENABLED`, `RTC_CATALOG_INVALID`, `LOCAL_CATALOG_CHANGED_SINCE_DECISION`, `PRODUCT_CHANGED_NCM`, `VALUE_ALREADY_SET_NEVER_OVERWRITTEN`, `NO_WRITE_REQUIRED`, `DECISION_CHANGED`, `PRODUCT_ROW_NOT_UNIQUE`, `UPDATE_ROWCOUNT_NOT_ONE`). `rowcount` deve ser 1; releitura confirma o valor; a FK física `C000025.CCLASSTRIB → RTC_CCLASSTRIB` é a segunda barreira. Falha de um produto não interrompe os demais.
Idempotência: com o valor correto gravado o produto deixa de ser candidato (`valid_existing`); segunda execução = 0 escritas.
## Auditoria e rollback lógico
Cada tentativa vira uma linha em `rtc_write_audits` (append-only): `tenant/installation/agent` (do contexto autenticado, nunca do payload), `client`, `job_id`, `product_code`, `field`, `outcome` (`APPLIED|ABORTED|FAILED`), `reason`, **`old_value`** (sempre nulo hoje: só se preenche campo vazio), `new_value`, `rule_id`, `evidence` (regra, entradas, item do anexo), `official_source_version` (data + sha256 da tabela cClassTrib e sha256 da LC 214), `local_catalog_hash`, `written_at`. Um resumo vai como evento `RTC_AUTO_APPLY` (severidade WARNING quando houve escrita). Reverter = `UPDATE C000025 SET CCLASSTRIB = NULL` para os `product_code` do `job_id` (não há botão de rollback nesta versão).
## Painel
Mesmo painel, mesma fila. No detalhe do Agent, com RTC ativa: "Reforma Tributária ativa — cClassTrib pendente em N produtos", "Produtos RTC: total · válidos · prontos para preenchimento · ambíguos/bloqueados", estado do preenchimento automático ("Desativado / Autorizado no Hub, aguardando o Agent (…) / Ativo") e o formulário de autorização. RTC desligada não mostra nada disto.
## Validação
- Testes: `agent/tests/test_rtc_auto_apply.py` (gates, candidatos, escrita restrita, idempotência, RTC desligada entre decisão e escrita, catálogo alterado/inválido, NCM alterado, valor existente, duplicata, limite por job), `central/tests/test_zero_touch_hub.py`, `central/tests/test_control_panel_pages.py`, `tests/test_rtc_copy_write_guard.py`.
- **E2E de escrita em CÓPIA descartável** (`scripts/rtc_copy_write_e2e.py`; cópia `scratch_rtc_e2e/TESTESOFICIAIS_RTC_COPY.FDB` via gbak; o script recusa qualquer outro caminho): empresa da cópia trocada para regime normal (CRT 3) como experimento; 280 candidatos → 280 escritas verificadas (`000001`×203, `200034`×75, `200014`×2), somente a coluna `CCLASSTRIB` mudou (diff da tabela inteira), 0 órfãos de FK/CST, catálogo RTC local intacto (145 linhas antes e depois), cobertura da cópia 0% → 53,55%; decisão velha (RTC desligada na cópia entre decisão e escrita) → 280 abortos, 0 escritas; segunda execução → 0 candidatos, 0 escritas. Resultado em `docs/runs/rtc_copy_write_e2e.json`.
- Bancos reais: 0 escritas, 0 tentativas.
## Limitações
Simples continua bloqueado (sem orientação oficial); `CCLASSTRIB_CONFLICT` só reporta; não há rollback automático; o texto dos anexos é interpretado de forma conservadora (condição ⇒ ambíguo). Para o piloto real: escolher uma instalação em regime normal com RTC ligada, ligar as três chaves e revisar a amostra de auditoria antes.
+3
View File
@@ -49,5 +49,8 @@ O fingerprint dos blockers inclui o estado RTC: 0→1 cria blockers novos; 1→0
Estado atual do parâmetro nas cópias locais: `CONSTRUCAO=1`, `TESTESOFICIAIS=1`, `MMMODAS=0`, `RINCAO=0`, `UNIAO=0` (cada uma com **uma** linha em `PARAMETROS`, `ODS 11.2`, Firebird 2.5.9). O discovery de `TESTESOFICIAIS.FDB` registrou `0` às 15:25 de 24/09 e a leitura de hoje (mesmo arquivo, mesmas 521 linhas) retorna `1`. Causas descartadas: linha errada / várias linhas (há 1), cache do Hub (a leitura é direta no Firebird, o Agent lê ao vivo), outro arquivo (mesmo caminho canônico). Causa real comprovada: **o valor mudou no próprio banco depois do discovery** (mesmo arquivo, mesma linha única). O autor da mudança não é rastreável (o Firebird 2.5 não guarda histórico de valores e o SGV não tem tabela de log de parâmetros); a hipótese mais simples é a alteração do parâmetro "usar reforma tributária" no SGV 5.1.0.4, que é quem lê esse parâmetro. Por isso o Agent nunca reaproveita um estado anterior.
Consequência medida (TESTESOFICIAIS, RTC ligada de fato): 521/521 produtos com `CCLASSTRIB_MISSING` ⇒ cobertura real 0%; com RTC desligada (estado do piloto) 94,82%.
## Determinação e preenchimento do cClassTrib
Ver `docs/cclasstrib_resolution.md` (fontes oficiais, catálogo local × oficial, regras) e `docs/rtc_auto_apply.md` (auto-apply restrito a `CCLASSTRIB`, desligado por padrão).
## Testes
RTC off/on, `CCLASSTRIB` vazio/válido/inválido/órfão/expirado, catálogo inválido: `central/tests/test_unified_fiscal_coverage.py`, `agent/tests/test_unified_fiscal.py`.
File diff suppressed because it is too large Load Diff
+230
View File
@@ -0,0 +1,230 @@
{
"database": "scratch_rtc_e2e/TESTESOFICIAIS_RTC_COPY.FDB (gbak restore of TESTESOFICIAIS)",
"real_database_writes": 0,
"real_database_write_attempts": 0,
"copy_original_crt": [
"1"
],
"copy_crt_for_experiment": "3",
"copy_rtc_param": 1,
"coverage_before": {
"total": 521,
"complete": 0,
"blocked": 521,
"unclassified": 0,
"coverage_percent": 0.0
},
"resolution_before": {
"total": 521,
"valid_existing": 0,
"ready_to_apply": 280,
"ambiguous": 215,
"insufficient": 26,
"conflicts": 0,
"official_mismatch": 0,
"not_applicable": 0,
"other_blocked": 0,
"by_rule": {
"RTC-A1": 41,
"RTC-A3": 10,
"RTC-R2": 26,
"RTC-R4": 203,
"RTC-R3": 77,
"RTC-A2": 164
},
"catalog_compatibility": {
"compatibility": "MATERIAL_MISMATCH",
"counts": {
"MATCH": 131,
"LOCAL_ONLY": 0,
"OFFICIAL_ONLY": 19,
"MISMATCH": 11,
"EXPIRED": 3
},
"expired_present_locally": 3
}
},
"stale_decision_run": {
"write_attempts": 280,
"write_successes": 0,
"aborted_reasons": {
"RTC_NO_LONGER_ENABLED": 280
}
},
"first_run": {
"mode": "AUTO_APPLY",
"ready_total": 280,
"write_attempts": 280,
"write_successes": 280,
"aborted_reasons": {},
"audit_sample": [
{
"outcome": "APPLIED",
"product_code": "000023",
"field": "CCLASSTRIB",
"old_value": null,
"new_value": "000001",
"cst_ibscbs": "000",
"rule_id": "RTC-R4",
"confidence_type": "EXCLUSION_OF_ALL_SPECIAL_REGIMES",
"evidence": {
"inputs_used": {
"ncm": "16025000",
"crt": "3",
"rtc_enabled": true
},
"evidence": [
{
"annex_hits": 0,
"chapter": "16"
}
],
"reason": "NCM_IN_NO_ANNEX_AND_NO_NATURE_BASED_CHAPTER"
},
"official_source_version": {
"cclasstrib_table": "2026-06-22",
"cclasstrib_sha256": "1448cb63a41bdb67ea30b4e11f4dc200d9568542c6b9335b6cff87a4fd664654",
"lc214_sha256": "5c7d59b3c49e5b7c066c2ad28bedf32791193a676ec1a163bec14bea5fa2f680"
},
"local_catalog_hash": "1da77aec27fbde88488d1edf562f3af315ab2b5d06efb2e0b2f1ed606585d459",
"timestamp": "2026-09-25T00:07:47+00:00",
"job_id": 424242,
"rowcount": 1,
"verified_after": true
},
{
"outcome": "APPLIED",
"product_code": "000024",
"field": "CCLASSTRIB",
"old_value": null,
"new_value": "200034",
"cst_ibscbs": "200",
"rule_id": "RTC-R3",
"confidence_type": "ANNEX_TEXT_MATCH",
"evidence": {
"inputs_used": {
"ncm": "19022000",
"crt": "3",
"rtc_enabled": true
},
"evidence": [
{
"annex": "VII",
"item": 9,
"prefix": "19022000",
"clean": true,
"revoked": false,
"text": "Massas alimentícias dos códigos 1902.20.00 e 1902.30.00 da NCM/SH"
}
],
"reason": "ANNEX_VII_CLEAN_ITEM_UNIQUE_DEFAULT_CLASS"
},
"official_source_version": {
"cclasstrib_table": "2026-06-22",
"cclasstrib_sha256": "1448cb63a41bdb67ea30b4e11f4dc200d9568542c6b9335b6cff87a4fd664654",
"lc214_sha256": "5c7d59b3c49e5b7c066c2ad28bedf32791193a676ec1a163bec14bea5fa2f680"
},
"local_catalog_hash": "1da77aec27fbde88488d1edf562f3af315ab2b5d06efb2e0b2f1ed606585d459",
"timestamp": "2026-09-25T00:07:47+00:00",
"job_id": 424242,
"rowcount": 1,
"verified_after": true
},
{
"outcome": "APPLIED",
"product_code": "000027",
"field": "CCLASSTRIB",
"old_value": null,
"new_value": "200034",
"cst_ibscbs": "200",
"rule_id": "RTC-R3",
"confidence_type": "ANNEX_TEXT_MATCH",
"evidence": {
"inputs_used": {
"ncm": "19022000",
"crt": "3",
"rtc_enabled": true
},
"evidence": [
{
"annex": "VII",
"item": 9,
"prefix": "19022000",
"clean": true,
"revoked": false,
"text": "Massas alimentícias dos códigos 1902.20.00 e 1902.30.00 da NCM/SH"
}
],
"reason": "ANNEX_VII_CLEAN_ITEM_UNIQUE_DEFAULT_CLASS"
},
"official_source_version": {
"cclasstrib_table": "2026-06-22",
"cclasstrib_sha256": "1448cb63a41bdb67ea30b4e11f4dc200d9568542c6b9335b6cff87a4fd664654",
"lc214_sha256": "5c7d59b3c49e5b7c066c2ad28bedf32791193a676ec1a163bec14bea5fa2f680"
},
"local_catalog_hash": "1da77aec27fbde88488d1edf562f3af315ab2b5d06efb2e0b2f1ed606585d459",
"timestamp": "2026-09-25T00:07:47+00:00",
"job_id": 424242,
"rowcount": 1,
"verified_after": true
}
]
},
"rows_changed": 280,
"columns_changed": [
"CCLASSTRIB"
],
"fk_orphans": 0,
"cst_orphans": 0,
"written_by_class": {
"000001": 203,
"200014": 2,
"200034": 75
},
"catalog_rows_unchanged": 145,
"coverage_after": {
"total": 521,
"complete": 279,
"blocked": 242,
"unclassified": 0,
"coverage_percent": 53.55
},
"resolution_after": {
"total": 521,
"valid_existing": 280,
"ready_to_apply": 0,
"ambiguous": 215,
"insufficient": 26,
"conflicts": 0,
"official_mismatch": 0,
"not_applicable": 0,
"other_blocked": 0,
"by_rule": {
"RTC-A1": 41,
"RTC-A3": 10,
"RTC-R2": 26,
"RTC-R4": 203,
"RTC-R3": 77,
"RTC-A2": 164
},
"catalog_compatibility": {
"compatibility": "MATERIAL_MISMATCH",
"counts": {
"MATCH": 131,
"LOCAL_ONLY": 0,
"OFFICIAL_ONLY": 19,
"MISMATCH": 11,
"EXPIRED": 3
},
"expired_present_locally": 3
}
},
"second_run": {
"ready_total": 0,
"write_attempts": 0,
"write_successes": 0
},
"IDEMPOTENT": true,
"ONLY_CCLASSTRIB_CHANGED": true,
"PASS": true
}
+164
View File
@@ -0,0 +1,164 @@
000001|000|Padrão|0|0|2026-01-01T00:00:00|||Situações tributadas integralmente pelo IBS e CBS.
000002|000|Padrão|0|0|2026-01-01T00:00:00||90111|Exploração de via
000003|000|Padrão|0|0|2026-01-01T00:00:00|||Regime automotivo - projetos incentivados (art. 311)
000004|000|Padrão|0|0|2026-01-01T00:00:00|||Regime automotivo - projetos incentivados (art. 312)
000005|000|Padrão|0|0|2026-01-01T00:00:00|||Operação com EAC destinado à mistura com gasolina A, mas com saída do biocombustível com destinação diversa
010001|010|Uniforme setorial|0|0|2026-01-01T00:00:00|||Operações do FGTS não realizadas pela Caixa Econômica Federal
010002|010|Uniforme setorial|0|0|2026-01-01T00:00:00|||Operações do serviço financeiro
011001|011|Uniforme nacional (referência)|60|60|2026-01-01T00:00:00|||Planos de assistência funerária.
011002|011|Uniforme nacional (referência)|60|60|2026-01-01T00:00:00|||Planos de assistência à saúde
011003|011|Uniforme nacional (referência)|60|60|2026-01-01T00:00:00||92401|Intermediação de planos de assistência à saúde
011004|011|Uniforme nacional (referência)|0|0|2026-01-01T00:00:00|||Concursos e prognósticos
011005|011|Uniforme nacional (referência)|30|30|2026-01-01T00:00:00|||Planos de assistência à saúde de animais domésticos
200001|200|Padrão|100|100|2026-01-01T00:00:00|||Serviços de transporte de bens até as zonas de processamento de exportação e bens exportados a partir das zonas de processamento de exportação
200002|200|Padrão|100|100|2026-01-01T00:00:00|||Fornecimento ou importação para produtor rural não contribuinte ou TAC
200003|200|Padrão|100|100|2026-01-01T00:00:00||1|Vendas de produtos destinados à alimentação humana (Anexo I)
200004|200|Padrão|100|100|2026-01-01T00:00:00||12|Fornecimento de dispositivos médicos (Anexo XII)
200005|200|Padrão|100|100|2026-01-01T00:00:00||4|Fornecimento de dispositivos médicos para órgãos da administração pública e entidades de saúde imunes (Anexo IV)
200006|200|Padrão|100|100|2026-01-01T00:00:00|||Situação de emergência de saúde pública reconhecida pelo Poder público
200007|200|Padrão|100|100|2026-01-01T00:00:00||13|Fornecimento dos dispositivos de acessibilidade próprios para pessoas com deficiência (Anexo XIII)
200008|200|Padrão|100|100|2026-01-01T00:00:00||5|Fornecimento dos dispositivos de acessibilidade próprios para pessoas com deficiência adquiridos por órgãos da administração pública (Anexo V)
200009|200|Padrão|100|100|2026-01-01T00:00:00|||Fornecimento dos medicamentos registrados na Anvisa
200010|200|Padrão|100|100|2026-01-01T00:00:00|||Fornecimento dos medicamentos registrados na Anvisa, adquiridos por órgãos da administração pública
200011|200|Padrão|100|100|2026-01-01T00:00:00||6|Fornecimento das composições para nutrição enteral e parenteral quando adquiridas por órgãos da administração pública (Anexo VI)
200012|200|Padrão|100|100|2026-01-01T00:00:00|||Situação de emergência de saúde pública reconhecida pelo Poder público
200013|200|Padrão|100|100|2026-01-01T00:00:00||91471|Fornecimento de tampões higiênicos, absorventes higiênicos internos ou externos
200014|200|Padrão|100|100|2026-01-01T00:00:00||15|Fornecimento dos produtos hortícolas, frutas e ovos (Anexo XV)
200015|200|Padrão|100|100|2026-01-01T00:00:00|||Venda de automóveis de passageiros de fabricação nacional adquiridos por motoristas profissionais ou pessoas com deficiência
200016|200|Padrão|100|100|2026-01-01T00:00:00||91561|Prestação de serviços de pesquisa e desenvolvimento por Instituição Científica, Tecnológica e de Inovação (ICT)
200017|200|Padrão|100|100|2026-01-01T00:00:00|||Operações relacionadas ao FGTS
200018|200|Padrão|100|100|2026-01-01T00:00:00|||Operações de resseguro e retrocessão
200019|200|Padrão|100|100|2026-01-01T00:00:00|||Importador dos serviços financeiros contribuinte
200020|200|Padrão|100|100|2026-01-01T00:00:00|||Operação praticada por sociedades cooperativas optantes por regime específico do IBS e CBS
200021|200|Padrão|100|100|2026-01-01T00:00:00|||Serviços de transporte público coletivo de passageiros ferroviário e hidroviário
200022|200|Padrão|100|100|2026-01-01T00:00:00|||Operação originada fora da ZFM que destine bem material industrializado a contribuinte estabelecido na ZFM
200023|200|Padrão|100|100|2026-01-01T00:00:00|||Operação realizada por indústria incentivada que destine bem material intermediário para outra indústria incentivada na ZFM
200024|200|Padrão|100|100|2026-01-01T00:00:00|||Operação originada fora das Áreas de Livre Comércio destinadas a contribuinte estabelecido nas Áreas de Livre Comércio
200025|200|Padrão|60|100|2026-01-01T00:00:00||93081|Fornecimento dos serviços de educação relacionados ao Programa Universidade para Todos (Prouni)
200026|200|Padrão|80|80|2026-01-01T00:00:00||91581|Locação de imóveis localizados nas zonas reabilitadas
200027|200|Padrão|70|70|2026-01-01T00:00:00||92611|Operações de locação, cessão onerosa e arrendamento de bens imóveis
200028|200|Padrão|60|60|2026-01-01T00:00:00||2|Fornecimento dos serviços de educação (Anexo II)
200029|200|Padrão|60|60|2026-01-01T00:00:00||3|Fornecimento dos serviços de saúde humana (Anexo III)
200030|200|Padrão|60|60|2026-01-01T00:00:00||4|Venda dos dispositivos médicos (Anexo IV)
200031|200|Padrão|60|60|2026-01-01T00:00:00||5|Fornecimento dos dispositivos de acessibilidade próprios para pessoas com deficiência (Anexo V)
200032|200|Padrão|60|60|2026-01-01T00:00:00|||Fornecimento dos medicamentos registrados na Anvisa ou produzidos por farmácias de manipulação, ressalvados os medicamentos sujeitos à alíquota zero
200033|200|Padrão|60|60|2026-01-01T00:00:00||6|Fornecimento das composições para nutrição enteral e parenteral (Anexo VI)
200034|200|Padrão|60|60|2026-01-01T00:00:00||7|Fornecimento dos alimentos destinados ao consumo humano (Anexo VII)
200035|200|Padrão|60|60|2026-01-01T00:00:00||8|Fornecimento dos produtos de higiene pessoal e limpeza (Anexo VIII)
200036|200|Padrão|60|60|2026-01-01T00:00:00|||Fornecimento de produtos agropecuários, aquícolas, pesqueiros, florestais e extrativistas vegetais in natura
200037|200|Padrão|60|60|2026-01-01T00:00:00|||Fornecimento de serviços ambientais de conservação ou recuperação da vegetação nativa
200038|200|Padrão|60|60|2026-01-01T00:00:00||9|Fornecimento dos insumos agropecuários e aquícolas (Anexo IX)
200039|200|Padrão|60|60|2026-01-01T00:00:00||10|Fornecimento dos bens e serviços relacionados com produções nacionais artísticas, culturais, de eventos, jornalísticas e audiovisuais (Anexo X)
200040|200|Padrão|60|60|2026-01-01T00:00:00|||Fornecimento de serviços de comunicação institucional à administração pública
200041|200|Padrão|60|60|2026-01-01T00:00:00||91411|Fornecimento de serviço de educação desportiva (art. 141. I)
200042|200|Padrão|60|60|2026-01-01T00:00:00|||Fornecimento de serviço de gestão e exploração do desporto (art. 141. II)
200043|200|Padrão|60|60|2026-01-01T00:00:00||11|Fornecimento à administração pública dos serviços e dos bens relativos à soberania (Anexo XI)
200044|200|Padrão|60|60|2026-01-01T00:00:00||11|Operações e prestações de serviços de segurança da informação e segurança cibernética desenvolvidos por sociedade que tenha sócio brasileiro (Anexo XI)
200045|200|Padrão|60|60|2026-01-01T00:00:00|||Operações relacionadas a projetos de reabilitação urbana de zonas históricas e de áreas críticas de recuperação e reconversão urbanística
200046|200|Padrão|50|50|2026-01-01T00:00:00|||Operações com bens imóveis
200047|200|Padrão|40|40|2026-01-01T00:00:00|||Bares e Restaurantes
200048|200|Padrão|40|40|2026-01-01T00:00:00||92811|Hotelaria, Parques de Diversão e Parques Temáticos
200049|200|Padrão|40|40|2026-01-01T00:00:00|||Transporte coletivo de passageiros rodoviário, ferroviário e hidroviário
200050|200|Padrão|40|40|2026-01-01T00:00:00|||Serviços de transporte aéreo regional coletivo de passageiros ou de carga
200051|200|Padrão|40|40|2026-01-01T00:00:00|||Agências de Turismo
200052|200|Padrão|30|30|2026-01-01T00:00:00||91271|Prestação de serviços de profissões intelectuais
200053|200|Padrão|100|100|2026-01-01T00:00:00|||Fornecimento de medicamentos registrados na Anvisa, quando classificados como soros ou vacinas
200054|200|Padrão|100|100|2026-01-01T00:00:00|||Fornecimento de bem material pela cooperativa de produção agropecuária a associado não sujeito ao regime regular do IBS e da CBS
220001|220|Fixa|0|0|2026-01-01T00:00:00|2026-01-01T00:00:00||Incorporação imobiliária submetida ao regime especial de tributação
220002|220|Fixa|0|0|2026-01-01T00:00:00|2026-01-01T00:00:00||Incorporação imobiliária submetida ao regime especial de tributação
220003|220|Fixa|0|0|2026-01-01T00:00:00|2026-01-01T00:00:00||Alienação de imóvel decorrente de parcelamento do solo
221001|221|Fixa|0|0|2026-01-01T00:00:00||94871|Locação, cessão onerosa ou arrendamento de bem imóvel com alíquota sobre a receita bruta
221002|221|Fixa|0|0|2026-01-01T00:00:00|||Incorporação imobiliária submetida ao regime especial de tributação
221003|221|Fixa|0|0|2026-01-01T00:00:00|||Incorporação imobiliária submetida ao regime especial de tributação
221004|221|Fixa|0|0|2026-01-01T00:00:00|||Alienação de imóvel decorrente de parcelamento do solo
222001|222|Padrão|0|0|2026-01-01T00:00:00|||Transporte internacional de passageiros, caso os trechos de ida e volta sejam vendidos em conjunto
400001|400|Sem alíquota|0|0|2026-01-01T00:00:00|||Fornecimento de serviços de transporte público coletivo de passageiros rodoviário e metroviário
400002|400|Sem alíquota|0|0|2026-01-01T00:00:00|||Fornecimento de serviços de transporte público coletivo de passageiros rodoviário e metroviário com medição por quilômetro rodado
410001|410|Sem alíquota|0|0|2026-01-01T00:00:00|||Fornecimento de bonificações quando constem no documento fiscal e que não dependam de evento posterior
410002|410|Sem alíquota|0|0|2026-01-01T00:00:00|||Transferências entre estabelecimentos pertencentes ao mesmo contribuinte
410003|410|Sem alíquota|0|0|2026-01-01T00:00:00|||Doações sem contraprestação em benefício do doador
410004|410|Sem alíquota|0|0|2026-01-01T00:00:00|||Exportações de bens e serviços
410005|410|Sem alíquota|0|0|2026-01-01T00:00:00|||Fornecimentos realizados pela União, pelos Estados, pelo Distrito Federal e pelos Municípios
410006|410|Sem alíquota|0|0|2026-01-01T00:00:00|||Fornecimentos realizados por entidades religiosas e templos de qualquer culto
410007|410|Sem alíquota|0|0|2026-01-01T00:00:00|||Fornecimentos realizados por partidos políticos, entidades sindicais e instituições de educação e de assistência social
410008|410|Sem alíquota|0|0|2026-01-01T00:00:00|||Fornecimentos de livros, jornais, periódicos e do papel destinado a sua impressão
410009|410|Sem alíquota|0|0|2026-01-01T00:00:00|||Fornecimentos de fonogramas e videofonogramas musicais produzidos no Brasil
410010|410|Sem alíquota|0|0|2026-01-01T00:00:00|||Fornecimentos de serviço de comunicação nas modalidades de radiodifusão sonora e de sons e imagens de recepção livre e gratuita
410011|410|Sem alíquota|0|0|2026-01-01T00:00:00|||Fornecimentos de ouro, quando definido em lei como ativo financeiro ou instrumento cambial
410012|410|Sem alíquota|0|0|2026-01-01T00:00:00|||Fornecimento de condomínio edilício não optante pelo regime regular
410013|410|Sem alíquota|0|0|2026-01-01T00:00:00|||Exportações de combustíveis
410014|410|Sem alíquota|0|0|2026-01-01T00:00:00|||Fornecimento de produtor rural não contribuinte
410015|410|Sem alíquota|0|0|2026-01-01T00:00:00|||Fornecimento por transportador autônomo não contribuinte
410016|410|Sem alíquota|0|0|2026-01-01T00:00:00|||Fornecimento ou aquisição de resíduos sólidos
410017|410|Sem alíquota|0|0|2026-01-01T00:00:00|||Aquisição de bem móvel com crédito presumido sob condição de revenda realizada
410018|410|Sem alíquota|0|0|2026-01-01T00:00:00|||Operações relacionadas aos fundos garantidores e executores de políticas públicas
410019|410|Sem alíquota|0|0|2026-01-01T00:00:00|||Exclusão da gorjeta na base de cálculo no fornecimento de alimentação
410020|410|Sem alíquota|0|0|2026-01-01T00:00:00|||Exclusão do valor de intermediação na base de cálculo no fornecimento de alimentação
410021|410|Sem alíquota|0|0|2026-01-01T00:00:00|||Contribuição de que trata o art. 149-A da Constituição Federal
410022|410|Sem alíquota|0|0|2026-01-01T00:00:00|||Consolidação da propriedade do bem pelo credor
410023|410|Sem alíquota|0|0|2026-01-01T00:00:00|||Alienação de bens móveis ou imóveis que tenham sido objeto de garantia em que o prestador da garantia não seja contribuinte
410024|410|Sem alíquota|0|0|2026-01-01T00:00:00|||Consolidação da propriedade do bem pelo grupo de consórcio
410025|410|Sem alíquota|0|0|2026-01-01T00:00:00|||Alienação de bem que tenha sido objeto de garantia em que o prestador da garantia não seja contribuinte
410026|410|Sem alíquota|0|0|2026-01-01T00:00:00|||Doação com anulação de crédito
410027|410|Sem alíquota|0|0|2026-01-01T00:00:00|||Exportação de serviço ou de bem imaterial
410028|410|Sem alíquota|0|0|2026-01-01T00:00:00|||Operações com bens imóveis realizadas por pessoas físicas não consideradas contribuintes
410029|410|Sem alíquota|0|0|2026-01-01T00:00:00|||Operações acobertadas somente pelo ICMS
410030|410|Sem alíquota|0|0|2026-01-01T00:00:00|||Estorno de crédito por perecimento, deteriorização, roubo, furto ou extravio.
410031|410|Sem alíquota|0|0|2026-01-01T00:00:00|||Fornecimento em período anterior ao início de vigência de incidências de CBS e IBS
410032|410|Sem alíquota|0|0|2026-01-01T00:00:00|||Tributos incidentes na operação que não integram a base de cálculo do IBS e da CBS
410033|410|Sem alíquota|0|0|2026-01-01T00:00:00|||Operações de Fundos de Investimento Imobiliário (FII) e Fundos de Investimento nas Cadeias Produtivas do Agronegócio (Fiagro)
410034|410|Sem alíquota|0|0|2026-01-01T00:00:00|||Operações de fundos de investimento
410035|410|Sem alíquota|0|0|2026-01-01T00:00:00|||Fornecimento realizado por nanoempreendedor
410036|410|Sem alíquota|0|0|2026-01-01T00:00:00|||Descontos incondicionais
410037|410|Sem alíquota|0|0|2026-01-01T00:00:00|||Importação os bens materiais sem incidência de IBS e CBS
410999|410|Sem alíquota|0|0|2026-01-01T00:00:00|||Operações não onerosas sem previsão de tributação, não especificadas anteriormente
510001|510|Sem alíquota|0|0|2026-01-01T00:00:00||90281|Operações, sujeitas a diferimento, com energia elétrica, relativas à importação, geração, comercialização, distribuição e transmissão
515001|515|Padrão|60|60|2026-01-01T00:00:00||9|Operações, sujeitas a diferimento, com insumos agropecuários e aquícolas (Anexo IX)
550001|550|Sem alíquota|0|0|2026-01-01T00:00:00|||Exportações de bens materiais
550002|550|Sem alíquota|0|0|2026-01-01T00:00:00|||Regime de Trânsito
550003|550|Sem alíquota|0|0|2026-01-01T00:00:00|||Regimes de Depósito (art. 85)
550004|550|Sem alíquota|0|0|2026-01-01T00:00:00|||Regimes de Depósito (art. 87)
550005|550|Sem alíquota|0|0|2026-01-01T00:00:00|||Regimes de Depósito (art. 87, Parágrafo único)
550006|550|Sem alíquota|0|0|2026-01-01T00:00:00|||Regimes de Permanência Temporária
550007|550|Sem alíquota|0|0|2026-01-01T00:00:00|||Regimes de Aperfeiçoamento
550008|550|Sem alíquota|0|0|2026-01-01T00:00:00|||Importação de bens para o Regime de Repetro-Temporário
550009|550|Sem alíquota|0|0|2026-01-01T00:00:00|||GNL-Temporário
550010|550|Sem alíquota|0|0|2026-01-01T00:00:00|||Repetro-Permanente
550011|550|Sem alíquota|0|0|2026-01-01T00:00:00|||Repetro-Industrialização
550012|550|Sem alíquota|0|0|2026-01-01T00:00:00|||Repetro-Nacional
550013|550|Sem alíquota|0|0|2026-01-01T00:00:00|||Repetro-Entreposto
550014|550|Sem alíquota|0|0|2026-01-01T00:00:00|||Zona de Processamento de Exportação
550015|550|Sem alíquota|0|0|2026-01-01T00:00:00|||Regime Tributário para Incentivo à Modernização e à Ampliação da Estrutura Portuária
550016|550|Sem alíquota|0|0|2026-01-01T00:00:00|||Regime Especial de Incentivos para o Desenvolvimento da Infraestrutura
550017|550|Sem alíquota|0|0|2026-01-01T00:00:00|||Regime Tributário para Incentivo à Atividade Naval - Renaval (Art. 107, I)
550018|550|Sem alíquota|0|0|2026-01-01T00:00:00|||Desoneração da aquisição de bens de capital
550019|550|Sem alíquota|0|0|2026-01-01T00:00:00|||Importação de bem material por indústria incentivada para utilização na ZFM
550020|550|Sem alíquota|0|0|2026-01-01T00:00:00|||Áreas de livre comércio
550021|550|Sem alíquota|0|0|2026-01-01T00:00:00|||Industrialização destinada a exportações
550022|550|Sem alíquota|0|0|2026-01-01T00:00:00|||Regime Especial de Incentivos para a Produção de Hidrogênio de Baixa Emissão de Carbono (Rehidro)
550023|550|Sem alíquota|0|0|2026-01-01T00:00:00|||Operações com hidrocarbonetos líquidos derivados de petróleo não combustíveis ou de gás natural, inclusive nafta
550024|550|Sem alíquota|0|0|2026-01-01T00:00:00|||Regime Tributário para Incentivo à Atividade Naval - Renaval (Art. 107, II)
550025|550|Sem alíquota|0|0|2026-01-01T00:00:00|||Regime Tributário para Incentivo à Atividade Naval - Renaval (Art. 107, III)
620001|620|Uniforme setorial|0|0|2026-01-01T00:00:00||91721|Tributação monofásica sobre combustíveis
620002|620|Uniforme setorial|0|0|2026-01-01T00:00:00||91722|Tributação monofásica com responsabilidade pela retenção sobre combustíveis
620003|620|Uniforme setorial|0|0|2026-01-01T00:00:00||91723|Tributação monofásica com responsabilidade de retenção de tributos por terceiros
620004|620|Uniforme setorial|0|0|2026-01-01T00:00:00||91724|Tributação monofásica sobre mistura de EAC com gasolina A em percentual superior ao obrigatório
620005|620|Uniforme setorial|0|0|2026-01-01T00:00:00||91725|Tributação monofásica sobre mistura de EAC com gasolina A em percentual inferior ao obrigatório
620006|620|Uniforme setorial|0|0|2026-01-01T00:00:00||91726|Tributação monofásica sobre combustíveis cobrada anteriormente
620007|620|Sem alíquota|0|0|2026-01-01T00:00:00|||Perecimento, deteriorização, roubo, furto ou extravio no regime monofásico
800001|800|Sem alíquota|0|0|2026-01-01T00:00:00|||Fusão, cisão ou incorporação
800002|800|Sem alíquota|0|0|2026-01-01T00:00:00|||Transferência de crédito do associado, inclusive as cooperativas singulares
810001|810|Sem alíquota|0|0|2026-01-01T00:00:00|||Crédito presumido de IBS sobre o valor apurado nos fornecimentos a partir da ZFM
811001|811|Sem alíquota|0|0|2026-01-01T00:00:00|||Anulação de Crédito por Saídas Imunes/Isentas
811002|811|Sem alíquota|0|0|2026-01-01T00:00:00|||Débitos de notas fiscais não processadas na apuração
811003|811|Sem alíquota|0|0|2026-01-01T00:00:00|||Desenquadramento do Simples Nacional
820001|820|Sem alíquota|0|0|2026-01-01T00:00:00||92341|Documento com informações de fornecimento de serviços de planos de assistência à saúde elencados no art. 234 da Lei Complementar nº214, de 2025
820002|820|Sem alíquota|0|0|2026-01-01T00:00:00||92361|Documento com informações de fornecimento de serviços de planos de assistência funerária
820003|820|Sem alíquota|0|0|2026-01-01T00:00:00||92431|Documento com informações de fornecimento de serviços de planos de assistência à saúde de animais domésticos
820004|820|Sem alíquota|0|0|2026-01-01T00:00:00|||Documento com informações de prestação de serviços de consursos de prognósticos
820005|820|Sem alíquota|0|0|2026-01-01T00:00:00|||Documento com informações de alienação de bens imóveis
820006|820|Sem alíquota|0|0|2026-01-01T00:00:00||90111|Documento com informações de fornecimento de serviços de exploração de via
820007|820|Sem alíquota|0|0|2026-01-01T00:00:00||91811|Documento com informações de fornecimento de serviços financeiros
820008|820|Sem alíquota|0|0|2026-01-01T00:00:00|||Documento com informações de fornecimento de serviço continuado, mas com tributação realizada em fatura anterior
820009|820|Sem alíquota|0|0|2026-01-01T00:00:00|||Cobrança relativa a fornecimentos declarados em outro documento
830001|830|Sem alíquota|0|0|2026-01-01T00:00:00|||Documento com exclusão da BC da CBS e do IBS de energia elétrica fornecida pela distribuidora à UC
+101
View File
@@ -0,0 +1,101 @@
database,product,ncm,current_cclasstrib,proposed_cclasstrib,status,rule_id,reason,confidence_type,scenario
TESTESOFICIAIS,000004,07069000,,,AMBIGUOUS,RTC-A1,ANNEX_XV_NEEDS_OPERATION_OR_CONDITION,NONE,"WHATIF_NORMAL_ON (regime normal, RTC on)"
TESTESOFICIAIS,000005,07069000,,,AMBIGUOUS,RTC-A1,ANNEX_XV_NEEDS_OPERATION_OR_CONDITION,NONE,"WHATIF_NORMAL_ON (regime normal, RTC on)"
TESTESOFICIAIS,000006,07069000,,,AMBIGUOUS,RTC-A1,ANNEX_XV_NEEDS_OPERATION_OR_CONDITION,NONE,"WHATIF_NORMAL_ON (regime normal, RTC on)"
TESTESOFICIAIS,000008,07069000,,,AMBIGUOUS,RTC-A1,ANNEX_XV_NEEDS_OPERATION_OR_CONDITION,NONE,"WHATIF_NORMAL_ON (regime normal, RTC on)"
TESTESOFICIAIS,000009,07069000,,,AMBIGUOUS,RTC-A1,ANNEX_XV_NEEDS_OPERATION_OR_CONDITION,NONE,"WHATIF_NORMAL_ON (regime normal, RTC on)"
TESTESOFICIAIS,000010,07069000,,,AMBIGUOUS,RTC-A1,ANNEX_XV_NEEDS_OPERATION_OR_CONDITION,NONE,"WHATIF_NORMAL_ON (regime normal, RTC on)"
TESTESOFICIAIS,000011,07069000,,,AMBIGUOUS,RTC-A1,ANNEX_XV_NEEDS_OPERATION_OR_CONDITION,NONE,"WHATIF_NORMAL_ON (regime normal, RTC on)"
TESTESOFICIAIS,000013,07069000,,,AMBIGUOUS,RTC-A1,ANNEX_XV_NEEDS_OPERATION_OR_CONDITION,NONE,"WHATIF_NORMAL_ON (regime normal, RTC on)"
TESTESOFICIAIS,000015,07133321,,,AMBIGUOUS,RTC-A3,SPECIAL_REGIME_DEPENDS_ON_NATURE_OR_OPERATION_OF_THE_GOOD,NONE,"WHATIF_NORMAL_ON (regime normal, RTC on)"
TESTESOFICIAIS,000017,03061790,,,AMBIGUOUS,RTC-A1,ANNEX_VII_NEEDS_OPERATION_OR_CONDITION,NONE,"WHATIF_NORMAL_ON (regime normal, RTC on)"
TESTESOFICIAIS,000018,02071100,,,AMBIGUOUS,RTC-A1,ANNEX_I_NEEDS_OPERATION_OR_CONDITION,NONE,"WHATIF_NORMAL_ON (regime normal, RTC on)"
TESTESOFICIAIS,000019,07133321,,,AMBIGUOUS,RTC-A3,SPECIAL_REGIME_DEPENDS_ON_NATURE_OR_OPERATION_OF_THE_GOOD,NONE,"WHATIF_NORMAL_ON (regime normal, RTC on)"
TESTESOFICIAIS,000021,07089000,,,AMBIGUOUS,RTC-A1,ANNEX_XV_NEEDS_OPERATION_OR_CONDITION,NONE,"WHATIF_NORMAL_ON (regime normal, RTC on)"
TESTESOFICIAIS,000022,07089000,,,AMBIGUOUS,RTC-A1,ANNEX_XV_NEEDS_OPERATION_OR_CONDITION,NONE,"WHATIF_NORMAL_ON (regime normal, RTC on)"
TESTESOFICIAIS,000023,16025000,,000001,RESOLVED_WITH_EXPLICIT_RULE,RTC-R4,NCM_IN_NO_ANNEX_AND_NO_NATURE_BASED_CHAPTER,EXCLUSION_OF_ALL_SPECIAL_REGIMES,"WHATIF_NORMAL_ON (regime normal, RTC on)"
TESTESOFICIAIS,000024,19022000,,200034,RESOLVED_WITH_EXPLICIT_RULE,RTC-R3,ANNEX_VII_CLEAN_ITEM_UNIQUE_DEFAULT_CLASS,ANNEX_TEXT_MATCH,"WHATIF_NORMAL_ON (regime normal, RTC on)"
TESTESOFICIAIS,000025,04061010,,,AMBIGUOUS,RTC-A1,ANNEX_I_NEEDS_OPERATION_OR_CONDITION,NONE,"WHATIF_NORMAL_ON (regime normal, RTC on)"
TESTESOFICIAIS,000026,03055100,,,AMBIGUOUS,RTC-A3,SPECIAL_REGIME_DEPENDS_ON_NATURE_OR_OPERATION_OF_THE_GOOD,NONE,"WHATIF_NORMAL_ON (regime normal, RTC on)"
TESTESOFICIAIS,000027,19022000,,200034,RESOLVED_WITH_EXPLICIT_RULE,RTC-R3,ANNEX_VII_CLEAN_ITEM_UNIQUE_DEFAULT_CLASS,ANNEX_TEXT_MATCH,"WHATIF_NORMAL_ON (regime normal, RTC on)"
TESTESOFICIAIS,000028,19022000,,200034,RESOLVED_WITH_EXPLICIT_RULE,RTC-R3,ANNEX_VII_CLEAN_ITEM_UNIQUE_DEFAULT_CLASS,ANNEX_TEXT_MATCH,"WHATIF_NORMAL_ON (regime normal, RTC on)"
CONSTRUCAO,000001,72142000,,000001,RESOLVED_WITH_EXPLICIT_RULE,RTC-R4,NCM_IN_NO_ANNEX_AND_NO_NATURE_BASED_CHAPTER,EXCLUSION_OF_ALL_SPECIAL_REGIMES,"WHATIF_NORMAL_ON (regime normal, RTC on)"
CONSTRUCAO,000002,72142000,,000001,RESOLVED_WITH_EXPLICIT_RULE,RTC-R4,NCM_IN_NO_ANNEX_AND_NO_NATURE_BASED_CHAPTER,EXCLUSION_OF_ALL_SPECIAL_REGIMES,"WHATIF_NORMAL_ON (regime normal, RTC on)"
CONSTRUCAO,000003,72142000,,000001,RESOLVED_WITH_EXPLICIT_RULE,RTC-R4,NCM_IN_NO_ANNEX_AND_NO_NATURE_BASED_CHAPTER,EXCLUSION_OF_ALL_SPECIAL_REGIMES,"WHATIF_NORMAL_ON (regime normal, RTC on)"
CONSTRUCAO,000004,72142000,,000001,RESOLVED_WITH_EXPLICIT_RULE,RTC-R4,NCM_IN_NO_ANNEX_AND_NO_NATURE_BASED_CHAPTER,EXCLUSION_OF_ALL_SPECIAL_REGIMES,"WHATIF_NORMAL_ON (regime normal, RTC on)"
CONSTRUCAO,000005,72142000,,000001,RESOLVED_WITH_EXPLICIT_RULE,RTC-R4,NCM_IN_NO_ANNEX_AND_NO_NATURE_BASED_CHAPTER,EXCLUSION_OF_ALL_SPECIAL_REGIMES,"WHATIF_NORMAL_ON (regime normal, RTC on)"
CONSTRUCAO,000006,72142000,,000001,RESOLVED_WITH_EXPLICIT_RULE,RTC-R4,NCM_IN_NO_ANNEX_AND_NO_NATURE_BASED_CHAPTER,EXCLUSION_OF_ALL_SPECIAL_REGIMES,"WHATIF_NORMAL_ON (regime normal, RTC on)"
CONSTRUCAO,000007,72142000,,000001,RESOLVED_WITH_EXPLICIT_RULE,RTC-R4,NCM_IN_NO_ANNEX_AND_NO_NATURE_BASED_CHAPTER,EXCLUSION_OF_ALL_SPECIAL_REGIMES,"WHATIF_NORMAL_ON (regime normal, RTC on)"
CONSTRUCAO,000008,25232910,,000001,RESOLVED_WITH_EXPLICIT_RULE,RTC-R4,NCM_IN_NO_ANNEX_AND_NO_NATURE_BASED_CHAPTER,EXCLUSION_OF_ALL_SPECIAL_REGIMES,"WHATIF_NORMAL_ON (regime normal, RTC on)"
CONSTRUCAO,000009,25232910,,000001,RESOLVED_WITH_EXPLICIT_RULE,RTC-R4,NCM_IN_NO_ANNEX_AND_NO_NATURE_BASED_CHAPTER,EXCLUSION_OF_ALL_SPECIAL_REGIMES,"WHATIF_NORMAL_ON (regime normal, RTC on)"
CONSTRUCAO,000010,25232910,,000001,RESOLVED_WITH_EXPLICIT_RULE,RTC-R4,NCM_IN_NO_ANNEX_AND_NO_NATURE_BASED_CHAPTER,EXCLUSION_OF_ALL_SPECIAL_REGIMES,"WHATIF_NORMAL_ON (regime normal, RTC on)"
CONSTRUCAO,000011,68118200,,000001,RESOLVED_WITH_EXPLICIT_RULE,RTC-R4,NCM_IN_NO_ANNEX_AND_NO_NATURE_BASED_CHAPTER,EXCLUSION_OF_ALL_SPECIAL_REGIMES,"WHATIF_NORMAL_ON (regime normal, RTC on)"
CONSTRUCAO,000012,72142000,,000001,RESOLVED_WITH_EXPLICIT_RULE,RTC-R4,NCM_IN_NO_ANNEX_AND_NO_NATURE_BASED_CHAPTER,EXCLUSION_OF_ALL_SPECIAL_REGIMES,"WHATIF_NORMAL_ON (regime normal, RTC on)"
CONSTRUCAO,000013,68101100,,000001,RESOLVED_WITH_EXPLICIT_RULE,RTC-R4,NCM_IN_NO_ANNEX_AND_NO_NATURE_BASED_CHAPTER,EXCLUSION_OF_ALL_SPECIAL_REGIMES,"WHATIF_NORMAL_ON (regime normal, RTC on)"
CONSTRUCAO,000014,68101100,,000001,RESOLVED_WITH_EXPLICIT_RULE,RTC-R4,NCM_IN_NO_ANNEX_AND_NO_NATURE_BASED_CHAPTER,EXCLUSION_OF_ALL_SPECIAL_REGIMES,"WHATIF_NORMAL_ON (regime normal, RTC on)"
CONSTRUCAO,000015,40139000,,000001,RESOLVED_WITH_EXPLICIT_RULE,RTC-R4,NCM_IN_NO_ANNEX_AND_NO_NATURE_BASED_CHAPTER,EXCLUSION_OF_ALL_SPECIAL_REGIMES,"WHATIF_NORMAL_ON (regime normal, RTC on)"
CONSTRUCAO,000016,40139000,,000001,RESOLVED_WITH_EXPLICIT_RULE,RTC-R4,NCM_IN_NO_ANNEX_AND_NO_NATURE_BASED_CHAPTER,EXCLUSION_OF_ALL_SPECIAL_REGIMES,"WHATIF_NORMAL_ON (regime normal, RTC on)"
CONSTRUCAO,000017,30045020,,,AMBIGUOUS,RTC-A1,ANNEX_IX_NEEDS_OPERATION_OR_CONDITION,NONE,"WHATIF_NORMAL_ON (regime normal, RTC on)"
CONSTRUCAO,000018,56075090,,000001,RESOLVED_WITH_EXPLICIT_RULE,RTC-R4,NCM_IN_NO_ANNEX_AND_NO_NATURE_BASED_CHAPTER,EXCLUSION_OF_ALL_SPECIAL_REGIMES,"WHATIF_NORMAL_ON (regime normal, RTC on)"
CONSTRUCAO,000019,56075090,,000001,RESOLVED_WITH_EXPLICIT_RULE,RTC-R4,NCM_IN_NO_ANNEX_AND_NO_NATURE_BASED_CHAPTER,EXCLUSION_OF_ALL_SPECIAL_REGIMES,"WHATIF_NORMAL_ON (regime normal, RTC on)"
CONSTRUCAO,000020,56075090,,000001,RESOLVED_WITH_EXPLICIT_RULE,RTC-R4,NCM_IN_NO_ANNEX_AND_NO_NATURE_BASED_CHAPTER,EXCLUSION_OF_ALL_SPECIAL_REGIMES,"WHATIF_NORMAL_ON (regime normal, RTC on)"
MMMODAS,000002,19022000,,200034,RESOLVED_WITH_EXPLICIT_RULE,RTC-R3,ANNEX_VII_CLEAN_ITEM_UNIQUE_DEFAULT_CLASS,ANNEX_TEXT_MATCH,"WHATIF_NORMAL_ON (regime normal, RTC on)"
MMMODAS,000003,61046300,,000001,RESOLVED_WITH_EXPLICIT_RULE,RTC-R4,NCM_IN_NO_ANNEX_AND_NO_NATURE_BASED_CHAPTER,EXCLUSION_OF_ALL_SPECIAL_REGIMES,"WHATIF_NORMAL_ON (regime normal, RTC on)"
MMMODAS,000006,61046300,,000001,RESOLVED_WITH_EXPLICIT_RULE,RTC-R4,NCM_IN_NO_ANNEX_AND_NO_NATURE_BASED_CHAPTER,EXCLUSION_OF_ALL_SPECIAL_REGIMES,"WHATIF_NORMAL_ON (regime normal, RTC on)"
MMMODAS,000008,61046200,,000001,RESOLVED_WITH_EXPLICIT_RULE,RTC-R4,NCM_IN_NO_ANNEX_AND_NO_NATURE_BASED_CHAPTER,EXCLUSION_OF_ALL_SPECIAL_REGIMES,"WHATIF_NORMAL_ON (regime normal, RTC on)"
MMMODAS,000011,61091000,,000001,RESOLVED_WITH_EXPLICIT_RULE,RTC-R4,NCM_IN_NO_ANNEX_AND_NO_NATURE_BASED_CHAPTER,EXCLUSION_OF_ALL_SPECIAL_REGIMES,"WHATIF_NORMAL_ON (regime normal, RTC on)"
MMMODAS,000013,61051000,,000001,RESOLVED_WITH_EXPLICIT_RULE,RTC-R4,NCM_IN_NO_ANNEX_AND_NO_NATURE_BASED_CHAPTER,EXCLUSION_OF_ALL_SPECIAL_REGIMES,"WHATIF_NORMAL_ON (regime normal, RTC on)"
MMMODAS,000014,61051000,,000001,RESOLVED_WITH_EXPLICIT_RULE,RTC-R4,NCM_IN_NO_ANNEX_AND_NO_NATURE_BASED_CHAPTER,EXCLUSION_OF_ALL_SPECIAL_REGIMES,"WHATIF_NORMAL_ON (regime normal, RTC on)"
MMMODAS,000016,61051000,,000001,RESOLVED_WITH_EXPLICIT_RULE,RTC-R4,NCM_IN_NO_ANNEX_AND_NO_NATURE_BASED_CHAPTER,EXCLUSION_OF_ALL_SPECIAL_REGIMES,"WHATIF_NORMAL_ON (regime normal, RTC on)"
MMMODAS,000017,62111100,,000001,RESOLVED_WITH_EXPLICIT_RULE,RTC-R4,NCM_IN_NO_ANNEX_AND_NO_NATURE_BASED_CHAPTER,EXCLUSION_OF_ALL_SPECIAL_REGIMES,"WHATIF_NORMAL_ON (regime normal, RTC on)"
MMMODAS,000018,62111100,,000001,RESOLVED_WITH_EXPLICIT_RULE,RTC-R4,NCM_IN_NO_ANNEX_AND_NO_NATURE_BASED_CHAPTER,EXCLUSION_OF_ALL_SPECIAL_REGIMES,"WHATIF_NORMAL_ON (regime normal, RTC on)"
MMMODAS,000019,62034200,,000001,RESOLVED_WITH_EXPLICIT_RULE,RTC-R4,NCM_IN_NO_ANNEX_AND_NO_NATURE_BASED_CHAPTER,EXCLUSION_OF_ALL_SPECIAL_REGIMES,"WHATIF_NORMAL_ON (regime normal, RTC on)"
MMMODAS,000020,42021220,,000001,RESOLVED_WITH_EXPLICIT_RULE,RTC-R4,NCM_IN_NO_ANNEX_AND_NO_NATURE_BASED_CHAPTER,EXCLUSION_OF_ALL_SPECIAL_REGIMES,"WHATIF_NORMAL_ON (regime normal, RTC on)"
MMMODAS,000022,42021900,,000001,RESOLVED_WITH_EXPLICIT_RULE,RTC-R4,NCM_IN_NO_ANNEX_AND_NO_NATURE_BASED_CHAPTER,EXCLUSION_OF_ALL_SPECIAL_REGIMES,"WHATIF_NORMAL_ON (regime normal, RTC on)"
MMMODAS,000023,65040090,,000001,RESOLVED_WITH_EXPLICIT_RULE,RTC-R4,NCM_IN_NO_ANNEX_AND_NO_NATURE_BASED_CHAPTER,EXCLUSION_OF_ALL_SPECIAL_REGIMES,"WHATIF_NORMAL_ON (regime normal, RTC on)"
MMMODAS,000024,61091000,,000001,RESOLVED_WITH_EXPLICIT_RULE,RTC-R4,NCM_IN_NO_ANNEX_AND_NO_NATURE_BASED_CHAPTER,EXCLUSION_OF_ALL_SPECIAL_REGIMES,"WHATIF_NORMAL_ON (regime normal, RTC on)"
MMMODAS,000025,61091000,,000001,RESOLVED_WITH_EXPLICIT_RULE,RTC-R4,NCM_IN_NO_ANNEX_AND_NO_NATURE_BASED_CHAPTER,EXCLUSION_OF_ALL_SPECIAL_REGIMES,"WHATIF_NORMAL_ON (regime normal, RTC on)"
MMMODAS,000026,61061000,,000001,RESOLVED_WITH_EXPLICIT_RULE,RTC-R4,NCM_IN_NO_ANNEX_AND_NO_NATURE_BASED_CHAPTER,EXCLUSION_OF_ALL_SPECIAL_REGIMES,"WHATIF_NORMAL_ON (regime normal, RTC on)"
MMMODAS,000027,62064000,,000001,RESOLVED_WITH_EXPLICIT_RULE,RTC-R4,NCM_IN_NO_ANNEX_AND_NO_NATURE_BASED_CHAPTER,EXCLUSION_OF_ALL_SPECIAL_REGIMES,"WHATIF_NORMAL_ON (regime normal, RTC on)"
MMMODAS,000028,62046200,,000001,RESOLVED_WITH_EXPLICIT_RULE,RTC-R4,NCM_IN_NO_ANNEX_AND_NO_NATURE_BASED_CHAPTER,EXCLUSION_OF_ALL_SPECIAL_REGIMES,"WHATIF_NORMAL_ON (regime normal, RTC on)"
MMMODAS,000029,62034300,,000001,RESOLVED_WITH_EXPLICIT_RULE,RTC-R4,NCM_IN_NO_ANNEX_AND_NO_NATURE_BASED_CHAPTER,EXCLUSION_OF_ALL_SPECIAL_REGIMES,"WHATIF_NORMAL_ON (regime normal, RTC on)"
RINCAO,000001,21050090,,000001,RESOLVED_WITH_EXPLICIT_RULE,RTC-R4,NCM_IN_NO_ANNEX_AND_NO_NATURE_BASED_CHAPTER,EXCLUSION_OF_ALL_SPECIAL_REGIMES,"WHATIF_NORMAL_ON (regime normal, RTC on)"
RINCAO,000004,17041000,,,AMBIGUOUS,RTC-A1,ANNEX_X_NEEDS_OPERATION_OR_CONDITION,NONE,"WHATIF_NORMAL_ON (regime normal, RTC on)"
RINCAO,000005,17041000,,,AMBIGUOUS,RTC-A1,ANNEX_X_NEEDS_OPERATION_OR_CONDITION,NONE,"WHATIF_NORMAL_ON (regime normal, RTC on)"
RINCAO,000006,17041000,,,AMBIGUOUS,RTC-A1,ANNEX_X_NEEDS_OPERATION_OR_CONDITION,NONE,"WHATIF_NORMAL_ON (regime normal, RTC on)"
RINCAO,000007,17041000,,,AMBIGUOUS,RTC-A1,ANNEX_X_NEEDS_OPERATION_OR_CONDITION,NONE,"WHATIF_NORMAL_ON (regime normal, RTC on)"
RINCAO,000008,17041000,,,AMBIGUOUS,RTC-A1,ANNEX_X_NEEDS_OPERATION_OR_CONDITION,NONE,"WHATIF_NORMAL_ON (regime normal, RTC on)"
RINCAO,000009,17041000,,,AMBIGUOUS,RTC-A1,ANNEX_X_NEEDS_OPERATION_OR_CONDITION,NONE,"WHATIF_NORMAL_ON (regime normal, RTC on)"
RINCAO,000010,18063210,,000001,RESOLVED_WITH_EXPLICIT_RULE,RTC-R4,NCM_IN_NO_ANNEX_AND_NO_NATURE_BASED_CHAPTER,EXCLUSION_OF_ALL_SPECIAL_REGIMES,"WHATIF_NORMAL_ON (regime normal, RTC on)"
RINCAO,000011,18063210,,000001,RESOLVED_WITH_EXPLICIT_RULE,RTC-R4,NCM_IN_NO_ANNEX_AND_NO_NATURE_BASED_CHAPTER,EXCLUSION_OF_ALL_SPECIAL_REGIMES,"WHATIF_NORMAL_ON (regime normal, RTC on)"
RINCAO,000012,18063210,,000001,RESOLVED_WITH_EXPLICIT_RULE,RTC-R4,NCM_IN_NO_ANNEX_AND_NO_NATURE_BASED_CHAPTER,EXCLUSION_OF_ALL_SPECIAL_REGIMES,"WHATIF_NORMAL_ON (regime normal, RTC on)"
RINCAO,000013,18063210,,000001,RESOLVED_WITH_EXPLICIT_RULE,RTC-R4,NCM_IN_NO_ANNEX_AND_NO_NATURE_BASED_CHAPTER,EXCLUSION_OF_ALL_SPECIAL_REGIMES,"WHATIF_NORMAL_ON (regime normal, RTC on)"
RINCAO,000014,18063210,,000001,RESOLVED_WITH_EXPLICIT_RULE,RTC-R4,NCM_IN_NO_ANNEX_AND_NO_NATURE_BASED_CHAPTER,EXCLUSION_OF_ALL_SPECIAL_REGIMES,"WHATIF_NORMAL_ON (regime normal, RTC on)"
RINCAO,000015,18063210,,000001,RESOLVED_WITH_EXPLICIT_RULE,RTC-R4,NCM_IN_NO_ANNEX_AND_NO_NATURE_BASED_CHAPTER,EXCLUSION_OF_ALL_SPECIAL_REGIMES,"WHATIF_NORMAL_ON (regime normal, RTC on)"
RINCAO,000016,18063210,,000001,RESOLVED_WITH_EXPLICIT_RULE,RTC-R4,NCM_IN_NO_ANNEX_AND_NO_NATURE_BASED_CHAPTER,EXCLUSION_OF_ALL_SPECIAL_REGIMES,"WHATIF_NORMAL_ON (regime normal, RTC on)"
RINCAO,000017,21069090,,,AMBIGUOUS,RTC-A2,LISTED_IN_MORE_THAN_ONE_ANNEX,NONE,"WHATIF_NORMAL_ON (regime normal, RTC on)"
RINCAO,000018,18063210,,000001,RESOLVED_WITH_EXPLICIT_RULE,RTC-R4,NCM_IN_NO_ANNEX_AND_NO_NATURE_BASED_CHAPTER,EXCLUSION_OF_ALL_SPECIAL_REGIMES,"WHATIF_NORMAL_ON (regime normal, RTC on)"
RINCAO,000019,18063210,,000001,RESOLVED_WITH_EXPLICIT_RULE,RTC-R4,NCM_IN_NO_ANNEX_AND_NO_NATURE_BASED_CHAPTER,EXCLUSION_OF_ALL_SPECIAL_REGIMES,"WHATIF_NORMAL_ON (regime normal, RTC on)"
RINCAO,000020,21069090,,,AMBIGUOUS,RTC-A2,LISTED_IN_MORE_THAN_ONE_ANNEX,NONE,"WHATIF_NORMAL_ON (regime normal, RTC on)"
RINCAO,000021,18063210,,000001,RESOLVED_WITH_EXPLICIT_RULE,RTC-R4,NCM_IN_NO_ANNEX_AND_NO_NATURE_BASED_CHAPTER,EXCLUSION_OF_ALL_SPECIAL_REGIMES,"WHATIF_NORMAL_ON (regime normal, RTC on)"
RINCAO,000022,18063210,,000001,RESOLVED_WITH_EXPLICIT_RULE,RTC-R4,NCM_IN_NO_ANNEX_AND_NO_NATURE_BASED_CHAPTER,EXCLUSION_OF_ALL_SPECIAL_REGIMES,"WHATIF_NORMAL_ON (regime normal, RTC on)"
UNIAO,000008,33059000,,000001,RESOLVED_WITH_EXPLICIT_RULE,RTC-R4,NCM_IN_NO_ANNEX_AND_NO_NATURE_BASED_CHAPTER,EXCLUSION_OF_ALL_SPECIAL_REGIMES,"WHATIF_NORMAL_ON (regime normal, RTC on)"
UNIAO,000009,21039021,,000001,RESOLVED_WITH_EXPLICIT_RULE,RTC-R4,NCM_IN_NO_ANNEX_AND_NO_NATURE_BASED_CHAPTER,EXCLUSION_OF_ALL_SPECIAL_REGIMES,"WHATIF_NORMAL_ON (regime normal, RTC on)"
UNIAO,000014,19059010,,200034,RESOLVED_WITH_EXPLICIT_RULE,RTC-R3,ANNEX_VII_CLEAN_ITEM_UNIQUE_DEFAULT_CLASS,ANNEX_TEXT_MATCH,"WHATIF_NORMAL_ON (regime normal, RTC on)"
UNIAO,000015,95051000,,000001,RESOLVED_WITH_EXPLICIT_RULE,RTC-R4,NCM_IN_NO_ANNEX_AND_NO_NATURE_BASED_CHAPTER,EXCLUSION_OF_ALL_SPECIAL_REGIMES,"WHATIF_NORMAL_ON (regime normal, RTC on)"
UNIAO,000016,21032010,,000001,RESOLVED_WITH_EXPLICIT_RULE,RTC-R4,NCM_IN_NO_ANNEX_AND_NO_NATURE_BASED_CHAPTER,EXCLUSION_OF_ALL_SPECIAL_REGIMES,"WHATIF_NORMAL_ON (regime normal, RTC on)"
UNIAO,000017,95030099,,000001,RESOLVED_WITH_EXPLICIT_RULE,RTC-R4,NCM_IN_NO_ANNEX_AND_NO_NATURE_BASED_CHAPTER,EXCLUSION_OF_ALL_SPECIAL_REGIMES,"WHATIF_NORMAL_ON (regime normal, RTC on)"
UNIAO,000018,38089999,,,AMBIGUOUS,RTC-A1,ANNEX_IX_NEEDS_OPERATION_OR_CONDITION,NONE,"WHATIF_NORMAL_ON (regime normal, RTC on)"
UNIAO,000019,38089999,,,AMBIGUOUS,RTC-A1,ANNEX_IX_NEEDS_OPERATION_OR_CONDITION,NONE,"WHATIF_NORMAL_ON (regime normal, RTC on)"
UNIAO,000020,38089999,,,AMBIGUOUS,RTC-A1,ANNEX_IX_NEEDS_OPERATION_OR_CONDITION,NONE,"WHATIF_NORMAL_ON (regime normal, RTC on)"
UNIAO,000021,95051000,,000001,RESOLVED_WITH_EXPLICIT_RULE,RTC-R4,NCM_IN_NO_ANNEX_AND_NO_NATURE_BASED_CHAPTER,EXCLUSION_OF_ALL_SPECIAL_REGIMES,"WHATIF_NORMAL_ON (regime normal, RTC on)"
UNIAO,000022,22021000,,000001,RESOLVED_WITH_EXPLICIT_RULE,RTC-R4,NCM_IN_NO_ANNEX_AND_NO_NATURE_BASED_CHAPTER,EXCLUSION_OF_ALL_SPECIAL_REGIMES,"WHATIF_NORMAL_ON (regime normal, RTC on)"
UNIAO,000023,19053100,,000001,RESOLVED_WITH_EXPLICIT_RULE,RTC-R4,NCM_IN_NO_ANNEX_AND_NO_NATURE_BASED_CHAPTER,EXCLUSION_OF_ALL_SPECIAL_REGIMES,"WHATIF_NORMAL_ON (regime normal, RTC on)"
UNIAO,000024,82041100,,000001,RESOLVED_WITH_EXPLICIT_RULE,RTC-R4,NCM_IN_NO_ANNEX_AND_NO_NATURE_BASED_CHAPTER,EXCLUSION_OF_ALL_SPECIAL_REGIMES,"WHATIF_NORMAL_ON (regime normal, RTC on)"
UNIAO,000025,04089900,,,AMBIGUOUS,RTC-A3,SPECIAL_REGIME_DEPENDS_ON_NATURE_OR_OPERATION_OF_THE_GOOD,NONE,"WHATIF_NORMAL_ON (regime normal, RTC on)"
UNIAO,000026,15171000,,200003,RESOLVED_WITH_EXPLICIT_RULE,RTC-R3,ANNEX_I_CLEAN_ITEM_UNIQUE_DEFAULT_CLASS,ANNEX_TEXT_MATCH,"WHATIF_NORMAL_ON (regime normal, RTC on)"
UNIAO,000027,82054000,,000001,RESOLVED_WITH_EXPLICIT_RULE,RTC-R4,NCM_IN_NO_ANNEX_AND_NO_NATURE_BASED_CHAPTER,EXCLUSION_OF_ALL_SPECIAL_REGIMES,"WHATIF_NORMAL_ON (regime normal, RTC on)"
UNIAO,000028,04090000,,200034,RESOLVED_WITH_EXPLICIT_RULE,RTC-R3,ANNEX_VII_CLEAN_ITEM_UNIQUE_DEFAULT_CLASS,ANNEX_TEXT_MATCH,"WHATIF_NORMAL_ON (regime normal, RTC on)"
UNIAO,000029,19053100,,000001,RESOLVED_WITH_EXPLICIT_RULE,RTC-R4,NCM_IN_NO_ANNEX_AND_NO_NATURE_BASED_CHAPTER,EXCLUSION_OF_ALL_SPECIAL_REGIMES,"WHATIF_NORMAL_ON (regime normal, RTC on)"
UNIAO,000030,04022120,,,AMBIGUOUS,RTC-A1,ANNEX_I_NEEDS_OPERATION_OR_CONDITION,NONE,"WHATIF_NORMAL_ON (regime normal, RTC on)"
UNIAO,000031,19053100,,000001,RESOLVED_WITH_EXPLICIT_RULE,RTC-R4,NCM_IN_NO_ANNEX_AND_NO_NATURE_BASED_CHAPTER,EXCLUSION_OF_ALL_SPECIAL_REGIMES,"WHATIF_NORMAL_ON (regime normal, RTC on)"
1 database product ncm current_cclasstrib proposed_cclasstrib status rule_id reason confidence_type scenario
2 TESTESOFICIAIS 000004 07069000 AMBIGUOUS RTC-A1 ANNEX_XV_NEEDS_OPERATION_OR_CONDITION NONE WHATIF_NORMAL_ON (regime normal, RTC on)
3 TESTESOFICIAIS 000005 07069000 AMBIGUOUS RTC-A1 ANNEX_XV_NEEDS_OPERATION_OR_CONDITION NONE WHATIF_NORMAL_ON (regime normal, RTC on)
4 TESTESOFICIAIS 000006 07069000 AMBIGUOUS RTC-A1 ANNEX_XV_NEEDS_OPERATION_OR_CONDITION NONE WHATIF_NORMAL_ON (regime normal, RTC on)
5 TESTESOFICIAIS 000008 07069000 AMBIGUOUS RTC-A1 ANNEX_XV_NEEDS_OPERATION_OR_CONDITION NONE WHATIF_NORMAL_ON (regime normal, RTC on)
6 TESTESOFICIAIS 000009 07069000 AMBIGUOUS RTC-A1 ANNEX_XV_NEEDS_OPERATION_OR_CONDITION NONE WHATIF_NORMAL_ON (regime normal, RTC on)
7 TESTESOFICIAIS 000010 07069000 AMBIGUOUS RTC-A1 ANNEX_XV_NEEDS_OPERATION_OR_CONDITION NONE WHATIF_NORMAL_ON (regime normal, RTC on)
8 TESTESOFICIAIS 000011 07069000 AMBIGUOUS RTC-A1 ANNEX_XV_NEEDS_OPERATION_OR_CONDITION NONE WHATIF_NORMAL_ON (regime normal, RTC on)
9 TESTESOFICIAIS 000013 07069000 AMBIGUOUS RTC-A1 ANNEX_XV_NEEDS_OPERATION_OR_CONDITION NONE WHATIF_NORMAL_ON (regime normal, RTC on)
10 TESTESOFICIAIS 000015 07133321 AMBIGUOUS RTC-A3 SPECIAL_REGIME_DEPENDS_ON_NATURE_OR_OPERATION_OF_THE_GOOD NONE WHATIF_NORMAL_ON (regime normal, RTC on)
11 TESTESOFICIAIS 000017 03061790 AMBIGUOUS RTC-A1 ANNEX_VII_NEEDS_OPERATION_OR_CONDITION NONE WHATIF_NORMAL_ON (regime normal, RTC on)
12 TESTESOFICIAIS 000018 02071100 AMBIGUOUS RTC-A1 ANNEX_I_NEEDS_OPERATION_OR_CONDITION NONE WHATIF_NORMAL_ON (regime normal, RTC on)
13 TESTESOFICIAIS 000019 07133321 AMBIGUOUS RTC-A3 SPECIAL_REGIME_DEPENDS_ON_NATURE_OR_OPERATION_OF_THE_GOOD NONE WHATIF_NORMAL_ON (regime normal, RTC on)
14 TESTESOFICIAIS 000021 07089000 AMBIGUOUS RTC-A1 ANNEX_XV_NEEDS_OPERATION_OR_CONDITION NONE WHATIF_NORMAL_ON (regime normal, RTC on)
15 TESTESOFICIAIS 000022 07089000 AMBIGUOUS RTC-A1 ANNEX_XV_NEEDS_OPERATION_OR_CONDITION NONE WHATIF_NORMAL_ON (regime normal, RTC on)
16 TESTESOFICIAIS 000023 16025000 000001 RESOLVED_WITH_EXPLICIT_RULE RTC-R4 NCM_IN_NO_ANNEX_AND_NO_NATURE_BASED_CHAPTER EXCLUSION_OF_ALL_SPECIAL_REGIMES WHATIF_NORMAL_ON (regime normal, RTC on)
17 TESTESOFICIAIS 000024 19022000 200034 RESOLVED_WITH_EXPLICIT_RULE RTC-R3 ANNEX_VII_CLEAN_ITEM_UNIQUE_DEFAULT_CLASS ANNEX_TEXT_MATCH WHATIF_NORMAL_ON (regime normal, RTC on)
18 TESTESOFICIAIS 000025 04061010 AMBIGUOUS RTC-A1 ANNEX_I_NEEDS_OPERATION_OR_CONDITION NONE WHATIF_NORMAL_ON (regime normal, RTC on)
19 TESTESOFICIAIS 000026 03055100 AMBIGUOUS RTC-A3 SPECIAL_REGIME_DEPENDS_ON_NATURE_OR_OPERATION_OF_THE_GOOD NONE WHATIF_NORMAL_ON (regime normal, RTC on)
20 TESTESOFICIAIS 000027 19022000 200034 RESOLVED_WITH_EXPLICIT_RULE RTC-R3 ANNEX_VII_CLEAN_ITEM_UNIQUE_DEFAULT_CLASS ANNEX_TEXT_MATCH WHATIF_NORMAL_ON (regime normal, RTC on)
21 TESTESOFICIAIS 000028 19022000 200034 RESOLVED_WITH_EXPLICIT_RULE RTC-R3 ANNEX_VII_CLEAN_ITEM_UNIQUE_DEFAULT_CLASS ANNEX_TEXT_MATCH WHATIF_NORMAL_ON (regime normal, RTC on)
22 CONSTRUCAO 000001 72142000 000001 RESOLVED_WITH_EXPLICIT_RULE RTC-R4 NCM_IN_NO_ANNEX_AND_NO_NATURE_BASED_CHAPTER EXCLUSION_OF_ALL_SPECIAL_REGIMES WHATIF_NORMAL_ON (regime normal, RTC on)
23 CONSTRUCAO 000002 72142000 000001 RESOLVED_WITH_EXPLICIT_RULE RTC-R4 NCM_IN_NO_ANNEX_AND_NO_NATURE_BASED_CHAPTER EXCLUSION_OF_ALL_SPECIAL_REGIMES WHATIF_NORMAL_ON (regime normal, RTC on)
24 CONSTRUCAO 000003 72142000 000001 RESOLVED_WITH_EXPLICIT_RULE RTC-R4 NCM_IN_NO_ANNEX_AND_NO_NATURE_BASED_CHAPTER EXCLUSION_OF_ALL_SPECIAL_REGIMES WHATIF_NORMAL_ON (regime normal, RTC on)
25 CONSTRUCAO 000004 72142000 000001 RESOLVED_WITH_EXPLICIT_RULE RTC-R4 NCM_IN_NO_ANNEX_AND_NO_NATURE_BASED_CHAPTER EXCLUSION_OF_ALL_SPECIAL_REGIMES WHATIF_NORMAL_ON (regime normal, RTC on)
26 CONSTRUCAO 000005 72142000 000001 RESOLVED_WITH_EXPLICIT_RULE RTC-R4 NCM_IN_NO_ANNEX_AND_NO_NATURE_BASED_CHAPTER EXCLUSION_OF_ALL_SPECIAL_REGIMES WHATIF_NORMAL_ON (regime normal, RTC on)
27 CONSTRUCAO 000006 72142000 000001 RESOLVED_WITH_EXPLICIT_RULE RTC-R4 NCM_IN_NO_ANNEX_AND_NO_NATURE_BASED_CHAPTER EXCLUSION_OF_ALL_SPECIAL_REGIMES WHATIF_NORMAL_ON (regime normal, RTC on)
28 CONSTRUCAO 000007 72142000 000001 RESOLVED_WITH_EXPLICIT_RULE RTC-R4 NCM_IN_NO_ANNEX_AND_NO_NATURE_BASED_CHAPTER EXCLUSION_OF_ALL_SPECIAL_REGIMES WHATIF_NORMAL_ON (regime normal, RTC on)
29 CONSTRUCAO 000008 25232910 000001 RESOLVED_WITH_EXPLICIT_RULE RTC-R4 NCM_IN_NO_ANNEX_AND_NO_NATURE_BASED_CHAPTER EXCLUSION_OF_ALL_SPECIAL_REGIMES WHATIF_NORMAL_ON (regime normal, RTC on)
30 CONSTRUCAO 000009 25232910 000001 RESOLVED_WITH_EXPLICIT_RULE RTC-R4 NCM_IN_NO_ANNEX_AND_NO_NATURE_BASED_CHAPTER EXCLUSION_OF_ALL_SPECIAL_REGIMES WHATIF_NORMAL_ON (regime normal, RTC on)
31 CONSTRUCAO 000010 25232910 000001 RESOLVED_WITH_EXPLICIT_RULE RTC-R4 NCM_IN_NO_ANNEX_AND_NO_NATURE_BASED_CHAPTER EXCLUSION_OF_ALL_SPECIAL_REGIMES WHATIF_NORMAL_ON (regime normal, RTC on)
32 CONSTRUCAO 000011 68118200 000001 RESOLVED_WITH_EXPLICIT_RULE RTC-R4 NCM_IN_NO_ANNEX_AND_NO_NATURE_BASED_CHAPTER EXCLUSION_OF_ALL_SPECIAL_REGIMES WHATIF_NORMAL_ON (regime normal, RTC on)
33 CONSTRUCAO 000012 72142000 000001 RESOLVED_WITH_EXPLICIT_RULE RTC-R4 NCM_IN_NO_ANNEX_AND_NO_NATURE_BASED_CHAPTER EXCLUSION_OF_ALL_SPECIAL_REGIMES WHATIF_NORMAL_ON (regime normal, RTC on)
34 CONSTRUCAO 000013 68101100 000001 RESOLVED_WITH_EXPLICIT_RULE RTC-R4 NCM_IN_NO_ANNEX_AND_NO_NATURE_BASED_CHAPTER EXCLUSION_OF_ALL_SPECIAL_REGIMES WHATIF_NORMAL_ON (regime normal, RTC on)
35 CONSTRUCAO 000014 68101100 000001 RESOLVED_WITH_EXPLICIT_RULE RTC-R4 NCM_IN_NO_ANNEX_AND_NO_NATURE_BASED_CHAPTER EXCLUSION_OF_ALL_SPECIAL_REGIMES WHATIF_NORMAL_ON (regime normal, RTC on)
36 CONSTRUCAO 000015 40139000 000001 RESOLVED_WITH_EXPLICIT_RULE RTC-R4 NCM_IN_NO_ANNEX_AND_NO_NATURE_BASED_CHAPTER EXCLUSION_OF_ALL_SPECIAL_REGIMES WHATIF_NORMAL_ON (regime normal, RTC on)
37 CONSTRUCAO 000016 40139000 000001 RESOLVED_WITH_EXPLICIT_RULE RTC-R4 NCM_IN_NO_ANNEX_AND_NO_NATURE_BASED_CHAPTER EXCLUSION_OF_ALL_SPECIAL_REGIMES WHATIF_NORMAL_ON (regime normal, RTC on)
38 CONSTRUCAO 000017 30045020 AMBIGUOUS RTC-A1 ANNEX_IX_NEEDS_OPERATION_OR_CONDITION NONE WHATIF_NORMAL_ON (regime normal, RTC on)
39 CONSTRUCAO 000018 56075090 000001 RESOLVED_WITH_EXPLICIT_RULE RTC-R4 NCM_IN_NO_ANNEX_AND_NO_NATURE_BASED_CHAPTER EXCLUSION_OF_ALL_SPECIAL_REGIMES WHATIF_NORMAL_ON (regime normal, RTC on)
40 CONSTRUCAO 000019 56075090 000001 RESOLVED_WITH_EXPLICIT_RULE RTC-R4 NCM_IN_NO_ANNEX_AND_NO_NATURE_BASED_CHAPTER EXCLUSION_OF_ALL_SPECIAL_REGIMES WHATIF_NORMAL_ON (regime normal, RTC on)
41 CONSTRUCAO 000020 56075090 000001 RESOLVED_WITH_EXPLICIT_RULE RTC-R4 NCM_IN_NO_ANNEX_AND_NO_NATURE_BASED_CHAPTER EXCLUSION_OF_ALL_SPECIAL_REGIMES WHATIF_NORMAL_ON (regime normal, RTC on)
42 MMMODAS 000002 19022000 200034 RESOLVED_WITH_EXPLICIT_RULE RTC-R3 ANNEX_VII_CLEAN_ITEM_UNIQUE_DEFAULT_CLASS ANNEX_TEXT_MATCH WHATIF_NORMAL_ON (regime normal, RTC on)
43 MMMODAS 000003 61046300 000001 RESOLVED_WITH_EXPLICIT_RULE RTC-R4 NCM_IN_NO_ANNEX_AND_NO_NATURE_BASED_CHAPTER EXCLUSION_OF_ALL_SPECIAL_REGIMES WHATIF_NORMAL_ON (regime normal, RTC on)
44 MMMODAS 000006 61046300 000001 RESOLVED_WITH_EXPLICIT_RULE RTC-R4 NCM_IN_NO_ANNEX_AND_NO_NATURE_BASED_CHAPTER EXCLUSION_OF_ALL_SPECIAL_REGIMES WHATIF_NORMAL_ON (regime normal, RTC on)
45 MMMODAS 000008 61046200 000001 RESOLVED_WITH_EXPLICIT_RULE RTC-R4 NCM_IN_NO_ANNEX_AND_NO_NATURE_BASED_CHAPTER EXCLUSION_OF_ALL_SPECIAL_REGIMES WHATIF_NORMAL_ON (regime normal, RTC on)
46 MMMODAS 000011 61091000 000001 RESOLVED_WITH_EXPLICIT_RULE RTC-R4 NCM_IN_NO_ANNEX_AND_NO_NATURE_BASED_CHAPTER EXCLUSION_OF_ALL_SPECIAL_REGIMES WHATIF_NORMAL_ON (regime normal, RTC on)
47 MMMODAS 000013 61051000 000001 RESOLVED_WITH_EXPLICIT_RULE RTC-R4 NCM_IN_NO_ANNEX_AND_NO_NATURE_BASED_CHAPTER EXCLUSION_OF_ALL_SPECIAL_REGIMES WHATIF_NORMAL_ON (regime normal, RTC on)
48 MMMODAS 000014 61051000 000001 RESOLVED_WITH_EXPLICIT_RULE RTC-R4 NCM_IN_NO_ANNEX_AND_NO_NATURE_BASED_CHAPTER EXCLUSION_OF_ALL_SPECIAL_REGIMES WHATIF_NORMAL_ON (regime normal, RTC on)
49 MMMODAS 000016 61051000 000001 RESOLVED_WITH_EXPLICIT_RULE RTC-R4 NCM_IN_NO_ANNEX_AND_NO_NATURE_BASED_CHAPTER EXCLUSION_OF_ALL_SPECIAL_REGIMES WHATIF_NORMAL_ON (regime normal, RTC on)
50 MMMODAS 000017 62111100 000001 RESOLVED_WITH_EXPLICIT_RULE RTC-R4 NCM_IN_NO_ANNEX_AND_NO_NATURE_BASED_CHAPTER EXCLUSION_OF_ALL_SPECIAL_REGIMES WHATIF_NORMAL_ON (regime normal, RTC on)
51 MMMODAS 000018 62111100 000001 RESOLVED_WITH_EXPLICIT_RULE RTC-R4 NCM_IN_NO_ANNEX_AND_NO_NATURE_BASED_CHAPTER EXCLUSION_OF_ALL_SPECIAL_REGIMES WHATIF_NORMAL_ON (regime normal, RTC on)
52 MMMODAS 000019 62034200 000001 RESOLVED_WITH_EXPLICIT_RULE RTC-R4 NCM_IN_NO_ANNEX_AND_NO_NATURE_BASED_CHAPTER EXCLUSION_OF_ALL_SPECIAL_REGIMES WHATIF_NORMAL_ON (regime normal, RTC on)
53 MMMODAS 000020 42021220 000001 RESOLVED_WITH_EXPLICIT_RULE RTC-R4 NCM_IN_NO_ANNEX_AND_NO_NATURE_BASED_CHAPTER EXCLUSION_OF_ALL_SPECIAL_REGIMES WHATIF_NORMAL_ON (regime normal, RTC on)
54 MMMODAS 000022 42021900 000001 RESOLVED_WITH_EXPLICIT_RULE RTC-R4 NCM_IN_NO_ANNEX_AND_NO_NATURE_BASED_CHAPTER EXCLUSION_OF_ALL_SPECIAL_REGIMES WHATIF_NORMAL_ON (regime normal, RTC on)
55 MMMODAS 000023 65040090 000001 RESOLVED_WITH_EXPLICIT_RULE RTC-R4 NCM_IN_NO_ANNEX_AND_NO_NATURE_BASED_CHAPTER EXCLUSION_OF_ALL_SPECIAL_REGIMES WHATIF_NORMAL_ON (regime normal, RTC on)
56 MMMODAS 000024 61091000 000001 RESOLVED_WITH_EXPLICIT_RULE RTC-R4 NCM_IN_NO_ANNEX_AND_NO_NATURE_BASED_CHAPTER EXCLUSION_OF_ALL_SPECIAL_REGIMES WHATIF_NORMAL_ON (regime normal, RTC on)
57 MMMODAS 000025 61091000 000001 RESOLVED_WITH_EXPLICIT_RULE RTC-R4 NCM_IN_NO_ANNEX_AND_NO_NATURE_BASED_CHAPTER EXCLUSION_OF_ALL_SPECIAL_REGIMES WHATIF_NORMAL_ON (regime normal, RTC on)
58 MMMODAS 000026 61061000 000001 RESOLVED_WITH_EXPLICIT_RULE RTC-R4 NCM_IN_NO_ANNEX_AND_NO_NATURE_BASED_CHAPTER EXCLUSION_OF_ALL_SPECIAL_REGIMES WHATIF_NORMAL_ON (regime normal, RTC on)
59 MMMODAS 000027 62064000 000001 RESOLVED_WITH_EXPLICIT_RULE RTC-R4 NCM_IN_NO_ANNEX_AND_NO_NATURE_BASED_CHAPTER EXCLUSION_OF_ALL_SPECIAL_REGIMES WHATIF_NORMAL_ON (regime normal, RTC on)
60 MMMODAS 000028 62046200 000001 RESOLVED_WITH_EXPLICIT_RULE RTC-R4 NCM_IN_NO_ANNEX_AND_NO_NATURE_BASED_CHAPTER EXCLUSION_OF_ALL_SPECIAL_REGIMES WHATIF_NORMAL_ON (regime normal, RTC on)
61 MMMODAS 000029 62034300 000001 RESOLVED_WITH_EXPLICIT_RULE RTC-R4 NCM_IN_NO_ANNEX_AND_NO_NATURE_BASED_CHAPTER EXCLUSION_OF_ALL_SPECIAL_REGIMES WHATIF_NORMAL_ON (regime normal, RTC on)
62 RINCAO 000001 21050090 000001 RESOLVED_WITH_EXPLICIT_RULE RTC-R4 NCM_IN_NO_ANNEX_AND_NO_NATURE_BASED_CHAPTER EXCLUSION_OF_ALL_SPECIAL_REGIMES WHATIF_NORMAL_ON (regime normal, RTC on)
63 RINCAO 000004 17041000 AMBIGUOUS RTC-A1 ANNEX_X_NEEDS_OPERATION_OR_CONDITION NONE WHATIF_NORMAL_ON (regime normal, RTC on)
64 RINCAO 000005 17041000 AMBIGUOUS RTC-A1 ANNEX_X_NEEDS_OPERATION_OR_CONDITION NONE WHATIF_NORMAL_ON (regime normal, RTC on)
65 RINCAO 000006 17041000 AMBIGUOUS RTC-A1 ANNEX_X_NEEDS_OPERATION_OR_CONDITION NONE WHATIF_NORMAL_ON (regime normal, RTC on)
66 RINCAO 000007 17041000 AMBIGUOUS RTC-A1 ANNEX_X_NEEDS_OPERATION_OR_CONDITION NONE WHATIF_NORMAL_ON (regime normal, RTC on)
67 RINCAO 000008 17041000 AMBIGUOUS RTC-A1 ANNEX_X_NEEDS_OPERATION_OR_CONDITION NONE WHATIF_NORMAL_ON (regime normal, RTC on)
68 RINCAO 000009 17041000 AMBIGUOUS RTC-A1 ANNEX_X_NEEDS_OPERATION_OR_CONDITION NONE WHATIF_NORMAL_ON (regime normal, RTC on)
69 RINCAO 000010 18063210 000001 RESOLVED_WITH_EXPLICIT_RULE RTC-R4 NCM_IN_NO_ANNEX_AND_NO_NATURE_BASED_CHAPTER EXCLUSION_OF_ALL_SPECIAL_REGIMES WHATIF_NORMAL_ON (regime normal, RTC on)
70 RINCAO 000011 18063210 000001 RESOLVED_WITH_EXPLICIT_RULE RTC-R4 NCM_IN_NO_ANNEX_AND_NO_NATURE_BASED_CHAPTER EXCLUSION_OF_ALL_SPECIAL_REGIMES WHATIF_NORMAL_ON (regime normal, RTC on)
71 RINCAO 000012 18063210 000001 RESOLVED_WITH_EXPLICIT_RULE RTC-R4 NCM_IN_NO_ANNEX_AND_NO_NATURE_BASED_CHAPTER EXCLUSION_OF_ALL_SPECIAL_REGIMES WHATIF_NORMAL_ON (regime normal, RTC on)
72 RINCAO 000013 18063210 000001 RESOLVED_WITH_EXPLICIT_RULE RTC-R4 NCM_IN_NO_ANNEX_AND_NO_NATURE_BASED_CHAPTER EXCLUSION_OF_ALL_SPECIAL_REGIMES WHATIF_NORMAL_ON (regime normal, RTC on)
73 RINCAO 000014 18063210 000001 RESOLVED_WITH_EXPLICIT_RULE RTC-R4 NCM_IN_NO_ANNEX_AND_NO_NATURE_BASED_CHAPTER EXCLUSION_OF_ALL_SPECIAL_REGIMES WHATIF_NORMAL_ON (regime normal, RTC on)
74 RINCAO 000015 18063210 000001 RESOLVED_WITH_EXPLICIT_RULE RTC-R4 NCM_IN_NO_ANNEX_AND_NO_NATURE_BASED_CHAPTER EXCLUSION_OF_ALL_SPECIAL_REGIMES WHATIF_NORMAL_ON (regime normal, RTC on)
75 RINCAO 000016 18063210 000001 RESOLVED_WITH_EXPLICIT_RULE RTC-R4 NCM_IN_NO_ANNEX_AND_NO_NATURE_BASED_CHAPTER EXCLUSION_OF_ALL_SPECIAL_REGIMES WHATIF_NORMAL_ON (regime normal, RTC on)
76 RINCAO 000017 21069090 AMBIGUOUS RTC-A2 LISTED_IN_MORE_THAN_ONE_ANNEX NONE WHATIF_NORMAL_ON (regime normal, RTC on)
77 RINCAO 000018 18063210 000001 RESOLVED_WITH_EXPLICIT_RULE RTC-R4 NCM_IN_NO_ANNEX_AND_NO_NATURE_BASED_CHAPTER EXCLUSION_OF_ALL_SPECIAL_REGIMES WHATIF_NORMAL_ON (regime normal, RTC on)
78 RINCAO 000019 18063210 000001 RESOLVED_WITH_EXPLICIT_RULE RTC-R4 NCM_IN_NO_ANNEX_AND_NO_NATURE_BASED_CHAPTER EXCLUSION_OF_ALL_SPECIAL_REGIMES WHATIF_NORMAL_ON (regime normal, RTC on)
79 RINCAO 000020 21069090 AMBIGUOUS RTC-A2 LISTED_IN_MORE_THAN_ONE_ANNEX NONE WHATIF_NORMAL_ON (regime normal, RTC on)
80 RINCAO 000021 18063210 000001 RESOLVED_WITH_EXPLICIT_RULE RTC-R4 NCM_IN_NO_ANNEX_AND_NO_NATURE_BASED_CHAPTER EXCLUSION_OF_ALL_SPECIAL_REGIMES WHATIF_NORMAL_ON (regime normal, RTC on)
81 RINCAO 000022 18063210 000001 RESOLVED_WITH_EXPLICIT_RULE RTC-R4 NCM_IN_NO_ANNEX_AND_NO_NATURE_BASED_CHAPTER EXCLUSION_OF_ALL_SPECIAL_REGIMES WHATIF_NORMAL_ON (regime normal, RTC on)
82 UNIAO 000008 33059000 000001 RESOLVED_WITH_EXPLICIT_RULE RTC-R4 NCM_IN_NO_ANNEX_AND_NO_NATURE_BASED_CHAPTER EXCLUSION_OF_ALL_SPECIAL_REGIMES WHATIF_NORMAL_ON (regime normal, RTC on)
83 UNIAO 000009 21039021 000001 RESOLVED_WITH_EXPLICIT_RULE RTC-R4 NCM_IN_NO_ANNEX_AND_NO_NATURE_BASED_CHAPTER EXCLUSION_OF_ALL_SPECIAL_REGIMES WHATIF_NORMAL_ON (regime normal, RTC on)
84 UNIAO 000014 19059010 200034 RESOLVED_WITH_EXPLICIT_RULE RTC-R3 ANNEX_VII_CLEAN_ITEM_UNIQUE_DEFAULT_CLASS ANNEX_TEXT_MATCH WHATIF_NORMAL_ON (regime normal, RTC on)
85 UNIAO 000015 95051000 000001 RESOLVED_WITH_EXPLICIT_RULE RTC-R4 NCM_IN_NO_ANNEX_AND_NO_NATURE_BASED_CHAPTER EXCLUSION_OF_ALL_SPECIAL_REGIMES WHATIF_NORMAL_ON (regime normal, RTC on)
86 UNIAO 000016 21032010 000001 RESOLVED_WITH_EXPLICIT_RULE RTC-R4 NCM_IN_NO_ANNEX_AND_NO_NATURE_BASED_CHAPTER EXCLUSION_OF_ALL_SPECIAL_REGIMES WHATIF_NORMAL_ON (regime normal, RTC on)
87 UNIAO 000017 95030099 000001 RESOLVED_WITH_EXPLICIT_RULE RTC-R4 NCM_IN_NO_ANNEX_AND_NO_NATURE_BASED_CHAPTER EXCLUSION_OF_ALL_SPECIAL_REGIMES WHATIF_NORMAL_ON (regime normal, RTC on)
88 UNIAO 000018 38089999 AMBIGUOUS RTC-A1 ANNEX_IX_NEEDS_OPERATION_OR_CONDITION NONE WHATIF_NORMAL_ON (regime normal, RTC on)
89 UNIAO 000019 38089999 AMBIGUOUS RTC-A1 ANNEX_IX_NEEDS_OPERATION_OR_CONDITION NONE WHATIF_NORMAL_ON (regime normal, RTC on)
90 UNIAO 000020 38089999 AMBIGUOUS RTC-A1 ANNEX_IX_NEEDS_OPERATION_OR_CONDITION NONE WHATIF_NORMAL_ON (regime normal, RTC on)
91 UNIAO 000021 95051000 000001 RESOLVED_WITH_EXPLICIT_RULE RTC-R4 NCM_IN_NO_ANNEX_AND_NO_NATURE_BASED_CHAPTER EXCLUSION_OF_ALL_SPECIAL_REGIMES WHATIF_NORMAL_ON (regime normal, RTC on)
92 UNIAO 000022 22021000 000001 RESOLVED_WITH_EXPLICIT_RULE RTC-R4 NCM_IN_NO_ANNEX_AND_NO_NATURE_BASED_CHAPTER EXCLUSION_OF_ALL_SPECIAL_REGIMES WHATIF_NORMAL_ON (regime normal, RTC on)
93 UNIAO 000023 19053100 000001 RESOLVED_WITH_EXPLICIT_RULE RTC-R4 NCM_IN_NO_ANNEX_AND_NO_NATURE_BASED_CHAPTER EXCLUSION_OF_ALL_SPECIAL_REGIMES WHATIF_NORMAL_ON (regime normal, RTC on)
94 UNIAO 000024 82041100 000001 RESOLVED_WITH_EXPLICIT_RULE RTC-R4 NCM_IN_NO_ANNEX_AND_NO_NATURE_BASED_CHAPTER EXCLUSION_OF_ALL_SPECIAL_REGIMES WHATIF_NORMAL_ON (regime normal, RTC on)
95 UNIAO 000025 04089900 AMBIGUOUS RTC-A3 SPECIAL_REGIME_DEPENDS_ON_NATURE_OR_OPERATION_OF_THE_GOOD NONE WHATIF_NORMAL_ON (regime normal, RTC on)
96 UNIAO 000026 15171000 200003 RESOLVED_WITH_EXPLICIT_RULE RTC-R3 ANNEX_I_CLEAN_ITEM_UNIQUE_DEFAULT_CLASS ANNEX_TEXT_MATCH WHATIF_NORMAL_ON (regime normal, RTC on)
97 UNIAO 000027 82054000 000001 RESOLVED_WITH_EXPLICIT_RULE RTC-R4 NCM_IN_NO_ANNEX_AND_NO_NATURE_BASED_CHAPTER EXCLUSION_OF_ALL_SPECIAL_REGIMES WHATIF_NORMAL_ON (regime normal, RTC on)
98 UNIAO 000028 04090000 200034 RESOLVED_WITH_EXPLICIT_RULE RTC-R3 ANNEX_VII_CLEAN_ITEM_UNIQUE_DEFAULT_CLASS ANNEX_TEXT_MATCH WHATIF_NORMAL_ON (regime normal, RTC on)
99 UNIAO 000029 19053100 000001 RESOLVED_WITH_EXPLICIT_RULE RTC-R4 NCM_IN_NO_ANNEX_AND_NO_NATURE_BASED_CHAPTER EXCLUSION_OF_ALL_SPECIAL_REGIMES WHATIF_NORMAL_ON (regime normal, RTC on)
100 UNIAO 000030 04022120 AMBIGUOUS RTC-A1 ANNEX_I_NEEDS_OPERATION_OR_CONDITION NONE WHATIF_NORMAL_ON (regime normal, RTC on)
101 UNIAO 000031 19053100 000001 RESOLVED_WITH_EXPLICIT_RULE RTC-R4 NCM_IN_NO_ANNEX_AND_NO_NATURE_BASED_CHAPTER EXCLUSION_OF_ALL_SPECIAL_REGIMES WHATIF_NORMAL_ON (regime normal, RTC on)
+941
View File
@@ -0,0 +1,941 @@
{
"mode": "READ_ONLY_DRY_RUN",
"FIREBIRD_WRITES": 0,
"WRITE_ATTEMPTS": 0,
"WRITE_SUCCESSES": 0,
"databases": {
"TESTESOFICIAIS": {
"crt": "1",
"total_products": 521,
"passes": {
"AS_FOUND": {
"crt": "1",
"rtc_enabled": true,
"products": 521,
"resolution_summary": {
"total": 521,
"valid_existing": 0,
"ready_to_apply": 0,
"ambiguous": 0,
"insufficient": 521,
"conflicts": 0,
"official_mismatch": 0,
"not_applicable": 0,
"other_blocked": 0,
"by_rule": {
"RTC-R1": 521
},
"by_reason": {
"SIMPLES_ORIENTATION_PENDING_OFFICIAL_NT": 521
},
"catalog_compatibility": {
"compatibility": "MATERIAL_MISMATCH",
"counts": {
"MATCH": 131,
"LOCAL_ONLY": 0,
"OFFICIAL_ONLY": 19,
"MISMATCH": 11,
"EXPIRED": 3
},
"expired_present_locally": 3
}
},
"by_status": {
"INSUFFICIENT_EVIDENCE": 521
},
"rtc_blocker_types": {
"CCLASSTRIB_MISSING_UNRESOLVED": 521
},
"AUTO_APPLY_CANDIDATES": 0,
"context": {
"rtc_param": "1",
"rtc_source": "PARAMETROS.USAR_REFORMA_TRIBUTARIA",
"rtc_observed_at": "2026-09-25T00:11:38+00:00",
"local_catalog_hash": "1da77aec27fbde88488d1edf562f3af315ab2b5d06efb2e0b2f1ed606585d459",
"official_catalog": {
"sha256": "1448cb63a41bdb67ea30b4e11f4dc200d9568542c6b9335b6cff87a4fd664654",
"publication": "2026-06-22",
"file": "cClassTrib 2026-06-22.xlsx"
},
"catalog_integrity": {
"classes": 145,
"cst": 18,
"orphan_classes": 0,
"bad_class_format": 0,
"bad_cst_format": 0,
"duplicates": 0,
"valid": true
}
}
},
"WHATIF_RTC_ON": {
"crt": "1",
"rtc_enabled": true,
"products": 521,
"resolution_summary": {
"total": 521,
"valid_existing": 0,
"ready_to_apply": 0,
"ambiguous": 0,
"insufficient": 521,
"conflicts": 0,
"official_mismatch": 0,
"not_applicable": 0,
"other_blocked": 0,
"by_rule": {
"RTC-R1": 521
},
"by_reason": {
"SIMPLES_ORIENTATION_PENDING_OFFICIAL_NT": 521
},
"catalog_compatibility": {
"compatibility": "MATERIAL_MISMATCH",
"counts": {
"MATCH": 131,
"LOCAL_ONLY": 0,
"OFFICIAL_ONLY": 19,
"MISMATCH": 11,
"EXPIRED": 3
},
"expired_present_locally": 3
}
},
"by_status": {
"INSUFFICIENT_EVIDENCE": 521
},
"rtc_blocker_types": {
"CCLASSTRIB_MISSING_UNRESOLVED": 521
},
"AUTO_APPLY_CANDIDATES": 0,
"context": {
"rtc_param": "1",
"rtc_source": "SIMULATION",
"rtc_observed_at": "2026-09-25T00:11:38+00:00",
"local_catalog_hash": "1da77aec27fbde88488d1edf562f3af315ab2b5d06efb2e0b2f1ed606585d459",
"official_catalog": {
"sha256": "1448cb63a41bdb67ea30b4e11f4dc200d9568542c6b9335b6cff87a4fd664654",
"publication": "2026-06-22",
"file": "cClassTrib 2026-06-22.xlsx"
},
"catalog_integrity": {
"classes": 145,
"cst": 18,
"orphan_classes": 0,
"bad_class_format": 0,
"bad_cst_format": 0,
"duplicates": 0,
"valid": true
}
}
},
"WHATIF_NORMAL_ON": {
"crt": "3",
"rtc_enabled": true,
"products": 521,
"resolution_summary": {
"total": 521,
"valid_existing": 0,
"ready_to_apply": 280,
"ambiguous": 215,
"insufficient": 26,
"conflicts": 0,
"official_mismatch": 0,
"not_applicable": 0,
"other_blocked": 0,
"by_rule": {
"RTC-A1": 41,
"RTC-A3": 10,
"RTC-R2": 26,
"RTC-R4": 203,
"RTC-R3": 77,
"RTC-A2": 164
},
"by_reason": {
"ANNEX_XV_NEEDS_OPERATION_OR_CONDITION": 14,
"SPECIAL_REGIME_DEPENDS_ON_NATURE_OR_OPERATION_OF_THE_GOOD": 10,
"NO_VALID_NCM": 26,
"ANNEX_VII_NEEDS_OPERATION_OR_CONDITION": 10,
"ANNEX_I_NEEDS_OPERATION_OR_CONDITION": 14,
"NCM_IN_NO_ANNEX_AND_NO_NATURE_BASED_CHAPTER": 203,
"ANNEX_VII_CLEAN_ITEM_UNIQUE_DEFAULT_CLASS": 75,
"ANNEX_XV_CLEAN_ITEM_UNIQUE_DEFAULT_CLASS": 2,
"LISTED_IN_MORE_THAN_ONE_ANNEX": 164,
"ANNEX_V_NEEDS_OPERATION_OR_CONDITION": 3
},
"catalog_compatibility": {
"compatibility": "MATERIAL_MISMATCH",
"counts": {
"MATCH": 131,
"LOCAL_ONLY": 0,
"OFFICIAL_ONLY": 19,
"MISMATCH": 11,
"EXPIRED": 3
},
"expired_present_locally": 3
}
},
"by_status": {
"AMBIGUOUS": 215,
"INSUFFICIENT_EVIDENCE": 26,
"RESOLVED_WITH_EXPLICIT_RULE": 280
},
"rtc_blocker_types": {
"CCLASSTRIB_AMBIGUOUS": 215,
"RTC_RULE_EVIDENCE_MISSING": 26,
"CCLASSTRIB_MISSING": 280
},
"AUTO_APPLY_CANDIDATES": 280,
"context": {
"rtc_param": "1",
"rtc_source": "SIMULATION",
"rtc_observed_at": "2026-09-25T00:11:38+00:00",
"local_catalog_hash": "1da77aec27fbde88488d1edf562f3af315ab2b5d06efb2e0b2f1ed606585d459",
"official_catalog": {
"sha256": "1448cb63a41bdb67ea30b4e11f4dc200d9568542c6b9335b6cff87a4fd664654",
"publication": "2026-06-22",
"file": "cClassTrib 2026-06-22.xlsx"
},
"catalog_integrity": {
"classes": 145,
"cst": 18,
"orphan_classes": 0,
"bad_class_format": 0,
"bad_cst_format": 0,
"duplicates": 0,
"valid": true
}
}
}
}
},
"CONSTRUCAO": {
"crt": "1",
"total_products": 3712,
"passes": {
"AS_FOUND": {
"crt": "1",
"rtc_enabled": true,
"products": 3712,
"resolution_summary": {
"total": 3712,
"valid_existing": 0,
"ready_to_apply": 0,
"ambiguous": 0,
"insufficient": 3712,
"conflicts": 0,
"official_mismatch": 0,
"not_applicable": 0,
"other_blocked": 0,
"by_rule": {
"RTC-R1": 3712
},
"by_reason": {
"SIMPLES_ORIENTATION_PENDING_OFFICIAL_NT": 3712
},
"catalog_compatibility": {
"compatibility": "MATERIAL_MISMATCH",
"counts": {
"MATCH": 131,
"LOCAL_ONLY": 0,
"OFFICIAL_ONLY": 19,
"MISMATCH": 11,
"EXPIRED": 3
},
"expired_present_locally": 3
}
},
"by_status": {
"INSUFFICIENT_EVIDENCE": 3712
},
"rtc_blocker_types": {
"CCLASSTRIB_MISSING_UNRESOLVED": 3712
},
"AUTO_APPLY_CANDIDATES": 0,
"context": {
"rtc_param": "1",
"rtc_source": "PARAMETROS.USAR_REFORMA_TRIBUTARIA",
"rtc_observed_at": "2026-09-25T00:11:39+00:00",
"local_catalog_hash": "be8c5899fff3e236c6c77bbb887087c0a2db048aeff763a5823676c7b478a151",
"official_catalog": {
"sha256": "1448cb63a41bdb67ea30b4e11f4dc200d9568542c6b9335b6cff87a4fd664654",
"publication": "2026-06-22",
"file": "cClassTrib 2026-06-22.xlsx"
},
"catalog_integrity": {
"classes": 145,
"cst": 18,
"orphan_classes": 0,
"bad_class_format": 0,
"bad_cst_format": 0,
"duplicates": 0,
"valid": true
}
}
},
"WHATIF_RTC_ON": {
"crt": "1",
"rtc_enabled": true,
"products": 3712,
"resolution_summary": {
"total": 3712,
"valid_existing": 0,
"ready_to_apply": 0,
"ambiguous": 0,
"insufficient": 3712,
"conflicts": 0,
"official_mismatch": 0,
"not_applicable": 0,
"other_blocked": 0,
"by_rule": {
"RTC-R1": 3712
},
"by_reason": {
"SIMPLES_ORIENTATION_PENDING_OFFICIAL_NT": 3712
},
"catalog_compatibility": {
"compatibility": "MATERIAL_MISMATCH",
"counts": {
"MATCH": 131,
"LOCAL_ONLY": 0,
"OFFICIAL_ONLY": 19,
"MISMATCH": 11,
"EXPIRED": 3
},
"expired_present_locally": 3
}
},
"by_status": {
"INSUFFICIENT_EVIDENCE": 3712
},
"rtc_blocker_types": {
"CCLASSTRIB_MISSING_UNRESOLVED": 3712
},
"AUTO_APPLY_CANDIDATES": 0,
"context": {
"rtc_param": "1",
"rtc_source": "SIMULATION",
"rtc_observed_at": "2026-09-25T00:11:39+00:00",
"local_catalog_hash": "be8c5899fff3e236c6c77bbb887087c0a2db048aeff763a5823676c7b478a151",
"official_catalog": {
"sha256": "1448cb63a41bdb67ea30b4e11f4dc200d9568542c6b9335b6cff87a4fd664654",
"publication": "2026-06-22",
"file": "cClassTrib 2026-06-22.xlsx"
},
"catalog_integrity": {
"classes": 145,
"cst": 18,
"orphan_classes": 0,
"bad_class_format": 0,
"bad_cst_format": 0,
"duplicates": 0,
"valid": true
}
}
},
"WHATIF_NORMAL_ON": {
"crt": "3",
"rtc_enabled": true,
"products": 3712,
"resolution_summary": {
"total": 3712,
"valid_existing": 0,
"ready_to_apply": 2725,
"ambiguous": 973,
"insufficient": 14,
"conflicts": 0,
"official_mismatch": 0,
"not_applicable": 0,
"other_blocked": 0,
"by_rule": {
"RTC-R4": 2725,
"RTC-A1": 255,
"RTC-R2": 14,
"RTC-A3": 682,
"RTC-A2": 36
},
"by_reason": {
"NCM_IN_NO_ANNEX_AND_NO_NATURE_BASED_CHAPTER": 2725,
"ANNEX_IX_NEEDS_OPERATION_OR_CONDITION": 63,
"NO_VALID_NCM": 14,
"SPECIAL_REGIME_DEPENDS_ON_NATURE_OR_OPERATION_OF_THE_GOOD": 682,
"ANNEX_IV_NEEDS_OPERATION_OR_CONDITION": 179,
"LISTED_IN_MORE_THAN_ONE_ANNEX": 36,
"ANNEX_XI_NEEDS_OPERATION_OR_CONDITION": 8,
"ANNEX_V_NEEDS_OPERATION_OR_CONDITION": 1,
"ANNEX_XIII_NEEDS_OPERATION_OR_CONDITION": 4
},
"catalog_compatibility": {
"compatibility": "MATERIAL_MISMATCH",
"counts": {
"MATCH": 131,
"LOCAL_ONLY": 0,
"OFFICIAL_ONLY": 19,
"MISMATCH": 11,
"EXPIRED": 3
},
"expired_present_locally": 3
}
},
"by_status": {
"RESOLVED_WITH_EXPLICIT_RULE": 2725,
"AMBIGUOUS": 973,
"INSUFFICIENT_EVIDENCE": 14
},
"rtc_blocker_types": {
"CCLASSTRIB_MISSING": 2725,
"CCLASSTRIB_AMBIGUOUS": 973,
"RTC_RULE_EVIDENCE_MISSING": 14
},
"AUTO_APPLY_CANDIDATES": 2725,
"context": {
"rtc_param": "1",
"rtc_source": "SIMULATION",
"rtc_observed_at": "2026-09-25T00:11:40+00:00",
"local_catalog_hash": "be8c5899fff3e236c6c77bbb887087c0a2db048aeff763a5823676c7b478a151",
"official_catalog": {
"sha256": "1448cb63a41bdb67ea30b4e11f4dc200d9568542c6b9335b6cff87a4fd664654",
"publication": "2026-06-22",
"file": "cClassTrib 2026-06-22.xlsx"
},
"catalog_integrity": {
"classes": 145,
"cst": 18,
"orphan_classes": 0,
"bad_class_format": 0,
"bad_cst_format": 0,
"duplicates": 0,
"valid": true
}
}
}
}
},
"MMMODAS": {
"crt": "1",
"total_products": 20997,
"passes": {
"AS_FOUND": {
"crt": "1",
"rtc_enabled": false,
"products": 20996,
"resolution_summary": null,
"by_status": {
"NOT_EVALUATED": 20996
},
"rtc_blocker_types": {},
"AUTO_APPLY_CANDIDATES": 0,
"context": {
"rtc_param": "0",
"rtc_source": "PARAMETROS.USAR_REFORMA_TRIBUTARIA",
"rtc_observed_at": "2026-09-25T00:11:43+00:00",
"local_catalog_hash": "1da77aec27fbde88488d1edf562f3af315ab2b5d06efb2e0b2f1ed606585d459",
"official_catalog": null,
"catalog_integrity": {
"classes": 145,
"cst": 18,
"orphan_classes": 0,
"bad_class_format": 0,
"bad_cst_format": 0,
"duplicates": 0,
"valid": true
}
}
},
"WHATIF_RTC_ON": {
"crt": "1",
"rtc_enabled": true,
"products": 20996,
"resolution_summary": {
"total": 20996,
"valid_existing": 0,
"ready_to_apply": 0,
"ambiguous": 0,
"insufficient": 20996,
"conflicts": 0,
"official_mismatch": 0,
"not_applicable": 0,
"other_blocked": 0,
"by_rule": {
"RTC-R1": 20996
},
"by_reason": {
"SIMPLES_ORIENTATION_PENDING_OFFICIAL_NT": 20996
},
"catalog_compatibility": {
"compatibility": "MATERIAL_MISMATCH",
"counts": {
"MATCH": 131,
"LOCAL_ONLY": 0,
"OFFICIAL_ONLY": 19,
"MISMATCH": 11,
"EXPIRED": 3
},
"expired_present_locally": 3
}
},
"by_status": {
"INSUFFICIENT_EVIDENCE": 20996
},
"rtc_blocker_types": {
"CCLASSTRIB_MISSING_UNRESOLVED": 20996
},
"AUTO_APPLY_CANDIDATES": 0,
"context": {
"rtc_param": "0",
"rtc_source": "SIMULATION",
"rtc_observed_at": "2026-09-25T00:11:44+00:00",
"local_catalog_hash": "1da77aec27fbde88488d1edf562f3af315ab2b5d06efb2e0b2f1ed606585d459",
"official_catalog": {
"sha256": "1448cb63a41bdb67ea30b4e11f4dc200d9568542c6b9335b6cff87a4fd664654",
"publication": "2026-06-22",
"file": "cClassTrib 2026-06-22.xlsx"
},
"catalog_integrity": {
"classes": 145,
"cst": 18,
"orphan_classes": 0,
"bad_class_format": 0,
"bad_cst_format": 0,
"duplicates": 0,
"valid": true
}
}
},
"WHATIF_NORMAL_ON": {
"crt": "3",
"rtc_enabled": true,
"products": 20996,
"resolution_summary": {
"total": 20996,
"valid_existing": 0,
"ready_to_apply": 20831,
"ambiguous": 49,
"insufficient": 116,
"conflicts": 0,
"official_mismatch": 0,
"not_applicable": 0,
"other_blocked": 0,
"by_rule": {
"RTC-R3": 1,
"RTC-R4": 20830,
"RTC-R2": 116,
"RTC-A3": 48,
"RTC-A1": 1
},
"by_reason": {
"ANNEX_VII_CLEAN_ITEM_UNIQUE_DEFAULT_CLASS": 1,
"NCM_IN_NO_ANNEX_AND_NO_NATURE_BASED_CHAPTER": 20830,
"NO_VALID_NCM": 116,
"SPECIAL_REGIME_DEPENDS_ON_NATURE_OR_OPERATION_OF_THE_GOOD": 48,
"ANNEX_XV_NEEDS_OPERATION_OR_CONDITION": 1
},
"catalog_compatibility": {
"compatibility": "MATERIAL_MISMATCH",
"counts": {
"MATCH": 131,
"LOCAL_ONLY": 0,
"OFFICIAL_ONLY": 19,
"MISMATCH": 11,
"EXPIRED": 3
},
"expired_present_locally": 3
}
},
"by_status": {
"RESOLVED_WITH_EXPLICIT_RULE": 20831,
"INSUFFICIENT_EVIDENCE": 116,
"AMBIGUOUS": 49
},
"rtc_blocker_types": {
"CCLASSTRIB_MISSING": 20831,
"RTC_RULE_EVIDENCE_MISSING": 116,
"CCLASSTRIB_AMBIGUOUS": 49
},
"AUTO_APPLY_CANDIDATES": 20831,
"context": {
"rtc_param": "0",
"rtc_source": "SIMULATION",
"rtc_observed_at": "2026-09-25T00:11:49+00:00",
"local_catalog_hash": "1da77aec27fbde88488d1edf562f3af315ab2b5d06efb2e0b2f1ed606585d459",
"official_catalog": {
"sha256": "1448cb63a41bdb67ea30b4e11f4dc200d9568542c6b9335b6cff87a4fd664654",
"publication": "2026-06-22",
"file": "cClassTrib 2026-06-22.xlsx"
},
"catalog_integrity": {
"classes": 145,
"cst": 18,
"orphan_classes": 0,
"bad_class_format": 0,
"bad_cst_format": 0,
"duplicates": 0,
"valid": true
}
}
}
}
},
"RINCAO": {
"crt": "3",
"total_products": 894,
"passes": {
"AS_FOUND": {
"crt": "3",
"rtc_enabled": false,
"products": 894,
"resolution_summary": null,
"by_status": {
"NOT_EVALUATED": 894
},
"rtc_blocker_types": {},
"AUTO_APPLY_CANDIDATES": 0,
"context": {
"rtc_param": "0",
"rtc_source": "PARAMETROS.USAR_REFORMA_TRIBUTARIA",
"rtc_observed_at": "2026-09-25T00:11:50+00:00",
"local_catalog_hash": "be8c5899fff3e236c6c77bbb887087c0a2db048aeff763a5823676c7b478a151",
"official_catalog": null,
"catalog_integrity": {
"classes": 145,
"cst": 18,
"orphan_classes": 0,
"bad_class_format": 0,
"bad_cst_format": 0,
"duplicates": 0,
"valid": true
}
}
},
"WHATIF_RTC_ON": {
"crt": "3",
"rtc_enabled": true,
"products": 894,
"resolution_summary": {
"total": 894,
"valid_existing": 0,
"ready_to_apply": 530,
"ambiguous": 364,
"insufficient": 0,
"conflicts": 0,
"official_mismatch": 0,
"not_applicable": 0,
"other_blocked": 0,
"by_rule": {
"RTC-R4": 467,
"RTC-A1": 129,
"RTC-A2": 234,
"RTC-R3": 63,
"RTC-A3": 1
},
"by_reason": {
"NCM_IN_NO_ANNEX_AND_NO_NATURE_BASED_CHAPTER": 467,
"ANNEX_X_NEEDS_OPERATION_OR_CONDITION": 109,
"LISTED_IN_MORE_THAN_ONE_ANNEX": 234,
"ANNEX_VII_CLEAN_ITEM_UNIQUE_DEFAULT_CLASS": 59,
"ANNEX_I_CLEAN_ITEM_UNIQUE_DEFAULT_CLASS": 2,
"ANNEX_XV_NEEDS_OPERATION_OR_CONDITION": 16,
"SPECIAL_REGIME_DEPENDS_ON_NATURE_OR_OPERATION_OF_THE_GOOD": 1,
"ANNEX_XV_CLEAN_ITEM_UNIQUE_DEFAULT_CLASS": 2,
"ANNEX_I_NEEDS_OPERATION_OR_CONDITION": 3,
"ANNEX_VII_NEEDS_OPERATION_OR_CONDITION": 1
},
"catalog_compatibility": {
"compatibility": "MATERIAL_MISMATCH",
"counts": {
"MATCH": 131,
"LOCAL_ONLY": 0,
"OFFICIAL_ONLY": 19,
"MISMATCH": 11,
"EXPIRED": 3
},
"expired_present_locally": 3
}
},
"by_status": {
"RESOLVED_WITH_EXPLICIT_RULE": 530,
"AMBIGUOUS": 364
},
"rtc_blocker_types": {
"CCLASSTRIB_MISSING": 530,
"CCLASSTRIB_AMBIGUOUS": 364
},
"AUTO_APPLY_CANDIDATES": 530,
"context": {
"rtc_param": "0",
"rtc_source": "SIMULATION",
"rtc_observed_at": "2026-09-25T00:11:50+00:00",
"local_catalog_hash": "be8c5899fff3e236c6c77bbb887087c0a2db048aeff763a5823676c7b478a151",
"official_catalog": {
"sha256": "1448cb63a41bdb67ea30b4e11f4dc200d9568542c6b9335b6cff87a4fd664654",
"publication": "2026-06-22",
"file": "cClassTrib 2026-06-22.xlsx"
},
"catalog_integrity": {
"classes": 145,
"cst": 18,
"orphan_classes": 0,
"bad_class_format": 0,
"bad_cst_format": 0,
"duplicates": 0,
"valid": true
}
}
},
"WHATIF_NORMAL_ON": {
"crt": "3",
"rtc_enabled": true,
"products": 894,
"resolution_summary": {
"total": 894,
"valid_existing": 0,
"ready_to_apply": 530,
"ambiguous": 364,
"insufficient": 0,
"conflicts": 0,
"official_mismatch": 0,
"not_applicable": 0,
"other_blocked": 0,
"by_rule": {
"RTC-R4": 467,
"RTC-A1": 129,
"RTC-A2": 234,
"RTC-R3": 63,
"RTC-A3": 1
},
"by_reason": {
"NCM_IN_NO_ANNEX_AND_NO_NATURE_BASED_CHAPTER": 467,
"ANNEX_X_NEEDS_OPERATION_OR_CONDITION": 109,
"LISTED_IN_MORE_THAN_ONE_ANNEX": 234,
"ANNEX_VII_CLEAN_ITEM_UNIQUE_DEFAULT_CLASS": 59,
"ANNEX_I_CLEAN_ITEM_UNIQUE_DEFAULT_CLASS": 2,
"ANNEX_XV_NEEDS_OPERATION_OR_CONDITION": 16,
"SPECIAL_REGIME_DEPENDS_ON_NATURE_OR_OPERATION_OF_THE_GOOD": 1,
"ANNEX_XV_CLEAN_ITEM_UNIQUE_DEFAULT_CLASS": 2,
"ANNEX_I_NEEDS_OPERATION_OR_CONDITION": 3,
"ANNEX_VII_NEEDS_OPERATION_OR_CONDITION": 1
},
"catalog_compatibility": {
"compatibility": "MATERIAL_MISMATCH",
"counts": {
"MATCH": 131,
"LOCAL_ONLY": 0,
"OFFICIAL_ONLY": 19,
"MISMATCH": 11,
"EXPIRED": 3
},
"expired_present_locally": 3
}
},
"by_status": {
"RESOLVED_WITH_EXPLICIT_RULE": 530,
"AMBIGUOUS": 364
},
"rtc_blocker_types": {
"CCLASSTRIB_MISSING": 530,
"CCLASSTRIB_AMBIGUOUS": 364
},
"AUTO_APPLY_CANDIDATES": 530,
"context": {
"rtc_param": "0",
"rtc_source": "SIMULATION",
"rtc_observed_at": "2026-09-25T00:11:50+00:00",
"local_catalog_hash": "be8c5899fff3e236c6c77bbb887087c0a2db048aeff763a5823676c7b478a151",
"official_catalog": {
"sha256": "1448cb63a41bdb67ea30b4e11f4dc200d9568542c6b9335b6cff87a4fd664654",
"publication": "2026-06-22",
"file": "cClassTrib 2026-06-22.xlsx"
},
"catalog_integrity": {
"classes": 145,
"cst": 18,
"orphan_classes": 0,
"bad_class_format": 0,
"bad_cst_format": 0,
"duplicates": 0,
"valid": true
}
}
}
}
},
"UNIAO": {
"crt": "1",
"total_products": 8942,
"passes": {
"AS_FOUND": {
"crt": "1",
"rtc_enabled": false,
"products": 8942,
"resolution_summary": null,
"by_status": {
"NOT_EVALUATED": 8942
},
"rtc_blocker_types": {},
"AUTO_APPLY_CANDIDATES": 0,
"context": {
"rtc_param": "0",
"rtc_source": "PARAMETROS.USAR_REFORMA_TRIBUTARIA",
"rtc_observed_at": "2026-09-25T00:11:52+00:00",
"local_catalog_hash": "be8c5899fff3e236c6c77bbb887087c0a2db048aeff763a5823676c7b478a151",
"official_catalog": null,
"catalog_integrity": {
"classes": 145,
"cst": 18,
"orphan_classes": 0,
"bad_class_format": 0,
"bad_cst_format": 0,
"duplicates": 0,
"valid": true
}
}
},
"WHATIF_RTC_ON": {
"crt": "1",
"rtc_enabled": true,
"products": 8942,
"resolution_summary": {
"total": 8942,
"valid_existing": 0,
"ready_to_apply": 0,
"ambiguous": 0,
"insufficient": 8942,
"conflicts": 0,
"official_mismatch": 0,
"not_applicable": 0,
"other_blocked": 0,
"by_rule": {
"RTC-R1": 8942
},
"by_reason": {
"SIMPLES_ORIENTATION_PENDING_OFFICIAL_NT": 8942
},
"catalog_compatibility": {
"compatibility": "MATERIAL_MISMATCH",
"counts": {
"MATCH": 131,
"LOCAL_ONLY": 0,
"OFFICIAL_ONLY": 19,
"MISMATCH": 11,
"EXPIRED": 3
},
"expired_present_locally": 3
}
},
"by_status": {
"INSUFFICIENT_EVIDENCE": 8942
},
"rtc_blocker_types": {
"CCLASSTRIB_MISSING_UNRESOLVED": 8942
},
"AUTO_APPLY_CANDIDATES": 0,
"context": {
"rtc_param": "0",
"rtc_source": "SIMULATION",
"rtc_observed_at": "2026-09-25T00:11:52+00:00",
"local_catalog_hash": "be8c5899fff3e236c6c77bbb887087c0a2db048aeff763a5823676c7b478a151",
"official_catalog": {
"sha256": "1448cb63a41bdb67ea30b4e11f4dc200d9568542c6b9335b6cff87a4fd664654",
"publication": "2026-06-22",
"file": "cClassTrib 2026-06-22.xlsx"
},
"catalog_integrity": {
"classes": 145,
"cst": 18,
"orphan_classes": 0,
"bad_class_format": 0,
"bad_cst_format": 0,
"duplicates": 0,
"valid": true
}
}
},
"WHATIF_NORMAL_ON": {
"crt": "3",
"rtc_enabled": true,
"products": 8942,
"resolution_summary": {
"total": 8942,
"valid_existing": 0,
"ready_to_apply": 7419,
"ambiguous": 1432,
"insufficient": 91,
"conflicts": 0,
"official_mismatch": 0,
"not_applicable": 0,
"other_blocked": 0,
"by_rule": {
"RTC-R4": 6430,
"RTC-R3": 989,
"RTC-A1": 821,
"RTC-A3": 296,
"RTC-R2": 91,
"RTC-A2": 315
},
"by_reason": {
"NCM_IN_NO_ANNEX_AND_NO_NATURE_BASED_CHAPTER": 6430,
"ANNEX_VII_CLEAN_ITEM_UNIQUE_DEFAULT_CLASS": 113,
"ANNEX_IX_NEEDS_OPERATION_OR_CONDITION": 174,
"SPECIAL_REGIME_DEPENDS_ON_NATURE_OR_OPERATION_OF_THE_GOOD": 296,
"ANNEX_I_CLEAN_ITEM_UNIQUE_DEFAULT_CLASS": 262,
"ANNEX_I_NEEDS_OPERATION_OR_CONDITION": 196,
"ANNEX_VII_NEEDS_OPERATION_OR_CONDITION": 193,
"NO_VALID_NCM": 91,
"LISTED_IN_MORE_THAN_ONE_ANNEX": 315,
"ANNEX_VIII_CLEAN_ITEM_UNIQUE_DEFAULT_CLASS": 580,
"ANNEX_X_NEEDS_OPERATION_OR_CONDITION": 22,
"ANNEX_IV_NEEDS_OPERATION_OR_CONDITION": 156,
"ANNEX_XV_CLEAN_ITEM_UNIQUE_DEFAULT_CLASS": 34,
"ANNEX_II_NEEDS_OPERATION_OR_CONDITION": 11,
"ANNEX_XV_NEEDS_OPERATION_OR_CONDITION": 42,
"ANNEX_V_NEEDS_OPERATION_OR_CONDITION": 12,
"ANNEX_XI_NEEDS_OPERATION_OR_CONDITION": 12,
"ANNEX_VI_NEEDS_OPERATION_OR_CONDITION": 3
},
"catalog_compatibility": {
"compatibility": "MATERIAL_MISMATCH",
"counts": {
"MATCH": 131,
"LOCAL_ONLY": 0,
"OFFICIAL_ONLY": 19,
"MISMATCH": 11,
"EXPIRED": 3
},
"expired_present_locally": 3
}
},
"by_status": {
"RESOLVED_WITH_EXPLICIT_RULE": 7419,
"AMBIGUOUS": 1432,
"INSUFFICIENT_EVIDENCE": 91
},
"rtc_blocker_types": {
"CCLASSTRIB_MISSING": 7419,
"CCLASSTRIB_AMBIGUOUS": 1432,
"RTC_RULE_EVIDENCE_MISSING": 91
},
"AUTO_APPLY_CANDIDATES": 7419,
"context": {
"rtc_param": "0",
"rtc_source": "SIMULATION",
"rtc_observed_at": "2026-09-25T00:11:54+00:00",
"local_catalog_hash": "be8c5899fff3e236c6c77bbb887087c0a2db048aeff763a5823676c7b478a151",
"official_catalog": {
"sha256": "1448cb63a41bdb67ea30b4e11f4dc200d9568542c6b9335b6cff87a4fd664654",
"publication": "2026-06-22",
"file": "cClassTrib 2026-06-22.xlsx"
},
"catalog_integrity": {
"classes": 145,
"cst": 18,
"orphan_classes": 0,
"bad_class_format": 0,
"bad_cst_format": 0,
"duplicates": 0,
"valid": true
}
}
}
}
}
}
}
@@ -0,0 +1,79 @@
{
"official_catalog": {
"file": "cClassTrib 2026-06-22.xlsx",
"sha256": "1448cb63a41bdb67ea30b4e11f4dc200d9568542c6b9335b6cff87a4fd664654",
"publication": "2026-06-22",
"source": "Portal Nacional da NF-e (cClassTrib)",
"parser": "rtc-cclasstrib-xlsx-v1"
},
"official_classes": 164,
"lc214": {
"document": "Lei Complementar 214/2025 compiled (through LC 227/2026)",
"url": "https://www.planalto.gov.br/ccivil_03/leis/lcp/lcp214.htm",
"sha256": "5c7d59b3c49e5b7c066c2ad28bedf32791193a676ec1a163bec14bea5fa2f680",
"accessed_at": "2026-09-24",
"fetched_via": "Internet Archive copy of the official URL"
},
"annex_items_total_and_clean": {
"I": [
26,
16
],
"II": [
9,
6
],
"III": [
30,
28
],
"IV": [
105,
98
],
"V": [
3,
3
],
"VI": [
81,
80
],
"VII": [
17,
7
],
"VIII": [
7,
7
],
"IX": [
35,
18
],
"X": [
57,
47
],
"XI": [
2,
0
],
"XII": [
17,
12
],
"XIII": [
6,
4
],
"XIV": [
82,
82
],
"XV": [
6,
4
]
}
}
+7
View File
@@ -51,3 +51,10 @@ Somente leitura. `FIREBIRD_WRITES=0`: a avaliação abre uma conexão `READ COMM
| TESTESOFICIAIS | 1 | **ligada** | 521 | 0 | 521 (RTC) | 0 | 0% | 94,82% (494/521) |
Bancos além do piloto: medida só da parte fiscal (`--fiscal-only`, sem decisão do Hub). TESTESOFICIAIS: com a decisão do Hub (antes 495/521 = 95,01%). Bloqueios PIS/COFINS: TESTESOFICIAIS 1 produto (incidência SUBST.TRIBUTÁRIA com CST 01), RINCAO 1 produto (`049`/`000`), demais 0. Produtos com warning (não bloqueante): RINCAO 78 (`4`→`04`, não canônico), UNIAO 2 (CST `000`/`060` inválido, ignorado por ser Simples), TESTESOFICIAIS 1 (não canônico). Aplicabilidade: Simples ⇒ `NOT_APPLICABLE`; RINCAO ⇒ `OPTIONAL` (894); TESTESOFICIAIS 519 `NOT_APPLICABLE` + 2 `REQUIRED`.
## RTC no motor único (cClassTrib) — atualização
- **RTC ligada** ⇒ o Agent avalia cada produto com o resolver determinístico (`docs/cclasstrib_resolution.md`) e transforma o resultado em UM blocker de causa raiz por produto (mesma fila, categoria `RTC`): `CCLASSTRIB_MISSING` (pronto para preencher), `CCLASSTRIB_MISSING_UNRESOLVED`, `CCLASSTRIB_AMBIGUOUS`, `RTC_RULE_EVIDENCE_MISSING`, `RTC_LOCAL_CATALOG_OUTDATED`, `CCLASSTRIB_OFFICIAL_MISMATCH`, `CCLASSTRIB_CONFLICT`. **RTC desligada** ⇒ nenhuma avaliação, nenhum blocker, nada no painel além de "Desativada".
- Cobertura continua UMA: produto só é completo quando o `CCLASSTRIB` é válido (ou seja, depois de preenchido pelo operador ou pelo auto-apply autorizado). Produtos "prontos para preenchimento" contam como bloqueados até a escrita.
- O painel explica o 0%: "Reforma Tributária ativa — cClassTrib pendente em N produtos" + resumo Total / válidos / prontos / ambíguos-bloqueados.
- Preenchimento automático: `docs/rtc_auto_apply.md` (desligado por padrão; três chaves; somente `CCLASSTRIB`; auditado).
+6 -6
View File
@@ -1,14 +1,14 @@
{
"artifact": "RELEASE_MANIFEST",
"GTI_FISCAL_VERSION": "1.1.8",
"GTI_FISCAL_VERSION": "1.1.9",
"generated_at": "2026-09-24T14:13:15+00:00",
"hub": {
"version": "1.1.8",
"version": "1.1.9",
"api_compat": "1.0",
"engine_version": "0.3.0",
"decision_version": "mission-04c-ipi-ex-split"
},
"agent_version": "1.1.8",
"agent_version": "1.1.9",
"fiscal_engine": {
"ENGINE_VERSION": "0.3.0",
"DECISION_VERSION": "mission-04c-ipi-ex-split",
@@ -17,10 +17,10 @@
"ICMS_CONTEXT_ENGINE_VERSION": "icms-context-v1"
},
"migrations": {
"alembic_head_in_repo": "0026_central_processing_control",
"applied_revision": "0026_central_processing_control",
"alembic_head_in_repo": "0027_rtc_auto_apply",
"applied_revision": "0027_rtc_auto_apply",
"applied_revision_source": "production Hub 10.0.10.117 (gti-fiscal-hub-fiscal-hub-1), verified 2026-09-24",
"count": 26
"count": 27
},
"datasets_active": [
{
+1 -1
View File
@@ -1 +1 @@
1.1.8
1.1.9
+102
View File
@@ -0,0 +1,102 @@
"""Build central/app/rtc_reference_data.py: the OFFICIAL cClassTrib catalog + the LC 214/2025 goods annexes as structured, hash-pinned reference data.
python scripts/build_rtc_reference_data.py
Inputs (all official, already downloaded/hashed):
sources/raw/bootstrap_v1/cClassTrib 2026-06-22.xlsx Portal Nacional NF-e cClassTrib table (parsed with the Hub's own parser)
sources_cache/wb_lc214.bin LC 214/2025 compiled (Planalto, through LC 227/2026), extracted to text
Nothing here decides anything: it only turns the published texts into data with provenance. Item texts that carry a condition/exception are
marked `clean=False` so the resolver never treats them as a deterministic match.
"""
import hashlib
import html
import json
import re
import sys
from datetime import date
from pathlib import Path
from pprint import pformat
ROOT = Path(__file__).resolve().parents[1]
sys.path.insert(0, str(ROOT))
from central.app.datasets.bootstrap_v1_extra import parse_rtc_cclasstrib # noqa: E402
XLSX = ROOT / "sources" / "raw" / "bootstrap_v1" / "cClassTrib 2026-06-22.xlsx"
LC214 = ROOT / "sources_cache" / "wb_lc214.bin"
ROMAN = {"I": 1, "II": 2, "III": 3, "IV": 4, "V": 5, "VI": 6, "VII": 7, "VIII": 8, "IX": 9, "X": 10, "XI": 11, "XII": 12, "XIII": 13, "XIV": 14, "XV": 15}
RESTRICTION = re.compile(r"exceto|ressalv|desde que|em conformidade|requisitos|somente|exclusiv|quando|observad|salvo|apenas|conforme|vedad|\ba\)|\bb\)|\bex\b|não", re.I)
CODE = re.compile(r"\b(\d{2}\.\d{2}(?:\.\d{1,2}){0,2}|\d{4}(?:\.\d{1,2}){1,3})\b")
def lc214_text():
raw = LC214.read_bytes()
text = raw.decode("latin-1", errors="replace")
text = re.sub(r"<script.*?</script>|<style.*?</style>", "", text, flags=re.S)
text = html.unescape(re.sub(r"<[^>]+>", " ", text))
return raw, re.sub(r"[ \t\r\f\v]+", " ", text)
def parse_annexes(text):
heads = [(m.start(), m.group(1)) for m in re.finditer(r"\n[ \xa0]*ANEXO ([IVXL]+)[ \xa0]*\n", text)]
blocks, seen = {}, set()
for i, (pos, roman) in enumerate(heads):
if roman in seen or roman not in ROMAN:
continue
# the goods annexes I..XV are the first occurrence of each numeral, before the Simples annexes (which repeat I..VII)
seen.add(roman)
end = heads[i + 1][0] if i + 1 < len(heads) else len(text)
blocks[roman] = text[pos:end]
out = {}
for roman, block in blocks.items():
head = " ".join(block[:600].split())
items, current, expected = [], None, 1
lines = [ln.strip() for ln in block.split("\n")]
for idx, ln in enumerate(lines):
if ln == str(expected):
nxt = next((x for x in lines[idx + 1:idx + 8] if x), "")
if nxt and not nxt.isdigit():
current = {"item": expected, "text": []}
items.append(current)
expected += 1
continue
if current is not None and ln:
current["text"].append(ln)
parsed = []
for it in items:
desc = " ".join(" ".join(it["text"]).split())
prefixes = sorted({re.sub(r"\D", "", c) for c in CODE.findall(desc)})
clean = not RESTRICTION.search(desc) and bool(prefixes)
parsed.append({"item": it["item"], "description": desc[:400], "prefixes": prefixes, "clean": clean})
out[roman] = {"number": ROMAN[roman], "title": head[:200], "revoked": "Revogado" in head, "items": parsed}
return out
def main():
payload = parse_rtc_cclasstrib(XLSX.read_bytes())
official = []
for e in payload.entries:
ind = e["indicators"]
official.append({"code": e["cclasstrib"], "cst": e["cst"], "name": (e["name"] or "")[:160], "tipo_aliquota": e["tipo_aliquota"], "p_red_ibs": e["p_red_ibs"], "p_red_cbs": e["p_red_cbs"],
"start": (e["d_ini_vig"] or "")[:10] or None, "end": (e["d_fim_vig"] or "")[:10] or None, "anexo": e.get("anexo"), "ind_nfe": ind.get("indNFe") == "1", "ind_nfce": ind.get("indNFCe") == "1"})
raw, text = lc214_text()
annexes = parse_annexes(text)
counts = {k: (len(v["items"]), sum(1 for i in v["items"] if i["clean"])) for k, v in annexes.items()}
module = ROOT / "central" / "app" / "rtc_reference_data.py"
catalog_meta = {"file": XLSX.name, "sha256": hashlib.sha256(XLSX.read_bytes()).hexdigest(), "publication": "2026-06-22", "source": "Portal Nacional da NF-e (cClassTrib)", "parser": payload.parser_version}
lc_meta = {"document": "Lei Complementar 214/2025 compiled (through LC 227/2026)", "url": "https://www.planalto.gov.br/ccivil_03/leis/lcp/lcp214.htm", "sha256": hashlib.sha256(raw).hexdigest(),
"accessed_at": date.today().isoformat(), "fetched_via": "Internet Archive copy of the official URL"}
header = '"""GENERATED by scripts/build_rtc_reference_data.py - do not edit. Official cClassTrib catalog and LC 214/2025 goods annexes with provenance."""' + "\n\n"
body = "\n\n".join([
"OFFICIAL_CATALOG = " + pformat(catalog_meta, width=160),
"OFFICIAL_CCLASSTRIB = " + pformat(official, width=220),
"LC214_SOURCE = " + pformat(lc_meta, width=160),
"LC214_ANNEXES = " + pformat(annexes, width=220),
]) + "\n"
module.write_text(header + body, encoding="utf-8")
(ROOT / "docs" / "sources" / "RTC_REFERENCE_MANIFEST_V1.json").write_text(json.dumps({"official_catalog": catalog_meta, "official_classes": len(official), "lc214": lc_meta, "annex_items_total_and_clean": counts}, ensure_ascii=False, indent=1), encoding="utf-8")
print(len(official), "official classes;", "annex items (total, clean):", counts)
if __name__ == "__main__":
main()
+140
View File
@@ -0,0 +1,140 @@
"""COPY-ONLY write E2E for the RTC auto-apply (C000025.CCLASSTRIB).
python scripts/rtc_copy_write_e2e.py
Runs ONLY against scratch_rtc_e2e/TESTESOFICIAIS_RTC_COPY.FDB (a gbak restore of TESTESOFICIAIS). It refuses any other path, and refuses every real database.
Scenario (all through the Agent's own code path `run_agent_service._rtc_auto_apply`):
1. the copy's company is switched to regime normal (C000004.CRT='3'): a disposable experiment, so the rules can act (the real company is Simples => 0 candidates);
2. assess -> candidates; 3. apply with all three gates open; 4. re-read, FK/CST checks, coverage; 5. ONLY CCLASSTRIB changed (whole-table diff);
6. second run: 0 candidates, 0 writes; 7. stale-decision abort (RTC switched off on the copy between decision and write) writes nothing.
Writes go to the copy and nowhere else.
"""
import importlib.util
import json
import os
import sys
from datetime import date
from pathlib import Path
from types import SimpleNamespace
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"))
from dotenv import load_dotenv # noqa: E402
load_dotenv(ROOT / ".env")
import fdb # noqa: E402
import local_completion as lc # noqa: E402
from central.app.operational_state import classify_product_state, summarize_operational_states # noqa: E402
from gti_fiscal_agent import firebird_reader, unified_fiscal # noqa: E402
COPY = str((ROOT / "scratch_rtc_e2e" / "TESTESOFICIAIS_RTC_COPY.FDB").resolve())
NCM_OK = set((ROOT / "docs" / "runs" / "ncm_valid_codes.txt").read_text().split())
def guard(path):
canon = os.path.normcase(os.path.abspath(path))
if canon != os.path.normcase(COPY):
raise SystemExit(f"REFUSED: {path} is not the disposable copy")
real = {os.path.normcase(os.path.abspath(p)) for p in lc.allowlist().values()}
if canon in real or "scratch_rtc_e2e" not in canon:
raise SystemExit("REFUSED: real database")
def connect(rw):
kw = dict(database=COPY, user=os.environ["FB_USER"], password=os.environ["FB_PASSWORD"], fb_library_name=os.environ.get("FIREBIRD_CLIENT_LIBRARY"))
if not rw:
kw["isolation_level"] = fdb.ISOLATION_LEVEL_READ_COMMITED_RO
return fdb.connect(**kw)
def load_service():
spec = importlib.util.spec_from_file_location("run_agent_service", ROOT / "agent" / "scripts" / "run_agent_service.py")
mod = importlib.util.module_from_spec(spec)
sys.modules[spec.name] = mod
spec.loader.exec_module(mod)
return mod
def digits(v):
return "".join(ch for ch in str(v or "") if ch.isdigit())
def snapshot_table(cur, table, key, skip_cols=()):
cur.execute(f"SELECT * FROM {table}")
cols = [d[0] for d in cur.description]
ki = cols.index(key)
return cols, {r[ki]: {c: v for c, v in zip(cols, r) if c not in skip_cols} for r in cur.fetchall()}
def coverage(env, company, ncm_valid, override=None):
conn = firebird_reader._connect(env)
try:
out = unified_fiscal.assess_from_connection(conn, company, ncm_valid=ncm_valid, rtc_override=override)
finally:
conn.close()
products = firebird_reader.read_products(env)
states = [classify_product_state(p, {"status": "EXACT_NCM"}, fiscal=out["findings"].get(str(p["CODIGO"]))) for p in products]
s = summarize_operational_states(states)
return out, {"total": s["TOTAL_PRODUCTS"], "complete": s["COMPLETE_PRODUCTS"], "blocked": s["SAFE_BLOCKED"], "unclassified": s["UNCLASSIFIED"], "coverage_percent": round(100 * s["COMPLETE_PRODUCTS"] / s["TOTAL_PRODUCTS"], 2)}
def main():
guard(COPY)
service = load_service()
env = {"FIREBIRD_DATABASE_PATH": COPY, "FB_USER": os.environ["FB_USER"], "FB_PASSWORD": os.environ["FB_PASSWORD"], "FIREBIRD_CLIENT_LIBRARY": os.environ.get("FIREBIRD_CLIENT_LIBRARY")}
report = {"database": "scratch_rtc_e2e/TESTESOFICIAIS_RTC_COPY.FDB (gbak restore of TESTESOFICIAIS)", "real_database_writes": 0, "real_database_write_attempts": 0}
# ---- 1. company -> regime normal on the COPY (documented experiment)
rw = connect(True); cur = rw.cursor()
cur.execute("SELECT DISTINCT TRIM(CRT) FROM C000004"); report["copy_original_crt"] = [r[0] for r in cur.fetchall()]
cur.execute("UPDATE C000004 SET CRT = '3'"); rw.commit()
cur.execute("SELECT DISTINCT TRIM(CRT) FROM C000004"); company = {"crt": cur.fetchone()[0]}
report["copy_crt_for_experiment"] = company["crt"]
_, before_c25 = snapshot_table(cur, "C000025", "CODIGO")
cur.execute("SELECT USAR_REFORMA_TRIBUTARIA FROM PARAMETROS"); report["copy_rtc_param"] = cur.fetchone()[0]
rw.commit(); rw.close()
ncm_valid = {str(c).strip(): digits(v["CLASSIFICACAO_FISCAL"]) in NCM_OK for c, v in before_c25.items()}
# ---- 2. assess (as found on the copy)
out, cov_before = coverage(env, company, ncm_valid)
report["coverage_before"] = cov_before
report["resolution_before"] = {k: v for k, v in out["context"]["rtc_resolution"].items() if k != "by_reason"}
products = firebird_reader.read_products(env)
config = SimpleNamespace(firebird_writes=True, rtc_auto_apply_enabled=True, rtc_auto_apply_max_per_job=500, database_path=COPY)
job = {"job_id": 424242, "authorizations": {"rtc_auto_apply": True}}
# ---- 7 (first, on purpose): stale decision - RTC switched off on the copy between decision and write => nothing is written
rw = connect(True); c = rw.cursor(); c.execute("UPDATE PARAMETROS SET USAR_REFORMA_TRIBUTARIA = 0"); rw.commit(); rw.close()
stale = service._rtc_auto_apply(env, config, job, products, out, company)
report["stale_decision_run"] = {"write_attempts": stale["write_attempts"], "write_successes": stale["write_successes"], "aborted_reasons": stale["aborted_reasons"]}
rw = connect(True); c = rw.cursor(); c.execute("UPDATE PARAMETROS SET USAR_REFORMA_TRIBUTARIA = 1"); rw.commit(); rw.close()
# ---- 3. apply
first = service._rtc_auto_apply(env, config, job, products, out, company)
report["first_run"] = {"mode": first["mode"], "ready_total": first["ready_total"], "write_attempts": first["write_attempts"], "write_successes": first["write_successes"], "aborted_reasons": first["aborted_reasons"],
"audit_sample": first["applied"][:3]}
# ---- 4/5. re-read, FK, whole-table diff
ro = connect(False); cur = ro.cursor()
cols, after_c25 = snapshot_table(cur, "C000025", "CODIGO")
changed = {code: [c for c in cols if before_c25[code][c] != row[c]] for code, row in after_c25.items() if any(before_c25[code][c] != row[c] for c in cols)}
report["rows_changed"] = len(changed)
report["columns_changed"] = sorted({c for v in changed.values() for c in v})
cur.execute("SELECT COUNT(*) FROM C000025 p WHERE p.CCLASSTRIB IS NOT NULL AND NOT EXISTS (SELECT 1 FROM RTC_CCLASSTRIB c WHERE c.CCLASSTRIB = p.CCLASSTRIB)"); report["fk_orphans"] = cur.fetchone()[0]
cur.execute("SELECT COUNT(*) FROM C000025 p JOIN RTC_CCLASSTRIB c ON c.CCLASSTRIB = p.CCLASSTRIB WHERE NOT EXISTS (SELECT 1 FROM RTC_CST s WHERE s.CSTIBSCBS = c.CSTIBSCBS)"); report["cst_orphans"] = cur.fetchone()[0]
cur.execute("SELECT CCLASSTRIB, COUNT(*) FROM C000025 WHERE CCLASSTRIB IS NOT NULL GROUP BY 1"); report["written_by_class"] = {r[0].strip(): r[1] for r in cur.fetchall()}
cur.execute("SELECT COUNT(*) FROM RTC_CCLASSTRIB"); report["catalog_rows_unchanged"] = cur.fetchone()[0]
ro.close()
out2, cov_after = coverage(env, company, ncm_valid)
report["coverage_after"] = cov_after
report["resolution_after"] = {k: v for k, v in out2["context"]["rtc_resolution"].items() if k != "by_reason"}
# ---- 6. second run
products2 = firebird_reader.read_products(env)
second = service._rtc_auto_apply(env, config, job, products2, out2, company)
report["second_run"] = {"ready_total": second["ready_total"], "write_attempts": second["write_attempts"], "write_successes": second["write_successes"]}
report["IDEMPOTENT"] = second["write_attempts"] == 0 and second["write_successes"] == 0
report["ONLY_CCLASSTRIB_CHANGED"] = report["columns_changed"] in ([], ["CCLASSTRIB"])
report["PASS"] = bool(report["IDEMPOTENT"] and report["ONLY_CCLASSTRIB_CHANGED"] and report["fk_orphans"] == 0 and report["cst_orphans"] == 0 and first["write_successes"] == first["write_attempts"] > 0
and report["stale_decision_run"]["write_successes"] == 0 and report["rows_changed"] == first["write_successes"])
(ROOT / "docs" / "runs" / "rtc_copy_write_e2e.json").write_text(json.dumps(report, ensure_ascii=False, indent=1, default=str), encoding="utf-8")
print(json.dumps({k: report[k] for k in ("copy_original_crt", "copy_rtc_param", "coverage_before", "coverage_after", "first_run", "stale_decision_run", "rows_changed", "columns_changed", "fk_orphans", "cst_orphans", "second_run", "IDEMPOTENT", "ONLY_CCLASSTRIB_CHANGED", "PASS")},
ensure_ascii=False, default=str)[:3000])
if __name__ == "__main__":
main()
+79
View File
@@ -0,0 +1,79 @@
"""READ-ONLY dry-run of the deterministic cClassTrib resolver over real Firebird databases. Writes NOTHING to any database.
python scripts/rtc_resolution_dryrun.py --db TESTESOFICIAIS CONSTRUCAO ... [--out docs/runs/rtc_resolution_dryrun.json]
For each database, three passes over the same read-only snapshot:
AS_FOUND the ERP's own RTC switch and the company's real CRT (what the Agent would report today)
WHATIF_RTC_ON RTC forced ON (pure evaluator; PARAMETROS is never touched), real CRT
WHATIF_NORMAL_ON RTC forced ON and regime normal (CRT 3): what the rules WOULD resolve once an official orientation exists for Simples
NCM validity = the code exists in the active official NCM table exported from the Hub (docs/runs/ncm_valid_codes.txt).
"""
import argparse
import csv
import json
import sys
from collections import Counter
from pathlib import Path
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"))
from dotenv import load_dotenv # noqa: E402
load_dotenv(ROOT / ".env")
import local_completion as lc # noqa: E402
from gti_fiscal_agent import firebird_reader, unified_fiscal # noqa: E402
from unified_coverage_e2e import resolved_crt # noqa: E402
NCM_OK = set((ROOT / "docs" / "runs" / "ncm_valid_codes.txt").read_text().split())
def digits(value):
return "".join(ch for ch in str(value or "") if ch.isdigit())
def one_pass(env, company, override, ncm_valid):
conn = firebird_reader._connect(env) # READ COMMITTED READ ONLY
try:
return unified_fiscal.assess_from_connection(conn, company, rtc_override=override, ncm_valid=ncm_valid)
finally:
conn.close()
def main():
ap = argparse.ArgumentParser(); ap.add_argument("--db", nargs="+", required=True); ap.add_argument("--out", default="docs/runs/rtc_resolution_dryrun.json"); ap.add_argument("--sample", type=int, default=100)
a = ap.parse_args()
report, sample = {}, []
for name in a.db:
path = lc.allowlist()[name]; env = lc.env_for(path)
crt = resolved_crt(name, path)
products = firebird_reader.read_products(env)
ncm_valid = {str(p["CODIGO"]): digits(p.get("CLASSIFICACAO_FISCAL")) in NCM_OK for p in products}
passes = {}
for label, override, company in (("AS_FOUND", None, {"crt": crt}), ("WHATIF_RTC_ON", True, {"crt": crt}), ("WHATIF_NORMAL_ON", True, {"crt": "3"})):
out = one_pass(env, company, override, ncm_valid)
summ = out["context"].get("rtc_resolution")
findings = out["findings"]
by_status = Counter((f.get("rtc_resolution") or {}).get("status", "NOT_EVALUATED") for f in findings.values())
conflicts = Counter(b["type"] for f in findings.values() for b in f["blockers"] if b["category"] == "RTC")
passes[label] = {"crt": company["crt"], "rtc_enabled": out["context"]["rtc_enabled"], "products": len(findings), "resolution_summary": summ, "by_status": dict(by_status), "rtc_blocker_types": dict(conflicts),
"AUTO_APPLY_CANDIDATES": sum(1 for f in findings.values() if (f.get("rtc_resolution") or {}).get("auto_apply_candidate") and not [b for b in f["blockers"] if b["type"] in ("CCLASSTRIB_CONFLICT",)])}
if label == "WHATIF_NORMAL_ON" and len(sample) < a.sample:
by_code = {str(p["CODIGO"]): p for p in products}
for code, f in findings.items():
res = f.get("rtc_resolution")
if res and len(sample) < a.sample and (res["auto_apply_candidate"] or res["status"] == "AMBIGUOUS") and (len([s for s in sample if s["database"] == name]) < max(1, a.sample // len(a.db))):
p = by_code.get(code, {})
sample.append({"database": name, "product": code, "ncm": digits(p.get("CLASSIFICACAO_FISCAL")), "current_cclasstrib": (p.get("CCLASSTRIB") or "").strip() if p.get("CCLASSTRIB") else "", "proposed_cclasstrib": res["resolved_cclasstrib"] or "",
"status": res["status"], "rule_id": res["rule_id"], "reason": res["reason"], "confidence_type": res["confidence_type"], "scenario": "WHATIF_NORMAL_ON (regime normal, RTC on)"})
passes[label]["context"] = {k: out["context"][k] for k in ("rtc_param", "rtc_source", "rtc_observed_at", "local_catalog_hash", "official_catalog", "catalog_integrity")}
report[name] = {"crt": crt, "total_products": len(products), "passes": passes}
print(name, "crt", crt, {k: (v["rtc_enabled"], v["by_status"], v["AUTO_APPLY_CANDIDATES"]) for k, v in passes.items()}, flush=True)
doc = {"mode": "READ_ONLY_DRY_RUN", "FIREBIRD_WRITES": 0, "WRITE_ATTEMPTS": 0, "WRITE_SUCCESSES": 0, "databases": report}
(ROOT / a.out).write_text(json.dumps(doc, ensure_ascii=False, indent=1, default=str), encoding="utf-8")
with open(ROOT / "docs" / "runs" / "rtc_resolution_audit_sample.csv", "w", newline="", encoding="utf-8-sig") as fh:
w = csv.DictWriter(fh, fieldnames=list(sample[0]) if sample else ["database"]); w.writeheader(); w.writerows(sample)
print("sample rows:", len(sample))
if __name__ == "__main__":
main()
+2 -2
View File
@@ -14,11 +14,11 @@ sys.path.insert(0, str(ROOT))
def test_versions_are_coherent_across_hub_agent_and_packages():
from central.app.version import GTI_FISCAL_VERSION
v = (ROOT / "release" / "VERSION").read_text(encoding="utf-8").strip()
assert v == GTI_FISCAL_VERSION == "1.1.8"
assert v == GTI_FISCAL_VERSION == "1.1.9"
pep = v.replace("-rc", "rc")
for pp in ("central/pyproject.toml", "agent/pyproject.toml"):
assert f'version = "{pep}"' in (ROOT / pp).read_text(encoding="utf-8")
assert '"AGENT_VERSION", "1.1.8"' in (ROOT / "agent" / "scripts" / "run_agent_service.py").read_text(encoding="utf-8")
assert '"AGENT_VERSION", "1.1.9"' in (ROOT / "agent" / "scripts" / "run_agent_service.py").read_text(encoding="utf-8")
manifest = ROOT / "release" / "RELEASE_MANIFEST.json"
if manifest.exists():
m = json.loads(manifest.read_text(encoding="utf-8"))
+30
View File
@@ -0,0 +1,30 @@
"""The RTC copy-write E2E may touch ONLY the disposable scratch copy; every real database path must be refused before any connection is opened."""
import importlib.util
import sys
from pathlib import Path
import pytest
ROOT = Path(__file__).resolve().parents[1]
def _load():
sys.path.insert(0, str(ROOT / "scripts")); sys.path.insert(0, str(ROOT / "agent" / "src"))
spec = importlib.util.spec_from_file_location("rtc_copy_write_e2e", ROOT / "scripts" / "rtc_copy_write_e2e.py")
module = importlib.util.module_from_spec(spec)
spec.loader.exec_module(module)
return module
def test_guard_accepts_only_the_exact_scratch_copy_and_refuses_real_databases():
module = _load()
module.guard(module.COPY) # the copy itself is fine
for path in (ROOT / "TESTESOFICIAIS.FDB", ROOT / "Banco" / "CONSTRUCAO.FDB", ROOT / "Banco" / "RINCAO.FDB", ROOT / "scratch_rtc_e2e" / "OTHER.FDB", Path("D:/PROJETOS/GTISISTEMAS/Banco/GTI.FDB")):
with pytest.raises(SystemExit):
module.guard(str(path))
assert "scratch_rtc_e2e" in module.COPY
def test_the_e2e_script_only_ever_connects_to_the_copy():
text = (ROOT / "scripts" / "rtc_copy_write_e2e.py").read_text(encoding="utf-8")
assert text.count("fdb.connect(") == 1 and "database=COPY" in text # the single connection helper is bound to the copy path