fix(rectification): collect dated events, then distinguish with conflict probes
Empty ledgers stay in natural-language collection. After the first dated event, dasha conflict probes reverse-infer 前事 and block offer until answered. Unique-minute confirmation stays closed at a representative time; adopt reverse-verifies remaining probes. Records BUG-348–351. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -170,7 +170,7 @@ def unique_minute_audit_row(allowed: bool) -> dict[str, str]:
|
||||
return {
|
||||
"technique": "唯一分钟确认",
|
||||
"status": "blocked",
|
||||
"note": "采用不等于确认唯一分钟。",
|
||||
"note": "本会话以代表性时间收口,不确认唯一分钟。",
|
||||
}
|
||||
|
||||
|
||||
@@ -218,6 +218,9 @@ def apply_confirmation_decision(receipt: dict[str, Any]) -> dict[str, Any]:
|
||||
receipt["confirmation_allowed"] = confirmation_allowed
|
||||
receipt["confirm_allowed"] = confirmation_allowed
|
||||
receipt["unique_minute_claim"] = False
|
||||
exact["unique_minute_path"] = (
|
||||
"awaiting_user_consent" if confirmation_allowed else "closed_at_representative"
|
||||
)
|
||||
exact["passed"] = confirmation_allowed
|
||||
exact["fail_closed"] = True
|
||||
exact["external_validation_status"] = vedastro_status
|
||||
@@ -539,6 +542,7 @@ def build_decision_receipt(
|
||||
"indistinguishable_width_minutes": width,
|
||||
"max_confirmation_width_minutes": MAX_CONFIRMATION_WIDTH_MINUTES,
|
||||
"external_validation_status": "not_evaluated",
|
||||
"unique_minute_path": "closed_at_representative",
|
||||
"required_scoreable_events": MIN_CONFIRMATION_EVENTS,
|
||||
"required_scoreable_domains": MIN_CONFIRMATION_DOMAINS,
|
||||
"reason": "engine_and_external_validation_must_explicitly_pass",
|
||||
@@ -609,6 +613,7 @@ def build_decision_receipt(
|
||||
"nakshatra_boundary": packet["nakshatra_boundary"],
|
||||
"precision_stage": packet["precision_stage"],
|
||||
"oos_blind_prompts": packet["oos_blind_prompts"],
|
||||
"discriminating_event_probes": packet.get("discriminating_event_probes") or [],
|
||||
"horary_observation": build_horary_observation(request),
|
||||
"unique_minute_claim": False,
|
||||
})
|
||||
|
||||
@@ -0,0 +1,590 @@
|
||||
"""Public-safe biographical probes from candidate dasha / varga differences.
|
||||
|
||||
Inverts event scoring: pick two representative minutes, find calendar years
|
||||
where Vimshottari + Narayana activation (or true period-start years) differ,
|
||||
and emit a yes/no life-event question. Never grants a unique minute.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from datetime import date, datetime, timedelta
|
||||
from typing import Any, Sequence
|
||||
|
||||
from scripts.active_rectification_event_engine import (
|
||||
DOMAIN_CONFIG,
|
||||
_active_narayana,
|
||||
_active_vimshottari,
|
||||
_score_event,
|
||||
)
|
||||
import dasha_analyzer
|
||||
import narayana_dasha
|
||||
from scripts.rectification.refinement_packet import match_level
|
||||
|
||||
MAX_PROBES = 3
|
||||
LEVEL_RANK = {"none": 0, "weak": 1, "medium": 2, "strong": 3}
|
||||
SCORING_LAYERS = ("d1", "d9", "d10", "d4", "d5", "d24", "d7", "d12", "d2", "d11", "d30")
|
||||
LAYER_DOMAIN = {
|
||||
"d9": "relationship",
|
||||
"d10": "career",
|
||||
"d4": "relocation",
|
||||
"d5": "education",
|
||||
"d24": "education",
|
||||
"d7": "family",
|
||||
"d12": "family",
|
||||
"d2": "finance",
|
||||
"d11": "finance",
|
||||
"d30": "health_pressure",
|
||||
}
|
||||
STAGE_DOMAIN = {
|
||||
"d9_refine": "relationship",
|
||||
"d10_refine": "career",
|
||||
"d4_refine": "relocation",
|
||||
"theme_refine": "relocation",
|
||||
"d5_refine": "education",
|
||||
}
|
||||
VOLUNTEER_ONLY = frozenset({"finance", "health_pressure"})
|
||||
DOMAIN_CATALOG: dict[str, dict[str, Any]] = {
|
||||
"education": {
|
||||
"event_family": "升学、高考、转学或学习环境变化",
|
||||
"quality_family": "高考或重要考试发挥明显失常、压力很大",
|
||||
"kind": "education_milestone",
|
||||
"age_lo": 16,
|
||||
"age_hi": 18,
|
||||
"varga": "D5 / D24",
|
||||
},
|
||||
"relocation": {
|
||||
"event_family": "搬家、离乡或长期异地",
|
||||
"quality_family": "搬家、离乡或住宿结构明显变化",
|
||||
"kind": "home_change",
|
||||
"age_lo": 18,
|
||||
"age_hi": 24,
|
||||
"varga": "D4",
|
||||
},
|
||||
"relationship": {
|
||||
"event_family": "认真关系进入、结束或关系观明显转变",
|
||||
"quality_family": "认真关系进入、结束或关系观明显转变",
|
||||
"kind": "relationship_change",
|
||||
"age_lo": 21,
|
||||
"age_hi": 26,
|
||||
"varga": "D9",
|
||||
},
|
||||
"career": {
|
||||
"event_family": "入职、升职或职责明显加重",
|
||||
"quality_family": "入职、升职或职责明显加重",
|
||||
"kind": "career_change",
|
||||
"age_lo": 22,
|
||||
"age_hi": 30,
|
||||
"varga": "D10",
|
||||
},
|
||||
"family": {
|
||||
"event_family": "家人相关的明显变化",
|
||||
"quality_family": "家人相关的明显变化",
|
||||
"kind": "family_event",
|
||||
"age_lo": 18,
|
||||
"age_hi": 30,
|
||||
"varga": "D12 / D7 / D3",
|
||||
},
|
||||
"finance": {
|
||||
"event_family": "收入、资产或财务明显变化",
|
||||
"quality_family": "收入、资产或财务明显变化",
|
||||
"kind": "finance_change",
|
||||
"age_lo": 22,
|
||||
"age_hi": 32,
|
||||
"varga": "D2 / D11",
|
||||
},
|
||||
"health_pressure": {
|
||||
"event_family": "健康、事故或持续压力明显变化",
|
||||
"quality_family": "健康、事故或持续压力明显变化",
|
||||
"kind": "self_health_event",
|
||||
"age_lo": 16,
|
||||
"age_hi": 40,
|
||||
"varga": "D30",
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
def _clock(value: str) -> int:
|
||||
return int(value[:2]) * 60 + int(value[3:5])
|
||||
|
||||
|
||||
def _context_time(context: dict[str, Any]) -> str | None:
|
||||
feature = context.get("feature") if isinstance(context.get("feature"), dict) else {}
|
||||
raw = feature.get("time")
|
||||
if isinstance(raw, str) and len(raw) >= 5:
|
||||
return raw[:5]
|
||||
at = context.get("candidate_at")
|
||||
if isinstance(at, datetime):
|
||||
return at.strftime("%H:%M")
|
||||
return None
|
||||
|
||||
|
||||
def _birth_year(value: object) -> int | None:
|
||||
text = str(value or "").strip()
|
||||
if len(text) < 4 or not text[:4].isdigit():
|
||||
return None
|
||||
year = int(text[:4])
|
||||
return year if 1900 <= year <= 2100 else None
|
||||
|
||||
|
||||
def _event_year(event: dict[str, Any]) -> int | None:
|
||||
for key in ("date", "date_start", "occurred_from"):
|
||||
year = _birth_year(event.get(key))
|
||||
if year is not None:
|
||||
return year
|
||||
return None
|
||||
|
||||
|
||||
def _year_label(year: int) -> str:
|
||||
return f"{year} 年前后"
|
||||
|
||||
|
||||
def _age_band_year(birth_year: int, domain: str, today: date) -> int | None:
|
||||
catalog = DOMAIN_CATALOG.get(domain)
|
||||
if not catalog:
|
||||
return None
|
||||
age = (int(catalog["age_lo"]) + int(catalog["age_hi"])) // 2
|
||||
year = birth_year + age
|
||||
latest = min(today.year, birth_year + 80)
|
||||
earliest = birth_year + 5
|
||||
if year < earliest or year > latest:
|
||||
return None
|
||||
return year
|
||||
|
||||
|
||||
def _probe_domains(
|
||||
scan: dict[str, Any],
|
||||
precision_current: str | None,
|
||||
events: Sequence[dict[str, Any]],
|
||||
) -> list[str]:
|
||||
volunteered = {
|
||||
str(event.get("domain"))
|
||||
for event in events
|
||||
if isinstance(event, dict) and event.get("domain")
|
||||
}
|
||||
ordered: list[str] = []
|
||||
stage_domain = STAGE_DOMAIN.get(str(precision_current or ""))
|
||||
if stage_domain:
|
||||
ordered.append(stage_domain)
|
||||
for layer in SCORING_LAYERS:
|
||||
domain = LAYER_DOMAIN.get(layer)
|
||||
if not domain or domain in ordered:
|
||||
continue
|
||||
if not scan.get(f"{layer}_candidates_differ"):
|
||||
continue
|
||||
if domain in VOLUNTEER_ONLY and domain not in volunteered:
|
||||
continue
|
||||
ordered.append(domain)
|
||||
if not ordered and scan.get("d1_candidates_differ"):
|
||||
ordered.append("education")
|
||||
return ordered
|
||||
|
||||
|
||||
def _static_contexts(built: dict[str, Any]) -> list[dict[str, Any]]:
|
||||
rows: list[dict[str, Any]] = []
|
||||
for context in built.get("static_contexts") or []:
|
||||
if isinstance(context, dict) and _context_time(context):
|
||||
rows.append(context)
|
||||
rows.sort(key=lambda item: _clock(str(_context_time(item))))
|
||||
return rows
|
||||
|
||||
|
||||
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]
|
||||
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")
|
||||
vargas = context.get("varga_charts")
|
||||
return (
|
||||
isinstance(chart, dict)
|
||||
and isinstance(planets, dict)
|
||||
and isinstance(vargas, dict)
|
||||
and isinstance(planets.get("Moon"), (int, float))
|
||||
and isinstance(context.get("ascendant_index"), int)
|
||||
)
|
||||
|
||||
|
||||
def _candidate_at(context: dict[str, Any], birth_date: str) -> datetime | None:
|
||||
at = context.get("candidate_at")
|
||||
if isinstance(at, datetime):
|
||||
return at
|
||||
time = _context_time(context)
|
||||
if not time:
|
||||
return None
|
||||
try:
|
||||
return datetime.strptime(f"{birth_date} {time}", "%Y-%m-%d %H:%M")
|
||||
except ValueError:
|
||||
return None
|
||||
|
||||
|
||||
def _has_domain_activation(rule_ids: Sequence[str]) -> bool:
|
||||
return any(
|
||||
("_domain_" in str(item) or str(item).endswith("_domain_house") or str(item).endswith("_domain_lord") or str(item).endswith("_domain_varga"))
|
||||
and not str(item).startswith("no_")
|
||||
for item in rule_ids
|
||||
)
|
||||
|
||||
|
||||
def _discriminates(left: Sequence[str], right: Sequence[str]) -> bool:
|
||||
left_rank = LEVEL_RANK.get(match_level(left), 0)
|
||||
right_rank = LEVEL_RANK.get(match_level(right), 0)
|
||||
if abs(left_rank - right_rank) >= 2:
|
||||
return True
|
||||
return _has_domain_activation(left) != _has_domain_activation(right)
|
||||
|
||||
|
||||
def _tracks_present(rule_ids: Sequence[str]) -> tuple[bool, bool]:
|
||||
text = [str(item) for item in rule_ids]
|
||||
return (
|
||||
any(item.startswith("vim_") for item in text),
|
||||
any(item.startswith("narayana_") for item in text),
|
||||
)
|
||||
|
||||
|
||||
def _vim_start_years(birth_date: str, moon_longitude: float, lo: int, hi: int) -> list[int]:
|
||||
nakshatra, progress, _ = dasha_analyzer.lon_to_nakshatra(float(moon_longitude))
|
||||
timeline, _, _, _ = dasha_analyzer.build_dasha_timeline(birth_date, nakshatra, progress)
|
||||
years: list[int] = []
|
||||
for major in timeline:
|
||||
start = major.get("start")
|
||||
if isinstance(start, datetime) and lo <= start.year <= hi:
|
||||
years.append(start.year)
|
||||
for minor in dasha_analyzer.build_antardasha(major):
|
||||
minor_start = minor.get("start")
|
||||
if isinstance(minor_start, datetime) and lo <= minor_start.year <= hi:
|
||||
years.append(minor_start.year)
|
||||
return years
|
||||
|
||||
|
||||
def _narayana_start_years(
|
||||
ascendant_index: int,
|
||||
planet_longitudes: dict[str, float],
|
||||
birth_date: str,
|
||||
lo: int,
|
||||
hi: int,
|
||||
) -> list[int] | None:
|
||||
periods = narayana_dasha.calc_narayana_mahadasha(ascendant_index, planet_longitudes)
|
||||
if not periods:
|
||||
return None
|
||||
birth = datetime.strptime(birth_date, "%Y-%m-%d")
|
||||
years: list[int] = []
|
||||
for major in periods:
|
||||
start_age = major.get("start_age")
|
||||
if not isinstance(start_age, (int, float)):
|
||||
return None
|
||||
year = (birth + timedelta(days=float(start_age) * 365.2425)).year
|
||||
if lo <= year <= hi:
|
||||
years.append(year)
|
||||
antars = narayana_dasha.calc_narayana_antardasha(periods, int(major["sign_idx"]))
|
||||
for minor in antars:
|
||||
minor_age = minor.get("start_age")
|
||||
if not isinstance(minor_age, (int, float)):
|
||||
continue
|
||||
minor_year = (birth + timedelta(days=float(minor_age) * 365.2425)).year
|
||||
if lo <= minor_year <= hi:
|
||||
years.append(minor_year)
|
||||
return years
|
||||
|
||||
|
||||
def _boundary_years(left: list[int], right: list[int]) -> set[int]:
|
||||
years: set[int] = set()
|
||||
for one, two in zip(left, right):
|
||||
if abs(one - two) >= 1:
|
||||
years.add(one)
|
||||
years.add(two)
|
||||
return years
|
||||
|
||||
|
||||
def _score_year(
|
||||
context: dict[str, Any],
|
||||
*,
|
||||
birth_date: str,
|
||||
domain: str,
|
||||
year: int,
|
||||
) -> dict[str, Any] | None:
|
||||
catalog = DOMAIN_CATALOG[domain]
|
||||
prefixes, _ = DOMAIN_CONFIG[domain]
|
||||
varga_charts = context.get("varga_charts") or {}
|
||||
domain_vargas = [varga_charts.get(prefix) for prefix in prefixes]
|
||||
if any(item is None for item in domain_vargas):
|
||||
return None
|
||||
candidate_at = _candidate_at(context, birth_date)
|
||||
moon = (context.get("planet_longitudes") or {}).get("Moon")
|
||||
if candidate_at is None or not isinstance(moon, (int, float)):
|
||||
return None
|
||||
event_at = datetime(year, 7, 1)
|
||||
event = {
|
||||
"id": f"probe-{domain}-{year}",
|
||||
"domain": domain,
|
||||
"event_kind": catalog["kind"],
|
||||
"date": f"{year}-07-01",
|
||||
"precision": "year",
|
||||
"summary": catalog["event_family"],
|
||||
}
|
||||
try:
|
||||
vimshottari = _active_vimshottari(birth_date, float(moon), event_at)
|
||||
narayana = _active_narayana(
|
||||
int(context["ascendant_index"]),
|
||||
context["planet_longitudes"],
|
||||
candidate_at,
|
||||
event_at,
|
||||
)
|
||||
except (KeyError, TypeError, ValueError):
|
||||
return None
|
||||
if narayana[0] is None or narayana[1] is None:
|
||||
return None
|
||||
return _score_event(
|
||||
candidate_time=str(_context_time(context)),
|
||||
event=event,
|
||||
natal_chart=context["chart"],
|
||||
varga_charts=[item for item in domain_vargas if item is not None],
|
||||
vimshottari=vimshottari,
|
||||
narayana=narayana,
|
||||
arudha_padas=context.get("arudha_padas") or {},
|
||||
)
|
||||
|
||||
|
||||
def _agent_brief(
|
||||
*,
|
||||
year_label: str,
|
||||
family: str,
|
||||
quality: bool = False,
|
||||
exam: bool = False,
|
||||
) -> str:
|
||||
if exam:
|
||||
return (
|
||||
f"年份锁定 {year_label}。已有高考或考试经历。"
|
||||
"请写成一句自然语言,问那次是否发挥失常或压力特别大。"
|
||||
"不得改年份。"
|
||||
)
|
||||
if quality:
|
||||
return (
|
||||
f"年份锁定 {year_label}。已有相关经历。"
|
||||
f"请写成一句自然语言,问{family}有没有发生过。"
|
||||
"不得改年份。"
|
||||
)
|
||||
return (
|
||||
f"年份锁定 {year_label}。事件家族:{family}。"
|
||||
"请写成一句自然语言是/否题。"
|
||||
"不得改年份。"
|
||||
)
|
||||
|
||||
|
||||
def _public_probe(
|
||||
*,
|
||||
year: int,
|
||||
domain: str,
|
||||
source: str,
|
||||
tracks: Sequence[str],
|
||||
tracks_agree: bool,
|
||||
user_meaning: str,
|
||||
event_family: str,
|
||||
) -> dict[str, Any]:
|
||||
return {
|
||||
"year": year,
|
||||
"year_label": _year_label(year),
|
||||
"domain": domain,
|
||||
"event_family": event_family,
|
||||
"source": source,
|
||||
"tracks": list(tracks),
|
||||
"tracks_agree": tracks_agree,
|
||||
"unique_minute_claim": False,
|
||||
"user_meaning": user_meaning,
|
||||
"role": "distinguish" if source == "known_event_quality" else "reverse_verify",
|
||||
}
|
||||
|
||||
|
||||
def _quality_probes(
|
||||
events: Sequence[dict[str, Any]],
|
||||
domains: Sequence[str],
|
||||
) -> list[dict[str, Any]]:
|
||||
rows: list[dict[str, Any]] = []
|
||||
seen: set[tuple[str, int]] = set()
|
||||
for event in events:
|
||||
if not isinstance(event, dict):
|
||||
continue
|
||||
domain = str(event.get("domain") or "")
|
||||
year = _event_year(event)
|
||||
if domain not in domains or year is None or (domain, year) in seen:
|
||||
continue
|
||||
seen.add((domain, year))
|
||||
summary = str(event.get("summary") or "")
|
||||
family = str(DOMAIN_CATALOG[domain]["quality_family"])
|
||||
exam = domain == "education" and ("高考" in summary or "考试" in summary)
|
||||
rows.append(_public_probe(
|
||||
year=year,
|
||||
domain=domain,
|
||||
source="known_event_quality",
|
||||
tracks=("vimshottari", "narayana"),
|
||||
tracks_agree=True,
|
||||
user_meaning=_agent_brief(
|
||||
year_label=_year_label(year),
|
||||
family=family,
|
||||
quality=True,
|
||||
exam=exam,
|
||||
),
|
||||
event_family=family,
|
||||
))
|
||||
return rows
|
||||
|
||||
|
||||
def _evaluate_year(
|
||||
left: dict[str, Any],
|
||||
right: dict[str, Any],
|
||||
*,
|
||||
birth_date: str,
|
||||
domain: str,
|
||||
year: int,
|
||||
source: str,
|
||||
) -> dict[str, Any] | None:
|
||||
scored_left = _score_year(left, birth_date=birth_date, domain=domain, year=year)
|
||||
scored_right = _score_year(right, birth_date=birth_date, domain=domain, year=year)
|
||||
if scored_left is None or scored_right is None:
|
||||
return None
|
||||
left_rules = scored_left.get("rule_ids") or []
|
||||
right_rules = scored_right.get("rule_ids") or []
|
||||
if not _discriminates(left_rules, right_rules):
|
||||
return None
|
||||
stronger = left_rules if LEVEL_RANK[match_level(left_rules)] >= LEVEL_RANK[match_level(right_rules)] else right_rules
|
||||
vim_hit, narayana_hit = _tracks_present(stronger)
|
||||
return _public_probe(
|
||||
year=year,
|
||||
domain=domain,
|
||||
source=source,
|
||||
tracks=("vimshottari", "narayana"),
|
||||
tracks_agree=vim_hit and narayana_hit,
|
||||
user_meaning=_agent_brief(
|
||||
year_label=_year_label(year),
|
||||
family=str(DOMAIN_CATALOG[domain]["event_family"]),
|
||||
),
|
||||
event_family=str(DOMAIN_CATALOG[domain]["event_family"]),
|
||||
)
|
||||
|
||||
|
||||
def discriminating_event_probes(
|
||||
request: dict[str, Any],
|
||||
built: dict[str, Any],
|
||||
*,
|
||||
scan: dict[str, Any],
|
||||
candidate_times: Sequence[str],
|
||||
representative_time: str | None,
|
||||
precision_current: str | None = None,
|
||||
today: date | None = None,
|
||||
) -> list[dict[str, Any]]:
|
||||
birth_date = str(request.get("birth_date") or "").strip()
|
||||
birth_year = _birth_year(birth_date)
|
||||
if birth_year is None:
|
||||
return []
|
||||
try:
|
||||
datetime.strptime(birth_date, "%Y-%m-%d")
|
||||
except ValueError:
|
||||
return []
|
||||
now = today or date.today()
|
||||
events = [item for item in (request.get("events") or []) if isinstance(item, dict)]
|
||||
domains = _probe_domains(scan, precision_current, events)
|
||||
if not domains:
|
||||
return []
|
||||
probes = _quality_probes(events, domains)
|
||||
covered_domains = {row["domain"] for row in probes}
|
||||
pair = _pick_representatives(built, scan, candidate_times, representative_time)
|
||||
lo, hi = birth_year + 5, min(now.year, birth_year + 80)
|
||||
can_score = (
|
||||
pair is not None
|
||||
and _scoreable(pair[0])
|
||||
and _scoreable(pair[1])
|
||||
)
|
||||
dasha_domains: set[str] = set()
|
||||
if can_score and pair is not None:
|
||||
left, right = pair
|
||||
left_moon = float(left["planet_longitudes"]["Moon"])
|
||||
right_moon = float(right["planet_longitudes"]["Moon"])
|
||||
vim_years = _boundary_years(
|
||||
_vim_start_years(birth_date, left_moon, lo, hi),
|
||||
_vim_start_years(birth_date, right_moon, lo, hi),
|
||||
)
|
||||
left_narayana = _narayana_start_years(int(left["ascendant_index"]), left["planet_longitudes"], birth_date, lo, hi)
|
||||
right_narayana = _narayana_start_years(int(right["ascendant_index"]), right["planet_longitudes"], birth_date, lo, hi)
|
||||
narayana_years: set[int] = set()
|
||||
if left_narayana is not None and right_narayana is not None:
|
||||
narayana_years = _boundary_years(left_narayana, right_narayana)
|
||||
for domain in domains:
|
||||
if domain in covered_domains:
|
||||
continue
|
||||
boundary = sorted((vim_years | narayana_years) & set(range(lo, hi + 1)))
|
||||
found = None
|
||||
for year in boundary:
|
||||
found = _evaluate_year(
|
||||
left, right, birth_date=birth_date, domain=domain, year=year, source="dasha_boundary",
|
||||
)
|
||||
if found:
|
||||
break
|
||||
if found is None:
|
||||
midpoint = _age_band_year(birth_year, domain, now)
|
||||
if midpoint is not None:
|
||||
found = _evaluate_year(
|
||||
left, right, birth_date=birth_date, domain=domain, year=midpoint, source="dasha_activation",
|
||||
)
|
||||
if found:
|
||||
probes.append(found)
|
||||
dasha_domains.add(domain)
|
||||
covered_domains.add(domain)
|
||||
for domain in domains:
|
||||
if domain in covered_domains or domain in dasha_domains:
|
||||
continue
|
||||
year = _age_band_year(birth_year, domain, now)
|
||||
if year is None:
|
||||
continue
|
||||
probes.append(_public_probe(
|
||||
year=year,
|
||||
domain=domain,
|
||||
source="age_band",
|
||||
tracks=("vimshottari", "narayana"),
|
||||
tracks_agree=False,
|
||||
user_meaning=_agent_brief(
|
||||
year_label=_year_label(year),
|
||||
family=str(DOMAIN_CATALOG[domain]["event_family"]),
|
||||
),
|
||||
event_family=str(DOMAIN_CATALOG[domain]["event_family"]),
|
||||
))
|
||||
covered_domains.add(domain)
|
||||
public: list[dict[str, Any]] = []
|
||||
seen: set[tuple[str, int, str]] = set()
|
||||
for row in probes:
|
||||
key = (str(row["domain"]), int(row["year"]), str(row["source"]))
|
||||
encoded = str(row)
|
||||
if key in seen or "points" in encoded:
|
||||
continue
|
||||
seen.add(key)
|
||||
public.append(row)
|
||||
if len(public) >= MAX_PROBES:
|
||||
break
|
||||
return public
|
||||
@@ -540,6 +540,16 @@ def build_refinement_packet(
|
||||
cluster = cluster_scan(built, candidate_times, representative_time, cluster_width_minutes)
|
||||
ledger = event_dasha_ledger(request, built, representative_time)
|
||||
agreement = dasha_agreement(built, candidate_times)
|
||||
stage = precision_stage(cluster, len(request.get("events") or []))
|
||||
from scripts.rectification.event_probes import discriminating_event_probes
|
||||
probes = discriminating_event_probes(
|
||||
request,
|
||||
built,
|
||||
scan=cluster,
|
||||
candidate_times=candidate_times,
|
||||
representative_time=representative_time,
|
||||
precision_current=str(stage.get("current") or "") or None,
|
||||
)
|
||||
return {
|
||||
"window_scan": scan,
|
||||
"event_dasha_ledger": ledger,
|
||||
@@ -547,8 +557,9 @@ def build_refinement_packet(
|
||||
"dasha_agreement": agreement,
|
||||
"lagna_contrast": lagna_contrast(built),
|
||||
"nakshatra_boundary": nakshatra_boundary(built, representative_time),
|
||||
"precision_stage": precision_stage(cluster, len(request.get("events") or [])),
|
||||
"precision_stage": stage,
|
||||
"oos_blind_prompts": oos_blind_prompts(request),
|
||||
"discriminating_event_probes": probes,
|
||||
"unique_minute_claim": False,
|
||||
"confirmation_allowed": False,
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user