339 lines
12 KiB
Python
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()
|