docs(research): measure why delivered range width equals the search window
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.
This commit is contained in:
@@ -0,0 +1,373 @@
|
||||
"""Pure helpers for the cluster-width research probe. No production defaults."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import math
|
||||
from collections.abc import Iterable, Sequence
|
||||
from typing import Any
|
||||
|
||||
from scripts.rectification.candidate_contrast import (
|
||||
SIGNATURE_LAYERS,
|
||||
cluster_contexts_by_signature,
|
||||
context_time,
|
||||
layer_value,
|
||||
)
|
||||
|
||||
SEPARATION_LEAD = 8
|
||||
DEFAULT_MAX_CLUSTERS = 64
|
||||
NARROW_WIDTH = 5
|
||||
|
||||
|
||||
def clock(value: str) -> int:
|
||||
stamp = str(value)[:5]
|
||||
return int(stamp[:2]) * 60 + int(stamp[3:5])
|
||||
|
||||
|
||||
def hhmm(value: object) -> str | None:
|
||||
text = str(value or "")[:5]
|
||||
return text if len(text) == 5 and text[2] == ":" else None
|
||||
|
||||
|
||||
def range_width(times: Sequence[str]) -> int | None:
|
||||
clocks = sorted(clock(item) for item in times if hhmm(item))
|
||||
if not clocks:
|
||||
return None
|
||||
return clocks[-1] - clocks[0] + 1
|
||||
|
||||
|
||||
def range_edges(times: Sequence[str]) -> tuple[str, str] | None:
|
||||
stamps = [str(item)[:5] for item in times if hhmm(item)]
|
||||
if not stamps:
|
||||
return None
|
||||
ordered = sorted(stamps, key=clock)
|
||||
return ordered[0], ordered[-1]
|
||||
|
||||
|
||||
def shannon_entropy(weights: Iterable[float]) -> float:
|
||||
values = [max(float(item), 0.0) for item in weights]
|
||||
total = sum(values)
|
||||
if total <= 0:
|
||||
return 0.0
|
||||
entropy = 0.0
|
||||
for value in values:
|
||||
if value <= 0:
|
||||
continue
|
||||
share = value / total
|
||||
entropy -= share * math.log(share, 2)
|
||||
return round(entropy, 6)
|
||||
|
||||
|
||||
def peak_score(cluster: dict[str, Any], by_time: dict[str, dict[str, Any]]) -> float:
|
||||
scores = [
|
||||
float(by_time[time].get("score") or 0)
|
||||
for time in cluster.get("times") or []
|
||||
if time in by_time
|
||||
]
|
||||
return max(scores) if scores else 0.0
|
||||
|
||||
|
||||
def layer_change_counts(contexts: Sequence[dict[str, Any]]) -> dict[str, dict[str, int]]:
|
||||
rows: dict[str, dict[str, int]] = {}
|
||||
for layer in SIGNATURE_LAYERS:
|
||||
values = [layer_value(context, layer) for context in contexts]
|
||||
present = [item for item in values if item is not None]
|
||||
unique = set(present)
|
||||
changes = sum(1 for left, right in zip(present, present[1:]) if left != right)
|
||||
rows[layer] = {
|
||||
"unique": len(unique),
|
||||
"changes": changes,
|
||||
"constant": int(len(unique) <= 1),
|
||||
}
|
||||
return rows
|
||||
|
||||
|
||||
def cluster_of(true_time: str, clusters: Sequence[dict[str, Any]]) -> dict[str, Any] | None:
|
||||
stamp = str(true_time)[:5]
|
||||
for cluster in clusters:
|
||||
times = [str(item)[:5] for item in cluster.get("times") or []]
|
||||
if stamp in times:
|
||||
return cluster
|
||||
return None
|
||||
|
||||
|
||||
def truth_cluster_independent(
|
||||
true_time: str,
|
||||
raw_clusters: Sequence[dict[str, Any]],
|
||||
merged_clusters: Sequence[dict[str, Any]],
|
||||
) -> bool:
|
||||
raw = cluster_of(true_time, raw_clusters)
|
||||
merged = cluster_of(true_time, merged_clusters)
|
||||
if raw is None or merged is None:
|
||||
return False
|
||||
return set(raw.get("times") or []) == set(merged.get("times") or [])
|
||||
|
||||
|
||||
def merge_adjacent_traced(
|
||||
clusters: Sequence[dict[str, Any]],
|
||||
by_time: dict[str, dict[str, Any]],
|
||||
max_clusters: int | None = DEFAULT_MAX_CLUSTERS,
|
||||
max_peak_delta: float | None = None,
|
||||
) -> tuple[list[dict[str, Any]], list[dict[str, Any]]]:
|
||||
"""Keep every minute. Merge adjacent weak clusters when over the cap.
|
||||
|
||||
If ``max_peak_delta`` is set, pairs whose peak-score gap exceeds that
|
||||
threshold are not merged, even if that leaves more than ``max_clusters``.
|
||||
``max_clusters is None`` means never merge.
|
||||
"""
|
||||
work = [
|
||||
{
|
||||
**cluster,
|
||||
"times": list(cluster.get("times") or []),
|
||||
"contexts": list(cluster.get("contexts") or []),
|
||||
}
|
||||
for cluster in clusters
|
||||
if cluster.get("times")
|
||||
]
|
||||
trace: list[dict[str, Any]] = []
|
||||
cap = len(work) if max_clusters is None else max(int(max_clusters), 1)
|
||||
while len(work) > cap:
|
||||
best_index = None
|
||||
best_key: tuple[float, float, int] | None = None
|
||||
for index in range(len(work) - 1):
|
||||
left = peak_score(work[index], by_time)
|
||||
right = peak_score(work[index + 1], by_time)
|
||||
delta = abs(left - right)
|
||||
if max_peak_delta is not None and delta > max_peak_delta + 1e-12:
|
||||
continue
|
||||
key = (min(left, right), left + right, index)
|
||||
if best_key is None or key < best_key:
|
||||
best_key = key
|
||||
best_index = index
|
||||
if best_index is None:
|
||||
break
|
||||
left = work[best_index]
|
||||
right = work[best_index + 1]
|
||||
left_peak = peak_score(left, by_time)
|
||||
right_peak = peak_score(right, by_time)
|
||||
merged_times = sorted(set(left["times"] + right["times"]), key=clock)
|
||||
trace.append({
|
||||
"left_key": left.get("signature_key"),
|
||||
"right_key": right.get("signature_key"),
|
||||
"left_peak": round(left_peak, 4),
|
||||
"right_peak": round(right_peak, 4),
|
||||
"delta": round(abs(left_peak - right_peak), 4),
|
||||
"min_peak": round(min(left_peak, right_peak), 4),
|
||||
"merged_size": len(merged_times),
|
||||
})
|
||||
work[best_index] = {
|
||||
"signature": left.get("signature"),
|
||||
"signature_key": f"{left.get('signature_key')}+{right.get('signature_key')}",
|
||||
"contexts": list(left.get("contexts") or []) + list(right.get("contexts") or []),
|
||||
"times": merged_times,
|
||||
"representative_time": merged_times[len(merged_times) // 2],
|
||||
"representative": left.get("representative") or right.get("representative"),
|
||||
}
|
||||
del work[best_index + 1]
|
||||
return work, trace
|
||||
|
||||
|
||||
def public_from_clusters(
|
||||
clusters: Sequence[dict[str, Any]],
|
||||
rows: Sequence[dict[str, Any]],
|
||||
) -> list[dict[str, Any]]:
|
||||
by_time = {stamp: row for row in rows if (stamp := hhmm(row.get("time")))}
|
||||
representatives: list[dict[str, Any]] = []
|
||||
for cluster in clusters:
|
||||
members = [by_time[time] for time in cluster.get("times") or [] if time in by_time]
|
||||
if not members:
|
||||
continue
|
||||
best = max(members, key=lambda row: (float(row.get("score") or 0), str(row.get("time"))))
|
||||
times = [time for time in cluster.get("times") or [] if time in by_time]
|
||||
representatives.append({
|
||||
**best,
|
||||
"cluster_times": times,
|
||||
"signature_key": cluster.get("signature_key"),
|
||||
})
|
||||
representatives.sort(key=lambda row: (-float(row.get("score") or 0), str(row.get("time"))))
|
||||
return representatives
|
||||
|
||||
|
||||
def raw_signature_clusters(contexts: Sequence[dict[str, Any]]) -> list[dict[str, Any]]:
|
||||
return cluster_contexts_by_signature(contexts)
|
||||
|
||||
|
||||
def still_valid_public(
|
||||
public: Sequence[dict[str, Any]],
|
||||
scores: dict[str, float],
|
||||
eliminated: set[str] | None = None,
|
||||
lead: float | None = SEPARATION_LEAD,
|
||||
) -> list[dict[str, Any]]:
|
||||
dropped = eliminated or set()
|
||||
active = [
|
||||
row for row in public
|
||||
if hhmm(row.get("time")) not in dropped
|
||||
]
|
||||
if not active:
|
||||
return []
|
||||
peak = max(float(scores.get(str(row.get("time"))[:5], row.get("score") or 0)) for row in active)
|
||||
if lead is None:
|
||||
return list(active)
|
||||
return [
|
||||
row for row in active
|
||||
if peak - float(scores.get(str(row.get("time"))[:5], row.get("score") or 0)) < lead
|
||||
]
|
||||
|
||||
|
||||
def delivery_from_public(public: Sequence[dict[str, Any]]) -> dict[str, Any]:
|
||||
times: list[str] = []
|
||||
seen: set[str] = set()
|
||||
for row in public:
|
||||
members = row.get("cluster_times") or [str(row.get("time"))[:5]]
|
||||
for time in members:
|
||||
stamp = str(time)[:5]
|
||||
if hhmm(stamp) and stamp not in seen:
|
||||
seen.add(stamp)
|
||||
times.append(stamp)
|
||||
edges = range_edges(times)
|
||||
return {
|
||||
"times": times,
|
||||
"width": range_width(times),
|
||||
"start": None if edges is None else edges[0],
|
||||
"end": None if edges is None else edges[1],
|
||||
"cluster_count": len(public),
|
||||
}
|
||||
|
||||
|
||||
def minute_mass_from_public(
|
||||
public: Sequence[dict[str, Any]],
|
||||
scores: dict[str, float],
|
||||
) -> dict[str, float]:
|
||||
mass: dict[str, float] = {}
|
||||
for row in public:
|
||||
stamp = str(row.get("time"))[:5]
|
||||
weight = max(float(scores.get(stamp, row.get("score") or 0)), 0.0)
|
||||
for time in row.get("cluster_times") or [stamp]:
|
||||
member = str(time)[:5]
|
||||
if hhmm(member):
|
||||
mass[member] = max(mass.get(member, 0.0), weight)
|
||||
return mass
|
||||
|
||||
|
||||
def smallest_mass_interval(
|
||||
mass: dict[str, float],
|
||||
coverage: float,
|
||||
) -> dict[str, Any]:
|
||||
"""Smallest contiguous clock span whose non-negative mass share >= coverage."""
|
||||
items = sorted(
|
||||
((time, weight) for time, weight in mass.items() if hhmm(time) and weight > 0),
|
||||
key=lambda item: clock(item[0]),
|
||||
)
|
||||
empty = {
|
||||
"times": [],
|
||||
"width": None,
|
||||
"start": None,
|
||||
"end": None,
|
||||
"mass_share": 0.0,
|
||||
"covered": False,
|
||||
}
|
||||
if not items:
|
||||
return empty
|
||||
total = sum(weight for _time, weight in items)
|
||||
if total <= 0:
|
||||
return empty
|
||||
need = total * float(coverage)
|
||||
n = len(items)
|
||||
prefix = [0.0]
|
||||
for _time, weight in items:
|
||||
prefix.append(prefix[-1] + weight)
|
||||
best_key: tuple[int, float, int] | None = None
|
||||
best_span: tuple[int, int] | None = None
|
||||
for left in range(n):
|
||||
for right in range(left, n):
|
||||
share = prefix[right + 1] - prefix[left]
|
||||
if share + 1e-12 < need:
|
||||
continue
|
||||
width = clock(items[right][0]) - clock(items[left][0]) + 1
|
||||
key = (width, -share, left)
|
||||
if best_key is None or key < best_key:
|
||||
best_key = key
|
||||
best_span = (left, right)
|
||||
if best_span is None:
|
||||
times = [time for time, _weight in items]
|
||||
edges = range_edges(times)
|
||||
return {
|
||||
"times": times,
|
||||
"width": range_width(times),
|
||||
"start": None if edges is None else edges[0],
|
||||
"end": None if edges is None else edges[1],
|
||||
"mass_share": 1.0,
|
||||
"covered": True,
|
||||
}
|
||||
left, right = best_span
|
||||
times = [items[index][0] for index in range(left, right + 1)]
|
||||
share = (prefix[right + 1] - prefix[left]) / total
|
||||
edges = range_edges(times)
|
||||
return {
|
||||
"times": times,
|
||||
"width": range_width(times),
|
||||
"start": None if edges is None else edges[0],
|
||||
"end": None if edges is None else edges[1],
|
||||
"mass_share": round(share, 6),
|
||||
"covered": True,
|
||||
}
|
||||
|
||||
|
||||
def time_in_delivery(true_time: str, times: Sequence[str]) -> bool:
|
||||
stamp = str(true_time)[:5]
|
||||
if stamp in {str(item)[:5] for item in times}:
|
||||
return True
|
||||
clocks = [clock(item) for item in times if hhmm(item)]
|
||||
if not clocks:
|
||||
return False
|
||||
point = clock(stamp)
|
||||
return min(clocks) <= point <= max(clocks)
|
||||
|
||||
|
||||
def top1_from_public(public: Sequence[dict[str, Any]], true_time: str) -> bool:
|
||||
if not public:
|
||||
return False
|
||||
scores = [float(row.get("score") or 0) for row in public]
|
||||
best = max(scores)
|
||||
leaders = [row for row in public if abs(float(row.get("score") or 0) - best) <= 1e-9]
|
||||
stamp = str(true_time)[:5]
|
||||
for row in leaders:
|
||||
members = [str(item)[:5] for item in (row.get("cluster_times") or [str(row.get("time"))[:5]])]
|
||||
if stamp in members or str(row.get("time"))[:5] == stamp:
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def tied_first(public: Sequence[dict[str, Any]]) -> bool:
|
||||
if len(public) < 2:
|
||||
return False
|
||||
scores = sorted({round(float(row.get("score") or 0), 4) for row in public}, reverse=True)
|
||||
if len(scores) < 2:
|
||||
return True
|
||||
return abs(scores[0] - scores[1]) <= 1e-9
|
||||
|
||||
|
||||
def metrics_bundle(
|
||||
*,
|
||||
public: Sequence[dict[str, Any]],
|
||||
true_time: str,
|
||||
window_times: Sequence[str],
|
||||
delivery_times: Sequence[str],
|
||||
delivery_width: int | None,
|
||||
independent: bool,
|
||||
entropy_scores: Sequence[float] | None = None,
|
||||
) -> dict[str, Any]:
|
||||
width = delivery_width if delivery_width is not None else range_width(list(delivery_times))
|
||||
covered = time_in_delivery(true_time, delivery_times)
|
||||
scores = entropy_scores if entropy_scores is not None else [float(row.get("score") or 0) for row in public]
|
||||
return {
|
||||
"top1_hit": top1_from_public(public, true_time),
|
||||
"coverage": covered,
|
||||
"width": width,
|
||||
"tie": tied_first(public),
|
||||
"entropy": shannon_entropy(max(score, 0.0) for score in scores),
|
||||
"truth_squeezed": not covered,
|
||||
"too_narrow": width is not None and width <= NARROW_WIDTH and covered,
|
||||
"public_count": len(public),
|
||||
"independent": independent,
|
||||
"window_width": range_width(list(window_times)),
|
||||
}
|
||||
@@ -0,0 +1,778 @@
|
||||
#!/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())
|
||||
Reference in New Issue
Block a user