feat: complete pre-ai local completion v3

This commit is contained in:
OpenCode
2026-09-21 16:30:27 -03:00
parent 6731eeca08
commit 84b08c2150
41 changed files with 4141 additions and 9 deletions
+55
View File
@@ -0,0 +1,55 @@
"""Offline-safe, ID-correlated provider batch execution."""
from dataclasses import dataclass, field
from .provider import ProviderTransportError
@dataclass
class BatchRunResult:
results_by_work_item_id: dict[str, dict] = field(default_factory=dict)
errors: dict[str, str] = field(default_factory=dict)
provider_calls: int = 0
retries: int = 0
usage: dict = field(default_factory=dict)
def run_batches(items, provider, batch_size: int, max_retries: int = 2, *, scenario: str | None = None) -> BatchRunResult:
if batch_size <= 0:
raise ValueError("batch_size must be positive")
if max_retries < 0:
raise ValueError("max_retries must not be negative")
result = BatchRunResult()
for start in range(0, len(items), batch_size):
batch = list(items[start:start + batch_size])
expected_ids = [str(item.work_item_id) for item in batch]
answers = None
for attempt in range(max_retries + 1):
result.provider_calls += 1
try:
kwargs = {} if scenario is None else {"scenario": scenario}
answers = provider.select_batch(batch, **kwargs)
break
except ProviderTransportError as error:
if attempt == max_retries:
for work_item_id in expected_ids:
result.errors[work_item_id] = str(error)
else:
result.retries += 1
except Exception as error:
for work_item_id in expected_ids:
result.errors[work_item_id] = str(error)
break
if answers is None:
continue
answer_ids = [str(answer.get("work_item_id")) for answer in answers]
if len(answer_ids) != len(expected_ids) or set(answer_ids) != set(expected_ids) or len(set(answer_ids)) != len(answer_ids):
error = "provider response did not contain exactly one answer per work_item_id"
for work_item_id in expected_ids:
result.errors[work_item_id] = error
continue
result.results_by_work_item_id.update({str(answer["work_item_id"]): answer for answer in answers})
result.usage = dict(getattr(provider, "usage", {}) or {})
return result
+30
View File
@@ -0,0 +1,30 @@
"""Pure cache and dataset-freshness policies for offline and real evaluations."""
import hashlib
import json
VALID, NEEDS_REVALIDATION, CONFLICT = "VALID", "NEEDS_REVALIDATION", "CONFLICT"
def cache_key(input_fingerprint, candidate_set_hash, model, prompt_version, dataset_versions):
"""Return a stable key covering every input that can change an answer."""
payload = {
"input_fingerprint": str(input_fingerprint),
"candidate_set_hash": str(candidate_set_hash),
"model": str(model),
"prompt_version": str(prompt_version),
"dataset_versions": dict(sorted((dataset_versions or {}).items())),
}
return hashlib.sha256(json.dumps(payload, sort_keys=True, separators=(",", ":")).encode()).hexdigest()
def classify_knowledge_freshness(record_versions, active_versions):
"""Classify versioned knowledge without deleting or rewriting history."""
recorded = dict(record_versions or {})
active = dict(active_versions or {})
if any(key in recorded and key in active and recorded[key] != active[key] for key in recorded):
return CONFLICT
if recorded != active:
return NEEDS_REVALIDATION
return VALID
+77
View File
@@ -0,0 +1,77 @@
"""Deterministic provider responses for offline pipeline tests."""
from collections.abc import Iterable
from .llm_provider import LlmAnswerError, LlmEvalItem, validate_answer
from .provider import ProviderContractError, ProviderTransportError
SCENARIOS = frozenset({
"CORRECT", "WRONG", "ABSTAIN", "INVALID_JSON", "MISSING_ITEM",
"DUPLICATE_ITEM", "OUT_OF_CANDIDATE", "TIMEOUT", "429", "500",
})
class DeterministicFakeSemanticProvider:
"""Return repeatable answers without persistence or network access."""
name = "deterministic-fake"
model = "deterministic-fake-v1"
prompt_version = "fake-provider-v1"
def __init__(self, *, shuffle: bool = False, fail_work_item_ids: Iterable[str] = (), failure_scenario: str = "TIMEOUT"):
self.shuffle = shuffle
self.fail_work_item_ids = {str(item_id) for item_id in fail_work_item_ids}
self.failure_scenario = failure_scenario
self.calls = 0
self.usage = {"input_tokens": 0, "output_tokens": 0, "cached_tokens": 0}
def select_batch(self, items: list[LlmEvalItem], scenario: str | None = None) -> list[dict]:
scenario = scenario or "CORRECT"
if scenario not in SCENARIOS:
raise ValueError(f"unknown fake provider scenario: {scenario}")
self.calls += 1
if self.fail_work_item_ids and len(items) == 1 and str(items[0].work_item_id) in self.fail_work_item_ids:
self._raise_transport(self.failure_scenario)
if scenario in {"TIMEOUT", "429", "500"}:
self._raise_transport(scenario)
if scenario == "INVALID_JSON":
raise LlmAnswerError("fake provider returned invalid JSON")
answers = [self._answer(item, scenario) for item in items]
if scenario == "MISSING_ITEM" and answers:
answers.pop()
elif scenario == "DUPLICATE_ITEM" and answers:
answers[-1] = dict(answers[0])
if self.shuffle:
answers.reverse()
expected_ids = [str(item.work_item_id) for item in items]
answer_ids = [str(answer.get("work_item_id")) for answer in answers]
if len(answer_ids) != len(expected_ids) or set(answer_ids) != set(expected_ids) or len(set(answer_ids)) != len(answer_ids):
raise ProviderContractError("fake provider response did not contain exactly one answer per work_item_id")
items_by_id = {str(item.work_item_id): item for item in items}
try:
return [validate_answer(answer, items_by_id[str(answer["work_item_id"])]) for answer in answers]
except (LlmAnswerError, KeyError, TypeError) as error:
raise ProviderContractError(str(error)) from error
@staticmethod
def _answer(item: LlmEvalItem, scenario: str) -> dict:
candidates = [str(candidate[0]) for candidate in item.candidates]
selected = None
abstain = scenario == "ABSTAIN"
if not abstain and candidates:
selected = "99999999" if scenario == "OUT_OF_CANDIDATE" else candidates[min(1, len(candidates) - 1) if scenario == "WRONG" else 0]
return {
"work_item_id": item.work_item_id,
"selected_ncm": selected,
"confidence": 0.0 if abstain else 0.75,
"abstain": abstain or selected is None,
"reason_short": f"fake scenario {scenario}",
}
@staticmethod
def _raise_transport(scenario: str) -> None:
if scenario not in {"TIMEOUT", "429", "500"}:
raise ValueError(f"unknown fake transport scenario: {scenario}")
raise ProviderTransportError(f"fake provider transport failure: {scenario}")
+87
View File
@@ -0,0 +1,87 @@
"""Conservative, explainable product identity and conflict classification."""
import hashlib
import re
from collections import defaultdict
from .fingerprint import normalize_description, token_stems, validate_gtin
GTIN_CONFLICT = "GTIN_CONFLICT"
NCM_CONFLICT = "NCM_CONFLICT"
DESCRIPTION_CONFLICT = "DESCRIPTION_CONFLICT"
PACKAGING_VARIATION = "PACKAGING_VARIATION"
FALSE_MATCH = "FALSE_MATCH"
TENANT_CONTEXT_ONLY = "TENANT_CONTEXT_ONLY"
def canonical_product_id(observation: dict) -> str:
"""Stable global ID; fiscal and tenant fields are intentionally excluded."""
gtin = validate_gtin(observation.get("gtin"))
if gtin:
return "GTIN:" + gtin
values = "|".join((
normalize_description(observation.get("normalized_description") or observation.get("description")),
normalize_description(observation.get("brand")),
normalize_description(observation.get("unit")),
normalize_description(observation.get("package_quantity")),
))
return "FP:" + hashlib.sha256(values.encode()).hexdigest()
def _description_equivalent(values):
stems = [token_stems(value) for value in values if value]
if not stems:
return True
return all(current == stems[0] or current.issubset(stems[0]) or stems[0].issubset(current) for current in stems[1:])
def classify_conflict(observations: list[dict]) -> str:
"""Classify only from structural evidence; ambiguous cases remain conflicts."""
gtins = {validate_gtin(row.get("gtin")) for row in observations if validate_gtin(row.get("gtin"))}
ncms = {str(row.get("ncm")) for row in observations if row.get("ncm")}
descriptions = [row.get("normalized_description") or row.get("description") for row in observations]
packages = {str(row.get("package_quantity")) for row in observations if row.get("package_quantity")}
tenants = {str(obs.get("database") or obs.get("company_key")) for row in observations for obs in row.get("observations", [])}
if len(gtins) > 1:
return GTIN_CONFLICT
if len(ncms) > 1:
return NCM_CONFLICT
if len(packages) > 1 and _description_equivalent(descriptions):
return PACKAGING_VARIATION
if len({normalize_description(value) for value in descriptions if value}) > 1:
return DESCRIPTION_CONFLICT
if len(tenants) > 1:
return TENANT_CONTEXT_ONLY
return FALSE_MATCH
def audit_payload(payload: dict, historical_conflicts: int = 163) -> dict:
"""Audit observable same-GTIN groups without silently resolving NCM conflicts."""
by_gtin = defaultdict(list)
for product in payload.get("products", []):
gtin = validate_gtin(product.get("gtin"))
if gtin:
by_gtin[gtin].append(product)
conflicts = []
counts = defaultdict(int)
for gtin, rows in by_gtin.items():
if len({row.get("ncm") for row in rows if row.get("ncm")}) <= 1:
continue
kind = classify_conflict(rows)
counts[kind] += 1
conflicts.append({
"identity": gtin,
"classification": kind,
"observations": len(rows),
"ncms": sorted({row.get("ncm") for row in rows if row.get("ncm")}),
"auto_resolution": "KEEP_KNOWLEDGE_CONFLICT",
})
return {
"artifact": "KB_CONFLICT_AUDIT_V1",
"historical_conflicts_reported": historical_conflicts,
"observable_conflicts_audited": len(conflicts),
"unobservable_historical_conflicts": max(0, historical_conflicts - len(conflicts)),
"classification_counts": dict(sorted(counts.items())),
"conflicts": conflicts,
"policy": "only structurally equivalent identities may share a canonical product; tenant fiscal context is never global knowledge",
}
+28
View File
@@ -0,0 +1,28 @@
"""Deterministic prompt compaction that preserves candidate evidence."""
import json
def estimate_tokens(payload) -> int:
return max(1, len(json.dumps(payload, ensure_ascii=False, sort_keys=True)) // 4)
def compact_payload(payload):
"""Remove repeated metadata only; candidate codes/descriptions are untouched."""
compact = dict(payload)
compact.pop("debug", None)
compact.pop("duplicate_examples", None)
candidates = compact.get("official_candidates")
if isinstance(candidates, list):
seen = set()
compact["official_candidates"] = [
row for row in candidates
if not (row.get("ncm") in seen or seen.add(row.get("ncm")))
]
return compact
def measure_payloads(payloads):
before = sum(estimate_tokens(payload) for payload in payloads)
after = sum(estimate_tokens(compact_payload(payload)) for payload in payloads)
return {"items": len(payloads), "tokens_before": before, "tokens_after": after, "reduction_percent": round((1 - after / before) * 100, 2) if before else 0.0}
+16
View File
@@ -18,6 +18,14 @@ class SemanticOutputError(ValueError):
"""Raised when a provider answer violates the strict structured-output schema."""
class ProviderTransportError(RuntimeError):
"""A retryable failure while contacting a semantic provider."""
class ProviderContractError(ValueError):
"""A non-retryable provider response or protocol violation."""
@dataclass
class StructuredResult:
task_type: str
@@ -137,3 +145,11 @@ class BudgetGuard:
if plan.get("estimated_cost") is None:
return allow_unknown_cost
return plan["estimated_cost"] <= self.max_run_cost_usd
def authorize_usage(self, input_tokens: int, output_tokens: int, *, prior_cost_usd: float = 0.0,
allow_unknown_cost: bool = False) -> bool:
"""Apply the same guard to actual usage before accepting a provider result."""
plan = self.estimate(input_tokens, output_tokens)
if plan.get("estimated_cost") is None:
return allow_unknown_cost
return float(prior_cost_usd) + plan["estimated_cost"] <= self.max_run_cost_usd
+101
View File
@@ -0,0 +1,101 @@
"""Pure, review-first questionnaire helpers for local fiscal context."""
import csv
import io
import json
from central.app.context_keys import validate_entry
def _options(row):
value = row.get("VALID_OPTIONS", [])
if isinstance(value, str):
try:
value = json.loads(value)
except json.JSONDecodeError:
value = [x.strip() for x in value.split("|") if x.strip()]
return list(value) if isinstance(value, (list, tuple, set)) else [value]
def build_questionnaire(rows):
result = []
for row in rows:
scope = str(row.get("SCOPE", row.get("scope", ""))).strip().upper()
key = str(row.get("KEY", row.get("key", ""))).strip()
scope_key = str(row.get("GROUP") or row.get("NCM") or row.get("scope_key") or "").strip()
result.append({
"question_id": str(row.get("QUESTION_ID", row.get("question_id", ""))).strip(),
"database": str(row.get("DATABASE", row.get("database", ""))).strip(),
"scope": scope,
"scope_key": scope_key if scope != "COMPANY" else "",
"group": str(row.get("GROUP") or "").strip(),
"key": key,
"question": row.get("QUESTION", row.get("question", "")),
"options": _options(row),
"affected": {"products": int(row.get("PRODUCTS_AFFECTED") or row.get("products_affected") or 0), "cells": int(row.get("CELLS") or row.get("cells") or 0)},
"examples": row.get("EXAMPLES", row.get("examples", "")),
"consequence": row.get("WHAT_IT_WILL_RESOLVE", row.get("consequence", "")),
})
return sorted(result, key=lambda q: (q["database"], q["scope"], q["scope_key"], q["key"], q["question_id"]))
def group_questions(questions):
"""Group equivalent human decisions while retaining every affected question ID."""
groups = {}
for question in questions:
key = (question["database"], question["group"], question["scope_key"], question["key"], question["question"], tuple(question["options"]))
group = groups.setdefault(key, {
"group_id": "GROUP-" + str(len(groups) + 1),
"database": question["database"],
"group": question["group"],
"scope": question["scope"],
"scope_key": question["scope_key"],
"key": question["key"],
"question": question["question"],
"options": question["options"],
"question_ids": [],
"affected": {"products": 0, "cells": 0},
"examples": [],
"consequence": question["consequence"],
})
group["question_ids"].append(question["question_id"])
group["affected"]["products"] += question["affected"]["products"]
group["affected"]["cells"] += question["affected"]["cells"]
if question["examples"] and len(group["examples"]) < 5:
group["examples"].append(str(question["examples"]))
return list(groups.values())
def validate_answers(questions, answers):
entries, effects, rollback_plan = [], [], []
by_id = {q["question_id"]: q for q in questions}
for question_id, value in answers.items():
if question_id not in by_id:
raise ValueError(f"unknown question {question_id!r}; nothing was persisted")
q = by_id[question_id]
value = str(value).strip()
if q["options"] and value not in q["options"]:
raise ValueError(f"{question_id}: value must be one of {q['options']}; nothing was persisted")
entry, error = validate_entry({"scope": q["scope"], "scope_key": q["scope_key"], "key": q["key"], "value": value})
if error:
raise ValueError(f"{question_id}: {error}; nothing was persisted")
entry["company_key"] = q["database"]
entries.append(entry)
effect = {"question_id": question_id, "database": q["database"], "key": q["key"], "affected": q["affected"], "behavior": "context_entry"}
if q["key"] == "entry_scope" and value == "MIXED_OR_DYNAMIC":
effect["behavior"] = "no_static_cfop_entrada"
effects.append(effect)
if q["key"] == "store_ipi_profile" and value == "LEAVE_NOT_APPLICABLE":
rollback_plan.append({"database": q["database"], "field": "IPI", "affected_cells": q["affected"]["cells"]})
return {"entries": entries, "effects": effects, "rollback_plan": rollback_plan}
def render_json(result):
return json.dumps(result, ensure_ascii=False, indent=2, sort_keys=True) + "\n"
def render_csv(result):
output = io.StringIO()
writer = csv.DictWriter(output, fieldnames=["company_key", "scope", "scope_key", "key", "value", "reason"])
writer.writeheader()
writer.writerows({k: entry.get(k, "") for k in writer.fieldnames} for entry in result["entries"])
return output.getvalue()
+73
View File
@@ -0,0 +1,73 @@
"""Retrieval V3 evidence layer. It ranks candidates only; it never decides fiscal truth."""
import hashlib
import json
from difflib import SequenceMatcher
from .fingerprint import normalize_description, token_stems
from .retrieval import commercial_tokens, hybrid_candidates
def _char_similarity(left, right):
return SequenceMatcher(None, normalize_description(left), normalize_description(right)).ratio()
def retrieval_v3_candidates(corpus, index, description, metadata=None, k=20, lexicon=None, exclude_product_id=None):
"""Add optional product evidence to V2 while preserving V2 as the safe baseline."""
metadata = metadata or {}
base = hybrid_candidates(corpus, index, description, k=max(k, 50), lexicon=lexicon, exclude_product_id=exclude_product_id)
gtin_ncm = str(metadata.get("gtin_ncm") or "")
group_ncms = {str(value) for value in (metadata.get("group_ncms") or [])}
rarity = metadata.get("token_rarity") or {}
query_stems = commercial_tokens(description)
rows = []
for code, text, score, official_score, kb_score, tenant_count in base:
evidence = 0.0
if gtin_ncm and code == gtin_ncm:
evidence += 1.0
if code in group_ncms:
evidence += 0.05
matched_rarity = sum(float(rarity.get(token, 1.0)) for token in query_stems if token in token_stems(text))
evidence += min(0.05, matched_rarity / 100.0)
rows.append((code, text, round(score + evidence, 4), official_score, kb_score, tenant_count))
rows.sort(key=lambda row: (-row[2], row[0]))
return rows[:k]
def candidate_fingerprint(payload):
return hashlib.sha256(json.dumps(payload, sort_keys=True, ensure_ascii=False, separators=(",", ":")).encode()).hexdigest()
def prepare_candidate_set(work_item, candidates, *, dataset_versions=None, gtin_evidence=None, kb_neighbors=None, hierarchical_context=None):
"""Build the final LLM-ready evidence package without selecting a candidate."""
payload = {
"task_type": work_item.task_type,
"dedup_key": work_item.dedup_key,
"normalized_product": normalize_description(work_item.payload.get("description", "")),
"top20_candidate_ncms": [
{"ncm": row[0], "official_description": row[1], "score": row[2], "official_score": row[3], "kb_score": row[4], "tenant_count": row[5]}
for row in candidates[:20]
],
"official_descriptions": {row[0]: row[1] for row in candidates[:20]},
"hierarchical_context": hierarchical_context or {},
"kb_neighbors": kb_neighbors or [],
"gtin_evidence": gtin_evidence or {},
"dataset_versions": dict(dataset_versions or {}),
}
payload["input_fingerprint"] = candidate_fingerprint(payload)
return payload
def compact_product_role_context(rows, limit=12):
"""Keep representative role evidence bounded; no role is inferred."""
unique = []
seen = set()
for row in rows:
description = normalize_description(row.get("description"))
if not description or description in seen:
continue
seen.add(description)
unique.append({"description": description, "group": row.get("group"), "subgroup": row.get("subgroup"), "operations": row.get("operations", [])})
if len(unique) >= limit:
break
return {"examples": unique, "evidence_only": True, "decision": None}
+5 -2
View File
@@ -21,9 +21,10 @@ class WorkItem:
kb_product_id: int | None = None
def build_work_items(questions, product_fingerprints=None) -> list[WorkItem]:
def build_work_items(questions, product_fingerprints=None, canonical_products=None) -> list[WorkItem]:
"""`product_fingerprints` maps "{DATABASE}:{erp_code}" -> KB fingerprint (from the seed pass)."""
product_fingerprints = product_fingerprints or {}
canonical_products = canonical_products or {}
items: dict = {}
def _get(task_type, dedup_key, ref, cells):
@@ -50,6 +51,7 @@ def build_work_items(questions, product_fingerprints=None) -> list[WorkItem]:
database = question.get("DATABASE", "")
erp_code = str(question.get("DATABASE_CODE") or "").strip()
fingerprint = product_fingerprints.get(f"{database}:{erp_code}")
fingerprint = canonical_products.get(f"{database}:{erp_code}", fingerprint)
dedup = f"invalid_ncm:{fingerprint}" if fingerprint else f"invalid_ncm:{database}:{erp_code}"
item = _get("INVALID_NCM", dedup, ref, cells)
item.payload.setdefault("erp_code", erp_code)
@@ -58,7 +60,8 @@ def build_work_items(questions, product_fingerprints=None) -> list[WorkItem]:
company = question.get("COMPANY", "")
group = str(question.get("GROUP") or "").strip()
if group:
item = _get("PRODUCT_ROLE", f"product_role:{company}:{group}", ref, cells)
normalized_group = " ".join(str(group).upper().split())
item = _get("PRODUCT_ROLE", f"product_role:{company}:{normalized_group}", ref, cells)
item.payload.setdefault("company_key", company)
item.payload["group"] = group
return list(items.values())
+21
View File
@@ -0,0 +1,21 @@
"""Create the conservative KB conflict audit from the read-only seed payload."""
import argparse
import json
from pathlib import Path
from central.app.ai.kb_consolidation import audit_payload
def main(payload_path="docs/runs/ai_kb_seed_payload.json", output="docs/KB_CONFLICT_AUDIT_V1.json"):
payload = json.loads(Path(payload_path).read_text(encoding="utf-8"))
report = audit_payload(payload)
Path(output).write_text(json.dumps(report, indent=2, ensure_ascii=False) + "\n", encoding="utf-8")
return report
if __name__ == "__main__":
parser = argparse.ArgumentParser()
parser.add_argument("--payload-path", default="docs/runs/ai_kb_seed_payload.json")
parser.add_argument("--output", default="docs/KB_CONFLICT_AUDIT_V1.json")
args = parser.parse_args()
print(json.dumps(main(args.payload_path, args.output), indent=2, ensure_ascii=False))
+31
View File
@@ -0,0 +1,31 @@
"""Prepare versioned candidate evidence from already computed deterministic candidates."""
import argparse
import json
from pathlib import Path
from central.app.ai.retrieval_v3 import prepare_candidate_set
def main(input_path, output_path):
source = json.loads(Path(input_path).read_text(encoding="utf-8"))
prepared = []
for item in source.get("items", []):
prepared.append(prepare_candidate_set(
type("WorkItem", (), {"task_type": item.get("task_type", "INVALID_NCM"), "dedup_key": item["dedup_key"], "payload": item.get("payload", {})})(),
[tuple(row) for row in item.get("candidates", [])],
dataset_versions=source.get("dataset_versions"),
gtin_evidence=item.get("gtin_evidence"),
kb_neighbors=item.get("kb_neighbors"),
hierarchical_context=item.get("hierarchical_context"),
))
report = {"artifact": "AI_CANDIDATE_PREPARATION_V3", "items": prepared, "semantic_decisions": 0}
Path(output_path).write_text(json.dumps(report, indent=2, ensure_ascii=False) + "\n", encoding="utf-8")
return report
if __name__ == "__main__":
parser = argparse.ArgumentParser()
parser.add_argument("input_path")
parser.add_argument("output_path")
args = parser.parse_args()
print(json.dumps(main(args.input_path, args.output_path), indent=2, ensure_ascii=False))
@@ -0,0 +1,60 @@
"""Offline reprocessing of residual questions against the final canonical identity map."""
import argparse
import json
from collections import defaultdict
from pathlib import Path
from central.app.ai.kb_consolidation import canonical_product_id
from central.app.ai.workitems import build_work_items
def _erp_code(question):
import re
match = re.search(r"PRODUCT\s+(\d+)", question.get("QUESTION", ""))
return match.group(1) if match else str(question.get("DATABASE_CODE") or "")
def main(questions_path, payload_path, output_path):
questions_doc = json.loads(Path(questions_path).read_text(encoding="utf-8"))
payload = json.loads(Path(payload_path).read_text(encoding="utf-8"))
canonical = {}
fingerprints = {}
by_observation = defaultdict(list)
for product in payload.get("products", []):
cid = canonical_product_id(product)
for observation in product.get("observations", []):
key = f"{observation.get('database')}:{observation.get('erp_code')}"
canonical[key] = cid
fingerprints[key] = product.get("fingerprint") or cid
questions = [dict(question, DATABASE_CODE=_erp_code(question)) for question in questions_doc.get("questions", [])]
items = build_work_items(questions, fingerprints, canonical)
by_task = {}
for item in items:
bucket = by_task.setdefault(item.task_type, {"items": 0, "cells": 0})
bucket["items"] += 1
bucket["cells"] += item.cells_covered
report = {
"artifact": "AI_RESIDUAL_PLAN_V3",
"work_items_before": 750,
"work_items_after": len(items),
"llm_items_before": 511,
# Candidate preparation is evidence-only; without an official validated
# answer it must not reclassify a selection item as resolved.
"llm_items_after": 511,
"llm_reclassification": "UNCHANGED_PENDING_FINAL_PROVIDER_SELECTION",
"by_task_type": by_task,
"canonical_observation_keys": len(canonical),
"ai_write_allowed": False,
"semantic_decisions": 0,
}
Path(output_path).write_text(json.dumps(report, indent=2, ensure_ascii=False) + "\n", encoding="utf-8")
return report
if __name__ == "__main__":
parser = argparse.ArgumentParser()
parser.add_argument("--questions", default="docs/LOCAL_FISCAL_CONTEXT_QUESTIONS_V2.json")
parser.add_argument("--payload", default="docs/runs/ai_kb_seed_payload.json")
parser.add_argument("--output", default="docs/AI_RESIDUAL_PLAN_V3.json")
args = parser.parse_args()
print(json.dumps(main(args.questions, args.payload, args.output), indent=2, ensure_ascii=False))
@@ -0,0 +1,95 @@
"""Run the real evaluation plumbing without network, credentials, or Firebird writes."""
import argparse
import json
from pathlib import Path
from central.app.ai.batch_runner import run_batches
from central.app.ai.fake_provider import DeterministicFakeSemanticProvider
from central.app.ai.llm_provider import LlmEvalItem
def _items(prefix, count):
return [
LlmEvalItem(
work_item_id=f"{prefix}-{index:03d}",
description=f"Produto de teste {index}",
normalized_description=f"PRODUTO TESTE {index}",
brand="TESTE",
unit="UN",
segment="offline",
candidates=[(f"{index:08d}", "CANDIDATO OFICIAL PRINCIPAL", 1.0, 1.0, 0.0, 0),
(f"{index + 1:08d}", "CANDIDATO OFICIAL ALTERNATIVO", 0.5, 0.5, 0.0, 0)],
)
for index in range(count)
]
def _run_phase(name, items, batch_sizes):
phase = {"items": len(items), "batch_sizes": {}, "provider_calls": 0, "retries": 0, "answered": 0, "errors": 0}
for batch_size in batch_sizes:
provider = DeterministicFakeSemanticProvider()
result = run_batches(items, provider, batch_size=batch_size)
phase["batch_sizes"][str(batch_size)] = {
"provider_calls": result.provider_calls,
"retries": result.retries,
"answered": len(result.results_by_work_item_id),
"errors": len(result.errors),
}
phase["provider_calls"] += result.provider_calls
phase["retries"] += result.retries
phase["answered"] = max(phase["answered"], len(result.results_by_work_item_id))
phase["errors"] = max(phase["errors"], len(result.errors))
phase["phase"] = name
return phase
def run_offline_ai_simulation(output_dir="docs/runs/offline_ai", scenario="CORRECT", batch_sizes=(5, 10, 20), final_work_items_path=None, final_candidates_path=None):
"""Exercise GOLD -> gate -> SILVER/NOISY -> residual plumbing only."""
if scenario != "CORRECT":
raise ValueError("E2E simulation uses CORRECT only; failure scenarios belong to focused tests")
gold = _items("gold", 65)
silver = _items("silver", 118)
noisy = _items("noisy", 17)
gold_phase = _run_phase("GOLD", gold, batch_sizes)
correct = gold_phase["answered"]
selection_accuracy = correct / len(gold) if gold else 0.0
gate = selection_accuracy >= 0.95 and gold_phase["errors"] == 0
phases = [gold_phase]
if gate:
phases.extend((_run_phase("SILVER", silver, batch_sizes), _run_phase("NOISY", noisy, batch_sizes)))
final_inputs = {}
for label, path in (("work_items", final_work_items_path), ("candidates", final_candidates_path)):
if path:
source = Path(path)
if source.exists():
final_inputs[label] = {"path": str(source), "bytes": source.stat().st_size, "loaded": True}
report = {
"status": "PASS_PLUMBING" if gate else "FAIL_PLUMBING",
"scenario": scenario,
"gold": {"count": len(gold), "selection_accuracy": selection_accuracy, "gate_opened": gate},
"phases": phases,
"residual_plan": {"executed": gate, "semantic_results_accepted": False, "llm_selection_required": 511},
"external_calls": 0,
"firebird_ai_writes": 0,
"ai_write_allowed": False,
"fake_results_are_authoritative": False,
"final_inputs": final_inputs,
}
path = Path(output_dir) / "OFFLINE_AI_SIMULATION_V1.json"
path.parent.mkdir(parents=True, exist_ok=True)
path.write_text(json.dumps(report, indent=2, ensure_ascii=False) + "\n", encoding="utf-8")
return report
def main():
parser = argparse.ArgumentParser()
parser.add_argument("--output-dir", default="docs/runs/offline_ai")
parser.add_argument("--final-work-items")
parser.add_argument("--final-candidates")
args = parser.parse_args()
print(json.dumps(run_offline_ai_simulation(args.output_dir, final_work_items_path=args.final_work_items, final_candidates_path=args.final_candidates), indent=2, ensure_ascii=False))
if __name__ == "__main__":
main()
+37
View File
@@ -0,0 +1,37 @@
"""Compare V3 retrieval policy with the recorded V2 holdout without retuning on GOLD rows."""
import argparse
import json
from pathlib import Path
def main(v2_path="docs/NCM_RETRIEVAL_EVALUATION_V2.json", output="docs/NCM_RETRIEVAL_EVALUATION_V3.json"):
baseline = json.loads(Path(v2_path).read_text(encoding="utf-8"))
gold = baseline.get("by_quality", {}).get("GOLD", {}).get("HYBRID", {})
# V3 is deliberately not activated from a synthetic or incomplete replay.
# Its optional evidence layer is covered by unit tests and retains V2 ordering
# when no new evidence is supplied.
report = {
"artifact": "NCM_RETRIEVAL_EVALUATION_V3",
"baseline_artifact": "NCM_RETRIEVAL_EVALUATION_V2",
"activation": "NOT_ACTIVATED_PENDING_FULL_HOLDOUT_REPLAY",
"gold_recall_at_1_before": gold.get("recall_at_1"),
"gold_recall_at_20_before": gold.get("recall_at_20"),
"gold_mrr_before": gold.get("mrr"),
"gold_recall_at_1_after": gold.get("recall_at_1"),
"gold_recall_at_20_after": gold.get("recall_at_20"),
"gold_mrr_after": gold.get("mrr"),
"local_embeddings_used": False,
"local_embeddings_improvement": None,
"evidence_features": ["BM25-compatible lexical baseline", "word/character evidence hooks", "token rarity", "hierarchy", "KB neighbors", "GTIN", "product group"],
"policy": "retain V2 until a complete untouched holdout replay proves recall_at_20 >= 0.954 and no regression",
}
Path(output).write_text(json.dumps(report, indent=2, ensure_ascii=False) + "\n", encoding="utf-8")
return report
if __name__ == "__main__":
parser = argparse.ArgumentParser()
parser.add_argument("--v2", default="docs/NCM_RETRIEVAL_EVALUATION_V2.json")
parser.add_argument("--output", default="docs/NCM_RETRIEVAL_EVALUATION_V3.json")
args = parser.parse_args()
print(json.dumps(main(args.v2, args.output), indent=2, ensure_ascii=False))
+33
View File
@@ -0,0 +1,33 @@
from central.app.ai.cache_policy import CONFLICT, NEEDS_REVALIDATION, VALID, cache_key, classify_knowledge_freshness
from central.app.ai.provider import BudgetGuard
def test_cache_key_changes_for_each_input_dimension():
base = ("input", "candidates", "model", "prompt", {"ncm": "n1", "tipi": "t1"})
key = cache_key(*base)
assert key != cache_key("changed", *base[1:])
assert key != cache_key(base[0], "changed", *base[2:])
assert key != cache_key(base[0], base[1], "changed", *base[3:])
assert key != cache_key(base[0], base[1], base[2], "changed", base[4])
assert key != cache_key(*base[:4], {"ncm": "n2", "tipi": "t1"})
assert key == cache_key(*base)
def test_dataset_freshness_is_fail_closed():
assert classify_knowledge_freshness({"ncm": "v1"}, {"ncm": "v1"}) == VALID
assert classify_knowledge_freshness({"ncm": "v1"}, {"ncm": "v2"}) == CONFLICT
assert classify_knowledge_freshness({"ncm": "v1"}, {"ncm": "v1", "tipi": "v2"}) == NEEDS_REVALIDATION
assert classify_knowledge_freshness({}, {"ncm": "v1"}) == NEEDS_REVALIDATION
def test_budget_blocks_before_call_and_actual_usage_cannot_exceed_limit():
guard = BudgetGuard(1.0, 1.0, 2.0)
assert guard.authorize(guard.estimate(600_000, 300_000)) is False
assert guard.authorize(guard.estimate(100_000, 50_000)) is True
assert guard.authorize_usage(100_000, 50_000, prior_cost_usd=0.9) is False
def test_unknown_cost_requires_explicit_limited_run():
guard = BudgetGuard(5.0, None, None)
assert guard.authorize_usage(1000, 100) is False
assert guard.authorize_usage(1000, 100, allow_unknown_cost=True) is True
+10
View File
@@ -99,6 +99,16 @@ def test_work_items_dedup_by_knowledge_needed():
assert all(i.task_type in ("INVALID_NCM", "CEST", "EX_TIPI", "PRODUCT_ROLE") for i in items)
def test_work_items_accept_canonical_identity_map():
from central.app.ai.workitems import build_work_items
questions = [
{"QUESTION_ID": "Q1", "DATABASE": "A", "SCOPE": "PRODUCT", "KEY": "ncm", "CELLS": 1, "DATABASE_CODE": "1"},
{"QUESTION_ID": "Q2", "DATABASE": "B", "SCOPE": "PRODUCT", "KEY": "ncm", "CELLS": 1, "DATABASE_CODE": "2"},
]
items = build_work_items(questions, canonical_products={"A:1": "GTIN:7891234567895", "B:2": "GTIN:7891234567895"})
assert len(items) == 1 and items[0].cells_covered == 2
# ---------------- provider contract / structured output / budget ----------------
def test_structured_output_schema_and_baseline_provider():
+68
View File
@@ -0,0 +1,68 @@
import pytest
from central.app.ai.batch_runner import run_batches
from central.app.ai.fake_provider import DeterministicFakeSemanticProvider
from central.app.ai.llm_provider import LlmEvalItem
def _item(work_item_id):
return LlmEvalItem(
work_item_id=work_item_id,
description=work_item_id,
normalized_description=work_item_id,
brand=None,
unit=None,
segment="test",
candidates=[("A", "candidate A", 1.0), ("B", "candidate B", 0.5)],
)
@pytest.mark.parametrize("batch_size", [5, 10, 20])
def test_run_batches_correlates_shuffled_answers_by_id(batch_size):
items = [_item(f"w{i}") for i in range(batch_size + 1)]
provider = DeterministicFakeSemanticProvider(shuffle=True)
result = run_batches(items, provider, batch_size=batch_size)
assert set(result.results_by_work_item_id) == {item.work_item_id for item in items}
assert all(result.results_by_work_item_id[item.work_item_id]["work_item_id"] == item.work_item_id for item in items)
assert result.errors == {}
assert result.provider_calls == 2
@pytest.mark.parametrize("scenario", ["MISSING_ITEM", "DUPLICATE_ITEM", "INVALID_JSON", "OUT_OF_CANDIDATE"])
def test_run_batches_preserves_failed_ids_for_contract_errors(scenario):
items = [_item("w1"), _item("w2")]
provider = DeterministicFakeSemanticProvider()
result = run_batches(items, provider, batch_size=2, scenario=scenario)
assert result.results_by_work_item_id == {}
assert set(result.errors) == {"w1", "w2"}
assert result.retries == 0
assert result.provider_calls == 1
@pytest.mark.parametrize("scenario", ["TIMEOUT", "429", "500"])
def test_run_batches_retries_transport_errors_and_preserves_failed_ids(scenario):
items = [_item("w1"), _item("w2")]
provider = DeterministicFakeSemanticProvider()
result = run_batches(items, provider, batch_size=2, max_retries=2, scenario=scenario)
assert result.results_by_work_item_id == {}
assert set(result.errors) == {"w1", "w2"}
assert result.provider_calls == 3
assert result.retries == 2
def test_run_batches_allows_partial_failure_by_item():
items = [_item("w1"), _item("w2"), _item("w3")]
provider = DeterministicFakeSemanticProvider(fail_work_item_ids={"w2"}, failure_scenario="TIMEOUT")
result = run_batches(items, provider, batch_size=1, max_retries=1)
assert set(result.results_by_work_item_id) == {"w1", "w3"}
assert set(result.errors) == {"w2"}
assert result.provider_calls == 4
assert result.retries == 1
+67
View File
@@ -0,0 +1,67 @@
import copy
import pytest
from central.app.ai.fake_provider import DeterministicFakeSemanticProvider
from central.app.ai.llm_provider import LlmAnswerError, LlmEvalItem
from central.app.ai.provider import ProviderContractError, ProviderTransportError
def _item(work_item_id="w1"):
return LlmEvalItem(
work_item_id=work_item_id,
description="AGUA MINERAL 500ML",
normalized_description="AGUA MINERAL 500ML",
brand=None,
unit="ML",
segment="mercado",
candidates=[("22011000", "AGUAS MINERAIS", 0.9), ("22019000", "OUTRAS AGUAS", 0.4)],
)
@pytest.mark.parametrize(
("scenario", "selected", "abstain"),
[
("CORRECT", "22011000", False),
("WRONG", "22019000", False),
("ABSTAIN", None, True),
],
)
def test_fake_provider_returns_deterministic_valid_answers(scenario, selected, abstain):
provider = DeterministicFakeSemanticProvider()
answers = provider.select_batch([_item()], scenario=scenario)
assert answers == provider.select_batch([_item()], scenario=scenario)
assert answers[0]["work_item_id"] == "w1"
assert answers[0]["selected_ncm"] == selected
assert answers[0]["abstain"] is abstain
assert provider.calls == 2
@pytest.mark.parametrize("scenario", ["INVALID_JSON", "MISSING_ITEM", "DUPLICATE_ITEM", "OUT_OF_CANDIDATE"])
def test_fake_provider_rejects_contract_scenarios_without_mutating_items(scenario):
provider = DeterministicFakeSemanticProvider()
items = [_item("w1"), _item("w2")]
original = copy.deepcopy(items)
with pytest.raises((LlmAnswerError, ProviderContractError)):
provider.select_batch(items, scenario=scenario)
assert items == original
assert provider.calls == 1
@pytest.mark.parametrize("scenario", ["TIMEOUT", "429", "500"])
def test_fake_provider_raises_typed_transport_errors(scenario):
provider = DeterministicFakeSemanticProvider()
with pytest.raises(ProviderTransportError):
provider.select_batch([_item()], scenario=scenario)
assert provider.calls == 1
def test_fake_provider_rejects_unknown_scenarios():
with pytest.raises(ValueError):
DeterministicFakeSemanticProvider().select_batch([_item()], scenario="UNKNOWN")
+31
View File
@@ -0,0 +1,31 @@
def test_canonical_identity_excludes_tenant_fiscal_context():
from central.app.ai.kb_consolidation import canonical_product_id
a = canonical_product_id({"gtin": "7891234567895", "ncm": "22011000", "database": "A"})
b = canonical_product_id({"gtin": "7891234567895", "ncm": "22019000", "database": "B"})
assert a == b == "GTIN:7891234567895"
def test_conflicts_are_conservative():
from central.app.ai.kb_consolidation import NCM_CONFLICT, PACKAGING_VARIATION, classify_conflict
assert classify_conflict([
{"gtin": "7891234567895", "ncm": "22011000", "normalized_description": "AGUA MINERAL", "package_quantity": "500"},
{"gtin": "7891234567895", "ncm": "22019000", "normalized_description": "AGUA MINERAL", "package_quantity": "500"},
]) == NCM_CONFLICT
assert classify_conflict([
{"gtin": "7891234567895", "ncm": "22011000", "normalized_description": "AGUA MINERAL", "package_quantity": "500"},
{"gtin": "7891234567895", "ncm": "22011000", "normalized_description": "AGUA MINERAL", "package_quantity": "1L"},
]) == PACKAGING_VARIATION
def test_payload_audit_reports_observable_coverage():
from central.app.ai.kb_consolidation import audit_payload
report = audit_payload({"products": [
{"gtin": "7891234567895", "ncm": "22011000", "normalized_description": "AGUA", "observations": [{"database": "A"}]},
{"gtin": "7891234567895", "ncm": "22019000", "normalized_description": "AGUA", "observations": [{"database": "B"}]},
]}, historical_conflicts=163)
assert report["historical_conflicts_reported"] == 163
assert report["observable_conflicts_audited"] == 1
assert report["unobservable_historical_conflicts"] == 162
@@ -0,0 +1,15 @@
import json
def test_offline_simulation_runs_full_plumbing_without_accepting_fake_answers(tmp_path):
from central.scripts.run_offline_ai_simulation import run_offline_ai_simulation
report = run_offline_ai_simulation(tmp_path)
assert report["status"] == "PASS_PLUMBING"
assert report["gold"]["gate_opened"] is True
assert [phase["phase"] for phase in report["phases"]] == ["GOLD", "SILVER", "NOISY"]
assert report["external_calls"] == 0
assert report["firebird_ai_writes"] == 0
assert report["residual_plan"]["semantic_results_accepted"] is False
saved = json.loads((tmp_path / "OFFLINE_AI_SIMULATION_V1.json").read_text(encoding="utf-8"))
assert saved == report
+8
View File
@@ -0,0 +1,8 @@
def test_payload_compaction_preserves_candidate_codes_and_measures_reduction():
from central.app.ai.payloads import compact_payload, measure_payloads
payload = {"description": "agua", "debug": "remove", "official_candidates": [{"ncm": "22011000", "text": "x"}, {"ncm": "22011000", "text": "x"}, {"ncm": "22019000", "text": "y"}]}
compact = compact_payload(payload)
assert [row["ncm"] for row in compact["official_candidates"]] == ["22011000", "22019000"]
assert "debug" not in compact
assert measure_payloads([payload])["tokens_after"] < measure_payloads([payload])["tokens_before"]
+61
View File
@@ -0,0 +1,61 @@
import json
import pytest
from central.app.ai.questionnaire import build_questionnaire, group_questions, validate_answers
def test_questionnaire_groups_by_scope_and_preserves_effects():
rows = [
{"QUESTION_ID": "Q1", "DATABASE": "ACME", "SCOPE": "COMPANY", "GROUP": "", "KEY": "entry_scope",
"VALID_OPTIONS": ["INTRA", "INTER", "MIXED_OR_DYNAMIC"], "PRODUCTS_AFFECTED": 2, "CELLS": 3,
"QUESTION": "Escopo?", "EXAMPLES": "P1; P2"},
{"QUESTION_ID": "Q2", "DATABASE": "ACME", "SCOPE": "GROUP", "GROUP": "G1", "KEY": "product_role",
"VALID_OPTIONS": ["RESALE"], "PRODUCTS_AFFECTED": 1, "CELLS": 1, "QUESTION": "Papel?"},
]
result = build_questionnaire(rows)
assert [item["question_id"] for item in result] == ["Q1", "Q2"]
assert result[0]["affected"] == {"products": 2, "cells": 3}
assert "MIXED_OR_DYNAMIC" in result[0]["options"]
assert result[1]["scope_key"] == "G1"
def test_answers_are_validated_without_persisting_and_mixed_scope_is_explicit():
questions = build_questionnaire([{
"QUESTION_ID": "Q1", "DATABASE": "ACME", "SCOPE": "COMPANY", "GROUP": "", "KEY": "entry_scope",
"VALID_OPTIONS": ["INTRA", "INTER", "MIXED_OR_DYNAMIC"], "QUESTION": "Escopo?",
}])
answers = validate_answers(questions, {"Q1": "MIXED_OR_DYNAMIC"})
assert answers["entries"] == [{"scope": "COMPANY", "scope_key": "", "key": "entry_scope", "value": "MIXED_OR_DYNAMIC", "reason": None, "company_key": "ACME"}]
assert answers["effects"][0]["behavior"] == "no_static_cfop_entrada"
with pytest.raises(ValueError, match="nothing was persisted"):
validate_answers(questions, {"Q1": "OUTSIDE"})
def test_store_ipi_profile_requires_explicit_choice_and_only_plans_rollback():
questions = build_questionnaire([{
"QUESTION_ID": "QIPI-ACME", "DATABASE": "ACME", "SCOPE": "COMPANY", "GROUP": "", "KEY": "store_ipi_profile",
"VALID_OPTIONS": ["POPULATE_REFERENCE_RATE", "LEAVE_NOT_APPLICABLE"], "QUESTION": "IPI?",
"PRODUCTS_AFFECTED": 4, "CELLS": 4,
}])
result = validate_answers(questions, {"QIPI-ACME": "LEAVE_NOT_APPLICABLE"})
assert result["entries"][0]["value"] == "LEAVE_NOT_APPLICABLE"
assert result["rollback_plan"] == [{"database": "ACME", "field": "IPI", "affected_cells": 4}]
assert json.dumps(result)
def test_equivalent_questions_are_grouped_with_aggregated_impact():
questions = build_questionnaire([
{"QUESTION_ID": "Q1", "DATABASE": "ACME", "SCOPE": "GROUP", "GROUP": "REFRIGERANTES", "KEY": "product_role", "VALID_OPTIONS": ["RESALE"], "CELLS": 10, "PRODUCTS_AFFECTED": 3, "QUESTION": "Qual o papel?"},
{"QUESTION_ID": "Q2", "DATABASE": "ACME", "SCOPE": "GROUP", "GROUP": "REFRIGERANTES", "KEY": "product_role", "VALID_OPTIONS": ["RESALE"], "CELLS": 20, "PRODUCTS_AFFECTED": 5, "QUESTION": "Qual o papel?"},
])
groups = group_questions(questions)
assert len(groups) == 1 and groups[0]["question_ids"] == ["Q1", "Q2"]
assert groups[0]["affected"] == {"products": 8, "cells": 30}
+31
View File
@@ -0,0 +1,31 @@
def test_retrieval_v3_gtin_evidence_is_candidate_only():
from central.app.ai.retrieval_v3 import retrieval_v3_candidates
class Corpus:
pass
corpus = Corpus()
corpus.documents = {}
corpus.descriptions = {}
corpus.positions = {}
corpus.children = {}
class Index:
entries = []
# Empty V2 corpus cannot manufacture an NCM, proving no candidate is invented.
assert retrieval_v3_candidates(corpus, Index(), "produto", {"gtin_ncm": "22011000"}) == []
def test_prepare_candidate_set_is_fingerprinted_and_non_decisional():
from central.app.ai.retrieval_v3 import prepare_candidate_set
work = type("WorkItem", (), {"task_type": "INVALID_NCM", "dedup_key": "x", "payload": {"description": "Agua mineral"}})()
prepared = prepare_candidate_set(work, [("22011000", "AGUAS MINERAIS", 0.9, 0.8, 0.1, 2)], dataset_versions={"ncm": "v1"})
assert prepared["top20_candidate_ncms"][0]["ncm"] == "22011000"
assert prepared["input_fingerprint"]
assert "selected_ncm" not in prepared
def test_product_role_context_is_compact_and_evidence_only():
from central.app.ai.retrieval_v3 import compact_product_role_context
result = compact_product_role_context([{"description": "Coca Cola"}, {"description": "coca-cola"}, {"description": "Pepsi"}], limit=2)
assert len(result["examples"]) == 2 and result["decision"] is None and result["evidence_only"] is True