fix: RTC observed live with provenance, non-circular PIS/COFINS applicability, canonical CST normalization (1.1.8)

- RTC: Agent reads ALL PARAMETROS rows every sync (conflict => off), reports rtc_source/rtc_observed_at/rtc_param_rows; legal_effective_context is informational only
- PIS/COFINS: applicability from CRT + PISCOFINS flag + incidence only (never from empty fields); Simples w/o special treatment NOT_APPLICABLE, taxed regime with explicit N OPTIONAL, unreadable declaration NEEDS_REVIEW
- CST: one digit with an official zero-padded form is VALID_AFTER_NORMALIZATION (non-blocking warning), nothing is written to the ERP
- tests: RTC toggle 0->1->0 across syncs (Agent + Hub stale closing), non-circularity, normalization, panel provenance

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
gti
2026-09-24 17:46:45 -03:00
co-authored by Claude Sonnet 5
parent 1430042322
commit 88f4318072
18 changed files with 679 additions and 814 deletions
+30 -9
View File
@@ -8,9 +8,10 @@ from __future__ import annotations
import hashlib
import json
from datetime import date
from collections import Counter
from datetime import date, datetime, timezone
from central.app.unified_fiscal_coverage import PIS_COFINS_FIELDS, catalog_integrity, evaluate_product, rtc_enabled
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"
@@ -44,12 +45,17 @@ def load_rtc_context(cursor) -> tuple[dict, dict | None, dict]:
cursor.execute("SELECT TRIM(rf.RDB$RELATION_NAME), TRIM(rf.RDB$FIELD_NAME) FROM RDB$RELATION_FIELDS rf WHERE rf.RDB$RELATION_NAME IN ('C000025','RTC_CCLASSTRIB','RTC_CST','PARAMETROS')")
for table, column in cursor.fetchall():
schema.setdefault(table, set()).add(column)
extras: dict = {"param_value": None, "physical_fk": False}
extras: dict = {"param_value": None, "param_rows": 0, "param_conflict": False, "physical_fk": False}
if "USAR_REFORMA_TRIBUTARIA" in schema.get("PARAMETROS", ()):
try:
cursor.execute("SELECT FIRST 1 USAR_REFORMA_TRIBUTARIA FROM PARAMETROS")
row = cursor.fetchone()
extras["param_value"] = None if row is None or row[0] is None else row[0]
# ALL rows are read: the source of truth is what THIS installation's database says NOW (never a cached or Hub-side value).
cursor.execute("SELECT FIRST 20 USAR_REFORMA_TRIBUTARIA FROM PARAMETROS")
values = [None if r[0] is None else str(r[0]).strip().upper() for r in cursor.fetchall()]
extras["param_rows"] = len(values)
if len(set(values)) > 1:
extras["param_conflict"] = True # rows disagree: never guess "on" (RTC stays out of scope, and the conflict is reported)
elif values:
extras["param_value"] = values[0]
except Exception: # noqa: BLE001 - unreadable parameter => RTC stays off (not enabled by the ERP)
extras["param_value"] = None
try:
@@ -92,11 +98,26 @@ def assess_from_connection(conn, company: dict | None, *, today: date | None = N
today = today or date.today()
products = read_products(cursor, schema)
findings = {}
warn_by_type: Counter = Counter()
warn_products: set = set()
samples: list = []
applicability: Counter = Counter()
for p in products:
found = evaluate_product(p, company, rtc, today)
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()
findings[str(p.get("CODIGO") or "")] = found
code = str(p.get("CODIGO") or "")
findings[code] = found
applicability[found["applicability"]["PIS_COFINS"]] += 1
for w in found.get("warnings", []):
warn_by_type[w["type"]] += 1
warn_products.add(code)
if len(samples) < 10:
samples.append({"product_code": code, **w})
integrity = catalog_integrity(catalog) if catalog else None
return {"context": {"rtc_enabled": enabled, "rtc_supported": caps["HAS_RTC_RELATIONSHIP"], "rtc_catalog_valid": caps["RTC_CATALOG_VALID"], "rtc_param": extras["param_value"],
"catalog_integrity": integrity, "crt": (company or {}).get("crt"), "capabilities": caps, "products_evaluated": len(products)},
return {"context": {"rtc_enabled": enabled, "erp_rtc_enabled": enabled, "rtc_supported": caps["HAS_RTC_RELATIONSHIP"], "rtc_catalog_valid": caps["RTC_CATALOG_VALID"], "rtc_param": extras["param_value"],
"rtc_source": "SIMULATION" if rtc_override is not None else "PARAMETROS.USAR_REFORMA_TRIBUTARIA", "rtc_param_rows": extras["param_rows"], "rtc_param_conflict": extras["param_conflict"],
"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),
"non_canonical_warnings": {"products": len(warn_products), "by_type": dict(sorted(warn_by_type.items())), "samples": samples}},
"findings": findings}
+47 -7
View File
@@ -16,8 +16,8 @@ CLASSES = [("000001", "000", date(2025, 5, 5), None), ("200003", "200", date(202
class FakeCursor:
"""READ ONLY fake: answers only SELECTs and records every statement so tests can prove nothing else ran."""
def __init__(self, param=0, schema=SCHEMA_ROWS, products=PRODUCTS, classes=CLASSES, cst=(("000",), ("200",))):
self.param, self.schema, self.products, self.classes, self.cst, self.statements, self._rows = param, schema, products, classes, cst, [], []
def __init__(self, param=0, params=None, schema=SCHEMA_ROWS, products=PRODUCTS, classes=CLASSES, cst=(("000",), ("200",))):
self.param, self.params, self.schema, self.products, self.classes, self.cst, self.statements, self._rows = param, params, schema, products, classes, cst, [], []
def execute(self, sql, *args):
self.statements.append(sql)
@@ -26,7 +26,7 @@ class FakeCursor:
if "FROM RDB$RELATION_FIELDS" in text:
self._rows = list(self.schema)
elif "FROM PARAMETROS" in text:
self._rows = [(self.param,)]
self._rows = [(v,) for v in (self.params if self.params is not None else [self.param])]
elif "RDB$RELATION_CONSTRAINTS" in text:
self._rows = [(1,)]
elif "FROM RTC_CCLASSTRIB" in text:
@@ -62,7 +62,7 @@ def test_rtc_off_in_the_erp_is_out_of_scope_and_never_blocks():
out = uf.assess_from_connection(conn, {"crt": "1"}, today=date(2026, 9, 24))
assert out["context"]["rtc_enabled"] is False and out["context"]["rtc_supported"] is True and out["context"]["rtc_catalog_valid"] is True
assert not [b for f in out["findings"].values() for b in f["blockers"] if b["category"] == "RTC"]
assert kinds(out["findings"]["1"]) == [] and kinds(out["findings"]["2"]) == [] and kinds(out["findings"]["3"]) == ["COFINS_CST_INVALID", "PIS_CST_INVALID"]
assert kinds(out["findings"]["1"]) == [] and kinds(out["findings"]["2"]) == [] and kinds(out["findings"]["3"]) == [] # Simples + explicit "no special treatment": not applicable, typed "2" is only a warning
assert all(s.strip().upper().startswith("SELECT") for s in conn.cur.statements) # read-only: SELECT statements only
@@ -70,19 +70,19 @@ 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", "COFINS_CST_INVALID", "PIS_CST_INVALID"] # one queue, one verdict per product
assert kinds(out["findings"]["3"]) == ["CCLASSTRIB_MISSING"] # 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_param"] == 0 and not any("UPDATE" in s.upper() for s in conn.cur.statements)
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)
def test_old_schema_without_rtc_columns_reports_unsupported_and_still_evaluates_pis_cofins():
old = [("C000025", c) for c in ("CODIGO", "COD_SAIDA_PIS", "PISCOFINS", "INCIDENCIA_PISCOFINS")]
rows = [("1", None, "N", "NENHUMA"), ("2", "9", "N", "NENHUMA")]
rows = [("1", None, "N", "NENHUMA"), ("2", "049", "N", "NENHUMA")]
out = uf.assess_from_connection(FakeConn(schema=old, products=rows, classes=[], cst=()), {"crt": "3"})
assert out["context"]["rtc_supported"] is False and out["context"]["rtc_enabled"] is False
assert kinds(out["findings"]["1"]) == [] and kinds(out["findings"]["2"]) == ["PIS_CST_INVALID"]
@@ -108,3 +108,43 @@ def test_input_fingerprint_changes_with_the_fiscal_values_and_the_rtc_switch():
changed = [("1", None, None, "99", None, None, None, None, "N", "NENHUMA")] + PRODUCTS[1:]
c = uf.assess_from_connection(FakeConn(param=0, products=changed), {"crt": "1"})["findings"]["1"]["input_fingerprint"]
assert len({a, b, c}) == 3 and re.fullmatch(r"[0-9a-f]{64}", a)
def test_rtc_state_is_read_from_this_database_now_with_provenance_and_timestamp():
out = uf.assess_from_connection(FakeConn(param=1), {"crt": "1"})
ctx = out["context"]
assert ctx["rtc_enabled"] is True and ctx["erp_rtc_enabled"] is True and ctx["rtc_source"] == "PARAMETROS.USAR_REFORMA_TRIBUTARIA" and ctx["rtc_param"] == "1" and ctx["rtc_param_rows"] == 1
assert ctx["rtc_param_conflict"] is False and ctx["rtc_observed_at"].endswith("+00:00")
assert ctx["legal_effective_context"]["informational_only"] is True and ctx["legal_effective_context"]["crt"] == "1"
def test_toggle_between_syncs_is_followed_in_both_directions_without_any_cache():
conn = FakeConn(param=0)
first = uf.assess_from_connection(conn, {"crt": "1"})
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["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"})
assert third["context"]["rtc_enabled"] is False and not [b for f in third["findings"].values() for b in f["blockers"] if b["category"] == "RTC"]
assert third["findings"]["1"]["input_fingerprint"] == first["findings"]["1"]["input_fingerprint"]
def test_conflicting_or_repeated_parameter_rows_never_guess_on():
same = uf.assess_from_connection(FakeConn(params=[1, 1]), {"crt": "1"})["context"]
assert same["rtc_enabled"] is True and same["rtc_param_rows"] == 2 and same["rtc_param_conflict"] is False
conflict = uf.assess_from_connection(FakeConn(params=[1, 0]), {"crt": "1"})
assert conflict["context"]["rtc_enabled"] is False and conflict["context"]["rtc_param_conflict"] is True
assert not [b for f in conflict["findings"].values() for b in f["blockers"] if b["category"] == "RTC"]
assert uf.assess_from_connection(FakeConn(params=[]), {"crt": "1"})["context"]["rtc_enabled"] is False
def test_non_canonical_cst_warnings_are_reported_without_blocking():
rows = [("1", None, None, "4", None, None, "4", None, "N", "NENHUMA"), ("2", None, None, None, None, None, None, None, "N", "NENHUMA")]
out = uf.assess_from_connection(FakeConn(products=rows), {"crt": "3"})
assert kinds(out["findings"]["1"]) == [] and kinds(out["findings"]["2"]) == []
w = out["context"]["non_canonical_warnings"]
assert w["products"] == 1 and w["by_type"] == {"CST_COFINS_NON_CANONICAL_FORMAT": 1, "CST_PIS_NON_CANONICAL_FORMAT": 1} and w["samples"][0]["product_code"] == "1"
assert out["context"]["pis_cofins_applicability"] == {"OPTIONAL": 2}
+23
View File
@@ -185,3 +185,26 @@ def test_partial_reprocess_does_not_claim_a_full_fiscal_evaluation(env_setup, mo
hub, state = env_setup
result, _ = run_job("REPROCESS_PRODUCTS", [product("1")], state, monkeypatch)
assert result["fiscal_context"] is None and result["operational"] is None
def test_rtc_toggle_between_two_syncs_is_followed_by_the_next_job(env_setup, monkeypatch):
"""The Agent asks the database EVERY sync: 0 -> 1 makes the next job report RTC blockers, 1 -> 0 makes the following job report none (Hub then closes the old ones)."""
from gti_fiscal_agent import unified_fiscal
switch = {"on": False}
def assess(conn, company, **kw):
found = _findings(**{"1": [("CCLASSTRIB_MISSING", "RTC")]}) if switch["on"] else {}
return {"context": {"rtc_enabled": switch["on"], "rtc_supported": True, "rtc_catalog_valid": True, "rtc_source": "PARAMETROS.USAR_REFORMA_TRIBUTARIA", "rtc_observed_at": "2026-09-24T20:00:00+00:00"}, "findings": found}
monkeypatch.setattr(unified_fiscal, "assess_from_connection", assess)
hub, state = env_setup
products = [product("1"), product("2")]
off1, _ = run_job("INITIAL_SYNC", products, state, monkeypatch)
switch["on"] = True
on, _ = run_job("SYNC_INCREMENTAL", products, state, monkeypatch) # nothing changed in C000025: the delta cache must not hide the RTC change
switch["on"] = False
off2, _ = run_job("SYNC_INCREMENTAL", products, state, monkeypatch)
assert off1["blocked_products"] == [] and off1["fiscal_context"]["rtc_enabled"] is False
assert [b["field"] for b in on["blocked_products"]] == ["RTC"] and on["fiscal_context"]["rtc_enabled"] is True and on["operational"]["BLOCKED_FISCAL_DATA"] == 1
assert on["fiscal_context"]["rtc_observed_at"] and on["fiscal_context"]["rtc_source"] == "PARAMETROS.USAR_REFORMA_TRIBUTARIA"
assert off2["blocked_products"] == [] and off2["fiscal_context"]["rtc_enabled"] is False and off2["fiscal_context"]["evaluated_all"] is True
assert off2["operational"]["COMPLETE_PRODUCTS"] == 2
+11 -2
View File
@@ -162,9 +162,18 @@ def priority_label(value):
def rtc_status_view(summary):
"""Discreet Reforma Tributária status from the last completed job's `fiscal_context`. None when the Agent did not report it (older Agents)."""
"""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
if not isinstance(context, dict) or "rtc_enabled" not in context:
return None
observed = None
if context.get("rtc_observed_at"):
try:
observed = format_ui_datetime(datetime.fromisoformat(str(context["rtc_observed_at"]).replace("Z", "+00:00")))
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")),
"catalog_valid": bool(context.get("rtc_catalog_valid"))}
"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 "")}
+63 -30
View File
@@ -54,17 +54,25 @@ def incidence_kind(value) -> str:
return "UNKNOWN"
def canonical_cst(value) -> str | None:
"""The official 2-digit CST this text stands for, or None. Exactly two official digits, or ONE digit whose zero-padded form is an official code
("4" -> "04"). Nothing else is ever normalised ("049", "000", "2x" stay invalid)."""
code = _text(value)
if code in CST_DOMAIN:
return code
if len(code) == 1 and code.isdigit() and code.zfill(2) in CST_DOMAIN:
return code.zfill(2)
return None
def validate_cst(value) -> str:
"""EMPTY | VALID | VALID_AFTER_NORMALIZATION | INVALID."""
code = _text(value)
if not code:
return "EMPTY"
return "VALID" if code in CST_DOMAIN else "INVALID"
def cst_suggestion(value) -> str | None:
"""A short numeric code that becomes an official CST once zero-padded to two digits ('4' -> '04'). Only a hint for the operator; never applied."""
code = _text(value)
return code.zfill(2) if code.isdigit() and len(code) == 1 and code.zfill(2) in CST_DOMAIN else None
if code in CST_DOMAIN:
return "VALID"
return "VALID_AFTER_NORMALIZATION" if canonical_cst(code) else "INVALID"
def validate_rate(value) -> str:
@@ -82,56 +90,71 @@ def validate_rate(value) -> str:
def pis_cofins_applicability(product: dict, crt) -> dict:
"""{"state": NOT_APPLICABLE | REQUIRED | OPTIONAL | NEEDS_REVIEW, "basis": ...}.
NOT_APPLICABLE the product carries no product-level PIS/COFINS treatment at all (flag N/blank, incidence NENHUMA/blank, all six fields blank).
The tax is then settled elsewhere: inside the DAS (Simples family) or from the company default (regime normal).
REQUIRED the product itself claims a special treatment (flag S, or incidence MONOFASICA/SUBSTITUICAO): the exit CST (and a rate when the CST needs one) must be valid.
OPTIONAL no special claim, but some CST/rate was typed: it is not mandatory, yet whatever is there must be valid.
NEEDS_REVIEW the incidence text is not one the SGV defines (NENHUMA / MONOFASICA / SUBST.TRIBUTARIA): applicability cannot be decided.
Decided ONLY from signals that are independent of the six CST/rate fields - an empty field is a data state, never proof of "not applicable":
* the company regime (CRT);
* the product's own declaration: PISCOFINS flag and INCIDENCIA_PISCOFINS;
* official rules (LC 123/2006 art. 13 IV/V and art. 18 par. 4-A I; Leis 9.718/98, 10.637/02, 10.833/03; SPED 4.3.x for the CST domain/semantics).
REQUIRED the product declares a special treatment (flag S, or incidence MONOFASICA / SUBSTITUICAO): the exit CST must be valid, whatever the fields hold.
NOT_APPLICABLE Simples family (CRT 1/2/4) with no special treatment declared: PIS/Cofins are collected inside the DAS (LC 123 art. 13), so the product carries no
obligation. The same verdict holds whether the fields are empty or typed.
OPTIONAL taxed regime (CRT 3 or other) AND the product explicitly declares "no special treatment" (flag N + incidence NENHUMA): the tax applies but the
company default covers it, so the product default is not mandatory; anything typed must still be valid.
NEEDS_REVIEW the declaration cannot be read: incidence text outside the SGV vocabulary, or a taxed regime with a blank flag/incidence.
"""
flag = _text(product.get("PISCOFINS")).upper()
kind = incidence_kind(product.get("INCIDENCIA_PISCOFINS"))
has_values = any(_text(product.get(c)) for c in PIS_COFINS_FIELDS)
raw_incidence = _text(product.get("INCIDENCIA_PISCOFINS"))
kind = incidence_kind(raw_incidence)
if kind == "UNKNOWN":
return {"state": "NEEDS_REVIEW", "basis": "INCIDENCE_NOT_DEFINED_BY_SGV"}
if flag == "S" or kind in ("MONOFASICA", "SUBSTITUICAO"):
return {"state": "REQUIRED", "basis": "PRODUCT_DECLARES_SPECIAL_TREATMENT"}
if has_values:
return {"state": "OPTIONAL", "basis": "VALUES_TYPED_WITHOUT_SPECIAL_CLAIM"}
return {"state": "NOT_APPLICABLE", "basis": "SIMPLES_DAS" if _text(crt) in SIMPLES_FAMILY else "COMPANY_DEFAULT"}
if _text(crt) in SIMPLES_FAMILY:
return {"state": "NOT_APPLICABLE", "basis": "SIMPLES_DAS_NO_SPECIAL_TREATMENT"}
if flag == "N" and raw_incidence:
return {"state": "OPTIONAL", "basis": "TAXED_REGIME_COMPANY_DEFAULT_COVERS"}
return {"state": "NEEDS_REVIEW", "basis": "NO_DECLARATION_IN_TAXED_REGIME"}
def evaluate_pis_cofins(product: dict, crt) -> dict:
"""{"applicability": {...}, "blockers": [(type, reason)]}. Entry and exit are separate; a missing OPTIONAL value never blocks."""
"""{"applicability": {...}, "blockers": [(type, reason)], "warnings": [(type, reason)]}. Entry and exit are separate; PIS and COFINS independent.
Blockers exist only where the field applies (REQUIRED / OPTIONAL). Non-blocking warnings report data-quality facts (non-canonical or ignored values)."""
app = pis_cofins_applicability(product, crt)
blockers: list[tuple[str, str]] = []
if app["state"] in ("NOT_APPLICABLE", "NEEDS_REVIEW"):
return {"applicability": app, "blockers": blockers}
warnings: list[tuple[str, str]] = []
kind = incidence_kind(product.get("INCIDENCIA_PISCOFINS"))
flag = _text(product.get("PISCOFINS")).upper()
applies = app["state"] in ("REQUIRED", "OPTIONAL")
for tax in ("PIS", "COFINS"):
entry, exit_, rate = product.get(f"COD_ENTRADA_{tax}"), product.get(f"COD_SAIDA_{tax}"), product.get(f"ALIQ_{tax}")
entry_state, exit_state, rate_state = validate_cst(entry), validate_cst(exit_), validate_rate(rate)
if entry_state == "INVALID":
blockers.append((f"{tax}_CST_INVALID", "ENTRY" + (f":UNPADDED_USE_{cst_suggestion(entry)}" if cst_suggestion(entry) else "")))
if exit_state == "INVALID":
blockers.append((f"{tax}_CST_INVALID", "EXIT" + (f":UNPADDED_USE_{cst_suggestion(exit_)}" if cst_suggestion(exit_) else "")))
for slot, value, state in (("ENTRY", entry, entry_state), ("EXIT", exit_, exit_state)):
if state == "VALID_AFTER_NORMALIZATION":
warnings.append((f"CST_{tax}_NON_CANONICAL_FORMAT", f"{slot}:{_text(value)}->{canonical_cst(value)}"))
elif state == "INVALID":
if applies:
blockers.append((f"{tax}_CST_INVALID", slot))
else:
warnings.append((f"CST_{tax}_INVALID_IGNORED", f"{slot}:{_text(value)}"))
if not applies:
continue
if app["state"] == "REQUIRED" and exit_state == "EMPTY":
blockers.append((f"{tax}_CST_MISSING", "EXIT"))
if rate_state == "INVALID":
blockers.append((f"{tax}_RATE_INVALID", "RATE"))
if exit_state == "VALID":
code = _text(exit_)
code = canonical_cst(exit_)
if code:
if code in RATE_REQUIRED_CST and rate_state == "EMPTY":
blockers.append((f"{tax}_RATE_MISSING", f"CST_{code}_NEEDS_RATE"))
if kind in INCIDENCE_CST and code not in INCIDENCE_CST[kind]:
blockers.append((f"{tax}_CST_INCIDENCE_MISMATCH", f"{kind}:CST_{code}"))
if kind == "NONE" and app["state"] == "REQUIRED" and code in RATE_NOT_APPLICABLE_CST - {"03"} and flag == "S":
blockers.append((f"{tax}_CST_INCIDENCE_MISMATCH", f"NENHUMA:CST_{code}"))
if flag == "N" and kind != "NONE":
if applies and flag == "N" and kind != "NONE":
blockers.append(("PIS_COFINS_FLAG_INCONSISTENT", "FLAG_N_WITH_SPECIAL_INCIDENCE"))
if flag == "S" and kind == "NONE" and not any(_text(product.get(c)) for c in PIS_COFINS_FIELDS):
if applies and flag == "S" and kind == "NONE" and not any(_text(product.get(c)) for c in PIS_COFINS_FIELDS):
blockers.append(("PIS_COFINS_FLAG_INCONSISTENT", "FLAG_S_WITHOUT_INCIDENCE_OR_CODES"))
return {"applicability": app, "blockers": _dedupe(blockers)}
return {"applicability": app, "blockers": _dedupe(blockers), "warnings": _dedupe(warnings)}
def _dedupe(items):
@@ -201,11 +224,21 @@ def evaluate_product(product: dict, company: dict | None, rtc: dict | None, toda
pis = evaluate_pis_cofins(product, crt)
rtc_result = evaluate_rtc(product, rtc, today)
blockers = [{"type": kind, "category": blocker_category(kind), "reason": reason} for kind, reason in pis["blockers"] + rtc_result["blockers"]]
warnings = [{"type": kind, "reason": reason} for kind, reason in pis["warnings"]]
mandatory = []
if pis["applicability"]["state"] == "REQUIRED":
mandatory += ["COD_SAIDA_PIS", "COD_SAIDA_COFINS"]
if rtc_result["applicability"]["state"] == "REQUIRED":
mandatory.append("CCLASSTRIB")
return {"blockers": blockers, "applicability": {"PIS_COFINS": pis["applicability"]["state"], "RTC": rtc_result["applicability"]["state"]},
"basis": {"PIS_COFINS": pis["applicability"]["basis"], "RTC": rtc_result["applicability"]["basis"]}, "mandatory_fields": mandatory,
"basis": {"PIS_COFINS": pis["applicability"]["basis"], "RTC": rtc_result["applicability"]["basis"]}, "mandatory_fields": mandatory, "warnings": warnings,
"needs_review": pis["applicability"]["state"] == "NEEDS_REVIEW"}
def legal_effective_context(crt) -> dict:
"""INFORMATIONAL ONLY (never a blocker, never part of completeness). The operational rule stays ERP_RTC_ENABLED; this records what the legislation
says about IBS/CBS in documents for the company regime so a difference in timing can be explained without inventing a rule."""
simples = _text(crt) in SIMPLES_FAMILY
return {"crt": _text(crt) or None, "informational_only": True,
"ibs_cbs_in_documents": "FROM_2027_FOR_SIMPLES_ORIENTATION_PENDING" if simples else ("MANDATORY_FROM_2026-01-01" if _text(crt) == "3" else "UNKNOWN_REGIME"),
"sources": ["NT 2025.002-RTC v1.51 (Portal NF-e, 04/08/2026): CRT 1/2/4 orientations in a future NT; IBS/CBS taxation of these taxpayers only from 2027", "LC 214/2025 art. 348"]}
+1 -1
View File
@@ -8,7 +8,7 @@
<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" }}">{{ 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 %}<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>
+12
View File
@@ -575,3 +575,15 @@ def test_pis_cofins_and_rtc_blockers_appear_in_the_same_queue_and_dashboard_brea
assert "fila RTC" not in queue.lower()
filtered = client.get("/blockers?scope=active&status=OPEN&field=RTC").text
assert "cClassTrib ausente" in filtered and "CST PIS inválido" not in filtered
def test_rtc_status_shows_where_and_when_the_agent_observed_it():
client, agent_id, _ = _panel_client()
_complete_sync(client, agent_id, {"rtc_enabled": True, "rtc_supported": True, "rtc_catalog_valid": True, "rtc_source": "PARAMETROS.USAR_REFORMA_TRIBUTARIA", "rtc_observed_at": "2026-09-24T20:00:00+00:00"})
text = client.get(f"/agents/{agent_id}").text
assert 'title="Lido no banco do cliente (PARAMETROS.USAR_REFORMA_TRIBUTARIA) em ' in text and ">Ativa<" in text
_complete_sync(client, agent_id, {"rtc_enabled": False, "rtc_param_conflict": True, "rtc_source": "PARAMETROS.USAR_REFORMA_TRIBUTARIA"})
conflict = client.get(f"/agents/{agent_id}").text
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
+89 -27
View File
@@ -1,19 +1,25 @@
from datetime import date
from central.app.operational_state import classify_product_state, summarize_operational_states
from central.app.unified_fiscal_coverage import (blocker_category, evaluate_product, incidence_kind, rtc_enabled, validate_cclasstrib)
from central.app.unified_fiscal_coverage import (blocker_category, canonical_cst, evaluate_product, incidence_kind, legal_effective_context, pis_cofins_applicability, rtc_enabled,
validate_cclasstrib, validate_cst)
CATALOG = {"classes": {"000001": "000", "200003": "200", "410004": "410", "999999": None, "555555": "555"}, "cst": {"000", "200", "410"}, "duplicates": 0}
ON = {"enabled": True, "catalog": CATALOG, "catalog_valid": True}
OFF = {"enabled": False, "catalog": CATALOG, "catalog_valid": True}
SIMPLES, NORMAL = {"crt": "1"}, {"crt": "3"}
EMPTY = {"CODIGO": "1", "PISCOFINS": "N", "INCIDENCIA_PISCOFINS": "NENHUMA"}
FIELDS = ("COD_ENTRADA_PIS", "COD_SAIDA_PIS", "ALIQ_PIS", "COD_ENTRADA_COFINS", "COD_SAIDA_COFINS", "ALIQ_COFINS")
def kinds(result):
return {b["type"] for b in result["blockers"]}
def warns(result):
return {w["type"] for w in result["warnings"]}
# ---------------------------------------------------------------- RTC on / off
def test_rtc_off_ignores_cclasstrib_completely():
for value in (None, "", "12x", "999999", "000001"):
@@ -48,14 +54,85 @@ def test_rtc_enabled_needs_schema_and_erp_switch():
assert rtc_enabled(caps) and not rtc_enabled({**caps, "RTC_ENABLED_BY_PARAM": False}) and not rtc_enabled({**caps, "HAS_RTC_RELATIONSHIP": False}) and not rtc_enabled({})
# ---------------------------------------------------------------- PIS / COFINS applicability
def test_empty_product_is_not_applicable_in_every_regime_and_never_blocks():
for company, basis in ((SIMPLES, "SIMPLES_DAS"), ({"crt": "2"}, "SIMPLES_DAS"), ({"crt": "4"}, "SIMPLES_DAS"), (NORMAL, "COMPANY_DEFAULT"), ({"crt": None}, "COMPANY_DEFAULT"), (None, "COMPANY_DEFAULT")):
r = evaluate_product(EMPTY, company, OFF)
assert r["applicability"]["PIS_COFINS"] == "NOT_APPLICABLE" and r["basis"]["PIS_COFINS"] == basis and r["blockers"] == []
assert evaluate_product({"CODIGO": "1"}, SIMPLES, OFF)["blockers"] == [] # columns absent in an old schema: same answer
def test_legal_context_is_informational_and_never_creates_a_blocker():
simples, normal = legal_effective_context("1"), legal_effective_context("3")
assert simples["informational_only"] and "2027" in simples["ibs_cbs_in_documents"] and normal["ibs_cbs_in_documents"] == "MANDATORY_FROM_2026-01-01"
assert legal_effective_context(None)["ibs_cbs_in_documents"] == "UNKNOWN_REGIME"
# the operational rule is the ERP switch only: a Simples company with RTC on is validated, one with RTC off is not, whatever the legal timing says
assert kinds(evaluate_product({**EMPTY, "CCLASSTRIB": None}, SIMPLES, ON)) == {"CCLASSTRIB_MISSING"} and kinds(evaluate_product({**EMPTY, "CCLASSTRIB": None}, SIMPLES, OFF)) == set()
# ---------------------------------------------------------------- PIS / COFINS applicability is NOT circular
def test_applicability_never_depends_on_whether_the_six_fields_are_empty():
typed = {f: "49" if "COD" in f else "0" for f in FIELDS}
for crt in ("1", "2", "4", "3", None):
for base in (EMPTY, {"CODIGO": "1", "PISCOFINS": "S", "INCIDENCIA_PISCOFINS": "MONOFÁSICA"}, {"CODIGO": "1", "PISCOFINS": "", "INCIDENCIA_PISCOFINS": ""}):
empty_state = pis_cofins_applicability(base, crt)
filled_state = pis_cofins_applicability({**base, **typed}, crt)
assert empty_state == filled_state, (crt, base) # same verdict with the fields empty or typed: emptiness proves nothing
def test_empty_fields_with_applicable_context_is_a_missing_blocker():
for crt in ("1", "3"):
r = evaluate_product({**EMPTY, "PISCOFINS": "S", "INCIDENCIA_PISCOFINS": "MONOFÁSICA"}, {"crt": crt}, OFF)
assert r["applicability"]["PIS_COFINS"] == "REQUIRED" and kinds(r) == {"PIS_CST_MISSING", "COFINS_CST_MISSING"}
def test_empty_fields_with_provably_not_applicable_context_is_not_applicable():
for crt in ("1", "2", "4"):
r = evaluate_product(EMPTY, {"crt": crt}, OFF)
assert r["applicability"]["PIS_COFINS"] == "NOT_APPLICABLE" and r["basis"]["PIS_COFINS"] == "SIMPLES_DAS_NO_SPECIAL_TREATMENT" and r["blockers"] == [] and not r["needs_review"]
assert evaluate_product({"CODIGO": "1"}, SIMPLES, OFF)["blockers"] == [] # columns absent in an old schema, Simples: nothing to require
def test_empty_fields_with_insufficient_context_need_review_and_never_default_to_complete():
for company in (NORMAL, {"crt": None}, None):
r = evaluate_product({"CODIGO": "1", "PISCOFINS": "", "INCIDENCIA_PISCOFINS": ""}, company, OFF)
assert r["applicability"]["PIS_COFINS"] == "NEEDS_REVIEW" and r["needs_review"] and r["blockers"] == []
state = classify_product_state({"CODIGO": "1"}, {"status": "EXACT_NCM"}, fiscal=r)["state"]
assert state == "NEEDS_REVIEW" # counted as UNCLASSIFIED, never inflating coverage
def test_taxed_regime_with_explicit_no_special_treatment_is_optional_and_typed_values_must_be_valid():
r = evaluate_product(EMPTY, NORMAL, OFF)
assert r["applicability"]["PIS_COFINS"] == "OPTIONAL" and r["blockers"] == []
assert kinds(evaluate_product({**EMPTY, "COD_SAIDA_PIS": "2x"}, NORMAL, OFF)) == {"PIS_CST_INVALID"}
def test_unknown_incidence_text_needs_review_in_every_regime():
for crt in ("1", "3"):
r = evaluate_product({**EMPTY, "INCIDENCIA_PISCOFINS": "OUTRA COISA"}, {"crt": crt}, OFF)
assert r["applicability"]["PIS_COFINS"] == "NEEDS_REVIEW" and r["needs_review"] and r["blockers"] == []
def test_simples_typed_garbage_is_ignored_as_a_warning_not_a_blocker():
row = {**EMPTY, "COD_ENTRADA_PIS": "2", "COD_SAIDA_PIS": "2", "ALIQ_PIS": "2", "COD_ENTRADA_COFINS": "2", "COD_SAIDA_COFINS": "2", "ALIQ_COFINS": "2"}
r = evaluate_product(row, SIMPLES, OFF)
assert r["applicability"]["PIS_COFINS"] == "NOT_APPLICABLE" and r["blockers"] == []
assert warns(r) == {"CST_PIS_NON_CANONICAL_FORMAT", "CST_COFINS_NON_CANONICAL_FORMAT"} # "2" is one digit of official "02": normalisable
bad = evaluate_product({**EMPTY, "COD_ENTRADA_COFINS": "000", "COD_SAIDA_COFINS": "060"}, SIMPLES, OFF)
assert bad["blockers"] == [] and warns(bad) == {"CST_COFINS_INVALID_IGNORED"}
# ---------------------------------------------------------------- CST normalisation
def test_canonical_cst_accepts_only_one_digit_with_an_official_zero_padded_form():
assert canonical_cst("4") == "04" and canonical_cst("04") == "04" and canonical_cst(" 4 ") == "04" and canonical_cst("9") == "09" and canonical_cst("1") == "01"
for text in ("0", "049", "000", "060", "2x", "x", "", None, "10", "12", "00"):
assert canonical_cst(text) is None, text
assert validate_cst("4") == "VALID_AFTER_NORMALIZATION" and validate_cst("04") == "VALID" and validate_cst("049") == "INVALID" and validate_cst("") == "EMPTY"
def test_unpadded_cst_is_semantically_valid_warns_and_is_used_canonically_for_the_rules():
row = {**EMPTY, "COD_SAIDA_PIS": "4", "COD_SAIDA_COFINS": "4"}
r = evaluate_product(row, NORMAL, OFF)
assert r["blockers"] == [] and warns(r) == {"CST_PIS_NON_CANONICAL_FORMAT", "CST_COFINS_NON_CANONICAL_FORMAT"}
mono = {**EMPTY, "PISCOFINS": "S", "INCIDENCIA_PISCOFINS": "MONOFÁSICA", "COD_SAIDA_PIS": "4", "COD_SAIDA_COFINS": "4"}
assert kinds(evaluate_product(mono, NORMAL, OFF)) == set() # "4" -> "04" is compatible with MONOFASICA
assert kinds(evaluate_product({**mono, "COD_SAIDA_PIS": "1", "ALIQ_PIS": "1,65"}, NORMAL, OFF)) == {"PIS_CST_INCIDENCE_MISMATCH"} # "1" -> "01" is not
assert kinds(evaluate_product({**EMPTY, "COD_SAIDA_PIS": "049"}, NORMAL, OFF)) == {"PIS_CST_INVALID"} # ambiguous: still a blocker
# ---------------------------------------------------------------- PIS / COFINS rules
def test_declared_monophasic_product_requires_a_valid_exit_cst():
good = {**EMPTY, "PISCOFINS": "S", "INCIDENCIA_PISCOFINS": "MONOFÁSICA", "COD_SAIDA_PIS": "04", "COD_SAIDA_COFINS": "04"}
r = evaluate_product(good, SIMPLES, OFF)
@@ -69,13 +146,6 @@ def test_st_incidence_with_taxable_cst_is_a_mismatch_and_pis_cofins_are_independ
assert kinds(evaluate_product(row, NORMAL, OFF)) == {"PIS_CST_INCIDENCE_MISMATCH"} # only PIS is wrong; COFINS 05 is right
def test_value_2_is_invalid_format_not_a_test_product_and_not_silently_ignored():
row = {**EMPTY, "COD_ENTRADA_PIS": "2", "COD_SAIDA_PIS": "2", "ALIQ_PIS": "2", "COD_ENTRADA_COFINS": "2", "COD_SAIDA_COFINS": "2", "ALIQ_COFINS": "2"}
r = evaluate_product(row, SIMPLES, OFF)
assert r["applicability"]["PIS_COFINS"] == "OPTIONAL" and kinds(r) == {"PIS_CST_INVALID", "COFINS_CST_INVALID"}
assert "PIS_RATE_INVALID" not in kinds(r) # rate 2 is a valid number; only the CST codes are malformed
def test_rate_rules_follow_the_cst():
base = {**EMPTY, "PISCOFINS": "S", "INCIDENCIA_PISCOFINS": "NENHUMA"}
assert kinds(evaluate_product({**base, "COD_SAIDA_PIS": "01", "COD_SAIDA_COFINS": "01"}, NORMAL, OFF)) == {"PIS_RATE_MISSING", "COFINS_RATE_MISSING"}
@@ -85,17 +155,15 @@ def test_rate_rules_follow_the_cst():
assert kinds(evaluate_product({**base, "COD_SAIDA_PIS": "49", "ALIQ_PIS": "150", "COD_SAIDA_COFINS": "49"}, SIMPLES, OFF)) == {"PIS_RATE_INVALID"}
def test_inconsistent_flags_and_unknown_incidence():
def test_inconsistent_flags():
assert "PIS_COFINS_FLAG_INCONSISTENT" in kinds(evaluate_product({**EMPTY, "PISCOFINS": "N", "INCIDENCIA_PISCOFINS": "MONOFÁSICA", "COD_SAIDA_PIS": "04", "COD_SAIDA_COFINS": "04"}, SIMPLES, OFF))
assert "PIS_COFINS_FLAG_INCONSISTENT" in kinds(evaluate_product({**EMPTY, "PISCOFINS": "S"}, SIMPLES, OFF))
unknown = evaluate_product({**EMPTY, "INCIDENCIA_PISCOFINS": "OUTRA COISA"}, SIMPLES, OFF)
assert unknown["needs_review"] and unknown["blockers"] == [] and unknown["applicability"]["PIS_COFINS"] == "NEEDS_REVIEW"
def test_incidence_text_is_encoding_and_accent_tolerant():
for text in ("MONOFÁSICA", "MONOFASICA", "MONOF�SICA", "monofásica"):
assert incidence_kind(text) == "MONOFASICA"
assert incidence_kind("SUBST.TRIBUTÁRIA") == "SUBSTITUICAO" and incidence_kind(None) == "NONE" and incidence_kind(" nenhuma ") == "NONE"
assert incidence_kind("SUBST.TRIBUTÁRIA") == "SUBSTITUICAO" and incidence_kind(None) == "NONE" and incidence_kind(" nenhuma ") == "NONE" and incidence_kind("???") == "UNKNOWN"
# ---------------------------------------------------------------- ONE definition of complete, ONE queue
@@ -105,7 +173,8 @@ def test_blocker_categories_feed_the_single_dashboard_breakdown():
def test_unified_coverage_invariant_and_rtc_toggle_changes_only_rtc_products():
products = [{**EMPTY, "CODIGO": "1"}, {**EMPTY, "CODIGO": "2", "COD_SAIDA_PIS": "2"}, {**EMPTY, "CODIGO": "3", "CCLASSTRIB": "200003"}, {**EMPTY, "CODIGO": "4", "INCIDENCIA_PISCOFINS": "???"}]
products = [{**EMPTY, "CODIGO": "1"}, {**EMPTY, "CODIGO": "2", "PISCOFINS": "S", "INCIDENCIA_PISCOFINS": "MONOFÁSICA"}, {**EMPTY, "CODIGO": "3", "CCLASSTRIB": "200003"},
{**EMPTY, "CODIGO": "4", "INCIDENCIA_PISCOFINS": "???"}]
def run(rtc):
states = [classify_product_state(p, {"status": "EXACT_NCM"}, fiscal=evaluate_product(p, SIMPLES, rtc)) for p in products]
@@ -118,14 +187,7 @@ def test_unified_coverage_invariant_and_rtc_toggle_changes_only_rtc_products():
def test_fiscal_blockers_never_hide_or_override_a_decision_blocker():
fiscal = evaluate_product({**EMPTY, "COD_SAIDA_PIS": "2"}, SIMPLES, OFF)
fiscal = evaluate_product({**EMPTY, "PISCOFINS": "S", "INCIDENCIA_PISCOFINS": "MONOFÁSICA"}, SIMPLES, OFF)
state = classify_product_state({"CODIGO": "1"}, {"status": "INVALID_NCM"}, fiscal=fiscal)
assert state["state"] == "BLOCKED_MISSING_EVIDENCE" # the NCM blocker stays the primary state
assert classify_product_state({"CODIGO": "1"}, {"status": "EXACT_NCM"}, fiscal=None)["state"] == "COMPLETE"
def test_unpadded_single_digit_cst_is_flagged_with_the_official_code_as_a_hint_only():
row = {**EMPTY, "COD_SAIDA_PIS": "4", "COD_SAIDA_COFINS": "4"}
r = evaluate_product(row, NORMAL, OFF)
assert kinds(r) == {"PIS_CST_INVALID", "COFINS_CST_INVALID"} and any("UNPADDED_USE_04" in b["reason"] for b in r["blockers"])
assert evaluate_product({**EMPTY, "COD_SAIDA_PIS": "049"}, NORMAL, OFF)["blockers"][0]["reason"] == "EXIT" # 3 digits: ambiguous, no hint
+28
View File
@@ -236,3 +236,31 @@ def test_partial_reprocess_never_closes_fiscal_blockers_of_other_products():
finish(client, engine, {"operational": OPERATIONAL, "blocked_products": [], "resolved_products": [], "fiscal_context": {"evaluated_all": False}})
with Session(engine) as s:
assert len(s.scalars(select(BlockedProduct).where(BlockedProduct.resolved_at.is_(None))).all()) == 1
def test_rtc_on_then_off_and_pis_fix_leave_no_stale_open_blockers_and_never_duplicate():
from central.app.models import BlockedProduct
client, engine = _client()
hb(client)
rtc_missing = _fiscal_blocker("20", "RTC", "CCLASSTRIB_MISSING", "r1")
pis_invalid = _fiscal_blocker("21", "PIS", "PIS_CST_INVALID", "p1")
op = {**OPERATIONAL, "BLOCKED_MISSING_EVIDENCE": 0, "BLOCKED_CONFLICT": 0}
finish(client, engine, {"operational": {**op, "COMPLETE": 519, "BLOCKED_FISCAL_DATA": 2, "SAFE_BLOCKED": 2}, "blocked_products": [rtc_missing, pis_invalid, dict(rtc_missing)], "resolved_products": [],
"fiscal_context": {"rtc_enabled": True, "evaluated_all": True}})
with Session(engine) as s:
assert len(s.scalars(select(BlockedProduct)).all()) == 2 # the repeated blocker is one row
age_last_sync(engine, 4000)
hb(client)
# RTC switched off in the ERP AND the PIS value was normalised/fixed: the next full pass reports nothing
finish(client, engine, {"operational": {**op, "COMPLETE": 521}, "blocked_products": [], "resolved_products": [], "fiscal_context": {"rtc_enabled": False, "evaluated_all": True}})
with Session(engine) as s:
rows = s.scalars(select(BlockedProduct)).all()
assert [r.resolved_at is not None for r in rows] == [True, True] and {r.resolution_source for r in rows} == {"AUTO_REEVALUATION"}
assert s.scalars(select(BlockedProduct).where(BlockedProduct.resolved_at.is_(None))).all() == []
age_last_sync(engine, 4000)
hb(client)
# RTC comes back on: the SAME blocker (same fingerprint) reopens instead of being duplicated
finish(client, engine, {"operational": {**op, "COMPLETE": 520, "BLOCKED_FISCAL_DATA": 1, "SAFE_BLOCKED": 1}, "blocked_products": [rtc_missing], "resolved_products": [], "fiscal_context": {"rtc_enabled": True, "evaluated_all": True}})
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)]
+26 -32
View File
@@ -1,52 +1,46 @@
# PIS/COFINS — aplicabilidade no cadastro de produto (SGV)
Implementação: `central/app/unified_fiscal_coverage.py` (`pis_cofins_applicability`, `evaluate_pis_cofins`). Somente leitura; nenhum valor é preenchido.
`ACCESSED_AT` de todas as fontes: **2026-09-24** (hashes em `docs/sources/SOURCES_MANIFEST_UNIFIED_FISCAL_V1.json` e `docs/sources/SOURCES_MANIFEST_V3.json`).
Implementação: `central/app/unified_fiscal_coverage.py` (`pis_cofins_applicability`, `evaluate_pis_cofins`, `canonical_cst`). Somente leitura; nenhum valor é preenchido nem normalizado no ERP.
`ACCESSED_AT` das fontes: **2026-09-24** (hashes em `docs/sources/SOURCES_MANIFEST_UNIFIED_FISCAL_V1.json` e `SOURCES_MANIFEST_V3.json`).
## 1. O que o campo do produto é (e o que não é)
Campos: `COD_ENTRADA_PIS`, `COD_SAIDA_PIS`, `ALIQ_PIS`, `COD_ENTRADA_COFINS`, `COD_SAIDA_COFINS`, `ALIQ_COFINS`, `PISCOFINS` (N/S), `INCIDENCIA_PISCOFINS` (NENHUMA / MONOFÁSICA / SUBST.TRIBUTÁRIA).
- São *defaults de cadastro* (tela Produtos > Config. Impostos > CST PIS/COFINS; binding provado por DFM em `docs/SGV_PRODUCT_FISCAL_UI_MAP_V1.json`). Os campos CST usam um seletor com a lista oficial "NN - descrição" (2 dígitos).
- O SGV copia esses defaults para os itens de documentos de entrada (SQL de inserção do item no executável: `COD_SAIDA_PIS, COD_ENTRADA_PIS, COD_SAIDA_COFINS, COD_ENTRADA_COFINS, ... cst_ipi`).
- Não são a tributação efetiva do documento. **DOCUMENT_PIS_COFINS_SOURCE**: em `NFCE_ITENS.CST_PIS_COFINS` o CST **não vem do cadastro do produto**. Evidência (TESTESOFICIAIS): os 3.336 itens com CST `01` são todos de dezembro/2022 (dia 01 a 31, sequência contínua de notas) e independentes do produto (produtos com `PISCOFINS=N`, campos vazios); antes (jun–nov/2022) e depois (2023–2026) os itens saem com CST em branco; o produto com CST `04`/MONOFÁSICA cadastrado emitiu em branco. Nas 4 bases de clientes (~570 mil itens) 100% saem em branco, mesmo para produtos com CST `04` no cadastro (RINCAO). O código do emissor de NFC-e não está disponível localmente (o executável SGV 5.0.6/5.1.0.4 não contém `CST_PIS_COFINS`), então a regra exata do documento **não é comprovável** aqui; o que é comprovado é que ela não lê o cadastro do produto.
- São *defaults de cadastro* (tela Produtos > Config. Impostos > CST PIS/COFINS; binding por DFM em `docs/SGV_PRODUCT_FISCAL_UI_MAP_V1.json`). Os campos CST usam um seletor com a lista oficial "NN - descrição" (2 dígitos).
- O SGV copia esses defaults para os itens de documentos de entrada (SQL de inserção do item no executável).
- Não são a tributação efetiva do documento. **DOCUMENT_PIS_COFINS_SOURCE**: em `NFCE_ITENS.CST_PIS_COFINS` o CST **não vem do cadastro do produto**. TESTESOFICIAIS: os 3.336 itens com CST `01` são todos de dezembro/2022 (dias 01–31, notas contíguas), independentes do produto (`PISCOFINS=N`, campos vazios); antes e depois os itens saem em branco; o produto com CST `04` cadastrado emitiu em branco. Nas 4 bases de clientes (~570 mil itens) 100% saem em branco. O emissor de NFC-e não existe localmente (o executável SGV não contém `CST_PIS_COFINS`), então a regra exata do documento **não é comprovável** aqui.
## 2. Modelo de aplicabilidade (por produto)
## 2. Modelo de aplicabilidade — não circular
A aplicabilidade usa **somente sinais independentes dos seis campos**: regime da empresa (CRT), flag `PISCOFINS`, `INCIDENCIA_PISCOFINS` e as regras oficiais abaixo. **Campo vazio é estado de dado, nunca prova de não aplicabilidade**: o veredito é idêntico com os campos vazios ou preenchidos (teste `test_applicability_never_depends_on_whether_the_six_fields_are_empty`).
| Estado | Condição (dados do próprio produto) | Efeito |
| Estado | Condição | Efeito |
|---|---|---|
| `NOT_APPLICABLE` | flag N/vazio, incidência NENHUMA/vazia, os 6 campos vazios | não entra na completude; sem blocker |
| `REQUIRED` | flag `S`, ou incidência MONOFÁSICA / SUBST.TRIBUTÁRIA | CST de **saída** válido obrigatório; alíquota conforme CST |
| `OPTIONAL` | sem alegação especial, mas algum CST/alíquota digitado | nada é obrigatório; o que existir tem de ser válido |
| `NEEDS_REVIEW` | incidência fora do vocabulário do SGV | único caso não decidível → "Não classificado" |
| `REQUIRED` | flag `S`, ou incidência MONOFÁSICA / SUBST.TRIBUTÁRIA (declaração própria do produto) | CST de **saída** válido obrigatório (vazio ⇒ `*_CST_MISSING`); alíquota conforme o CST |
| `NOT_APPLICABLE` | CRT 1/2/4 (Simples) **sem** tratamento especial declarado | não entra na completude; valor digitado vira só *warning* |
| `OPTIONAL` | regime tributado (CRT 3 ou outro) **e** declaração explícita "sem tratamento especial" (flag `N` + incidência NENHUMA) | nada obrigatório; o que existir tem de ser válido |
| `NEEDS_REVIEW` | incidência fora do vocabulário do SGV, ou regime tributado com flag/incidência em branco (declaração ilegível) | único caso não decidível → "Não classificado" |
Base de `NOT_APPLICABLE`: `SIMPLES_DAS` para CRT 1/2/4; `COMPANY_DEFAULT` para CRT 3 e demais (o produto sem tratamento próprio herda o padrão da empresa).
Por quê (fontes): Simples — LC 123/2006 art. 13, IV e V (Cofins e PIS no DAS) e art. 18, §4º-A, I (segregação da receita monofásica): o produto sem tratamento especial não tem PIS/COFINS de documento a calcular. Regime normal — Lei 9.718/1998 arts. 2º–3º (cumulativo), Lei 10.637/2002 art. 1º (PIS) e Lei 10.833/2003 art. 1º (Cofins) (não cumulativo): o tributo incide, mas o perfil (cumulativo/não cumulativo) é fato da empresa; o default do produto é opcional porque o padrão vem da empresa. Regimes especiais por NCM (SPED 4.3.10–4.3.15, Lei 10.147/2000) servem para *coerência de CST* e para o motor de correção — não tornam obrigatório um produto que não declara nada, porque o SGV não usa o campo na emissão e a extração das tabelas por NCM é parcial. Documento — NT 2025.002-RTC v1.51, Grupos Q/S: o grupo é obrigatório no item da NF-e (rejeições 745/748), o que vale para o documento e não para o cadastro.
### Por que é assim (fontes)
1. **Simples Nacional (CRT 1, 2, 4)** — SOURCE Planalto; DOCUMENT LC 123/2006 compilada; ARTICLES art. 13, IV (Cofins) e V (PIS/Pasep): recolhidos mensalmente no DAS; art. 18, §4º-A, I: o contribuinte deve segregar a receita sujeita à tributação monofásica. Logo o produto sem regime especial não tem PIS/COFINS de documento a calcular; só a monofasia exige segregação — e ela é *declarada* pelo produto (flag S / incidência).
2. **Regime normal (CRT 3)** — Lei 9.718/1998 art. 2º-3º (cumulativo, faturamento), Lei 10.637/2002 art. 1º (PIS não cumulativo), Lei 10.833/2003 art. 1º (Cofins não cumulativa). O regime (cumulativo/não cumulativo) é fato da empresa e alimenta o padrão da empresa, não o produto; por isso o produto vazio é `NOT_APPLICABLE (COMPANY_DEFAULT)` e o perfil da empresa continua sendo pergunta de empresa (`OPERATOR_COMPANY_QUESTIONS_V1.md`).
3. **Regimes especiais por NCM** — SPED EFD-Contribuições, tabelas 4.3.10/4.3.11 (monofásica, CST 02/03/04), 4.3.12 (ST, CST 05), 4.3.13 (alíquota zero, CST 06), 4.3.14 (isenção, CST 07), 4.3.15 (sem incidência, CST 08) — versões 2026 baixadas de `sped.rfb.gov.br/arquivo/download/{1638,5786,1642,1643,1646,1651}` (`SPED_PISCOFINS_TABLES_V3.json`; 4.3.13 v1.34 de 16/04/2026). Lei 10.147/2000 (monofasia de medicamentos/perfumaria). Servem para *validar coerência* (CST × incidência) e para o motor de correção de produto; **não** tornam obrigatório preencher um produto que não declara nada, porque no SGV o campo não alimenta o documento (item 1).
4. **Domínio de CST** — SPED tabelas 4.3.3 (CST-PIS) e 4.3.4 (CST-COFINS): 01–09, 49, 50–56, 60–67, 70–75, 98, 99. O seletor do SGV grava 2 dígitos.
5. **Documento fiscal** — NT 2025.002-RTC v1.51 (Portal NF-e, publicada 04/08/2026), Grupos Q (PIS) e S (COFINS): o grupo é obrigatório no item da NF-e (rejeições 745/748), com exceções por finalidade. Isso vale para o **documento**; não obriga o cadastro do produto a ter o default preenchido.
## 3. Regras de validação (blockers)
Entrada e saída são avaliadas separadamente; PIS e COFINS são independentes.
## 3. CST: normalização canônica (somente leitura)
`canonical_cst`: aceita **exatamente** 2 dígitos oficiais (SPED 4.3.3/4.3.4: 01–09, 49, 50–56, 60–67, 70–75, 98, 99) ou **um** dígito cujo preenchimento com zero à esquerda seja um código oficial (`4` → `04`). Nada mais é normalizado (`049`, `000`, `060`, `2x`, `0` continuam inválidos).
- `VALID_AFTER_NORMALIZATION`: semanticamente válido para a cobertura e para as regras (CST × incidência, alíquota); gera *warning* não bloqueante `CST_PIS_NON_CANONICAL_FORMAT` / `CST_COFINS_NON_CANONICAL_FORMAT` (contagem e amostras em `fiscal_context.non_canonical_warnings`).
- Nada é escrito no Firebird; escrever `04` seria decisão futura.
## 4. Blockers (só onde o campo se aplica: REQUIRED/OPTIONAL)
| Blocker | Quando |
|---|---|
| `{PIS,COFINS}_CST_INVALID` | CST de entrada ou saída digitado que não é um código oficial de 2 dígitos (ex.: `2`, `4`, `049`). Dica no motivo quando um dígito vira código oficial com zero à esquerda (`4` → `04`); nunca aplicada |
| `{PIS,COFINS}_CST_INVALID` | CST digitado que não é canônico nem normalizável |
| `{PIS,COFINS}_CST_MISSING` | produto `REQUIRED` sem CST de saída |
| `{PIS,COFINS}_RATE_MISSING` | CST de saída 01/02 sem alíquota |
| `{PIS,COFINS}_RATE_MISSING` | CST de saída (canônico) 01/02 sem alíquota |
| `{PIS,COFINS}_RATE_INVALID` | alíquota não numérica ou > 100 |
| `{PIS,COFINS}_CST_INCIDENCE_MISMATCH` | incidência MONOFÁSICA com CST fora de {02,03,04}; SUBST.TRIBUTÁRIA com CST ≠ 05 |
| `{PIS,COFINS}_CST_INCIDENCE_MISMATCH` | MONOFÁSICA com CST fora de {02,03,04}; SUBST.TRIBUTÁRIA com CST ≠ 05 |
| `PIS_COFINS_FLAG_INCONSISTENT` | flag N com incidência especial; flag S sem incidência e sem códigos |
Alíquota: CST 01/02 exigem percentual; 03 (valor por unidade) e 04–09 não têm percentual; demais CSTs, opcional (validada se existir).
Warnings (nunca bloqueiam): `CST_*_NON_CANONICAL_FORMAT`, `CST_*_INVALID_IGNORED` (valor inválido digitado em produto `NOT_APPLICABLE`).
## 4. Regimes no corpus SGV
Encontrados: CRT 1 (Simples: CONSTRUCAO, MMMODAS, UNIAO, TESTESOFICIAIS) e CRT 3 (RINCAO, via EMISSOR). O motor não assume CRT 1: a base de `NOT_APPLICABLE` e as regras de validade dependem apenas dos dados do produto; o CRT só escolhe a *base* (DAS × padrão da empresa) e será usado pelos perfis de empresa.
## 5. Regimes no corpus SGV
CRT 1 (CONSTRUCAO, MMMODAS, UNIAO, TESTESOFICIAIS) e CRT 3 (RINCAO, via EMISSOR). Nenhuma regra assume CRT 1.
## 5. Campos ignorados quando não aplicáveis
Produto `NOT_APPLICABLE`: os 6 campos, `PISCOFINS` e `INCIDENCIA_PISCOFINS` não contam. Candidatos a *write target* futuro (não usados nesta versão, sem escrita): `COD_SAIDA_PIS`, `COD_SAIDA_COFINS` (a partir das tabelas 4.3.10–4.3.15, já aplicadas na missão Local Completion V2 nas cópias autorizadas).
## 6. Campos ignorados quando não aplicáveis / write targets candidatos
Produto `NOT_APPLICABLE`: os 6 campos, `PISCOFINS` e `INCIDENCIA_PISCOFINS` não contam. Candidatos futuros (sem escrita nesta versão): `COD_SAIDA_PIS`, `COD_SAIDA_COFINS` e a normalização `4→04`.
+13 -2
View File
@@ -35,8 +35,19 @@ O schema do produto não modela alíquota IBS/CBS, valor IBS/CBS nem `cCredPres`
Nota fiscal vs. regra do produto: para CRT 1 a legislação só exige IBS/CBS no documento a partir de 2027; a regra deste motor segue o **interruptor do ERP** — quando o cliente liga a reforma no SGV, o cadastro passa a ser cobrado.
## Achado de corpus (somente leitura)
A cópia local `TESTESOFICIAIS.FDB` tem `USAR_REFORMA_TRIBUTARIA=1` e 0 produtos com `CCLASSTRIB`, enquanto o piloto confirmado pelo operador tem `0`. Nada foi alterado no arquivo; o resultado do piloto é reproduzido por simulação pura (`RTC_ENABLED=false`, `scripts/unified_coverage_e2e.py`).
## Fonte da verdade (ERP_RTC_ENABLED)
Quem decide é o **Agent**, lendo o banco real daquela instalação **a cada varredura** (nunca uma decisão guardada no Hub, nunca cache local):
`SELECT FIRST 20 USAR_REFORMA_TRIBUTARIA FROM PARAMETROS` — todas as linhas. Uma linha (ou linhas iguais) truthy ⇒ ativa; linhas divergentes ⇒ **não** ativa e `rtc_param_conflict=true` (nunca "adivinha" ligado); parâmetro ausente/nulo ⇒ desativada.
O estado observado vai em `fiscal_context` do resultado do job e aparece no painel com proveniência (tooltip): `rtc_enabled`/`erp_rtc_enabled`, `rtc_source` (`PARAMETROS.USAR_REFORMA_TRIBUTARIA`), `rtc_param`, `rtc_param_rows`, `rtc_param_conflict`, `rtc_observed_at` (UTC), `rtc_supported`, `rtc_catalog_valid`.
O fingerprint dos blockers inclui o estado RTC: 0→1 cria blockers novos; 1→0 faz a varredura seguinte não reenviar os RTC e o Hub os fecha (`AUTO_REEVALUATION`).
## ERP_RTC_ENABLED × LEGAL_EFFECTIVE_CONTEXT (registrados separadamente)
- `ERP_RTC_ENABLED` é a regra operacional do produto e a única que entra na completude.
- `legal_effective_context(crt)` é **informativo** (`informational_only=true`, nunca blocker): para CRT 1/2/4 a NT 2025.002 v1.51 diz que as orientações de IBS/CBS em documentos virão em NT futura e que a tributação desses contribuintes ocorre a partir de 2027 (LC 214/2025 art. 348); para CRT 3 o preenchimento é obrigatório desde 01/01/2026. Uma empresa Simples com o interruptor ligado no SGV é validada; uma com o interruptor desligado não é, seja qual for o calendário legal.
## Achado de corpus e causa da divergência de RTC (somente leitura)
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%.
## 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`.
+74 -41
View File
@@ -1,4 +1,5 @@
{
"mode": "FULL (Hub decision + fiscal)",
"database": "TESTESOFICIAIS",
"crt": "1",
"products": 521,
@@ -19,21 +20,18 @@
"UNCLASSIFIED_PRODUCTS": 0,
"COVERAGE_PERCENT": 0.0,
"INVARIANT_PASS": true,
"PIS_BLOCKERS": 2,
"COFINS_BLOCKERS": 2,
"PIS_BLOCKERS": 1,
"COFINS_BLOCKERS": 1,
"RTC_BLOCKERS": 521,
"OTHER_BLOCKERS": 0,
"blockers_by_type": {
"CCLASSTRIB_MISSING": 521,
"COFINS_CST_INCIDENCE_MISMATCH": 1,
"COFINS_CST_INVALID": 1,
"PIS_CST_INCIDENCE_MISMATCH": 1,
"PIS_CST_INVALID": 1
"PIS_CST_INCIDENCE_MISMATCH": 1
},
"applicability": {
"NOT_APPLICABLE": 518,
"REQUIRED": 2,
"OPTIONAL": 1
"NOT_APPLICABLE": 519,
"REQUIRED": 2
},
"rtc_applicability": {
"REQUIRED": 521
@@ -281,11 +279,26 @@
]
}
],
"non_canonical_warnings": 1,
"context": {
"rtc_enabled": true,
"erp_rtc_enabled": true,
"rtc_supported": true,
"rtc_catalog_valid": true,
"rtc_param": 1,
"rtc_param": "1",
"rtc_source": "PARAMETROS.USAR_REFORMA_TRIBUTARIA",
"rtc_param_rows": 1,
"rtc_param_conflict": false,
"rtc_observed_at": "2026-09-24T20:40:30+00:00",
"legal_effective_context": {
"crt": "1",
"informational_only": true,
"ibs_cbs_in_documents": "FROM_2027_FOR_SIMPLES_ORIENTATION_PENDING",
"sources": [
"NT 2025.002-RTC v1.51 (Portal NF-e, 04/08/2026): CRT 1/2/4 orientations in a future NT; IBS/CBS taxation of these taxpayers only from 2027",
"LC 214/2025 art. 348"
]
},
"catalog_integrity": {
"classes": 145,
"cst": 18,
@@ -296,37 +309,67 @@
"valid": true
},
"crt": "1",
"products_evaluated": 521
"products_evaluated": 521,
"pis_cofins_applicability": {
"NOT_APPLICABLE": 519,
"REQUIRED": 2
},
"non_canonical_warnings": {
"products": 1,
"by_type": {
"CST_COFINS_NON_CANONICAL_FORMAT": 2,
"CST_PIS_NON_CANONICAL_FORMAT": 2
},
"samples": [
{
"product_code": "001434",
"type": "CST_PIS_NON_CANONICAL_FORMAT",
"reason": "ENTRY:2->02"
},
{
"product_code": "001434",
"type": "CST_PIS_NON_CANONICAL_FORMAT",
"reason": "EXIT:2->02"
},
{
"product_code": "001434",
"type": "CST_COFINS_NON_CANONICAL_FORMAT",
"reason": "ENTRY:2->02"
},
{
"product_code": "001434",
"type": "CST_COFINS_NON_CANONICAL_FORMAT",
"reason": "EXIT:2->02"
}
]
}
}
},
"RTC_OFF_SIMULATION": {
"rtc_enabled": false,
"TOTAL_PRODUCTS": 521,
"COMPLETE_PRODUCTS": 493,
"SAFE_BLOCKED_PRODUCTS": 28,
"COMPLETE_PRODUCTS": 494,
"SAFE_BLOCKED_PRODUCTS": 27,
"FAILED_PRODUCTS": 0,
"UNCLASSIFIED_PRODUCTS": 0,
"COVERAGE_PERCENT": 94.63,
"COVERAGE_PERCENT": 94.82,
"INVARIANT_PASS": true,
"PIS_BLOCKERS": 2,
"COFINS_BLOCKERS": 2,
"PIS_BLOCKERS": 1,
"COFINS_BLOCKERS": 1,
"RTC_BLOCKERS": 0,
"OTHER_BLOCKERS": 0,
"blockers_by_type": {
"COFINS_CST_INCIDENCE_MISMATCH": 1,
"COFINS_CST_INVALID": 1,
"PIS_CST_INCIDENCE_MISMATCH": 1,
"PIS_CST_INVALID": 1
"PIS_CST_INCIDENCE_MISMATCH": 1
},
"applicability": {
"NOT_APPLICABLE": 518,
"REQUIRED": 2,
"OPTIONAL": 1
"NOT_APPLICABLE": 519,
"REQUIRED": 2
},
"rtc_applicability": {
"NOT_APPLICABLE": 521
},
"changed_products": 2,
"changed_products": 1,
"examples": [
{
"product": "000700",
@@ -336,17 +379,9 @@
"COFINS_CST_INCIDENCE_MISMATCH",
"PIS_CST_INCIDENCE_MISMATCH"
]
},
{
"product": "001434",
"BEFORE_STATUS": "COMPLETE",
"AFTER_STATUS": "BLOCKED_FISCAL_DATA",
"REASON": [
"COFINS_CST_INVALID",
"PIS_CST_INVALID"
]
}
]
],
"non_canonical_warnings": 1
},
"RTC_ON_SIMULATION": {
"rtc_enabled": true,
@@ -357,21 +392,18 @@
"UNCLASSIFIED_PRODUCTS": 0,
"COVERAGE_PERCENT": 0.0,
"INVARIANT_PASS": true,
"PIS_BLOCKERS": 2,
"COFINS_BLOCKERS": 2,
"PIS_BLOCKERS": 1,
"COFINS_BLOCKERS": 1,
"RTC_BLOCKERS": 521,
"OTHER_BLOCKERS": 0,
"blockers_by_type": {
"CCLASSTRIB_MISSING": 521,
"COFINS_CST_INCIDENCE_MISMATCH": 1,
"COFINS_CST_INVALID": 1,
"PIS_CST_INCIDENCE_MISMATCH": 1,
"PIS_CST_INVALID": 1
"PIS_CST_INCIDENCE_MISMATCH": 1
},
"applicability": {
"NOT_APPLICABLE": 518,
"REQUIRED": 2,
"OPTIONAL": 1
"NOT_APPLICABLE": 519,
"REQUIRED": 2
},
"rtc_applicability": {
"REQUIRED": 521
@@ -618,7 +650,8 @@
"CCLASSTRIB_MISSING"
]
}
]
],
"non_canonical_warnings": 1
}
},
"SAFETY": {
+29 -4
View File
@@ -276,11 +276,26 @@
]
}
],
"non_canonical_warnings": 0,
"context": {
"rtc_enabled": true,
"erp_rtc_enabled": true,
"rtc_supported": true,
"rtc_catalog_valid": true,
"rtc_param": 1,
"rtc_param": "1",
"rtc_source": "PARAMETROS.USAR_REFORMA_TRIBUTARIA",
"rtc_param_rows": 1,
"rtc_param_conflict": false,
"rtc_observed_at": "2026-09-24T20:40:41+00:00",
"legal_effective_context": {
"crt": "1",
"informational_only": true,
"ibs_cbs_in_documents": "FROM_2027_FOR_SIMPLES_ORIENTATION_PENDING",
"sources": [
"NT 2025.002-RTC v1.51 (Portal NF-e, 04/08/2026): CRT 1/2/4 orientations in a future NT; IBS/CBS taxation of these taxpayers only from 2027",
"LC 214/2025 art. 348"
]
},
"catalog_integrity": {
"classes": 145,
"cst": 18,
@@ -291,7 +306,15 @@
"valid": true
},
"crt": "1",
"products_evaluated": 3712
"products_evaluated": 3712,
"pis_cofins_applicability": {
"NOT_APPLICABLE": 3712
},
"non_canonical_warnings": {
"products": 0,
"by_type": {},
"samples": []
}
}
},
"RTC_OFF_SIMULATION": {
@@ -315,7 +338,8 @@
"NOT_APPLICABLE": 3712
},
"changed_products": 0,
"examples": []
"examples": [],
"non_canonical_warnings": 0
},
"RTC_ON_SIMULATION": {
"rtc_enabled": true,
@@ -581,7 +605,8 @@
"CCLASSTRIB_MISSING"
]
}
]
],
"non_canonical_warnings": 0
}
},
"SAFETY": {
+34 -12
View File
@@ -1,7 +1,7 @@
{
"mode": "FISCAL_ONLY (no Hub decision)",
"database": "MMMODAS",
"crt": "2",
"crt": "1",
"products": 20997,
"fiscal_products_read": 20997,
"BEFORE": {
@@ -26,19 +26,33 @@
"OTHER_BLOCKERS": 0,
"blockers_by_type": {},
"applicability": {
"OPTIONAL": 1,
"NOT_APPLICABLE": 20995
"NOT_APPLICABLE": 20996
},
"rtc_applicability": {
"NOT_APPLICABLE": 20996
},
"changed_products": 0,
"examples": [],
"non_canonical_warnings": 0,
"context": {
"rtc_enabled": false,
"erp_rtc_enabled": false,
"rtc_supported": true,
"rtc_catalog_valid": true,
"rtc_param": 0,
"rtc_param": "0",
"rtc_source": "PARAMETROS.USAR_REFORMA_TRIBUTARIA",
"rtc_param_rows": 1,
"rtc_param_conflict": false,
"rtc_observed_at": "2026-09-24T20:40:49+00:00",
"legal_effective_context": {
"crt": "1",
"informational_only": true,
"ibs_cbs_in_documents": "FROM_2027_FOR_SIMPLES_ORIENTATION_PENDING",
"sources": [
"NT 2025.002-RTC v1.51 (Portal NF-e, 04/08/2026): CRT 1/2/4 orientations in a future NT; IBS/CBS taxation of these taxpayers only from 2027",
"LC 214/2025 art. 348"
]
},
"catalog_integrity": {
"classes": 145,
"cst": 18,
@@ -48,8 +62,16 @@
"duplicates": 0,
"valid": true
},
"crt": "2",
"products_evaluated": 20997
"crt": "1",
"products_evaluated": 20997,
"pis_cofins_applicability": {
"NOT_APPLICABLE": 20997
},
"non_canonical_warnings": {
"products": 0,
"by_type": {},
"samples": []
}
}
},
"RTC_OFF_SIMULATION": {
@@ -67,14 +89,14 @@
"OTHER_BLOCKERS": 0,
"blockers_by_type": {},
"applicability": {
"OPTIONAL": 1,
"NOT_APPLICABLE": 20995
"NOT_APPLICABLE": 20996
},
"rtc_applicability": {
"NOT_APPLICABLE": 20996
},
"changed_products": 0,
"examples": []
"examples": [],
"non_canonical_warnings": 0
},
"RTC_ON_SIMULATION": {
"rtc_enabled": true,
@@ -93,8 +115,7 @@
"CCLASSTRIB_MISSING": 20996
},
"applicability": {
"OPTIONAL": 1,
"NOT_APPLICABLE": 20995
"NOT_APPLICABLE": 20996
},
"rtc_applicability": {
"REQUIRED": 20996
@@ -341,7 +362,8 @@
"CCLASSTRIB_MISSING"
]
}
]
],
"non_canonical_warnings": 0
}
},
"SAFETY": {
+111 -556
View File
@@ -1,6 +1,7 @@
{
"mode": "FISCAL_ONLY (no Hub decision)",
"database": "RINCAO",
"crt": null,
"crt": "3",
"products": 894,
"fiscal_products_read": 894,
"BEFORE": {
@@ -13,292 +14,30 @@
"ERP_AS_FOUND": {
"rtc_enabled": false,
"TOTAL_PRODUCTS": 894,
"COMPLETE_PRODUCTS": 815,
"SAFE_BLOCKED_PRODUCTS": 79,
"COMPLETE_PRODUCTS": 893,
"SAFE_BLOCKED_PRODUCTS": 1,
"FAILED_PRODUCTS": 0,
"UNCLASSIFIED_PRODUCTS": 0,
"COVERAGE_PERCENT": 91.16,
"COVERAGE_PERCENT": 99.89,
"INVARIANT_PASS": true,
"PIS_BLOCKERS": 79,
"COFINS_BLOCKERS": 79,
"PIS_BLOCKERS": 1,
"COFINS_BLOCKERS": 1,
"RTC_BLOCKERS": 0,
"OTHER_BLOCKERS": 0,
"blockers_by_type": {
"COFINS_CST_INVALID": 79,
"PIS_CST_INVALID": 79
"COFINS_CST_INVALID": 1,
"PIS_CST_INVALID": 1
},
"applicability": {
"NOT_APPLICABLE": 815,
"OPTIONAL": 79
"OPTIONAL": 894
},
"rtc_applicability": {
"NOT_APPLICABLE": 894
},
"changed_products": 79,
"changed_products": 1,
"examples": [
{
"product": "000139",
"BEFORE_STATUS": "COMPLETE",
"AFTER_STATUS": "BLOCKED_FISCAL_DATA",
"REASON": [
"COFINS_CST_INVALID",
"PIS_CST_INVALID"
]
},
{
"product": "000140",
"BEFORE_STATUS": "COMPLETE",
"AFTER_STATUS": "BLOCKED_FISCAL_DATA",
"REASON": [
"COFINS_CST_INVALID",
"PIS_CST_INVALID"
]
},
{
"product": "000141",
"BEFORE_STATUS": "COMPLETE",
"AFTER_STATUS": "BLOCKED_FISCAL_DATA",
"REASON": [
"COFINS_CST_INVALID",
"PIS_CST_INVALID"
]
},
{
"product": "000142",
"BEFORE_STATUS": "COMPLETE",
"AFTER_STATUS": "BLOCKED_FISCAL_DATA",
"REASON": [
"COFINS_CST_INVALID",
"PIS_CST_INVALID"
]
},
{
"product": "000143",
"BEFORE_STATUS": "COMPLETE",
"AFTER_STATUS": "BLOCKED_FISCAL_DATA",
"REASON": [
"COFINS_CST_INVALID",
"PIS_CST_INVALID"
]
},
{
"product": "000144",
"BEFORE_STATUS": "COMPLETE",
"AFTER_STATUS": "BLOCKED_FISCAL_DATA",
"REASON": [
"COFINS_CST_INVALID",
"PIS_CST_INVALID"
]
},
{
"product": "000145",
"BEFORE_STATUS": "COMPLETE",
"AFTER_STATUS": "BLOCKED_FISCAL_DATA",
"REASON": [
"COFINS_CST_INVALID",
"PIS_CST_INVALID"
]
},
{
"product": "000146",
"BEFORE_STATUS": "COMPLETE",
"AFTER_STATUS": "BLOCKED_FISCAL_DATA",
"REASON": [
"COFINS_CST_INVALID",
"PIS_CST_INVALID"
]
},
{
"product": "000147",
"BEFORE_STATUS": "COMPLETE",
"AFTER_STATUS": "BLOCKED_FISCAL_DATA",
"REASON": [
"COFINS_CST_INVALID",
"PIS_CST_INVALID"
]
},
{
"product": "000148",
"BEFORE_STATUS": "COMPLETE",
"AFTER_STATUS": "BLOCKED_FISCAL_DATA",
"REASON": [
"COFINS_CST_INVALID",
"PIS_CST_INVALID"
]
},
{
"product": "000149",
"BEFORE_STATUS": "COMPLETE",
"AFTER_STATUS": "BLOCKED_FISCAL_DATA",
"REASON": [
"COFINS_CST_INVALID",
"PIS_CST_INVALID"
]
},
{
"product": "000150",
"BEFORE_STATUS": "COMPLETE",
"AFTER_STATUS": "BLOCKED_FISCAL_DATA",
"REASON": [
"COFINS_CST_INVALID",
"PIS_CST_INVALID"
]
},
{
"product": "000156",
"BEFORE_STATUS": "COMPLETE",
"AFTER_STATUS": "BLOCKED_FISCAL_DATA",
"REASON": [
"COFINS_CST_INVALID",
"PIS_CST_INVALID"
]
},
{
"product": "000157",
"BEFORE_STATUS": "COMPLETE",
"AFTER_STATUS": "BLOCKED_FISCAL_DATA",
"REASON": [
"COFINS_CST_INVALID",
"PIS_CST_INVALID"
]
},
{
"product": "000158",
"BEFORE_STATUS": "COMPLETE",
"AFTER_STATUS": "BLOCKED_FISCAL_DATA",
"REASON": [
"COFINS_CST_INVALID",
"PIS_CST_INVALID"
]
},
{
"product": "000159",
"BEFORE_STATUS": "COMPLETE",
"AFTER_STATUS": "BLOCKED_FISCAL_DATA",
"REASON": [
"COFINS_CST_INVALID",
"PIS_CST_INVALID"
]
},
{
"product": "000160",
"BEFORE_STATUS": "COMPLETE",
"AFTER_STATUS": "BLOCKED_FISCAL_DATA",
"REASON": [
"COFINS_CST_INVALID",
"PIS_CST_INVALID"
]
},
{
"product": "000161",
"BEFORE_STATUS": "COMPLETE",
"AFTER_STATUS": "BLOCKED_FISCAL_DATA",
"REASON": [
"COFINS_CST_INVALID",
"PIS_CST_INVALID"
]
},
{
"product": "000165",
"BEFORE_STATUS": "COMPLETE",
"AFTER_STATUS": "BLOCKED_FISCAL_DATA",
"REASON": [
"COFINS_CST_INVALID",
"PIS_CST_INVALID"
]
},
{
"product": "000166",
"BEFORE_STATUS": "COMPLETE",
"AFTER_STATUS": "BLOCKED_FISCAL_DATA",
"REASON": [
"COFINS_CST_INVALID",
"PIS_CST_INVALID"
]
},
{
"product": "000167",
"BEFORE_STATUS": "COMPLETE",
"AFTER_STATUS": "BLOCKED_FISCAL_DATA",
"REASON": [
"COFINS_CST_INVALID",
"PIS_CST_INVALID"
]
},
{
"product": "000168",
"BEFORE_STATUS": "COMPLETE",
"AFTER_STATUS": "BLOCKED_FISCAL_DATA",
"REASON": [
"COFINS_CST_INVALID",
"PIS_CST_INVALID"
]
},
{
"product": "000169",
"BEFORE_STATUS": "COMPLETE",
"AFTER_STATUS": "BLOCKED_FISCAL_DATA",
"REASON": [
"COFINS_CST_INVALID",
"PIS_CST_INVALID"
]
},
{
"product": "000170",
"BEFORE_STATUS": "COMPLETE",
"AFTER_STATUS": "BLOCKED_FISCAL_DATA",
"REASON": [
"COFINS_CST_INVALID",
"PIS_CST_INVALID"
]
},
{
"product": "000172",
"BEFORE_STATUS": "COMPLETE",
"AFTER_STATUS": "BLOCKED_FISCAL_DATA",
"REASON": [
"COFINS_CST_INVALID",
"PIS_CST_INVALID"
]
},
{
"product": "000173",
"BEFORE_STATUS": "COMPLETE",
"AFTER_STATUS": "BLOCKED_FISCAL_DATA",
"REASON": [
"COFINS_CST_INVALID",
"PIS_CST_INVALID"
]
},
{
"product": "000174",
"BEFORE_STATUS": "COMPLETE",
"AFTER_STATUS": "BLOCKED_FISCAL_DATA",
"REASON": [
"COFINS_CST_INVALID",
"PIS_CST_INVALID"
]
},
{
"product": "000175",
"BEFORE_STATUS": "COMPLETE",
"AFTER_STATUS": "BLOCKED_FISCAL_DATA",
"REASON": [
"COFINS_CST_INVALID",
"PIS_CST_INVALID"
]
},
{
"product": "000176",
"BEFORE_STATUS": "COMPLETE",
"AFTER_STATUS": "BLOCKED_FISCAL_DATA",
"REASON": [
"COFINS_CST_INVALID",
"PIS_CST_INVALID"
]
},
{
"product": "000177",
"product": "000866",
"BEFORE_STATUS": "COMPLETE",
"AFTER_STATUS": "BLOCKED_FISCAL_DATA",
"REASON": [
@@ -307,11 +46,26 @@
]
}
],
"non_canonical_warnings": 78,
"context": {
"rtc_enabled": false,
"erp_rtc_enabled": false,
"rtc_supported": true,
"rtc_catalog_valid": true,
"rtc_param": 0,
"rtc_param": "0",
"rtc_source": "PARAMETROS.USAR_REFORMA_TRIBUTARIA",
"rtc_param_rows": 1,
"rtc_param_conflict": false,
"rtc_observed_at": "2026-09-24T20:40:58+00:00",
"legal_effective_context": {
"crt": "3",
"informational_only": true,
"ibs_cbs_in_documents": "MANDATORY_FROM_2026-01-01",
"sources": [
"NT 2025.002-RTC v1.51 (Portal NF-e, 04/08/2026): CRT 1/2/4 orientations in a future NT; IBS/CBS taxation of these taxpayers only from 2027",
"LC 214/2025 art. 348"
]
},
"catalog_integrity": {
"classes": 145,
"cst": 18,
@@ -321,299 +75,99 @@
"duplicates": 0,
"valid": true
},
"crt": null,
"products_evaluated": 894
"crt": "3",
"products_evaluated": 894,
"pis_cofins_applicability": {
"OPTIONAL": 894
},
"non_canonical_warnings": {
"products": 78,
"by_type": {
"CST_COFINS_NON_CANONICAL_FORMAT": 78,
"CST_PIS_NON_CANONICAL_FORMAT": 78
},
"samples": [
{
"product_code": "000139",
"type": "CST_PIS_NON_CANONICAL_FORMAT",
"reason": "EXIT:4->04"
},
{
"product_code": "000139",
"type": "CST_COFINS_NON_CANONICAL_FORMAT",
"reason": "EXIT:4->04"
},
{
"product_code": "000140",
"type": "CST_PIS_NON_CANONICAL_FORMAT",
"reason": "EXIT:4->04"
},
{
"product_code": "000140",
"type": "CST_COFINS_NON_CANONICAL_FORMAT",
"reason": "EXIT:4->04"
},
{
"product_code": "000141",
"type": "CST_PIS_NON_CANONICAL_FORMAT",
"reason": "EXIT:4->04"
},
{
"product_code": "000141",
"type": "CST_COFINS_NON_CANONICAL_FORMAT",
"reason": "EXIT:4->04"
},
{
"product_code": "000142",
"type": "CST_PIS_NON_CANONICAL_FORMAT",
"reason": "EXIT:4->04"
},
{
"product_code": "000142",
"type": "CST_COFINS_NON_CANONICAL_FORMAT",
"reason": "EXIT:4->04"
},
{
"product_code": "000143",
"type": "CST_PIS_NON_CANONICAL_FORMAT",
"reason": "EXIT:4->04"
},
{
"product_code": "000143",
"type": "CST_COFINS_NON_CANONICAL_FORMAT",
"reason": "EXIT:4->04"
}
]
}
}
},
"RTC_OFF_SIMULATION": {
"rtc_enabled": false,
"TOTAL_PRODUCTS": 894,
"COMPLETE_PRODUCTS": 815,
"SAFE_BLOCKED_PRODUCTS": 79,
"COMPLETE_PRODUCTS": 893,
"SAFE_BLOCKED_PRODUCTS": 1,
"FAILED_PRODUCTS": 0,
"UNCLASSIFIED_PRODUCTS": 0,
"COVERAGE_PERCENT": 91.16,
"COVERAGE_PERCENT": 99.89,
"INVARIANT_PASS": true,
"PIS_BLOCKERS": 79,
"COFINS_BLOCKERS": 79,
"PIS_BLOCKERS": 1,
"COFINS_BLOCKERS": 1,
"RTC_BLOCKERS": 0,
"OTHER_BLOCKERS": 0,
"blockers_by_type": {
"COFINS_CST_INVALID": 79,
"PIS_CST_INVALID": 79
"COFINS_CST_INVALID": 1,
"PIS_CST_INVALID": 1
},
"applicability": {
"NOT_APPLICABLE": 815,
"OPTIONAL": 79
"OPTIONAL": 894
},
"rtc_applicability": {
"NOT_APPLICABLE": 894
},
"changed_products": 79,
"changed_products": 1,
"examples": [
{
"product": "000139",
"BEFORE_STATUS": "COMPLETE",
"AFTER_STATUS": "BLOCKED_FISCAL_DATA",
"REASON": [
"COFINS_CST_INVALID",
"PIS_CST_INVALID"
]
},
{
"product": "000140",
"BEFORE_STATUS": "COMPLETE",
"AFTER_STATUS": "BLOCKED_FISCAL_DATA",
"REASON": [
"COFINS_CST_INVALID",
"PIS_CST_INVALID"
]
},
{
"product": "000141",
"BEFORE_STATUS": "COMPLETE",
"AFTER_STATUS": "BLOCKED_FISCAL_DATA",
"REASON": [
"COFINS_CST_INVALID",
"PIS_CST_INVALID"
]
},
{
"product": "000142",
"BEFORE_STATUS": "COMPLETE",
"AFTER_STATUS": "BLOCKED_FISCAL_DATA",
"REASON": [
"COFINS_CST_INVALID",
"PIS_CST_INVALID"
]
},
{
"product": "000143",
"BEFORE_STATUS": "COMPLETE",
"AFTER_STATUS": "BLOCKED_FISCAL_DATA",
"REASON": [
"COFINS_CST_INVALID",
"PIS_CST_INVALID"
]
},
{
"product": "000144",
"BEFORE_STATUS": "COMPLETE",
"AFTER_STATUS": "BLOCKED_FISCAL_DATA",
"REASON": [
"COFINS_CST_INVALID",
"PIS_CST_INVALID"
]
},
{
"product": "000145",
"BEFORE_STATUS": "COMPLETE",
"AFTER_STATUS": "BLOCKED_FISCAL_DATA",
"REASON": [
"COFINS_CST_INVALID",
"PIS_CST_INVALID"
]
},
{
"product": "000146",
"BEFORE_STATUS": "COMPLETE",
"AFTER_STATUS": "BLOCKED_FISCAL_DATA",
"REASON": [
"COFINS_CST_INVALID",
"PIS_CST_INVALID"
]
},
{
"product": "000147",
"BEFORE_STATUS": "COMPLETE",
"AFTER_STATUS": "BLOCKED_FISCAL_DATA",
"REASON": [
"COFINS_CST_INVALID",
"PIS_CST_INVALID"
]
},
{
"product": "000148",
"BEFORE_STATUS": "COMPLETE",
"AFTER_STATUS": "BLOCKED_FISCAL_DATA",
"REASON": [
"COFINS_CST_INVALID",
"PIS_CST_INVALID"
]
},
{
"product": "000149",
"BEFORE_STATUS": "COMPLETE",
"AFTER_STATUS": "BLOCKED_FISCAL_DATA",
"REASON": [
"COFINS_CST_INVALID",
"PIS_CST_INVALID"
]
},
{
"product": "000150",
"BEFORE_STATUS": "COMPLETE",
"AFTER_STATUS": "BLOCKED_FISCAL_DATA",
"REASON": [
"COFINS_CST_INVALID",
"PIS_CST_INVALID"
]
},
{
"product": "000156",
"BEFORE_STATUS": "COMPLETE",
"AFTER_STATUS": "BLOCKED_FISCAL_DATA",
"REASON": [
"COFINS_CST_INVALID",
"PIS_CST_INVALID"
]
},
{
"product": "000157",
"BEFORE_STATUS": "COMPLETE",
"AFTER_STATUS": "BLOCKED_FISCAL_DATA",
"REASON": [
"COFINS_CST_INVALID",
"PIS_CST_INVALID"
]
},
{
"product": "000158",
"BEFORE_STATUS": "COMPLETE",
"AFTER_STATUS": "BLOCKED_FISCAL_DATA",
"REASON": [
"COFINS_CST_INVALID",
"PIS_CST_INVALID"
]
},
{
"product": "000159",
"BEFORE_STATUS": "COMPLETE",
"AFTER_STATUS": "BLOCKED_FISCAL_DATA",
"REASON": [
"COFINS_CST_INVALID",
"PIS_CST_INVALID"
]
},
{
"product": "000160",
"BEFORE_STATUS": "COMPLETE",
"AFTER_STATUS": "BLOCKED_FISCAL_DATA",
"REASON": [
"COFINS_CST_INVALID",
"PIS_CST_INVALID"
]
},
{
"product": "000161",
"BEFORE_STATUS": "COMPLETE",
"AFTER_STATUS": "BLOCKED_FISCAL_DATA",
"REASON": [
"COFINS_CST_INVALID",
"PIS_CST_INVALID"
]
},
{
"product": "000165",
"BEFORE_STATUS": "COMPLETE",
"AFTER_STATUS": "BLOCKED_FISCAL_DATA",
"REASON": [
"COFINS_CST_INVALID",
"PIS_CST_INVALID"
]
},
{
"product": "000166",
"BEFORE_STATUS": "COMPLETE",
"AFTER_STATUS": "BLOCKED_FISCAL_DATA",
"REASON": [
"COFINS_CST_INVALID",
"PIS_CST_INVALID"
]
},
{
"product": "000167",
"BEFORE_STATUS": "COMPLETE",
"AFTER_STATUS": "BLOCKED_FISCAL_DATA",
"REASON": [
"COFINS_CST_INVALID",
"PIS_CST_INVALID"
]
},
{
"product": "000168",
"BEFORE_STATUS": "COMPLETE",
"AFTER_STATUS": "BLOCKED_FISCAL_DATA",
"REASON": [
"COFINS_CST_INVALID",
"PIS_CST_INVALID"
]
},
{
"product": "000169",
"BEFORE_STATUS": "COMPLETE",
"AFTER_STATUS": "BLOCKED_FISCAL_DATA",
"REASON": [
"COFINS_CST_INVALID",
"PIS_CST_INVALID"
]
},
{
"product": "000170",
"BEFORE_STATUS": "COMPLETE",
"AFTER_STATUS": "BLOCKED_FISCAL_DATA",
"REASON": [
"COFINS_CST_INVALID",
"PIS_CST_INVALID"
]
},
{
"product": "000172",
"BEFORE_STATUS": "COMPLETE",
"AFTER_STATUS": "BLOCKED_FISCAL_DATA",
"REASON": [
"COFINS_CST_INVALID",
"PIS_CST_INVALID"
]
},
{
"product": "000173",
"BEFORE_STATUS": "COMPLETE",
"AFTER_STATUS": "BLOCKED_FISCAL_DATA",
"REASON": [
"COFINS_CST_INVALID",
"PIS_CST_INVALID"
]
},
{
"product": "000174",
"BEFORE_STATUS": "COMPLETE",
"AFTER_STATUS": "BLOCKED_FISCAL_DATA",
"REASON": [
"COFINS_CST_INVALID",
"PIS_CST_INVALID"
]
},
{
"product": "000175",
"BEFORE_STATUS": "COMPLETE",
"AFTER_STATUS": "BLOCKED_FISCAL_DATA",
"REASON": [
"COFINS_CST_INVALID",
"PIS_CST_INVALID"
]
},
{
"product": "000176",
"BEFORE_STATUS": "COMPLETE",
"AFTER_STATUS": "BLOCKED_FISCAL_DATA",
"REASON": [
"COFINS_CST_INVALID",
"PIS_CST_INVALID"
]
},
{
"product": "000177",
"product": "000866",
"BEFORE_STATUS": "COMPLETE",
"AFTER_STATUS": "BLOCKED_FISCAL_DATA",
"REASON": [
@@ -621,7 +175,8 @@
"PIS_CST_INVALID"
]
}
]
],
"non_canonical_warnings": 78
},
"RTC_ON_SIMULATION": {
"rtc_enabled": true,
@@ -632,18 +187,17 @@
"UNCLASSIFIED_PRODUCTS": 0,
"COVERAGE_PERCENT": 0.0,
"INVARIANT_PASS": true,
"PIS_BLOCKERS": 79,
"COFINS_BLOCKERS": 79,
"PIS_BLOCKERS": 1,
"COFINS_BLOCKERS": 1,
"RTC_BLOCKERS": 894,
"OTHER_BLOCKERS": 0,
"blockers_by_type": {
"CCLASSTRIB_MISSING": 894,
"COFINS_CST_INVALID": 79,
"PIS_CST_INVALID": 79
"COFINS_CST_INVALID": 1,
"PIS_CST_INVALID": 1
},
"applicability": {
"NOT_APPLICABLE": 815,
"OPTIONAL": 79
"OPTIONAL": 894
},
"rtc_applicability": {
"REQUIRED": 894
@@ -890,7 +444,8 @@
"CCLASSTRIB_MISSING"
]
}
]
],
"non_canonical_warnings": 78
}
},
"SAFETY": {
+62 -66
View File
@@ -1,7 +1,7 @@
{
"mode": "FISCAL_ONLY (no Hub decision)",
"database": "UNIAO",
"crt": "3",
"crt": "1",
"products": 8942,
"fiscal_products_read": 8942,
"BEFORE": {
@@ -14,50 +14,45 @@
"ERP_AS_FOUND": {
"rtc_enabled": false,
"TOTAL_PRODUCTS": 8942,
"COMPLETE_PRODUCTS": 8940,
"SAFE_BLOCKED_PRODUCTS": 2,
"COMPLETE_PRODUCTS": 8942,
"SAFE_BLOCKED_PRODUCTS": 0,
"FAILED_PRODUCTS": 0,
"UNCLASSIFIED_PRODUCTS": 0,
"COVERAGE_PERCENT": 99.98,
"COVERAGE_PERCENT": 100.0,
"INVARIANT_PASS": true,
"PIS_BLOCKERS": 0,
"COFINS_BLOCKERS": 2,
"COFINS_BLOCKERS": 0,
"RTC_BLOCKERS": 0,
"OTHER_BLOCKERS": 0,
"blockers_by_type": {
"COFINS_CST_INVALID": 2
},
"blockers_by_type": {},
"applicability": {
"NOT_APPLICABLE": 8170,
"OPTIONAL": 772
"NOT_APPLICABLE": 8942
},
"rtc_applicability": {
"NOT_APPLICABLE": 8942
},
"changed_products": 2,
"examples": [
{
"product": "000304",
"BEFORE_STATUS": "COMPLETE",
"AFTER_STATUS": "BLOCKED_FISCAL_DATA",
"REASON": [
"COFINS_CST_INVALID"
]
},
{
"product": "003090",
"BEFORE_STATUS": "COMPLETE",
"AFTER_STATUS": "BLOCKED_FISCAL_DATA",
"REASON": [
"COFINS_CST_INVALID"
]
}
],
"changed_products": 0,
"examples": [],
"non_canonical_warnings": 2,
"context": {
"rtc_enabled": false,
"erp_rtc_enabled": false,
"rtc_supported": true,
"rtc_catalog_valid": true,
"rtc_param": 0,
"rtc_param": "0",
"rtc_source": "PARAMETROS.USAR_REFORMA_TRIBUTARIA",
"rtc_param_rows": 1,
"rtc_param_conflict": false,
"rtc_observed_at": "2026-09-24T20:41:07+00:00",
"legal_effective_context": {
"crt": "1",
"informational_only": true,
"ibs_cbs_in_documents": "FROM_2027_FOR_SIMPLES_ORIENTATION_PENDING",
"sources": [
"NT 2025.002-RTC v1.51 (Portal NF-e, 04/08/2026): CRT 1/2/4 orientations in a future NT; IBS/CBS taxation of these taxpayers only from 2027",
"LC 214/2025 art. 348"
]
},
"catalog_integrity": {
"classes": 145,
"cst": 18,
@@ -67,52 +62,54 @@
"duplicates": 0,
"valid": true
},
"crt": "3",
"products_evaluated": 8942
"crt": "1",
"products_evaluated": 8942,
"pis_cofins_applicability": {
"NOT_APPLICABLE": 8942
},
"non_canonical_warnings": {
"products": 2,
"by_type": {
"CST_COFINS_INVALID_IGNORED": 2
},
"samples": [
{
"product_code": "000304",
"type": "CST_COFINS_INVALID_IGNORED",
"reason": "ENTRY:000"
},
{
"product_code": "003090",
"type": "CST_COFINS_INVALID_IGNORED",
"reason": "EXIT:060"
}
]
}
}
},
"RTC_OFF_SIMULATION": {
"rtc_enabled": false,
"TOTAL_PRODUCTS": 8942,
"COMPLETE_PRODUCTS": 8940,
"SAFE_BLOCKED_PRODUCTS": 2,
"COMPLETE_PRODUCTS": 8942,
"SAFE_BLOCKED_PRODUCTS": 0,
"FAILED_PRODUCTS": 0,
"UNCLASSIFIED_PRODUCTS": 0,
"COVERAGE_PERCENT": 99.98,
"COVERAGE_PERCENT": 100.0,
"INVARIANT_PASS": true,
"PIS_BLOCKERS": 0,
"COFINS_BLOCKERS": 2,
"COFINS_BLOCKERS": 0,
"RTC_BLOCKERS": 0,
"OTHER_BLOCKERS": 0,
"blockers_by_type": {
"COFINS_CST_INVALID": 2
},
"blockers_by_type": {},
"applicability": {
"NOT_APPLICABLE": 8170,
"OPTIONAL": 772
"NOT_APPLICABLE": 8942
},
"rtc_applicability": {
"NOT_APPLICABLE": 8942
},
"changed_products": 2,
"examples": [
{
"product": "000304",
"BEFORE_STATUS": "COMPLETE",
"AFTER_STATUS": "BLOCKED_FISCAL_DATA",
"REASON": [
"COFINS_CST_INVALID"
]
},
{
"product": "003090",
"BEFORE_STATUS": "COMPLETE",
"AFTER_STATUS": "BLOCKED_FISCAL_DATA",
"REASON": [
"COFINS_CST_INVALID"
]
}
]
"changed_products": 0,
"examples": [],
"non_canonical_warnings": 2
},
"RTC_ON_SIMULATION": {
"rtc_enabled": true,
@@ -124,16 +121,14 @@
"COVERAGE_PERCENT": 0.0,
"INVARIANT_PASS": true,
"PIS_BLOCKERS": 0,
"COFINS_BLOCKERS": 2,
"COFINS_BLOCKERS": 0,
"RTC_BLOCKERS": 8942,
"OTHER_BLOCKERS": 0,
"blockers_by_type": {
"CCLASSTRIB_MISSING": 8942,
"COFINS_CST_INVALID": 2
"CCLASSTRIB_MISSING": 8942
},
"applicability": {
"NOT_APPLICABLE": 8170,
"OPTIONAL": 772
"NOT_APPLICABLE": 8942
},
"rtc_applicability": {
"REQUIRED": 8942
@@ -380,7 +375,8 @@
"CCLASSTRIB_MISSING"
]
}
]
],
"non_canonical_warnings": 2
}
},
"SAFETY": {
+10 -21
View File
@@ -39,26 +39,15 @@ Cada blocker fiscal é um blocker comum: `blocker_type=BLOCKED_FISCAL_DATA`, `fi
## Segurança
Somente leitura. `FIREBIRD_WRITES=0`: a avaliação abre uma conexão `READ COMMITTED READ ONLY`, executa apenas SELECT e nunca preenche `CCLASSTRIB`, PIS ou COFINS. Testes provam que só SELECT é emitido.
## Validação no banco real (somente leitura)
`scripts/unified_coverage_e2e.py` → `docs/runs/unified_coverage_e2e*.json`. TESTESOFICIAIS (521 produtos, CRT 1):
## Validação nos bancos reais (somente leitura, cinco bancos)
`scripts/unified_coverage_e2e.py` → `docs/runs/unified_coverage_e2e*.json`. RTC lida **do banco como está hoje**; "RTC desligada" é simulação pura do avaliador (o parâmetro nunca é tocado).
| | Antes (só decisão) | Depois — RTC desligada (estado do piloto) |
|---|---|---|
| Completos | 495 (95,01%) | 493 (94,63%) |
| Bloqueados | 26 | 28 |
| Falhas / Não classificados | 0 / 0 | 0 / 0 |
| RTC blockers | — | 0 |
| PIS / COFINS blockers | — | 2 / 2 (2 produtos) |
| Banco | CRT | RTC no banco | Produtos | Completos | Bloqueados | Não class. | Cobertura (como está) | Cobertura com RTC off |
|---|---|---|---|---|---|---|---|---|
| CONSTRUCAO | 1 | **ligada** | 3.712 | 0 | 3.712 (RTC) | 0 | 0% | 100% |
| MMMODAS | 1 | desligada | 20.997 | 20.997 | 0 | 0 | 100% | 100% |
| RINCAO | 3 | desligada | 894 | 893 | 1 | 0 | 99,89% (antes da normalização: 91,16%) | 99,89% |
| UNIAO | 1 | desligada | 8.942 | 8.942 | 0 | 0 | 100% | 100% |
| TESTESOFICIAIS | 1 | **ligada** | 521 | 0 | 521 (RTC) | 0 | 0% | 94,82% (494/521) |
Aplicabilidade PIS/COFINS: 518 `NOT_APPLICABLE`, 2 `REQUIRED`, 1 `OPTIONAL`, 0 `NEEDS_REVIEW` (antes: 492 `UNKNOWN_APPLICABILITY`). Os 2 produtos bloqueados são legítimos: um com CST `2`/`2` (formato inválido) e um com incidência SUBST.TRIBUTÁRIA e CST 01 (incompatível com CST 05).
### Corpus de clientes (RTC desligada, somente PIS/COFINS; `--fiscal-only`, sem decisão do Hub)
| Banco | CRT | Produtos | NOT_APPLICABLE | OPTIONAL | Blockers PIS/COFINS | Cobertura da parte fiscal |
|---|---|---|---|---|---|---|
| CONSTRUCAO | 1 | 3.712 | 3.712 | 0 | 0 | 100% |
| MMMODAS | 1 | 20.997 | 20.995 | 1 | 0 | 100% |
| UNIAO | 1 | 8.942 | 8.170 | 772 | 2 produtos (COFINS `000`/`060`, estilo CST de ICMS) | 99,98% |
| RINCAO | 3 | 894 | 815 | 79 | 79 produtos (78 com CST `4` sem zero à esquerda + 1) | 91,16% |
| TESTESOFICIAIS | 1 | 521 | 518 | 1 | 2 produtos | 99,62% (94,63% com a decisão do Hub) |
`OPTIONAL` = valor digitado sem flag/incidência especial. RINCAO: `4` deve ser `04` (o seletor do SGV grava 2 dígitos); o motivo do blocker traz a dica `UNPADDED_USE_04`, nunca aplicada.
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`.
+16 -4
View File
@@ -34,6 +34,18 @@ def old_unknown(product):
return not evidence
def resolved_crt(name, path):
"""Company regime exactly as the Agent resolves it (deterministic company-context resolver)."""
from gti_fiscal_agent import company_context as cc
conn = lc.connect(path)
try:
cur = conn.cursor()
result = cc.resolve(cc.collect_evidence(cur), cc.config_from_env(lc._cfg_env(name)))
return result.get("company_crt") if result.get("status") == "PASS" else None
finally:
conn.close()
def main():
ap = argparse.ArgumentParser(); ap.add_argument("--db", default="TESTESOFICIAIS"); ap.add_argument("--out", default="docs/runs/unified_coverage_e2e.json"); ap.add_argument("--crt", default=None); ap.add_argument("--fiscal-only", action="store_true", help="skip the Hub classification (decisions = EXACT_NCM); measures only the PIS/COFINS + RTC contribution")
a = ap.parse_args()
@@ -47,7 +59,7 @@ def main():
cursor.execute("SELECT DISTINCT TRIM(CRT) FROM C000004"); crts = [r[0] for r in cursor.fetchall()]
finally:
connection.close()
crt = a.crt or (crts[0] if len(crts) == 1 else None)
crt = a.crt or resolved_crt(a.db, path) or (crts[0] if len(crts) == 1 else None)
client = HubClient(os.environ.get("HUB_URL", lc.HUB), os.environ["AGENT_DEV_TOKEN"])
decisions = [{"status": "EXACT_NCM"} for _ in products] if a.fiscal_only else client.classify_batch([to_hub_product(p) for p in products])
before_states = [classify_product_state(p, d) for p, d in zip(products, decisions)]
@@ -76,9 +88,9 @@ def main():
"INVARIANT_PASS": total == summary["COMPLETE_PRODUCTS"] + summary["SAFE_BLOCKED"] + summary["FAILED_TECHNICAL"] + summary["UNCLASSIFIED"],
"PIS_BLOCKERS": by_category["PIS"], "COFINS_BLOCKERS": by_category["COFINS"], "RTC_BLOCKERS": by_category["RTC"], "OTHER_BLOCKERS": by_category["OTHER"], "blockers_by_type": dict(sorted(by_type.items())),
"applicability": dict(Counter(f["applicability"]["PIS_COFINS"] for f in findings.values())), "rtc_applicability": dict(Counter(f["applicability"]["RTC"] for f in findings.values())),
"changed_products": len(changed), "examples": changed[:30]}
"changed_products": len(changed), "examples": changed[:30], "non_canonical_warnings": out["context"]["non_canonical_warnings"]["products"]}
if label == "ERP_AS_FOUND":
runs[label]["context"] = {k: v for k, v in out["context"].items() if k != "capabilities"}
runs[label]["context"] = {k: v for k, v in out["context"].items() if k not in ("capabilities",)}
total = before["TOTAL_PRODUCTS"]
report = {"mode": "FISCAL_ONLY (no Hub decision)" if a.fiscal_only else "FULL (Hub decision + fiscal)", "database": a.db, "crt": crt, "products": len(products), "fiscal_products_read": len(fiscal_products),
"BEFORE": {"COMPLETE_PRODUCTS": before["COMPLETE_PRODUCTS"], "SAFE_BLOCKED_PRODUCTS": before["SAFE_BLOCKED"], "COVERAGE_PERCENT": round(100 * before["COMPLETE_PRODUCTS"] / total, 2),
@@ -87,7 +99,7 @@ def main():
Path(ROOT / a.out).write_text(json.dumps(report, ensure_ascii=False, indent=1, default=str), encoding="utf-8")
print(json.dumps({k: report[k] for k in ("database", "crt", "products", "BEFORE")}, ensure_ascii=False))
for label, r in runs.items():
print(label, {k: r[k] for k in ("rtc_enabled", "TOTAL_PRODUCTS", "COMPLETE_PRODUCTS", "SAFE_BLOCKED_PRODUCTS", "FAILED_PRODUCTS", "UNCLASSIFIED_PRODUCTS", "COVERAGE_PERCENT", "INVARIANT_PASS", "PIS_BLOCKERS", "COFINS_BLOCKERS", "RTC_BLOCKERS", "OTHER_BLOCKERS")})
print(label, {k: r[k] for k in ("rtc_enabled", "TOTAL_PRODUCTS", "COMPLETE_PRODUCTS", "SAFE_BLOCKED_PRODUCTS", "FAILED_PRODUCTS", "UNCLASSIFIED_PRODUCTS", "COVERAGE_PERCENT", "INVARIANT_PASS", "PIS_BLOCKERS", "COFINS_BLOCKERS", "RTC_BLOCKERS", "OTHER_BLOCKERS", "non_canonical_warnings")})
print(" applicability", r["applicability"], "blockers", r["blockers_by_type"])