docs(research): measure minute-resolution scoring; no variant clears all radii
Independent Staging Quality Gate / validate (push) Failing after 9m27s
Independent Staging Quality Gate / publish (push) Skipped

Build holdout v4 from the public AA set, correct the two v3 dates, and sweep
R1–R5 plus pairs offline. No production scoring defaults change. No
implementation brief: delivered width stays the full window on every radius.
This commit is contained in:
jesse-ux
2026-09-14 20:43:27 +08:00
parent 9943a05a01
commit 2d2467dca1
11 changed files with 60785 additions and 4 deletions
+346
View File
@@ -0,0 +1,346 @@
"""Pure helpers for the minute-resolution scoring sweep. No production defaults."""
from __future__ import annotations
import math
from collections.abc import Iterable, Sequence
from typing import Any
from scripts.active_rectification_event_engine import DOMAIN_CONFIG
from scripts.rectification.candidate_contrast import (
SIGNATURE_LAYERS,
cluster_contexts_by_signature,
context_time,
layer_value,
select_signature_representatives,
)
from scripts.rectification.scoring_service import PRECISION_WEIGHTS, _event_kind_factor
VARGA_RULE_WEIGHT = {
"vim_md_domain_varga": 2.0,
"vim_ad_domain_varga": 1.5,
"vim_pd_domain_varga": 0.75,
}
KP_MATCH = {
"md": (0.50, 0.25),
"ad": (0.35, 0.15),
"pd": (0.20, 0.10),
}
DOMAIN_SIGNATURE_LAYERS = {
"education": ("d24", "d5"),
"relocation": ("d4",),
"relationship": ("d9",),
"career": ("d10",),
"occupation": ("d10",),
"finance": ("d2", "d11"),
"health_pressure": ("d30",),
"family": ("d12", "d7", "d3"),
}
EXTRA_LAYER_VARGA = {
"d2": "D2",
"d3": "D3",
"d5": "D5",
"d7": "D7",
"d11": "D11",
"d30": "D30",
}
NARROW_WIDTH = 5
def clock(value: str) -> int:
stamp = str(value)[:5]
return int(stamp[:2]) * 60 + int(stamp[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 range_width(times: Sequence[str]) -> int | None:
clocks = sorted(clock(item) for item in times if str(item)[:5])
if not clocks:
return None
return clocks[-1] - clocks[0] + 1
def varga_divisor(count: int, mode: str) -> float:
n = max(int(count), 1)
if mode == "len":
return float(n)
if mode == "sqrt":
return math.sqrt(n)
if mode == "fixed2":
return 2.0
return 2.0 * n
def rescale_varga_points(
points: float,
rule_ids: Sequence[str],
domain: str,
event_kind: str,
precision: str,
mode: str,
) -> float:
if mode == "2len":
return float(points)
prefixes, _houses = DOMAIN_CONFIG[domain]
n = max(len(prefixes), 1)
old_div = varga_divisor(n, "2len")
new_div = varga_divisor(n, mode)
if abs(old_div - new_div) < 1e-12:
return float(points)
delta_inner = 0.0
for rule in rule_ids:
weight = VARGA_RULE_WEIGHT.get(str(rule))
if weight is None:
continue
delta_inner += weight * (1.0 / new_div - 1.0 / old_div)
precision_w = PRECISION_WEIGHTS.get(precision, 1.0)
kind = _event_kind_factor(event_kind, rule_ids)
return round(float(points) + delta_inner * kind * (precision_w ** 2), 4)
def aggregate_samples(samples: Sequence[float], mode: str, temperature: float = 1.0) -> float:
values = [float(item) for item in samples]
if not values:
return 0.0
if mode == "max":
return round(max(values), 4)
if mode == "lse":
temp = max(float(temperature), 1e-6)
peak = max(values)
mean_exp = sum(math.exp((item - peak) / temp) for item in values) / len(values)
return round(temp * math.log(mean_exp) + peak, 4)
return round(sum(values) / len(values), 4)
def subtract_event_floor(by_time: dict[str, float]) -> dict[str, float]:
if not by_time:
return {}
floor = min(by_time.values())
return {time: round(score - floor, 4) for time, score in by_time.items()}
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 kp_event_points(
context: dict[str, Any],
domain: str,
vim_lords: tuple[str, str, str],
weight: float,
) -> float:
feature = context.get("feature") if isinstance(context.get("feature"), dict) else {}
snapshot = feature.get("kp_cusps") if isinstance(feature.get("kp_cusps"), dict) else {}
if snapshot.get("status") != "executed":
return 0.0
houses = snapshot.get("houses") if isinstance(snapshot.get("houses"), dict) else {}
_prefixes, target_houses = DOMAIN_CONFIG[domain]
points = 0.0
md, ad, pd = vim_lords
for house in target_houses:
row = houses.get(str(house))
if not isinstance(row, dict):
continue
sub = str(row.get("sub_lord") or "")
sub_sub = str(row.get("sub_sub_lord") or "")
for lord, (sub_w, sub_sub_w) in (
(md, KP_MATCH["md"]),
(ad, KP_MATCH["ad"]),
(pd, KP_MATCH["pd"]),
):
if lord and lord == sub:
points += sub_w
if lord and lord == sub_sub:
points += sub_sub_w
return round(points * float(weight), 4)
def kp_sub_lord_key(context: dict[str, Any], houses: Sequence[int]) -> tuple[str, ...]:
feature = context.get("feature") if isinstance(context.get("feature"), dict) else {}
snapshot = feature.get("kp_cusps") if isinstance(feature.get("kp_cusps"), dict) else {}
table = snapshot.get("houses") if isinstance(snapshot.get("houses"), dict) else {}
keys = []
for house in houses:
row = table.get(str(house)) if isinstance(table.get(str(house)), dict) else {}
keys.append(str(row.get("sub_lord") or ""))
return tuple(keys)
def kp_changes_in_window(contexts: Sequence[dict[str, Any]], domain: str) -> int:
_prefixes, houses = DOMAIN_CONFIG[domain]
seen: list[tuple[str, ...]] = []
for context in contexts:
key = kp_sub_lord_key(context, houses)
if not seen or seen[-1] != key:
seen.append(key)
return max(len(seen) - 1, 0)
def dynamic_signature_layers(domains: Sequence[str]) -> tuple[str, ...]:
layers = ["d1"]
for domain in domains:
for layer in DOMAIN_SIGNATURE_LAYERS.get(domain, ()):
if layer not in layers:
layers.append(layer)
return tuple(layers)
def layer_value_extended(context: dict[str, Any], layer: str) -> int | None:
if layer in SIGNATURE_LAYERS or layer == "md" or layer == "d1":
return layer_value(context, layer)
name = EXTRA_LAYER_VARGA.get(layer)
if not name:
return None
feature = context.get("feature") if isinstance(context.get("feature"), dict) else {}
vargas = feature.get("varga_ascendants") if isinstance(feature.get("varga_ascendants"), dict) else {}
raw = vargas.get(name)
return raw if isinstance(raw, int) else None
def cluster_contexts(
contexts: Sequence[dict[str, Any]],
layers: Sequence[str] | None = None,
) -> list[dict[str, Any]]:
if not layers or tuple(layers) == SIGNATURE_LAYERS:
return cluster_contexts_by_signature(contexts)
buckets: dict[tuple[int | None, ...], list[dict[str, Any]]] = {}
for context in contexts:
if not isinstance(context, dict):
continue
time = context_time(context)
if not time:
continue
signature = tuple(layer_value_extended(context, layer) for layer in layers)
buckets.setdefault(signature, []).append(context)
clusters: list[dict[str, Any]] = []
for signature, members in buckets.items():
ordered = sorted(members, key=lambda item: clock(str(context_time(item))))
times = [str(context_time(item)) for item in ordered]
clusters.append({
"signature": signature,
"signature_key": ",".join("x" if value is None else str(value) for value in signature),
"contexts": ordered,
"times": times,
"representative_time": times[len(times) // 2],
"representative": ordered[len(ordered) // 2],
})
clusters.sort(key=lambda item: clock(item["representative_time"]))
return clusters
def public_rows(
rows: Sequence[dict[str, Any]],
contexts: Sequence[dict[str, Any]],
layers: Sequence[str] | None = None,
) -> list[dict[str, Any]]:
if not layers or tuple(layers) == SIGNATURE_LAYERS:
return select_signature_representatives(rows, contexts)
by_time = {str(row.get("time"))[:5]: row for row in rows if str(row.get("time"))}
clusters = cluster_contexts(contexts, layers)
from scripts.rectification.candidate_contrast import cap_clusters_by_adjacent_merge
clusters = cap_clusters_by_adjacent_merge(clusters, by_time)
representatives: list[dict[str, Any]] = []
for cluster in clusters:
members = [by_time[time] for time in cluster["times"] 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"))))
representatives.append({
**best,
"cluster_times": [time for time in cluster["times"] if time in by_time],
})
representatives.sort(key=lambda row: (-float(row.get("score") or 0), str(row.get("time"))))
return representatives or list(rows)[:1]
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 metrics_from_public(
public: Sequence[dict[str, Any]],
true_time: str,
window_times: Sequence[str],
) -> dict[str, Any]:
if not public:
return {
"top1_hit": False,
"coverage": False,
"width": None,
"tie": False,
"entropy": 0.0,
"truth_squeezed": True,
"too_narrow": False,
"leader_count": 0,
"public_count": 0,
"true_cluster_rank": None,
"true_cluster_size": 0,
}
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]
delivered = []
seen: set[str] = set()
for row in public:
for time in row.get("cluster_times") or [str(row.get("time"))[:5]]:
stamp = str(time)[:5]
if stamp not in seen:
seen.add(stamp)
delivered.append(stamp)
true = str(true_time)[:5]
true_in_leaders = any(true in (row.get("cluster_times") or [str(row.get("time"))[:5]]) for row in leaders)
if not true_in_leaders:
true_in_leaders = any(str(row.get("time"))[:5] == true for row in leaders)
rank = None
size = 0
ordered = sorted(public, key=lambda row: (-float(row.get("score") or 0), str(row.get("time"))))
for index, row in enumerate(ordered, start=1):
members = [str(item)[:5] for item in (row.get("cluster_times") or [str(row.get("time"))[:5]])]
if true in members:
rank = index
size = len(members)
break
width = range_width(delivered)
second = sorted({round(float(row.get("score") or 0), 4) for row in public}, reverse=True)
tied = len(leaders) >= 2 or (len(second) >= 2 and abs(second[0] - second[1]) <= 1e-9)
return {
"top1_hit": true_in_leaders,
"coverage": true in seen,
"width": width,
"tie": tied,
"entropy": shannon_entropy(max(score, 0.0) for score in scores),
"truth_squeezed": true not in seen,
"too_narrow": width is not None and width <= NARROW_WIDTH and true in seen,
"leader_count": len(leaders),
"public_count": len(public),
"true_cluster_rank": rank,
"true_cluster_size": size,
"window_width": range_width(list(window_times)),
}