Adds test_weak_composed_evidence_never_becomes_a_candidate_regardless_of_the_write_flag, proving from the real field_contract.resolve_product (not an isolated stub) that the beer misclassification fixed on master (see "fix: product_role never infers OWN_PRODUCTION from a single COMPOSICAO occurrence") can never reach select_candidates as an AUTO_FILL_SAFE candidate, independent of cfop_ippt_write_mode/write_gate. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
240 lines
13 KiB
Python
240 lines
13 KiB
Python
from gti_fiscal_agent import cfop_ippt_auto_apply as apply_mod
|
|
from central.app.field_contract import ALREADY, CHANGE, MANUAL, NA
|
|
|
|
|
|
class FakeFirebird:
|
|
"""A tiny C000025: every statement is recorded; only SELECT <field> and the one allowed UPDATE per field are understood."""
|
|
|
|
def __init__(self, products):
|
|
self.products = {c: dict(v) for c, v in products.items()} # code -> {"CFOP": ..., "IPPT": ...}
|
|
self.statements, self.commits, self.rollbacks = [], 0, 0
|
|
self._pending = {}
|
|
|
|
def cursor(self):
|
|
return FakeCursor(self)
|
|
|
|
def commit(self):
|
|
for (code, field), value in self._pending.items():
|
|
self.products[code][field] = value
|
|
self._pending = {}
|
|
self.commits += 1
|
|
|
|
def rollback(self):
|
|
self._pending = {}
|
|
self.rollbacks += 1
|
|
|
|
|
|
class FakeCursor:
|
|
def __init__(self, db):
|
|
self.db, self._rows, self.rowcount = db, [], -1
|
|
|
|
def fetchall(self):
|
|
return self._rows
|
|
|
|
def fetchone(self):
|
|
return self._rows[0] if self._rows else None
|
|
|
|
def execute(self, sql, params=()):
|
|
db = self.db
|
|
db.statements.append((sql, tuple(params)))
|
|
text = " ".join(sql.split())
|
|
up = text.upper()
|
|
for field in ("CFOP", "IPPT"):
|
|
if up == f"SELECT {field} FROM C000025 WHERE CODIGO = ?":
|
|
code = params[0]
|
|
self._rows = [(db.products[code][field],)] if code in db.products else []
|
|
if code in db.products and db.products[code].get("DUP"):
|
|
self._rows = self._rows * 2
|
|
return
|
|
if up.startswith(f"UPDATE C000025 SET {field} = ? WHERE CODIGO = ? AND"):
|
|
proposed, code, expected = params
|
|
product = db.products.get(code)
|
|
current = (product.get(field) if product else None) or None
|
|
hit = product is not None and current == (expected or None)
|
|
self.rowcount = 1 if hit else 0
|
|
if hit:
|
|
db._pending[(code, field)] = proposed
|
|
return
|
|
raise AssertionError(f"unexpected SQL: {sql!r}")
|
|
|
|
|
|
def candidate(code, field, expected_before, proposed, reason="X"):
|
|
return {"product_code": code, "field": field, "expected_before": expected_before, "proposed": proposed, "reason": reason,
|
|
"fingerprint": apply_mod.candidate_fingerprint(code, field, expected_before, proposed)}
|
|
|
|
|
|
def results_for(code, field, *, state, current, proposed, write_eligible):
|
|
return {code: {field: {"state": state, "current": current, "proposed": proposed, "write_eligible": write_eligible, "reason": "R"}}}
|
|
|
|
|
|
# ------------------------------------------------------------------ write_gate (flag semantics)
|
|
|
|
def test_write_gate_default_off_blocks_every_mode_but_enabled_and_approval_required():
|
|
assert apply_mod.write_gate(mode="DISABLED")["allowed"] is False
|
|
assert apply_mod.write_gate(mode="DRY_RUN")["allowed"] is False
|
|
assert apply_mod.write_gate(mode="APPROVAL_REQUIRED", approved=False)["allowed"] is False
|
|
assert apply_mod.write_gate(mode="APPROVAL_REQUIRED", approved=True)["allowed"] is True
|
|
assert apply_mod.write_gate(mode="ENABLED")["allowed"] is True
|
|
assert apply_mod.write_gate(mode="ENABLED", capability=False)["allowed"] is False
|
|
assert apply_mod.write_gate(mode="NOT_A_REAL_MODE")["allowed"] is False
|
|
|
|
|
|
# ------------------------------------------------------------------ select_candidates only picks AUTO_FILL_SAFE CFOP/IPPT
|
|
|
|
def test_select_candidates_only_change_and_write_eligible_cfop_or_ippt():
|
|
results = {
|
|
"000001": {"CFOP": {"state": CHANGE, "current": "5102", "proposed": "5101", "write_eligible": True, "reason": "R1"},
|
|
"IPPT": {"state": MANUAL, "current": None, "proposed": None, "write_eligible": False, "reason": "R2"},
|
|
"CSOSN": {"state": CHANGE, "current": None, "proposed": "102", "write_eligible": True, "reason": "R3"}}, # not CFOP/IPPT: never a candidate
|
|
"000002": {"CFOP": {"state": ALREADY, "current": "5102", "proposed": None, "write_eligible": False, "reason": "R4"},
|
|
"IPPT": {"state": CHANGE, "current": "T", "proposed": "P", "write_eligible": False, "reason": "R5"}}, # write_eligible=False: never a candidate
|
|
"000003": {"IPPT": {"state": CHANGE, "current": "T", "proposed": "P", "write_eligible": True, "reason": "R6"}},
|
|
}
|
|
cands, total = apply_mod.select_candidates(results)
|
|
assert total == 2
|
|
keys = {(c["product_code"], c["field"]) for c in cands}
|
|
assert keys == {("000001", "CFOP"), ("000003", "IPPT")}
|
|
assert all(c["field"] in apply_mod.ALLOWED_FIELDS for c in cands)
|
|
|
|
|
|
def test_weak_composed_evidence_never_becomes_a_candidate_regardless_of_the_write_flag():
|
|
"""Missão 4 / Bloco A.3: a cell whose role evidence is weak (field_contract itself refuses to
|
|
classify it OWN_PRODUCTION/RESALE with confidence) must never reach select_candidates as
|
|
AUTO_FILL_SAFE - proven end-to-end from the real resolver, not just asserted in isolation.
|
|
This holds independently of cfop_ippt_write_mode/write_gate, which only gates whether
|
|
apply_candidates() is ever invoked - it never affects what counts as a candidate."""
|
|
from central.app.field_contract import resolve_product, Context, WRITE_FIELDS
|
|
|
|
def product(**kw):
|
|
p = {f: None for f in WRITE_FIELDS}
|
|
p.update({"CODIGO": "000321", "PRODUTO": "CERVEJA LONG NECK NOVA SCHIN", "CODGRUPO": "G1", "TIPO": "VENDA", "PROD_COZINHA": None, "PRODUTO_SERVICO": None,
|
|
"CLASSIFICACAO_FISCAL": "22030000", "CFOP": "5102", "CSOSN": "102", "CST": "000", "SITUACAO_TRIBUTARIA": "T", "IAT": "A", "IPPT": "T",
|
|
"INCIDENCIA_PISCOFINS": "NENHUMA", "PISCOFINS": "N"})
|
|
p.update(kw)
|
|
return p
|
|
|
|
decision = {"status": "EXACT_NCM", "cest_status": "NO_CEST_MAPPING_FOUND", "ipi_status": "IPI_RESOLVED", "ba_st_mva_status": "NO_MVA_MAPPING_FOUND", "dataset_versions": {},
|
|
"classification": {"ncm": "22030000", "ipi": {"rate_kind": "NUMERIC", "rate_numeric": 3.9, "rate": "3.9"}, "ipi_candidates": [], "cest": None, "cest_candidates": [], "ba_st_mva": None, "ba_st_mva_candidates": []}}
|
|
refs = {"cfop_valid": {"5101", "5102"}, "csosn": {"102"}, "cst_icms": set(), "cst_pis": set(), "cst_cofins": set(), "origem": set("012345678"), "fcp_uf": {}, "cbs_rate_2026": None, "cclasstrib_count": 0}
|
|
co = {"uf": "BA", "crt": "1", "contribuinte_ipi": "N", "company_code": "000001"}
|
|
ev_weak = {"recipes": set(), "composed": set(), "composed_ratio": {"000321": {"composed": 1, "other": 19}}, "purchases": {}, "stock_entry": {"000321": True}, "stock_in": {"000321": True}}
|
|
result = resolve_product(product(), decision, co, refs, ev_weak, Context([]))
|
|
candidates, total = apply_mod.select_candidates({"000321": result})
|
|
assert total == 0 and candidates == [] # the weak-evidence beer never becomes a write candidate
|
|
|
|
|
|
def test_select_candidates_respects_limit():
|
|
results = {f"{i:06d}": {"CFOP": {"state": CHANGE, "current": None, "proposed": "5102", "write_eligible": True, "reason": "R"}} for i in range(5)}
|
|
cands, total = apply_mod.select_candidates(results, limit=2)
|
|
assert total == 5 and len(cands) == 2
|
|
|
|
|
|
# ------------------------------------------------------------------ apply_candidate: happy path, idempotency, stale guard
|
|
|
|
def test_apply_candidate_writes_verifies_and_commits():
|
|
db = FakeFirebird({"000001": {"CFOP": "5102"}})
|
|
c = candidate("000001", "CFOP", "5102", "5101")
|
|
rec = apply_mod.apply_candidate(db, c, now="t0", job_id=1)
|
|
assert rec["outcome"] == "APPLIED" and rec["old_value"] == "5102" and rec["new_value"] == "5101" and rec["verified_after"] is True
|
|
assert db.products["000001"]["CFOP"] == "5101" and db.commits >= 1 and db.rollbacks == 0
|
|
|
|
|
|
def test_second_run_over_the_same_candidate_writes_zero_no_write_required():
|
|
db = FakeFirebird({"000001": {"CFOP": "5101"}}) # already at the proposed value (as if a first pass already applied it)
|
|
c = candidate("000001", "CFOP", "5102", "5101")
|
|
try:
|
|
apply_mod.apply_candidate(db, c, now="t0", job_id=1)
|
|
assert False, "expected CfopIpptApplyAborted"
|
|
except apply_mod.CfopIpptApplyAborted as e:
|
|
assert e.reason == "NO_WRITE_REQUIRED"
|
|
assert db.products["000001"]["CFOP"] == "5101" # unchanged, no write attempted
|
|
|
|
|
|
def test_human_override_wins_stale_value_aborts_without_writing():
|
|
db = FakeFirebird({"000001": {"CFOP": "9999"}}) # a human (or anything else) set a THIRD value since the candidate was selected
|
|
c = candidate("000001", "CFOP", "5102", "5101") # candidate still expects the OLD "5102"
|
|
try:
|
|
apply_mod.apply_candidate(db, c, now="t0", job_id=1)
|
|
assert False, "expected CfopIpptApplyAborted"
|
|
except apply_mod.CfopIpptApplyAborted as e:
|
|
assert e.reason == "STALE_VALUE_CHANGED_SINCE_DECISION"
|
|
assert db.products["000001"]["CFOP"] == "9999" # the human's value is preserved, never overwritten
|
|
|
|
|
|
def test_apply_candidate_blank_expected_before_and_fill():
|
|
db = FakeFirebird({"000801": {"CFOP": None}})
|
|
c = candidate("000801", "CFOP", None, "5102")
|
|
rec = apply_mod.apply_candidate(db, c, now="t0", job_id=1)
|
|
assert rec["outcome"] == "APPLIED" and rec["old_value"] is None and rec["new_value"] == "5102"
|
|
|
|
|
|
def test_apply_candidate_product_not_found_or_duplicate_row_aborts():
|
|
db = FakeFirebird({})
|
|
try:
|
|
apply_mod.apply_candidate(db, candidate("999999", "CFOP", None, "5102"), now="t0")
|
|
assert False
|
|
except apply_mod.CfopIpptApplyAborted as e:
|
|
assert e.reason == "PRODUCT_NOT_FOUND"
|
|
db2 = FakeFirebird({"000001": {"CFOP": "5102", "DUP": True}})
|
|
try:
|
|
apply_mod.apply_candidate(db2, candidate("000001", "CFOP", "5102", "5101"), now="t0")
|
|
assert False
|
|
except apply_mod.CfopIpptApplyAborted as e:
|
|
assert e.reason == "PRODUCT_ROW_NOT_UNIQUE"
|
|
|
|
|
|
# ------------------------------------------------------------------ apply_candidates: batch semantics, authorize() gate, flag OFF => zero writes
|
|
|
|
def test_apply_candidates_batch_never_stops_on_one_abort_and_reports_reasons():
|
|
db = FakeFirebird({"000001": {"CFOP": "5102"}, "000002": {"CFOP": "9999"}})
|
|
cands = [candidate("000001", "CFOP", "5102", "5101"), candidate("000002", "CFOP", "5102", "5101")]
|
|
out = apply_mod.apply_candidates(db, cands, now="t0", job_id=1)
|
|
assert out["write_successes"] == 1 and out["write_attempts"] == 2
|
|
assert out["aborted_reasons"] == {"STALE_VALUE_CHANGED_SINCE_DECISION": 1}
|
|
assert db.products["000001"]["CFOP"] == "5101" and db.products["000002"]["CFOP"] == "9999"
|
|
|
|
|
|
def test_flag_off_means_caller_never_invokes_apply_zero_writes():
|
|
"""The Agent must gate BEFORE calling apply_candidates at all when write_gate(...).allowed is False -
|
|
this is the actual "flag OFF => zero writes" contract; apply_candidates itself is only ever reached
|
|
once the gate already said yes."""
|
|
gate = apply_mod.write_gate(mode="DISABLED")
|
|
assert not gate["allowed"]
|
|
db = FakeFirebird({"000001": {"CFOP": "5102"}})
|
|
# execute_job's real code path: `if gate["allowed"]: apply_candidates(...)` - simulate the guard directly.
|
|
if gate["allowed"]:
|
|
apply_mod.apply_candidates(db, [candidate("000001", "CFOP", "5102", "5101")], now="t0")
|
|
assert db.products["000001"]["CFOP"] == "5102" and db.commits == 0
|
|
|
|
|
|
def test_authorize_callback_can_veto_a_candidate_right_before_the_write():
|
|
db = FakeFirebird({"000001": {"CFOP": "5102"}})
|
|
out = apply_mod.apply_candidates(db, [candidate("000001", "CFOP", "5102", "5101")], now="t0", authorize=lambda code, field: "AUTHORIZATION_CHANGED")
|
|
assert out["write_successes"] == 0 and out["aborted_reasons"] == {"AUTHORIZATION_CHANGED": 1}
|
|
assert db.products["000001"]["CFOP"] == "5102"
|
|
|
|
|
|
# ------------------------------------------------------------------ rollback: only if the value still equals what was written
|
|
|
|
def test_rollback_restores_old_value_when_unchanged():
|
|
db = FakeFirebird({"000001": {"CFOP": "5101"}})
|
|
item = {"product_code": "000001", "field": "CFOP", "old_value": "5102", "new_value": "5101", "audit_id": 42}
|
|
rec = apply_mod.rollback_one(db, item, now="t1", job_id=1)
|
|
assert rec["outcome"] == "ROLLED_BACK" and db.products["000001"]["CFOP"] == "5102"
|
|
|
|
|
|
def test_rollback_refuses_when_a_human_changed_the_value_since():
|
|
db = FakeFirebird({"000001": {"CFOP": "7777"}}) # someone changed it after the write, before the rollback
|
|
item = {"product_code": "000001", "field": "CFOP", "old_value": "5102", "new_value": "5101", "audit_id": 42}
|
|
rec = apply_mod.rollback_one(db, item, now="t1", job_id=1)
|
|
assert rec["outcome"] == "ABORTED" and rec["reason"] == "ROLLBACK_CONFLICT_VALUE_CHANGED"
|
|
assert db.products["000001"]["CFOP"] == "7777" # untouched
|
|
|
|
|
|
def test_rollback_records_batch():
|
|
db = FakeFirebird({"000001": {"CFOP": "5101"}, "000002": {"IPPT": "P"}})
|
|
items = [{"product_code": "000001", "field": "CFOP", "old_value": "5102", "new_value": "5101"},
|
|
{"product_code": "000002", "field": "IPPT", "old_value": "T", "new_value": "P"}]
|
|
out = apply_mod.rollback_records(db, items, now="t1", job_id=1)
|
|
assert out["rolled_back"] == 2
|