Livrare LOT 1 - Didi
This commit is contained in:
commit
5380c3fc63
990 changed files with 133308 additions and 0 deletions
143
ai_platform/modules/didi_brain/lint/_state.py
Normal file
143
ai_platform/modules/didi_brain/lint/_state.py
Normal file
|
|
@ -0,0 +1,143 @@
|
|||
"""Persistent state for the Lint pass.
|
||||
|
||||
A single JSON file at `lint/_contradictions.json` stores every pair we have
|
||||
already evaluated, keyed by a stable pair_hash. Re-runs skip any pair already
|
||||
evaluated at the current prompt version.
|
||||
|
||||
Only CONTRADICTORY verdicts are the "interesting output" but we keep
|
||||
EQUIVALENT / INCOMPARABLE too because:
|
||||
|
||||
- EQUIVALENT pairs are paraphrase clusters (useful later for canonicalization)
|
||||
- All labels matter for the idempotency ledger
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import hashlib
|
||||
import json
|
||||
from dataclasses import asdict, dataclass, field
|
||||
from datetime import datetime, timezone
|
||||
from pathlib import Path
|
||||
|
||||
_STATE_FILE = Path(__file__).resolve().parent / "_contradictions.json"
|
||||
STATE_VERSION = "v1"
|
||||
|
||||
|
||||
def pair_hash(atom_a_id: str, atom_b_id: str) -> str:
|
||||
"""Canonical hash of an (a, b) pair, invariant under swap."""
|
||||
lo, hi = sorted((atom_a_id, atom_b_id))
|
||||
return hashlib.sha1(f"{lo}|{hi}".encode("utf-8")).hexdigest()[:16]
|
||||
|
||||
|
||||
@dataclass(slots=True, frozen=True)
|
||||
class PairVerdict:
|
||||
pair_hash: str
|
||||
atom_a_id: str
|
||||
atom_b_id: str
|
||||
atom_a_url: str
|
||||
atom_b_url: str
|
||||
atom_a_claim: str
|
||||
atom_b_claim: str
|
||||
label: str # EQUIVALENT / CONTRADICTORY / INCOMPARABLE
|
||||
confidence: float
|
||||
similarity: float # the first-stage embedding similarity that selected this pair
|
||||
detected_at: str
|
||||
prompt_version: str
|
||||
error: str | None = None
|
||||
|
||||
|
||||
@dataclass(slots=True)
|
||||
class LintStats:
|
||||
atoms_seen: int = 0
|
||||
candidates_generated: int = 0
|
||||
pairs_evaluated: int = 0
|
||||
pairs_skipped_cached: int = 0
|
||||
contradictory: int = 0
|
||||
equivalent: int = 0
|
||||
incomparable: int = 0
|
||||
errors: int = 0
|
||||
|
||||
|
||||
class LintState:
|
||||
"""Loads/saves the contradiction ledger JSON with idempotency support."""
|
||||
|
||||
def __init__(self, path: Path | None = None):
|
||||
self._path = path or _STATE_FILE
|
||||
self._evaluated: dict[str, PairVerdict] = {}
|
||||
self._prompt_version: str = "v1"
|
||||
self._last_run: str | None = None
|
||||
self._load()
|
||||
|
||||
def _load(self) -> None:
|
||||
if not self._path.exists():
|
||||
return
|
||||
try:
|
||||
data = json.loads(self._path.read_text(encoding="utf-8"))
|
||||
except (json.JSONDecodeError, OSError):
|
||||
# Corrupt or unreadable state — start fresh rather than crash.
|
||||
return
|
||||
self._prompt_version = data.get("prompt_version", "v1")
|
||||
self._last_run = data.get("last_run")
|
||||
for raw in data.get("evaluated", []):
|
||||
try:
|
||||
pv = PairVerdict(**raw)
|
||||
except TypeError:
|
||||
continue
|
||||
self._evaluated[pv.pair_hash] = pv
|
||||
|
||||
def set_prompt_version(self, pv: str) -> None:
|
||||
self._prompt_version = pv
|
||||
|
||||
def already_evaluated(self, h: str, prompt_version: str) -> bool:
|
||||
pv = self._evaluated.get(h)
|
||||
return pv is not None and pv.prompt_version == prompt_version
|
||||
|
||||
def get(self, h: str) -> PairVerdict | None:
|
||||
return self._evaluated.get(h)
|
||||
|
||||
def upsert(self, verdict: PairVerdict) -> None:
|
||||
self._evaluated[verdict.pair_hash] = verdict
|
||||
|
||||
@property
|
||||
def all_verdicts(self) -> list[PairVerdict]:
|
||||
return list(self._evaluated.values())
|
||||
|
||||
@property
|
||||
def all_contradictions(self) -> list[PairVerdict]:
|
||||
return [v for v in self._evaluated.values() if v.label == "CONTRADICTORY"]
|
||||
|
||||
@property
|
||||
def all_equivalents(self) -> list[PairVerdict]:
|
||||
return [v for v in self._evaluated.values() if v.label == "EQUIVALENT"]
|
||||
|
||||
def save(self) -> None:
|
||||
"""Write the full ledger to disk atomically (write-temp + rename)."""
|
||||
self._path.parent.mkdir(parents=True, exist_ok=True)
|
||||
contras = self.all_contradictions
|
||||
equivs = self.all_equivalents
|
||||
body = {
|
||||
"version": STATE_VERSION,
|
||||
"prompt_version": self._prompt_version,
|
||||
"last_run": datetime.now(timezone.utc).isoformat(),
|
||||
"stats": {
|
||||
"total_pairs": len(self._evaluated),
|
||||
"contradictory": len(contras),
|
||||
"equivalent": len(equivs),
|
||||
"incomparable": len(self._evaluated) - len(contras) - len(equivs),
|
||||
},
|
||||
"evaluated": [asdict(v) for v in self._evaluated.values()],
|
||||
}
|
||||
tmp = self._path.with_suffix(self._path.suffix + ".tmp")
|
||||
tmp.write_text(
|
||||
json.dumps(body, indent=2, ensure_ascii=False),
|
||||
encoding="utf-8",
|
||||
)
|
||||
tmp.replace(self._path)
|
||||
|
||||
@property
|
||||
def path(self) -> Path:
|
||||
return self._path
|
||||
|
||||
|
||||
def now_iso() -> str:
|
||||
return datetime.now(timezone.utc).isoformat()
|
||||
Loading…
Add table
Add a link
Reference in a new issue