fix(consult): attach transit windows and VedAstro minute snapshots
Independent Staging Quality Gate / validate (push) Successful in 9m12s
Independent Staging Quality Gate / publish (push) Has been cancelled

Consult only copied Sade Sati and never searched trigger dates; V9 score left official minute identity unevaluated. Search a 90-day slow-planet window and run two-candidate snapshots with timeout staying not_evaluated.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Jesse_Chen
2026-08-19 19:07:44 +08:00
parent 99833eba56
commit 80969c9b31
9 changed files with 637 additions and 28 deletions
+329 -18
View File
@@ -352,6 +352,151 @@ def _compare_vedastro_minute_snapshots(candidate_snapshots):
}
def _vedastro_minute_sensitive_candidate_times(result) -> list[str]:
decisions = result.get('candidate_decisions') if isinstance(result, dict) else None
times = []
if not isinstance(decisions, list):
return times
for item in decisions:
if not isinstance(item, dict):
continue
time_value = item.get('time')
if (
isinstance(time_value, str)
and re.fullmatch(r'(?:[01]\d|2[0-3]):[0-5]\d', time_value)
and time_value not in times
):
times.append(time_value)
if len(times) == 2:
break
return times
def _unevaluated_vedastro_minute_sensitive(reason: str) -> dict:
return {
'status': 'not_evaluated',
'summary': {
'reason': reason,
'discriminated': False,
},
}
def _evaluate_vedastro_minute_sensitive_pair(request, times: list[str]) -> dict:
if not isinstance(request, dict) or len(times) != 2:
return _unevaluated_vedastro_minute_sensitive('vedastro_minute_pair_unavailable')
try:
birth_day = datetime.strptime(str(request.get('birth_date') or ''), '%Y-%m-%d')
lat = float(request['lat'])
lon = float(request['lon'])
tz = float(request['tz'])
except (KeyError, TypeError, ValueError):
return _unevaluated_vedastro_minute_sensitive('vedastro_minute_request_incomplete')
try:
adapter = _load_local_module('vedastro_service_adapter')
except Exception:
return _unevaluated_vedastro_minute_sensitive('vedastro_minute_adapter_unavailable')
def snapshot_for(candidate_time: str):
hour, minute = candidate_time.split(':', 1)
case = {
'year': birth_day.year,
'month': birth_day.month,
'day': birth_day.day,
'hour': int(hour),
'minute': int(minute),
'second': 0,
'lat': lat,
'lon': lon,
'tz': tz,
}
return _safe_vedastro_adapter_call(
adapter.run_rectification_minute_snapshot_for_case,
case,
case_id=f'rectification_v5_minute_{candidate_time.replace(":", "")}',
)
timeout = _foreground_vedastro_budget_seconds()
futures = [_FOREGROUND_VEDASTRO_EXECUTOR.submit(snapshot_for, candidate_time) for candidate_time in times]
reports = []
deadline = time.monotonic() + timeout
for future in futures:
remaining = max(0.0, deadline - time.monotonic())
try:
reports.append(future.result(timeout=remaining))
except FuturesTimeoutError:
return _unevaluated_vedastro_minute_sensitive('vedastro_minute_snapshot_timeout')
except Exception:
return _unevaluated_vedastro_minute_sensitive('vedastro_minute_snapshot_error')
summaries = [
_safe_vedastro_minute_snapshot_summary(candidate_time, report)
for candidate_time, report in zip(times, reports)
]
if any(
report.get('status') == 'timeout' or report.get('_failure_kind') == 'timeout'
for report in reports
):
return _unevaluated_vedastro_minute_sensitive('vedastro_minute_snapshot_timeout')
complete = all(
_vedastro_minute_snapshot_is_complete(report, summary)
for report, summary in zip(reports, summaries)
)
if not complete:
return _unevaluated_vedastro_minute_sensitive('vedastro_minute_snapshot_incomplete')
comparison = _compare_vedastro_minute_snapshots(summaries)
discriminated_layers = [
str(name) for name in (comparison.get('discriminated_layers') or [])[:6] if name
]
if comparison.get('discriminated'):
return {
'status': 'passed',
'summary': {
'reason': 'minute_sensitive_layers_discriminated',
'discriminated': True,
'discriminated_layers': discriminated_layers,
},
}
return {
'status': 'failed',
'summary': {
'reason': 'vedastro_minute_sensitive_layers_not_discriminated',
'discriminated': False,
},
}
def _attach_vedastro_minute_sensitive_to_score(result, request):
if not isinstance(result, dict):
return result
receipt = result.get('decision_receipt')
if not isinstance(receipt, dict):
receipt = result.get('candidate_decision_receipt')
if not isinstance(receipt, dict):
return result
exact = receipt.setdefault('gates', {}).setdefault('exact_confirmation', {})
if not isinstance(exact, dict):
exact = {}
receipt.setdefault('gates', {})['exact_confirmation'] = exact
times = _vedastro_minute_sensitive_candidate_times(result)
if len(times) < 2 or receipt.get('acceptance_allowed') is not True:
exact.setdefault('external_validation_status', 'not_evaluated')
exact.setdefault('vedastro_minute_sensitive', {
'reason': 'local_candidate_not_ready_for_minute_sensitive_check',
'discriminated': False,
})
result['decision_receipt'] = receipt
if isinstance(result.get('candidate_decision_receipt'), dict):
result['candidate_decision_receipt'] = receipt
return result
evaluation = _evaluate_vedastro_minute_sensitive_pair(request, times)
exact['external_validation_status'] = evaluation['status']
exact['vedastro_minute_sensitive'] = evaluation['summary']
result['decision_receipt'] = receipt
if 'candidate_decision_receipt' in result:
result['candidate_decision_receipt'] = receipt
return result
def _safe_vedastro_adapter_call(call, *args, **kwargs):
try:
result = call(*args, **kwargs)
@@ -582,6 +727,10 @@ _WESTERN_TIMING_LAYER_NAMES = (
'transit_duration_scan',
'parans',
)
_CONSULTATION_TRANSIT_WINDOW_DAYS = 90
_CONSULTATION_TRANSIT_PLANETS = ('Saturn', 'Jupiter', 'Rahu', 'Ketu')
_WESTERN_ASPECT_KEYS = ('transit_planet', 'natal_point', 'aspect', 'orb')
_WESTERN_WINDOW_KEYS = ('transit_planet', 'natal_point', 'aspect', 'start_date', 'end_date', 'min_orb')
def _western_default_timing(body: dict) -> dict:
@@ -880,6 +1029,159 @@ def _sign_of(point) -> str | None:
return None
def _iso_date_only(value) -> str | None:
if hasattr(value, 'strftime'):
try:
return value.strftime('%Y-%m-%d')
except (TypeError, ValueError, OverflowError):
return None
if isinstance(value, str) and re.match(r'\d{4}-\d{2}-\d{2}', value):
return value[:10]
return None
def _consultation_point_lon(point) -> float | None:
if not isinstance(point, dict):
return None
raw = point.get('lon')
if raw is None:
raw = point.get('longitude')
if raw is None:
raw = point.get('degree_raw')
try:
return float(raw)
except (TypeError, ValueError):
return None
def _compact_consultation_trigger(item) -> dict | None:
if not isinstance(item, dict):
return None
date = _iso_date_only(item.get('date') or item.get('start_date'))
end = _iso_date_only(item.get('end_date'))
planet = item.get('planet')
target = item.get('target') or item.get('sensitive_point')
kind = item.get('kind') or item.get('type') or item.get('event')
compact = {}
if date:
compact['date'] = date
if isinstance(planet, str) and planet.strip():
compact['planet'] = planet.strip()
if isinstance(target, str) and target.strip():
compact['target'] = target.strip()
if isinstance(kind, str) and kind.strip():
compact['kind'] = kind.strip()[:80]
orb = item.get('orb')
if isinstance(orb, (int, float)):
compact['orb'] = round(float(orb), 2)
if date and end and end != date:
compact['window'] = f'{date}..{end}'
return compact or None
def _consultation_transit_search_window(body: dict) -> tuple[datetime, datetime]:
start = _consultation_reference_date(body).replace(tzinfo=None, hour=0, minute=0, second=0, microsecond=0)
return start, start + timedelta(days=_CONSULTATION_TRANSIT_WINDOW_DAYS)
def _search_consultation_transit_triggers(chart: dict, body: dict, *, ayanamsa: str) -> tuple[list, dict]:
start, end = _consultation_transit_search_window(body)
period = {'start': start.strftime('%Y-%m-%d'), 'end': end.strftime('%Y-%m-%d')}
existing = chart.get('transit_triggers')
if isinstance(existing, list) and existing:
compact = [row for row in (_compact_consultation_trigger(item) for item in existing[:6]) if row]
if compact:
return compact, period
planets = chart.get('planets') if isinstance(chart.get('planets'), dict) else {}
ascendant = chart.get('ascendant') if isinstance(chart.get('ascendant'), dict) else {}
points = []
asc_lon = _consultation_point_lon(ascendant)
if asc_lon is not None:
points.append(('Ascendant', asc_lon))
moon = planets.get('Moon') if isinstance(planets.get('Moon'), dict) else {}
moon_lon = _consultation_point_lon(moon)
if moon_lon is not None:
points.append(('Moon', moon_lon))
if not points:
return [], period
transit_mod = _load_local_module('transit_trigger')
found = []
for name, degree in points:
for planet in _CONSULTATION_TRANSIT_PLANETS:
rows = transit_mod.search_transit_triggers(
planet,
degree,
start,
end,
ayanamsa_name=ayanamsa,
)
for row in rows:
if not isinstance(row, dict):
continue
payload = dict(row)
payload.setdefault('planet', planet)
payload.setdefault('sensitive_point', name)
compact = _compact_consultation_trigger(payload)
if compact:
found.append(compact)
if len(found) >= 6:
found.sort(key=lambda item: item.get('date') or '9999-12-31')
return found[:6], period
found.sort(key=lambda item: item.get('date') or '9999-12-31')
return found[:6], period
def _compact_scalar_rows(rows, keys, limit=6) -> list:
compact = []
if not isinstance(rows, list):
return compact
for item in rows:
if not isinstance(item, dict):
continue
row = {}
for key in keys:
value = item.get(key)
if key in {'start_date', 'end_date', 'target_date'} or str(key).endswith('_date'):
value = _iso_date_only(value)
if value is None or value == '':
continue
if isinstance(value, float):
row[key] = round(value, 4)
elif isinstance(value, (str, int, bool)):
row[key] = value
if row:
compact.append(row)
if len(compact) >= limit:
break
return compact
def _compact_western_timing_layer(layer: dict) -> dict:
compact = {
'status': _audit_row_status(layer.get('status') or 'executed'),
'boundary': layer.get('boundary') or layer.get('reason'),
}
for key in ('target_date', 'start_date', 'end_date'):
date = _iso_date_only(layer.get(key))
if date:
compact[key] = date
year = layer.get('target_year')
if isinstance(year, int):
compact['target_year'] = year
return_date = _iso_date_only(layer.get('return_local_time'))
if return_date:
compact['return_date'] = return_date
aspects = _compact_scalar_rows(layer.get('aspects'), _WESTERN_ASPECT_KEYS)
if aspects:
compact['aspects'] = aspects
windows = _compact_scalar_rows(layer.get('windows'), _WESTERN_WINDOW_KEYS)
if windows:
compact['windows'] = windows
if isinstance(layer.get('event_count'), int):
compact['event_count'] = layer['event_count']
return compact
def _compact_western_spectrum(packet: dict | None) -> dict:
if not isinstance(packet, dict) or not packet:
return {
@@ -896,10 +1198,7 @@ def _compact_western_spectrum(packet: dict | None) -> dict:
for name in _WESTERN_TIMING_LAYER_NAMES:
layer = timing.get(name)
if isinstance(layer, dict):
techniques[name] = {
'status': _audit_row_status(layer.get('status') or 'executed'),
'boundary': layer.get('boundary') or layer.get('reason'),
}
techniques[name] = _compact_western_timing_layer(layer)
else:
techniques[name] = {'status': 'blocked', 'boundary': 'layer_not_materialized'}
natal_status = 'executed' if packet.get('status') != 'blocked' and natal_block else str(packet.get('status') or 'blocked')
@@ -1358,21 +1657,31 @@ def _attach_local_consultation_layers(handler, chart: dict, birth_payload: dict,
if not compact_sade:
compact_sade = None
triggers = []
raw_triggers = chart.get('transit_triggers') or (modules.get('transit_triggers') if isinstance(modules.get('transit_triggers'), list) else [])
if isinstance(raw_triggers, list):
for item in raw_triggers[:6]:
if not isinstance(item, dict):
continue
triggers.append({
key: item[key]
for key in ('date', 'planet', 'target', 'kind', 'orb')
if key in item and item[key] is not None
})
search_period = None
try:
triggers, search_period = _search_consultation_transit_triggers(
chart,
body,
ayanamsa=str(body.get('ayanamsa') or birth_payload.get('ayanamsa') or 'lahiri'),
)
except Exception as trigger_exc:
start, end = _consultation_transit_search_window(body)
search_period = {'start': start.strftime('%Y-%m-%d'), 'end': end.strftime('%Y-%m-%d')}
diagnostics.append({
'layer': 'transit_triggers',
'status': 'unavailable',
'reason': trigger_exc.__class__.__name__,
})
modules['transits'] = {
'status': 'executed' if compact_sade or triggers else 'blocked',
'status': 'executed' if compact_sade or triggers or search_period else 'blocked',
'sade_sati': compact_sade,
'triggers': triggers,
'boundary': 'observation windows, not guaranteed events',
'trigger_count': len(triggers),
'search_period': search_period,
'boundary': (
'observation windows, not guaranteed events; '
'empty triggers means none in the searched window, not that transits were skipped'
),
}
except Exception as exc:
modules['transits'] = {
@@ -8691,18 +9000,20 @@ class JyotishAPIHandler(BaseHTTPRequestHandler):
def _compute_rectification_v5_score(self, body):
from scripts.rectification.api_service import score_candidates
request = self._rectification_v5_request(body)
return {
'success': True,
'endpoint': 'rectification_v5_score',
**score_candidates(self._rectification_v5_request(body)),
**_attach_vedastro_minute_sensitive_to_score(score_candidates(request), request),
}
def _compute_rectification_v5_diagnostics(self, body):
from scripts.rectification.api_service import diagnostics
request = self._rectification_v5_request(body)
return {
'success': True,
'endpoint': 'rectification_v5_diagnostics',
**diagnostics(self._rectification_v5_request(body)),
**_attach_vedastro_minute_sensitive_to_score(diagnostics(request), request),
}
def _compute_rectification_v5_vedastro_validate(self, body):