"""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 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" if code in CST_DOMAIN: return "VALID" return "VALID_AFTER_NORMALIZATION" if canonical_cst(code) else "INVALID" 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": ...}. 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() 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 _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)], "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]] = [] 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) 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")) 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 applies and flag == "N" and kind != "NONE": blockers.append(("PIS_COFINS_FLAG_INCONSISTENT", "FLAG_N_WITH_SPECIAL_INCIDENCE")) 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), "warnings": _dedupe(warnings)} 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"]] 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, "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"]}