Files
Jyotisha/scripts/research/d12_parent_probes_run.py
T

339 lines
12 KiB
Python

#!/usr/bin/env python3
"""Score the pre-registered D12 parent probes. Research only.
Charts come from domain_calculation_service.compute_chart. D12 signs come
from varga.calc_varga. The script writes one JSON file and no timestamps.
"""
from __future__ import annotations
import hashlib
import sys
from datetime import datetime, timedelta
from pathlib import Path
from typing import Any
_SCRIPTS = str(Path(__file__).resolve().parents[1])
if _SCRIPTS not in sys.path:
sys.path.insert(0, _SCRIPTS)
import domain_calculation_service
import swisseph as swe
from scripts.research import d12_parent_probes_lib as lib
PROFILES = {
"father_9": ("D12", 9),
"father_10": ("D12", 10),
"d1_father_9": ("D1", 9),
}
FAMILY = (("father_9", 10), ("father_9", 30), ("father_10", 10), ("father_10", 30))
SETTINGS = {
"baseline": ("raman", "mean"),
"lahiri_mean": ("lahiri", "mean"),
"raman_true": ("raman", "true"),
}
OFFSETS = list(range(-30, 31))
NEEDED = ("Sun", "Moon", "Mercury", "Venus", "Mars", "Jupiter", "Saturn", "Rahu")
def file_sha256(path: Path) -> str:
return hashlib.sha256(path.read_bytes()).hexdigest()
def birth_moment(birth: dict[str, Any]) -> datetime:
clock = str(birth["time"])[:5]
return datetime.strptime(f"{birth['date']} {clock}", "%Y-%m-%d %H:%M")
def placement_at(moment: datetime, birth: dict[str, Any], ayanamsa: str, node_mode: str) -> lib.Placement:
chart = domain_calculation_service.compute_chart(
{
"year": moment.year,
"month": moment.month,
"day": moment.day,
"hour": moment.hour,
"minute": moment.minute,
"second": 0,
"lat": float(birth["latitude"]),
"lon": float(birth["longitude"]),
"tz": float(birth["timezone_offset"]),
"ayanamsa": ayanamsa,
"node_mode": node_mode,
}
)
planets = chart["planets"]
longitudes: dict[str, float] = {}
d1_signs: dict[str, int] = {}
for name in NEEDED:
row = planets.get(name)
if not isinstance(row, dict) or "lon" not in row:
raise RuntimeError(f"chart is missing {name}")
longitudes[name] = float(row["lon"])
if "sign_idx" in row:
d1_signs[name] = int(row["sign_idx"])
else:
d1_signs[name] = int(float(row["lon"]) // 30) % 12
ascendant = chart.get("ascendant") or {}
if "lon" not in ascendant:
raise RuntimeError("chart is missing the ascendant longitude")
return lib.placement_from_longitudes(float(ascendant["lon"]), longitudes, d1_signs)
def sky_grid(cases: list[dict[str, Any]], ayanamsa: str, node_mode: str) -> dict[str, dict[int, lib.Placement]]:
grid: dict[str, dict[int, lib.Placement]] = {}
for index, case in enumerate(cases, start=1):
moment = birth_moment(case["birth"])
rows: dict[int, lib.Placement] = {}
for offset in OFFSETS:
rows[offset] = placement_at(moment + timedelta(minutes=offset), case["birth"], ayanamsa, node_mode)
grid[str(case["case_id"])] = rows
print(f"{ayanamsa}/{node_mode} {index}/{len(cases)}", file=sys.stderr)
return grid
def signs_of(grid: dict[str, dict[int, lib.Placement]]) -> dict[str, dict[int, int]]:
return {
case_id: {offset: placement.d12_lagna for offset, placement in rows.items()}
for case_id, rows in grid.items()
}
def predictions_of(
grid: dict[str, dict[int, lib.Placement]],
chart: str,
father_house: int,
) -> dict[str, dict[int, dict[str, str]]]:
return {
case_id: {
offset: lib.predict(placement, chart, father_house)
for offset, placement in rows.items()
}
for case_id, rows in grid.items()
}
def load_births(prereg: dict[str, Any]) -> list[dict[str, Any]]:
payload = json_load(lib.HOLDOUT_PATH)
cases = list(payload["cases"])
digest = lib.case_id_digest([str(case["case_id"]) for case in cases])
if digest != prereg["birth_records"]["case_id_sha256"]:
raise SystemExit("birth case-id digest does not match the preregistration")
if len(cases) != 77:
raise SystemExit("birth file does not contain 77 cases")
return cases
def json_load(path: Path) -> dict[str, Any]:
import json
return json.loads(path.read_text(encoding="utf-8"))
def load_answers(birth_ids: set[str]) -> tuple[dict[str, dict[str, str]], list[dict[str, Any]], list[str]]:
if not lib.FACTS_PATH.is_file():
raise SystemExit(f"missing facts: {lib.FACTS_PATH}")
payload = json_load(lib.FACTS_PATH)
coded: dict[str, dict[str, str]] = {}
excluded: list[dict[str, Any]] = []
for case in payload.get("cases") or []:
case_id = str(case.get("case_id") or "")
if case_id not in birth_ids:
raise SystemExit(f"fact case is not in the frozen birth list: {case_id}")
answers = lib.validate_fact_case(case)
decisive = lib.decisive_count(answers)
if decisive >= lib.MIN_DECISIVE:
coded[case_id] = answers
else:
excluded.append({"case_id": case_id, "decisive": decisive})
not_coded = sorted(birth_ids - set(coded) - {row["case_id"] for row in excluded})
return coded, excluded, not_coded
def robustness_block(
rows: dict[str, dict[str, Any]],
baseline: dict[str, dict[int, dict[str, str]]],
alternate: dict[str, dict[int, dict[str, str]]],
) -> dict[str, Any]:
sides = {
case_id: (int(row["true_offset"]), int(row["other_offset"]))
for case_id, row in rows.items()
if row["boundary"]
}
changes = lib.change_count(baseline, alternate, sides)
denom = len(sides) * 2 * len(lib.PROBES)
return {
"changes": changes,
"denom": denom,
"display": lib.display_ratio(changes, denom),
"below_line": lib.ratio_below(changes, denom, lib.ROBUST_MAX),
}
def public_cases(rows: dict[str, dict[str, Any]]) -> list[dict[str, Any]]:
published = []
for case_id in sorted(rows):
row = dict(rows[case_id])
row["case_id"] = case_id
row.pop("differ", None)
published.append(row)
return published
def ephemeris_version() -> str:
version = getattr(swe, "version", None)
return str(version) if version else "unknown"
def run() -> dict[str, Any]:
prereg = lib.load_prereg()
lib.assert_prereg_locked(prereg)
births = load_births(prereg)
birth_ids = {str(case["case_id"]) for case in births}
answers, excluded, not_coded = load_answers(birth_ids)
included_ids = sorted(answers)
included_births = [case for case in births if str(case["case_id"]) in answers]
included_births.sort(key=lambda case: str(case["case_id"]))
skies = {
name: sky_grid(included_births, ayanamsa, node_mode)
for name, (ayanamsa, node_mode) in SETTINGS.items()
}
sign_grid = signs_of(skies["baseline"])
predicted = {
profile: predictions_of(skies["baseline"], chart, father_house)
for profile, (chart, father_house) in PROFILES.items()
}
alternate = {
setting: {
profile: predictions_of(skies[setting], "D12", father_house)
for profile, father_house in (("father_9", 9), ("father_10", 10))
}
for setting in ("lahiri_mean", "raman_true")
}
maps = lib.permutation_maps(included_ids, lib.PERM_SEED, lib.PERMUTATIONS)
cells: dict[str, Any] = {}
controls: dict[str, Any] = {}
any_robust_significant = False
for profile, radius in FAMILY:
key = f"{profile}_r{radius}"
evaluated = lib.evaluate_cell(
included_ids,
sign_grid,
predicted[profile],
answers,
radius,
maps,
)
ayanamsa_shift = robustness_block(
evaluated["cases"],
predicted[profile],
alternate["lahiri_mean"][profile],
)
node_shift = robustness_block(
evaluated["cases"],
predicted[profile],
alternate["raman_true"][profile],
)
robust = bool(ayanamsa_shift["below_line"] and node_shift["below_line"])
if evaluated["significant"] and robust:
any_robust_significant = True
control = lib.positive_control_cell(included_ids, sign_grid, predicted[profile], radius)
cells[key] = {
"profile": profile,
"radius_minutes": radius,
"summary": evaluated["summary"],
"significant": evaluated["significant"],
"head_hit_numer": evaluated["head_hit_numer"],
"head_hit_denom": evaluated["head_hit_denom"],
"head_hit_display": evaluated["head_hit_display"],
"retained_numer": evaluated["retained_numer"],
"retained_denom": evaluated["retained_denom"],
"retained_display": evaluated["retained_display"],
"no_boundary": evaluated["no_boundary"],
"robustness_ayanamsa": ayanamsa_shift,
"robustness_node": node_shift,
"robust": robust,
"not_a_gate_segment_rates": True,
"cases": public_cases(evaluated["cases"]),
}
controls[key] = {
"summary": control["summary"],
"differing": control["differing"],
"pipeline_exact": control["pipeline_exact"],
"missed": control["missed"],
"p_below_0_01": control["p_below"],
}
d1_cells = {}
for radius in (10, 30):
evaluated = lib.evaluate_cell(
included_ids,
sign_grid,
predicted["d1_father_9"],
answers,
radius,
maps,
)
d1_cells[f"d1_father_9_r{radius}"] = {
"summary": evaluated["summary"],
"significant_if_it_were_in_the_family": evaluated["significant"],
"head_hit_display": evaluated["head_hit_display"],
"retained_display": evaluated["retained_display"],
"no_boundary": evaluated["no_boundary"],
"not_a_gate": True,
}
control_exact = all(row["pipeline_exact"] for row in controls.values())
min_differing = min((row["differing"] for row in controls.values()), default=0)
control_p_below = all(row["p_below_0_01"] for row in controls.values())
status = lib.decide_status(
included=len(included_ids),
control_exact=control_exact,
differing=min_differing,
control_p_below=control_p_below,
any_robust_significant=any_robust_significant,
)
coded_rows = []
facts_payload = json_load(lib.FACTS_PATH)
for case in facts_payload.get("cases") or []:
coded_rows.append(lib.validate_fact_case(case))
return {
"id": "d12_parent_probes_results_2026_10_12",
"preregistration": "docs/research/d12_parent_probes_preregistration_2026_10_12.json",
"preregistration_sha256": file_sha256(lib.PREREG_PATH),
"facts_sha256": file_sha256(lib.FACTS_PATH),
"ephemeris": ephemeris_version(),
"python": f"{sys.version_info.major}.{sys.version_info.minor}.{sys.version_info.micro}",
"settings": {
"baseline": ["raman", "mean"],
"ayanamsa_alt": ["lahiri", "mean"],
"node_alt": ["raman", "true"],
},
"coded_cases": len(coded_rows),
"included_cases": len(included_ids),
"included_case_ids": included_ids,
"excluded_few_decisive": excluded,
"not_coded": not_coded,
"unclear_rate_coded": lib.answer_tally(coded_rows),
"unclear_rate_included": lib.answer_tally([answers[case_id] for case_id in included_ids]),
"cells": cells,
"positive_control": controls,
"d1_control": d1_cells,
"decision": status,
"decision_note": {
"pass": "过门",
"fail_closed": "不上线",
"underpowered_sample": "欠力",
"control_underpowered": "欠力",
"control_invalid": "结论无效",
}[status],
}
def main() -> None:
payload = run()
lib.RESULTS_PATH.write_bytes(lib.canonical_bytes(payload))
print(f"wrote {lib.RESULTS_PATH} decision={payload['decision']}", file=sys.stderr)
if __name__ == "__main__":
main()