Files
Jyotisha/scripts/research/minute_resolution_lib.py
T
jesse-ux 2d2467dca1
Independent Staging Quality Gate / validate (push) Failing after 9m27s
Independent Staging Quality Gate / publish (push) Skipped
docs(research): measure minute-resolution scoring; no variant clears all radii
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.
2026-09-14 20:43:27 +08:00

347 lines
12 KiB
Python

"""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)),
}