feat: unified fiscal coverage - PIS/COFINS applicability + conditional RTC in one definition, one queue, one panel
- central/app/unified_fiscal_coverage.py: pure rules (PIS/COFINS NOT_APPLICABLE/REQUIRED/OPTIONAL/NEEDS_REVIEW, RTC only when the ERP switch is on) - agent: read-only loader (unified_fiscal.py) replaces the legacy/expanded module; findings feed the single operational state, coverage and blocker queue - hub: BLOCKED_FISCAL_DATA blockers (PIS/COFINS/RTC) share the queue; stale ones close on the next full pass; dashboard breakdown gains CEST/PIS/COFINS/RTC - panel: legacy/expanded coverage block removed; discreet 'Reforma Tributaria: Ativa/Desativada' - docs: unified_fiscal_completeness, pis_cofins_applicability, rtc_pis_cofins_model + official source manifests; read-only E2E script Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -44,3 +44,4 @@ scripts/_*
|
||||
*.gz
|
||||
*.pkl
|
||||
.pytmp/
|
||||
sources_cache/legal/
|
||||
|
||||
@@ -30,8 +30,8 @@ sys.path.insert(0, str(AGENT_ROOT / "src"))
|
||||
sys.path.insert(0, str(PROJECT_ROOT)) # ships central/app engine modules read-only (central.app.*)
|
||||
|
||||
from gti_fiscal_agent.client import HubClient # noqa: E402
|
||||
from gti_fiscal_agent.firebird_reader import _connect as _open_read_only, check_connection, read_products # noqa: E402
|
||||
from gti_fiscal_agent.rtc_pis_cofins import assess_from_connection # noqa: E402
|
||||
from gti_fiscal_agent import firebird_reader as _firebird_reader, unified_fiscal as _unified # noqa: E402
|
||||
from gti_fiscal_agent.firebird_reader import check_connection, read_products # noqa: E402
|
||||
from gti_fiscal_agent.pipeline import run_pipeline # noqa: E402
|
||||
from gti_fiscal_agent.runner import REFERENCE_SCHEMA_V1, product_fingerprint, to_hub_product # noqa: E402
|
||||
from central.app.operational_state import classify_product_state, summarize_operational_states # noqa: E402
|
||||
@@ -187,16 +187,27 @@ def open_write_connection(env, database_path, firebird_writes=True):
|
||||
)
|
||||
|
||||
|
||||
def _expanded_scope(env, legacy_states):
|
||||
"""RTC + PIS/COFINS assessment (read-only, SELECT only). Isolated: a failure here never fails the job or changes legacy coverage."""
|
||||
def _unified_fiscal(env, company):
|
||||
"""PIS/COFINS applicability + RTC (only when the ERP has the reform on), read-only (SELECT only). Fail-closed: if it cannot be assessed the job fails
|
||||
instead of reporting a coverage that ignores these fields."""
|
||||
connection = _firebird_reader._connect(env)
|
||||
try:
|
||||
connection = _open_read_only(env)
|
||||
try:
|
||||
return assess_from_connection(connection, legacy_states)
|
||||
finally:
|
||||
connection.close()
|
||||
except Exception as error: # noqa: BLE001
|
||||
return {"scope_version": "expanded-v1", "unavailable": type(error).__name__}
|
||||
return _unified.assess_from_connection(connection, company)
|
||||
finally:
|
||||
connection.close()
|
||||
|
||||
|
||||
def _fiscal_blocked_products(products, findings):
|
||||
"""One queue entry per (product, blocker). Same shape as every other blocker; `field` is the dashboard category (PIS/COFINS/RTC)."""
|
||||
out = []
|
||||
for product in products:
|
||||
code = str(product.get("CODIGO") or "")
|
||||
found = findings.get(code) or {}
|
||||
for blocker in found.get("blockers") or []:
|
||||
out.append({"company": "", "product_code": code, "gtin": product.get("GTIN") or product.get("EAN_GTIN"), "description": product.get("PRODUTO"), "blocker_type": "BLOCKED_FISCAL_DATA",
|
||||
"field": blocker["category"], "reason_code": blocker["type"], "evidence_summary": {"reason": blocker["reason"], "applicability": found.get("applicability")},
|
||||
"input_fingerprint": found.get("input_fingerprint", "")})
|
||||
return out
|
||||
|
||||
|
||||
def execute_job(job, config, executor, state, env):
|
||||
@@ -238,7 +249,8 @@ def execute_job(job, config, executor, state, env):
|
||||
if write_connection is not None:
|
||||
write_connection.close()
|
||||
|
||||
operational = coverage = expanded_scope = None
|
||||
operational = coverage = fiscal_context = None
|
||||
fiscal = None
|
||||
if sync_kind:
|
||||
if cache:
|
||||
for product, decision in zip(to_process, decisions):
|
||||
@@ -247,14 +259,23 @@ def execute_job(job, config, executor, state, env):
|
||||
all_decisions = [state.cached_decision(p["CODIGO"]) or {} for p in products]
|
||||
else:
|
||||
all_decisions = decisions
|
||||
product_states = [classify_product_state(p, d) for p, d in zip(products, all_decisions)]
|
||||
operational = summarize_operational_states(product_states)
|
||||
coverage = build_job_coverage(env, client, products, all_decisions)
|
||||
expanded_scope = _expanded_scope(env, {s["product_code"]: s["state"] for s in product_states})
|
||||
company = {"crt": ((coverage or {}).get("company_context") or {}).get("company_crt")}
|
||||
fiscal = _unified_fiscal(env, company)
|
||||
findings = fiscal["findings"]
|
||||
product_states = [classify_product_state(p, d, fiscal=findings.get(str(p["CODIGO"]))) for p, d in zip(products, all_decisions)]
|
||||
operational = summarize_operational_states(product_states)
|
||||
fiscal_context = {**fiscal["context"], "evaluated_all": kind != "REPROCESS_PRODUCTS"}
|
||||
else:
|
||||
fiscal = _unified_fiscal(env, None)
|
||||
fiscal_blocked = _fiscal_blocked_products(products, fiscal["findings"])
|
||||
fiscal_codes = {b["product_code"] for b in fiscal_blocked}
|
||||
summary["blocked_products"] = summary["blocked_products"] + fiscal_blocked
|
||||
summary["resolved_products"] = [c for c in summary["resolved_products"] if str(c) not in fiscal_codes]
|
||||
result_summary = {
|
||||
"coverage": coverage,
|
||||
"operational": operational,
|
||||
"expanded_scope": expanded_scope,
|
||||
"fiscal_context": fiscal_context,
|
||||
"fiscal_mode": getattr(config, "fiscal_mode", None),
|
||||
"release_version": env.get("AGENT_VERSION") or "1.1.7",
|
||||
"products_total": len(products),
|
||||
|
||||
@@ -1,277 +0,0 @@
|
||||
"""READ-ONLY RTC (cClassTrib / CST IBS-CBS) and PIS/COFINS assessment of SGV products.
|
||||
|
||||
Everything here is observation: capability detection, catalog integrity, deterministic validators, applicability and an
|
||||
EXPANDED coverage view that sits next to (never replaces) the legacy scope coverage. Nothing writes to Firebird, nothing
|
||||
fills a value, and no fiscal rule is invented: where local data cannot prove that a field applies, the answer is
|
||||
UNKNOWN_APPLICABILITY, not a blocker.
|
||||
|
||||
Vocabulary kept apart on purpose: SCHEMA_SUPPORT (the column exists) / DATA_PRESENT (it has a value) /
|
||||
APPLICABILITY (the field applies to this product) / MANDATORYNESS (a missing value is a defect). Only the last one
|
||||
may block, and only when applicability is proven from local, reliable data.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import re
|
||||
|
||||
LOCAL_CATALOG_ONLY = True # the RTC catalog used is the one stored in the client's own SGV database; no official dataset is integrated here
|
||||
|
||||
PRODUCT_TABLE = "C000025"
|
||||
RTC_CCLASSTRIB_TABLE = "RTC_CCLASSTRIB"
|
||||
RTC_CST_TABLE = "RTC_CST"
|
||||
PIS_COFINS_COLUMNS = ("COD_ENTRADA_PIS", "COD_SAIDA_PIS", "ALIQ_PIS", "COD_ENTRADA_COFINS", "COD_SAIDA_COFINS", "ALIQ_COFINS", "PISCOFINS", "INCIDENCIA_PISCOFINS")
|
||||
PRODUCT_COLUMNS = ("CODIGO", "CCLASSTRIB") + PIS_COFINS_COLUMNS
|
||||
|
||||
# Official CST PIS/COFINS codes (2 digits). Used for FORMAT/membership only, never to pick a value.
|
||||
OFFICIAL_PIS_COFINS_CST = frozenset([f"{n:02d}" for n in (*range(1, 10), 49, *range(50, 57), *range(60, 68), *range(70, 76), 98, 99)])
|
||||
|
||||
# ---------------------------------------------------------------- capabilities -------------------------------------------------
|
||||
|
||||
|
||||
def detect_rtc_capabilities(schema: dict, *, param_value=None, catalog: dict | None = None, physical_fk: bool | None = None) -> dict:
|
||||
"""schema: {TABLE: {COLUMN, ...}}. Fail-closed: anything not proven is False. Old databases without RTC simply report False."""
|
||||
product_cols = set(schema.get(PRODUCT_TABLE, ()))
|
||||
class_cols = set(schema.get(RTC_CCLASSTRIB_TABLE, ()))
|
||||
cst_cols = set(schema.get(RTC_CST_TABLE, ()))
|
||||
has_product = "CCLASSTRIB" in product_cols
|
||||
has_class = {"CCLASSTRIB", "CSTIBSCBS"} <= class_cols
|
||||
has_cst = "CSTIBSCBS" in cst_cols
|
||||
has_relationship = bool(has_product and has_class and has_cst) # column-level relationship: product -> class -> cst
|
||||
caps = {
|
||||
"HAS_PRODUCT_CCLASSTRIB": has_product,
|
||||
"HAS_RTC_CCLASSTRIB_TABLE": has_class,
|
||||
"HAS_RTC_CST_TABLE": has_cst,
|
||||
"HAS_RTC_RELATIONSHIP": has_relationship,
|
||||
"HAS_PHYSICAL_RTC_FK": bool(physical_fk),
|
||||
"RTC_ENABLED_BY_PARAM": None if param_value is None else str(param_value).strip() in {"1", "S", "T", "TRUE"},
|
||||
"RTC_CATALOG_VALID": False,
|
||||
"LOCAL_CATALOG_ONLY": LOCAL_CATALOG_ONLY,
|
||||
}
|
||||
if catalog is not None and has_class and has_cst:
|
||||
caps["RTC_CATALOG_VALID"] = catalog_integrity(catalog)["valid"]
|
||||
return caps
|
||||
|
||||
|
||||
def catalog_integrity(catalog: dict) -> dict:
|
||||
"""catalog: {"classes": {cClassTrib: cst_or_None}, "cst": {code, ...}, "duplicates": int}. Pure."""
|
||||
classes, cst = catalog.get("classes", {}), set(catalog.get("cst", ()))
|
||||
bad_format = [c for c in classes if not re.fullmatch(r"\d{6}", str(c))]
|
||||
bad_cst_format = [c for c in cst if not re.fullmatch(r"\d{3}", str(c))]
|
||||
orphans = [c for c, s in classes.items() if s is None or str(s) not in cst]
|
||||
return {"classes": len(classes), "cst": len(cst), "orphan_classes": len(orphans), "bad_class_format": len(bad_format),
|
||||
"bad_cst_format": len(bad_cst_format), "duplicates": int(catalog.get("duplicates", 0)),
|
||||
"valid": bool(classes and cst and not orphans and not bad_format and not bad_cst_format and not catalog.get("duplicates"))}
|
||||
|
||||
# ---------------------------------------------------------------- validators ---------------------------------------------------
|
||||
|
||||
|
||||
def _text(value) -> str:
|
||||
return "" if value is None else str(value).strip()
|
||||
|
||||
|
||||
def validate_cclasstrib(value, catalog: dict | None) -> str:
|
||||
"""EMPTY | INVALID_FORMAT | NOT_IN_LOCAL_CATALOG | VALID_LOCAL | CST_MISSING | VALID_WITH_CST (deterministic, no inference)."""
|
||||
code = _text(value)
|
||||
if not code:
|
||||
return "EMPTY"
|
||||
if not re.fullmatch(r"\d{6}", code):
|
||||
return "INVALID_FORMAT"
|
||||
if not catalog or code not in catalog.get("classes", {}):
|
||||
return "NOT_IN_LOCAL_CATALOG"
|
||||
if not catalog.get("cst"):
|
||||
return "VALID_LOCAL" # code exists, but there is no CST table to verify its CST
|
||||
cst = catalog["classes"][code]
|
||||
return "VALID_WITH_CST" if cst is not None and str(cst) in catalog["cst"] else "CST_MISSING"
|
||||
|
||||
|
||||
def validate_pis_cofins_cst(value) -> str:
|
||||
code = _text(value)
|
||||
if not code:
|
||||
return "EMPTY"
|
||||
return "VALID" if code in OFFICIAL_PIS_COFINS_CST else "INVALID_FORMAT"
|
||||
|
||||
|
||||
def validate_pis_cofins_rate(value) -> str:
|
||||
text = _text(value)
|
||||
if not text:
|
||||
return "EMPTY"
|
||||
if not re.fullmatch(r"\d{1,3}([.,]\d{1,4})?", text):
|
||||
return "INVALID_RATE"
|
||||
return "VALID" if float(text.replace(",", ".")) <= 100 else "INVALID_RATE"
|
||||
|
||||
|
||||
def validate_pis_cofins(product: dict) -> dict:
|
||||
"""Field-level validation for one product row. Flag consistency compares the ERP's own flag/incidence with its own codes."""
|
||||
out = {}
|
||||
for tax in ("PIS", "COFINS"):
|
||||
out[f"{tax}_ENTRY_CST"] = validate_pis_cofins_cst(product.get(f"COD_ENTRADA_{tax}"))
|
||||
out[f"{tax}_EXIT_CST"] = validate_pis_cofins_cst(product.get(f"COD_SAIDA_{tax}"))
|
||||
out[f"{tax}_RATE"] = validate_pis_cofins_rate(product.get(f"ALIQ_{tax}"))
|
||||
flag = _text(product.get("PISCOFINS")).upper()
|
||||
incidence = _text(product.get("INCIDENCIA_PISCOFINS")).upper()
|
||||
has_codes = any(_text(product.get(c)) for c in PIS_COFINS_COLUMNS[:6])
|
||||
special = incidence not in ("", "NENHUMA")
|
||||
inconsistent = (flag == "N" and (special or has_codes)) or (flag == "S" and incidence in ("", "NENHUMA"))
|
||||
out["FLAG_CONSISTENCY"] = "INCONSISTENT" if inconsistent else ("EMPTY" if not flag else "CONSISTENT")
|
||||
return out
|
||||
|
||||
# ---------------------------------------------------------------- applicability -----------------------------------------------
|
||||
|
||||
|
||||
def rtc_applicability(caps: dict) -> str:
|
||||
"""APPLICABLE only when the ERP itself is switched to the reform (PARAMETROS.USAR_REFORMA_TRIBUTARIA) AND the model exists.
|
||||
NOT_ENABLED_BY_ERP when the ERP says it does not use RTC; NOT_SUPPORTED when the schema cannot hold it; else UNKNOWN."""
|
||||
if not (caps.get("HAS_PRODUCT_CCLASSTRIB") and caps.get("HAS_RTC_RELATIONSHIP")):
|
||||
return "NOT_SUPPORTED"
|
||||
enabled = caps.get("RTC_ENABLED_BY_PARAM")
|
||||
if enabled is None:
|
||||
return "UNKNOWN_APPLICABILITY"
|
||||
return "APPLICABLE" if enabled else "NOT_ENABLED_BY_ERP"
|
||||
|
||||
|
||||
def pis_cofins_applicability(product: dict, schema_ok: bool) -> str:
|
||||
"""Product-level PIS/COFINS defaults are treated as applicable only when the product itself carries evidence of them
|
||||
(a code, or a special incidence/flag). A product with no data and flag N / NENHUMA gives NO proof either way (documents
|
||||
take their CST from elsewhere), so it is UNKNOWN_APPLICABILITY - not complete and not blocked."""
|
||||
if not schema_ok:
|
||||
return "NOT_SUPPORTED"
|
||||
evidence = any(_text(product.get(c)) for c in PIS_COFINS_COLUMNS[:6]) or _text(product.get("PISCOFINS")).upper() == "S" \
|
||||
or _text(product.get("INCIDENCIA_PISCOFINS")).upper() not in ("", "NENHUMA")
|
||||
return "APPLICABLE" if evidence else "UNKNOWN_APPLICABILITY"
|
||||
|
||||
# ---------------------------------------------------------------- blockers + expanded coverage ------------------------------
|
||||
|
||||
|
||||
def expanded_findings(product: dict, caps: dict, catalog: dict | None) -> dict:
|
||||
"""{"blockers": [(TYPE, reason)], "unknown": [area], "notes": [...]}: specific blockers only where applicability is proven."""
|
||||
blockers, unknown = [], []
|
||||
rtc = rtc_applicability(caps)
|
||||
if rtc == "APPLICABLE":
|
||||
state = validate_cclasstrib(product.get("CCLASSTRIB"), catalog)
|
||||
if state == "EMPTY":
|
||||
blockers.append(("CCLASSTRIB_MISSING", "EMPTY"))
|
||||
elif state == "INVALID_FORMAT":
|
||||
blockers.append(("CCLASSTRIB_INVALID", state))
|
||||
elif state == "NOT_IN_LOCAL_CATALOG":
|
||||
blockers.append(("CCLASSTRIB_NOT_IN_CATALOG", state))
|
||||
elif state == "CST_MISSING":
|
||||
blockers.append(("RTC_CST_INVALID", state))
|
||||
elif rtc == "UNKNOWN_APPLICABILITY":
|
||||
unknown.append("RTC")
|
||||
schema_ok = caps.get("_PIS_COFINS_SCHEMA", False)
|
||||
pis_cofins = pis_cofins_applicability(product, schema_ok)
|
||||
if pis_cofins == "APPLICABLE":
|
||||
checks = validate_pis_cofins(product)
|
||||
for tax in ("PIS", "COFINS"):
|
||||
for slot, label in (("ENTRY_CST", "ENTRY"), ("EXIT_CST", "EXIT")):
|
||||
result = checks[f"{tax}_{slot}"]
|
||||
if result == "INVALID_FORMAT":
|
||||
blockers.append((f"{tax}_CST_INVALID", f"{label}:{result}"))
|
||||
if checks[f"{tax}_RATE"] == "INVALID_RATE":
|
||||
blockers.append((f"{tax}_RATE_INVALID", "INVALID_RATE"))
|
||||
exit_state = checks[f"{tax}_EXIT_CST"]
|
||||
if exit_state == "EMPTY" and checks[f"{tax}_RATE"] != "EMPTY":
|
||||
blockers.append((f"{tax}_CST_MISSING", "EXIT:EMPTY_WITH_RATE"))
|
||||
if checks["FLAG_CONSISTENCY"] == "INCONSISTENT":
|
||||
blockers.append(("PIS_COFINS_FLAG_INCONSISTENT", "FLAG_VS_DATA"))
|
||||
elif pis_cofins == "UNKNOWN_APPLICABILITY":
|
||||
unknown.append("PIS_COFINS")
|
||||
return {"blockers": blockers, "unknown": unknown, "rtc_applicability": rtc, "pis_cofins_applicability": pis_cofins}
|
||||
|
||||
|
||||
def expanded_scope_summary(products: list[dict], legacy_states: dict, caps: dict, catalog: dict | None) -> dict:
|
||||
"""Legacy coverage is reported unchanged (LEGACY_SCOPE_COVERAGE); EXPANDED_FISCAL_SCOPE_COVERAGE adds RTC + PIS/COFINS.
|
||||
legacy_states: {product_code: state} from classify_product_state (COMPLETE/CORRECTED/BLOCKED_*/FAILED_TECHNICAL)."""
|
||||
legacy = {"complete": 0, "safe_blocked": 0, "failed": 0}
|
||||
expanded = {"complete": 0, "safe_blocked": 0, "failed": 0, "unknown_applicability": 0}
|
||||
blockers: dict[str, int] = {}
|
||||
blocked_rows = []
|
||||
caps = dict(caps)
|
||||
for product in products:
|
||||
code = str(product.get("CODIGO") or "")
|
||||
state = legacy_states.get(code, "COMPLETE")
|
||||
if state == "FAILED_TECHNICAL":
|
||||
legacy["failed"] += 1
|
||||
expanded["failed"] += 1
|
||||
continue
|
||||
if state.startswith("BLOCKED_"):
|
||||
legacy["safe_blocked"] += 1
|
||||
expanded["safe_blocked"] += 1 # blocked in the legacy scope stays blocked: the expanded scope only ADDS conditions
|
||||
continue
|
||||
legacy["complete"] += 1
|
||||
found = expanded_findings(product, caps, catalog)
|
||||
if found["blockers"]:
|
||||
expanded["safe_blocked"] += 1
|
||||
for kind, reason in found["blockers"]:
|
||||
blockers[kind] = blockers.get(kind, 0) + 1
|
||||
if len(blocked_rows) < 200:
|
||||
blocked_rows.append({"product_code": code, "type": kind, "reason": reason})
|
||||
elif found["unknown"]:
|
||||
expanded["unknown_applicability"] += 1
|
||||
else:
|
||||
expanded["complete"] += 1
|
||||
total = len(products)
|
||||
return {"scope_version": "expanded-v1", "local_catalog_only": LOCAL_CATALOG_ONLY, "total_products": total,
|
||||
"legacy_scope": legacy, "expanded_scope": expanded, "blockers_by_type": dict(sorted(blockers.items())),
|
||||
"blocker_samples": blocked_rows, "invariant_pass": sum(expanded.values()) == total and sum(legacy.values()) == total,
|
||||
"capabilities": {k: v for k, v in caps.items() if not k.startswith("_")}}
|
||||
|
||||
# ---------------------------------------------------------------- read-only loader -------------------------------------------
|
||||
|
||||
|
||||
def load_rtc_context(cursor) -> tuple[dict, dict | None, dict]:
|
||||
"""Read schema + catalog + ERP parameter through an already open READ ONLY cursor (SELECT only).
|
||||
Returns (schema, catalog|None, extras). Never raises for a database that lacks RTC."""
|
||||
schema: dict[str, set] = {}
|
||||
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}
|
||||
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]
|
||||
except Exception: # noqa: BLE001 - unreadable parameter => applicability stays unknown
|
||||
extras["param_value"] = None
|
||||
try:
|
||||
cursor.execute("SELECT COUNT(*) FROM RDB$RELATION_CONSTRAINTS WHERE RDB$RELATION_NAME = 'C000025' AND RDB$CONSTRAINT_TYPE = 'FOREIGN KEY' AND RDB$CONSTRAINT_NAME CONTAINING 'CCLASSTRIB'")
|
||||
extras["physical_fk"] = cursor.fetchone()[0] > 0
|
||||
except Exception: # noqa: BLE001
|
||||
pass
|
||||
catalog = None
|
||||
if {"CCLASSTRIB", "CSTIBSCBS"} <= schema.get(RTC_CCLASSTRIB_TABLE, set()) and "CSTIBSCBS" in schema.get(RTC_CST_TABLE, set()):
|
||||
cursor.execute("SELECT TRIM(CCLASSTRIB), TRIM(CSTIBSCBS) FROM RTC_CCLASSTRIB")
|
||||
rows = cursor.fetchall()
|
||||
classes = {c: (s or None) for c, s in rows}
|
||||
cursor.execute("SELECT TRIM(CSTIBSCBS) FROM RTC_CST")
|
||||
cst = {r[0] for r in cursor.fetchall()}
|
||||
catalog = {"classes": classes, "cst": cst, "duplicates": len(rows) - len(classes)}
|
||||
return schema, catalog, extras
|
||||
|
||||
|
||||
def read_products_rtc_pis_cofins(cursor, schema: dict) -> list[dict]:
|
||||
"""SELECT of only the columns that exist (old schemas keep working)."""
|
||||
have = schema.get(PRODUCT_TABLE, set())
|
||||
columns = [c for c in PRODUCT_COLUMNS if c in have]
|
||||
if "CODIGO" not in columns:
|
||||
return []
|
||||
cursor.execute(f"SELECT {', '.join(columns)} FROM {PRODUCT_TABLE} ORDER BY CODIGO")
|
||||
return [dict(zip(columns, row)) for row in cursor.fetchall()]
|
||||
|
||||
|
||||
def assess_from_connection(conn, legacy_states: dict) -> dict:
|
||||
"""One read-only pass: capabilities -> catalog integrity -> expanded scope. Any failure degrades to an explicit unavailable
|
||||
marker; it never raises into the job and never touches the database beyond SELECT."""
|
||||
try:
|
||||
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"])
|
||||
caps["_PIS_COFINS_SCHEMA"] = all(c in schema.get(PRODUCT_TABLE, ()) for c in PIS_COFINS_COLUMNS[:6])
|
||||
products = read_products_rtc_pis_cofins(cursor, schema)
|
||||
summary = expanded_scope_summary(products, legacy_states, caps, catalog)
|
||||
summary["catalog_integrity"] = catalog_integrity(catalog) if catalog else None
|
||||
summary["capabilities"]["PIS_COFINS_SCHEMA_VALID"] = caps["_PIS_COFINS_SCHEMA"]
|
||||
summary["data_present"] = {c: sum(1 for p in products if _text(p.get(c))) for c in ("CCLASSTRIB",) + PIS_COFINS_COLUMNS if any(c in p for p in products[:1])}
|
||||
return summary
|
||||
except Exception as error: # noqa: BLE001
|
||||
return {"scope_version": "expanded-v1", "unavailable": type(error).__name__}
|
||||
@@ -0,0 +1,102 @@
|
||||
"""READ-ONLY ERP loader for the unified fiscal completeness (PIS/COFINS applicability + RTC when the ERP has the reform on).
|
||||
|
||||
The rules live in central/app/unified_fiscal_coverage.py (pure). This module only observes the ERP through an already open
|
||||
READ ONLY connection (SELECT only): schema capabilities, RTC catalog, PARAMETROS.USAR_REFORMA_TRIBUTARIA and the product columns.
|
||||
Nothing is written and no value is ever filled in.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import hashlib
|
||||
import json
|
||||
from datetime import date
|
||||
|
||||
from central.app.unified_fiscal_coverage import PIS_COFINS_FIELDS, catalog_integrity, evaluate_product, 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")
|
||||
TRUTHY = {"1", "S", "T", "TRUE"}
|
||||
|
||||
|
||||
def detect_rtc_capabilities(schema: dict, *, param_value=None, catalog: dict | None = None, physical_fk: bool | None = None) -> dict:
|
||||
"""schema: {TABLE: {COLUMN, ...}}. Fail-closed: anything not proven is False. Old databases without RTC simply report False."""
|
||||
product_cols = set(schema.get(PRODUCT_TABLE, ()))
|
||||
class_cols = set(schema.get(RTC_CCLASSTRIB_TABLE, ()))
|
||||
cst_cols = set(schema.get(RTC_CST_TABLE, ()))
|
||||
has_product = "CCLASSTRIB" in product_cols
|
||||
has_class = {"CCLASSTRIB", "CSTIBSCBS"} <= class_cols
|
||||
has_cst = "CSTIBSCBS" in cst_cols
|
||||
caps = {
|
||||
"HAS_PRODUCT_CCLASSTRIB": has_product, "HAS_RTC_CCLASSTRIB_TABLE": has_class, "HAS_RTC_CST_TABLE": has_cst,
|
||||
"HAS_RTC_RELATIONSHIP": bool(has_product and has_class and has_cst), "HAS_PHYSICAL_RTC_FK": bool(physical_fk),
|
||||
"RTC_ENABLED_BY_PARAM": False if param_value is None else str(param_value).strip().upper() in TRUTHY,
|
||||
"RTC_CATALOG_VALID": False,
|
||||
}
|
||||
if catalog is not None and has_class and has_cst:
|
||||
caps["RTC_CATALOG_VALID"] = catalog_integrity(catalog)["valid"]
|
||||
return caps
|
||||
|
||||
|
||||
def load_rtc_context(cursor) -> tuple[dict, dict | None, dict]:
|
||||
"""Schema + catalog + ERP parameter through a READ ONLY cursor. Never raises for a database that lacks RTC."""
|
||||
schema: dict[str, set] = {}
|
||||
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}
|
||||
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]
|
||||
except Exception: # noqa: BLE001 - unreadable parameter => RTC stays off (not enabled by the ERP)
|
||||
extras["param_value"] = None
|
||||
try:
|
||||
cursor.execute("SELECT COUNT(*) FROM RDB$RELATION_CONSTRAINTS WHERE RDB$RELATION_NAME = 'C000025' AND RDB$CONSTRAINT_TYPE = 'FOREIGN KEY' AND RDB$CONSTRAINT_NAME CONTAINING 'CCLASSTRIB'")
|
||||
extras["physical_fk"] = cursor.fetchone()[0] > 0
|
||||
except Exception: # noqa: BLE001
|
||||
pass
|
||||
catalog = None
|
||||
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")
|
||||
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}
|
||||
return schema, catalog, extras
|
||||
|
||||
|
||||
def read_products(cursor, schema: dict) -> list[dict]:
|
||||
"""SELECT of only the columns that exist (old schemas keep working)."""
|
||||
have = schema.get(PRODUCT_TABLE, set())
|
||||
columns = [c for c in PRODUCT_COLUMNS if c in have]
|
||||
if "CODIGO" not in columns:
|
||||
return []
|
||||
cursor.execute(f"SELECT {', '.join(columns)} FROM {PRODUCT_TABLE} ORDER BY CODIGO")
|
||||
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:
|
||||
"""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)."""
|
||||
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"])
|
||||
enabled = rtc_enabled(caps) if rtc_override is None else bool(rtc_override and caps["HAS_RTC_RELATIONSHIP"])
|
||||
rtc = {"enabled": enabled, "catalog": catalog, "catalog_valid": caps["RTC_CATALOG_VALID"]}
|
||||
today = today or date.today()
|
||||
products = read_products(cursor, schema)
|
||||
findings = {}
|
||||
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
|
||||
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)},
|
||||
"findings": findings}
|
||||
@@ -1,3 +1,24 @@
|
||||
import sys
|
||||
from pathlib import Path
|
||||
sys.path.insert(0, str(Path(__file__).parents[1] / "src"))
|
||||
|
||||
|
||||
import pytest
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def _no_real_firebird_for_unified_fiscal(monkeypatch, request):
|
||||
"""The unified fiscal assessment opens its own READ ONLY connection; unit tests have no Firebird, so it is neutral here unless a test opts in."""
|
||||
if request.node.get_closest_marker("real_unified_fiscal"):
|
||||
return
|
||||
from gti_fiscal_agent import firebird_reader, unified_fiscal
|
||||
|
||||
class _Conn:
|
||||
def close(self):
|
||||
pass
|
||||
monkeypatch.setattr(firebird_reader, "_connect", lambda env: _Conn())
|
||||
monkeypatch.setattr(unified_fiscal, "assess_from_connection", lambda conn, company, **kw: {"context": {"rtc_enabled": False, "rtc_supported": False, "rtc_catalog_valid": False, "products_evaluated": 0}, "findings": {}})
|
||||
|
||||
|
||||
def pytest_configure(config):
|
||||
config.addinivalue_line("markers", "real_unified_fiscal: use the real unified fiscal assessment (with a fake cursor) instead of the neutral stub")
|
||||
|
||||
@@ -1,134 +0,0 @@
|
||||
"""RTC (cClassTrib / CST IBS-CBS) and PIS/COFINS: capabilities, validators, applicability, legacy vs expanded coverage. Read-only."""
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
sys.path.insert(0, str(Path(__file__).parents[1] / "src"))
|
||||
|
||||
from gti_fiscal_agent import rtc_pis_cofins as r # noqa: E402
|
||||
|
||||
FULL_SCHEMA = {"C000025": {"CODIGO", "CCLASSTRIB", *r.PIS_COFINS_COLUMNS}, "RTC_CCLASSTRIB": {"CCLASSTRIB", "CSTIBSCBS"}, "RTC_CST": {"CSTIBSCBS"}}
|
||||
CATALOG = {"classes": {"000001": "000", "200003": "200", "999999": None}, "cst": {"000", "200"}, "duplicates": 0}
|
||||
|
||||
|
||||
def caps(schema=FULL_SCHEMA, param=0, catalog=CATALOG):
|
||||
c = r.detect_rtc_capabilities(schema, param_value=param, catalog=catalog, physical_fk=True)
|
||||
c["_PIS_COFINS_SCHEMA"] = all(col in schema.get("C000025", ()) for col in r.PIS_COFINS_COLUMNS[:6])
|
||||
return c
|
||||
|
||||
|
||||
def test_database_without_rtc_fails_closed_and_is_not_blocked():
|
||||
c = caps({"C000025": {"CODIGO"}}, param=None, catalog=None)
|
||||
assert not any(c[k] for k in ("HAS_PRODUCT_CCLASSTRIB", "HAS_RTC_CCLASSTRIB_TABLE", "HAS_RTC_CST_TABLE", "HAS_RTC_RELATIONSHIP", "RTC_CATALOG_VALID"))
|
||||
assert r.rtc_applicability(c) == "NOT_SUPPORTED"
|
||||
found = r.expanded_findings({"CODIGO": 1}, c, None)
|
||||
assert found["blockers"] == [] and found["unknown"] == []
|
||||
|
||||
|
||||
def test_partial_rtc_column_without_catalog_tables_is_not_a_relationship():
|
||||
c = caps({"C000025": {"CODIGO", "CCLASSTRIB"}}, param=1, catalog=None)
|
||||
assert c["HAS_PRODUCT_CCLASSTRIB"] and not c["HAS_RTC_RELATIONSHIP"] and not c["RTC_CATALOG_VALID"]
|
||||
assert r.rtc_applicability(c) == "NOT_SUPPORTED"
|
||||
|
||||
|
||||
def test_full_model_and_valid_catalog_capabilities():
|
||||
good = {"classes": {"000001": "000"}, "cst": {"000"}, "duplicates": 0}
|
||||
c = caps(catalog=good)
|
||||
assert c["HAS_RTC_RELATIONSHIP"] and c["RTC_CATALOG_VALID"] and c["HAS_PHYSICAL_RTC_FK"] and c["RTC_ENABLED_BY_PARAM"] is False and c["LOCAL_CATALOG_ONLY"]
|
||||
assert not caps(catalog=CATALOG)["RTC_CATALOG_VALID"] # a class pointing to a missing CST makes the catalog invalid
|
||||
|
||||
|
||||
def test_cclasstrib_validator_states():
|
||||
assert r.validate_cclasstrib(None, CATALOG) == "EMPTY" and r.validate_cclasstrib(" ", CATALOG) == "EMPTY"
|
||||
assert r.validate_cclasstrib("12A456", CATALOG) == "INVALID_FORMAT" and r.validate_cclasstrib("12345", CATALOG) == "INVALID_FORMAT"
|
||||
assert r.validate_cclasstrib("123456", CATALOG) == "NOT_IN_LOCAL_CATALOG"
|
||||
assert r.validate_cclasstrib("200003", CATALOG) == "VALID_WITH_CST"
|
||||
assert r.validate_cclasstrib("999999", CATALOG) == "CST_MISSING"
|
||||
assert r.validate_cclasstrib("000001", {"classes": {"000001": None}, "cst": set()}) == "VALID_LOCAL"
|
||||
assert r.validate_cclasstrib("000001", None) == "NOT_IN_LOCAL_CATALOG"
|
||||
|
||||
|
||||
def test_pis_cofins_validators():
|
||||
assert r.validate_pis_cofins_cst("") == "EMPTY" and r.validate_pis_cofins_cst(None) == "EMPTY"
|
||||
assert r.validate_pis_cofins_cst("01") == "VALID" and r.validate_pis_cofins_cst("73") == "VALID"
|
||||
assert r.validate_pis_cofins_cst("2") == "INVALID_FORMAT" and r.validate_pis_cofins_cst("10") == "INVALID_FORMAT"
|
||||
assert r.validate_pis_cofins_rate("0,65") == "VALID" and r.validate_pis_cofins_rate("3") == "VALID" and r.validate_pis_cofins_rate("") == "EMPTY"
|
||||
assert r.validate_pis_cofins_rate("abc") == "INVALID_RATE" and r.validate_pis_cofins_rate("150") == "INVALID_RATE"
|
||||
ok = r.validate_pis_cofins({"PISCOFINS": "S", "INCIDENCIA_PISCOFINS": "MONOFASICA", "COD_SAIDA_PIS": "04", "COD_SAIDA_COFINS": "04"})
|
||||
assert ok["FLAG_CONSISTENCY"] == "CONSISTENT"
|
||||
bad = r.validate_pis_cofins({"PISCOFINS": "N", "INCIDENCIA_PISCOFINS": "NENHUMA", "COD_SAIDA_PIS": "01"})
|
||||
assert bad["FLAG_CONSISTENCY"] == "INCONSISTENT"
|
||||
|
||||
|
||||
def test_empty_pis_cofins_is_unknown_applicability_not_a_blocker():
|
||||
found = r.expanded_findings({"CODIGO": 1, "PISCOFINS": "N", "INCIDENCIA_PISCOFINS": "NENHUMA"}, caps(), CATALOG)
|
||||
assert found["blockers"] == [] and found["unknown"] == ["PIS_COFINS"] and found["rtc_applicability"] == "NOT_ENABLED_BY_ERP"
|
||||
|
||||
|
||||
def test_populated_pis_cofins_is_applicable_and_valid_data_completes():
|
||||
row = {"CODIGO": 1, "PISCOFINS": "S", "INCIDENCIA_PISCOFINS": "SUBST.TRIBUTARIA", "COD_ENTRADA_PIS": "73", "COD_SAIDA_PIS": "01", "ALIQ_PIS": "0,65",
|
||||
"COD_ENTRADA_COFINS": "73", "COD_SAIDA_COFINS": "01", "ALIQ_COFINS": "3"}
|
||||
found = r.expanded_findings(row, caps(), CATALOG)
|
||||
assert found["blockers"] == [] and found["unknown"] == [] and found["pis_cofins_applicability"] == "APPLICABLE"
|
||||
|
||||
|
||||
def test_invalid_values_where_applicable_create_specific_blockers():
|
||||
row = {"CODIGO": 2, "PISCOFINS": "N", "INCIDENCIA_PISCOFINS": "NENHUMA", "COD_ENTRADA_PIS": "2", "COD_SAIDA_PIS": "2", "ALIQ_PIS": "2",
|
||||
"COD_ENTRADA_COFINS": "2", "COD_SAIDA_COFINS": "2", "ALIQ_COFINS": "x"}
|
||||
kinds = {k for k, _ in r.expanded_findings(row, caps(), CATALOG)["blockers"]}
|
||||
assert {"PIS_CST_INVALID", "COFINS_CST_INVALID", "COFINS_RATE_INVALID", "PIS_COFINS_FLAG_INCONSISTENT"} <= kinds
|
||||
|
||||
|
||||
def test_rtc_becomes_applicable_only_when_the_erp_enables_it():
|
||||
on = caps(param=1)
|
||||
assert r.rtc_applicability(on) == "APPLICABLE"
|
||||
assert {k for k, _ in r.expanded_findings({"CODIGO": 1}, on, CATALOG)["blockers"]} == {"CCLASSTRIB_MISSING"}
|
||||
assert {k for k, _ in r.expanded_findings({"CODIGO": 1, "CCLASSTRIB": "123456"}, on, CATALOG)["blockers"]} == {"CCLASSTRIB_NOT_IN_CATALOG"}
|
||||
assert {k for k, _ in r.expanded_findings({"CODIGO": 1, "CCLASSTRIB": "999999"}, on, CATALOG)["blockers"]} == {"RTC_CST_INVALID"}
|
||||
assert {k for k, _ in r.expanded_findings({"CODIGO": 1, "CCLASSTRIB": "12x"}, on, CATALOG)["blockers"]} == {"CCLASSTRIB_INVALID"}
|
||||
assert not [k for k, _ in r.expanded_findings({"CODIGO": 1, "CCLASSTRIB": "200003"}, on, CATALOG)["blockers"] if k.startswith(("CCLASS", "RTC"))]
|
||||
assert r.rtc_applicability(caps(param=None)) == "UNKNOWN_APPLICABILITY"
|
||||
|
||||
|
||||
def test_legacy_and_expanded_coverage_are_reported_side_by_side_and_close():
|
||||
products = [{"CODIGO": 1}, {"CODIGO": 2}, {"CODIGO": 3},
|
||||
{"CODIGO": 4, "PISCOFINS": "S", "INCIDENCIA_PISCOFINS": "MONOFASICA", "COD_SAIDA_PIS": "01", "COD_SAIDA_COFINS": "01", "COD_ENTRADA_PIS": "73", "COD_ENTRADA_COFINS": "73"},
|
||||
{"CODIGO": 5, "COD_SAIDA_PIS": "2", "PISCOFINS": "S"}]
|
||||
states = {"1": "COMPLETE", "2": "BLOCKED_MISSING_EVIDENCE", "3": "FAILED_TECHNICAL", "4": "COMPLETE", "5": "CORRECTED"}
|
||||
out = r.expanded_scope_summary(products, states, caps(), CATALOG)
|
||||
assert out["legacy_scope"] == {"complete": 3, "safe_blocked": 1, "failed": 1}
|
||||
# 1 unknown (no PIS/COFINS evidence), 1 complete (#4 valid), blocked = legacy-blocked #2 + #5 (invalid CST), failed #3
|
||||
assert out["expanded_scope"] == {"complete": 1, "safe_blocked": 2, "failed": 1, "unknown_applicability": 1}
|
||||
assert out["invariant_pass"] and out["blockers_by_type"].get("PIS_CST_INVALID") == 1 and out["local_catalog_only"]
|
||||
|
||||
|
||||
class FakeCursor:
|
||||
def __init__(self, tables, rows):
|
||||
self.tables, self.rows, self.result = tables, rows, []
|
||||
|
||||
def execute(self, sql):
|
||||
assert sql.lstrip().upper().startswith("SELECT") # read-only by construction
|
||||
if "RDB$RELATION_FIELDS" in sql:
|
||||
self.result = [(t, c) for t, cols in self.tables.items() for c in cols]
|
||||
elif "USAR_REFORMA_TRIBUTARIA" in sql:
|
||||
self.result = [(0,)]
|
||||
elif "RDB$RELATION_CONSTRAINTS" in sql:
|
||||
self.result = [(1,)]
|
||||
elif "FROM RTC_CCLASSTRIB" in sql:
|
||||
self.result = [("000001", "000")]
|
||||
elif "FROM RTC_CST" in sql:
|
||||
self.result = [("000",)]
|
||||
elif "FROM C000025" in sql:
|
||||
self.result = self.rows
|
||||
|
||||
def fetchall(self):
|
||||
return self.result
|
||||
|
||||
def fetchone(self):
|
||||
return self.result[0] if self.result else None
|
||||
|
||||
|
||||
def test_loader_reads_schema_catalog_and_param_with_select_only():
|
||||
cur = FakeCursor({"C000025": ["CODIGO", "CCLASSTRIB"], "RTC_CCLASSTRIB": ["CCLASSTRIB", "CSTIBSCBS"], "RTC_CST": ["CSTIBSCBS"], "PARAMETROS": ["USAR_REFORMA_TRIBUTARIA"]}, [(1, None)])
|
||||
schema, catalog, extras = r.load_rtc_context(cur)
|
||||
assert catalog == {"classes": {"000001": "000"}, "cst": {"000"}, "duplicates": 0} and extras == {"param_value": 0, "physical_fk": True}
|
||||
assert r.read_products_rtc_pis_cofins(cur, schema) == [{"CODIGO": 1, "CCLASSTRIB": None}]
|
||||
@@ -0,0 +1,110 @@
|
||||
import re
|
||||
from datetime import date
|
||||
|
||||
import pytest
|
||||
|
||||
from gti_fiscal_agent import unified_fiscal as uf
|
||||
|
||||
pytestmark = pytest.mark.real_unified_fiscal
|
||||
|
||||
SCHEMA_ROWS = ([("C000025", c) for c in ("CODIGO", "CCLASSTRIB", "COD_ENTRADA_PIS", "COD_SAIDA_PIS", "ALIQ_PIS", "COD_ENTRADA_COFINS", "COD_SAIDA_COFINS", "ALIQ_COFINS", "PISCOFINS", "INCIDENCIA_PISCOFINS")]
|
||||
+ [("RTC_CCLASSTRIB", c) for c in ("CCLASSTRIB", "CSTIBSCBS", "INICIO_VIGENCIA", "FIM_VIGENCIA")] + [("RTC_CST", "CSTIBSCBS")] + [("PARAMETROS", "USAR_REFORMA_TRIBUTARIA")])
|
||||
PRODUCT_COLS = ("CODIGO", "CCLASSTRIB", "COD_ENTRADA_PIS", "COD_SAIDA_PIS", "ALIQ_PIS", "COD_ENTRADA_COFINS", "COD_SAIDA_COFINS", "ALIQ_COFINS", "PISCOFINS", "INCIDENCIA_PISCOFINS")
|
||||
PRODUCTS = [("1", None, None, None, None, None, None, None, "N", "NENHUMA"), ("2", "200003", None, "04", None, None, "04", None, "S", "MONOFÁSICA"), ("3", None, "2", "2", "2", "2", "2", "2", "N", "NENHUMA")]
|
||||
CLASSES = [("000001", "000", date(2025, 5, 5), None), ("200003", "200", date(2025, 5, 5), None)]
|
||||
|
||||
|
||||
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 execute(self, sql, *args):
|
||||
self.statements.append(sql)
|
||||
text = " ".join(sql.split()).upper()
|
||||
assert text.startswith("SELECT"), f"non-SELECT statement: {sql}"
|
||||
if "FROM RDB$RELATION_FIELDS" in text:
|
||||
self._rows = list(self.schema)
|
||||
elif "FROM PARAMETROS" in text:
|
||||
self._rows = [(self.param,)]
|
||||
elif "RDB$RELATION_CONSTRAINTS" in text:
|
||||
self._rows = [(1,)]
|
||||
elif "FROM RTC_CCLASSTRIB" in text:
|
||||
self._rows = list(self.classes)
|
||||
elif "FROM RTC_CST" in text:
|
||||
self._rows = list(self.cst)
|
||||
elif "FROM C000025" in text:
|
||||
self._rows = list(self.products)
|
||||
else:
|
||||
raise AssertionError(sql)
|
||||
|
||||
def fetchall(self):
|
||||
return self._rows
|
||||
|
||||
def fetchone(self):
|
||||
return self._rows[0] if self._rows else None
|
||||
|
||||
|
||||
class FakeConn:
|
||||
def __init__(self, **kw):
|
||||
self.cur = FakeCursor(**kw)
|
||||
|
||||
def cursor(self):
|
||||
return self.cur
|
||||
|
||||
|
||||
def kinds(found):
|
||||
return sorted({b["type"] for b in found["blockers"]})
|
||||
|
||||
|
||||
def test_rtc_off_in_the_erp_is_out_of_scope_and_never_blocks():
|
||||
conn = FakeConn(param=0)
|
||||
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 all(s.strip().upper().startswith("SELECT") for s in conn.cur.statements) # read-only: SELECT statements only
|
||||
|
||||
|
||||
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
|
||||
|
||||
|
||||
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)
|
||||
|
||||
|
||||
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")]
|
||||
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"]
|
||||
|
||||
|
||||
@pytest.mark.parametrize("value,expected", [(None, False), (0, False), ("0", False), (1, True), ("1", True), ("S", True), ("N", False)])
|
||||
def test_parameter_values_are_read_fail_closed(value, expected):
|
||||
caps = uf.detect_rtc_capabilities({"C000025": {"CCLASSTRIB"}, "RTC_CCLASSTRIB": {"CCLASSTRIB", "CSTIBSCBS"}, "RTC_CST": {"CSTIBSCBS"}}, param_value=value)
|
||||
assert caps["RTC_ENABLED_BY_PARAM"] is expected
|
||||
|
||||
|
||||
def test_catalog_with_orphan_or_duplicate_is_invalid_and_flagged_only_when_rtc_is_on():
|
||||
bad = FakeConn(param=1, classes=CLASSES + [("200003", "200", date(2025, 5, 5), None)]) # duplicate class
|
||||
on = uf.assess_from_connection(bad, {"crt": "1"})
|
||||
assert on["context"]["rtc_catalog_valid"] is False and kinds(on["findings"]["2"]) == ["RTC_CATALOG_INVALID"]
|
||||
off = uf.assess_from_connection(FakeConn(param=0, classes=CLASSES + [("200003", "200", date(2025, 5, 5), None)]), {"crt": "1"})
|
||||
assert not [b for f in off["findings"].values() for b in f["blockers"] if b["category"] == "RTC"]
|
||||
|
||||
|
||||
def test_input_fingerprint_changes_with_the_fiscal_values_and_the_rtc_switch():
|
||||
a = uf.assess_from_connection(FakeConn(param=0), {"crt": "1"})["findings"]["1"]["input_fingerprint"]
|
||||
b = uf.assess_from_connection(FakeConn(param=1), {"crt": "1"})["findings"]["1"]["input_fingerprint"]
|
||||
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)
|
||||
@@ -146,3 +146,42 @@ def test_heartbeats_keep_flowing_while_a_long_job_runs(monkeypatch, tmp_path):
|
||||
monkeypatch.setattr(service, "execute_job", lambda *a, **k: (time.sleep(0.4), ({"ok": 1}, None))[1])
|
||||
service.run(_config(agent_id="2", poll_interval_seconds=0, state_db=str(tmp_path / "a.db")), {"AGENT_ID": "2"}, stopper=stopper)
|
||||
assert beats.count("BUSY") >= 3 # heartbeats during the job, not only before it
|
||||
|
||||
|
||||
def _findings(**by_code):
|
||||
out = {}
|
||||
for code, kinds in by_code.items():
|
||||
out[code] = {"blockers": [{"type": t, "category": c, "reason": "R"} for t, c in kinds], "applicability": {"PIS_COFINS": "REQUIRED", "RTC": "NOT_APPLICABLE"}, "input_fingerprint": "fp" + code, "needs_review": False}
|
||||
return out
|
||||
|
||||
|
||||
def test_unified_fiscal_blockers_change_the_single_coverage_and_the_single_queue(env_setup, monkeypatch):
|
||||
from gti_fiscal_agent import unified_fiscal
|
||||
findings = _findings(**{"2": [("PIS_CST_INVALID", "PIS"), ("COFINS_CST_INVALID", "COFINS")]})
|
||||
monkeypatch.setattr(unified_fiscal, "assess_from_connection", lambda conn, company, **kw: {"context": {"rtc_enabled": False, "rtc_supported": True, "rtc_catalog_valid": True}, "findings": findings})
|
||||
hub, state = env_setup
|
||||
result, error = run_job("INITIAL_SYNC", [product("1"), product("2"), product("3", ncm="")], state, monkeypatch)
|
||||
assert error is None
|
||||
op = result["operational"]
|
||||
assert op["TOTAL_PRODUCTS"] == 3 and op["COMPLETE_PRODUCTS"] == 1 and op["SAFE_BLOCKED"] == 2 and op["BLOCKED_FISCAL_DATA"] == 1 # one number, one definition
|
||||
assert op["COMPLETE_PRODUCTS"] + op["SAFE_BLOCKED"] + op["FAILED_TECHNICAL"] + op["UNCLASSIFIED"] == 3
|
||||
queue = sorted((b["product_code"], b["field"], b["reason_code"]) for b in result["blocked_products"])
|
||||
assert queue == [("2", "COFINS", "COFINS_CST_INVALID"), ("2", "PIS", "PIS_CST_INVALID"), ("3", "NCM", "INVALID_NCM")] # ONE queue: NCM and fiscal blockers together
|
||||
assert result["resolved_products"] == ["1"] # a fiscal blocker keeps the product out of resolved
|
||||
assert result["fiscal_context"]["rtc_enabled"] is False and result["fiscal_context"]["evaluated_all"] is True
|
||||
assert "expanded_scope" not in result
|
||||
|
||||
|
||||
def test_rtc_context_is_reported_but_blockers_only_exist_when_the_evaluator_produced_them(env_setup, monkeypatch):
|
||||
from gti_fiscal_agent import unified_fiscal
|
||||
monkeypatch.setattr(unified_fiscal, "assess_from_connection", lambda conn, company, **kw: {"context": {"rtc_enabled": True, "rtc_supported": True, "rtc_catalog_valid": True}, "findings": _findings(**{"1": [("CCLASSTRIB_MISSING", "RTC")]})})
|
||||
hub, state = env_setup
|
||||
result, _ = run_job("REPROCESS_ALL", [product("1"), product("2")], state, monkeypatch)
|
||||
assert [b["reason_code"] for b in result["blocked_products"]] == ["CCLASSTRIB_MISSING"] and result["blocked_products"][0]["field"] == "RTC"
|
||||
assert result["fiscal_context"]["rtc_enabled"] is True and result["operational"]["BLOCKED_FISCAL_DATA"] == 1
|
||||
|
||||
|
||||
def test_partial_reprocess_does_not_claim_a_full_fiscal_evaluation(env_setup, monkeypatch):
|
||||
hub, state = env_setup
|
||||
result, _ = run_job("REPROCESS_PRODUCTS", [product("1")], state, monkeypatch)
|
||||
assert result["fiscal_context"] is None and result["operational"] is None
|
||||
|
||||
+4
-1
@@ -11,7 +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 .blocked_products import list_blockers, resolve_blocker, resolve_product_blockers, upsert_blocker
|
||||
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
|
||||
from .events import record_event
|
||||
@@ -422,10 +422,13 @@ def router(session_factory, bootstrap_token):
|
||||
if job is None or job.agent_id != agent.id: raise HTTPException(404, "job not found")
|
||||
result_summary = payload.get("result_summary", {})
|
||||
installation = session.scalar(select(Installation).where(Installation.agent_id == agent.id))
|
||||
pass_started = datetime.now(timezone.utc)
|
||||
for blocker in result_summary.get("blocked_products", []):
|
||||
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 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")
|
||||
if installation is not None:
|
||||
_apply_job_outcome(session, agent, installation, job, result_summary, failed=False)
|
||||
|
||||
@@ -18,7 +18,7 @@ def upsert_blocker(session, tenant_id: int, payload: dict, *, installation_id: i
|
||||
row = session.scalar(select(BlockedProduct).where(BlockedProduct.blocker_fingerprint == fingerprint))
|
||||
now = datetime.now(timezone.utc)
|
||||
if row is None:
|
||||
row = BlockedProduct(tenant_id=tenant_id, installation_id=installation_id, company=payload.get("company", ""), product_code=str(payload.get("product_code", "")), gtin=payload.get("gtin"), description=payload.get("description"), blocker_type=payload.get("blocker_type", "BLOCKED_MISSING_EVIDENCE"), field=payload.get("field", ""), reason_code=payload.get("reason_code", ""), evidence_summary=payload.get("evidence_summary") or {}, input_fingerprint=payload.get("input_fingerprint", ""), blocker_fingerprint=fingerprint)
|
||||
row = BlockedProduct(tenant_id=tenant_id, installation_id=installation_id, company=payload.get("company", ""), product_code=str(payload.get("product_code", "")), gtin=payload.get("gtin"), description=payload.get("description"), blocker_type=payload.get("blocker_type", "BLOCKED_MISSING_EVIDENCE"), field=payload.get("field", ""), reason_code=payload.get("reason_code", ""), evidence_summary=payload.get("evidence_summary") or {}, input_fingerprint=payload.get("input_fingerprint", ""), blocker_fingerprint=fingerprint, first_seen_at=now, last_seen_at=now)
|
||||
session.add(row)
|
||||
else:
|
||||
row.last_seen_at = now
|
||||
@@ -73,3 +73,20 @@ def resolve_product_blockers(session, tenant_id: int, product_code: str, source:
|
||||
if rows:
|
||||
session.commit()
|
||||
return len(rows)
|
||||
|
||||
|
||||
FISCAL_BLOCKER_FIELDS = ("PIS", "COFINS", "RTC")
|
||||
|
||||
|
||||
def resolve_stale_fiscal_blockers(session, tenant_id: int, installation_id: int, since: datetime) -> int:
|
||||
"""After a FULL evaluation of the unified fiscal fields, every open PIS/COFINS/RTC blocker of the installation that this pass did not
|
||||
re-report (last_seen_at < since) no longer holds: the ERP data changed, or RTC was switched off. Other blocker fields are untouched."""
|
||||
rows = session.scalars(select(BlockedProduct).where(BlockedProduct.tenant_id == tenant_id, BlockedProduct.installation_id == installation_id,
|
||||
BlockedProduct.resolved_at.is_(None), BlockedProduct.field.in_(FISCAL_BLOCKER_FIELDS), BlockedProduct.last_seen_at < since)).all()
|
||||
now = datetime.now(timezone.utc)
|
||||
for row in rows:
|
||||
row.resolved_at = now
|
||||
row.resolution_source = "AUTO_REEVALUATION"
|
||||
if rows:
|
||||
session.commit()
|
||||
return len(rows)
|
||||
|
||||
@@ -16,7 +16,7 @@ from .panel_presentation import health_reason_label, blocker_reason_label, block
|
||||
|
||||
|
||||
HEALTH_STATES = ("ONLINE", "DEGRADED", "OFFLINE", "FAILED", "UNKNOWN")
|
||||
BLOCKER_FIELDS = ("CFOP", "ST", "ICMS", "CSOSN", "NCM", "OTHER")
|
||||
BLOCKER_FIELDS = ("NCM", "CEST", "CFOP", "ST", "ICMS", "CSOSN", "PIS", "COFINS", "RTC", "OTHER")
|
||||
ATTENTION_CANDIDATE_LIMIT = 10
|
||||
INACTIVE_ENROLLMENT_STATES = ("REVOKED", "FAILED", "EXPIRED")
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ import json
|
||||
|
||||
STATES = {
|
||||
"COMPLETE", "CORRECTED", "BLOCKED_MISSING_EVIDENCE", "BLOCKED_CONFLICT",
|
||||
"BLOCKED_COMPANY_CONTEXT", "BLOCKED_SCHEMA", "FAILED_TECHNICAL",
|
||||
"BLOCKED_COMPANY_CONTEXT", "BLOCKED_SCHEMA", "BLOCKED_FISCAL_DATA", "FAILED_TECHNICAL", "NEEDS_REVIEW",
|
||||
}
|
||||
|
||||
|
||||
@@ -17,7 +17,8 @@ def blocker_fingerprint(tenant: str, company: str, product_code: str, field: str
|
||||
return hashlib.sha256(json.dumps(raw, sort_keys=True, separators=(",", ":")).encode()).hexdigest()
|
||||
|
||||
|
||||
def classify_product_state(product: dict, decision: dict, *, write_status: str | None = None) -> dict:
|
||||
def classify_product_state(product: dict, decision: dict, *, write_status: str | None = None, fiscal: dict | None = None) -> dict:
|
||||
"""`fiscal` = unified_fiscal_coverage.evaluate_product(...): PIS/COFINS + (only when the ERP has RTC on) cClassTrib. One definition of COMPLETE."""
|
||||
status = str(decision.get("status") or "").upper()
|
||||
if write_status == "FAILED":
|
||||
state = "FAILED_TECHNICAL"
|
||||
@@ -33,7 +34,13 @@ def classify_product_state(product: dict, decision: dict, *, write_status: str |
|
||||
state = "CORRECTED"
|
||||
else:
|
||||
state = "COMPLETE"
|
||||
return {"product_code": str(product.get("CODIGO") or ""), "state": state, "decision_status": status}
|
||||
fiscal = fiscal or {}
|
||||
if state in ("COMPLETE", "CORRECTED"):
|
||||
if fiscal.get("blockers"):
|
||||
state = "BLOCKED_FISCAL_DATA"
|
||||
elif fiscal.get("needs_review"):
|
||||
state = "NEEDS_REVIEW"
|
||||
return {"product_code": str(product.get("CODIGO") or ""), "state": state, "decision_status": status, "fiscal_blockers": list(fiscal.get("blockers") or [])}
|
||||
|
||||
|
||||
def should_retry_blocker(previous_input_fingerprint: str | None, current_input_fingerprint: str) -> bool:
|
||||
@@ -44,6 +51,8 @@ def summarize_operational_states(states: list[dict]) -> dict:
|
||||
counts = Counter(item.get("state") for item in states)
|
||||
result = {state: counts.get(state, 0) for state in sorted(STATES)}
|
||||
result["TOTAL_PRODUCTS"] = len(states)
|
||||
result["UNCLASSIFIED"] = result["NEEDS_REVIEW"]
|
||||
result["COMPLETE_PRODUCTS"] = result["COMPLETE"] + result["CORRECTED"]
|
||||
result["SAFE_BLOCKED"] = sum(result[state] for state in STATES if state.startswith("BLOCKED_"))
|
||||
result["AUTONOMOUSLY_PROCESSABLE_PRODUCTS"] = result["TOTAL_PRODUCTS"] - result["FAILED_TECHNICAL"]
|
||||
result["AGENT_HEALTH"] = "HEALTHY" if result["FAILED_TECHNICAL"] == 0 else "DEGRADED"
|
||||
|
||||
@@ -80,7 +80,7 @@ def operational_priority(blocker) -> str:
|
||||
return "CRITICAL"
|
||||
if blocker_type in {"BLOCKED_MISSING_EVIDENCE", "MISSING_EVIDENCE"}:
|
||||
return "HIGH"
|
||||
if field in {"CFOP", "ST", "ICMS", "CSOSN", "NCM"} or reason in {"INSUFFICIENT_EVIDENCE", "NO_EVIDENCE"}:
|
||||
if field in {"CFOP", "ST", "ICMS", "CSOSN", "NCM", "CEST", "PIS", "COFINS", "RTC"} or reason in {"INSUFFICIENT_EVIDENCE", "NO_EVIDENCE"}:
|
||||
return "NORMAL"
|
||||
return "LOW"
|
||||
|
||||
@@ -120,16 +120,17 @@ def job_kind_label(value):
|
||||
return _human_label(value, {"SYNC_INCREMENTAL": "Sincronização incremental", "INITIAL_SYNC": "Sincronização inicial", "REPROCESS_ALL": "Reprocessar tudo", "REPROCESS_PRODUCTS": "Reprocessar produtos"})
|
||||
|
||||
|
||||
_RTC_LABELS = {
|
||||
"CCLASSTRIB_MISSING": "cClassTrib ausente", "CCLASSTRIB_INVALID": "cClassTrib inválido", "CCLASSTRIB_NOT_IN_CATALOG": "cClassTrib fora do catálogo local",
|
||||
"RTC_CST_INVALID": "CST IBS/CBS inválido", "PIS_CST_MISSING": "CST PIS ausente", "PIS_CST_INVALID": "CST PIS inválido", "PIS_RATE_INVALID": "Alíquota PIS inválida",
|
||||
"COFINS_CST_MISSING": "CST COFINS ausente", "COFINS_CST_INVALID": "CST COFINS inválido", "COFINS_RATE_INVALID": "Alíquota COFINS inválida",
|
||||
"PIS_COFINS_FLAG_INCONSISTENT": "Flag PIS/COFINS inconsistente",
|
||||
_FISCAL_LABELS = {
|
||||
"CCLASSTRIB_MISSING": "cClassTrib ausente", "CCLASSTRIB_INVALID_FORMAT": "cClassTrib com formato inválido", "CCLASSTRIB_NOT_IN_CATALOG": "cClassTrib fora do catálogo local",
|
||||
"RTC_CST_MISSING": "CST IBS/CBS ausente no catálogo", "RTC_CATALOG_INVALID": "Catálogo RTC local inválido",
|
||||
"PIS_CST_MISSING": "CST PIS ausente", "PIS_CST_INVALID": "CST PIS inválido", "PIS_RATE_MISSING": "Alíquota PIS ausente", "PIS_RATE_INVALID": "Alíquota PIS inválida", "PIS_CST_INCIDENCE_MISMATCH": "CST PIS incompatível com a incidência",
|
||||
"COFINS_CST_MISSING": "CST COFINS ausente", "COFINS_CST_INVALID": "CST COFINS inválido", "COFINS_RATE_MISSING": "Alíquota COFINS ausente", "COFINS_RATE_INVALID": "Alíquota COFINS inválida", "COFINS_CST_INCIDENCE_MISMATCH": "CST COFINS incompatível com a incidência",
|
||||
"PIS_COFINS_FLAG_INCONSISTENT": "Flag PIS/COFINS inconsistente", "BLOCKED_FISCAL_DATA": "Dado fiscal do cadastro",
|
||||
}
|
||||
|
||||
|
||||
def blocker_type_label(value):
|
||||
return _human_label(value, {"BLOCKED_MISSING_EVIDENCE": "Evidência fiscal ausente", "MISSING_EVIDENCE": "Evidência fiscal ausente", "TECHNICAL_FAILURE": "Falha técnica", "CONNECTION_ERROR": "Erro de conexão", "OTHER": "Outro", **_RTC_LABELS})
|
||||
return _human_label(value, {"BLOCKED_MISSING_EVIDENCE": "Evidência fiscal ausente", "MISSING_EVIDENCE": "Evidência fiscal ausente", "TECHNICAL_FAILURE": "Falha técnica", "CONNECTION_ERROR": "Erro de conexão", "OTHER": "Outro", **_FISCAL_LABELS})
|
||||
|
||||
|
||||
_REASON_LABELS = {
|
||||
@@ -143,7 +144,7 @@ _REASON_LABELS = {
|
||||
|
||||
|
||||
def blocker_reason_label(value):
|
||||
return _human_label(value, _REASON_LABELS)
|
||||
return _human_label(value, {**_REASON_LABELS, **_FISCAL_LABELS})
|
||||
|
||||
|
||||
def health_reason_label(value):
|
||||
@@ -160,12 +161,10 @@ def priority_label(value):
|
||||
return _human_label(value, {"CRITICAL": "Crítica", "HIGH": "Alta", "NORMAL": "Média", "MEDIUM": "Média", "LOW": "Baixa"})
|
||||
|
||||
|
||||
def expanded_scope_view(summary):
|
||||
"""Panel-ready view of the Agent's `expanded_scope` job summary (legacy and expanded coverage side by side). None when absent/unavailable."""
|
||||
if not isinstance(summary, dict) or "expanded_scope" not in summary:
|
||||
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)."""
|
||||
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
|
||||
legacy, expanded, total = summary.get("legacy_scope") or {}, summary["expanded_scope"] or {}, summary.get("total_products") or 0
|
||||
percent = lambda part: round(100 * part / total, 1) if total else None
|
||||
return {"total": total, "legacy": legacy, "expanded": expanded, "legacy_percent": percent(legacy.get("complete", 0)), "expanded_percent": percent(expanded.get("complete", 0)),
|
||||
"blockers": [{"type": key, "label": blocker_type_label(key), "count": count} for key, count in (summary.get("blockers_by_type") or {}).items()],
|
||||
"capabilities": summary.get("capabilities") or {}, "local_catalog_only": bool(summary.get("local_catalog_only"))}
|
||||
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"))}
|
||||
|
||||
@@ -0,0 +1,211 @@
|
||||
"""UNIFIED fiscal completeness: ONE definition of "complete" for a product (pure, no I/O).
|
||||
|
||||
mandatory(product, company, rtc) = base fiscal fields (decided elsewhere: NCM/CEST/CFOP/ICMS/IPI ...)
|
||||
+ PIS/COFINS when they APPLY to the product
|
||||
+ RTC (cClassTrib -> CST IBS/CBS) ONLY when the ERP has the tax reform switched on.
|
||||
|
||||
There is no "legacy scope" and no "expanded scope": every finding here becomes a normal blocker in the single queue and
|
||||
changes the single coverage number. Nothing here writes to the ERP or invents a fiscal value.
|
||||
|
||||
Sources (see docs/pis_cofins_applicability.md): SPED EFD-Contribuicoes table 4.3.3/4.3.4 (CST domain), 4.3.10-4.3.15 (special regimes),
|
||||
LC 123/2006 art. 13/18 (PIS/Cofins inside the Simples DAS), Leis 9.718/98, 10.637/2002, 10.833/2003 (cumulative / non-cumulative),
|
||||
NT 2025.002-RTC v1.51 (IBS/CBS fields), LC 214/2025 art. 348.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import re
|
||||
import unicodedata
|
||||
from datetime import date
|
||||
|
||||
# Official CST PIS / CST COFINS domain (SPED tables 4.3.3 / 4.3.4). Membership only - never used to pick a value.
|
||||
CST_DOMAIN = frozenset([f"{n:02d}" for n in (*range(1, 10), 49, *range(50, 57), *range(60, 68), *range(70, 76), 98, 99)])
|
||||
RATE_REQUIRED_CST = frozenset({"01", "02"}) # taxable by percentage: a rate is needed
|
||||
RATE_NOT_APPLICABLE_CST = frozenset({"03", "04", "05", "06", "07", "08", "09"}) # per-unit amount / monophasic / ST / zero / exempt / no incidence / suspended
|
||||
INCIDENCE_CST = {"MONOFASICA": frozenset({"02", "03", "04"}), "SUBSTITUICAO": frozenset({"05"})} # 4.3.10/4.3.11 (02,03,04) and 4.3.12 (05)
|
||||
SIMPLES_FAMILY = frozenset({"1", "2", "4"}) # CRT 1 Simples, 2 Simples excess sublimit, 4 MEI (LC 123/2006): PIS/Cofins are collected inside the DAS
|
||||
|
||||
PIS_COFINS_FIELDS = ("COD_ENTRADA_PIS", "COD_SAIDA_PIS", "ALIQ_PIS", "COD_ENTRADA_COFINS", "COD_SAIDA_COFINS", "ALIQ_COFINS")
|
||||
|
||||
FIELD_BY_BLOCKER_PREFIX = (("PIS", "PIS"), ("COFINS", "COFINS"), ("CCLASSTRIB", "RTC"), ("RTC", "RTC"))
|
||||
|
||||
|
||||
def _text(value) -> str:
|
||||
return "" if value is None else str(value).strip()
|
||||
|
||||
|
||||
def blocker_category(blocker_type: str) -> str:
|
||||
"""Dashboard category of a blocker type: PIS | COFINS | RTC | OTHER."""
|
||||
for prefix, category in FIELD_BY_BLOCKER_PREFIX:
|
||||
if blocker_type.startswith(prefix):
|
||||
return category
|
||||
return "OTHER"
|
||||
|
||||
|
||||
def incidence_kind(value) -> str:
|
||||
"""NONE | MONOFASICA | SUBSTITUICAO | UNKNOWN (accent/case-insensitive, encoding tolerant). Anything the SGV does not define is UNKNOWN, never NONE."""
|
||||
raw = _text(value)
|
||||
text = re.sub(r"[^A-Z]", "", unicodedata.normalize("NFKD", raw).encode("ascii", "ignore").decode().upper())
|
||||
if not raw or text == "NENHUMA":
|
||||
return "NONE"
|
||||
if text.startswith("MONOF"):
|
||||
return "MONOFASICA"
|
||||
if text.startswith("SUBST"):
|
||||
return "SUBSTITUICAO"
|
||||
return "UNKNOWN"
|
||||
|
||||
|
||||
def validate_cst(value) -> str:
|
||||
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
|
||||
|
||||
|
||||
def validate_rate(value) -> str:
|
||||
text = _text(value)
|
||||
if not text:
|
||||
return "EMPTY"
|
||||
if not re.fullmatch(r"\d{1,3}([.,]\d{1,4})?", text):
|
||||
return "INVALID"
|
||||
return "VALID" if float(text.replace(",", ".")) <= 100 else "INVALID"
|
||||
|
||||
|
||||
# ------------------------------------------------------------------------------------------------- PIS / COFINS
|
||||
|
||||
|
||||
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.
|
||||
"""
|
||||
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)
|
||||
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"}
|
||||
|
||||
|
||||
def evaluate_pis_cofins(product: dict, crt) -> dict:
|
||||
"""{"applicability": {...}, "blockers": [(type, reason)]}. Entry and exit are separate; a missing OPTIONAL value never blocks."""
|
||||
app = pis_cofins_applicability(product, crt)
|
||||
blockers: list[tuple[str, str]] = []
|
||||
if app["state"] in ("NOT_APPLICABLE", "NEEDS_REVIEW"):
|
||||
return {"applicability": app, "blockers": blockers}
|
||||
kind = incidence_kind(product.get("INCIDENCIA_PISCOFINS"))
|
||||
flag = _text(product.get("PISCOFINS")).upper()
|
||||
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 "")))
|
||||
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_)
|
||||
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":
|
||||
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):
|
||||
blockers.append(("PIS_COFINS_FLAG_INCONSISTENT", "FLAG_S_WITHOUT_INCIDENCE_OR_CODES"))
|
||||
return {"applicability": app, "blockers": _dedupe(blockers)}
|
||||
|
||||
|
||||
def _dedupe(items):
|
||||
seen, out = set(), []
|
||||
for item in items:
|
||||
if item not in seen:
|
||||
seen.add(item)
|
||||
out.append(item)
|
||||
return out
|
||||
|
||||
|
||||
# ------------------------------------------------------------------------------------------------- RTC
|
||||
|
||||
|
||||
def rtc_enabled(caps: dict) -> bool:
|
||||
"""RTC is in scope ONLY when the ERP is switched to the reform (PARAMETROS.USAR_REFORMA_TRIBUTARIA truthy) AND its schema can hold it."""
|
||||
return bool(caps.get("HAS_PRODUCT_CCLASSTRIB") and caps.get("HAS_RTC_RELATIONSHIP") and caps.get("RTC_ENABLED_BY_PARAM"))
|
||||
|
||||
|
||||
def catalog_integrity(catalog: dict) -> dict:
|
||||
"""catalog: {"classes": {cClassTrib: cst_or_None}, "cst": {code, ...}, "duplicates": int}."""
|
||||
classes, cst = catalog.get("classes", {}), set(catalog.get("cst", ()))
|
||||
bad_format = [c for c in classes if not re.fullmatch(r"\d{6}", str(c))]
|
||||
bad_cst_format = [c for c in cst if not re.fullmatch(r"\d{3}", str(c))]
|
||||
orphans = [c for c, s in classes.items() if s is None or str(s) not in cst]
|
||||
return {"classes": len(classes), "cst": len(cst), "orphan_classes": len(orphans), "bad_class_format": len(bad_format), "bad_cst_format": len(bad_cst_format),
|
||||
"duplicates": int(catalog.get("duplicates", 0)), "valid": bool(classes and cst and not orphans and not bad_format and not bad_cst_format and not catalog.get("duplicates"))}
|
||||
|
||||
|
||||
def validate_cclasstrib(value, catalog: dict | None, today: date | None = None) -> str:
|
||||
"""EMPTY | INVALID_FORMAT | NOT_IN_CATALOG | EXPIRED | CST_MISSING | VALID (CST IBS/CBS derived through the catalog, never stored on the product)."""
|
||||
code = _text(value)
|
||||
if not code:
|
||||
return "EMPTY"
|
||||
if not re.fullmatch(r"\d{6}", code):
|
||||
return "INVALID_FORMAT"
|
||||
if not catalog or code not in catalog.get("classes", {}):
|
||||
return "NOT_IN_CATALOG"
|
||||
validity = (catalog.get("validity") or {}).get(code)
|
||||
if validity and today is not None:
|
||||
start, end = validity
|
||||
if (start and start > today) or (end and end < today):
|
||||
return "EXPIRED"
|
||||
cst = catalog["classes"][code]
|
||||
return "VALID" if cst is not None and str(cst) in set(catalog.get("cst", ())) else "CST_MISSING"
|
||||
|
||||
|
||||
def evaluate_rtc(product: dict, rtc: dict | None, today: date | None = None) -> dict:
|
||||
"""rtc: {"enabled": bool, "catalog": {...}|None, "catalog_valid": bool}. Disabled => NOT_APPLICABLE and NO blocker, whatever CCLASSTRIB holds."""
|
||||
if not rtc or not rtc.get("enabled"):
|
||||
return {"applicability": {"state": "NOT_APPLICABLE", "basis": "RTC_DISABLED_IN_ERP"}, "blockers": []}
|
||||
app = {"state": "REQUIRED", "basis": "RTC_ENABLED_IN_ERP"}
|
||||
if not rtc.get("catalog_valid"):
|
||||
return {"applicability": app, "blockers": [("RTC_CATALOG_INVALID", "LOCAL_CATALOG_FAILED_INTEGRITY")]}
|
||||
state = validate_cclasstrib(product.get("CCLASSTRIB"), rtc.get("catalog"), today)
|
||||
mapping = {"EMPTY": "CCLASSTRIB_MISSING", "INVALID_FORMAT": "CCLASSTRIB_INVALID_FORMAT", "NOT_IN_CATALOG": "CCLASSTRIB_NOT_IN_CATALOG", "EXPIRED": "CCLASSTRIB_NOT_IN_CATALOG", "CST_MISSING": "RTC_CST_MISSING"}
|
||||
return {"applicability": app, "blockers": [(mapping[state], state)] if state in mapping else []}
|
||||
|
||||
|
||||
# ------------------------------------------------------------------------------------------------- unified
|
||||
|
||||
|
||||
def evaluate_product(product: dict, company: dict | None, rtc: dict | None, today: date | None = None) -> dict:
|
||||
"""The single fiscal-completeness verdict for the fields this module owns.
|
||||
Returns {"blockers": [{"type","category","reason"}], "applicability": {"PIS_COFINS": state, "RTC": state}, "mandatory_fields": [...], "needs_review": bool}."""
|
||||
crt = (company or {}).get("crt")
|
||||
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"]]
|
||||
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,
|
||||
"needs_review": pis["applicability"]["state"] == "NEEDS_REVIEW"}
|
||||
+2
-2
@@ -15,7 +15,7 @@ from .config import settings
|
||||
from .blocker_resolution import BlockerResolutionUnavailable, _company_key, resolve_blocker_with_context
|
||||
from .context_keys import ALLOWED_KEYS, ENUMS
|
||||
from .installation_identity import reference_agent_version
|
||||
from .panel_presentation import expanded_scope_view, job_kind_label, job_status_label
|
||||
from .panel_presentation import job_kind_label, job_status_label, rtc_status_view
|
||||
from .control_plane import blocker_detail, blocker_filter_summary, dashboard_summary, installation_view, jobs_status_summary, list_blockers, list_installations, list_jobs, list_panel_events
|
||||
from .human_auth import create_session, current_session, require_csrf, verify_password
|
||||
from .panel_presentation import format_ui_datetime
|
||||
@@ -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 {"expanded": expanded_scope_view(last_summary), "enabled": bool(installation.processing_enabled), "interval_minutes": installation.sync_interval_seconds // 60,
|
||||
return {"rtc": rtc_status_view(last_summary), "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}
|
||||
|
||||
|
||||
@@ -8,11 +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><dt>Último job</dt><dd>{% if ops.last_job %}<a href="/jobs?scope=all&job_id={{ ops.last_job.id }}">{{ ops.last_job.kind_label }}</a> · {{ ops.last_job.status_label }}{% else %}nenhum{% endif %}</dd></dl></section>
|
||||
{% if ops.expanded %}<section class="panel"><div class="panel-title"><div><h2>Cobertura fiscal</h2><p class="muted">Escopo anterior e escopo ampliado (cClassTrib e PIS/COFINS quando aplicáveis). Catálogo RTC local do ERP.</p></div></div>
|
||||
<dl class="facts"><dt>Escopo anterior</dt><dd>{{ ops.expanded.legacy.complete }} completos · {{ ops.expanded.legacy.safe_blocked }} bloqueados · {{ ops.expanded.legacy.failed }} falhas{% if ops.expanded.legacy_percent is not none %} · {{ ops.expanded.legacy_percent|string|replace(".", ",") }}%{% endif %}</dd>
|
||||
<dt>Escopo ampliado</dt><dd>{{ ops.expanded.expanded.complete }} completos · {{ ops.expanded.expanded.safe_blocked }} bloqueados · {{ ops.expanded.expanded.failed }} falhas · {{ ops.expanded.expanded.unknown_applicability }} com aplicabilidade desconhecida</dd>
|
||||
<dt>Novos bloqueios</dt><dd>{% for b in ops.expanded.blockers %}<span class="badge blocked">{{ b.label }} · {{ b.count }}</span> {% else %}nenhum{% endfor %}</dd></dl></section>{% 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&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 }}&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>
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
{% if reason_code_filter %}<input type="hidden" name="reason_code" value="{{ reason_code_filter }}">{% endif %}
|
||||
<label>Cliente<input name="client" value="{{ client_filter }}"></label>
|
||||
<label>Instalação / servidor<input name="installation" value="{{ installation_filter }}"></label>
|
||||
<label>Campo<select name="field"><option value="">Todos</option>{% for value in ["CFOP","ST","ICMS","CSOSN","NCM","OTHER"] %}<option value="{{ value }}" {% if field_filter==value %}selected{% endif %}>{{ value if value!="OTHER" else "Outros" }}</option>{% endfor %}</select></label>
|
||||
<label>Campo<select name="field"><option value="">Todos</option>{% for value in ["NCM","CEST","CFOP","ST","ICMS","CSOSN","PIS","COFINS","RTC","OTHER"] %}<option value="{{ value }}" {% if field_filter==value %}selected{% endif %}>{{ value if value!="OTHER" else "Outros" }}</option>{% endfor %}</select></label>
|
||||
<label>Tipo de blocker<input name="blocker_type" value="{{ blocker_type_filter }}" placeholder="Tipo técnico ou nome"></label>
|
||||
<label>Status<select name="status"><option value="OPEN" {% if status_filter=="OPEN" %}selected{% endif %}>Abertos</option><option value="RESOLVED" {% if status_filter=="RESOLVED" %}selected{% endif %}>Resolvidos</option><option value="ALL" {% if status_filter=="ALL" %}selected{% endif %}>Todos</option></select></label>
|
||||
<label>Visualização<select name="scope"><option value="active" {% if scope_filter=="active" %}selected{% endif %}>Operação ativa</option><option value="all" {% if scope_filter=="all" %}selected{% endif %}>Histórico e inativos</option></select></label>
|
||||
@@ -26,7 +26,7 @@
|
||||
</form>
|
||||
{% if field_filter or reason_code_filter or scope_filter=="all" or status_filter!="OPEN" %}<p class="active-filters" aria-live="polite">Filtros ativos: {% if scope_filter=="all" %}<span class="filter-chip">Histórico e inativos</span>{% endif %}{% if field_filter %}<span class="filter-chip">Campo: {{ field_filter }}</span>{% endif %}{% if reason_code_filter %}<span class="filter-chip">Motivo: {{ reason_code_filter }}</span>{% endif %}{% if status_filter!="OPEN" %}<span class="filter-chip">Status: {{ "Resolvidos" if status_filter=="RESOLVED" else "Todos" }}</span>{% endif %}</p>{% endif %}
|
||||
</section>
|
||||
<section class="panel"><div class="panel-title"><h2>Distribuição dos blockers filtrados</h2></div><div class="bar-grid">{% for key in ["CFOP","ST","ICMS","CSOSN","NCM","OTHER"] %}{% if summary.blocker_breakdown[key] > 0 %}<a class="bar-row bar-link" href="/blockers?{{ field_query_base|e }}&field={{ key }}"><span>{{ key }}</span><b>{{ summary.blocker_breakdown[key] }}</b><span aria-hidden="true">→</span></a>{% else %}<div class="bar-row muted"><span>{{ key }}</span><b>0</b></div>{% endif %}{% endfor %}</div></section>
|
||||
<section class="panel"><div class="panel-title"><h2>Distribuição dos blockers filtrados</h2></div><div class="bar-grid">{% for key in ["NCM","CEST","CFOP","ST","ICMS","CSOSN","PIS","COFINS","RTC","OTHER"] %}{% if summary.blocker_breakdown[key] > 0 %}<a class="bar-row bar-link" href="/blockers?{{ field_query_base|e }}&field={{ key }}"><span>{{ key }}</span><b>{{ summary.blocker_breakdown[key] }}</b><span aria-hidden="true">→</span></a>{% else %}<div class="bar-row muted"><span>{{ key }}</span><b>0</b></div>{% endif %}{% endfor %}</div></section>
|
||||
<div class="panel table-wrap blockers-table-wrap"><table class="blockers-table"><caption>Fila de blockers · {{ result.total }} resultados</caption><thead><tr><th>Cliente / servidor</th><th>Produto</th><th>Campo / tipo</th><th>Prioridade</th><th>Problema</th><th>Última ocorrência</th><th>Ação</th></tr></thead><tbody>
|
||||
{% for item in result["items"] %}<tr><td data-label="Cliente / servidor"><b>{{ item.client or "Cliente não identificado" }}</b><small>{{ item.hostname or item.display_name or "Instalação não informada" }}</small></td><td data-label="Produto"><a class="row-primary-link" href="{{ item.link }}"><b>{{ item.product_code }}</b><small>{{ item.description or "Sem descrição" }}</small></a></td><td data-label="Campo / tipo">{{ item.field or "Outros" }}<small>{{ item.blocker_type_label }}</small></td><td data-label="Prioridade"><span class="badge priority-{{ item.priority|lower }}">{{ item.priority_label }}</span></td><td data-label="Problema">{{ item.reason_label }}<small>{{ item.age }}</small></td><td data-label="Última ocorrência" title="{{ item.last_seen_at }}">{{ item.last_seen_display }}<small>{{ item.retry_count }} tentativas</small></td><td data-label="Ação"><a class="secondary" href="{{ item.link }}">Ver detalhes</a></td></tr>{% else %}<tr><td colspan="7" class="empty-state">Nenhum blocker corresponde aos filtros. <a href="/blockers?scope=all&status=ALL">Consultar histórico</a></td></tr>{% endfor %}
|
||||
</tbody></table></div>
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
</article>
|
||||
<article class="panel">
|
||||
<div class="panel-title"><h2>Breakdown dos blockers</h2><a href="/blockers?scope=active&status=OPEN">Ver blockers</a></div>
|
||||
{% for key in ["CFOP","ST","ICMS","CSOSN","NCM","OTHER"] %}
|
||||
{% for key in ["NCM","CEST","CFOP","ST","ICMS","CSOSN","PIS","COFINS","RTC","OTHER"] %}
|
||||
{% if summary.open_blocker_breakdown[key] > 0 %}<a class="bar-row bar-link" href="/blockers?scope=active&status=OPEN&field={{ key }}" aria-label="{{ key }}: {{ summary.open_blocker_breakdown[key] }} blockers abertos"><span>{{ key }}</span><b>{{ summary.open_blocker_breakdown[key] }}</b><span aria-hidden="true">→</span></a>
|
||||
{% else %}<div class="bar-row muted"><span>{{ key }}</span><b>0</b></div>{% endif %}
|
||||
{% endfor %}
|
||||
|
||||
@@ -536,3 +536,42 @@ def test_agent_detail_processing_hierarchy_and_status_badges():
|
||||
assert "Mantém o heartbeat, mas não executa jobs" in page and "Recalcula todos os produtos" in page
|
||||
assert "Saúde técnica: " not in page and "Status fiscal: " not in page # context lives in the small label, not in the badge
|
||||
assert '<small class="status-label">Saúde técnica</small>' in page
|
||||
|
||||
|
||||
def _complete_sync(client, agent_id, fiscal_context):
|
||||
with client._session_factory() as session:
|
||||
session.add(Job(agent_id=agent_id, kind="SYNC_INCREMENTAL", status="COMPLETED", payload={}, result_summary={"fiscal_context": fiscal_context}, created_at=datetime(2026, 9, 22, 19, 0, tzinfo=timezone.utc),
|
||||
finished_at=datetime(2026, 9, 22, 19, 5, tzinfo=timezone.utc)))
|
||||
session.commit()
|
||||
|
||||
|
||||
def test_agent_detail_has_one_coverage_and_shows_rtc_status_only_when_reported():
|
||||
client, agent_id, _ = _panel_client()
|
||||
plain = client.get(f"/agents/{agent_id}").text
|
||||
assert "Reforma Tributária" not in plain # older Agents do not report it
|
||||
for text in (plain,):
|
||||
assert "Escopo anterior" not in text and "Escopo ampliado" not in text and "aplicabilidade desconhecida" not in text
|
||||
_complete_sync(client, agent_id, {"rtc_enabled": False, "rtc_supported": True, "rtc_catalog_valid": True})
|
||||
off = client.get(f"/agents/{agent_id}").text
|
||||
assert re.search(r"Reforma Tributária</dt><dd><span[^>]*>Desativada</span>", off) and "Escopo anterior" not in off and "Escopo ampliado" not in off
|
||||
assert "cClassTrib" not in off # RTC off never surfaces RTC as a problem
|
||||
_complete_sync(client, agent_id, {"rtc_enabled": True, "rtc_supported": True, "rtc_catalog_valid": True})
|
||||
on = client.get(f"/agents/{agent_id}").text
|
||||
assert re.search(r"Reforma Tributária</dt><dd><span[^>]*>Ativa</span>", on)
|
||||
|
||||
|
||||
def test_pis_cofins_and_rtc_blockers_appear_in_the_same_queue_and_dashboard_breakdown():
|
||||
client, agent_id, _ = _panel_client()
|
||||
with client._session_factory() as session:
|
||||
installation = session.scalar(select(Installation).where(Installation.agent_id == agent_id))
|
||||
for code, field, reason in (("P-010", "PIS", "PIS_CST_INVALID"), ("P-011", "COFINS", "COFINS_CST_MISSING"), ("P-012", "RTC", "CCLASSTRIB_MISSING")):
|
||||
upsert_blocker(session, installation.tenant_id, {"company": "Acme", "product_code": code, "description": "x", "blocker_type": "BLOCKED_FISCAL_DATA", "field": field, "reason_code": reason, "input_fingerprint": code},
|
||||
installation_id=installation.id)
|
||||
dash = client.get("/").text
|
||||
for key in ("PIS", "COFINS", "RTC", "CEST"):
|
||||
assert f"<span>{key}</span>" in dash
|
||||
queue = client.get("/blockers?scope=active&status=OPEN").text
|
||||
assert "CST PIS inválido" in queue and "CST COFINS ausente" in queue and "cClassTrib ausente" in queue
|
||||
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
|
||||
|
||||
@@ -108,7 +108,7 @@ def test_dashboard_reconciles_five_health_states_and_counts_distinct_safe_produc
|
||||
assert summary["agents_total"] == sum(summary["health_counts"].values())
|
||||
assert summary["safe_blocked_products"] == 2
|
||||
assert summary["open_blockers"] == 3
|
||||
assert summary["open_blocker_breakdown"] == {"CFOP": 1, "ST": 0, "ICMS": 1, "CSOSN": 0, "NCM": 1, "OTHER": 0}
|
||||
assert summary["open_blocker_breakdown"] == {"NCM": 1, "CEST": 0, "CFOP": 1, "ST": 0, "ICMS": 1, "CSOSN": 0, "PIS": 0, "COFINS": 0, "RTC": 0, "OTHER": 0}
|
||||
assert summary["online"] == 1 and summary["safe_blocked"] == 2
|
||||
|
||||
|
||||
@@ -576,12 +576,11 @@ def test_remaining_technical_enums_have_human_labels():
|
||||
assert blocker_reason_label("SOMETHING_NEW") == "Something new" # unknown codes degrade readably, never crash
|
||||
|
||||
|
||||
def test_expanded_scope_view_labels_and_absence():
|
||||
from central.app.panel_presentation import expanded_scope_view
|
||||
assert expanded_scope_view(None) is None and expanded_scope_view({"coverage": {}}) is None
|
||||
view = expanded_scope_view({"total_products": 10, "legacy_scope": {"complete": 8, "safe_blocked": 2, "failed": 0},
|
||||
"expanded_scope": {"complete": 1, "safe_blocked": 3, "failed": 0, "unknown_applicability": 6},
|
||||
"blockers_by_type": {"PIS_CST_INVALID": 1, "CCLASSTRIB_MISSING": 2}, "local_catalog_only": True})
|
||||
assert view["legacy_percent"] == 80.0 and view["expanded_percent"] == 10.0 and view["local_catalog_only"]
|
||||
assert {b["label"] for b in view["blockers"]} == {"CST PIS inválido", "cClassTrib ausente"}
|
||||
assert blocker_type_label("RTC_CST_INVALID") == "CST IBS/CBS inválido" and blocker_type_label("COFINS_RATE_INVALID") == "Alíquota COFINS inválida"
|
||||
def test_rtc_status_view_is_discreet_and_optional():
|
||||
from central.app.panel_presentation import rtc_status_view
|
||||
assert rtc_status_view(None) is None and rtc_status_view({"coverage": {}}) is None
|
||||
assert rtc_status_view({"fiscal_context": {"rtc_enabled": False}})["label"] == "Desativada"
|
||||
on = rtc_status_view({"fiscal_context": {"rtc_enabled": True, "rtc_supported": True, "rtc_catalog_valid": True}})
|
||||
assert on["label"] == "Ativa" and on["enabled"] and on["catalog_valid"]
|
||||
assert blocker_type_label("RTC_CST_MISSING") == "CST IBS/CBS ausente no catálogo" and blocker_type_label("COFINS_RATE_INVALID") == "Alíquota COFINS inválida"
|
||||
assert blocker_type_label("PIS_CST_INCIDENCE_MISMATCH") == "CST PIS incompatível com a incidência"
|
||||
|
||||
@@ -0,0 +1,131 @@
|
||||
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)
|
||||
|
||||
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"}
|
||||
|
||||
|
||||
def kinds(result):
|
||||
return {b["type"] for b in result["blockers"]}
|
||||
|
||||
|
||||
# ---------------------------------------------------------------- RTC on / off
|
||||
def test_rtc_off_ignores_cclasstrib_completely():
|
||||
for value in (None, "", "12x", "999999", "000001"):
|
||||
r = evaluate_product({**EMPTY, "CCLASSTRIB": value}, SIMPLES, OFF)
|
||||
assert r["blockers"] == [] and r["applicability"]["RTC"] == "NOT_APPLICABLE" and "CCLASSTRIB" not in r["mandatory_fields"]
|
||||
assert evaluate_product(EMPTY, SIMPLES, None)["blockers"] == []
|
||||
|
||||
|
||||
def test_rtc_on_requires_and_validates_cclasstrib():
|
||||
assert kinds(evaluate_product({**EMPTY, "CCLASSTRIB": None}, SIMPLES, ON)) == {"CCLASSTRIB_MISSING"}
|
||||
assert kinds(evaluate_product({**EMPTY, "CCLASSTRIB": "12x"}, SIMPLES, ON)) == {"CCLASSTRIB_INVALID_FORMAT"}
|
||||
assert kinds(evaluate_product({**EMPTY, "CCLASSTRIB": "123456"}, SIMPLES, ON)) == {"CCLASSTRIB_NOT_IN_CATALOG"}
|
||||
assert kinds(evaluate_product({**EMPTY, "CCLASSTRIB": "999999"}, SIMPLES, ON)) == {"RTC_CST_MISSING"} # class without CST
|
||||
assert kinds(evaluate_product({**EMPTY, "CCLASSTRIB": "555555"}, SIMPLES, ON)) == {"RTC_CST_MISSING"} # class points to a CST that is not in RTC_CST (orphan)
|
||||
ok = evaluate_product({**EMPTY, "CCLASSTRIB": "200003"}, SIMPLES, ON)
|
||||
assert ok["blockers"] == [] and ok["applicability"]["RTC"] == "REQUIRED" and "CCLASSTRIB" in ok["mandatory_fields"]
|
||||
|
||||
|
||||
def test_rtc_on_with_invalid_catalog_blocks_with_a_single_clear_reason():
|
||||
r = evaluate_product({**EMPTY, "CCLASSTRIB": "200003"}, SIMPLES, {"enabled": True, "catalog": CATALOG, "catalog_valid": False})
|
||||
assert kinds(r) == {"RTC_CATALOG_INVALID"}
|
||||
|
||||
|
||||
def test_rtc_vigencia_is_used_only_when_the_catalog_carries_it():
|
||||
cat = {**CATALOG, "validity": {"200003": (date(2027, 1, 1), None), "410004": (date(2025, 1, 1), date(2025, 12, 31))}}
|
||||
assert validate_cclasstrib("200003", cat, date(2026, 9, 24)) == "EXPIRED" and validate_cclasstrib("410004", cat, date(2026, 9, 24)) == "EXPIRED"
|
||||
assert validate_cclasstrib("000001", cat, date(2026, 9, 24)) == "VALID" and validate_cclasstrib("200003", CATALOG, date(2026, 9, 24)) == "VALID"
|
||||
|
||||
|
||||
def test_rtc_enabled_needs_schema_and_erp_switch():
|
||||
caps = {"HAS_PRODUCT_CCLASSTRIB": True, "HAS_RTC_RELATIONSHIP": True, "RTC_ENABLED_BY_PARAM": True}
|
||||
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_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)
|
||||
assert r["blockers"] == [] and r["applicability"]["PIS_COFINS"] == "REQUIRED" and set(r["mandatory_fields"]) == {"COD_SAIDA_PIS", "COD_SAIDA_COFINS"}
|
||||
assert kinds(evaluate_product({**good, "COD_SAIDA_PIS": None}, SIMPLES, OFF)) == {"PIS_CST_MISSING"}
|
||||
assert kinds(evaluate_product({**good, "COD_SAIDA_COFINS": "01", "ALIQ_COFINS": "7,6"}, SIMPLES, OFF)) == {"COFINS_CST_INCIDENCE_MISMATCH"}
|
||||
|
||||
|
||||
def test_st_incidence_with_taxable_cst_is_a_mismatch_and_pis_cofins_are_independent():
|
||||
row = {**EMPTY, "PISCOFINS": "S", "INCIDENCIA_PISCOFINS": "SUBST.TRIBUTÁRIA", "COD_ENTRADA_PIS": "73", "COD_SAIDA_PIS": "01", "ALIQ_PIS": "0,65", "COD_ENTRADA_COFINS": "73", "COD_SAIDA_COFINS": "05"}
|
||||
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"}
|
||||
assert kinds(evaluate_product({**base, "COD_SAIDA_PIS": "01", "ALIQ_PIS": "1,65", "COD_SAIDA_COFINS": "01", "ALIQ_COFINS": "7,6"}, NORMAL, OFF)) == set()
|
||||
assert kinds(evaluate_product({**base, "COD_SAIDA_PIS": "49", "COD_SAIDA_COFINS": "49"}, SIMPLES, OFF)) == set()
|
||||
assert kinds(evaluate_product({**base, "COD_SAIDA_PIS": "49", "ALIQ_PIS": "abc", "COD_SAIDA_COFINS": "49"}, SIMPLES, OFF)) == {"PIS_RATE_INVALID"}
|
||||
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():
|
||||
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"
|
||||
|
||||
|
||||
# ---------------------------------------------------------------- ONE definition of complete, ONE queue
|
||||
def test_blocker_categories_feed_the_single_dashboard_breakdown():
|
||||
assert {blocker_category(t) for t in ("PIS_CST_INVALID", "PIS_COFINS_FLAG_INCONSISTENT")} == {"PIS"} and blocker_category("COFINS_RATE_MISSING") == "COFINS"
|
||||
assert {blocker_category(t) for t in ("CCLASSTRIB_MISSING", "RTC_CST_MISSING", "RTC_CATALOG_INVALID")} == {"RTC"} and blocker_category("XYZ") == "OTHER"
|
||||
|
||||
|
||||
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": "???"}]
|
||||
|
||||
def run(rtc):
|
||||
states = [classify_product_state(p, {"status": "EXACT_NCM"}, fiscal=evaluate_product(p, SIMPLES, rtc)) for p in products]
|
||||
return summarize_operational_states(states)
|
||||
off, on = run(OFF), run(ON)
|
||||
for s in (off, on):
|
||||
assert s["TOTAL_PRODUCTS"] == s["COMPLETE_PRODUCTS"] + s["SAFE_BLOCKED"] + s["FAILED_TECHNICAL"] + s["UNCLASSIFIED"] == 4
|
||||
assert off["COMPLETE_PRODUCTS"] == 2 and off["BLOCKED_FISCAL_DATA"] == 1 and off["UNCLASSIFIED"] == 1 # RTC off: cClassTrib never counts
|
||||
assert on["COMPLETE_PRODUCTS"] == 1 and on["BLOCKED_FISCAL_DATA"] == 3 and on["UNCLASSIFIED"] == 0 # RTC on: only product 3 has a valid cClassTrib; a real blocker outranks NEEDS_REVIEW
|
||||
|
||||
|
||||
def test_fiscal_blockers_never_hide_or_override_a_decision_blocker():
|
||||
fiscal = evaluate_product({**EMPTY, "COD_SAIDA_PIS": "2"}, 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
|
||||
@@ -194,3 +194,45 @@ def test_panel_controls_require_login():
|
||||
anon = TestClient(create_app(database_probe=lambda: True, session_factory=factory, bootstrap_token=None))
|
||||
for action in ("sync-now", "pause", "resume"):
|
||||
assert anon.post(f"/agents/{agent_id}/{action}", data={}, follow_redirects=False).status_code == 303 # bounced to login, nothing executed
|
||||
|
||||
|
||||
def _fiscal_blocker(code, field, reason, fp):
|
||||
return {"company": "", "product_code": code, "gtin": None, "description": "P" + code, "blocker_type": "BLOCKED_FISCAL_DATA", "field": field, "reason_code": reason, "evidence_summary": {}, "input_fingerprint": fp}
|
||||
|
||||
|
||||
def test_fiscal_blockers_share_the_single_queue_and_are_resolved_by_the_next_full_pass():
|
||||
"""PIS/COFINS/RTC blockers are ordinary blockers; when a later FULL pass no longer reports them (data fixed / RTC switched off) they close automatically."""
|
||||
from central.app.models import BlockedProduct
|
||||
client, engine = _client()
|
||||
hb(client)
|
||||
operational = {**OPERATIONAL, "COMPLETE": 519, "BLOCKED_MISSING_EVIDENCE": 0, "BLOCKED_CONFLICT": 0, "BLOCKED_FISCAL_DATA": 2, "SAFE_BLOCKED": 2, "TOTAL_PRODUCTS": 521}
|
||||
first = {"operational": operational, "blocked_products": [_fiscal_blocker("10", "PIS", "PIS_CST_INVALID", "a"), _fiscal_blocker("11", "RTC", "CCLASSTRIB_MISSING", "b")], "resolved_products": [],
|
||||
"fiscal_context": {"rtc_enabled": True, "evaluated_all": True}}
|
||||
finish(client, engine, first)
|
||||
with Session(engine) as s:
|
||||
rows = s.scalars(select(BlockedProduct).where(BlockedProduct.resolved_at.is_(None))).all()
|
||||
assert sorted((r.product_code, r.field) for r in rows) == [("10", "PIS"), ("11", "RTC")]
|
||||
snap = s.scalars(select(AgentSnapshot)).one()
|
||||
assert (snap.fiscally_complete, snap.safe_blocked, snap.total_products) == (519, 2, 521)
|
||||
age_last_sync(engine, 4000)
|
||||
hb(client)
|
||||
second = {"operational": {**OPERATIONAL, "COMPLETE": 520, "BLOCKED_MISSING_EVIDENCE": 0, "BLOCKED_CONFLICT": 0, "BLOCKED_FISCAL_DATA": 1, "SAFE_BLOCKED": 1},
|
||||
"blocked_products": [_fiscal_blocker("10", "PIS", "PIS_CST_INVALID", "a")], "resolved_products": [], "fiscal_context": {"rtc_enabled": False, "evaluated_all": True}}
|
||||
finish(client, engine, second)
|
||||
with Session(engine) as s:
|
||||
open_rows = s.scalars(select(BlockedProduct).where(BlockedProduct.resolved_at.is_(None))).all()
|
||||
assert [(r.product_code, r.field) for r in open_rows] == [("10", "PIS")] # RTC blocker closed because RTC is off now
|
||||
closed = s.scalars(select(BlockedProduct).where(BlockedProduct.resolved_at.is_not(None))).all()
|
||||
assert [(r.product_code, r.resolution_source) for r in closed] == [("11", "AUTO_REEVALUATION")]
|
||||
|
||||
|
||||
def test_partial_reprocess_never_closes_fiscal_blockers_of_other_products():
|
||||
from central.app.models import BlockedProduct
|
||||
client, engine = _client()
|
||||
hb(client)
|
||||
finish(client, engine, {"operational": OPERATIONAL, "blocked_products": [_fiscal_blocker("10", "PIS", "PIS_CST_INVALID", "a")], "resolved_products": [], "fiscal_context": {"evaluated_all": True}})
|
||||
age_last_sync(engine, 4000)
|
||||
hb(client)
|
||||
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
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
# 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`).
|
||||
|
||||
## 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.
|
||||
|
||||
## 2. Modelo de aplicabilidade (por produto)
|
||||
|
||||
| Estado | Condição (dados do próprio produto) | 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" |
|
||||
|
||||
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 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.
|
||||
|
||||
| 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_MISSING` | produto `REQUIRED` sem CST de saída |
|
||||
| `{PIS,COFINS}_RATE_MISSING` | CST de saída 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_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).
|
||||
|
||||
## 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. 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).
|
||||
@@ -1,60 +1,42 @@
|
||||
# Modelo RTC + PIS/COFINS no SGV (evidência: TESTESOFICIAIS.FDB)
|
||||
# Reforma Tributária (RTC) no cadastro de produto — modelo condicional
|
||||
|
||||
Tudo abaixo é observação read-only. Nenhum valor é preenchido, nenhum write é habilitado (`FIREBIRD_WRITES=0`).
|
||||
Código: `agent/src/gti_fiscal_agent/rtc_pis_cofins.py` (capabilities, validadores, aplicabilidade, cobertura ampliada),
|
||||
integrado ao job de sync do Agent (`expanded_scope` no `result_summary`) e exibido no detalhe do Agent.
|
||||
Substitui o modelo anterior de "escopo ampliado". Não existe segundo escopo, segundo painel ou segunda fila: RTC é **uma condição** da definição única de completude (`docs/unified_fiscal_completeness.md`).
|
||||
|
||||
## 1. Modelo real do RTC
|
||||
## Regra definitiva
|
||||
`RTC_ENABLED = PARAMETROS.USAR_REFORMA_TRIBUTARIA truthy (1/S/T/TRUE) E o schema comporta RTC`
|
||||
(`C000025.CCLASSTRIB`, `RTC_CCLASSTRIB(CCLASSTRIB, CSTIBSCBS)`, `RTC_CST(CSTIBSCBS)`).
|
||||
|
||||
`C000025.CCLASSTRIB` (VARCHAR(6)) → `RTC_CCLASSTRIB.CCLASSTRIB` (CHAR(6), PK) → `RTC_CCLASSTRIB.CSTIBSCBS` (CHAR(3)) → `RTC_CST.CSTIBSCBS` (CHAR(3), PK).
|
||||
- `RTC_ENABLED=false` (parâmetro 0, nulo, ausente ou schema antigo): `CCLASSTRIB` e CST IBS/CBS **não entram** na completude; nenhum blocker RTC; o painel só mostra "Reforma Tributária: Desativada".
|
||||
- `RTC_ENABLED=true`: RTC passa a ser obrigatório para todo produto.
|
||||
|
||||
- **Há FK física** nos dois elos: `FK_C000025_CCLASSTRIB` (RESTRICT/RESTRICT) e `FK_RTC_CCLASSTRIB_CST`.
|
||||
- Catálogo local: 145 cClassTrib (6 dígitos, únicos, 0 duplicados, 0 órfãos) para 18 CST IBS/CBS (3 dígitos); nenhum registro com `FIM_VIGENCIA`; `INICIO_VIGENCIA` de 2025-05-05 a 2025-11-19. Cada CST tem ao menos um cClassTrib.
|
||||
- Atributos do catálogo (não são valores do produto): `TIPO_ALIQUOTA`, `PRED_IBS`/`PRED_CBS` (redução), `IND_TRIB_REGULAR`, `IND_CREDPRES_OPER`, `IND_ESTORNO_CRED`, monofasia, `IND_NFE/NFCE/...` (documentos aplicáveis), `ANEXO`, `LINK`. `RTC_CST` descreve o CST (`IND_IBSCBS`, `IND_REDBC`, `IND_REDALIQ`, `IND_DIF`, `IND_CREDPRES_IBSZFM`...).
|
||||
- **Não existem no produto**: CST IBS/CBS, alíquotas IBS/CBS, cCredPres. O CST IBS/CBS só se obtém **do catálogo, a partir do cClassTrib informado**.
|
||||
- `PARAMETROS.USAR_REFORMA_TRIBUTARIA = 0`: o ERP não usa RTC neste banco. Documentos (`C000062`, `C000032`, `C000088`, `NFCE_ITENS`) não têm campos RTC.
|
||||
- `LOCAL_CATALOG_ONLY=true`: comparamos apenas com o catálogo do próprio banco. Não há dataset oficial integrado; o ponto de extensão é `catalog` em `expanded_scope_summary`/`validate_cclasstrib`.
|
||||
Não há estado "desconhecido": um parâmetro ilegível conta como não habilitado pelo ERP (fail-closed para *escopo*, sem inflar cobertura porque o campo não é exigido).
|
||||
|
||||
## 2. PIS/COFINS no produto (`C000025`)
|
||||
## Validação com RTC ativa
|
||||
`C000025.CCLASSTRIB → RTC_CCLASSTRIB.CSTIBSCBS → RTC_CST` (o CST IBS/CBS **não** é coluna do produto; é derivado pela relação).
|
||||
|
||||
| Campo | Tipo | Semântica observada |
|
||||
|---|---|---|
|
||||
| `COD_ENTRADA_PIS` / `COD_ENTRADA_COFINS` | VARCHAR(3) | CST de entrada (ex.: `73`); 2/521 preenchidos |
|
||||
| `COD_SAIDA_PIS` / `COD_SAIDA_COFINS` | VARCHAR(3) | CST de saída (ex.: `01`, `04`); 3/521 |
|
||||
| `ALIQ_PIS` / `ALIQ_COFINS` | VARCHAR(8) | percentual **textual** com vírgula (`0,65`, `3`); 2 não vazios |
|
||||
| `PISCOFINS` | VARCHAR(1) | flag S/N (N=519, S=2) |
|
||||
| `INCIDENCIA_PISCOFINS` | VARCHAR(30) | regime adicional: NENHUMA=519, MONOFÁSICA=1, SUBST.TRIBUTÁRIA=1 (coerente com o flag) |
|
||||
| Blocker (PT-BR) | Condição |
|
||||
|---|---|
|
||||
| `CCLASSTRIB_MISSING` cClassTrib ausente | vazio |
|
||||
| `CCLASSTRIB_INVALID_FORMAT` cClassTrib com formato inválido | não tem exatamente 6 dígitos |
|
||||
| `CCLASSTRIB_NOT_IN_CATALOG` cClassTrib fora do catálogo local | inexistente em `RTC_CCLASSTRIB`, ou fora da vigência (`INICIO_VIGENCIA` futura / `FIM_VIGENCIA` passada — colunas do próprio catálogo) |
|
||||
| `RTC_CST_MISSING` CST IBS/CBS ausente no catálogo | a classe não aponta CST ou o CST não existe em `RTC_CST` |
|
||||
| `RTC_CATALOG_INVALID` Catálogo RTC local inválido | catálogo com órfãos, duplicatas ou formatos inválidos (todo produto, enquanto RTC ativa) |
|
||||
|
||||
- CST oficial PIS/COFINS tem 2 dígitos; um produto traz `2` em todos os campos (formato inválido, aparenta dado de teste) e é o único com flag inconsistente (`N` com códigos).
|
||||
- Não existe catálogo PIS/COFINS no SGV (nenhuma tabela com PIS/COFINS/CST no nome além de `RTC_CST`). Os códigos são texto livre.
|
||||
- **Produto × documento.** `C000062` (itens de saída) grava `COD_*_PIS/COFINS` e alíquotas por item (7 de 693 com `20`/`41`/`12`); `NFCE_ITENS.CST_PIS_COFINS` tem `01` em 3.336 itens, inclusive de produtos com `INCIDENCIA=NENHUMA` e sem código no produto. Conclusão: o documento **não** depende do cadastro do produto para o CST. `PRODUCT_DEFAULT_MODEL` = campos esparsos em `C000025` (2–3 produtos); `DOCUMENT_ITEM_MODEL` = campos por item; `CANONICAL_SOURCE_FOR_PRODUCT_DEFAULT` = `C000025` somente onde preenchido, sem fonte canônica para os demais.
|
||||
Integridade do catálogo: sem órfãos (classe → CST inexistente), sem duplicatas, classes com 6 dígitos, CST com 3 dígitos. TESTESOFICIAIS: 145 classes, 18 CSTs, 0 órfãos, 0 duplicatas.
|
||||
|
||||
## 3. Aplicabilidade (conservadora)
|
||||
## O que NÃO é validado
|
||||
O schema do produto não modela alíquota IBS/CBS, valor IBS/CBS nem `cCredPres`. Não existem blockers desses campos e nenhum write target foi inventado. A validação do produto reflete só o que o SGV guarda como default: `CCLASSTRIB`.
|
||||
|
||||
Quatro coisas separadas: SCHEMA_SUPPORT, DATA_PRESENT, APPLICABILITY, MANDATORYNESS. Só a última bloqueia, e só com aplicabilidade provada por dado local.
|
||||
## Fontes (ACCESSED_AT 2026-09-24)
|
||||
- NT 2025.002-RTC v1.51, Portal Nacional da NF-e (publicada 04/08/2026): campos IBS/CBS (grupo UB) obrigatórios em 2026; para emitentes CRT 1/2/4 as orientações "serão publicadas em NT futura" porque a tributação IBS/CBS/IS desses contribuintes ocorre a partir de 2027 (art. 348 da LC 214/2025).
|
||||
- LC 214/2025 (Planalto, compilada): art. 348 (2026: IBS/CBS compensados com PIS/Cofins; alíquotas de teste).
|
||||
- Catálogo local do ERP (`RTC_CCLASSTRIB`/`RTC_CST`, `PUBLICACAO`/`INICIO_VIGENCIA`) e cClassTrib oficial no Hub (`cClassTrib 2026-06-22.xlsx`).
|
||||
- `PARAMETROS.USAR_REFORMA_TRIBUTARIA`: parâmetro do próprio SGV (executável 5.1.0.4 o lê; o 5.0.6.124 não tem RTC).
|
||||
|
||||
- **RTC**: `NOT_SUPPORTED` (schema sem o modelo) · `NOT_ENABLED_BY_ERP` (parâmetro do ERP = 0; não bloqueia nem conta como desconhecido) · `UNKNOWN_APPLICABILITY` (parâmetro ilegível) · `APPLICABLE` (parâmetro = 1 e modelo existe → cClassTrib passa a ser exigido/validado).
|
||||
- **PIS/COFINS**: `APPLICABLE` só quando o próprio produto traz evidência (algum código/alíquota, `PISCOFINS='S'` ou incidência especial). Produto vazio com `N`/`NENHUMA` é `UNKNOWN_APPLICABILITY`: não é completo nem bloqueado.
|
||||
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.
|
||||
|
||||
## 4. Validadores (determinísticos, sem preenchimento)
|
||||
## 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`).
|
||||
|
||||
- cClassTrib: `EMPTY`, `INVALID_FORMAT` (≠ 6 dígitos), `NOT_IN_LOCAL_CATALOG`, `VALID_LOCAL`, `CST_MISSING`, `VALID_WITH_CST`.
|
||||
- CST PIS/COFINS: `EMPTY`, `VALID`, `INVALID_FORMAT` (fora do conjunto oficial 01–09, 49, 50–56, 60–67, 70–75, 98, 99). Alíquota: `EMPTY`, `VALID` (0–100, vírgula ou ponto), `INVALID_RATE`. Flag: `CONSISTENT`/`INCONSISTENT`/`EMPTY`.
|
||||
- Blockers específicos (rótulos PT-BR no painel): `CCLASSTRIB_MISSING/INVALID/NOT_IN_CATALOG`, `RTC_CST_INVALID`, `PIS|COFINS_CST_MISSING/INVALID`, `PIS|COFINS_RATE_INVALID`, `PIS_COFINS_FLAG_INCONSISTENT`. Eles ficam em `expanded_scope.blockers_by_type`, **separados** dos blockers legados (não alteram o invariante 495+26+0=521 do escopo anterior).
|
||||
|
||||
## 5. Cobertura: legado × ampliada
|
||||
|
||||
`legacy_scope` (LEGACY_SCOPE_COVERAGE) é preservado sem alteração. `expanded_scope` (EXPANDED_FISCAL_SCOPE_COVERAGE) só **acrescenta** condições: bloqueado/falha legado continua assim; completo legado vira completo ampliado, bloqueado (blocker específico) ou `unknown_applicability`. Invariante: soma = total.
|
||||
|
||||
Resultado no TESTESOFICIAIS (521 produtos): legado 495 completos / 26 bloqueados / 0 falhas; ampliado 2 completos / 27 bloqueados / 0 falhas / 492 com aplicabilidade desconhecida; blockers novos: `PIS_CST_INVALID` 2, `COFINS_CST_INVALID` 2, `PIS_COFINS_FLAG_INCONSISTENT` 1 (mesmo produto). Os 492 desconhecidos **não** são defeitos: faltam evidências para dizer se o cadastro do produto deve trazer CST de PIS/COFINS.
|
||||
|
||||
## 6. Limitações
|
||||
|
||||
- Sem catálogo oficial RTC nem regra tributária: nenhuma correspondência é inventada.
|
||||
- A aplicabilidade de PIS/COFINS por regime (CRT 1 aqui) não está determinada por dado local confiável.
|
||||
- Vigência do cClassTrib não é usada como bloqueio (o catálogo local não tem `FIM_VIGENCIA`).
|
||||
|
||||
## 7. Campos candidatos a write futuro (NÃO autorizado)
|
||||
|
||||
`CANDIDATE`, sem write habilitado e sem autorização: `C000025.CCLASSTRIB`, `COD_ENTRADA_PIS`, `COD_SAIDA_PIS`, `ALIQ_PIS`, `COD_ENTRADA_COFINS`, `COD_SAIDA_COFINS`, `ALIQ_COFINS`.
|
||||
Não devem ser escritos: `RTC_CCLASSTRIB`, `RTC_CST` (catálogos), `PARAMETROS.USAR_REFORMA_TRIBUTARIA`, `PISCOFINS`/`INCIDENCIA_PISCOFINS` sem evidência de regime, campos de documentos fiscais, e qualquer CST IBS/CBS "inventado" no produto.
|
||||
## 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`.
|
||||
|
||||
@@ -0,0 +1,630 @@
|
||||
{
|
||||
"database": "TESTESOFICIAIS",
|
||||
"crt": "1",
|
||||
"products": 521,
|
||||
"fiscal_products_read": 521,
|
||||
"BEFORE": {
|
||||
"COMPLETE_PRODUCTS": 495,
|
||||
"SAFE_BLOCKED_PRODUCTS": 26,
|
||||
"COVERAGE_PERCENT": 95.01,
|
||||
"UNKNOWN_APPLICABILITY_previous_model": 518
|
||||
},
|
||||
"runs": {
|
||||
"ERP_AS_FOUND": {
|
||||
"rtc_enabled": true,
|
||||
"TOTAL_PRODUCTS": 521,
|
||||
"COMPLETE_PRODUCTS": 0,
|
||||
"SAFE_BLOCKED_PRODUCTS": 521,
|
||||
"FAILED_PRODUCTS": 0,
|
||||
"UNCLASSIFIED_PRODUCTS": 0,
|
||||
"COVERAGE_PERCENT": 0.0,
|
||||
"INVARIANT_PASS": true,
|
||||
"PIS_BLOCKERS": 2,
|
||||
"COFINS_BLOCKERS": 2,
|
||||
"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
|
||||
},
|
||||
"applicability": {
|
||||
"NOT_APPLICABLE": 518,
|
||||
"REQUIRED": 2,
|
||||
"OPTIONAL": 1
|
||||
},
|
||||
"rtc_applicability": {
|
||||
"REQUIRED": 521
|
||||
},
|
||||
"changed_products": 495,
|
||||
"examples": [
|
||||
{
|
||||
"product": "000004",
|
||||
"BEFORE_STATUS": "COMPLETE",
|
||||
"AFTER_STATUS": "BLOCKED_FISCAL_DATA",
|
||||
"REASON": [
|
||||
"CCLASSTRIB_MISSING"
|
||||
]
|
||||
},
|
||||
{
|
||||
"product": "000005",
|
||||
"BEFORE_STATUS": "COMPLETE",
|
||||
"AFTER_STATUS": "BLOCKED_FISCAL_DATA",
|
||||
"REASON": [
|
||||
"CCLASSTRIB_MISSING"
|
||||
]
|
||||
},
|
||||
{
|
||||
"product": "000006",
|
||||
"BEFORE_STATUS": "COMPLETE",
|
||||
"AFTER_STATUS": "BLOCKED_FISCAL_DATA",
|
||||
"REASON": [
|
||||
"CCLASSTRIB_MISSING"
|
||||
]
|
||||
},
|
||||
{
|
||||
"product": "000008",
|
||||
"BEFORE_STATUS": "COMPLETE",
|
||||
"AFTER_STATUS": "BLOCKED_FISCAL_DATA",
|
||||
"REASON": [
|
||||
"CCLASSTRIB_MISSING"
|
||||
]
|
||||
},
|
||||
{
|
||||
"product": "000009",
|
||||
"BEFORE_STATUS": "COMPLETE",
|
||||
"AFTER_STATUS": "BLOCKED_FISCAL_DATA",
|
||||
"REASON": [
|
||||
"CCLASSTRIB_MISSING"
|
||||
]
|
||||
},
|
||||
{
|
||||
"product": "000010",
|
||||
"BEFORE_STATUS": "COMPLETE",
|
||||
"AFTER_STATUS": "BLOCKED_FISCAL_DATA",
|
||||
"REASON": [
|
||||
"CCLASSTRIB_MISSING"
|
||||
]
|
||||
},
|
||||
{
|
||||
"product": "000011",
|
||||
"BEFORE_STATUS": "COMPLETE",
|
||||
"AFTER_STATUS": "BLOCKED_FISCAL_DATA",
|
||||
"REASON": [
|
||||
"CCLASSTRIB_MISSING"
|
||||
]
|
||||
},
|
||||
{
|
||||
"product": "000013",
|
||||
"BEFORE_STATUS": "COMPLETE",
|
||||
"AFTER_STATUS": "BLOCKED_FISCAL_DATA",
|
||||
"REASON": [
|
||||
"CCLASSTRIB_MISSING"
|
||||
]
|
||||
},
|
||||
{
|
||||
"product": "000015",
|
||||
"BEFORE_STATUS": "COMPLETE",
|
||||
"AFTER_STATUS": "BLOCKED_FISCAL_DATA",
|
||||
"REASON": [
|
||||
"CCLASSTRIB_MISSING"
|
||||
]
|
||||
},
|
||||
{
|
||||
"product": "000017",
|
||||
"BEFORE_STATUS": "COMPLETE",
|
||||
"AFTER_STATUS": "BLOCKED_FISCAL_DATA",
|
||||
"REASON": [
|
||||
"CCLASSTRIB_MISSING"
|
||||
]
|
||||
},
|
||||
{
|
||||
"product": "000018",
|
||||
"BEFORE_STATUS": "COMPLETE",
|
||||
"AFTER_STATUS": "BLOCKED_FISCAL_DATA",
|
||||
"REASON": [
|
||||
"CCLASSTRIB_MISSING"
|
||||
]
|
||||
},
|
||||
{
|
||||
"product": "000019",
|
||||
"BEFORE_STATUS": "COMPLETE",
|
||||
"AFTER_STATUS": "BLOCKED_FISCAL_DATA",
|
||||
"REASON": [
|
||||
"CCLASSTRIB_MISSING"
|
||||
]
|
||||
},
|
||||
{
|
||||
"product": "000021",
|
||||
"BEFORE_STATUS": "COMPLETE",
|
||||
"AFTER_STATUS": "BLOCKED_FISCAL_DATA",
|
||||
"REASON": [
|
||||
"CCLASSTRIB_MISSING"
|
||||
]
|
||||
},
|
||||
{
|
||||
"product": "000022",
|
||||
"BEFORE_STATUS": "COMPLETE",
|
||||
"AFTER_STATUS": "BLOCKED_FISCAL_DATA",
|
||||
"REASON": [
|
||||
"CCLASSTRIB_MISSING"
|
||||
]
|
||||
},
|
||||
{
|
||||
"product": "000023",
|
||||
"BEFORE_STATUS": "COMPLETE",
|
||||
"AFTER_STATUS": "BLOCKED_FISCAL_DATA",
|
||||
"REASON": [
|
||||
"CCLASSTRIB_MISSING"
|
||||
]
|
||||
},
|
||||
{
|
||||
"product": "000024",
|
||||
"BEFORE_STATUS": "COMPLETE",
|
||||
"AFTER_STATUS": "BLOCKED_FISCAL_DATA",
|
||||
"REASON": [
|
||||
"CCLASSTRIB_MISSING"
|
||||
]
|
||||
},
|
||||
{
|
||||
"product": "000025",
|
||||
"BEFORE_STATUS": "COMPLETE",
|
||||
"AFTER_STATUS": "BLOCKED_FISCAL_DATA",
|
||||
"REASON": [
|
||||
"CCLASSTRIB_MISSING"
|
||||
]
|
||||
},
|
||||
{
|
||||
"product": "000026",
|
||||
"BEFORE_STATUS": "COMPLETE",
|
||||
"AFTER_STATUS": "BLOCKED_FISCAL_DATA",
|
||||
"REASON": [
|
||||
"CCLASSTRIB_MISSING"
|
||||
]
|
||||
},
|
||||
{
|
||||
"product": "000027",
|
||||
"BEFORE_STATUS": "COMPLETE",
|
||||
"AFTER_STATUS": "BLOCKED_FISCAL_DATA",
|
||||
"REASON": [
|
||||
"CCLASSTRIB_MISSING"
|
||||
]
|
||||
},
|
||||
{
|
||||
"product": "000028",
|
||||
"BEFORE_STATUS": "COMPLETE",
|
||||
"AFTER_STATUS": "BLOCKED_FISCAL_DATA",
|
||||
"REASON": [
|
||||
"CCLASSTRIB_MISSING"
|
||||
]
|
||||
},
|
||||
{
|
||||
"product": "000029",
|
||||
"BEFORE_STATUS": "COMPLETE",
|
||||
"AFTER_STATUS": "BLOCKED_FISCAL_DATA",
|
||||
"REASON": [
|
||||
"CCLASSTRIB_MISSING"
|
||||
]
|
||||
},
|
||||
{
|
||||
"product": "000032",
|
||||
"BEFORE_STATUS": "COMPLETE",
|
||||
"AFTER_STATUS": "BLOCKED_FISCAL_DATA",
|
||||
"REASON": [
|
||||
"CCLASSTRIB_MISSING"
|
||||
]
|
||||
},
|
||||
{
|
||||
"product": "000033",
|
||||
"BEFORE_STATUS": "COMPLETE",
|
||||
"AFTER_STATUS": "BLOCKED_FISCAL_DATA",
|
||||
"REASON": [
|
||||
"CCLASSTRIB_MISSING"
|
||||
]
|
||||
},
|
||||
{
|
||||
"product": "000034",
|
||||
"BEFORE_STATUS": "COMPLETE",
|
||||
"AFTER_STATUS": "BLOCKED_FISCAL_DATA",
|
||||
"REASON": [
|
||||
"CCLASSTRIB_MISSING"
|
||||
]
|
||||
},
|
||||
{
|
||||
"product": "000035",
|
||||
"BEFORE_STATUS": "COMPLETE",
|
||||
"AFTER_STATUS": "BLOCKED_FISCAL_DATA",
|
||||
"REASON": [
|
||||
"CCLASSTRIB_MISSING"
|
||||
]
|
||||
},
|
||||
{
|
||||
"product": "000036",
|
||||
"BEFORE_STATUS": "COMPLETE",
|
||||
"AFTER_STATUS": "BLOCKED_FISCAL_DATA",
|
||||
"REASON": [
|
||||
"CCLASSTRIB_MISSING"
|
||||
]
|
||||
},
|
||||
{
|
||||
"product": "000038",
|
||||
"BEFORE_STATUS": "COMPLETE",
|
||||
"AFTER_STATUS": "BLOCKED_FISCAL_DATA",
|
||||
"REASON": [
|
||||
"CCLASSTRIB_MISSING"
|
||||
]
|
||||
},
|
||||
{
|
||||
"product": "000040",
|
||||
"BEFORE_STATUS": "COMPLETE",
|
||||
"AFTER_STATUS": "BLOCKED_FISCAL_DATA",
|
||||
"REASON": [
|
||||
"CCLASSTRIB_MISSING"
|
||||
]
|
||||
},
|
||||
{
|
||||
"product": "000041",
|
||||
"BEFORE_STATUS": "COMPLETE",
|
||||
"AFTER_STATUS": "BLOCKED_FISCAL_DATA",
|
||||
"REASON": [
|
||||
"CCLASSTRIB_MISSING"
|
||||
]
|
||||
},
|
||||
{
|
||||
"product": "000042",
|
||||
"BEFORE_STATUS": "COMPLETE",
|
||||
"AFTER_STATUS": "BLOCKED_FISCAL_DATA",
|
||||
"REASON": [
|
||||
"CCLASSTRIB_MISSING"
|
||||
]
|
||||
}
|
||||
],
|
||||
"context": {
|
||||
"rtc_enabled": true,
|
||||
"rtc_supported": true,
|
||||
"rtc_catalog_valid": true,
|
||||
"rtc_param": 1,
|
||||
"catalog_integrity": {
|
||||
"classes": 145,
|
||||
"cst": 18,
|
||||
"orphan_classes": 0,
|
||||
"bad_class_format": 0,
|
||||
"bad_cst_format": 0,
|
||||
"duplicates": 0,
|
||||
"valid": true
|
||||
},
|
||||
"crt": "1",
|
||||
"products_evaluated": 521
|
||||
}
|
||||
},
|
||||
"RTC_OFF_SIMULATION": {
|
||||
"rtc_enabled": false,
|
||||
"TOTAL_PRODUCTS": 521,
|
||||
"COMPLETE_PRODUCTS": 493,
|
||||
"SAFE_BLOCKED_PRODUCTS": 28,
|
||||
"FAILED_PRODUCTS": 0,
|
||||
"UNCLASSIFIED_PRODUCTS": 0,
|
||||
"COVERAGE_PERCENT": 94.63,
|
||||
"INVARIANT_PASS": true,
|
||||
"PIS_BLOCKERS": 2,
|
||||
"COFINS_BLOCKERS": 2,
|
||||
"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
|
||||
},
|
||||
"applicability": {
|
||||
"NOT_APPLICABLE": 518,
|
||||
"REQUIRED": 2,
|
||||
"OPTIONAL": 1
|
||||
},
|
||||
"rtc_applicability": {
|
||||
"NOT_APPLICABLE": 521
|
||||
},
|
||||
"changed_products": 2,
|
||||
"examples": [
|
||||
{
|
||||
"product": "000700",
|
||||
"BEFORE_STATUS": "COMPLETE",
|
||||
"AFTER_STATUS": "BLOCKED_FISCAL_DATA",
|
||||
"REASON": [
|
||||
"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"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"RTC_ON_SIMULATION": {
|
||||
"rtc_enabled": true,
|
||||
"TOTAL_PRODUCTS": 521,
|
||||
"COMPLETE_PRODUCTS": 0,
|
||||
"SAFE_BLOCKED_PRODUCTS": 521,
|
||||
"FAILED_PRODUCTS": 0,
|
||||
"UNCLASSIFIED_PRODUCTS": 0,
|
||||
"COVERAGE_PERCENT": 0.0,
|
||||
"INVARIANT_PASS": true,
|
||||
"PIS_BLOCKERS": 2,
|
||||
"COFINS_BLOCKERS": 2,
|
||||
"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
|
||||
},
|
||||
"applicability": {
|
||||
"NOT_APPLICABLE": 518,
|
||||
"REQUIRED": 2,
|
||||
"OPTIONAL": 1
|
||||
},
|
||||
"rtc_applicability": {
|
||||
"REQUIRED": 521
|
||||
},
|
||||
"changed_products": 495,
|
||||
"examples": [
|
||||
{
|
||||
"product": "000004",
|
||||
"BEFORE_STATUS": "COMPLETE",
|
||||
"AFTER_STATUS": "BLOCKED_FISCAL_DATA",
|
||||
"REASON": [
|
||||
"CCLASSTRIB_MISSING"
|
||||
]
|
||||
},
|
||||
{
|
||||
"product": "000005",
|
||||
"BEFORE_STATUS": "COMPLETE",
|
||||
"AFTER_STATUS": "BLOCKED_FISCAL_DATA",
|
||||
"REASON": [
|
||||
"CCLASSTRIB_MISSING"
|
||||
]
|
||||
},
|
||||
{
|
||||
"product": "000006",
|
||||
"BEFORE_STATUS": "COMPLETE",
|
||||
"AFTER_STATUS": "BLOCKED_FISCAL_DATA",
|
||||
"REASON": [
|
||||
"CCLASSTRIB_MISSING"
|
||||
]
|
||||
},
|
||||
{
|
||||
"product": "000008",
|
||||
"BEFORE_STATUS": "COMPLETE",
|
||||
"AFTER_STATUS": "BLOCKED_FISCAL_DATA",
|
||||
"REASON": [
|
||||
"CCLASSTRIB_MISSING"
|
||||
]
|
||||
},
|
||||
{
|
||||
"product": "000009",
|
||||
"BEFORE_STATUS": "COMPLETE",
|
||||
"AFTER_STATUS": "BLOCKED_FISCAL_DATA",
|
||||
"REASON": [
|
||||
"CCLASSTRIB_MISSING"
|
||||
]
|
||||
},
|
||||
{
|
||||
"product": "000010",
|
||||
"BEFORE_STATUS": "COMPLETE",
|
||||
"AFTER_STATUS": "BLOCKED_FISCAL_DATA",
|
||||
"REASON": [
|
||||
"CCLASSTRIB_MISSING"
|
||||
]
|
||||
},
|
||||
{
|
||||
"product": "000011",
|
||||
"BEFORE_STATUS": "COMPLETE",
|
||||
"AFTER_STATUS": "BLOCKED_FISCAL_DATA",
|
||||
"REASON": [
|
||||
"CCLASSTRIB_MISSING"
|
||||
]
|
||||
},
|
||||
{
|
||||
"product": "000013",
|
||||
"BEFORE_STATUS": "COMPLETE",
|
||||
"AFTER_STATUS": "BLOCKED_FISCAL_DATA",
|
||||
"REASON": [
|
||||
"CCLASSTRIB_MISSING"
|
||||
]
|
||||
},
|
||||
{
|
||||
"product": "000015",
|
||||
"BEFORE_STATUS": "COMPLETE",
|
||||
"AFTER_STATUS": "BLOCKED_FISCAL_DATA",
|
||||
"REASON": [
|
||||
"CCLASSTRIB_MISSING"
|
||||
]
|
||||
},
|
||||
{
|
||||
"product": "000017",
|
||||
"BEFORE_STATUS": "COMPLETE",
|
||||
"AFTER_STATUS": "BLOCKED_FISCAL_DATA",
|
||||
"REASON": [
|
||||
"CCLASSTRIB_MISSING"
|
||||
]
|
||||
},
|
||||
{
|
||||
"product": "000018",
|
||||
"BEFORE_STATUS": "COMPLETE",
|
||||
"AFTER_STATUS": "BLOCKED_FISCAL_DATA",
|
||||
"REASON": [
|
||||
"CCLASSTRIB_MISSING"
|
||||
]
|
||||
},
|
||||
{
|
||||
"product": "000019",
|
||||
"BEFORE_STATUS": "COMPLETE",
|
||||
"AFTER_STATUS": "BLOCKED_FISCAL_DATA",
|
||||
"REASON": [
|
||||
"CCLASSTRIB_MISSING"
|
||||
]
|
||||
},
|
||||
{
|
||||
"product": "000021",
|
||||
"BEFORE_STATUS": "COMPLETE",
|
||||
"AFTER_STATUS": "BLOCKED_FISCAL_DATA",
|
||||
"REASON": [
|
||||
"CCLASSTRIB_MISSING"
|
||||
]
|
||||
},
|
||||
{
|
||||
"product": "000022",
|
||||
"BEFORE_STATUS": "COMPLETE",
|
||||
"AFTER_STATUS": "BLOCKED_FISCAL_DATA",
|
||||
"REASON": [
|
||||
"CCLASSTRIB_MISSING"
|
||||
]
|
||||
},
|
||||
{
|
||||
"product": "000023",
|
||||
"BEFORE_STATUS": "COMPLETE",
|
||||
"AFTER_STATUS": "BLOCKED_FISCAL_DATA",
|
||||
"REASON": [
|
||||
"CCLASSTRIB_MISSING"
|
||||
]
|
||||
},
|
||||
{
|
||||
"product": "000024",
|
||||
"BEFORE_STATUS": "COMPLETE",
|
||||
"AFTER_STATUS": "BLOCKED_FISCAL_DATA",
|
||||
"REASON": [
|
||||
"CCLASSTRIB_MISSING"
|
||||
]
|
||||
},
|
||||
{
|
||||
"product": "000025",
|
||||
"BEFORE_STATUS": "COMPLETE",
|
||||
"AFTER_STATUS": "BLOCKED_FISCAL_DATA",
|
||||
"REASON": [
|
||||
"CCLASSTRIB_MISSING"
|
||||
]
|
||||
},
|
||||
{
|
||||
"product": "000026",
|
||||
"BEFORE_STATUS": "COMPLETE",
|
||||
"AFTER_STATUS": "BLOCKED_FISCAL_DATA",
|
||||
"REASON": [
|
||||
"CCLASSTRIB_MISSING"
|
||||
]
|
||||
},
|
||||
{
|
||||
"product": "000027",
|
||||
"BEFORE_STATUS": "COMPLETE",
|
||||
"AFTER_STATUS": "BLOCKED_FISCAL_DATA",
|
||||
"REASON": [
|
||||
"CCLASSTRIB_MISSING"
|
||||
]
|
||||
},
|
||||
{
|
||||
"product": "000028",
|
||||
"BEFORE_STATUS": "COMPLETE",
|
||||
"AFTER_STATUS": "BLOCKED_FISCAL_DATA",
|
||||
"REASON": [
|
||||
"CCLASSTRIB_MISSING"
|
||||
]
|
||||
},
|
||||
{
|
||||
"product": "000029",
|
||||
"BEFORE_STATUS": "COMPLETE",
|
||||
"AFTER_STATUS": "BLOCKED_FISCAL_DATA",
|
||||
"REASON": [
|
||||
"CCLASSTRIB_MISSING"
|
||||
]
|
||||
},
|
||||
{
|
||||
"product": "000032",
|
||||
"BEFORE_STATUS": "COMPLETE",
|
||||
"AFTER_STATUS": "BLOCKED_FISCAL_DATA",
|
||||
"REASON": [
|
||||
"CCLASSTRIB_MISSING"
|
||||
]
|
||||
},
|
||||
{
|
||||
"product": "000033",
|
||||
"BEFORE_STATUS": "COMPLETE",
|
||||
"AFTER_STATUS": "BLOCKED_FISCAL_DATA",
|
||||
"REASON": [
|
||||
"CCLASSTRIB_MISSING"
|
||||
]
|
||||
},
|
||||
{
|
||||
"product": "000034",
|
||||
"BEFORE_STATUS": "COMPLETE",
|
||||
"AFTER_STATUS": "BLOCKED_FISCAL_DATA",
|
||||
"REASON": [
|
||||
"CCLASSTRIB_MISSING"
|
||||
]
|
||||
},
|
||||
{
|
||||
"product": "000035",
|
||||
"BEFORE_STATUS": "COMPLETE",
|
||||
"AFTER_STATUS": "BLOCKED_FISCAL_DATA",
|
||||
"REASON": [
|
||||
"CCLASSTRIB_MISSING"
|
||||
]
|
||||
},
|
||||
{
|
||||
"product": "000036",
|
||||
"BEFORE_STATUS": "COMPLETE",
|
||||
"AFTER_STATUS": "BLOCKED_FISCAL_DATA",
|
||||
"REASON": [
|
||||
"CCLASSTRIB_MISSING"
|
||||
]
|
||||
},
|
||||
{
|
||||
"product": "000038",
|
||||
"BEFORE_STATUS": "COMPLETE",
|
||||
"AFTER_STATUS": "BLOCKED_FISCAL_DATA",
|
||||
"REASON": [
|
||||
"CCLASSTRIB_MISSING"
|
||||
]
|
||||
},
|
||||
{
|
||||
"product": "000040",
|
||||
"BEFORE_STATUS": "COMPLETE",
|
||||
"AFTER_STATUS": "BLOCKED_FISCAL_DATA",
|
||||
"REASON": [
|
||||
"CCLASSTRIB_MISSING"
|
||||
]
|
||||
},
|
||||
{
|
||||
"product": "000041",
|
||||
"BEFORE_STATUS": "COMPLETE",
|
||||
"AFTER_STATUS": "BLOCKED_FISCAL_DATA",
|
||||
"REASON": [
|
||||
"CCLASSTRIB_MISSING"
|
||||
]
|
||||
},
|
||||
{
|
||||
"product": "000042",
|
||||
"BEFORE_STATUS": "COMPLETE",
|
||||
"AFTER_STATUS": "BLOCKED_FISCAL_DATA",
|
||||
"REASON": [
|
||||
"CCLASSTRIB_MISSING"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"SAFETY": {
|
||||
"FIREBIRD_WRITES": 0,
|
||||
"WRITE_ATTEMPTS": 0,
|
||||
"WRITE_SUCCESSES": 0,
|
||||
"connection_mode": "READ COMMITTED READ ONLY (Agent reader); no write-capable connection opened"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,593 @@
|
||||
{
|
||||
"mode": "FISCAL_ONLY (no Hub decision)",
|
||||
"database": "CONSTRUCAO",
|
||||
"crt": "1",
|
||||
"products": 3712,
|
||||
"fiscal_products_read": 3712,
|
||||
"BEFORE": {
|
||||
"COMPLETE_PRODUCTS": 3712,
|
||||
"SAFE_BLOCKED_PRODUCTS": 0,
|
||||
"COVERAGE_PERCENT": 100.0,
|
||||
"UNKNOWN_APPLICABILITY_previous_model": 3712
|
||||
},
|
||||
"runs": {
|
||||
"ERP_AS_FOUND": {
|
||||
"rtc_enabled": true,
|
||||
"TOTAL_PRODUCTS": 3712,
|
||||
"COMPLETE_PRODUCTS": 0,
|
||||
"SAFE_BLOCKED_PRODUCTS": 3712,
|
||||
"FAILED_PRODUCTS": 0,
|
||||
"UNCLASSIFIED_PRODUCTS": 0,
|
||||
"COVERAGE_PERCENT": 0.0,
|
||||
"INVARIANT_PASS": true,
|
||||
"PIS_BLOCKERS": 0,
|
||||
"COFINS_BLOCKERS": 0,
|
||||
"RTC_BLOCKERS": 3712,
|
||||
"OTHER_BLOCKERS": 0,
|
||||
"blockers_by_type": {
|
||||
"CCLASSTRIB_MISSING": 3712
|
||||
},
|
||||
"applicability": {
|
||||
"NOT_APPLICABLE": 3712
|
||||
},
|
||||
"rtc_applicability": {
|
||||
"REQUIRED": 3712
|
||||
},
|
||||
"changed_products": 3712,
|
||||
"examples": [
|
||||
{
|
||||
"product": "000001",
|
||||
"BEFORE_STATUS": "COMPLETE",
|
||||
"AFTER_STATUS": "BLOCKED_FISCAL_DATA",
|
||||
"REASON": [
|
||||
"CCLASSTRIB_MISSING"
|
||||
]
|
||||
},
|
||||
{
|
||||
"product": "000002",
|
||||
"BEFORE_STATUS": "COMPLETE",
|
||||
"AFTER_STATUS": "BLOCKED_FISCAL_DATA",
|
||||
"REASON": [
|
||||
"CCLASSTRIB_MISSING"
|
||||
]
|
||||
},
|
||||
{
|
||||
"product": "000003",
|
||||
"BEFORE_STATUS": "COMPLETE",
|
||||
"AFTER_STATUS": "BLOCKED_FISCAL_DATA",
|
||||
"REASON": [
|
||||
"CCLASSTRIB_MISSING"
|
||||
]
|
||||
},
|
||||
{
|
||||
"product": "000004",
|
||||
"BEFORE_STATUS": "COMPLETE",
|
||||
"AFTER_STATUS": "BLOCKED_FISCAL_DATA",
|
||||
"REASON": [
|
||||
"CCLASSTRIB_MISSING"
|
||||
]
|
||||
},
|
||||
{
|
||||
"product": "000005",
|
||||
"BEFORE_STATUS": "COMPLETE",
|
||||
"AFTER_STATUS": "BLOCKED_FISCAL_DATA",
|
||||
"REASON": [
|
||||
"CCLASSTRIB_MISSING"
|
||||
]
|
||||
},
|
||||
{
|
||||
"product": "000006",
|
||||
"BEFORE_STATUS": "COMPLETE",
|
||||
"AFTER_STATUS": "BLOCKED_FISCAL_DATA",
|
||||
"REASON": [
|
||||
"CCLASSTRIB_MISSING"
|
||||
]
|
||||
},
|
||||
{
|
||||
"product": "000007",
|
||||
"BEFORE_STATUS": "COMPLETE",
|
||||
"AFTER_STATUS": "BLOCKED_FISCAL_DATA",
|
||||
"REASON": [
|
||||
"CCLASSTRIB_MISSING"
|
||||
]
|
||||
},
|
||||
{
|
||||
"product": "000008",
|
||||
"BEFORE_STATUS": "COMPLETE",
|
||||
"AFTER_STATUS": "BLOCKED_FISCAL_DATA",
|
||||
"REASON": [
|
||||
"CCLASSTRIB_MISSING"
|
||||
]
|
||||
},
|
||||
{
|
||||
"product": "000009",
|
||||
"BEFORE_STATUS": "COMPLETE",
|
||||
"AFTER_STATUS": "BLOCKED_FISCAL_DATA",
|
||||
"REASON": [
|
||||
"CCLASSTRIB_MISSING"
|
||||
]
|
||||
},
|
||||
{
|
||||
"product": "000010",
|
||||
"BEFORE_STATUS": "COMPLETE",
|
||||
"AFTER_STATUS": "BLOCKED_FISCAL_DATA",
|
||||
"REASON": [
|
||||
"CCLASSTRIB_MISSING"
|
||||
]
|
||||
},
|
||||
{
|
||||
"product": "000011",
|
||||
"BEFORE_STATUS": "COMPLETE",
|
||||
"AFTER_STATUS": "BLOCKED_FISCAL_DATA",
|
||||
"REASON": [
|
||||
"CCLASSTRIB_MISSING"
|
||||
]
|
||||
},
|
||||
{
|
||||
"product": "000012",
|
||||
"BEFORE_STATUS": "COMPLETE",
|
||||
"AFTER_STATUS": "BLOCKED_FISCAL_DATA",
|
||||
"REASON": [
|
||||
"CCLASSTRIB_MISSING"
|
||||
]
|
||||
},
|
||||
{
|
||||
"product": "000013",
|
||||
"BEFORE_STATUS": "COMPLETE",
|
||||
"AFTER_STATUS": "BLOCKED_FISCAL_DATA",
|
||||
"REASON": [
|
||||
"CCLASSTRIB_MISSING"
|
||||
]
|
||||
},
|
||||
{
|
||||
"product": "000014",
|
||||
"BEFORE_STATUS": "COMPLETE",
|
||||
"AFTER_STATUS": "BLOCKED_FISCAL_DATA",
|
||||
"REASON": [
|
||||
"CCLASSTRIB_MISSING"
|
||||
]
|
||||
},
|
||||
{
|
||||
"product": "000015",
|
||||
"BEFORE_STATUS": "COMPLETE",
|
||||
"AFTER_STATUS": "BLOCKED_FISCAL_DATA",
|
||||
"REASON": [
|
||||
"CCLASSTRIB_MISSING"
|
||||
]
|
||||
},
|
||||
{
|
||||
"product": "000016",
|
||||
"BEFORE_STATUS": "COMPLETE",
|
||||
"AFTER_STATUS": "BLOCKED_FISCAL_DATA",
|
||||
"REASON": [
|
||||
"CCLASSTRIB_MISSING"
|
||||
]
|
||||
},
|
||||
{
|
||||
"product": "000017",
|
||||
"BEFORE_STATUS": "COMPLETE",
|
||||
"AFTER_STATUS": "BLOCKED_FISCAL_DATA",
|
||||
"REASON": [
|
||||
"CCLASSTRIB_MISSING"
|
||||
]
|
||||
},
|
||||
{
|
||||
"product": "000018",
|
||||
"BEFORE_STATUS": "COMPLETE",
|
||||
"AFTER_STATUS": "BLOCKED_FISCAL_DATA",
|
||||
"REASON": [
|
||||
"CCLASSTRIB_MISSING"
|
||||
]
|
||||
},
|
||||
{
|
||||
"product": "000019",
|
||||
"BEFORE_STATUS": "COMPLETE",
|
||||
"AFTER_STATUS": "BLOCKED_FISCAL_DATA",
|
||||
"REASON": [
|
||||
"CCLASSTRIB_MISSING"
|
||||
]
|
||||
},
|
||||
{
|
||||
"product": "000020",
|
||||
"BEFORE_STATUS": "COMPLETE",
|
||||
"AFTER_STATUS": "BLOCKED_FISCAL_DATA",
|
||||
"REASON": [
|
||||
"CCLASSTRIB_MISSING"
|
||||
]
|
||||
},
|
||||
{
|
||||
"product": "000021",
|
||||
"BEFORE_STATUS": "COMPLETE",
|
||||
"AFTER_STATUS": "BLOCKED_FISCAL_DATA",
|
||||
"REASON": [
|
||||
"CCLASSTRIB_MISSING"
|
||||
]
|
||||
},
|
||||
{
|
||||
"product": "000022",
|
||||
"BEFORE_STATUS": "COMPLETE",
|
||||
"AFTER_STATUS": "BLOCKED_FISCAL_DATA",
|
||||
"REASON": [
|
||||
"CCLASSTRIB_MISSING"
|
||||
]
|
||||
},
|
||||
{
|
||||
"product": "000023",
|
||||
"BEFORE_STATUS": "COMPLETE",
|
||||
"AFTER_STATUS": "BLOCKED_FISCAL_DATA",
|
||||
"REASON": [
|
||||
"CCLASSTRIB_MISSING"
|
||||
]
|
||||
},
|
||||
{
|
||||
"product": "000024",
|
||||
"BEFORE_STATUS": "COMPLETE",
|
||||
"AFTER_STATUS": "BLOCKED_FISCAL_DATA",
|
||||
"REASON": [
|
||||
"CCLASSTRIB_MISSING"
|
||||
]
|
||||
},
|
||||
{
|
||||
"product": "000025",
|
||||
"BEFORE_STATUS": "COMPLETE",
|
||||
"AFTER_STATUS": "BLOCKED_FISCAL_DATA",
|
||||
"REASON": [
|
||||
"CCLASSTRIB_MISSING"
|
||||
]
|
||||
},
|
||||
{
|
||||
"product": "000026",
|
||||
"BEFORE_STATUS": "COMPLETE",
|
||||
"AFTER_STATUS": "BLOCKED_FISCAL_DATA",
|
||||
"REASON": [
|
||||
"CCLASSTRIB_MISSING"
|
||||
]
|
||||
},
|
||||
{
|
||||
"product": "000027",
|
||||
"BEFORE_STATUS": "COMPLETE",
|
||||
"AFTER_STATUS": "BLOCKED_FISCAL_DATA",
|
||||
"REASON": [
|
||||
"CCLASSTRIB_MISSING"
|
||||
]
|
||||
},
|
||||
{
|
||||
"product": "000028",
|
||||
"BEFORE_STATUS": "COMPLETE",
|
||||
"AFTER_STATUS": "BLOCKED_FISCAL_DATA",
|
||||
"REASON": [
|
||||
"CCLASSTRIB_MISSING"
|
||||
]
|
||||
},
|
||||
{
|
||||
"product": "000029",
|
||||
"BEFORE_STATUS": "COMPLETE",
|
||||
"AFTER_STATUS": "BLOCKED_FISCAL_DATA",
|
||||
"REASON": [
|
||||
"CCLASSTRIB_MISSING"
|
||||
]
|
||||
},
|
||||
{
|
||||
"product": "000030",
|
||||
"BEFORE_STATUS": "COMPLETE",
|
||||
"AFTER_STATUS": "BLOCKED_FISCAL_DATA",
|
||||
"REASON": [
|
||||
"CCLASSTRIB_MISSING"
|
||||
]
|
||||
}
|
||||
],
|
||||
"context": {
|
||||
"rtc_enabled": true,
|
||||
"rtc_supported": true,
|
||||
"rtc_catalog_valid": true,
|
||||
"rtc_param": 1,
|
||||
"catalog_integrity": {
|
||||
"classes": 145,
|
||||
"cst": 18,
|
||||
"orphan_classes": 0,
|
||||
"bad_class_format": 0,
|
||||
"bad_cst_format": 0,
|
||||
"duplicates": 0,
|
||||
"valid": true
|
||||
},
|
||||
"crt": "1",
|
||||
"products_evaluated": 3712
|
||||
}
|
||||
},
|
||||
"RTC_OFF_SIMULATION": {
|
||||
"rtc_enabled": false,
|
||||
"TOTAL_PRODUCTS": 3712,
|
||||
"COMPLETE_PRODUCTS": 3712,
|
||||
"SAFE_BLOCKED_PRODUCTS": 0,
|
||||
"FAILED_PRODUCTS": 0,
|
||||
"UNCLASSIFIED_PRODUCTS": 0,
|
||||
"COVERAGE_PERCENT": 100.0,
|
||||
"INVARIANT_PASS": true,
|
||||
"PIS_BLOCKERS": 0,
|
||||
"COFINS_BLOCKERS": 0,
|
||||
"RTC_BLOCKERS": 0,
|
||||
"OTHER_BLOCKERS": 0,
|
||||
"blockers_by_type": {},
|
||||
"applicability": {
|
||||
"NOT_APPLICABLE": 3712
|
||||
},
|
||||
"rtc_applicability": {
|
||||
"NOT_APPLICABLE": 3712
|
||||
},
|
||||
"changed_products": 0,
|
||||
"examples": []
|
||||
},
|
||||
"RTC_ON_SIMULATION": {
|
||||
"rtc_enabled": true,
|
||||
"TOTAL_PRODUCTS": 3712,
|
||||
"COMPLETE_PRODUCTS": 0,
|
||||
"SAFE_BLOCKED_PRODUCTS": 3712,
|
||||
"FAILED_PRODUCTS": 0,
|
||||
"UNCLASSIFIED_PRODUCTS": 0,
|
||||
"COVERAGE_PERCENT": 0.0,
|
||||
"INVARIANT_PASS": true,
|
||||
"PIS_BLOCKERS": 0,
|
||||
"COFINS_BLOCKERS": 0,
|
||||
"RTC_BLOCKERS": 3712,
|
||||
"OTHER_BLOCKERS": 0,
|
||||
"blockers_by_type": {
|
||||
"CCLASSTRIB_MISSING": 3712
|
||||
},
|
||||
"applicability": {
|
||||
"NOT_APPLICABLE": 3712
|
||||
},
|
||||
"rtc_applicability": {
|
||||
"REQUIRED": 3712
|
||||
},
|
||||
"changed_products": 3712,
|
||||
"examples": [
|
||||
{
|
||||
"product": "000001",
|
||||
"BEFORE_STATUS": "COMPLETE",
|
||||
"AFTER_STATUS": "BLOCKED_FISCAL_DATA",
|
||||
"REASON": [
|
||||
"CCLASSTRIB_MISSING"
|
||||
]
|
||||
},
|
||||
{
|
||||
"product": "000002",
|
||||
"BEFORE_STATUS": "COMPLETE",
|
||||
"AFTER_STATUS": "BLOCKED_FISCAL_DATA",
|
||||
"REASON": [
|
||||
"CCLASSTRIB_MISSING"
|
||||
]
|
||||
},
|
||||
{
|
||||
"product": "000003",
|
||||
"BEFORE_STATUS": "COMPLETE",
|
||||
"AFTER_STATUS": "BLOCKED_FISCAL_DATA",
|
||||
"REASON": [
|
||||
"CCLASSTRIB_MISSING"
|
||||
]
|
||||
},
|
||||
{
|
||||
"product": "000004",
|
||||
"BEFORE_STATUS": "COMPLETE",
|
||||
"AFTER_STATUS": "BLOCKED_FISCAL_DATA",
|
||||
"REASON": [
|
||||
"CCLASSTRIB_MISSING"
|
||||
]
|
||||
},
|
||||
{
|
||||
"product": "000005",
|
||||
"BEFORE_STATUS": "COMPLETE",
|
||||
"AFTER_STATUS": "BLOCKED_FISCAL_DATA",
|
||||
"REASON": [
|
||||
"CCLASSTRIB_MISSING"
|
||||
]
|
||||
},
|
||||
{
|
||||
"product": "000006",
|
||||
"BEFORE_STATUS": "COMPLETE",
|
||||
"AFTER_STATUS": "BLOCKED_FISCAL_DATA",
|
||||
"REASON": [
|
||||
"CCLASSTRIB_MISSING"
|
||||
]
|
||||
},
|
||||
{
|
||||
"product": "000007",
|
||||
"BEFORE_STATUS": "COMPLETE",
|
||||
"AFTER_STATUS": "BLOCKED_FISCAL_DATA",
|
||||
"REASON": [
|
||||
"CCLASSTRIB_MISSING"
|
||||
]
|
||||
},
|
||||
{
|
||||
"product": "000008",
|
||||
"BEFORE_STATUS": "COMPLETE",
|
||||
"AFTER_STATUS": "BLOCKED_FISCAL_DATA",
|
||||
"REASON": [
|
||||
"CCLASSTRIB_MISSING"
|
||||
]
|
||||
},
|
||||
{
|
||||
"product": "000009",
|
||||
"BEFORE_STATUS": "COMPLETE",
|
||||
"AFTER_STATUS": "BLOCKED_FISCAL_DATA",
|
||||
"REASON": [
|
||||
"CCLASSTRIB_MISSING"
|
||||
]
|
||||
},
|
||||
{
|
||||
"product": "000010",
|
||||
"BEFORE_STATUS": "COMPLETE",
|
||||
"AFTER_STATUS": "BLOCKED_FISCAL_DATA",
|
||||
"REASON": [
|
||||
"CCLASSTRIB_MISSING"
|
||||
]
|
||||
},
|
||||
{
|
||||
"product": "000011",
|
||||
"BEFORE_STATUS": "COMPLETE",
|
||||
"AFTER_STATUS": "BLOCKED_FISCAL_DATA",
|
||||
"REASON": [
|
||||
"CCLASSTRIB_MISSING"
|
||||
]
|
||||
},
|
||||
{
|
||||
"product": "000012",
|
||||
"BEFORE_STATUS": "COMPLETE",
|
||||
"AFTER_STATUS": "BLOCKED_FISCAL_DATA",
|
||||
"REASON": [
|
||||
"CCLASSTRIB_MISSING"
|
||||
]
|
||||
},
|
||||
{
|
||||
"product": "000013",
|
||||
"BEFORE_STATUS": "COMPLETE",
|
||||
"AFTER_STATUS": "BLOCKED_FISCAL_DATA",
|
||||
"REASON": [
|
||||
"CCLASSTRIB_MISSING"
|
||||
]
|
||||
},
|
||||
{
|
||||
"product": "000014",
|
||||
"BEFORE_STATUS": "COMPLETE",
|
||||
"AFTER_STATUS": "BLOCKED_FISCAL_DATA",
|
||||
"REASON": [
|
||||
"CCLASSTRIB_MISSING"
|
||||
]
|
||||
},
|
||||
{
|
||||
"product": "000015",
|
||||
"BEFORE_STATUS": "COMPLETE",
|
||||
"AFTER_STATUS": "BLOCKED_FISCAL_DATA",
|
||||
"REASON": [
|
||||
"CCLASSTRIB_MISSING"
|
||||
]
|
||||
},
|
||||
{
|
||||
"product": "000016",
|
||||
"BEFORE_STATUS": "COMPLETE",
|
||||
"AFTER_STATUS": "BLOCKED_FISCAL_DATA",
|
||||
"REASON": [
|
||||
"CCLASSTRIB_MISSING"
|
||||
]
|
||||
},
|
||||
{
|
||||
"product": "000017",
|
||||
"BEFORE_STATUS": "COMPLETE",
|
||||
"AFTER_STATUS": "BLOCKED_FISCAL_DATA",
|
||||
"REASON": [
|
||||
"CCLASSTRIB_MISSING"
|
||||
]
|
||||
},
|
||||
{
|
||||
"product": "000018",
|
||||
"BEFORE_STATUS": "COMPLETE",
|
||||
"AFTER_STATUS": "BLOCKED_FISCAL_DATA",
|
||||
"REASON": [
|
||||
"CCLASSTRIB_MISSING"
|
||||
]
|
||||
},
|
||||
{
|
||||
"product": "000019",
|
||||
"BEFORE_STATUS": "COMPLETE",
|
||||
"AFTER_STATUS": "BLOCKED_FISCAL_DATA",
|
||||
"REASON": [
|
||||
"CCLASSTRIB_MISSING"
|
||||
]
|
||||
},
|
||||
{
|
||||
"product": "000020",
|
||||
"BEFORE_STATUS": "COMPLETE",
|
||||
"AFTER_STATUS": "BLOCKED_FISCAL_DATA",
|
||||
"REASON": [
|
||||
"CCLASSTRIB_MISSING"
|
||||
]
|
||||
},
|
||||
{
|
||||
"product": "000021",
|
||||
"BEFORE_STATUS": "COMPLETE",
|
||||
"AFTER_STATUS": "BLOCKED_FISCAL_DATA",
|
||||
"REASON": [
|
||||
"CCLASSTRIB_MISSING"
|
||||
]
|
||||
},
|
||||
{
|
||||
"product": "000022",
|
||||
"BEFORE_STATUS": "COMPLETE",
|
||||
"AFTER_STATUS": "BLOCKED_FISCAL_DATA",
|
||||
"REASON": [
|
||||
"CCLASSTRIB_MISSING"
|
||||
]
|
||||
},
|
||||
{
|
||||
"product": "000023",
|
||||
"BEFORE_STATUS": "COMPLETE",
|
||||
"AFTER_STATUS": "BLOCKED_FISCAL_DATA",
|
||||
"REASON": [
|
||||
"CCLASSTRIB_MISSING"
|
||||
]
|
||||
},
|
||||
{
|
||||
"product": "000024",
|
||||
"BEFORE_STATUS": "COMPLETE",
|
||||
"AFTER_STATUS": "BLOCKED_FISCAL_DATA",
|
||||
"REASON": [
|
||||
"CCLASSTRIB_MISSING"
|
||||
]
|
||||
},
|
||||
{
|
||||
"product": "000025",
|
||||
"BEFORE_STATUS": "COMPLETE",
|
||||
"AFTER_STATUS": "BLOCKED_FISCAL_DATA",
|
||||
"REASON": [
|
||||
"CCLASSTRIB_MISSING"
|
||||
]
|
||||
},
|
||||
{
|
||||
"product": "000026",
|
||||
"BEFORE_STATUS": "COMPLETE",
|
||||
"AFTER_STATUS": "BLOCKED_FISCAL_DATA",
|
||||
"REASON": [
|
||||
"CCLASSTRIB_MISSING"
|
||||
]
|
||||
},
|
||||
{
|
||||
"product": "000027",
|
||||
"BEFORE_STATUS": "COMPLETE",
|
||||
"AFTER_STATUS": "BLOCKED_FISCAL_DATA",
|
||||
"REASON": [
|
||||
"CCLASSTRIB_MISSING"
|
||||
]
|
||||
},
|
||||
{
|
||||
"product": "000028",
|
||||
"BEFORE_STATUS": "COMPLETE",
|
||||
"AFTER_STATUS": "BLOCKED_FISCAL_DATA",
|
||||
"REASON": [
|
||||
"CCLASSTRIB_MISSING"
|
||||
]
|
||||
},
|
||||
{
|
||||
"product": "000029",
|
||||
"BEFORE_STATUS": "COMPLETE",
|
||||
"AFTER_STATUS": "BLOCKED_FISCAL_DATA",
|
||||
"REASON": [
|
||||
"CCLASSTRIB_MISSING"
|
||||
]
|
||||
},
|
||||
{
|
||||
"product": "000030",
|
||||
"BEFORE_STATUS": "COMPLETE",
|
||||
"AFTER_STATUS": "BLOCKED_FISCAL_DATA",
|
||||
"REASON": [
|
||||
"CCLASSTRIB_MISSING"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"SAFETY": {
|
||||
"FIREBIRD_WRITES": 0,
|
||||
"WRITE_ATTEMPTS": 0,
|
||||
"WRITE_SUCCESSES": 0,
|
||||
"connection_mode": "READ COMMITTED READ ONLY (Agent reader); no write-capable connection opened"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,353 @@
|
||||
{
|
||||
"mode": "FISCAL_ONLY (no Hub decision)",
|
||||
"database": "MMMODAS",
|
||||
"crt": "2",
|
||||
"products": 20997,
|
||||
"fiscal_products_read": 20997,
|
||||
"BEFORE": {
|
||||
"COMPLETE_PRODUCTS": 20997,
|
||||
"SAFE_BLOCKED_PRODUCTS": 0,
|
||||
"COVERAGE_PERCENT": 100.0,
|
||||
"UNKNOWN_APPLICABILITY_previous_model": 20996
|
||||
},
|
||||
"runs": {
|
||||
"ERP_AS_FOUND": {
|
||||
"rtc_enabled": false,
|
||||
"TOTAL_PRODUCTS": 20997,
|
||||
"COMPLETE_PRODUCTS": 20997,
|
||||
"SAFE_BLOCKED_PRODUCTS": 0,
|
||||
"FAILED_PRODUCTS": 0,
|
||||
"UNCLASSIFIED_PRODUCTS": 0,
|
||||
"COVERAGE_PERCENT": 100.0,
|
||||
"INVARIANT_PASS": true,
|
||||
"PIS_BLOCKERS": 0,
|
||||
"COFINS_BLOCKERS": 0,
|
||||
"RTC_BLOCKERS": 0,
|
||||
"OTHER_BLOCKERS": 0,
|
||||
"blockers_by_type": {},
|
||||
"applicability": {
|
||||
"OPTIONAL": 1,
|
||||
"NOT_APPLICABLE": 20995
|
||||
},
|
||||
"rtc_applicability": {
|
||||
"NOT_APPLICABLE": 20996
|
||||
},
|
||||
"changed_products": 0,
|
||||
"examples": [],
|
||||
"context": {
|
||||
"rtc_enabled": false,
|
||||
"rtc_supported": true,
|
||||
"rtc_catalog_valid": true,
|
||||
"rtc_param": 0,
|
||||
"catalog_integrity": {
|
||||
"classes": 145,
|
||||
"cst": 18,
|
||||
"orphan_classes": 0,
|
||||
"bad_class_format": 0,
|
||||
"bad_cst_format": 0,
|
||||
"duplicates": 0,
|
||||
"valid": true
|
||||
},
|
||||
"crt": "2",
|
||||
"products_evaluated": 20997
|
||||
}
|
||||
},
|
||||
"RTC_OFF_SIMULATION": {
|
||||
"rtc_enabled": false,
|
||||
"TOTAL_PRODUCTS": 20997,
|
||||
"COMPLETE_PRODUCTS": 20997,
|
||||
"SAFE_BLOCKED_PRODUCTS": 0,
|
||||
"FAILED_PRODUCTS": 0,
|
||||
"UNCLASSIFIED_PRODUCTS": 0,
|
||||
"COVERAGE_PERCENT": 100.0,
|
||||
"INVARIANT_PASS": true,
|
||||
"PIS_BLOCKERS": 0,
|
||||
"COFINS_BLOCKERS": 0,
|
||||
"RTC_BLOCKERS": 0,
|
||||
"OTHER_BLOCKERS": 0,
|
||||
"blockers_by_type": {},
|
||||
"applicability": {
|
||||
"OPTIONAL": 1,
|
||||
"NOT_APPLICABLE": 20995
|
||||
},
|
||||
"rtc_applicability": {
|
||||
"NOT_APPLICABLE": 20996
|
||||
},
|
||||
"changed_products": 0,
|
||||
"examples": []
|
||||
},
|
||||
"RTC_ON_SIMULATION": {
|
||||
"rtc_enabled": true,
|
||||
"TOTAL_PRODUCTS": 20997,
|
||||
"COMPLETE_PRODUCTS": 0,
|
||||
"SAFE_BLOCKED_PRODUCTS": 20997,
|
||||
"FAILED_PRODUCTS": 0,
|
||||
"UNCLASSIFIED_PRODUCTS": 0,
|
||||
"COVERAGE_PERCENT": 0.0,
|
||||
"INVARIANT_PASS": true,
|
||||
"PIS_BLOCKERS": 0,
|
||||
"COFINS_BLOCKERS": 0,
|
||||
"RTC_BLOCKERS": 20996,
|
||||
"OTHER_BLOCKERS": 0,
|
||||
"blockers_by_type": {
|
||||
"CCLASSTRIB_MISSING": 20996
|
||||
},
|
||||
"applicability": {
|
||||
"OPTIONAL": 1,
|
||||
"NOT_APPLICABLE": 20995
|
||||
},
|
||||
"rtc_applicability": {
|
||||
"REQUIRED": 20996
|
||||
},
|
||||
"changed_products": 20997,
|
||||
"examples": [
|
||||
{
|
||||
"product": "000002",
|
||||
"BEFORE_STATUS": "COMPLETE",
|
||||
"AFTER_STATUS": "BLOCKED_FISCAL_DATA",
|
||||
"REASON": [
|
||||
"CCLASSTRIB_MISSING"
|
||||
]
|
||||
},
|
||||
{
|
||||
"product": "000003",
|
||||
"BEFORE_STATUS": "COMPLETE",
|
||||
"AFTER_STATUS": "BLOCKED_FISCAL_DATA",
|
||||
"REASON": [
|
||||
"CCLASSTRIB_MISSING"
|
||||
]
|
||||
},
|
||||
{
|
||||
"product": "000006",
|
||||
"BEFORE_STATUS": "COMPLETE",
|
||||
"AFTER_STATUS": "BLOCKED_FISCAL_DATA",
|
||||
"REASON": [
|
||||
"CCLASSTRIB_MISSING"
|
||||
]
|
||||
},
|
||||
{
|
||||
"product": "000008",
|
||||
"BEFORE_STATUS": "COMPLETE",
|
||||
"AFTER_STATUS": "BLOCKED_FISCAL_DATA",
|
||||
"REASON": [
|
||||
"CCLASSTRIB_MISSING"
|
||||
]
|
||||
},
|
||||
{
|
||||
"product": "000011",
|
||||
"BEFORE_STATUS": "COMPLETE",
|
||||
"AFTER_STATUS": "BLOCKED_FISCAL_DATA",
|
||||
"REASON": [
|
||||
"CCLASSTRIB_MISSING"
|
||||
]
|
||||
},
|
||||
{
|
||||
"product": "000013",
|
||||
"BEFORE_STATUS": "COMPLETE",
|
||||
"AFTER_STATUS": "BLOCKED_FISCAL_DATA",
|
||||
"REASON": [
|
||||
"CCLASSTRIB_MISSING"
|
||||
]
|
||||
},
|
||||
{
|
||||
"product": "000014",
|
||||
"BEFORE_STATUS": "COMPLETE",
|
||||
"AFTER_STATUS": "BLOCKED_FISCAL_DATA",
|
||||
"REASON": [
|
||||
"CCLASSTRIB_MISSING"
|
||||
]
|
||||
},
|
||||
{
|
||||
"product": "000016",
|
||||
"BEFORE_STATUS": "COMPLETE",
|
||||
"AFTER_STATUS": "BLOCKED_FISCAL_DATA",
|
||||
"REASON": [
|
||||
"CCLASSTRIB_MISSING"
|
||||
]
|
||||
},
|
||||
{
|
||||
"product": "000017",
|
||||
"BEFORE_STATUS": "COMPLETE",
|
||||
"AFTER_STATUS": "BLOCKED_FISCAL_DATA",
|
||||
"REASON": [
|
||||
"CCLASSTRIB_MISSING"
|
||||
]
|
||||
},
|
||||
{
|
||||
"product": "000018",
|
||||
"BEFORE_STATUS": "COMPLETE",
|
||||
"AFTER_STATUS": "BLOCKED_FISCAL_DATA",
|
||||
"REASON": [
|
||||
"CCLASSTRIB_MISSING"
|
||||
]
|
||||
},
|
||||
{
|
||||
"product": "000019",
|
||||
"BEFORE_STATUS": "COMPLETE",
|
||||
"AFTER_STATUS": "BLOCKED_FISCAL_DATA",
|
||||
"REASON": [
|
||||
"CCLASSTRIB_MISSING"
|
||||
]
|
||||
},
|
||||
{
|
||||
"product": "000020",
|
||||
"BEFORE_STATUS": "COMPLETE",
|
||||
"AFTER_STATUS": "BLOCKED_FISCAL_DATA",
|
||||
"REASON": [
|
||||
"CCLASSTRIB_MISSING"
|
||||
]
|
||||
},
|
||||
{
|
||||
"product": "000022",
|
||||
"BEFORE_STATUS": "COMPLETE",
|
||||
"AFTER_STATUS": "BLOCKED_FISCAL_DATA",
|
||||
"REASON": [
|
||||
"CCLASSTRIB_MISSING"
|
||||
]
|
||||
},
|
||||
{
|
||||
"product": "000023",
|
||||
"BEFORE_STATUS": "COMPLETE",
|
||||
"AFTER_STATUS": "BLOCKED_FISCAL_DATA",
|
||||
"REASON": [
|
||||
"CCLASSTRIB_MISSING"
|
||||
]
|
||||
},
|
||||
{
|
||||
"product": "000024",
|
||||
"BEFORE_STATUS": "COMPLETE",
|
||||
"AFTER_STATUS": "BLOCKED_FISCAL_DATA",
|
||||
"REASON": [
|
||||
"CCLASSTRIB_MISSING"
|
||||
]
|
||||
},
|
||||
{
|
||||
"product": "000025",
|
||||
"BEFORE_STATUS": "COMPLETE",
|
||||
"AFTER_STATUS": "BLOCKED_FISCAL_DATA",
|
||||
"REASON": [
|
||||
"CCLASSTRIB_MISSING"
|
||||
]
|
||||
},
|
||||
{
|
||||
"product": "000026",
|
||||
"BEFORE_STATUS": "COMPLETE",
|
||||
"AFTER_STATUS": "BLOCKED_FISCAL_DATA",
|
||||
"REASON": [
|
||||
"CCLASSTRIB_MISSING"
|
||||
]
|
||||
},
|
||||
{
|
||||
"product": "000027",
|
||||
"BEFORE_STATUS": "COMPLETE",
|
||||
"AFTER_STATUS": "BLOCKED_FISCAL_DATA",
|
||||
"REASON": [
|
||||
"CCLASSTRIB_MISSING"
|
||||
]
|
||||
},
|
||||
{
|
||||
"product": "000028",
|
||||
"BEFORE_STATUS": "COMPLETE",
|
||||
"AFTER_STATUS": "BLOCKED_FISCAL_DATA",
|
||||
"REASON": [
|
||||
"CCLASSTRIB_MISSING"
|
||||
]
|
||||
},
|
||||
{
|
||||
"product": "000029",
|
||||
"BEFORE_STATUS": "COMPLETE",
|
||||
"AFTER_STATUS": "BLOCKED_FISCAL_DATA",
|
||||
"REASON": [
|
||||
"CCLASSTRIB_MISSING"
|
||||
]
|
||||
},
|
||||
{
|
||||
"product": "000030",
|
||||
"BEFORE_STATUS": "COMPLETE",
|
||||
"AFTER_STATUS": "BLOCKED_FISCAL_DATA",
|
||||
"REASON": [
|
||||
"CCLASSTRIB_MISSING"
|
||||
]
|
||||
},
|
||||
{
|
||||
"product": "000031",
|
||||
"BEFORE_STATUS": "COMPLETE",
|
||||
"AFTER_STATUS": "BLOCKED_FISCAL_DATA",
|
||||
"REASON": [
|
||||
"CCLASSTRIB_MISSING"
|
||||
]
|
||||
},
|
||||
{
|
||||
"product": "000032",
|
||||
"BEFORE_STATUS": "COMPLETE",
|
||||
"AFTER_STATUS": "BLOCKED_FISCAL_DATA",
|
||||
"REASON": [
|
||||
"CCLASSTRIB_MISSING"
|
||||
]
|
||||
},
|
||||
{
|
||||
"product": "000033",
|
||||
"BEFORE_STATUS": "COMPLETE",
|
||||
"AFTER_STATUS": "BLOCKED_FISCAL_DATA",
|
||||
"REASON": [
|
||||
"CCLASSTRIB_MISSING"
|
||||
]
|
||||
},
|
||||
{
|
||||
"product": "000034",
|
||||
"BEFORE_STATUS": "COMPLETE",
|
||||
"AFTER_STATUS": "BLOCKED_FISCAL_DATA",
|
||||
"REASON": [
|
||||
"CCLASSTRIB_MISSING"
|
||||
]
|
||||
},
|
||||
{
|
||||
"product": "000038",
|
||||
"BEFORE_STATUS": "COMPLETE",
|
||||
"AFTER_STATUS": "BLOCKED_FISCAL_DATA",
|
||||
"REASON": [
|
||||
"CCLASSTRIB_MISSING"
|
||||
]
|
||||
},
|
||||
{
|
||||
"product": "000039",
|
||||
"BEFORE_STATUS": "COMPLETE",
|
||||
"AFTER_STATUS": "BLOCKED_FISCAL_DATA",
|
||||
"REASON": [
|
||||
"CCLASSTRIB_MISSING"
|
||||
]
|
||||
},
|
||||
{
|
||||
"product": "000040",
|
||||
"BEFORE_STATUS": "COMPLETE",
|
||||
"AFTER_STATUS": "BLOCKED_FISCAL_DATA",
|
||||
"REASON": [
|
||||
"CCLASSTRIB_MISSING"
|
||||
]
|
||||
},
|
||||
{
|
||||
"product": "000041",
|
||||
"BEFORE_STATUS": "COMPLETE",
|
||||
"AFTER_STATUS": "BLOCKED_FISCAL_DATA",
|
||||
"REASON": [
|
||||
"CCLASSTRIB_MISSING"
|
||||
]
|
||||
},
|
||||
{
|
||||
"product": "000042",
|
||||
"BEFORE_STATUS": "COMPLETE",
|
||||
"AFTER_STATUS": "BLOCKED_FISCAL_DATA",
|
||||
"REASON": [
|
||||
"CCLASSTRIB_MISSING"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"SAFETY": {
|
||||
"FIREBIRD_WRITES": 0,
|
||||
"WRITE_ATTEMPTS": 0,
|
||||
"WRITE_SUCCESSES": 0,
|
||||
"connection_mode": "READ COMMITTED READ ONLY (Agent reader); no write-capable connection opened"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,902 @@
|
||||
{
|
||||
"database": "RINCAO",
|
||||
"crt": null,
|
||||
"products": 894,
|
||||
"fiscal_products_read": 894,
|
||||
"BEFORE": {
|
||||
"COMPLETE_PRODUCTS": 894,
|
||||
"SAFE_BLOCKED_PRODUCTS": 0,
|
||||
"COVERAGE_PERCENT": 100.0,
|
||||
"UNKNOWN_APPLICABILITY_previous_model": 815
|
||||
},
|
||||
"runs": {
|
||||
"ERP_AS_FOUND": {
|
||||
"rtc_enabled": false,
|
||||
"TOTAL_PRODUCTS": 894,
|
||||
"COMPLETE_PRODUCTS": 815,
|
||||
"SAFE_BLOCKED_PRODUCTS": 79,
|
||||
"FAILED_PRODUCTS": 0,
|
||||
"UNCLASSIFIED_PRODUCTS": 0,
|
||||
"COVERAGE_PERCENT": 91.16,
|
||||
"INVARIANT_PASS": true,
|
||||
"PIS_BLOCKERS": 79,
|
||||
"COFINS_BLOCKERS": 79,
|
||||
"RTC_BLOCKERS": 0,
|
||||
"OTHER_BLOCKERS": 0,
|
||||
"blockers_by_type": {
|
||||
"COFINS_CST_INVALID": 79,
|
||||
"PIS_CST_INVALID": 79
|
||||
},
|
||||
"applicability": {
|
||||
"NOT_APPLICABLE": 815,
|
||||
"OPTIONAL": 79
|
||||
},
|
||||
"rtc_applicability": {
|
||||
"NOT_APPLICABLE": 894
|
||||
},
|
||||
"changed_products": 79,
|
||||
"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",
|
||||
"BEFORE_STATUS": "COMPLETE",
|
||||
"AFTER_STATUS": "BLOCKED_FISCAL_DATA",
|
||||
"REASON": [
|
||||
"COFINS_CST_INVALID",
|
||||
"PIS_CST_INVALID"
|
||||
]
|
||||
}
|
||||
],
|
||||
"context": {
|
||||
"rtc_enabled": false,
|
||||
"rtc_supported": true,
|
||||
"rtc_catalog_valid": true,
|
||||
"rtc_param": 0,
|
||||
"catalog_integrity": {
|
||||
"classes": 145,
|
||||
"cst": 18,
|
||||
"orphan_classes": 0,
|
||||
"bad_class_format": 0,
|
||||
"bad_cst_format": 0,
|
||||
"duplicates": 0,
|
||||
"valid": true
|
||||
},
|
||||
"crt": null,
|
||||
"products_evaluated": 894
|
||||
}
|
||||
},
|
||||
"RTC_OFF_SIMULATION": {
|
||||
"rtc_enabled": false,
|
||||
"TOTAL_PRODUCTS": 894,
|
||||
"COMPLETE_PRODUCTS": 815,
|
||||
"SAFE_BLOCKED_PRODUCTS": 79,
|
||||
"FAILED_PRODUCTS": 0,
|
||||
"UNCLASSIFIED_PRODUCTS": 0,
|
||||
"COVERAGE_PERCENT": 91.16,
|
||||
"INVARIANT_PASS": true,
|
||||
"PIS_BLOCKERS": 79,
|
||||
"COFINS_BLOCKERS": 79,
|
||||
"RTC_BLOCKERS": 0,
|
||||
"OTHER_BLOCKERS": 0,
|
||||
"blockers_by_type": {
|
||||
"COFINS_CST_INVALID": 79,
|
||||
"PIS_CST_INVALID": 79
|
||||
},
|
||||
"applicability": {
|
||||
"NOT_APPLICABLE": 815,
|
||||
"OPTIONAL": 79
|
||||
},
|
||||
"rtc_applicability": {
|
||||
"NOT_APPLICABLE": 894
|
||||
},
|
||||
"changed_products": 79,
|
||||
"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",
|
||||
"BEFORE_STATUS": "COMPLETE",
|
||||
"AFTER_STATUS": "BLOCKED_FISCAL_DATA",
|
||||
"REASON": [
|
||||
"COFINS_CST_INVALID",
|
||||
"PIS_CST_INVALID"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"RTC_ON_SIMULATION": {
|
||||
"rtc_enabled": true,
|
||||
"TOTAL_PRODUCTS": 894,
|
||||
"COMPLETE_PRODUCTS": 0,
|
||||
"SAFE_BLOCKED_PRODUCTS": 894,
|
||||
"FAILED_PRODUCTS": 0,
|
||||
"UNCLASSIFIED_PRODUCTS": 0,
|
||||
"COVERAGE_PERCENT": 0.0,
|
||||
"INVARIANT_PASS": true,
|
||||
"PIS_BLOCKERS": 79,
|
||||
"COFINS_BLOCKERS": 79,
|
||||
"RTC_BLOCKERS": 894,
|
||||
"OTHER_BLOCKERS": 0,
|
||||
"blockers_by_type": {
|
||||
"CCLASSTRIB_MISSING": 894,
|
||||
"COFINS_CST_INVALID": 79,
|
||||
"PIS_CST_INVALID": 79
|
||||
},
|
||||
"applicability": {
|
||||
"NOT_APPLICABLE": 815,
|
||||
"OPTIONAL": 79
|
||||
},
|
||||
"rtc_applicability": {
|
||||
"REQUIRED": 894
|
||||
},
|
||||
"changed_products": 894,
|
||||
"examples": [
|
||||
{
|
||||
"product": "000001",
|
||||
"BEFORE_STATUS": "COMPLETE",
|
||||
"AFTER_STATUS": "BLOCKED_FISCAL_DATA",
|
||||
"REASON": [
|
||||
"CCLASSTRIB_MISSING"
|
||||
]
|
||||
},
|
||||
{
|
||||
"product": "000004",
|
||||
"BEFORE_STATUS": "COMPLETE",
|
||||
"AFTER_STATUS": "BLOCKED_FISCAL_DATA",
|
||||
"REASON": [
|
||||
"CCLASSTRIB_MISSING"
|
||||
]
|
||||
},
|
||||
{
|
||||
"product": "000005",
|
||||
"BEFORE_STATUS": "COMPLETE",
|
||||
"AFTER_STATUS": "BLOCKED_FISCAL_DATA",
|
||||
"REASON": [
|
||||
"CCLASSTRIB_MISSING"
|
||||
]
|
||||
},
|
||||
{
|
||||
"product": "000006",
|
||||
"BEFORE_STATUS": "COMPLETE",
|
||||
"AFTER_STATUS": "BLOCKED_FISCAL_DATA",
|
||||
"REASON": [
|
||||
"CCLASSTRIB_MISSING"
|
||||
]
|
||||
},
|
||||
{
|
||||
"product": "000007",
|
||||
"BEFORE_STATUS": "COMPLETE",
|
||||
"AFTER_STATUS": "BLOCKED_FISCAL_DATA",
|
||||
"REASON": [
|
||||
"CCLASSTRIB_MISSING"
|
||||
]
|
||||
},
|
||||
{
|
||||
"product": "000008",
|
||||
"BEFORE_STATUS": "COMPLETE",
|
||||
"AFTER_STATUS": "BLOCKED_FISCAL_DATA",
|
||||
"REASON": [
|
||||
"CCLASSTRIB_MISSING"
|
||||
]
|
||||
},
|
||||
{
|
||||
"product": "000009",
|
||||
"BEFORE_STATUS": "COMPLETE",
|
||||
"AFTER_STATUS": "BLOCKED_FISCAL_DATA",
|
||||
"REASON": [
|
||||
"CCLASSTRIB_MISSING"
|
||||
]
|
||||
},
|
||||
{
|
||||
"product": "000010",
|
||||
"BEFORE_STATUS": "COMPLETE",
|
||||
"AFTER_STATUS": "BLOCKED_FISCAL_DATA",
|
||||
"REASON": [
|
||||
"CCLASSTRIB_MISSING"
|
||||
]
|
||||
},
|
||||
{
|
||||
"product": "000011",
|
||||
"BEFORE_STATUS": "COMPLETE",
|
||||
"AFTER_STATUS": "BLOCKED_FISCAL_DATA",
|
||||
"REASON": [
|
||||
"CCLASSTRIB_MISSING"
|
||||
]
|
||||
},
|
||||
{
|
||||
"product": "000012",
|
||||
"BEFORE_STATUS": "COMPLETE",
|
||||
"AFTER_STATUS": "BLOCKED_FISCAL_DATA",
|
||||
"REASON": [
|
||||
"CCLASSTRIB_MISSING"
|
||||
]
|
||||
},
|
||||
{
|
||||
"product": "000013",
|
||||
"BEFORE_STATUS": "COMPLETE",
|
||||
"AFTER_STATUS": "BLOCKED_FISCAL_DATA",
|
||||
"REASON": [
|
||||
"CCLASSTRIB_MISSING"
|
||||
]
|
||||
},
|
||||
{
|
||||
"product": "000014",
|
||||
"BEFORE_STATUS": "COMPLETE",
|
||||
"AFTER_STATUS": "BLOCKED_FISCAL_DATA",
|
||||
"REASON": [
|
||||
"CCLASSTRIB_MISSING"
|
||||
]
|
||||
},
|
||||
{
|
||||
"product": "000015",
|
||||
"BEFORE_STATUS": "COMPLETE",
|
||||
"AFTER_STATUS": "BLOCKED_FISCAL_DATA",
|
||||
"REASON": [
|
||||
"CCLASSTRIB_MISSING"
|
||||
]
|
||||
},
|
||||
{
|
||||
"product": "000016",
|
||||
"BEFORE_STATUS": "COMPLETE",
|
||||
"AFTER_STATUS": "BLOCKED_FISCAL_DATA",
|
||||
"REASON": [
|
||||
"CCLASSTRIB_MISSING"
|
||||
]
|
||||
},
|
||||
{
|
||||
"product": "000017",
|
||||
"BEFORE_STATUS": "COMPLETE",
|
||||
"AFTER_STATUS": "BLOCKED_FISCAL_DATA",
|
||||
"REASON": [
|
||||
"CCLASSTRIB_MISSING"
|
||||
]
|
||||
},
|
||||
{
|
||||
"product": "000018",
|
||||
"BEFORE_STATUS": "COMPLETE",
|
||||
"AFTER_STATUS": "BLOCKED_FISCAL_DATA",
|
||||
"REASON": [
|
||||
"CCLASSTRIB_MISSING"
|
||||
]
|
||||
},
|
||||
{
|
||||
"product": "000019",
|
||||
"BEFORE_STATUS": "COMPLETE",
|
||||
"AFTER_STATUS": "BLOCKED_FISCAL_DATA",
|
||||
"REASON": [
|
||||
"CCLASSTRIB_MISSING"
|
||||
]
|
||||
},
|
||||
{
|
||||
"product": "000020",
|
||||
"BEFORE_STATUS": "COMPLETE",
|
||||
"AFTER_STATUS": "BLOCKED_FISCAL_DATA",
|
||||
"REASON": [
|
||||
"CCLASSTRIB_MISSING"
|
||||
]
|
||||
},
|
||||
{
|
||||
"product": "000021",
|
||||
"BEFORE_STATUS": "COMPLETE",
|
||||
"AFTER_STATUS": "BLOCKED_FISCAL_DATA",
|
||||
"REASON": [
|
||||
"CCLASSTRIB_MISSING"
|
||||
]
|
||||
},
|
||||
{
|
||||
"product": "000022",
|
||||
"BEFORE_STATUS": "COMPLETE",
|
||||
"AFTER_STATUS": "BLOCKED_FISCAL_DATA",
|
||||
"REASON": [
|
||||
"CCLASSTRIB_MISSING"
|
||||
]
|
||||
},
|
||||
{
|
||||
"product": "000023",
|
||||
"BEFORE_STATUS": "COMPLETE",
|
||||
"AFTER_STATUS": "BLOCKED_FISCAL_DATA",
|
||||
"REASON": [
|
||||
"CCLASSTRIB_MISSING"
|
||||
]
|
||||
},
|
||||
{
|
||||
"product": "000024",
|
||||
"BEFORE_STATUS": "COMPLETE",
|
||||
"AFTER_STATUS": "BLOCKED_FISCAL_DATA",
|
||||
"REASON": [
|
||||
"CCLASSTRIB_MISSING"
|
||||
]
|
||||
},
|
||||
{
|
||||
"product": "000025",
|
||||
"BEFORE_STATUS": "COMPLETE",
|
||||
"AFTER_STATUS": "BLOCKED_FISCAL_DATA",
|
||||
"REASON": [
|
||||
"CCLASSTRIB_MISSING"
|
||||
]
|
||||
},
|
||||
{
|
||||
"product": "000026",
|
||||
"BEFORE_STATUS": "COMPLETE",
|
||||
"AFTER_STATUS": "BLOCKED_FISCAL_DATA",
|
||||
"REASON": [
|
||||
"CCLASSTRIB_MISSING"
|
||||
]
|
||||
},
|
||||
{
|
||||
"product": "000027",
|
||||
"BEFORE_STATUS": "COMPLETE",
|
||||
"AFTER_STATUS": "BLOCKED_FISCAL_DATA",
|
||||
"REASON": [
|
||||
"CCLASSTRIB_MISSING"
|
||||
]
|
||||
},
|
||||
{
|
||||
"product": "000028",
|
||||
"BEFORE_STATUS": "COMPLETE",
|
||||
"AFTER_STATUS": "BLOCKED_FISCAL_DATA",
|
||||
"REASON": [
|
||||
"CCLASSTRIB_MISSING"
|
||||
]
|
||||
},
|
||||
{
|
||||
"product": "000029",
|
||||
"BEFORE_STATUS": "COMPLETE",
|
||||
"AFTER_STATUS": "BLOCKED_FISCAL_DATA",
|
||||
"REASON": [
|
||||
"CCLASSTRIB_MISSING"
|
||||
]
|
||||
},
|
||||
{
|
||||
"product": "000030",
|
||||
"BEFORE_STATUS": "COMPLETE",
|
||||
"AFTER_STATUS": "BLOCKED_FISCAL_DATA",
|
||||
"REASON": [
|
||||
"CCLASSTRIB_MISSING"
|
||||
]
|
||||
},
|
||||
{
|
||||
"product": "000031",
|
||||
"BEFORE_STATUS": "COMPLETE",
|
||||
"AFTER_STATUS": "BLOCKED_FISCAL_DATA",
|
||||
"REASON": [
|
||||
"CCLASSTRIB_MISSING"
|
||||
]
|
||||
},
|
||||
{
|
||||
"product": "000032",
|
||||
"BEFORE_STATUS": "COMPLETE",
|
||||
"AFTER_STATUS": "BLOCKED_FISCAL_DATA",
|
||||
"REASON": [
|
||||
"CCLASSTRIB_MISSING"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"SAFETY": {
|
||||
"FIREBIRD_WRITES": 0,
|
||||
"WRITE_ATTEMPTS": 0,
|
||||
"WRITE_SUCCESSES": 0,
|
||||
"connection_mode": "READ COMMITTED READ ONLY (Agent reader); no write-capable connection opened"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,392 @@
|
||||
{
|
||||
"mode": "FISCAL_ONLY (no Hub decision)",
|
||||
"database": "UNIAO",
|
||||
"crt": "3",
|
||||
"products": 8942,
|
||||
"fiscal_products_read": 8942,
|
||||
"BEFORE": {
|
||||
"COMPLETE_PRODUCTS": 8942,
|
||||
"SAFE_BLOCKED_PRODUCTS": 0,
|
||||
"COVERAGE_PERCENT": 100.0,
|
||||
"UNKNOWN_APPLICABILITY_previous_model": 8170
|
||||
},
|
||||
"runs": {
|
||||
"ERP_AS_FOUND": {
|
||||
"rtc_enabled": false,
|
||||
"TOTAL_PRODUCTS": 8942,
|
||||
"COMPLETE_PRODUCTS": 8940,
|
||||
"SAFE_BLOCKED_PRODUCTS": 2,
|
||||
"FAILED_PRODUCTS": 0,
|
||||
"UNCLASSIFIED_PRODUCTS": 0,
|
||||
"COVERAGE_PERCENT": 99.98,
|
||||
"INVARIANT_PASS": true,
|
||||
"PIS_BLOCKERS": 0,
|
||||
"COFINS_BLOCKERS": 2,
|
||||
"RTC_BLOCKERS": 0,
|
||||
"OTHER_BLOCKERS": 0,
|
||||
"blockers_by_type": {
|
||||
"COFINS_CST_INVALID": 2
|
||||
},
|
||||
"applicability": {
|
||||
"NOT_APPLICABLE": 8170,
|
||||
"OPTIONAL": 772
|
||||
},
|
||||
"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"
|
||||
]
|
||||
}
|
||||
],
|
||||
"context": {
|
||||
"rtc_enabled": false,
|
||||
"rtc_supported": true,
|
||||
"rtc_catalog_valid": true,
|
||||
"rtc_param": 0,
|
||||
"catalog_integrity": {
|
||||
"classes": 145,
|
||||
"cst": 18,
|
||||
"orphan_classes": 0,
|
||||
"bad_class_format": 0,
|
||||
"bad_cst_format": 0,
|
||||
"duplicates": 0,
|
||||
"valid": true
|
||||
},
|
||||
"crt": "3",
|
||||
"products_evaluated": 8942
|
||||
}
|
||||
},
|
||||
"RTC_OFF_SIMULATION": {
|
||||
"rtc_enabled": false,
|
||||
"TOTAL_PRODUCTS": 8942,
|
||||
"COMPLETE_PRODUCTS": 8940,
|
||||
"SAFE_BLOCKED_PRODUCTS": 2,
|
||||
"FAILED_PRODUCTS": 0,
|
||||
"UNCLASSIFIED_PRODUCTS": 0,
|
||||
"COVERAGE_PERCENT": 99.98,
|
||||
"INVARIANT_PASS": true,
|
||||
"PIS_BLOCKERS": 0,
|
||||
"COFINS_BLOCKERS": 2,
|
||||
"RTC_BLOCKERS": 0,
|
||||
"OTHER_BLOCKERS": 0,
|
||||
"blockers_by_type": {
|
||||
"COFINS_CST_INVALID": 2
|
||||
},
|
||||
"applicability": {
|
||||
"NOT_APPLICABLE": 8170,
|
||||
"OPTIONAL": 772
|
||||
},
|
||||
"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"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"RTC_ON_SIMULATION": {
|
||||
"rtc_enabled": true,
|
||||
"TOTAL_PRODUCTS": 8942,
|
||||
"COMPLETE_PRODUCTS": 0,
|
||||
"SAFE_BLOCKED_PRODUCTS": 8942,
|
||||
"FAILED_PRODUCTS": 0,
|
||||
"UNCLASSIFIED_PRODUCTS": 0,
|
||||
"COVERAGE_PERCENT": 0.0,
|
||||
"INVARIANT_PASS": true,
|
||||
"PIS_BLOCKERS": 0,
|
||||
"COFINS_BLOCKERS": 2,
|
||||
"RTC_BLOCKERS": 8942,
|
||||
"OTHER_BLOCKERS": 0,
|
||||
"blockers_by_type": {
|
||||
"CCLASSTRIB_MISSING": 8942,
|
||||
"COFINS_CST_INVALID": 2
|
||||
},
|
||||
"applicability": {
|
||||
"NOT_APPLICABLE": 8170,
|
||||
"OPTIONAL": 772
|
||||
},
|
||||
"rtc_applicability": {
|
||||
"REQUIRED": 8942
|
||||
},
|
||||
"changed_products": 8942,
|
||||
"examples": [
|
||||
{
|
||||
"product": "000008",
|
||||
"BEFORE_STATUS": "COMPLETE",
|
||||
"AFTER_STATUS": "BLOCKED_FISCAL_DATA",
|
||||
"REASON": [
|
||||
"CCLASSTRIB_MISSING"
|
||||
]
|
||||
},
|
||||
{
|
||||
"product": "000009",
|
||||
"BEFORE_STATUS": "COMPLETE",
|
||||
"AFTER_STATUS": "BLOCKED_FISCAL_DATA",
|
||||
"REASON": [
|
||||
"CCLASSTRIB_MISSING"
|
||||
]
|
||||
},
|
||||
{
|
||||
"product": "000014",
|
||||
"BEFORE_STATUS": "COMPLETE",
|
||||
"AFTER_STATUS": "BLOCKED_FISCAL_DATA",
|
||||
"REASON": [
|
||||
"CCLASSTRIB_MISSING"
|
||||
]
|
||||
},
|
||||
{
|
||||
"product": "000015",
|
||||
"BEFORE_STATUS": "COMPLETE",
|
||||
"AFTER_STATUS": "BLOCKED_FISCAL_DATA",
|
||||
"REASON": [
|
||||
"CCLASSTRIB_MISSING"
|
||||
]
|
||||
},
|
||||
{
|
||||
"product": "000016",
|
||||
"BEFORE_STATUS": "COMPLETE",
|
||||
"AFTER_STATUS": "BLOCKED_FISCAL_DATA",
|
||||
"REASON": [
|
||||
"CCLASSTRIB_MISSING"
|
||||
]
|
||||
},
|
||||
{
|
||||
"product": "000017",
|
||||
"BEFORE_STATUS": "COMPLETE",
|
||||
"AFTER_STATUS": "BLOCKED_FISCAL_DATA",
|
||||
"REASON": [
|
||||
"CCLASSTRIB_MISSING"
|
||||
]
|
||||
},
|
||||
{
|
||||
"product": "000018",
|
||||
"BEFORE_STATUS": "COMPLETE",
|
||||
"AFTER_STATUS": "BLOCKED_FISCAL_DATA",
|
||||
"REASON": [
|
||||
"CCLASSTRIB_MISSING"
|
||||
]
|
||||
},
|
||||
{
|
||||
"product": "000019",
|
||||
"BEFORE_STATUS": "COMPLETE",
|
||||
"AFTER_STATUS": "BLOCKED_FISCAL_DATA",
|
||||
"REASON": [
|
||||
"CCLASSTRIB_MISSING"
|
||||
]
|
||||
},
|
||||
{
|
||||
"product": "000020",
|
||||
"BEFORE_STATUS": "COMPLETE",
|
||||
"AFTER_STATUS": "BLOCKED_FISCAL_DATA",
|
||||
"REASON": [
|
||||
"CCLASSTRIB_MISSING"
|
||||
]
|
||||
},
|
||||
{
|
||||
"product": "000021",
|
||||
"BEFORE_STATUS": "COMPLETE",
|
||||
"AFTER_STATUS": "BLOCKED_FISCAL_DATA",
|
||||
"REASON": [
|
||||
"CCLASSTRIB_MISSING"
|
||||
]
|
||||
},
|
||||
{
|
||||
"product": "000022",
|
||||
"BEFORE_STATUS": "COMPLETE",
|
||||
"AFTER_STATUS": "BLOCKED_FISCAL_DATA",
|
||||
"REASON": [
|
||||
"CCLASSTRIB_MISSING"
|
||||
]
|
||||
},
|
||||
{
|
||||
"product": "000023",
|
||||
"BEFORE_STATUS": "COMPLETE",
|
||||
"AFTER_STATUS": "BLOCKED_FISCAL_DATA",
|
||||
"REASON": [
|
||||
"CCLASSTRIB_MISSING"
|
||||
]
|
||||
},
|
||||
{
|
||||
"product": "000024",
|
||||
"BEFORE_STATUS": "COMPLETE",
|
||||
"AFTER_STATUS": "BLOCKED_FISCAL_DATA",
|
||||
"REASON": [
|
||||
"CCLASSTRIB_MISSING"
|
||||
]
|
||||
},
|
||||
{
|
||||
"product": "000025",
|
||||
"BEFORE_STATUS": "COMPLETE",
|
||||
"AFTER_STATUS": "BLOCKED_FISCAL_DATA",
|
||||
"REASON": [
|
||||
"CCLASSTRIB_MISSING"
|
||||
]
|
||||
},
|
||||
{
|
||||
"product": "000026",
|
||||
"BEFORE_STATUS": "COMPLETE",
|
||||
"AFTER_STATUS": "BLOCKED_FISCAL_DATA",
|
||||
"REASON": [
|
||||
"CCLASSTRIB_MISSING"
|
||||
]
|
||||
},
|
||||
{
|
||||
"product": "000027",
|
||||
"BEFORE_STATUS": "COMPLETE",
|
||||
"AFTER_STATUS": "BLOCKED_FISCAL_DATA",
|
||||
"REASON": [
|
||||
"CCLASSTRIB_MISSING"
|
||||
]
|
||||
},
|
||||
{
|
||||
"product": "000028",
|
||||
"BEFORE_STATUS": "COMPLETE",
|
||||
"AFTER_STATUS": "BLOCKED_FISCAL_DATA",
|
||||
"REASON": [
|
||||
"CCLASSTRIB_MISSING"
|
||||
]
|
||||
},
|
||||
{
|
||||
"product": "000029",
|
||||
"BEFORE_STATUS": "COMPLETE",
|
||||
"AFTER_STATUS": "BLOCKED_FISCAL_DATA",
|
||||
"REASON": [
|
||||
"CCLASSTRIB_MISSING"
|
||||
]
|
||||
},
|
||||
{
|
||||
"product": "000030",
|
||||
"BEFORE_STATUS": "COMPLETE",
|
||||
"AFTER_STATUS": "BLOCKED_FISCAL_DATA",
|
||||
"REASON": [
|
||||
"CCLASSTRIB_MISSING"
|
||||
]
|
||||
},
|
||||
{
|
||||
"product": "000031",
|
||||
"BEFORE_STATUS": "COMPLETE",
|
||||
"AFTER_STATUS": "BLOCKED_FISCAL_DATA",
|
||||
"REASON": [
|
||||
"CCLASSTRIB_MISSING"
|
||||
]
|
||||
},
|
||||
{
|
||||
"product": "000032",
|
||||
"BEFORE_STATUS": "COMPLETE",
|
||||
"AFTER_STATUS": "BLOCKED_FISCAL_DATA",
|
||||
"REASON": [
|
||||
"CCLASSTRIB_MISSING"
|
||||
]
|
||||
},
|
||||
{
|
||||
"product": "000033",
|
||||
"BEFORE_STATUS": "COMPLETE",
|
||||
"AFTER_STATUS": "BLOCKED_FISCAL_DATA",
|
||||
"REASON": [
|
||||
"CCLASSTRIB_MISSING"
|
||||
]
|
||||
},
|
||||
{
|
||||
"product": "000034",
|
||||
"BEFORE_STATUS": "COMPLETE",
|
||||
"AFTER_STATUS": "BLOCKED_FISCAL_DATA",
|
||||
"REASON": [
|
||||
"CCLASSTRIB_MISSING"
|
||||
]
|
||||
},
|
||||
{
|
||||
"product": "000035",
|
||||
"BEFORE_STATUS": "COMPLETE",
|
||||
"AFTER_STATUS": "BLOCKED_FISCAL_DATA",
|
||||
"REASON": [
|
||||
"CCLASSTRIB_MISSING"
|
||||
]
|
||||
},
|
||||
{
|
||||
"product": "000036",
|
||||
"BEFORE_STATUS": "COMPLETE",
|
||||
"AFTER_STATUS": "BLOCKED_FISCAL_DATA",
|
||||
"REASON": [
|
||||
"CCLASSTRIB_MISSING"
|
||||
]
|
||||
},
|
||||
{
|
||||
"product": "000037",
|
||||
"BEFORE_STATUS": "COMPLETE",
|
||||
"AFTER_STATUS": "BLOCKED_FISCAL_DATA",
|
||||
"REASON": [
|
||||
"CCLASSTRIB_MISSING"
|
||||
]
|
||||
},
|
||||
{
|
||||
"product": "000038",
|
||||
"BEFORE_STATUS": "COMPLETE",
|
||||
"AFTER_STATUS": "BLOCKED_FISCAL_DATA",
|
||||
"REASON": [
|
||||
"CCLASSTRIB_MISSING"
|
||||
]
|
||||
},
|
||||
{
|
||||
"product": "000039",
|
||||
"BEFORE_STATUS": "COMPLETE",
|
||||
"AFTER_STATUS": "BLOCKED_FISCAL_DATA",
|
||||
"REASON": [
|
||||
"CCLASSTRIB_MISSING"
|
||||
]
|
||||
},
|
||||
{
|
||||
"product": "000040",
|
||||
"BEFORE_STATUS": "COMPLETE",
|
||||
"AFTER_STATUS": "BLOCKED_FISCAL_DATA",
|
||||
"REASON": [
|
||||
"CCLASSTRIB_MISSING"
|
||||
]
|
||||
},
|
||||
{
|
||||
"product": "000041",
|
||||
"BEFORE_STATUS": "COMPLETE",
|
||||
"AFTER_STATUS": "BLOCKED_FISCAL_DATA",
|
||||
"REASON": [
|
||||
"CCLASSTRIB_MISSING"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"SAFETY": {
|
||||
"FIREBIRD_WRITES": 0,
|
||||
"WRITE_ATTEMPTS": 0,
|
||||
"WRITE_SUCCESSES": 0,
|
||||
"connection_mode": "READ COMMITTED READ ONLY (Agent reader); no write-capable connection opened"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
{
|
||||
"generated": "2026-09-24",
|
||||
"items": [
|
||||
{
|
||||
"name": "LC 123/2006 (Simples Nacional), compilada",
|
||||
"source_url": "https://www.planalto.gov.br/ccivil_03/leis/lcp/lcp123.htm",
|
||||
"accessed_at": "2026-09-24",
|
||||
"sha256": "8c446540a798b1fe5cfff380cf1a736394ea5bfe0a8ce7351151dca77d02eee4",
|
||||
"bytes": 1622252,
|
||||
"note": "art. 13 IV/V (Cofins e PIS no DAS), art. 18 par. 4-A I (segregacao da receita monofasica). Obtida via Internet Archive (planalto recusa conexao direta)"
|
||||
},
|
||||
{
|
||||
"name": "Lei 9.718/1998, compilada",
|
||||
"source_url": "https://www.planalto.gov.br/ccivil_03/leis/l9718compilada.htm",
|
||||
"accessed_at": "2026-09-24",
|
||||
"sha256": "79b8c471f84ec9d702e6e6ef8cb28198143c0cbc4db59a14714c5c5d1b188906",
|
||||
"bytes": 75858,
|
||||
"note": "art. 2-3: PIS/Cofins sobre faturamento (regime cumulativo)"
|
||||
},
|
||||
{
|
||||
"name": "Lei 10.637/2002, compilada",
|
||||
"source_url": "https://www.planalto.gov.br/ccivil_03/leis/2002/l10637.htm",
|
||||
"accessed_at": "2026-09-24",
|
||||
"sha256": "eaa97c5448184815c96fb68ffcd6e136721d66965b609af60b6832fdd741382e",
|
||||
"bytes": 238160,
|
||||
"note": "art. 1: PIS/Pasep nao cumulativo"
|
||||
},
|
||||
{
|
||||
"name": "Lei 10.833/2003, compilada",
|
||||
"source_url": "https://www.planalto.gov.br/ccivil_03/leis/2003/l10.833.htm",
|
||||
"accessed_at": "2026-09-24",
|
||||
"sha256": "4d562e534472c620c404e55e56544b1b17ead935fd9fcc767fe5a19eb6062a3a",
|
||||
"bytes": 533449,
|
||||
"note": "art. 1: Cofins nao cumulativa"
|
||||
},
|
||||
{
|
||||
"name": "Lei 10.147/2000, compilada",
|
||||
"source_url": "https://www.planalto.gov.br/ccivil_03/leis/l10147.htm",
|
||||
"accessed_at": "2026-09-24",
|
||||
"sha256": "4b5d3c4c62ae967516b42be56c7cc8fb6315590fd98c4202c91fb2877f59c703",
|
||||
"bytes": 35624,
|
||||
"note": "incidencia monofasica (medicamentos, perfumaria/higiene)"
|
||||
},
|
||||
{
|
||||
"name": "LC 214/2025, compilada",
|
||||
"source_url": "https://www.planalto.gov.br/ccivil_03/leis/lcp/lcp214.htm",
|
||||
"accessed_at": "2026-09-24",
|
||||
"sha256": "5c7d59b3c49e5b7c066c2ad28bedf32791193a676ec1a163bec14bea5fa2f680",
|
||||
"bytes": 5400654,
|
||||
"note": "art. 348: transicao 2026 (IBS/CBS compensados); Simples so a partir de 2027"
|
||||
},
|
||||
{
|
||||
"name": "NT 2025.002-RTC v1.51 (NF-e/NFC-e IBS/CBS/IS)",
|
||||
"source_url": "https://www.nfe.fazenda.gov.br/portal/exibirArquivo.aspx?conteudo=AKD/muSmiIY=",
|
||||
"accessed_at": "2026-09-24",
|
||||
"sha256": "a4aaaa181522b43cd90b502f8ccb9e4bdabea30fed838e2d0790be5ba77254a4",
|
||||
"bytes": 2182361,
|
||||
"note": "publicada 04/08/2026; CRT 1/2/4: orientacoes IBS/CBS em NT futura, tributacao so a partir de 2027 (art. 348 LC 214); grupos Q/S (PIS/COFINS) obrigatorios no documento (rejeicoes 745/748)"
|
||||
},
|
||||
{
|
||||
"name": "NT 2026.002 v1.10a",
|
||||
"source_url": "https://www.nfe.fazenda.gov.br/portal/exibirArquivo.aspx?conteudo=8zOzIahvn8I=",
|
||||
"accessed_at": "2026-09-24",
|
||||
"sha256": "705f771830565c209d815659347ac4ea251901c77064cc163ce5c93f311e6e64",
|
||||
"bytes": 767698,
|
||||
"note": "publicada 25/08/2026 (consultada; sem regra adicional de PIS/COFINS de produto)"
|
||||
}
|
||||
],
|
||||
"sped_tables": "docs/sources/SOURCES_MANIFEST_V3.json + SPED_PISCOFINS_TABLES_V3.json (4.3.3/4.3.4 CST domain; 4.3.10-4.3.15 special regimes)"
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
# Completude fiscal unificada (UNIFIED_FISCAL_COVERAGE)
|
||||
|
||||
Uma definição de "completo", um painel, uma fila de blockers, uma cobertura.
|
||||
|
||||
## Definição
|
||||
```
|
||||
campos obrigatórios(produto, empresa) =
|
||||
campos fiscais base aplicáveis (NCM, CEST, CFOP, ICMS/CST/CSOSN, IPI ... — decididos pelo motor de decisão do Hub)
|
||||
+ PIS/COFINS aplicáveis (docs/pis_cofins_applicability.md)
|
||||
+ RTC (cClassTrib → CST IBS/CBS) SOMENTE quando o ERP tem a Reforma Tributária ligada (docs/rtc_pis_cofins_model.md)
|
||||
```
|
||||
Campos `NOT_APPLICABLE` não contam contra a completude. Nenhum campo é exigido só porque a coluna existe.
|
||||
|
||||
## Onde vive (nenhuma cópia paralela)
|
||||
| Peça | Arquivo |
|
||||
|---|---|
|
||||
| regras puras (PIS/COFINS, RTC, `evaluate_product`) | `central/app/unified_fiscal_coverage.py` |
|
||||
| estado do produto (`COMPLETE`/`CORRECTED`/`BLOCKED_*`/`BLOCKED_FISCAL_DATA`/`FAILED_TECHNICAL`/`NEEDS_REVIEW`) | `central/app/operational_state.py` (`classify_product_state(..., fiscal=)`) |
|
||||
| leitura do ERP (somente SELECT) | `agent/src/gti_fiscal_agent/unified_fiscal.py` |
|
||||
| integração no job do Agent | `agent/scripts/run_agent_service.py` (`_unified_fiscal`, `_fiscal_blocked_products`) |
|
||||
| snapshot / cobertura no Hub | `central/app/api.py` (`_apply_job_outcome` — inalterado: usa o mesmo `operational`) |
|
||||
| fila única + fechamento automático | `central/app/blocked_products.py` (`resolve_stale_fiscal_blockers`) |
|
||||
| painel | `central/app/panel_presentation.py`, `central/templates/{dashboard,blockers,agent_detail}.html` |
|
||||
|
||||
## Estados finais do produto
|
||||
`COMPLETE` (+`CORRECTED`) · `SAFE_BLOCKED` (`BLOCKED_*`, incluindo `BLOCKED_FISCAL_DATA` para PIS/COFINS/RTC) · `FAILED_TECHNICAL` · `NEEDS_REVIEW` (não classificado; só incidência PIS/COFINS fora do vocabulário do SGV).
|
||||
|
||||
Invariante: `TOTAL = COMPLETE + SAFE_BLOCKED + FAILED + UNCLASSIFIED` (o Hub calcula "não classificados" como `total − completos − bloqueados − falhas`, então a igualdade fecha por construção e é reverificada em `coverage_invariant_pass`).
|
||||
Precedência: um blocker de decisão (NCM, conflito, contexto) permanece o estado principal; blockers fiscais só rebaixam produto que seria completo. Um blocker real vale mais que `NEEDS_REVIEW`.
|
||||
|
||||
## Fila única e fechamento
|
||||
Cada blocker fiscal é um blocker comum: `blocker_type=BLOCKED_FISCAL_DATA`, `field ∈ {PIS, COFINS, RTC}`, `reason_code` = tipo (ex.: `PIS_CST_INVALID`). A cada job de varredura completa (`fiscal_context.evaluated_all=true`) o Agent reenvia todos os blockers vigentes e o Hub fecha (`AUTO_REEVALUATION`) os blockers PIS/COFINS/RTC da instalação não reenviados (dado corrigido, ou RTC desligada). Reprocesso parcial não fecha nada de outros produtos. O fingerprint do blocker inclui os valores fiscais lidos.
|
||||
|
||||
## Painel
|
||||
- Dashboard: um único bloco "Cobertura dos produtos" (Produtos, Completos, Bloqueados com segurança, Falhas técnicas, Não classificados, Cobertura fiscal). Breakdown de blockers: `NCM, CEST, CFOP, ST, ICMS, CSOSN, PIS, COFINS, RTC, OTHER` (categoria com 0 fica esmaecida).
|
||||
- Detalhe do Agent: a mesma cobertura + uma linha discreta "Reforma Tributária: Ativa/Desativada" (do último job). RTC desativada nunca aparece como problema. O bloco "Escopo anterior/ampliado" foi removido.
|
||||
- Removidos do código: `expanded_scope_summary`, `expanded_scope_view`, `LEGACY_SCOPE_COVERAGE`, `EXPANDED_SCOPE_COVERAGE`, `UNKNOWN_APPLICABILITY` (como estado em massa).
|
||||
|
||||
## 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):
|
||||
|
||||
| | 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) |
|
||||
|
||||
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.
|
||||
@@ -0,0 +1,95 @@
|
||||
"""READ-ONLY end-to-end of the UNIFIED fiscal coverage over a real Firebird database (default: TESTESOFICIAIS.FDB).
|
||||
|
||||
python scripts/unified_coverage_e2e.py [--db TESTESOFICIAIS] [--out docs/runs/unified_coverage_e2e.json]
|
||||
|
||||
Reads the products with the Agent's own reader (SELECT only), classifies them through the Hub, then computes the operational states
|
||||
BEFORE (decision only) and AFTER (decision + PIS/COFINS + RTC when enabled). The ERP's RTC switch is reported as found; a what-if run
|
||||
with RTC forced ON / OFF uses the pure evaluator only (nothing is written, PARAMETROS is never touched).
|
||||
"""
|
||||
import argparse
|
||||
import json
|
||||
import os
|
||||
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 central.app.operational_state import classify_product_state, summarize_operational_states # noqa: E402
|
||||
from gti_fiscal_agent import firebird_reader, unified_fiscal # noqa: E402
|
||||
from gti_fiscal_agent.client import HubClient # noqa: E402
|
||||
from gti_fiscal_agent.runner import to_hub_product # noqa: E402
|
||||
|
||||
PIS_COLS = ("COD_ENTRADA_PIS", "COD_SAIDA_PIS", "ALIQ_PIS", "COD_ENTRADA_COFINS", "COD_SAIDA_COFINS", "ALIQ_COFINS")
|
||||
|
||||
|
||||
def old_unknown(product):
|
||||
"""The previous model's UNKNOWN_APPLICABILITY: no product-level evidence of PIS/COFINS at all."""
|
||||
text = lambda v: "" if v is None else str(v).strip() # noqa: E731
|
||||
evidence = any(text(product.get(c)) for c in PIS_COLS) or text(product.get("PISCOFINS")).upper() == "S" or text(product.get("INCIDENCIA_PISCOFINS")).upper() not in ("", "NENHUMA")
|
||||
return not evidence
|
||||
|
||||
|
||||
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()
|
||||
path = lc.allowlist()[a.db]; env = lc.env_for(path)
|
||||
products = firebird_reader.read_products(env)
|
||||
connection = firebird_reader._connect(env) # READ COMMITTED READ ONLY (Agent reader)
|
||||
try:
|
||||
cursor = connection.cursor()
|
||||
schema, _catalog, extras = unified_fiscal.load_rtc_context(cursor)
|
||||
fiscal_products = unified_fiscal.read_products(cursor, schema)
|
||||
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)
|
||||
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)]
|
||||
before = summarize_operational_states(before_states)
|
||||
company = {"crt": crt}
|
||||
runs = {}
|
||||
for label, override in (("ERP_AS_FOUND", None), ("RTC_OFF_SIMULATION", False), ("RTC_ON_SIMULATION", True)):
|
||||
conn = firebird_reader._connect(env)
|
||||
try:
|
||||
out = unified_fiscal.assess_from_connection(conn, company, rtc_override=override)
|
||||
finally:
|
||||
conn.close()
|
||||
findings = out["findings"]
|
||||
states = [classify_product_state(p, d, fiscal=findings.get(str(p["CODIGO"]))) for p, d in zip(products, decisions)]
|
||||
summary = summarize_operational_states(states)
|
||||
by_type, by_category = Counter(), Counter()
|
||||
for f in findings.values():
|
||||
for kind in {(b["type"], b["category"]) for b in f["blockers"]}:
|
||||
by_type[kind[0]] += 1; by_category[kind[1]] += 1
|
||||
changed = [{"product": s["product_code"], "BEFORE_STATUS": b["state"], "AFTER_STATUS": s["state"], "REASON": sorted({x["type"] for x in s["fiscal_blockers"]}) or ("NEEDS_REVIEW" if s["state"] == "NEEDS_REVIEW" else "")}
|
||||
for b, s in zip(before_states, states) if b["state"] != s["state"]]
|
||||
total = summary["TOTAL_PRODUCTS"]
|
||||
runs[label] = {"rtc_enabled": out["context"]["rtc_enabled"], "TOTAL_PRODUCTS": total, "COMPLETE_PRODUCTS": summary["COMPLETE_PRODUCTS"], "SAFE_BLOCKED_PRODUCTS": summary["SAFE_BLOCKED"],
|
||||
"FAILED_PRODUCTS": summary["FAILED_TECHNICAL"], "UNCLASSIFIED_PRODUCTS": summary["UNCLASSIFIED"],
|
||||
"COVERAGE_PERCENT": round(100 * summary["COMPLETE_PRODUCTS"] / total, 2) if total else 0.0,
|
||||
"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]}
|
||||
if label == "ERP_AS_FOUND":
|
||||
runs[label]["context"] = {k: v for k, v in out["context"].items() if k != "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),
|
||||
"UNKNOWN_APPLICABILITY_previous_model": sum(1 for p in fiscal_products if old_unknown(p))}, "runs": runs,
|
||||
"SAFETY": {"FIREBIRD_WRITES": 0, "WRITE_ATTEMPTS": 0, "WRITE_SUCCESSES": 0, "connection_mode": "READ COMMITTED READ ONLY (Agent reader); no write-capable connection opened"}}
|
||||
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(" applicability", r["applicability"], "blockers", r["blockers_by_type"])
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user