Files
gti d21a9d1140 Add 'fiscal/' from commit '102ba84500dbd9655a0471288c3e278652593ca9'
git-subtree-dir: fiscal
git-subtree-mainline: d95165fc8f
git-subtree-split: 102ba84500
2026-09-29 14:43:23 -03:00

108 lines
6.5 KiB
Python

"""ICMS Context Engine V1: ST applicability, ST withholding evidence, product role, CSOSN decision V2.
Pure functions, no I/O, no writes (every output has write_eligible=False). History is only used to
validate/flag; it is never an input to a decision.
"""
ICMS_CONTEXT_ENGINE_VERSION = "icms-context-v1"
def _digits(v):
return "".join(c for c in str(v or "") if c.isdigit())
def st_status(ncm_status, mva_status, cest_status, erp_cest, ba_item_cest, ba_ncm_sh_len):
"""ST_CONFIRMED needs a unique BA Anexo 1 item whose CEST the ERP product itself carries
(or a confirmed CEST). ST_NOT_APPLICABLE needs BOTH official lists (BA Anexo 1 and CEST) silent
for a valid NCM. A CEST *candidate* never confirms ST."""
if ncm_status != "EXACT_NCM":
return "ST_UNKNOWN", "NCM_NOT_VALID"
if mva_status == "MVA_RESOLVED":
if cest_status == "CEST_CONFIRMED" or (_digits(erp_cest) and _digits(erp_cest) == _digits(ba_item_cest)):
return "ST_CONFIRMED", "UNIQUE_BA_ANNEX1_ITEM_CORROBORATED_BY_CEST"
return "ST_CANDIDATE", "UNIQUE_BA_ANNEX1_ITEM_WITHOUT_CEST_CORROBORATION"
if mva_status == "MVA_CANDIDATES":
return "ST_CANDIDATE", "MULTIPLE_BA_ANNEX1_ITEMS"
if mva_status == "NO_MVA_MAPPING_FOUND":
if cest_status == "NO_CEST_MAPPING_FOUND":
return "ST_NOT_APPLICABLE", "NCM_ABSENT_FROM_BA_ANNEX1_AND_CEST_LIST"
return "ST_UNKNOWN", "CEST_LISTED_BUT_BA_ANNEX1_ABSENT"
return "ST_UNKNOWN", "NO_ST_EVIDENCE"
def withheld_status(purchase_rows):
"""purchase_rows: entrada item rows for the product with keys icms_retido (S/N), valor_retido, sub_valor.
CONFIRMED only with positive purchase-side evidence; explicit 'N'/0 is NOT_CONFIRMED; no rows UNKNOWN."""
if not purchase_rows:
return "ST_WITHHOLDING_UNKNOWN", "NO_PURCHASE_ITEM_FOR_PRODUCT"
for r in purchase_rows:
if str(r.get("icms_retido") or "").strip().upper() == "S" or float(r.get("valor_retido") or 0) > 0 or float(r.get("sub_valor") or 0) > 0:
return "ST_ALREADY_WITHHELD_CONFIRMED", "PURCHASE_ITEM_SHOWS_WITHHELD_ICMS_ST"
return "ST_ALREADY_WITHHELD_NOT_CONFIRMED", "PURCHASE_ITEMS_FLAG_ICMS_RETIDO_N_AND_ZERO_ST_VALUES"
def product_role(erp_tipo, prod_cozinha, has_recipe, sold_as_composition, service_flag, purchase_evidence):
ev = []
if service_flag:
return "SERVICE", ["PRODUTO_SERVICO"]
if str(erp_tipo or "").strip().upper() == "MATERIA-PRIMA":
return "INPUT", ["C000025.TIPO=MATERIA-PRIMA"]
own = []
if prod_cozinha: own.append("PROD_COZINHA=1")
if has_recipe: own.append("RECEITA_INGREDIENTES")
if sold_as_composition: own.append("C000032.TIPO=COMPOSICAO")
if own and purchase_evidence:
return "UNKNOWN", own + ["ALSO_HAS_PURCHASE_EVIDENCE_CONFLICT"]
if own:
return "OWN_PRODUCTION", own
if str(erp_tipo or "").strip().upper() == "VENDA" and purchase_evidence:
return "RESALE", ["C000025.TIPO=VENDA", "PURCHASE_DOCUMENT_EVIDENCE"]
return "UNKNOWN", ev or ["NO_OBJECTIVE_ROLE_EVIDENCE"]
def csosn_v2(current, company_default, ncm_status, st, withheld, role, csosn_domain):
"""States: CSOSN_RESOLVED / CANDIDATE / CONTEXT_REQUIRED / CONFLICT / UNRESOLVED.
RESOLVED only where the fiscal condition is explicitly proven (ST confirmed + withholding confirmed + role
known -> 500). Everything else stays conservative; never assumes 102."""
cur = None if current is None or str(current).strip() == "" else str(current).strip()
out = {"field": "CSOSN", "current_value": cur, "proposed_csosn": None, "write_eligible": False, "engine": ICMS_CONTEXT_ENGINE_VERSION}
def res(state, reason, conf, action, proposed=None, **kw):
return {**out, "status": state, "reason_code": reason, "confidence": conf, "action_class": action, "proposed_csosn": proposed, **kw}
if ncm_status != "EXACT_NCM":
return res("CSOSN_UNRESOLVED", "NCM_NOT_VALID", "NONE", "NO_RULE")
if role in ("INPUT", "SERVICE"):
return res("CSOSN_CONTEXT_REQUIRED", f"ROLE_{role}_NOT_A_MERCHANDISE_SALE_DEFAULT", "NONE", "CONTEXT_REQUIRED")
if st == "ST_CONFIRMED":
if withheld == "ST_ALREADY_WITHHELD_CONFIRMED" and role in ("RESALE",):
state = "CSOSN_RESOLVED"
return res(state, "ST_CONFIRMED_AND_WITHHOLDING_CONFIRMED_RESALE", "HIGH", "ALREADY_CORRECT" if cur == "500" else "AUTO_APPLY_SAFE", "500")
if withheld == "ST_ALREADY_WITHHELD_NOT_CONFIRMED" and cur == "500":
return res("CSOSN_CONFLICT", "CSOSN_500_BUT_PURCHASE_ITEMS_SHOW_NO_ST_WITHHELD", "MEDIUM", "REVIEW_REQUIRED")
if withheld == "ST_ALREADY_WITHHELD_CONFIRMED" and cur != "500":
return res("CSOSN_CONFLICT", "ST_WITHHELD_CONFIRMED_BUT_CSOSN_NOT_500", "MEDIUM", "REVIEW_REQUIRED", "500")
return res("CSOSN_CONTEXT_REQUIRED", "ST_CONFIRMED_WITHHOLDING_OR_ROLE_UNPROVEN", "NONE", "CONTEXT_REQUIRED",
consistency_flag=None if cur == "500" else "ST_CONFIRMED_BUT_CSOSN_NOT_500")
if st in ("ST_CANDIDATE", "ST_UNKNOWN"):
return res("CSOSN_CONTEXT_REQUIRED", f"{st}_APPLICABILITY_UNPROVEN", "NONE", "CONTEXT_REQUIRED")
# ST_NOT_APPLICABLE: taxed vs exempt/not-taxed treatment (102 vs 400/300) has no source
if role in ("RESALE", "OWN_PRODUCTION") and cur == company_default and cur in csosn_domain:
return res("CSOSN_CANDIDATE", "NON_ST_MATCHES_COMPANY_DEFAULT_ICMS_TREATMENT_UNPROVEN", "MEDIUM", "REVIEW_REQUIRED", cur)
if role == "UNKNOWN":
return res("CSOSN_CONTEXT_REQUIRED", "PRODUCT_ROLE_UNKNOWN", "NONE", "CONTEXT_REQUIRED")
return res("CSOSN_CANDIDATE" if cur else "CSOSN_CONTEXT_REQUIRED", "DIFFERS_FROM_OR_LACKS_COMPANY_DEFAULT_TREATMENT_UNPROVEN", "LOW", "REVIEW_REQUIRED" if cur else "CONTEXT_REQUIRED")
def st_conflict_class(cest_status, mva_status, ncm_sh_len, erp_cest, ba_item_cest, erp_cfop, history_emitted_current, non_st_but_erp_5405):
"""Primary label for an ST-related CFOP/CSOSN conflict, plus all evidence flags."""
flags = []
if non_st_but_erp_5405 or (_digits(erp_cest) and _digits(ba_item_cest) and _digits(erp_cest) != _digits(ba_item_cest)):
flags.append("BA_ST_RULE_CONFLICT")
if history_emitted_current:
flags.append("ERP_HISTORY_CONFLICT")
if cest_status == "CEST_CANDIDATES":
flags.append("MULTIPLE_CEST")
if cest_status == "CEST_DESCRIPTION_MISMATCH":
flags.append("DESCRIPTION_MISMATCH")
if ncm_sh_len is not None and ncm_sh_len <= 4:
flags.append("NCM_TOO_BROAD")
return (flags[0] if flags else "NO_EVIDENCE"), flags or ["NO_EVIDENCE"]