62afa521f4
Implement offset and softmax relative-support scales and a public holdout calibration script, but leave the default proportional because no scheme passed the coverage-and-width gate. Co-authored-by: Cursor <cursoragent@cursor.com>
291 lines
11 KiB
Python
291 lines
11 KiB
Python
#!/usr/bin/env python3
|
|
"""Compare relative_support scales on public minute-rectification cases.
|
|
|
|
Reads development cases for inspection and the sealed holdout for the
|
|
enablement gate. Scores each window once, then rebuilds public candidate
|
|
decisions under proportional / offset / softmax temperatures.
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
import argparse
|
|
import json
|
|
import statistics
|
|
import sys
|
|
import traceback
|
|
from calendar import monthrange
|
|
from datetime import date, datetime
|
|
from decimal import Decimal
|
|
from pathlib import Path
|
|
from typing import Any
|
|
from uuid import NAMESPACE_URL, uuid5
|
|
|
|
ROOT = Path(__file__).resolve().parents[1]
|
|
if str(ROOT) not in sys.path:
|
|
sys.path.insert(0, str(ROOT))
|
|
|
|
from scripts.rectification.candidate_contrast import cluster_contexts_by_signature, context_time
|
|
from scripts.rectification.contracts import normalize_rectification_request
|
|
from scripts.rectification.decision_policy import build_candidate_decisions
|
|
from scripts.rectification.scoring_service import (
|
|
build_event_contribution_matrix,
|
|
score_from_matrix,
|
|
scoreable_request,
|
|
)
|
|
|
|
DEVELOPMENT_MANIFEST = ROOT / "references" / "real_case_calibration" / "minute_rectification_development_v1.json"
|
|
HOLDOUT_MANIFEST = ROOT / "references" / "real_case_calibration" / "minute_rectification_holdout_v3.json"
|
|
LEAD = 8
|
|
TODAY = date(2026, 9, 6)
|
|
KIND_BY_DOMAIN = {
|
|
"education": "education_milestone",
|
|
"career": "career_change",
|
|
"relationship": "relationship_commitment",
|
|
"relocation": "relocation",
|
|
"health_pressure": "self_health_event",
|
|
"health": "self_health_event",
|
|
"finance": "finance_change",
|
|
"family": "family_event",
|
|
}
|
|
SCHEMES: tuple[dict[str, Any], ...] = (
|
|
{"id": "proportional", "mode": "proportional", "temperature": None},
|
|
{"id": "offset", "mode": "offset", "temperature": None},
|
|
{"id": "softmax_0.25", "mode": "softmax", "temperature": Decimal("0.25")},
|
|
{"id": "softmax_0.5", "mode": "softmax", "temperature": Decimal("0.5")},
|
|
{"id": "softmax_1", "mode": "softmax", "temperature": Decimal("1")},
|
|
{"id": "softmax_2", "mode": "softmax", "temperature": Decimal("2")},
|
|
)
|
|
|
|
|
|
def _clock(value: str) -> int:
|
|
return int(value[:2]) * 60 + int(value[3:5])
|
|
|
|
|
|
def _hhmm_from_minutes(value: int) -> str:
|
|
wrapped = value % 1440
|
|
return f"{wrapped // 60:02d}:{wrapped % 60:02d}"
|
|
|
|
|
|
def _shift_clock(value: str, delta: int) -> str:
|
|
return _hhmm_from_minutes(_clock(value) + delta)
|
|
|
|
|
|
def _expand_date(raw: str, precision: str) -> tuple[str, str]:
|
|
text = str(raw or "").strip()
|
|
if precision == "day":
|
|
day = date.fromisoformat(text)
|
|
return day.isoformat(), day.isoformat()
|
|
if precision == "month":
|
|
month = date.fromisoformat(f"{text}-01") if len(text) == 7 else date.fromisoformat(text[:10]).replace(day=1)
|
|
last = monthrange(month.year, month.month)[1]
|
|
return month.isoformat(), month.replace(day=last).isoformat()
|
|
year = int(text[:4])
|
|
return f"{year}-01-01", f"{year}-12-31"
|
|
|
|
|
|
def _event_kind(event: dict[str, Any]) -> str:
|
|
domain = str(event.get("domain") or "")
|
|
description = str(event.get("description") or "").lower()
|
|
if domain == "relationship" and not any(token in description for token in ("married", "wedding", "wife", "husband")):
|
|
return "relationship_start"
|
|
return KIND_BY_DOMAIN[domain]
|
|
|
|
|
|
def _request_from_case(case: dict[str, Any]) -> dict[str, Any]:
|
|
birth = case["birth"]
|
|
true_time = str(birth["time"])[:5]
|
|
radius = int(case.get("candidate_radius_minutes") or 10)
|
|
events = []
|
|
for event in case.get("events") or []:
|
|
precision = str(event.get("precision") or "year")
|
|
start, end = _expand_date(str(event.get("date") or ""), precision)
|
|
domain = str(event.get("domain") or "")
|
|
events.append({
|
|
"id": str(uuid5(NAMESPACE_URL, str(event.get("id") or ""))),
|
|
"domain": domain,
|
|
"event_kind": _event_kind(event),
|
|
"date_start": start,
|
|
"date_end": end,
|
|
"precision": precision,
|
|
"summary": str(event.get("description") or event.get("id") or domain)[:200],
|
|
})
|
|
return normalize_rectification_request({
|
|
"birth_date": str(birth["date"]),
|
|
"start_time": _shift_clock(true_time, -radius),
|
|
"end_time": _shift_clock(true_time, radius),
|
|
"lat": float(birth["latitude"]),
|
|
"lon": float(birth["longitude"]),
|
|
"tz": float(birth["timezone_offset"]),
|
|
"events": events,
|
|
}, today=TODAY)
|
|
|
|
|
|
def _cluster_bounds(time: str, clusters: list[dict[str, Any]]) -> tuple[str, str]:
|
|
for cluster in clusters:
|
|
times = [str(item)[:5] for item in cluster.get("times") or [] if str(item)]
|
|
if time not in times:
|
|
continue
|
|
ordered = sorted(times, key=_clock)
|
|
return ordered[0], ordered[-1]
|
|
return time, time
|
|
|
|
|
|
def _union_metrics(
|
|
decisions: list[dict[str, Any]],
|
|
*,
|
|
true_time: str,
|
|
start_time: str,
|
|
clusters: list[dict[str, Any]],
|
|
) -> dict[str, Any]:
|
|
if not decisions:
|
|
return {"width": None, "covers": False, "peak": 0, "valid_count": 0}
|
|
peak = max(int(item["relative_support"]) for item in decisions)
|
|
valid = [item for item in decisions if peak - int(item["relative_support"]) < LEAD]
|
|
origin = _clock(start_time)
|
|
points: list[int] = []
|
|
for item in valid:
|
|
time = str(item["time"])[:5]
|
|
lo, hi = _cluster_bounds(time, clusters)
|
|
for stamp in (time, lo, hi):
|
|
points.append((_clock(stamp) - origin) % 1440)
|
|
width = (max(points) - min(points) + 1) if points else None
|
|
true_offset = (_clock(true_time) - origin) % 1440
|
|
covers = width is not None and min(points) <= true_offset <= max(points)
|
|
return {
|
|
"width": width,
|
|
"covers": covers,
|
|
"peak": peak,
|
|
"valid_count": len(valid),
|
|
"supports": [int(item["relative_support"]) for item in decisions],
|
|
}
|
|
|
|
|
|
def _score_case(case: dict[str, Any]) -> dict[str, Any]:
|
|
request = _request_from_case(case)
|
|
scoring = scoreable_request(request)
|
|
built = build_event_contribution_matrix(scoring)
|
|
rows = score_from_matrix(scoring, built)
|
|
contexts = built.get("static_contexts") if isinstance(built.get("static_contexts"), list) else None
|
|
clusters = cluster_contexts_by_signature(
|
|
[item for item in (contexts or []) if isinstance(item, dict) and context_time(item)]
|
|
) if contexts else []
|
|
true_time = str(case["birth"]["time"])[:5]
|
|
schemes = {}
|
|
for scheme in SCHEMES:
|
|
decisions = build_candidate_decisions(
|
|
rows,
|
|
result_id=str(uuid5(NAMESPACE_URL, case["case_id"])),
|
|
static_contexts=contexts,
|
|
support_mode=scheme["mode"],
|
|
temperature=scheme["temperature"],
|
|
)
|
|
schemes[scheme["id"]] = _union_metrics(
|
|
decisions,
|
|
true_time=true_time,
|
|
start_time=request["start_time"],
|
|
clusters=clusters,
|
|
)
|
|
return {
|
|
"case_id": case["case_id"],
|
|
"radius": int(case.get("candidate_radius_minutes") or 10),
|
|
"schemes": schemes,
|
|
}
|
|
|
|
|
|
def _summarize(rows: list[dict[str, Any]]) -> dict[str, Any]:
|
|
table: dict[str, Any] = {}
|
|
for scheme in SCHEMES:
|
|
widths = [int(item["schemes"][scheme["id"]]["width"]) for item in rows if item["schemes"][scheme["id"]]["width"] is not None]
|
|
covered = sum(1 for item in rows if item["schemes"][scheme["id"]]["covers"])
|
|
table[scheme["id"]] = {
|
|
"n": len(rows),
|
|
"coverage": covered,
|
|
"median_width": statistics.median(widths) if widths else None,
|
|
"mean_width": round(statistics.mean(widths), 2) if widths else None,
|
|
}
|
|
return table
|
|
|
|
|
|
def _pick_scheme(holdout: dict[str, Any]) -> dict[str, Any]:
|
|
baseline = holdout["proportional"]
|
|
old_coverage = int(baseline["coverage"])
|
|
old_median = baseline["median_width"]
|
|
passing = []
|
|
for scheme_id, row in holdout.items():
|
|
if scheme_id == "proportional":
|
|
continue
|
|
median = row["median_width"]
|
|
if median is None or old_median is None:
|
|
continue
|
|
if int(row["coverage"]) >= old_coverage - 1 and median < old_median:
|
|
passing.append((scheme_id, row))
|
|
if not passing:
|
|
return {"id": "proportional", "enabled": False, "reason": "no_scheme_passed_holdout_gate"}
|
|
preferred = [item for item in passing if item[0] == "offset"]
|
|
chosen = preferred[0] if preferred else min(passing, key=lambda item: (item[1]["median_width"], -item[1]["coverage"]))
|
|
return {"id": chosen[0], "enabled": True, "reason": "passed_holdout_gate"}
|
|
|
|
|
|
def _load_cases(path: Path, *, holdout_only: bool) -> list[dict[str, Any]]:
|
|
payload = json.loads(path.read_text(encoding="utf-8"))
|
|
cases = list(payload.get("cases") or [])
|
|
if holdout_only:
|
|
cases = [item for item in cases if item.get("excluded_from_tuning") is True]
|
|
return cases
|
|
|
|
|
|
def run(limit: int | None = None) -> dict[str, Any]:
|
|
development_cases = _load_cases(DEVELOPMENT_MANIFEST, holdout_only=False)
|
|
holdout_cases = _load_cases(HOLDOUT_MANIFEST, holdout_only=True)
|
|
if limit is not None:
|
|
development_cases = development_cases[:limit]
|
|
holdout_cases = holdout_cases[:limit]
|
|
development_rows = [_score_case(item) for item in development_cases]
|
|
holdout_rows = [_score_case(item) for item in holdout_cases]
|
|
development = _summarize(development_rows)
|
|
holdout = _summarize(holdout_rows)
|
|
return {
|
|
"lead": LEAD,
|
|
"development": development,
|
|
"holdout": holdout,
|
|
"selection": _pick_scheme(holdout),
|
|
"development_rows": development_rows,
|
|
"holdout_rows": holdout_rows,
|
|
}
|
|
|
|
|
|
def _print_table(title: str, table: dict[str, Any]) -> None:
|
|
print(title)
|
|
print(f"{'scheme':<16} {'coverage':>10} {'median':>10} {'mean':>10}")
|
|
for scheme_id, row in table.items():
|
|
print(
|
|
f"{scheme_id:<16} "
|
|
f"{row['coverage']}/{row['n']:<7} "
|
|
f"{str(row['median_width']):>10} "
|
|
f"{str(row['mean_width']):>10}"
|
|
)
|
|
|
|
|
|
def main() -> int:
|
|
parser = argparse.ArgumentParser(description=__doc__)
|
|
parser.add_argument("--limit", type=int, default=None)
|
|
parser.add_argument("--json-out", type=Path)
|
|
args = parser.parse_args()
|
|
try:
|
|
report = run(args.limit)
|
|
except Exception:
|
|
traceback.print_exc()
|
|
return 1
|
|
_print_table("development", report["development"])
|
|
print()
|
|
_print_table("holdout", report["holdout"])
|
|
print()
|
|
print("selection", json.dumps(report["selection"], ensure_ascii=False))
|
|
if args.json_out:
|
|
args.json_out.parent.mkdir(parents=True, exist_ok=True)
|
|
args.json_out.write_text(json.dumps(report, ensure_ascii=False, indent=2) + "\n", encoding="utf-8")
|
|
return 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
raise SystemExit(main())
|