147 lines
9.0 KiB
Python
147 lines
9.0 KiB
Python
"""Historical evidence -> KB: temporal validity, lineage independence, conflicts, promotion audit, idempotent import. No provider is ever contacted."""
|
|
from sqlalchemy import select
|
|
from sqlalchemy.orm import Session
|
|
|
|
from central.app.enrichment import history, kb
|
|
from central.app.enrichment.resolver import resolve
|
|
from central.app.enrichment.quota import ProviderGateway
|
|
from central.app.models import KbKnowledge, KbProduct, KbPromotionAudit
|
|
from central.tests.test_product_enrichment_resolver import COSMOS, Fake, G1, G2, G3, env, gtin, seed # noqa: F401
|
|
|
|
G = "0" + G1 # 14-digit key
|
|
|
|
|
|
def staging(client, gtin13, ncm, *, master=True, years=None, nfe=0, desc="REFRIGERANTE COLA LATA 350ML", code="1"):
|
|
return {"client": client, "master": [{"code": code, "desc": desc, "gtin": gtin13, "codbarra": "", "ncm": ncm if master else "", "cest": ""}],
|
|
"nfce": [{"code": code, "year": y, "ncm": ncm, "cest": "", "n": n} for y, n in (years or {}).items()], "nfe": [{"code": code, "codbarra": gtin13, "ncm": ncm, "desc": desc, "n": nfe}] if nfe else [], "barcodes": []}
|
|
|
|
|
|
def run(session, stagings, dry=False):
|
|
obs = history.build_observations(stagings)
|
|
cur = history.official_sets(session)
|
|
return history.apply(session, obs, lambda v: v in cur, dry_run=dry)
|
|
|
|
|
|
def status(session, key=G):
|
|
p = session.scalar(select(KbProduct).where(KbProduct.gtin == key))
|
|
cur = history.official_sets(session)
|
|
return kb.evaluate(session, p, "ncm", lambda v: v in cur)
|
|
|
|
|
|
def test_single_client_multi_year_history_is_corroborated_never_verified(env):
|
|
r = run(env, [staging("A", G1, "22021000", years={2023: 300, 2024: 700, 2025: 900})])
|
|
assert r["by_status"] == {"CORROBORATED": 1} and r["WOULD_VERIFY"] == 0
|
|
assert env.scalars(select(KbPromotionAudit)).all() == []
|
|
|
|
|
|
def test_master_plus_documents_of_same_erp_are_one_family(env):
|
|
run(env, [staging("A", G1, "22021000", master=True, years={2025: 5})]) # few docs, one year: weak
|
|
assert status(env)["status"] == kb.OBSERVED and status(env)["families"] == []
|
|
|
|
|
|
def test_two_clients_corroborate_but_three_clients_alone_do_not_verify(env):
|
|
r = run(env, [staging(c, G1, "22021000", years={2024: 2}) for c in ("A", "B", "C")])
|
|
assert r["by_status"] == {"CORROBORATED": 1} and r["multi_client"] == 1 and r["WOULD_VERIFY"] == 0
|
|
|
|
|
|
def test_history_plus_independent_provider_verifies_and_is_audited(env):
|
|
run(env, [staging("A", G1, "22021000", years={2023: 50, 2024: 60})])
|
|
p = env.scalar(select(KbProduct).where(KbProduct.gtin == G))
|
|
kb.add_evidence(env, p, "ncm", "22021000", "provider:oscbr", family="PROVIDER:oscbr")
|
|
ev = status(env)
|
|
assert ev["status"] == kb.VERIFIED_KB and set(ev["families"]) == {"PROVIDER:oscbr", "CLIENT_DATA"}
|
|
audit = env.scalars(select(KbPromotionAudit)).one()
|
|
assert audit.new_status == "VERIFIED_KB" and audit.rule_version == kb.RULE_VERSION and audit.official_dataset["sha256"] and audit.sources["documents"] == 110
|
|
status(env)
|
|
assert len(env.scalars(select(KbPromotionAudit)).all()) == 1 # no duplicate audit
|
|
|
|
|
|
def test_different_current_ncms_are_a_conflict_not_majority(env):
|
|
r = run(env, [staging("A", G1, "22021000", years={2024: 900, 2025: 900}), staging("B", G1, "19059090", years={2024: 5, 2025: 5}), staging("C", G1, "22021000", years={2024: 900})])
|
|
assert r["CONFLICTS"] == 1
|
|
res = resolve(env, ProviderGateway(env, []), gtin_raw=G1, description="REFRIGERANTE COLA LATA 350ML", current={}, allow_external=False)
|
|
assert res["state"] == "CONFLICT" and res["fields"]["CLASSIFICACAO_FISCAL"]["status"] == "CONFLICT"
|
|
|
|
|
|
def test_expired_ncm_is_history_not_a_candidate(env):
|
|
run(env, [staging("A", G1, "11111111", years={2019: 900, 2020: 900})]) # expired in the fixture dataset
|
|
ev = status(env)
|
|
assert ev["value"] is None and ev["status"] == kb.OBSERVED
|
|
row = env.scalar(select(KbKnowledge).where(KbKnowledge.knowledge_key == "ncm"))
|
|
assert row.evidence["info"]["historical_state"] == "HISTORICAL_EXPIRED" and row.status == kb.STALE
|
|
|
|
|
|
def test_ncm_changed_over_time_prefers_current(env):
|
|
run(env, [staging("A", G1, "11111111", years={2019: 900, 2020: 900}), staging("A", G1, "22021000", master=False, years={2024: 50, 2025: 60})])
|
|
ev = status(env)
|
|
assert ev["value"] == "22021000" and ev["status"] == kb.CORROBORATED
|
|
|
|
|
|
def test_stale_ncm_with_equal_recency_does_not_count_as_stable(env):
|
|
run(env, [staging("A", G1, "22021000", master=False, years={2024: 50, 2025: 60}), staging("B", G1, "11111111", master=False, years={2025: 500})])
|
|
assert status(env)["status"] in (kb.OBSERVED,) # the old code is as recent: not stable, so not even corroborated
|
|
|
|
|
|
def test_import_is_idempotent_and_dry_run_writes_nothing(env):
|
|
st = [staging("A", G1, "22021000", years={2023: 300, 2024: 700}), staging("B", G3, "19059090", desc="BISCOITO RECHEADO 130G", code="9", years={2024: 30, 2025: 30})]
|
|
dry = run(env, st, dry=True)
|
|
assert env.scalars(select(KbProduct)).all() == []
|
|
a, b = run(env, st), run(env, st)
|
|
assert a["by_status"] == b["by_status"] == dry["by_status"]
|
|
assert len(env.scalars(select(KbKnowledge).where(KbKnowledge.knowledge_key == "ncm")).all()) == 2
|
|
row = env.scalar(select(KbKnowledge).where(KbKnowledge.knowledge_key == "ncm", KbKnowledge.value == "22021000"))
|
|
assert row.evidence["lineages"]["A"]["docs"] == 1000 # replaced, not doubled
|
|
|
|
|
|
def test_aliases_and_no_company_tax_data_stored(env):
|
|
run(env, [staging("A", G1, "22021000", years={2024: 30, 2025: 30}, desc="COCA COLA LT 350"), staging("B", G1, "22021000", master=False, desc="REFRIG COCA-COLA 350ML", code="2", years={2025: 5})])
|
|
p = env.scalar(select(KbProduct).where(KbProduct.gtin == G))
|
|
aliases = {r.value for r in env.scalars(select(KbKnowledge).where(KbKnowledge.kb_product_id == p.id, KbKnowledge.knowledge_key == "alias"))}
|
|
assert len(aliases) >= 2
|
|
blob = repr([r.evidence for r in env.scalars(select(KbKnowledge))]).upper()
|
|
assert "CFOP" not in blob and "CSOSN" not in blob and "CRT" not in blob
|
|
|
|
|
|
def test_kb_hit_makes_zero_external_requests(env):
|
|
run(env, [staging("A", G1, "22021000", years={2023: 50, 2024: 60})])
|
|
p = env.scalar(select(KbProduct).where(KbProduct.gtin == G))
|
|
kb.add_evidence(env, p, "ncm", "22021000", "provider:oscbr", family="PROVIDER:oscbr")
|
|
env.commit()
|
|
spy = Fake("cosmos", COSMOS(), True)
|
|
r = resolve(env, ProviderGateway(env, [spy]), gtin_raw=G1, description="REFRIGERANTE COLA LATA 350ML", current={})
|
|
assert r["state"] == "EXACT_GTIN_VERIFIED" and spy.calls == 0 and r["fields"]["CLASSIFICACAO_FISCAL"]["status"] == "AUTO_FILL_SAFE"
|
|
|
|
|
|
def test_revalidation_marks_stale_review_and_keeps_knowledge(env):
|
|
run(env, [staging("A", G1, "22021000", years={2023: 50, 2024: 60})])
|
|
p = env.scalar(select(KbProduct).where(KbProduct.gtin == G))
|
|
kb.add_evidence(env, p, "ncm", "22021000", "provider:oscbr", family="PROVIDER:oscbr")
|
|
assert status(env)["status"] == kb.VERIFIED_KB
|
|
out = kb.revalidate(env, lambda v: True)
|
|
assert out["stale"] == 0 and status(env)["status"] == kb.VERIFIED_KB # NCM still vigente: stays valid
|
|
out = kb.revalidate(env, lambda v: False) # the official dataset dropped it
|
|
row = env.scalar(select(KbKnowledge).where(KbKnowledge.knowledge_key == "ncm"))
|
|
assert out["stale"] == 1 and row.status == kb.STALE and row.evidence["info"]["stale_reason"] == "STALE_REVIEW_REQUIRED"
|
|
assert [a.new_status for a in env.scalars(select(KbPromotionAudit)).all()] == ["VERIFIED_KB", "STALE"]
|
|
|
|
|
|
def test_provider_disagreeing_with_verified_kb_creates_conflict_not_overwrite(env):
|
|
run(env, [staging("A", G1, "22021000", years={2023: 50, 2024: 60})])
|
|
p = env.scalar(select(KbProduct).where(KbProduct.gtin == G))
|
|
kb.add_evidence(env, p, "ncm", "22021000", "provider:oscbr", family="PROVIDER:oscbr")
|
|
assert status(env)["status"] == kb.VERIFIED_KB
|
|
kb.add_evidence(env, p, "ncm", "19059090", "provider:cosmos", family="PROVIDER:cosmos")
|
|
ev = status(env)
|
|
assert ev["status"] == kb.CONFLICT and ev["value"] is None # write blocked until a human resolves it
|
|
|
|
|
|
def test_aliases_from_history_enable_name_only_match_and_generic_names_stay_ambiguous(env):
|
|
run(env, [staging("A", G1, "22021000", years={2023: 50, 2024: 60}, desc="COCA-COLA ORIGINAL LATA 350 ML")])
|
|
p = env.scalar(select(KbProduct).where(KbProduct.gtin == G))
|
|
kb.add_evidence(env, p, "ncm", "22021000", "provider:oscbr", family="PROVIDER:oscbr")
|
|
env.commit()
|
|
r = resolve(env, ProviderGateway(env, []), gtin_raw="", description="COCA COLA LATA 350ML", current={})
|
|
assert r["state"] == "EXACT_KB_NAME_VERIFIED" and r["fields"]["CLASSIFICACAO_FISCAL"]["value"] == "22021000"
|
|
g = resolve(env, ProviderGateway(env, []), gtin_raw="", description="REFRIGERANTE", current={})
|
|
assert g["state"] == "AMBIGUOUS" and all(f["status"] != "AUTO_FILL_SAFE" for f in g["fields"].values())
|