feat(rectification): expand dated dasha_boundary probe supply
Distinguish-stage reverse-verify questions were exhausting after 1–3 dated probes. Union boundary windows across representative pairs, keep multiple years per domain, raise the public cap, and allow activation fallback without relaxing MIN_BOUNDARY_DAYS or scoring. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -42,7 +42,22 @@ from scripts.rectification.probe_question_contract import (
|
||||
)
|
||||
from scripts.rectification.refinement_packet import match_level
|
||||
|
||||
MAX_PROBES = 3
|
||||
# Public discriminator list. Four to six domains typically compete; 8 lets about
|
||||
# three domains keep two years plus a couple of activation fallbacks without
|
||||
# flooding the ask layer, which still ranks globally by information_gain.
|
||||
MAX_PROBES = 8
|
||||
# Collection still asks at most three missing-domain age-band questions.
|
||||
MAX_COLLECTION_PROBES = 3
|
||||
# N: keep the top scored probes per domain (boundary years, plus at most one
|
||||
# activation if the domain is still under this cap).
|
||||
MAX_PROBES_PER_DOMAIN = 3
|
||||
# K: per-domain evaluation budget of unique (year, month) windows.
|
||||
# N <= K. Endpoints and evenly spaced months are tried first (at most K).
|
||||
# If those K miss every discriminator, scan the remainder until the first
|
||||
# hit so a lone month is not dropped; do not keep scanning to fill N.
|
||||
# Shrink K if runtime more than doubles; do not drop multi-pair union.
|
||||
# MAX_PROBES is the published cap after a global information_gain sort.
|
||||
MAX_BOUNDARY_CANDIDATES_PER_DOMAIN = 8
|
||||
MIN_BOUNDARY_DAYS = 45
|
||||
LEVEL_RANK = {"none": 0, "weak": 1, "medium": 2, "strong": 3}
|
||||
LEVEL_P = {"none": 0.15, "weak": 0.35, "medium": 0.62, "strong": 0.82}
|
||||
@@ -284,59 +299,6 @@ def _remaining_contexts(built: dict[str, Any], candidate_times: Sequence[str]) -
|
||||
return remaining
|
||||
|
||||
|
||||
def _pick_representatives(
|
||||
built: dict[str, Any],
|
||||
scan: dict[str, Any],
|
||||
candidate_times: Sequence[str],
|
||||
representative_time: str | None,
|
||||
) -> tuple[dict[str, Any], dict[str, Any]] | None:
|
||||
contexts = _static_contexts(built)
|
||||
by_time = {_context_time(item): item for item in contexts}
|
||||
times = [str(_context_time(item)) for item in contexts]
|
||||
remaining: list[str] = []
|
||||
for raw in candidate_times:
|
||||
time = str(raw or "")[:5]
|
||||
if len(time) >= 5 and time in by_time and time not in remaining:
|
||||
remaining.append(time)
|
||||
if len(remaining) >= 2:
|
||||
remaining_set = set(remaining)
|
||||
for transition in scan.get("transitions") or []:
|
||||
if not isinstance(transition, dict):
|
||||
continue
|
||||
layer = transition.get("layer")
|
||||
at = str(transition.get("at") or "")[:5]
|
||||
if layer not in SCORING_LAYERS or at not in by_time or at not in remaining_set:
|
||||
continue
|
||||
left = None
|
||||
for time in remaining:
|
||||
if _clock(time) < _clock(at):
|
||||
left = time
|
||||
if left and left != at:
|
||||
return by_time[left], by_time[at]
|
||||
return by_time[remaining[0]], by_time[remaining[-1]]
|
||||
for transition in scan.get("transitions") or []:
|
||||
if not isinstance(transition, dict):
|
||||
continue
|
||||
layer = transition.get("layer")
|
||||
at = str(transition.get("at") or "")[:5]
|
||||
if layer not in SCORING_LAYERS or at not in by_time:
|
||||
continue
|
||||
index = times.index(at)
|
||||
left = times[index - 1] if index > 0 else at
|
||||
if left != at:
|
||||
return by_time[left], by_time[at]
|
||||
picked: list[str] = []
|
||||
for raw in [*candidate_times, representative_time]:
|
||||
time = str(raw or "")[:5]
|
||||
if len(time) >= 5 and time in by_time and time not in picked:
|
||||
picked.append(time)
|
||||
if len(picked) >= 2:
|
||||
return by_time[picked[0]], by_time[picked[-1]]
|
||||
if len(times) >= 2:
|
||||
return by_time[times[0]], by_time[times[-1]]
|
||||
return None
|
||||
|
||||
|
||||
def _scoreable(context: dict[str, Any]) -> bool:
|
||||
chart = context.get("chart")
|
||||
planets = context.get("planet_longitudes")
|
||||
@@ -480,6 +442,196 @@ def _boundary_years(left: list[int], right: list[int]) -> set[int]:
|
||||
return {item.year for item in _boundary_windows(left, right)}
|
||||
|
||||
|
||||
def _representative_pairs(
|
||||
reps: Sequence[dict[str, Any]],
|
||||
) -> list[tuple[dict[str, Any], dict[str, Any]]]:
|
||||
ordered = sorted(
|
||||
[item for item in reps if _scoreable(item) and _context_time(item)],
|
||||
key=lambda item: _clock(str(_context_time(item))),
|
||||
)
|
||||
if len(ordered) < 2:
|
||||
return []
|
||||
pairs: list[tuple[dict[str, Any], dict[str, Any]]] = []
|
||||
seen: set[tuple[str, str]] = set()
|
||||
|
||||
def add(left: dict[str, Any], right: dict[str, Any]) -> None:
|
||||
left_time = str(_context_time(left) or "")
|
||||
right_time = str(_context_time(right) or "")
|
||||
if not left_time or not right_time or left_time == right_time:
|
||||
return
|
||||
key = (left_time, right_time) if left_time < right_time else (right_time, left_time)
|
||||
if key in seen:
|
||||
return
|
||||
seen.add(key)
|
||||
pairs.append((left, right))
|
||||
|
||||
for index in range(len(ordered) - 1):
|
||||
add(ordered[index], ordered[index + 1])
|
||||
if len(ordered) > 2:
|
||||
add(ordered[0], ordered[-1])
|
||||
return pairs
|
||||
|
||||
|
||||
def _vim_cache_key(birth_date: str, moon: float, lo: int, hi: int) -> tuple[Any, ...]:
|
||||
return (birth_date, round(float(moon), 6), lo, hi)
|
||||
|
||||
|
||||
def _narayana_cache_key(
|
||||
ascendant_index: int,
|
||||
planet_longitudes: dict[str, Any],
|
||||
birth_date: str,
|
||||
lo: int,
|
||||
hi: int,
|
||||
) -> tuple[Any, ...]:
|
||||
planet_key = tuple(
|
||||
sorted(
|
||||
(str(name), round(float(lon), 6))
|
||||
for name, lon in planet_longitudes.items()
|
||||
if isinstance(lon, (int, float))
|
||||
)
|
||||
)
|
||||
return (int(ascendant_index), planet_key, birth_date, lo, hi)
|
||||
|
||||
|
||||
def _union_boundary_dates(
|
||||
reps: Sequence[dict[str, Any]],
|
||||
*,
|
||||
birth_date: str,
|
||||
lo: int,
|
||||
hi: int,
|
||||
) -> list[date]:
|
||||
vim_cache: dict[tuple[Any, ...], list[date]] = {}
|
||||
narayana_cache: dict[tuple[Any, ...], list[date] | None] = {}
|
||||
dates_by_key: dict[tuple[int, int], date] = {}
|
||||
for left, right in _representative_pairs(reps):
|
||||
left_moon = float(left["planet_longitudes"]["Moon"])
|
||||
right_moon = float(right["planet_longitudes"]["Moon"])
|
||||
left_vim_key = _vim_cache_key(birth_date, left_moon, lo, hi)
|
||||
right_vim_key = _vim_cache_key(birth_date, right_moon, lo, hi)
|
||||
if left_vim_key not in vim_cache:
|
||||
vim_cache[left_vim_key] = _vim_start_dates(birth_date, left_moon, lo, hi)
|
||||
if right_vim_key not in vim_cache:
|
||||
vim_cache[right_vim_key] = _vim_start_dates(birth_date, right_moon, lo, hi)
|
||||
windows = list(_boundary_windows(vim_cache[left_vim_key], vim_cache[right_vim_key]))
|
||||
left_nara_key = _narayana_cache_key(
|
||||
int(left["ascendant_index"]),
|
||||
left["planet_longitudes"],
|
||||
birth_date,
|
||||
lo,
|
||||
hi,
|
||||
)
|
||||
right_nara_key = _narayana_cache_key(
|
||||
int(right["ascendant_index"]),
|
||||
right["planet_longitudes"],
|
||||
birth_date,
|
||||
lo,
|
||||
hi,
|
||||
)
|
||||
if left_nara_key not in narayana_cache:
|
||||
narayana_cache[left_nara_key] = _narayana_start_dates(
|
||||
int(left["ascendant_index"]),
|
||||
left["planet_longitudes"],
|
||||
birth_date,
|
||||
lo,
|
||||
hi,
|
||||
)
|
||||
if right_nara_key not in narayana_cache:
|
||||
narayana_cache[right_nara_key] = _narayana_start_dates(
|
||||
int(right["ascendant_index"]),
|
||||
right["planet_longitudes"],
|
||||
birth_date,
|
||||
lo,
|
||||
hi,
|
||||
)
|
||||
left_narayana = narayana_cache[left_nara_key]
|
||||
right_narayana = narayana_cache[right_nara_key]
|
||||
if left_narayana is not None and right_narayana is not None:
|
||||
windows.extend(_boundary_windows(left_narayana, right_narayana))
|
||||
for item in windows:
|
||||
dates_by_key.setdefault((item.year, item.month), item)
|
||||
return sorted(dates_by_key.values(), key=lambda item: (item.year, item.month))
|
||||
|
||||
|
||||
def _bounded_candidate_dates(dates: Sequence[date], limit: int) -> list[date]:
|
||||
items = list(dates)
|
||||
if limit <= 0 or len(items) <= limit:
|
||||
return items
|
||||
if limit == 1:
|
||||
return items[:1]
|
||||
picked: list[date] = []
|
||||
seen: set[tuple[int, int]] = set()
|
||||
last_index = len(items) - 1
|
||||
for step in range(limit):
|
||||
index = (step * last_index + (limit - 1) // 2) // (limit - 1)
|
||||
item = items[index]
|
||||
key = (item.year, item.month)
|
||||
if key in seen:
|
||||
continue
|
||||
seen.add(key)
|
||||
picked.append(item)
|
||||
if len(picked) < limit:
|
||||
for item in items:
|
||||
key = (item.year, item.month)
|
||||
if key in seen:
|
||||
continue
|
||||
seen.add(key)
|
||||
picked.append(item)
|
||||
if len(picked) >= limit:
|
||||
break
|
||||
return picked
|
||||
|
||||
|
||||
def _evaluation_order(dates: Sequence[date], limit: int) -> list[date]:
|
||||
sampled = _bounded_candidate_dates(dates, limit)
|
||||
sampled_keys = {(item.year, item.month) for item in sampled}
|
||||
remainder = [item for item in dates if (item.year, item.month) not in sampled_keys]
|
||||
return sampled + remainder
|
||||
|
||||
|
||||
def _probe_sort_key(row: dict[str, Any]) -> tuple[float, str]:
|
||||
return (-float(row.get("information_gain") or 0), str(row.get("semantic_key") or ""))
|
||||
|
||||
|
||||
def _best_probe_per_year(rows: Sequence[dict[str, Any]]) -> list[dict[str, Any]]:
|
||||
by_year: dict[int, dict[str, Any]] = {}
|
||||
for row in sorted(rows, key=_probe_sort_key):
|
||||
year = int(row["year"])
|
||||
if year not in by_year:
|
||||
by_year[year] = row
|
||||
return list(by_year.values())
|
||||
|
||||
|
||||
def _try_activation_probe(
|
||||
*,
|
||||
reps: Sequence[dict[str, Any]],
|
||||
birth_date: str,
|
||||
birth_year: int,
|
||||
domain: str,
|
||||
now: date,
|
||||
blocked_years: set[int],
|
||||
holdout_keys: set[str],
|
||||
clusters: Sequence[dict[str, Any]],
|
||||
set_version: str,
|
||||
) -> dict[str, Any] | None:
|
||||
midpoint = _age_band_year(birth_year, domain, now)
|
||||
if midpoint is None or midpoint in blocked_years or f"{domain}:{midpoint}" in holdout_keys:
|
||||
return None
|
||||
found = _evaluate_contexts(
|
||||
reps,
|
||||
birth_date=birth_date,
|
||||
domain=domain,
|
||||
year=midpoint,
|
||||
source="dasha_activation",
|
||||
clusters=clusters,
|
||||
set_version=set_version,
|
||||
)
|
||||
if found is None or distinguish_contract_errors(found):
|
||||
return None
|
||||
if not isinstance(found.get("year"), int) or int(found["year"]) <= 0:
|
||||
return None
|
||||
return found
|
||||
|
||||
|
||||
def _score_year(
|
||||
context: dict[str, Any],
|
||||
*,
|
||||
@@ -906,7 +1058,7 @@ def evidence_collection_probes(
|
||||
),
|
||||
event_family=str(DOMAIN_CATALOG[domain]["event_family"]),
|
||||
))
|
||||
if len(rows) >= MAX_PROBES:
|
||||
if len(rows) >= MAX_COLLECTION_PROBES:
|
||||
break
|
||||
return rows
|
||||
|
||||
@@ -984,27 +1136,7 @@ def discriminating_event_probes(
|
||||
if not domains:
|
||||
return []
|
||||
lo, hi = birth_year + 5, min(now.year, birth_year + 80)
|
||||
left, right = reps[0], reps[-1]
|
||||
left_moon = float(left["planet_longitudes"]["Moon"])
|
||||
right_moon = float(right["planet_longitudes"]["Moon"])
|
||||
vim_windows = _boundary_windows(
|
||||
_vim_start_dates(birth_date, left_moon, lo, hi),
|
||||
_vim_start_dates(birth_date, right_moon, lo, hi),
|
||||
)
|
||||
left_narayana = _narayana_start_dates(int(left["ascendant_index"]), left["planet_longitudes"], birth_date, lo, hi)
|
||||
right_narayana = _narayana_start_dates(int(right["ascendant_index"]), right["planet_longitudes"], birth_date, lo, hi)
|
||||
narayana_windows: list[date] = []
|
||||
if left_narayana is not None and right_narayana is not None:
|
||||
narayana_windows = _boundary_windows(left_narayana, right_narayana)
|
||||
boundary_dates: list[date] = []
|
||||
seen_windows: set[tuple[int, int]] = set()
|
||||
for item in [*vim_windows, *narayana_windows]:
|
||||
key = (item.year, item.month)
|
||||
if key in seen_windows:
|
||||
continue
|
||||
seen_windows.add(key)
|
||||
boundary_dates.append(item)
|
||||
boundary_dates.sort()
|
||||
boundary_dates = _union_boundary_dates(reps, birth_date=birth_date, lo=lo, hi=hi)
|
||||
probes: list[dict[str, Any]] = []
|
||||
for domain in domains:
|
||||
if domain not in DOMAIN_CATALOG:
|
||||
@@ -1012,14 +1144,20 @@ def discriminating_event_probes(
|
||||
known_years = _event_years(events, domain)
|
||||
blocked_years = _existence_blocked_years(domain, known_years)
|
||||
domain_lo = _domain_year_floor(birth_year, domain, lo)
|
||||
boundary = [item for item in boundary_dates if domain_lo <= item.year <= hi]
|
||||
best = None
|
||||
for at in boundary:
|
||||
if at.year in blocked_years:
|
||||
continue
|
||||
if f"{domain}:{at.year}" in holdout_keys:
|
||||
continue
|
||||
found = _evaluate_contexts(
|
||||
eligible = [
|
||||
item
|
||||
for item in boundary_dates
|
||||
if domain_lo <= item.year <= hi
|
||||
and item.year not in blocked_years
|
||||
and f"{domain}:{item.year}" not in holdout_keys
|
||||
]
|
||||
found: list[dict[str, Any]] = []
|
||||
evaluated = 0
|
||||
sample_size = min(MAX_BOUNDARY_CANDIDATES_PER_DOMAIN, len(eligible))
|
||||
for at in _evaluation_order(eligible, MAX_BOUNDARY_CANDIDATES_PER_DOMAIN):
|
||||
if evaluated >= sample_size and found:
|
||||
break
|
||||
row = _evaluate_contexts(
|
||||
reps,
|
||||
birth_date=birth_date,
|
||||
domain=domain,
|
||||
@@ -1029,25 +1167,42 @@ def discriminating_event_probes(
|
||||
clusters=clusters,
|
||||
set_version=set_version,
|
||||
)
|
||||
if found is None:
|
||||
evaluated += 1
|
||||
if row is None or distinguish_contract_errors(row):
|
||||
continue
|
||||
if best is None or float(found["information_gain"]) > float(best["information_gain"]):
|
||||
best = found
|
||||
if best is None:
|
||||
midpoint = _age_band_year(birth_year, domain, now)
|
||||
if midpoint is not None and midpoint not in blocked_years and f"{domain}:{midpoint}" not in holdout_keys:
|
||||
best = _evaluate_contexts(
|
||||
reps,
|
||||
birth_date=birth_date,
|
||||
domain=domain,
|
||||
year=midpoint,
|
||||
source="dasha_activation",
|
||||
clusters=clusters,
|
||||
set_version=set_version,
|
||||
if not isinstance(row.get("year"), int) or int(row["year"]) <= 0:
|
||||
continue
|
||||
found.append(row)
|
||||
if evaluated > sample_size:
|
||||
break
|
||||
kept = _best_probe_per_year(found)[:MAX_PROBES_PER_DOMAIN]
|
||||
if len(kept) < MAX_PROBES_PER_DOMAIN:
|
||||
activation = _try_activation_probe(
|
||||
reps=reps,
|
||||
birth_date=birth_date,
|
||||
birth_year=birth_year,
|
||||
domain=domain,
|
||||
now=now,
|
||||
blocked_years=blocked_years,
|
||||
holdout_keys=set(holdout_keys),
|
||||
clusters=clusters,
|
||||
set_version=set_version,
|
||||
)
|
||||
if activation is not None:
|
||||
activation_key = (
|
||||
str(activation["domain"]),
|
||||
int(activation["year"]),
|
||||
int(activation.get("month") or 0),
|
||||
str(activation["source"]),
|
||||
)
|
||||
if best and not distinguish_contract_errors(best):
|
||||
probes.append(best)
|
||||
probes.sort(key=lambda row: (-float(row.get("information_gain") or 0), str(row.get("semantic_key") or "")))
|
||||
existing = {
|
||||
(str(row["domain"]), int(row["year"]), int(row.get("month") or 0), str(row["source"]))
|
||||
for row in kept
|
||||
}
|
||||
if activation_key not in existing:
|
||||
kept.append(activation)
|
||||
probes.extend(kept)
|
||||
probes.sort(key=_probe_sort_key)
|
||||
public: list[dict[str, Any]] = []
|
||||
seen: set[tuple[str, int, int, str]] = set()
|
||||
for row in probes:
|
||||
@@ -1055,6 +1210,8 @@ def discriminating_event_probes(
|
||||
continue
|
||||
if distinguish_contract_errors(row):
|
||||
continue
|
||||
if not isinstance(row.get("year"), int) or int(row["year"]) <= 0:
|
||||
continue
|
||||
key = (str(row["domain"]), int(row["year"]), int(row.get("month") or 0), str(row["source"]))
|
||||
encoded = str(row)
|
||||
if key in seen or "points" in encoded:
|
||||
|
||||
Reference in New Issue
Block a user