Add jyotish oracle evidence calibration gate
This commit is contained in:
@@ -0,0 +1,251 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Audit Vimshottari Dasha boundary drift against an external reference date.
|
||||
|
||||
This script is diagnostic. It does not tune or override the production Dasha
|
||||
engine; it quantifies how much of a boundary difference can be explained by
|
||||
birth-clock precision and year-length constants.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import math
|
||||
import os
|
||||
import sys
|
||||
from datetime import datetime, timedelta
|
||||
from typing import Any
|
||||
|
||||
|
||||
SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
|
||||
if SCRIPT_DIR not in sys.path:
|
||||
sys.path.insert(0, SCRIPT_DIR)
|
||||
|
||||
import jyotish_engine as engine # noqa: E402
|
||||
|
||||
|
||||
YEAR_LENGTHS = [365.0, 365.2422, 365.25, 365.25636, 360.0]
|
||||
NAKSHATRA_SPAN = 360.0 / 27.0
|
||||
|
||||
|
||||
def _birth_time_string(hour: int, minute: int, second: int = 0) -> str:
|
||||
if second:
|
||||
return f"{hour:02d}:{minute:02d}:{second:02d}"
|
||||
return f"{hour:02d}:{minute:02d}:00"
|
||||
|
||||
|
||||
def _birth_datetime(args: argparse.Namespace, *, second: int | None = None) -> datetime:
|
||||
sec = args.second if second is None else second
|
||||
return datetime(args.year, args.month, args.day, args.hour, args.minute, sec)
|
||||
|
||||
|
||||
def _engine_args(args: argparse.Namespace, *, second: int | None = None) -> argparse.Namespace:
|
||||
return argparse.Namespace(
|
||||
moon_lon=None,
|
||||
nakshatra=None,
|
||||
pada=None,
|
||||
birthdate=None,
|
||||
today=None,
|
||||
year=args.year,
|
||||
month=args.month,
|
||||
day=args.day,
|
||||
hour=args.hour,
|
||||
minute=args.minute,
|
||||
second=args.second if second is None else second,
|
||||
lat=args.lat,
|
||||
lon=args.lon,
|
||||
tz=args.tz,
|
||||
node_mode=args.node_mode,
|
||||
)
|
||||
|
||||
|
||||
def _chart(args: argparse.Namespace, *, second: int | None = None) -> dict[str, Any]:
|
||||
chart, _asc_idx, _jd, _ayanamsa = engine._compute_chart_from_args(_engine_args(args, second=second))
|
||||
if chart is None:
|
||||
raise RuntimeError("Swiss Ephemeris is required for dasha reference audit")
|
||||
return chart
|
||||
|
||||
|
||||
def _moon_context(args: argparse.Namespace, *, second: int | None = None) -> dict[str, Any]:
|
||||
chart = _chart(args, second=second)
|
||||
moon = chart.get("planets", {}).get("Moon", {})
|
||||
moon_lon = float(moon["degree_raw"])
|
||||
nak_index = int(moon_lon / NAKSHATRA_SPAN)
|
||||
progress = (moon_lon % NAKSHATRA_SPAN) / NAKSHATRA_SPAN
|
||||
nak_name, start_lord, start_years = engine.NAKSHATRA_LIST[nak_index % 27]
|
||||
return {
|
||||
"chart": chart,
|
||||
"moon_lon": moon_lon,
|
||||
"nakshatra_index": nak_index,
|
||||
"nakshatra": nak_name,
|
||||
"start_lord": start_lord,
|
||||
"start_years": start_years,
|
||||
"progress": progress,
|
||||
"elapsed_years": progress * start_years,
|
||||
"balance_years": start_years - progress * start_years,
|
||||
}
|
||||
|
||||
|
||||
def _boundary_from_context(
|
||||
birth_dt: datetime,
|
||||
context: dict[str, Any],
|
||||
year_length: float,
|
||||
) -> datetime:
|
||||
elapsed_years = float(context["elapsed_years"])
|
||||
return birth_dt - timedelta(days=elapsed_years * year_length)
|
||||
|
||||
|
||||
def _boundary_snapshot(args: argparse.Namespace, *, second: int, year_length: float = 365.25) -> dict[str, Any]:
|
||||
ctx = _moon_context(args, second=second)
|
||||
birth_dt = _birth_datetime(args, second=second)
|
||||
start_dt = _boundary_from_context(birth_dt, ctx, year_length)
|
||||
return {
|
||||
"birth_time": _birth_time_string(args.hour, args.minute, second),
|
||||
"year_length": year_length,
|
||||
"moon_lon": round(ctx["moon_lon"], 8),
|
||||
"nakshatra": ctx["nakshatra"],
|
||||
"start_lord": ctx["start_lord"],
|
||||
"progress": round(ctx["progress"], 10),
|
||||
"elapsed_years": round(ctx["elapsed_years"], 8),
|
||||
"balance_years": round(ctx["balance_years"], 8),
|
||||
"start_datetime": start_dt.isoformat(timespec="seconds"),
|
||||
}
|
||||
|
||||
|
||||
def _required_moon_delta_for_days(
|
||||
days_delta: float,
|
||||
start_years: float,
|
||||
year_length: float,
|
||||
) -> float:
|
||||
"""Return approximate Moon longitude delta in degrees for a boundary shift."""
|
||||
elapsed_year_delta = abs(days_delta) / year_length
|
||||
progress_delta = elapsed_year_delta / start_years if start_years else 0.0
|
||||
return progress_delta * NAKSHATRA_SPAN
|
||||
|
||||
|
||||
def build_report(args: argparse.Namespace) -> dict[str, Any]:
|
||||
production = engine.cmd_dasha(_engine_args(args))
|
||||
if "error" in production:
|
||||
raise RuntimeError(production["error"])
|
||||
first = production["timeline"][0]
|
||||
context = _moon_context(args)
|
||||
birth_dt = _birth_datetime(args)
|
||||
start_dt = datetime.fromisoformat(first["start_datetime"])
|
||||
target_dt = datetime.strptime(args.target_start_date, "%Y-%m-%d")
|
||||
date_delta_days = (start_dt.date() - target_dt.date()).days
|
||||
exact_delta_days = (start_dt - target_dt).total_seconds() / 86400.0
|
||||
required_moon_delta_deg = _required_moon_delta_for_days(
|
||||
exact_delta_days,
|
||||
float(context["start_years"]),
|
||||
365.25,
|
||||
)
|
||||
|
||||
with_seconds = _boundary_snapshot(args, second=args.second)
|
||||
minute_only = _boundary_snapshot(args, second=0)
|
||||
with_seconds_dt = datetime.fromisoformat(with_seconds["start_datetime"])
|
||||
minute_only_dt = datetime.fromisoformat(minute_only["start_datetime"])
|
||||
clock_translation_seconds = args.second
|
||||
moon_recalculation_seconds = int((with_seconds_dt - minute_only_dt).total_seconds()) - clock_translation_seconds
|
||||
|
||||
year_sensitivity = []
|
||||
for year_length in YEAR_LENGTHS:
|
||||
start = _boundary_from_context(birth_dt, context, year_length)
|
||||
year_sensitivity.append({
|
||||
"year_length": year_length,
|
||||
"start_datetime": start.isoformat(timespec="seconds"),
|
||||
"delta_days_vs_target_date": (start.date() - target_dt.date()).days,
|
||||
})
|
||||
|
||||
min_delta = min(abs(row["delta_days_vs_target_date"]) for row in year_sensitivity)
|
||||
if min_delta > 0:
|
||||
finding = (
|
||||
"当前参考差异无法由秒级输入或年长常数单独解释;"
|
||||
"下一步应对比外部 oracle 的 Moon sidereal longitude、ayanamsa 与 Vimshottari 起算口径。"
|
||||
)
|
||||
else:
|
||||
finding = "当前参考差异可能由年长常数解释;需增加外部 oracle 样本确认。"
|
||||
|
||||
return {
|
||||
"scope": "vimshottari_dasha_reference_boundary_audit",
|
||||
"case": {
|
||||
"date": f"{args.year:04d}-{args.month:02d}-{args.day:02d}",
|
||||
"birth_time": _birth_time_string(args.hour, args.minute, args.second),
|
||||
"lat": args.lat,
|
||||
"lon": args.lon,
|
||||
"tz": args.tz,
|
||||
"node_mode": args.node_mode,
|
||||
},
|
||||
"engine": {
|
||||
"moon_lon": round(context["moon_lon"], 8),
|
||||
"nakshatra": context["nakshatra"],
|
||||
"start_lord": context["start_lord"],
|
||||
"progress": round(context["progress"], 10),
|
||||
"elapsed_years": round(context["elapsed_years"], 8),
|
||||
"balance_years": round(context["balance_years"], 8),
|
||||
"start_datetime": first["start_datetime"],
|
||||
"end_datetime": first["end_datetime"],
|
||||
"start_date": first["start"],
|
||||
"end_date": first["end"],
|
||||
},
|
||||
"target_reference": {
|
||||
"source": args.target_source,
|
||||
"start_date": args.target_start_date,
|
||||
"date_delta_days": date_delta_days,
|
||||
"exact_delta_days": round(exact_delta_days, 6),
|
||||
"required_moon_delta_degrees": round(required_moon_delta_deg, 8),
|
||||
"required_moon_delta_arcmin_range": {
|
||||
"min": round(required_moon_delta_deg * 60 * 0.95, 6),
|
||||
"max": round(required_moon_delta_deg * 60 * 1.05, 6),
|
||||
},
|
||||
},
|
||||
"clock_precision_sensitivity": {
|
||||
"with_seconds": with_seconds,
|
||||
"minute_only": minute_only,
|
||||
"seconds_effect": {
|
||||
"start_delta_seconds": int((with_seconds_dt - minute_only_dt).total_seconds()),
|
||||
"clock_translation_seconds": clock_translation_seconds,
|
||||
"moon_recalculation_seconds": moon_recalculation_seconds,
|
||||
"moon_delta_degrees": round(with_seconds["moon_lon"] - minute_only["moon_lon"], 8),
|
||||
"note": (
|
||||
"Changing birth seconds both translates the birth clock and recomputes Moon longitude; "
|
||||
"near long Dasha lords this can shift the historical start boundary by more than seconds."
|
||||
),
|
||||
},
|
||||
},
|
||||
"year_length_sensitivity": year_sensitivity,
|
||||
"finding": finding,
|
||||
"boundary": (
|
||||
"This is a calculation-boundary audit, not an event-prediction accuracy claim. "
|
||||
"Do not tune production Dasha constants to one PDF without a larger oracle set."
|
||||
),
|
||||
}
|
||||
|
||||
|
||||
def parse_args(argv: list[str] | None = None) -> argparse.Namespace:
|
||||
parser = argparse.ArgumentParser(description="Audit Vimshottari Dasha reference drift")
|
||||
parser.add_argument("--year", type=int, required=True)
|
||||
parser.add_argument("--month", type=int, required=True)
|
||||
parser.add_argument("--day", type=int, required=True)
|
||||
parser.add_argument("--hour", type=int, required=True)
|
||||
parser.add_argument("--minute", type=int, required=True)
|
||||
parser.add_argument("--second", type=int, default=0)
|
||||
parser.add_argument("--lat", type=float, required=True)
|
||||
parser.add_argument("--lon", type=float, required=True)
|
||||
parser.add_argument("--tz", type=float, required=True)
|
||||
parser.add_argument("--node-mode", choices=["mean", "true"], default="mean")
|
||||
parser.add_argument("--target-start-date", required=True)
|
||||
parser.add_argument("--target-source", default="external_reference")
|
||||
return parser.parse_args(argv)
|
||||
|
||||
|
||||
def main(argv: list[str] | None = None) -> int:
|
||||
args = parse_args(argv)
|
||||
if args.second < 0 or args.second > 59:
|
||||
raise SystemExit("--second must be between 0 and 59")
|
||||
report = build_report(args)
|
||||
print(json.dumps(report, ensure_ascii=False, indent=2))
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
Reference in New Issue
Block a user