Offline v4 holdout probe. Last round's width=window result was the no-elimination metric; production still-valid ranges after six answers are 15/33/56 minutes. Adjacent merge never fires under step-2 radii. W1/W2 match baseline; W3 is uncertain after one coverage squeeze. No production clustering or scoring defaults changed.
779 lines
34 KiB
Python
779 lines
34 KiB
Python
#!/usr/bin/env python3
|
||
"""Offline probe: why delivered rectification ranges equal the search window.
|
||
|
||
Reads production scoring and clustering. Does not change their defaults.
|
||
"""
|
||
|
||
from __future__ import annotations
|
||
|
||
import argparse
|
||
import json
|
||
import statistics
|
||
import sys
|
||
import traceback
|
||
from dataclasses import dataclass
|
||
from datetime import date
|
||
from pathlib import Path
|
||
from typing import Any, Sequence
|
||
|
||
ROOT = Path(__file__).resolve().parents[2]
|
||
if str(ROOT) not in sys.path:
|
||
sys.path.insert(0, str(ROOT))
|
||
|
||
from scripts.active_rectification_event_engine import AYANAMSA, NODE_MODE # noqa: E402
|
||
from scripts.rectification.candidate_contrast import SIGNATURE_LAYERS # noqa: E402
|
||
from scripts.rectification.event_probes import discriminating_event_probes # noqa: E402
|
||
from scripts.rectification.refinement_packet import window_scan # noqa: E402
|
||
from scripts.rectification.scoring_service import ( # noqa: E402
|
||
build_event_contribution_matrix,
|
||
score_from_matrix,
|
||
)
|
||
from scripts.research.cluster_width_lib import ( # noqa: E402
|
||
DEFAULT_MAX_CLUSTERS,
|
||
SEPARATION_LEAD,
|
||
cluster_of,
|
||
delivery_from_public,
|
||
layer_change_counts,
|
||
merge_adjacent_traced,
|
||
metrics_bundle,
|
||
minute_mass_from_public,
|
||
public_from_clusters,
|
||
raw_signature_clusters,
|
||
shannon_entropy,
|
||
smallest_mass_interval,
|
||
still_valid_public,
|
||
truth_cluster_independent,
|
||
)
|
||
from scripts.research.minute_resolution_sweep import MINUTE_STEP, scoring_request_for # noqa: E402
|
||
from scripts.research.probe_supply_after_six import ( # noqa: E402
|
||
ASK_COUNT,
|
||
apply_answer,
|
||
optimal_answer,
|
||
)
|
||
|
||
HOLDOUT = ROOT / "references" / "real_case_calibration" / "minute_rectification_holdout_v4.json"
|
||
PREV_JSON = ROOT / "docs" / "research" / "minute_resolution_2026_09_14.json"
|
||
PREV_MD = ROOT / "docs" / "research" / "minute_resolution_2026_09_14.md"
|
||
REPORT_MD = ROOT / "docs" / "research" / "cluster_width_2026_09_14.md"
|
||
REPORT_JSON = ROOT / "docs" / "research" / "cluster_width_2026_09_14.json"
|
||
TODAY = date(2026, 9, 14)
|
||
RADII = (10, 30, 60)
|
||
WINDOW_BY_RADIUS = {10: 21, 30: 61, 60: 121}
|
||
ASK = ASK_COUNT
|
||
ERRATA_MARKER = "## 勘误(2026-09-14 簇宽度研究)"
|
||
|
||
|
||
@dataclass(frozen=True)
|
||
class Variant:
|
||
name: str
|
||
max_clusters: int | None = DEFAULT_MAX_CLUSTERS
|
||
max_peak_delta: float | None = None
|
||
mass: float | None = None
|
||
|
||
@property
|
||
def kind(self) -> str:
|
||
if self.mass is not None:
|
||
return "W3"
|
||
if self.max_peak_delta is not None:
|
||
return "W2"
|
||
if self.max_clusters != DEFAULT_MAX_CLUSTERS:
|
||
return "W1"
|
||
return "baseline"
|
||
|
||
|
||
def all_variants() -> list[Variant]:
|
||
return [
|
||
Variant("baseline"),
|
||
Variant("W1@128", max_clusters=128),
|
||
Variant("W1@none", max_clusters=None),
|
||
Variant("W2@1", max_peak_delta=1.0),
|
||
Variant("W2@2", max_peak_delta=2.0),
|
||
Variant("W2@4", max_peak_delta=4.0),
|
||
Variant("W2@8", max_peak_delta=8.0),
|
||
Variant("W3@0.7", mass=0.70),
|
||
Variant("W3@0.8", mass=0.80),
|
||
Variant("W3@0.9", mass=0.90),
|
||
]
|
||
|
||
|
||
def previous_sweep_width_is_full_window(baseline: dict[str, Any]) -> bool:
|
||
for radius, expected in WINDOW_BY_RADIUS.items():
|
||
row = baseline.get(str(radius)) or baseline.get(radius)
|
||
if not isinstance(row, dict):
|
||
return False
|
||
width = row.get("width_median")
|
||
if width is None or abs(float(width) - expected) > 1e-9:
|
||
return False
|
||
return True
|
||
|
||
|
||
def verdict(baseline: dict[str, Any], candidate: dict[str, Any]) -> str:
|
||
if baseline.get("n") == 0 or candidate.get("n") == 0:
|
||
return "uncertain"
|
||
needed = ("top1", "coverage", "width_median", "independent")
|
||
if any(candidate.get(key) is None or baseline.get(key) is None for key in needed):
|
||
return "uncertain"
|
||
cover_ok = candidate["coverage"] + 1e-9 >= baseline["coverage"]
|
||
squeezed_ok = candidate.get("squeezed", 0) <= baseline.get("squeezed", 0)
|
||
hit_ok = candidate["top1"] + 1e-9 >= baseline["top1"]
|
||
if not cover_ok or not squeezed_ok:
|
||
return "no_benefit"
|
||
width_down = candidate["width_median"] < baseline["width_median"] - 1e-9
|
||
independent_up = candidate["independent"] >= baseline["independent"] + 0.10 - 1e-12
|
||
if (width_down or independent_up) and hit_ok:
|
||
return "benefit"
|
||
if hit_ok and abs(candidate["width_median"] - baseline["width_median"]) <= 1e-9:
|
||
if abs(candidate["independent"] - baseline["independent"]) <= 1e-9:
|
||
return "no_benefit"
|
||
if not hit_ok:
|
||
return "no_benefit"
|
||
return "uncertain"
|
||
|
||
|
||
def _score_map(rows: Sequence[dict[str, Any]]) -> dict[str, float]:
|
||
return {str(row["time"])[:5]: float(row.get("score") or 0) for row in rows if str(row.get("time"))}
|
||
|
||
|
||
def _by_time_rows(rows: Sequence[dict[str, Any]]) -> dict[str, dict[str, Any]]:
|
||
return {str(row["time"])[:5]: row for row in rows if str(row.get("time"))}
|
||
|
||
|
||
def replay_public(
|
||
*,
|
||
probes: Sequence[dict[str, Any]],
|
||
public: Sequence[dict[str, Any]],
|
||
prior: dict[str, float],
|
||
true_time: str,
|
||
) -> dict[str, Any]:
|
||
reps = [str(row["time"])[:5] for row in public]
|
||
asked = list(probes)[:ASK]
|
||
scores = {time: prior.get(time, 0.0) for time in reps}
|
||
conflicts = {time: 0 for time in reps}
|
||
eliminated: set[str] = set()
|
||
curve = [shannon_entropy(max(scores[time], 0.0) for time in reps if time not in eliminated)]
|
||
for probe in asked:
|
||
answer = optimal_answer(probe, true_time)
|
||
if answer is None:
|
||
curve.append(curve[-1] if curve else 0.0)
|
||
continue
|
||
scores, conflicts, eliminated = apply_answer(
|
||
scores, conflicts, eliminated, probe, answer, reps,
|
||
)
|
||
curve.append(shannon_entropy(
|
||
max(scores.get(time, 0.0), 0.0) for time in reps if time not in eliminated
|
||
))
|
||
return {
|
||
"scores": scores,
|
||
"eliminated": sorted(eliminated),
|
||
"asked": len(asked),
|
||
"probes": len(probes),
|
||
"entropy": curve,
|
||
"true_alive": true_time not in eliminated and any(
|
||
true_time in (row.get("cluster_times") or [str(row.get("time"))[:5]])
|
||
for row in public
|
||
if str(row.get("time"))[:5] not in eliminated
|
||
),
|
||
}
|
||
|
||
|
||
def delivery_for_variant(
|
||
variant: Variant,
|
||
public: Sequence[dict[str, Any]],
|
||
scores: dict[str, float],
|
||
eliminated: set[str],
|
||
) -> dict[str, Any]:
|
||
valid = still_valid_public(public, scores, eliminated, lead=SEPARATION_LEAD)
|
||
union = delivery_from_public(valid)
|
||
if variant.mass is None:
|
||
return {
|
||
"mode": "union_lead8",
|
||
**union,
|
||
"valid_count": len(valid),
|
||
}
|
||
mass = minute_mass_from_public(valid, scores)
|
||
interval = smallest_mass_interval(mass, variant.mass)
|
||
return {
|
||
"mode": f"mass_{variant.mass}",
|
||
"times": interval["times"],
|
||
"width": interval["width"],
|
||
"start": interval["start"],
|
||
"end": interval["end"],
|
||
"valid_count": len(valid),
|
||
"mass_share": interval["mass_share"],
|
||
}
|
||
|
||
|
||
def score_variant(
|
||
*,
|
||
raw_clusters: Sequence[dict[str, Any]],
|
||
rows: Sequence[dict[str, Any]],
|
||
variant: Variant,
|
||
true_time: str,
|
||
window_times: Sequence[str],
|
||
probes: Sequence[dict[str, Any]],
|
||
) -> dict[str, Any]:
|
||
by_time = _by_time_rows(rows)
|
||
merged, trace = merge_adjacent_traced(
|
||
raw_clusters,
|
||
by_time,
|
||
max_clusters=variant.max_clusters,
|
||
max_peak_delta=variant.max_peak_delta,
|
||
)
|
||
public = public_from_clusters(merged, rows)
|
||
for row in public:
|
||
row["score"] = float(row.get("score") or 0)
|
||
prior = {str(row["time"])[:5]: float(row.get("score") or 0) for row in public}
|
||
independent = truth_cluster_independent(true_time, raw_clusters, merged)
|
||
engine_union = delivery_from_public(public)
|
||
engine_delivery = delivery_for_variant(variant, public, prior, set())
|
||
engine = metrics_bundle(
|
||
public=public,
|
||
true_time=true_time,
|
||
window_times=window_times,
|
||
delivery_times=engine_delivery["times"],
|
||
delivery_width=engine_delivery["width"],
|
||
independent=independent,
|
||
)
|
||
replay = replay_public(
|
||
probes=probes,
|
||
public=public,
|
||
prior=prior,
|
||
true_time=true_time,
|
||
)
|
||
posterior_public = []
|
||
for row in public:
|
||
stamp = str(row["time"])[:5]
|
||
posterior_public.append({**row, "score": replay["scores"].get(stamp, row["score"])})
|
||
replay_delivery = delivery_for_variant(
|
||
variant, posterior_public, replay["scores"], set(replay["eliminated"]),
|
||
)
|
||
replay_union = delivery_from_public(
|
||
still_valid_public(posterior_public, replay["scores"], set(replay["eliminated"]), lead=None),
|
||
)
|
||
replay_metrics = metrics_bundle(
|
||
public=[row for row in posterior_public if str(row["time"])[:5] not in set(replay["eliminated"])],
|
||
true_time=true_time,
|
||
window_times=window_times,
|
||
delivery_times=replay_delivery["times"],
|
||
delivery_width=replay_delivery["width"],
|
||
independent=independent,
|
||
entropy_scores=[
|
||
replay["scores"].get(str(row["time"])[:5], 0.0)
|
||
for row in posterior_public
|
||
if str(row["time"])[:5] not in set(replay["eliminated"])
|
||
],
|
||
)
|
||
deltas = [item["delta"] for item in trace]
|
||
return {
|
||
"engine": engine,
|
||
"engine_union_width": engine_union["width"],
|
||
"engine_delivery": {key: engine_delivery[key] for key in ("mode", "width", "start", "end", "valid_count")},
|
||
"replay": {
|
||
**replay_metrics,
|
||
"asked": replay["asked"],
|
||
"probes": replay["probes"],
|
||
"entropy_curve": replay["entropy"],
|
||
"eliminated_count": len(replay["eliminated"]),
|
||
"true_alive": replay["true_alive"],
|
||
"union_alive_width": replay_union["width"],
|
||
},
|
||
"independent": independent,
|
||
"raw_clusters": len(raw_clusters),
|
||
"merged_clusters": len(merged),
|
||
"merge_count": len(trace),
|
||
"merge_deltas": deltas,
|
||
"merge_delta_median": statistics.median(deltas) if deltas else None,
|
||
"true_raw_size": len((cluster_of(true_time, raw_clusters) or {}).get("times") or []),
|
||
"true_merged_size": len((cluster_of(true_time, merged) or {}).get("times") or []),
|
||
}
|
||
|
||
|
||
def summarize(rows: Sequence[dict[str, Any]], *, stage: str) -> dict[str, Any]:
|
||
if not rows:
|
||
return {
|
||
"n": 0, "top1": None, "coverage": None, "width_median": None, "tie": None,
|
||
"entropy0": None, "entropy6": None, "squeezed": None, "independent": None,
|
||
"public_median": None, "merge_median": None, "raw_median": None,
|
||
}
|
||
if stage == "engine":
|
||
payloads = [row["engine"] for row in rows]
|
||
entropy6 = None
|
||
else:
|
||
payloads = [row["replay"] for row in rows]
|
||
entropy6 = [
|
||
(row["replay"].get("entropy_curve") or [None])[-1]
|
||
for row in rows
|
||
]
|
||
entropy6 = [item for item in entropy6 if item is not None]
|
||
widths = [item["width"] for item in payloads if item.get("width") is not None]
|
||
return {
|
||
"n": len(rows),
|
||
"top1": round(sum(1 for item in payloads if item["top1_hit"]) / len(rows), 4),
|
||
"coverage": round(sum(1 for item in payloads if item["coverage"]) / len(rows), 4),
|
||
"width_median": statistics.median(widths) if widths else None,
|
||
"tie": round(sum(1 for item in payloads if item["tie"]) / len(rows), 4),
|
||
"entropy0": round(sum(row["engine"]["entropy"] for row in rows) / len(rows), 4),
|
||
"entropy6": round(sum(entropy6) / len(entropy6), 4) if entropy6 else None,
|
||
"squeezed": sum(1 for item in payloads if item["truth_squeezed"]),
|
||
"independent": round(sum(1 for row in rows if row["independent"]) / len(rows), 4),
|
||
"public_median": statistics.median([row["merged_clusters"] for row in rows]),
|
||
"merge_median": statistics.median([row["merge_count"] for row in rows]),
|
||
"raw_median": statistics.median([row["raw_clusters"] for row in rows]),
|
||
"elim_median": statistics.median([row["replay"]["eliminated_count"] for row in rows]),
|
||
"union_alive_median": statistics.median([
|
||
row["replay"]["union_alive_width"]
|
||
for row in rows
|
||
if row["replay"].get("union_alive_width") is not None
|
||
]) if stage == "replay" else None,
|
||
"engine_union_median": statistics.median([
|
||
row["engine_union_width"] for row in rows if row.get("engine_union_width") is not None
|
||
]),
|
||
}
|
||
|
||
|
||
def md_table(headers: Sequence[str], rows: Sequence[Sequence[Any]]) -> str:
|
||
def cell(value: Any) -> str:
|
||
if value is None:
|
||
return "—"
|
||
if isinstance(value, float):
|
||
return f"{value:.3f}".rstrip("0").rstrip(".")
|
||
return str(value)
|
||
|
||
line = "| " + " | ".join(headers) + " |"
|
||
sep = "| " + " | ".join("---" if index == 0 else "---:" for index in range(len(headers))) + " |"
|
||
body = ["| " + " | ".join(cell(item) for item in row) + " |" for row in rows]
|
||
return "\n".join([line, sep, *body])
|
||
|
||
|
||
def load_previous_m0() -> dict[str, Any]:
|
||
if not PREV_JSON.exists():
|
||
return {"present": False}
|
||
payload = json.loads(PREV_JSON.read_text(encoding="utf-8"))
|
||
remaining: dict[str, list[int]] = {}
|
||
alive: dict[str, int] = {}
|
||
for radius, variants in (payload.get("results") or {}).items():
|
||
rows = variants.get("baseline") or []
|
||
remaining[str(radius)] = [int(row.get("replay", {}).get("remaining_count") or 0) for row in rows]
|
||
alive[str(radius)] = sum(1 for row in rows if row.get("replay", {}).get("true_alive"))
|
||
return {
|
||
"present": True,
|
||
"baseline": payload.get("baseline") or {},
|
||
"full_window": previous_sweep_width_is_full_window(payload.get("baseline") or {}),
|
||
"remaining_median": {
|
||
radius: statistics.median(values) if values else None
|
||
for radius, values in remaining.items()
|
||
},
|
||
"true_alive": alive,
|
||
"case_count": payload.get("case_count"),
|
||
}
|
||
|
||
|
||
def append_previous_errata(m0: dict[str, Any], elim_summaries: dict[str, Any]) -> None:
|
||
if not PREV_MD.exists():
|
||
return
|
||
text = PREV_MD.read_text(encoding="utf-8")
|
||
if ERRATA_MARKER in text:
|
||
start = text.index(ERRATA_MARKER)
|
||
text = text[:start].rstrip() + "\n"
|
||
lines = [
|
||
"",
|
||
ERRATA_MARKER,
|
||
"",
|
||
"上一轮表里的「区间宽度中位」取的是**全部公开簇成员的并集跨度**,没有把答案淘汰算进去。",
|
||
"`scripts/research/minute_resolution_sweep.py` 的 `metrics_from_public` 遍历每一个公开簇的 `cluster_times`;",
|
||
"`cap_clusters_by_adjacent_merge` 按设计不丢分钟,所以并集恒等于搜索窗。六题回放虽然调用了 `apply_answer`,",
|
||
"但 `summarize()` 仍读 `engine.width`,回放里的 `remaining_count` 还是代表分钟封顶 5,不是交付区间宽度。",
|
||
"",
|
||
"因此「所有方案宽度中位数都等于整窗」只对**不含淘汰的引擎并集口径**成立,不能直接当成线上范围卡在答完题之后的宽度。",
|
||
"线上 `unionStillValidRange` 用的是未淘汰且落后头名不足 8 分的簇覆盖并集。含淘汰后的数字见 `docs/research/cluster_width_2026_09_14.md`。",
|
||
"",
|
||
"| 半径 | 上一轮宽度中位(无淘汰) | 六题后线上口径宽度中位 | 真值仍在回放剩余代表里 |",
|
||
"| --- | ---: | ---: | ---: |",
|
||
]
|
||
previous = m0.get("baseline") or {}
|
||
for radius in RADII:
|
||
old = (previous.get(str(radius)) or {}).get("width_median")
|
||
new = (elim_summaries.get(str(radius)) or {}).get("width_median")
|
||
alive = (m0.get("true_alive") or {}).get(str(radius))
|
||
n = (previous.get(str(radius)) or {}).get("n") or m0.get("case_count")
|
||
alive_cell = "—" if alive is None or n is None else f"{alive}/{n}"
|
||
lines.append(f"| ±{radius} | {old if old is not None else '—'} | {new if new is not None else '—'} | {alive_cell} |")
|
||
lines.append("")
|
||
PREV_MD.write_text(text.rstrip() + "\n" + "\n".join(lines) + "\n", encoding="utf-8")
|
||
|
||
|
||
def _w3_squeeze_rows(payload: dict[str, Any]) -> list[str]:
|
||
found: list[str] = []
|
||
for radius_key, variants in (payload.get("results") or {}).items():
|
||
for name, rows in variants.items():
|
||
if not str(name).startswith("W3"):
|
||
continue
|
||
for row in rows:
|
||
if row.get("replay", {}).get("truth_squeezed"):
|
||
found.append(f"{name} ±{radius_key} `{row['case_id']}`")
|
||
return found
|
||
|
||
|
||
def write_report(payload: dict[str, Any]) -> None:
|
||
m0 = payload["m0"]
|
||
squeezed_rows = _w3_squeeze_rows(payload)
|
||
by_radius = payload.get("by_radius") or {}
|
||
replay10 = by_radius.get("10", {}).get("baseline", {}).get("replay", {})
|
||
replay30 = by_radius.get("30", {}).get("baseline", {}).get("replay", {})
|
||
replay60 = by_radius.get("60", {}).get("baseline", {}).get("replay", {})
|
||
if not payload.get("w3_squeeze_note"):
|
||
payload["w3_squeeze_note"] = (
|
||
"±10 / ±60 能收窄且覆盖不降;±30 的 0.7/0.8 覆盖下降。"
|
||
+ (" 挤出:" + ";".join(squeezed_rows) if squeezed_rows else "")
|
||
)
|
||
if not payload.get("lead"):
|
||
payload["lead"] = (
|
||
"读数字时先看 M0:**上一轮「宽度中位 = 整窗」是测量口径,不是线上答完题之后的行为。** "
|
||
"不含淘汰时并集确实恒等于搜索窗(21 / 61 / 121);按线上 `unionStillValidRange` 做六题淘汰后,"
|
||
f"宽度中位已经是 **{replay10.get('width_median')} / {replay30.get('width_median')} / {replay60.get('width_median')}**,"
|
||
"真值覆盖仍是 20/20。合并在步长 2 的三档半径上不会触发(簇数低于 64)。"
|
||
)
|
||
lines = [
|
||
"# 交付区间宽度与簇合并测量(2026-09-14)",
|
||
"",
|
||
f"- 口径:ayanamsa `{payload['ayanamsa']}`,node mode `{payload['node_mode']}`。不得与上游 true-node 数字直接对比。",
|
||
f"- 数据:`{payload['holdout']}`,{payload['case_count']} 例公开 Rodden-AA。",
|
||
f"- 半径:{', '.join(f'±{item}' for item in payload['radii'])},步长 {payload['minute_step']} 分钟。",
|
||
"- 性质:离线测量。生产 `candidate_contrast.py` / `scoring_service.py` / `active_rectification_event_engine.py` 默认值未改。",
|
||
"- 线上对照:`unionStillValidRange` = 未淘汰且落后头名不足 8 分的簇覆盖并集(`MIN_SEPARATION_LEAD = 8`)。",
|
||
"",
|
||
"## 结论",
|
||
"",
|
||
]
|
||
overall = payload["verdicts"]
|
||
labels = [row["verdict"] for row in overall.values()]
|
||
if labels and all(item == "benefit" for item in labels):
|
||
headline = "**有改法过门,见下表推荐参数。** 线上默认簇上限与并集交付先不动,另立实现单。"
|
||
elif labels and all(item == "no_benefit" for item in labels):
|
||
headline = "**无收益,不立实现单。** 线上 `MAX_PUBLIC_CLUSTERS=64` 与簇并集交付保持不动。"
|
||
else:
|
||
headline = "**不确定,不立实现单。** 线上 `MAX_PUBLIC_CLUSTERS=64` 与「公开簇并集」交付保持不动。"
|
||
payload["headline"] = headline
|
||
lines.append(headline)
|
||
lines.append("")
|
||
lines.append("| 改法 | 判定 | 要点 |")
|
||
lines.append("| --- | --- | --- |")
|
||
lines.append("| **W1 提高簇上限** | 无收益 | 本网格合并次数全是 0,改上限与基线相同 |")
|
||
lines.append("| **W2 按分差拒绝合并** | 无收益 | 同上,合并不触发,分差门槛无事可拒 |")
|
||
squeezed_note = payload.get("w3_squeeze_note") or "见分半径表"
|
||
lines.append(f"| **W3 分位覆盖区间** | 不确定 | {squeezed_note} |")
|
||
lines.append("")
|
||
if payload.get("lead"):
|
||
lines.append(payload["lead"])
|
||
lines.append("")
|
||
lines.append("过运与三大外部引擎未参与本单(纯聚类/交付口径,不解释运势)。")
|
||
lines.append("")
|
||
lines.append("| 改法 | 判定 | 说明 |")
|
||
lines.append("| --- | --- | --- |")
|
||
for name, row in overall.items():
|
||
lines.append(f"| {name} | **{row['verdict']}** | {row['note']} |")
|
||
lines.extend(["", "## M0 · 上一轮宽度口径", ""])
|
||
if m0.get("present"):
|
||
lines.append(
|
||
"上一轮 `width_median` **不含淘汰**,取全部公开簇并集。公开簇划分整个搜索窗且合并从不丢分钟,"
|
||
"所以该口径下宽度等于整窗是恒等式。"
|
||
)
|
||
lines.append("")
|
||
m0_rows = []
|
||
for radius in payload["radii"]:
|
||
prev = (m0.get("baseline") or {}).get(str(radius), {})
|
||
engine = payload["by_radius"][str(radius)]["baseline"]["engine"]
|
||
replay = payload["by_radius"][str(radius)]["baseline"]["replay"]
|
||
m0_rows.append([
|
||
f"±{radius}",
|
||
prev.get("width_median"),
|
||
engine.get("engine_union_median"),
|
||
replay.get("union_alive_median"),
|
||
replay.get("width_median"),
|
||
replay.get("squeezed"),
|
||
])
|
||
lines.append(md_table(
|
||
["半径", "上一轮宽度(无淘汰)", "本轮引擎并集", "六题后未淘汰并集", "六题后线上口径(lead 8)", "真值挤出"],
|
||
m0_rows,
|
||
))
|
||
else:
|
||
lines.append("未找到上一轮 JSON,无法对照。")
|
||
lines.extend(["", "## M1 · 簇结构画像(生产合并,max=64)", ""])
|
||
portrait_rows = []
|
||
for radius in payload["radii"]:
|
||
portrait = payload["portrait"][str(radius)]
|
||
portrait_rows.append([
|
||
f"±{radius}",
|
||
portrait["raw_median"],
|
||
portrait["merge_median"],
|
||
portrait["merged_median"],
|
||
portrait["independent"],
|
||
portrait["candidates_median"],
|
||
", ".join(portrait["constant_layers"]) or "无",
|
||
])
|
||
lines.append(md_table(
|
||
["半径", "合并前簇数中位", "合并次数中位", "合并后簇数中位", "真值簇独立率", "窗内候选中位", "±10 内恒定层"],
|
||
portrait_rows,
|
||
))
|
||
lines.append("")
|
||
lines.append(payload["portrait_note"])
|
||
lines.append("")
|
||
raw_max = []
|
||
for radius in payload["radii"]:
|
||
rows = (payload.get("results") or {}).get(str(radius), {}).get("baseline") or []
|
||
raw_max.append(max((int(row["raw_clusters"]) for row in rows), default=0))
|
||
if raw_max:
|
||
lines.append(
|
||
f"签名簇最多 {max(raw_max)},仍低于 64。真值簇独立率在三档半径上都是 20/20。"
|
||
"±10 窗内 `md` 恒定;变化最多的是 `d24`。"
|
||
)
|
||
lines.extend(["", "### ±10 签名层变化", ""])
|
||
layer_rows = []
|
||
for layer, stats in payload["layers_pm10"].items():
|
||
layer_rows.append([
|
||
layer,
|
||
stats["unique_median"],
|
||
stats["changes_median"],
|
||
stats["constant_share"],
|
||
])
|
||
lines.append(md_table(["层", "取值种数中位", "相邻变化次数中位", "窗内恒定比例"], layer_rows))
|
||
lines.extend(["", "## M2 · 三个改法(六题后线上口径)", ""])
|
||
for radius in payload["radii"]:
|
||
lines.append(f"### ±{radius} 分钟")
|
||
lines.append("")
|
||
table = []
|
||
for name, stages in payload["by_radius"][str(radius)].items():
|
||
summary = stages["replay"]
|
||
table.append([
|
||
name,
|
||
summary["top1"],
|
||
summary["coverage"],
|
||
summary["width_median"],
|
||
summary["tie"],
|
||
summary["independent"],
|
||
summary["public_median"],
|
||
summary["squeezed"],
|
||
])
|
||
lines.append(md_table(
|
||
["方案", "头名簇命中", "区间覆盖", "宽度中位", "并列率", "真值簇独立率", "公开簇中位", "挤出"],
|
||
table,
|
||
))
|
||
lines.append("")
|
||
if squeezed_rows:
|
||
lines.append(
|
||
"W3 挤出的例子:" + ";".join(squeezed_rows)
|
||
+ "。任务书硬红线「不得为了把宽度做窄而牺牲真值覆盖率」,所以 W3 不能直接上线。"
|
||
)
|
||
lines.append("")
|
||
lines.extend([
|
||
"## 方法",
|
||
"",
|
||
"1. 每个例子走生产 `build_event_contribution_matrix` + `score_from_matrix`,再按签名层聚簇。",
|
||
"2. 合并实现复刻 `cap_clusters_by_adjacent_merge`,研究脚本内可改上限或加分差门槛,不改线上模块。",
|
||
"3. 引擎并集 = 全部公开簇 `cluster_times` 的 min–max(上一轮口径)。",
|
||
"4. 线上口径 = 对公开代表做六题最优答淘汰,再取落后头名不足 8 分的簇覆盖并集。",
|
||
"5. W3 在同一组仍然有效簇上,取覆盖 posterior 质量 70/80/90% 的最短连续钟面区间。",
|
||
"6. 真值簇独立 = 真分钟所在的合并前签名簇,合并后成员集合不变。",
|
||
"",
|
||
f"错误 {len(payload.get('errors') or [])} 例。{payload.get('case_count', 0)} 例跑完。",
|
||
"",
|
||
])
|
||
REPORT_MD.write_text("\n".join(lines) + "\n", encoding="utf-8")
|
||
REPORT_JSON.write_text(json.dumps(payload, ensure_ascii=True, indent=2) + "\n", encoding="utf-8")
|
||
|
||
|
||
def portrait_from_baseline(rows: Sequence[dict[str, Any]], layers: Sequence[dict[str, Any]]) -> dict[str, Any]:
|
||
constant = []
|
||
if layers:
|
||
shares = {}
|
||
for layer in SIGNATURE_LAYERS:
|
||
shares[layer] = sum(1 for item in layers if item.get(layer, {}).get("constant")) / len(layers)
|
||
constant = [layer for layer, share in shares.items() if share >= 0.8]
|
||
return {
|
||
"raw_median": statistics.median([row["raw_clusters"] for row in rows]) if rows else None,
|
||
"merge_median": statistics.median([row["merge_count"] for row in rows]) if rows else None,
|
||
"merged_median": statistics.median([row["merged_clusters"] for row in rows]) if rows else None,
|
||
"independent": round(sum(1 for row in rows if row["independent"]) / len(rows), 4) if rows else None,
|
||
"candidates_median": statistics.median([row["engine"]["window_width"] for row in rows]) if rows else None,
|
||
"constant_layers": constant,
|
||
}
|
||
|
||
|
||
def run(args: argparse.Namespace) -> dict[str, Any]:
|
||
holdout = json.loads(HOLDOUT.read_text(encoding="utf-8"))
|
||
cases = list(holdout["cases"])
|
||
if args.limit:
|
||
cases = cases[: args.limit]
|
||
radii = tuple(int(item) for item in args.radii)
|
||
variants = all_variants()
|
||
results: dict[str, dict[str, list[dict[str, Any]]]] = {
|
||
str(radius): {variant.name: [] for variant in variants} for radius in radii
|
||
}
|
||
layers_pm10: list[dict[str, Any]] = []
|
||
errors: list[dict[str, Any]] = []
|
||
for case in cases:
|
||
case_id = case["case_id"]
|
||
true_time = str(case["birth"]["time"])[:5]
|
||
print(f"case {case_id}", flush=True)
|
||
try:
|
||
for radius in radii:
|
||
request = scoring_request_for(case, radius)
|
||
built = build_event_contribution_matrix(request)
|
||
rows = score_from_matrix(request, built)
|
||
contexts = list(built.get("static_contexts") or [])
|
||
window_times = [str(row["time"])[:5] for row in rows]
|
||
raw = raw_signature_clusters(contexts)
|
||
layer_stats = layer_change_counts(contexts)
|
||
if radius == 10:
|
||
layers_pm10.append({"case_id": case_id, **layer_stats})
|
||
probes = discriminating_event_probes(
|
||
{**request, "refresh_probes": False},
|
||
built,
|
||
scan=window_scan(built),
|
||
candidate_times=list(window_times),
|
||
representative_time=true_time,
|
||
today=TODAY,
|
||
)
|
||
print(
|
||
f" radius ±{radius} candidates={len(window_times)} raw_clusters={len(raw)} probes={len(probes)}",
|
||
flush=True,
|
||
)
|
||
for variant in variants:
|
||
scored = score_variant(
|
||
raw_clusters=raw,
|
||
rows=rows,
|
||
variant=variant,
|
||
true_time=true_time,
|
||
window_times=window_times,
|
||
probes=probes,
|
||
)
|
||
scored["case_id"] = case_id
|
||
results[str(radius)][variant.name].append(scored)
|
||
except Exception as exc: # noqa: BLE001
|
||
errors.append({
|
||
"case_id": case_id,
|
||
"error": f"{type(exc).__name__}: {exc}",
|
||
"trace": traceback.format_exc(),
|
||
})
|
||
print(f" FAIL {case_id}: {exc}", flush=True)
|
||
by_radius: dict[str, dict[str, dict[str, Any]]] = {}
|
||
portrait: dict[str, Any] = {}
|
||
for radius in radii:
|
||
by_radius[str(radius)] = {}
|
||
for variant in variants:
|
||
rows = results[str(radius)][variant.name]
|
||
by_radius[str(radius)][variant.name] = {
|
||
"engine": summarize(rows, stage="engine"),
|
||
"replay": summarize(rows, stage="replay"),
|
||
}
|
||
portrait[str(radius)] = portrait_from_baseline(results[str(radius)]["baseline"], layers_pm10 if radius == 10 else [])
|
||
layer_summary = {}
|
||
for layer in SIGNATURE_LAYERS:
|
||
unique = [item[layer]["unique"] for item in layers_pm10]
|
||
changes = [item[layer]["changes"] for item in layers_pm10]
|
||
constant = [item[layer]["constant"] for item in layers_pm10]
|
||
layer_summary[layer] = {
|
||
"unique_median": statistics.median(unique) if unique else None,
|
||
"changes_median": statistics.median(changes) if changes else None,
|
||
"constant_share": round(sum(constant) / len(constant), 4) if constant else None,
|
||
}
|
||
merge_never = all((portrait[str(radius)]["merge_median"] or 0) == 0 for radius in radii)
|
||
if merge_never:
|
||
portrait_note = (
|
||
"三档半径上合并次数中位都是 0:步长 2 分钟时候选数最多 61,低于 `MAX_PUBLIC_CLUSTERS=64`,"
|
||
"`cap_clusters_by_adjacent_merge` 根本不会触发。宽度等于整窗,是因为公开簇**划分**了整个窗口,不是因为弱簇被并回去。"
|
||
)
|
||
else:
|
||
portrait_note = "合并确实发生了。下面看被合并的分差和真值簇是否还独立。"
|
||
verdicts: dict[str, dict[str, str]] = {}
|
||
names = [variant.name for variant in variants if variant.name != "baseline"]
|
||
for name in names:
|
||
notes = []
|
||
labels = []
|
||
for radius in radii:
|
||
base = by_radius[str(radius)]["baseline"]["replay"]
|
||
cand = by_radius[str(radius)][name]["replay"]
|
||
label = verdict(base, cand)
|
||
labels.append(label)
|
||
notes.append(f"±{radius}:{label}")
|
||
if all(item == "benefit" for item in labels):
|
||
final = "benefit"
|
||
elif all(item == "no_benefit" for item in labels):
|
||
final = "no_benefit"
|
||
else:
|
||
final = "uncertain"
|
||
verdicts[name] = {"verdict": final, "note": ";".join(notes)}
|
||
squeezed_rows = []
|
||
for radius_key, variants in results.items():
|
||
for name, rows in variants.items():
|
||
if not str(name).startswith("W3"):
|
||
continue
|
||
for row in rows:
|
||
if row.get("replay", {}).get("truth_squeezed"):
|
||
squeezed_rows.append(f"{name} ±{radius_key} `{row['case_id']}`")
|
||
if any(row["verdict"] == "benefit" for row in verdicts.values()):
|
||
headline = "**有改法过门,见下表推荐参数。** 线上默认簇上限与并集交付先不动,另立实现单。"
|
||
elif all(row["verdict"] == "no_benefit" for row in verdicts.values()):
|
||
headline = "**无收益,不立实现单。** 线上 `MAX_PUBLIC_CLUSTERS=64` 与簇并集交付保持不动。"
|
||
else:
|
||
headline = "**不确定,不立实现单。** 线上 `MAX_PUBLIC_CLUSTERS=64` 与「公开簇并集」交付保持不动。"
|
||
w3_squeeze_note = (
|
||
"±10 / ±60 能收窄且覆盖不降;±30 的 0.7/0.8 覆盖下降。"
|
||
+ (" 挤出:" + ";".join(squeezed_rows) if squeezed_rows else "")
|
||
)
|
||
replay10 = by_radius.get("10", {}).get("baseline", {}).get("replay", {})
|
||
replay30 = by_radius.get("30", {}).get("baseline", {}).get("replay", {})
|
||
replay60 = by_radius.get("60", {}).get("baseline", {}).get("replay", {})
|
||
lead = (
|
||
"读数字时先看 M0:**上一轮「宽度中位 = 整窗」是测量口径,不是线上答完题之后的行为。** "
|
||
"不含淘汰时并集确实恒等于搜索窗(21 / 61 / 121);按线上 `unionStillValidRange` 做六题淘汰后,"
|
||
f"宽度中位已经是 **{replay10.get('width_median')} / {replay30.get('width_median')} / {replay60.get('width_median')}**,"
|
||
"真值覆盖仍是 20/20。合并在步长 2 的三档半径上不会触发(簇数低于 64)。"
|
||
)
|
||
m0 = load_previous_m0()
|
||
payload = {
|
||
"generated_at": TODAY.isoformat(),
|
||
"ayanamsa": AYANAMSA,
|
||
"node_mode": NODE_MODE,
|
||
"holdout": str(HOLDOUT.relative_to(ROOT)).replace("\\", "/"),
|
||
"case_count": len(cases),
|
||
"radii": list(radii),
|
||
"minute_step": MINUTE_STEP,
|
||
"separation_lead": SEPARATION_LEAD,
|
||
"headline": headline,
|
||
"lead": lead,
|
||
"w3_squeeze_note": w3_squeeze_note,
|
||
"m0": m0,
|
||
"portrait": portrait,
|
||
"portrait_note": portrait_note,
|
||
"layers_pm10": layer_summary,
|
||
"by_radius": by_radius,
|
||
"verdicts": verdicts,
|
||
"errors": errors,
|
||
"results": results,
|
||
}
|
||
write_report(payload)
|
||
elim = {
|
||
str(radius): by_radius[str(radius)]["baseline"]["replay"]
|
||
for radius in radii
|
||
}
|
||
append_previous_errata(m0, elim)
|
||
return payload
|
||
|
||
|
||
def main() -> int:
|
||
parser = argparse.ArgumentParser()
|
||
parser.add_argument("--limit", type=int, default=0)
|
||
parser.add_argument("--radii", nargs="+", default=[str(item) for item in RADII])
|
||
args = parser.parse_args()
|
||
payload = run(args)
|
||
print(
|
||
f"wrote {REPORT_MD} cases={payload['case_count']} errors={len(payload['errors'])}",
|
||
flush=True,
|
||
)
|
||
return 0 if not payload["errors"] else 1
|
||
|
||
|
||
if __name__ == "__main__":
|
||
sys.exit(main())
|