#!/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()