fix: RTC observed live with provenance, non-circular PIS/COFINS applicability, canonical CST normalization (1.1.8)
- RTC: Agent reads ALL PARAMETROS rows every sync (conflict => off), reports rtc_source/rtc_observed_at/rtc_param_rows; legal_effective_context is informational only - PIS/COFINS: applicability from CRT + PISCOFINS flag + incidence only (never from empty fields); Simples w/o special treatment NOT_APPLICABLE, taxed regime with explicit N OPTIONAL, unreadable declaration NEEDS_REVIEW - CST: one digit with an official zero-padded form is VALID_AFTER_NORMALIZATION (non-blocking warning), nothing is written to the ERP - tests: RTC toggle 0->1->0 across syncs (Agent + Hub stale closing), non-circularity, normalization, panel provenance Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -54,17 +54,25 @@ def incidence_kind(value) -> str:
|
||||
return "UNKNOWN"
|
||||
|
||||
|
||||
def canonical_cst(value) -> str | None:
|
||||
"""The official 2-digit CST this text stands for, or None. Exactly two official digits, or ONE digit whose zero-padded form is an official code
|
||||
("4" -> "04"). Nothing else is ever normalised ("049", "000", "2x" stay invalid)."""
|
||||
code = _text(value)
|
||||
if code in CST_DOMAIN:
|
||||
return code
|
||||
if len(code) == 1 and code.isdigit() and code.zfill(2) in CST_DOMAIN:
|
||||
return code.zfill(2)
|
||||
return None
|
||||
|
||||
|
||||
def validate_cst(value) -> str:
|
||||
"""EMPTY | VALID | VALID_AFTER_NORMALIZATION | INVALID."""
|
||||
code = _text(value)
|
||||
if not code:
|
||||
return "EMPTY"
|
||||
return "VALID" if code in CST_DOMAIN else "INVALID"
|
||||
|
||||
|
||||
def cst_suggestion(value) -> str | None:
|
||||
"""A short numeric code that becomes an official CST once zero-padded to two digits ('4' -> '04'). Only a hint for the operator; never applied."""
|
||||
code = _text(value)
|
||||
return code.zfill(2) if code.isdigit() and len(code) == 1 and code.zfill(2) in CST_DOMAIN else None
|
||||
if code in CST_DOMAIN:
|
||||
return "VALID"
|
||||
return "VALID_AFTER_NORMALIZATION" if canonical_cst(code) else "INVALID"
|
||||
|
||||
|
||||
def validate_rate(value) -> str:
|
||||
@@ -82,56 +90,71 @@ def validate_rate(value) -> str:
|
||||
def pis_cofins_applicability(product: dict, crt) -> dict:
|
||||
"""{"state": NOT_APPLICABLE | REQUIRED | OPTIONAL | NEEDS_REVIEW, "basis": ...}.
|
||||
|
||||
NOT_APPLICABLE the product carries no product-level PIS/COFINS treatment at all (flag N/blank, incidence NENHUMA/blank, all six fields blank).
|
||||
The tax is then settled elsewhere: inside the DAS (Simples family) or from the company default (regime normal).
|
||||
REQUIRED the product itself claims a special treatment (flag S, or incidence MONOFASICA/SUBSTITUICAO): the exit CST (and a rate when the CST needs one) must be valid.
|
||||
OPTIONAL no special claim, but some CST/rate was typed: it is not mandatory, yet whatever is there must be valid.
|
||||
NEEDS_REVIEW the incidence text is not one the SGV defines (NENHUMA / MONOFASICA / SUBST.TRIBUTARIA): applicability cannot be decided.
|
||||
Decided ONLY from signals that are independent of the six CST/rate fields - an empty field is a data state, never proof of "not applicable":
|
||||
* the company regime (CRT);
|
||||
* the product's own declaration: PISCOFINS flag and INCIDENCIA_PISCOFINS;
|
||||
* official rules (LC 123/2006 art. 13 IV/V and art. 18 par. 4-A I; Leis 9.718/98, 10.637/02, 10.833/03; SPED 4.3.x for the CST domain/semantics).
|
||||
|
||||
REQUIRED the product declares a special treatment (flag S, or incidence MONOFASICA / SUBSTITUICAO): the exit CST must be valid, whatever the fields hold.
|
||||
NOT_APPLICABLE Simples family (CRT 1/2/4) with no special treatment declared: PIS/Cofins are collected inside the DAS (LC 123 art. 13), so the product carries no
|
||||
obligation. The same verdict holds whether the fields are empty or typed.
|
||||
OPTIONAL taxed regime (CRT 3 or other) AND the product explicitly declares "no special treatment" (flag N + incidence NENHUMA): the tax applies but the
|
||||
company default covers it, so the product default is not mandatory; anything typed must still be valid.
|
||||
NEEDS_REVIEW the declaration cannot be read: incidence text outside the SGV vocabulary, or a taxed regime with a blank flag/incidence.
|
||||
"""
|
||||
flag = _text(product.get("PISCOFINS")).upper()
|
||||
kind = incidence_kind(product.get("INCIDENCIA_PISCOFINS"))
|
||||
has_values = any(_text(product.get(c)) for c in PIS_COFINS_FIELDS)
|
||||
raw_incidence = _text(product.get("INCIDENCIA_PISCOFINS"))
|
||||
kind = incidence_kind(raw_incidence)
|
||||
if kind == "UNKNOWN":
|
||||
return {"state": "NEEDS_REVIEW", "basis": "INCIDENCE_NOT_DEFINED_BY_SGV"}
|
||||
if flag == "S" or kind in ("MONOFASICA", "SUBSTITUICAO"):
|
||||
return {"state": "REQUIRED", "basis": "PRODUCT_DECLARES_SPECIAL_TREATMENT"}
|
||||
if has_values:
|
||||
return {"state": "OPTIONAL", "basis": "VALUES_TYPED_WITHOUT_SPECIAL_CLAIM"}
|
||||
return {"state": "NOT_APPLICABLE", "basis": "SIMPLES_DAS" if _text(crt) in SIMPLES_FAMILY else "COMPANY_DEFAULT"}
|
||||
if _text(crt) in SIMPLES_FAMILY:
|
||||
return {"state": "NOT_APPLICABLE", "basis": "SIMPLES_DAS_NO_SPECIAL_TREATMENT"}
|
||||
if flag == "N" and raw_incidence:
|
||||
return {"state": "OPTIONAL", "basis": "TAXED_REGIME_COMPANY_DEFAULT_COVERS"}
|
||||
return {"state": "NEEDS_REVIEW", "basis": "NO_DECLARATION_IN_TAXED_REGIME"}
|
||||
|
||||
|
||||
def evaluate_pis_cofins(product: dict, crt) -> dict:
|
||||
"""{"applicability": {...}, "blockers": [(type, reason)]}. Entry and exit are separate; a missing OPTIONAL value never blocks."""
|
||||
"""{"applicability": {...}, "blockers": [(type, reason)], "warnings": [(type, reason)]}. Entry and exit are separate; PIS and COFINS independent.
|
||||
Blockers exist only where the field applies (REQUIRED / OPTIONAL). Non-blocking warnings report data-quality facts (non-canonical or ignored values)."""
|
||||
app = pis_cofins_applicability(product, crt)
|
||||
blockers: list[tuple[str, str]] = []
|
||||
if app["state"] in ("NOT_APPLICABLE", "NEEDS_REVIEW"):
|
||||
return {"applicability": app, "blockers": blockers}
|
||||
warnings: list[tuple[str, str]] = []
|
||||
kind = incidence_kind(product.get("INCIDENCIA_PISCOFINS"))
|
||||
flag = _text(product.get("PISCOFINS")).upper()
|
||||
applies = app["state"] in ("REQUIRED", "OPTIONAL")
|
||||
for tax in ("PIS", "COFINS"):
|
||||
entry, exit_, rate = product.get(f"COD_ENTRADA_{tax}"), product.get(f"COD_SAIDA_{tax}"), product.get(f"ALIQ_{tax}")
|
||||
entry_state, exit_state, rate_state = validate_cst(entry), validate_cst(exit_), validate_rate(rate)
|
||||
if entry_state == "INVALID":
|
||||
blockers.append((f"{tax}_CST_INVALID", "ENTRY" + (f":UNPADDED_USE_{cst_suggestion(entry)}" if cst_suggestion(entry) else "")))
|
||||
if exit_state == "INVALID":
|
||||
blockers.append((f"{tax}_CST_INVALID", "EXIT" + (f":UNPADDED_USE_{cst_suggestion(exit_)}" if cst_suggestion(exit_) else "")))
|
||||
for slot, value, state in (("ENTRY", entry, entry_state), ("EXIT", exit_, exit_state)):
|
||||
if state == "VALID_AFTER_NORMALIZATION":
|
||||
warnings.append((f"CST_{tax}_NON_CANONICAL_FORMAT", f"{slot}:{_text(value)}->{canonical_cst(value)}"))
|
||||
elif state == "INVALID":
|
||||
if applies:
|
||||
blockers.append((f"{tax}_CST_INVALID", slot))
|
||||
else:
|
||||
warnings.append((f"CST_{tax}_INVALID_IGNORED", f"{slot}:{_text(value)}"))
|
||||
if not applies:
|
||||
continue
|
||||
if app["state"] == "REQUIRED" and exit_state == "EMPTY":
|
||||
blockers.append((f"{tax}_CST_MISSING", "EXIT"))
|
||||
if rate_state == "INVALID":
|
||||
blockers.append((f"{tax}_RATE_INVALID", "RATE"))
|
||||
if exit_state == "VALID":
|
||||
code = _text(exit_)
|
||||
code = canonical_cst(exit_)
|
||||
if code:
|
||||
if code in RATE_REQUIRED_CST and rate_state == "EMPTY":
|
||||
blockers.append((f"{tax}_RATE_MISSING", f"CST_{code}_NEEDS_RATE"))
|
||||
if kind in INCIDENCE_CST and code not in INCIDENCE_CST[kind]:
|
||||
blockers.append((f"{tax}_CST_INCIDENCE_MISMATCH", f"{kind}:CST_{code}"))
|
||||
if kind == "NONE" and app["state"] == "REQUIRED" and code in RATE_NOT_APPLICABLE_CST - {"03"} and flag == "S":
|
||||
blockers.append((f"{tax}_CST_INCIDENCE_MISMATCH", f"NENHUMA:CST_{code}"))
|
||||
if flag == "N" and kind != "NONE":
|
||||
if applies and flag == "N" and kind != "NONE":
|
||||
blockers.append(("PIS_COFINS_FLAG_INCONSISTENT", "FLAG_N_WITH_SPECIAL_INCIDENCE"))
|
||||
if flag == "S" and kind == "NONE" and not any(_text(product.get(c)) for c in PIS_COFINS_FIELDS):
|
||||
if applies and flag == "S" and kind == "NONE" and not any(_text(product.get(c)) for c in PIS_COFINS_FIELDS):
|
||||
blockers.append(("PIS_COFINS_FLAG_INCONSISTENT", "FLAG_S_WITHOUT_INCIDENCE_OR_CODES"))
|
||||
return {"applicability": app, "blockers": _dedupe(blockers)}
|
||||
return {"applicability": app, "blockers": _dedupe(blockers), "warnings": _dedupe(warnings)}
|
||||
|
||||
|
||||
def _dedupe(items):
|
||||
@@ -201,11 +224,21 @@ def evaluate_product(product: dict, company: dict | None, rtc: dict | None, toda
|
||||
pis = evaluate_pis_cofins(product, crt)
|
||||
rtc_result = evaluate_rtc(product, rtc, today)
|
||||
blockers = [{"type": kind, "category": blocker_category(kind), "reason": reason} for kind, reason in pis["blockers"] + rtc_result["blockers"]]
|
||||
warnings = [{"type": kind, "reason": reason} for kind, reason in pis["warnings"]]
|
||||
mandatory = []
|
||||
if pis["applicability"]["state"] == "REQUIRED":
|
||||
mandatory += ["COD_SAIDA_PIS", "COD_SAIDA_COFINS"]
|
||||
if rtc_result["applicability"]["state"] == "REQUIRED":
|
||||
mandatory.append("CCLASSTRIB")
|
||||
return {"blockers": blockers, "applicability": {"PIS_COFINS": pis["applicability"]["state"], "RTC": rtc_result["applicability"]["state"]},
|
||||
"basis": {"PIS_COFINS": pis["applicability"]["basis"], "RTC": rtc_result["applicability"]["basis"]}, "mandatory_fields": mandatory,
|
||||
"basis": {"PIS_COFINS": pis["applicability"]["basis"], "RTC": rtc_result["applicability"]["basis"]}, "mandatory_fields": mandatory, "warnings": warnings,
|
||||
"needs_review": pis["applicability"]["state"] == "NEEDS_REVIEW"}
|
||||
|
||||
|
||||
def legal_effective_context(crt) -> dict:
|
||||
"""INFORMATIONAL ONLY (never a blocker, never part of completeness). The operational rule stays ERP_RTC_ENABLED; this records what the legislation
|
||||
says about IBS/CBS in documents for the company regime so a difference in timing can be explained without inventing a rule."""
|
||||
simples = _text(crt) in SIMPLES_FAMILY
|
||||
return {"crt": _text(crt) or None, "informational_only": True,
|
||||
"ibs_cbs_in_documents": "FROM_2027_FOR_SIMPLES_ORIENTATION_PENDING" if simples else ("MANDATORY_FROM_2026-01-01" if _text(crt) == "3" else "UNKNOWN_REGIME"),
|
||||
"sources": ["NT 2025.002-RTC v1.51 (Portal NF-e, 04/08/2026): CRT 1/2/4 orientations in a future NT; IBS/CBS taxation of these taxpayers only from 2027", "LC 214/2025 art. 348"]}
|
||||
|
||||
Reference in New Issue
Block a user