255 lines
9.3 KiB
Python
255 lines
9.3 KiB
Python
"""D12 parent-probe research helpers. Public charts only; no production edits."""
|
|
|
|
from __future__ import annotations
|
|
|
|
from datetime import datetime
|
|
from decimal import Decimal
|
|
|
|
import pytest
|
|
|
|
from scripts.research import d12_parent_probes_lib as lib
|
|
|
|
|
|
BODIES = ("Sun", "Moon", "Mercury", "Venus", "Mars", "Jupiter", "Saturn", "Rahu", "Ketu")
|
|
|
|
|
|
def signs(**updates: int) -> dict[str, int]:
|
|
base = {name: 0 for name in BODIES}
|
|
base.update(updates)
|
|
return base
|
|
|
|
|
|
def test_preregistration_locks_the_gate_numbers() -> None:
|
|
prereg = lib.load_prereg()
|
|
lib.assert_prereg_locked(prereg)
|
|
births = lib.json.loads(lib.HOLDOUT_PATH.read_text(encoding="utf-8"))["cases"]
|
|
digest = lib.case_id_digest([case["case_id"] for case in births])
|
|
assert digest == prereg["birth_records"]["case_id_sha256"]
|
|
assert len(births) == 77
|
|
|
|
|
|
def test_house_lord_career_and_separation_follow_the_preregistration() -> None:
|
|
assert lib.house_of(0, 0) == 1
|
|
assert lib.house_of(8, 0) == 9
|
|
assert lib.lord_of(0, 9) == "Jupiter"
|
|
assert lib.lord_of(0, 4) == "Moon"
|
|
assert lib.career_house(9) == 6
|
|
assert lib.career_house(10) == 7
|
|
assert lib.separated(0, 5)
|
|
assert lib.separated(0, 1)
|
|
assert not lib.separated(0, 0)
|
|
assert not lib.separated(0, 2)
|
|
|
|
|
|
def test_care_abstains_only_on_a_kendra_tie_and_financial_uses_house_2() -> None:
|
|
abstain = lib.read_probes(signs(**{name: 1 for name in BODIES}), 0, 9)
|
|
assert abstain["mother_more_daily_care"] == "abstain"
|
|
assert abstain["father_financial_lead"] == "yes"
|
|
study_no = lib.read_probes(
|
|
signs(Sun=0, Moon=1, Jupiter=3, Mercury=4, Venus=4, Mars=4, Saturn=2, Rahu=4),
|
|
1,
|
|
9,
|
|
)
|
|
assert study_no["father_guided_study"] == "no"
|
|
institutional = lib.read_probes(signs(Saturn=5), 0, 9)
|
|
assert institutional["father_work_institutional"] == "yes"
|
|
|
|
|
|
def test_missing_rahu_does_not_invent_a_node() -> None:
|
|
bare = {name: 0 for name in BODIES if name != "Rahu"}
|
|
labels = lib.read_probes(bare, 0, 9)
|
|
assert labels["parents_long_apart"] in {"yes", "no"}
|
|
|
|
|
|
def test_nearest_cut_tie_break_and_true_side() -> None:
|
|
signs_at = {-2: 1, -1: 1, 0: 1, 1: 2, 2: 2}
|
|
assert lib.nearest_cut(lib.cuts(signs_at)) == (0, 1)
|
|
tied = {-3: 0, -2: 1, -1: 1, 0: 1, 1: 1, 2: 2, 3: 2}
|
|
assert lib.nearest_cut(lib.cuts(tied)) == (1, 2)
|
|
mirror = {-3: 0, -2: 0, -1: 1, 0: 1, 1: 1, 2: 1, 3: 2}
|
|
assert lib.nearest_cut(lib.cuts(mirror)) == (-2, -1)
|
|
both = {-1: 0, 0: 1, 1: 2}
|
|
assert lib.nearest_cut(lib.cuts(both)) == (0, 1)
|
|
assert lib.true_side(signs_at, (0, 1)) == (0, 1)
|
|
with pytest.raises(RuntimeError):
|
|
lib.true_side({0: 1, 1: 1}, (0, 1))
|
|
|
|
|
|
def test_segment_representative_prefers_the_recorded_minute() -> None:
|
|
assert lib.segment_representative([-2, -1, 0, 1]) == 0
|
|
assert lib.segment_representative([-4, -3]) == -3
|
|
assert lib.segment_representative([2, 3, 4]) == 2
|
|
|
|
|
|
def test_tie_is_not_a_selection_and_unclear_is_not_counted() -> None:
|
|
answers = {probe: "yes" for probe in lib.PROBES}
|
|
same = {probe: "yes" for probe in lib.PROBES}
|
|
assert lib.side_score(same, answers) == 6
|
|
mixed = dict(same)
|
|
mixed["parents_long_apart"] = "abstain"
|
|
unclear = dict(answers)
|
|
unclear["father_guided_study"] = "unclear"
|
|
assert lib.side_score(mixed, unclear) == 4
|
|
assert lib.selection_count(
|
|
["a"],
|
|
{"a": answers},
|
|
{"a": same},
|
|
{"a": same},
|
|
) == 0
|
|
|
|
|
|
def test_fisher_yates_is_repeatable_and_shuffles_whole_vectors() -> None:
|
|
first = lib.permutation_maps(["b", "a", "c"], seed=20261012, count=3)
|
|
second = lib.permutation_maps(["b", "a", "c"], seed=20261012, count=3)
|
|
assert first == second
|
|
assert all(sorted(row) == ["a", "b", "c"] for row in first)
|
|
assigned = lib.assign_answers(
|
|
["a", "b", "c"],
|
|
["c", "a", "b"],
|
|
{"a": {"k": "yes"}, "b": {"k": "no"}, "c": {"k": "unclear"}},
|
|
)
|
|
assert assigned["a"]["k"] == "unclear"
|
|
assert assigned["b"]["k"] == "yes"
|
|
|
|
|
|
def test_window_selects_the_matching_side_and_the_head_segment() -> None:
|
|
offsets = [-1, 0, 1]
|
|
sign_at = {offset: 0 if offset < 1 else 1 for offset in offsets}
|
|
true_labels = {probe: "yes" for probe in lib.PROBES}
|
|
other_labels = {probe: "no" for probe in lib.PROBES}
|
|
predictions = {-1: true_labels, 0: true_labels, 1: other_labels}
|
|
answers = {probe: "yes" for probe in lib.PROBES}
|
|
row = lib.case_window(sign_at, predictions, answers, 1)
|
|
assert row["boundary"] is True
|
|
assert row["true_offset"] == 0
|
|
assert row["other_offset"] == 1
|
|
assert row["selected"] is True
|
|
assert row["head_hit"] is True
|
|
assert row["retained"] is True
|
|
planted = lib.plant_answers(true_labels)
|
|
assert planted["mother_more_daily_care"] == "yes"
|
|
assert lib.plant_answers({"mother_more_daily_care": "abstain", **{probe: "no" for probe in lib.PROBES if probe != "mother_more_daily_care"}})[
|
|
"mother_more_daily_care"
|
|
] == "unclear"
|
|
|
|
|
|
def test_permutation_summary_uses_the_exact_ratio() -> None:
|
|
summary = lib.permutation_summary(5, 10, [0, 1, 5, 6])
|
|
assert summary["extreme"] == 2
|
|
assert summary["p_numer"] == 3
|
|
assert summary["p_denom"] == 5
|
|
assert summary["above_null_mean"] is True
|
|
assert summary["percentile_numer"] == 2
|
|
assert lib.ratio_below(12, 1000, lib.ALPHA) is True
|
|
assert lib.ratio_below(13, 1000, lib.ALPHA) is False
|
|
assert lib.ratio_below(199, 1000, lib.ROBUST_MAX) is True
|
|
assert lib.ratio_below(200, 1000, lib.ROBUST_MAX) is False
|
|
assert lib.cell_is_significant(
|
|
{"above_null_mean": True, "p_numer": 12, "p_denom": 1000}
|
|
) is True
|
|
assert lib.cell_is_significant(
|
|
{"above_null_mean": True, "p_numer": 13, "p_denom": 1000}
|
|
) is False
|
|
|
|
|
|
def test_positive_control_detects_a_planted_difference() -> None:
|
|
sign_at = {offset: 0 if offset <= 0 else 1 for offset in range(-1, 2)}
|
|
yes_labels = {probe: "yes" for probe in lib.PROBES}
|
|
no_labels = {probe: "no" for probe in lib.PROBES}
|
|
case_ids = [f"c{index:02d}" for index in range(20)]
|
|
signs = {case_id: sign_at for case_id in case_ids}
|
|
predicted = {}
|
|
for index, case_id in enumerate(case_ids):
|
|
if index % 2 == 0:
|
|
predicted[case_id] = {-1: yes_labels, 0: yes_labels, 1: no_labels}
|
|
else:
|
|
predicted[case_id] = {-1: no_labels, 0: no_labels, 1: yes_labels}
|
|
control = lib.positive_control_cell(case_ids, signs, predicted, 1)
|
|
assert control["pipeline_exact"] is True
|
|
assert control["differing"] == 20
|
|
assert control["summary"]["observed_numer"] == 20
|
|
assert control["p_below"] is True
|
|
|
|
|
|
def test_decision_status_does_not_pass_a_small_or_blind_sample() -> None:
|
|
assert lib.decide_status(
|
|
included=77,
|
|
control_exact=True,
|
|
differing=30,
|
|
control_p_below=True,
|
|
any_robust_significant=True,
|
|
) == "pass"
|
|
assert lib.decide_status(
|
|
included=59,
|
|
control_exact=True,
|
|
differing=30,
|
|
control_p_below=True,
|
|
any_robust_significant=True,
|
|
) == "underpowered_sample"
|
|
assert lib.decide_status(
|
|
included=77,
|
|
control_exact=True,
|
|
differing=30,
|
|
control_p_below=True,
|
|
any_robust_significant=False,
|
|
) == "fail_closed"
|
|
assert lib.decide_status(
|
|
included=77,
|
|
control_exact=True,
|
|
differing=19,
|
|
control_p_below=True,
|
|
any_robust_significant=True,
|
|
) == "control_underpowered"
|
|
assert lib.decide_status(
|
|
included=77,
|
|
control_exact=False,
|
|
differing=30,
|
|
control_p_below=True,
|
|
any_robust_significant=True,
|
|
) == "control_invalid"
|
|
|
|
|
|
def test_fact_row_requires_an_opened_https_source() -> None:
|
|
row = {
|
|
"answer": "unclear",
|
|
"source_grade": "encyclopedia",
|
|
"url": "https://en.wikipedia.org/wiki/Example",
|
|
"excerpt": "The page names the parents and does not say who did the daily care.",
|
|
}
|
|
case = {"case_id": "example", "probes": {probe: dict(row) for probe in lib.PROBES}}
|
|
answers = lib.validate_fact_case(case)
|
|
assert lib.decisive_count(answers) == 0
|
|
broken = {"case_id": "example", "probes": {probe: dict(row) for probe in lib.PROBES}}
|
|
broken["probes"]["parents_long_apart"]["url"] = "http://insecure.example"
|
|
with pytest.raises(SystemExit):
|
|
lib.validate_fact_case(broken)
|
|
|
|
|
|
def test_change_count_and_canonical_bytes_are_stable() -> None:
|
|
labels = {probe: "yes" for probe in lib.PROBES}
|
|
flipped = dict(labels)
|
|
flipped["parents_long_apart"] = "no"
|
|
baseline = {"a": {0: labels, 1: labels}}
|
|
alternate = {"a": {0: labels, 1: flipped}}
|
|
assert lib.change_count(baseline, alternate, {"a": (0, 1)}) == 1
|
|
payload = {"b": 1, "a": {"d": 2, "c": 3}}
|
|
assert lib.canonical_bytes(payload) == lib.canonical_bytes(payload)
|
|
assert lib.canonical_bytes(payload).endswith(b"\n")
|
|
assert Decimal("0.0125") == lib.ALPHA
|
|
|
|
|
|
def test_production_chart_feeds_d12_without_a_second_ephemeris() -> None:
|
|
from scripts.research.d12_parent_probes_run import placement_at
|
|
|
|
birth = {
|
|
"date": "2000-01-01",
|
|
"time": "12:00",
|
|
"latitude": 0.0,
|
|
"longitude": 0.0,
|
|
"timezone_offset": 0.0,
|
|
}
|
|
placement = placement_at(datetime(2000, 1, 1, 12, 0), birth, "raman", "mean")
|
|
assert 0 <= placement.d12_lagna <= 11
|
|
assert set(lib.predict(placement, "D12", 9)) == set(lib.PROBES)
|
|
assert set(lib.predict(placement, "D1", 9)) == set(lib.PROBES)
|