"""As-of harness (§6.6 look-ahead guard). Every scorer reads the `visible_claims` TEMP VIEW, never `claims` directly: at nomination time only claims dated <= as_of are visible, so the backtest can't reward noticing what already happened. The view also resolves merged canonical topics (topics.status='merged') to a stable `topic_id`. """ from __future__ import annotations import sqlite3 class Scorer: """Context manager that binds a run to an as_of date and exposes `visible_claims`. mode='backtest' enforces strict as-of discipline; 'forward' is the live pilot. as_of is a controlled ISO date (YYYY-MM-DD) — safe to inline into the view DDL (views can't take params).""" def __init__(self, conn: sqlite3.Connection, as_of: str, *, mode: str = "backtest") -> None: self.conn = conn self.as_of = as_of self.mode = mode def __enter__(self) -> "Scorer": self.conn.executescript( f""" DROP VIEW IF EXISTS visible_claims; CREATE TEMP VIEW visible_claims AS SELECT c.*, COALESCE((SELECT t.merged_into FROM topics t WHERE t.topic_canonical = c.topic_canonical AND t.status='merged'), c.topic_canonical) AS topic_id FROM claims c JOIN documents d ON d.doc_id = c.doc_id WHERE c.date IS NOT NULL AND c.date <= '{self.as_of}'; """ ) return self def __exit__(self, *exc) -> None: self.conn.execute("DROP VIEW IF EXISTS visible_claims") def count_visible(self) -> int: return self.conn.execute("SELECT COUNT(*) FROM visible_claims").fetchone()[0]