249 lines
11 KiB
Python
249 lines
11 KiB
Python
"""Nearest chart-boundary reads window_scan transitions only.
|
|
|
|
Public AA chart: Steve Jobs, 1955-02-24 19:15, San Francisco
|
|
(references/public_oracle_cases.json id steve_jobs_1955_aa).
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
import copy
|
|
from datetime import datetime, timedelta
|
|
|
|
import pytest
|
|
|
|
from scripts import domain_calculation_service, varga
|
|
from scripts.active_rectification_event_engine import _d11_chart
|
|
from scripts.rectification.event_probes import LAYER_DOMAIN as EVENT_LAYER_DOMAIN
|
|
from scripts.rectification.nearest_boundary import LAYER_DOMAIN, nearest_chart_boundary
|
|
from scripts.rectification.refinement_packet import window_scan
|
|
from scripts.rectification_varga_segments_api import BOUNDARY_CHARTS, scan_varga_segments
|
|
|
|
JOBS = {
|
|
"year": 1955, "month": 2, "day": 24,
|
|
"lat": 37.7749, "lon": -122.4194, "tz": -8.0,
|
|
}
|
|
DIVISIONS = [2, 4, 5, 7, 9, 10, 12, 24, 30]
|
|
VARGA_CHARTS = ["D2", "D4", "D5", "D7", "D9", "D10", "D11", "D12", "D24", "D30"]
|
|
|
|
|
|
def _features(start: datetime, count: int) -> list[dict]:
|
|
rows = []
|
|
for offset in range(count):
|
|
moment = start + timedelta(minutes=offset)
|
|
chart = domain_calculation_service.compute_chart({
|
|
**JOBS,
|
|
"year": moment.year, "month": moment.month, "day": moment.day,
|
|
"hour": moment.hour, "minute": moment.minute,
|
|
"ayanamsa": "raman", "node_mode": "mean",
|
|
})
|
|
ascendant = float(chart["ascendant"]["lon"])
|
|
planets = {
|
|
name: float(item["lon"])
|
|
for name, item in chart.get("planets", {}).items()
|
|
if isinstance(item, dict) and isinstance(item.get("lon"), (int, float))
|
|
}
|
|
computed = varga.calc_all_vargas(planets, ascendant, divisions=DIVISIONS)
|
|
vargas = {f"D{value['_meta']['div']}": int(value["Ascendant"]["sign_idx"]) for value in computed.values()}
|
|
vargas["D11"] = int(_d11_chart(planets, ascendant)["Ascendant"]["sign_idx"])
|
|
rows.append({"feature": {
|
|
"time": moment.strftime("%H:%M"),
|
|
"ascendant_sign_index": int(ascendant // 30) % 12,
|
|
"varga_ascendants": vargas,
|
|
}})
|
|
return rows
|
|
|
|
|
|
@pytest.fixture(scope="module")
|
|
def jobs_scan():
|
|
built = {"static_contexts": _features(datetime(1955, 2, 24, 19, 0), 41)}
|
|
scan = window_scan(built)
|
|
return built, scan
|
|
|
|
|
|
def test_domain_map_matches_event_probes():
|
|
assert LAYER_DOMAIN == EVENT_LAYER_DOMAIN
|
|
assert "d1" not in LAYER_DOMAIN
|
|
|
|
|
|
def test_ignores_display_layers_and_keeps_the_earlier_tie():
|
|
scan = {"transitions": [
|
|
{"layer": "kp1", "at": "19:16", "from_sign": "白羊座", "to_sign": "金牛座"},
|
|
{"layer": "hora", "at": "19:10", "from_sign": "白羊座", "to_sign": "金牛座"},
|
|
{"layer": "d9", "at": "19:10", "from_sign": "狮子座", "to_sign": "狮子座"},
|
|
{"layer": "d10", "at": "19:10", "from_sign": "白羊座", "to_sign": "金牛座"},
|
|
{"layer": "d12", "at": "19:20", "from_sign": "双子座", "to_sign": "巨蟹座"},
|
|
{"layer": "d5", "at": "19:20", "from_sign": "处女座", "to_sign": "天秤座"},
|
|
{"layer": "d24", "at": "19:20", "from_sign": "天蝎座", "to_sign": "射手座"},
|
|
], "scanned": True}
|
|
before = copy.deepcopy(scan)
|
|
tied = nearest_chart_boundary(scan, "19:15")
|
|
assert scan == before
|
|
assert tied["found"] is True
|
|
assert tied["at"] == "19:10"
|
|
assert tied["distance_minutes"] == 5
|
|
assert [row["layer"] for row in tied["layers"]] == ["d10"]
|
|
assert tied["topics"] == ["工作"]
|
|
assert tied["domains"] == ["career"]
|
|
both = nearest_chart_boundary(scan, "19:20")
|
|
assert [row["layer"] for row in both["layers"]] == ["d5", "d24", "d12"]
|
|
assert both["topics"] == ["学业", "父母"]
|
|
assert both["distance_minutes"] == 0
|
|
wrapped = nearest_chart_boundary(
|
|
{"transitions": [{"layer": "d9", "at": "23:58", "from_sign": "白羊座", "to_sign": "金牛座"}]},
|
|
"00:02",
|
|
)
|
|
assert wrapped["distance_minutes"] == 4
|
|
assert nearest_chart_boundary({"transitions": []}, "19:15")["reason"] == "no_transition"
|
|
assert nearest_chart_boundary(None, "19:15")["reason"] == "unavailable"
|
|
|
|
|
|
def test_jobs_inside_flush_and_stable(jobs_scan):
|
|
_built, scan = jobs_scan
|
|
assert "nearest_boundary" not in scan
|
|
changes = [
|
|
row for row in scan["transitions"]
|
|
if row.get("from_sign") and row.get("to_sign") and row["from_sign"] != row["to_sign"]
|
|
and row["layer"] in {"d1", "d9", "d10", "d4", "d5", "d24", "d7", "d12", "d2", "d11", "d30"}
|
|
]
|
|
assert changes, "public AA window produced no chart change"
|
|
flush_at = changes[0]["at"]
|
|
flush = nearest_chart_boundary(scan, flush_at)
|
|
assert flush["found"] is True
|
|
assert flush["distance_minutes"] == 0
|
|
assert flush["at"] == flush_at
|
|
assert flush["layers"]
|
|
assert all(row["from_sign"] != row["to_sign"] for row in flush["layers"])
|
|
|
|
change_clocks = {row["at"] for row in changes}
|
|
inside_clock = None
|
|
inside_distance = -1
|
|
for minute in range(19 * 60, 19 * 60 + 41):
|
|
clock = f"{minute // 60:02d}:{minute % 60:02d}"
|
|
if clock in change_clocks:
|
|
continue
|
|
found = nearest_chart_boundary(scan, clock)
|
|
distance = found["distance_minutes"]
|
|
if found["found"] and distance > inside_distance:
|
|
inside_clock, inside_distance = clock, distance
|
|
assert inside_clock is not None
|
|
assert inside_distance >= 1
|
|
inside = nearest_chart_boundary(scan, inside_clock)
|
|
assert inside["at"] != inside_clock
|
|
|
|
features = jobs_scan[0]["static_contexts"]
|
|
stable_start = stable_count = best_start = best_count = 0
|
|
previous = None
|
|
for index, row in enumerate(features):
|
|
signature = (
|
|
row["feature"]["ascendant_sign_index"],
|
|
tuple(sorted(row["feature"]["varga_ascendants"].items())),
|
|
)
|
|
if signature == previous:
|
|
stable_count += 1
|
|
else:
|
|
stable_start, stable_count, previous = index, 1, signature
|
|
if stable_count > best_count:
|
|
best_start, best_count = stable_start, stable_count
|
|
assert best_count >= 2
|
|
stable_rows = features[best_start:best_start + best_count]
|
|
stable_scan = window_scan({"static_contexts": stable_rows})
|
|
recorded = stable_rows[best_count // 2]["feature"]["time"]
|
|
stable = nearest_chart_boundary(stable_scan, recorded)
|
|
assert stable["found"] is False
|
|
assert stable["reason"] == "no_transition"
|
|
print(
|
|
f"JOBS_BOUNDARY flush={flush_at} layers={[row['layer'] for row in flush['layers']]} "
|
|
f"inside={inside_clock} distance={inside_distance} at={inside['at']} "
|
|
f"topics={inside['topics']} stable={stable_rows[0]['feature']['time']}-{stable_rows[-1]['feature']['time']} "
|
|
f"recorded={recorded}"
|
|
)
|
|
|
|
|
|
def test_api_boundary_is_optional_and_matches_window_scan(tmp_path):
|
|
start = datetime(1955, 2, 24, 19, 12)
|
|
body = {
|
|
"start_at": start.strftime("%Y-%m-%dT%H:%M"),
|
|
"end_at": (start + timedelta(minutes=6)).strftime("%Y-%m-%dT%H:%M"),
|
|
"latitude": JOBS["lat"], "longitude": JOBS["lon"], "timezone_offset": JOBS["tz"],
|
|
"target_charts": ["D1", "D9", "D10"],
|
|
}
|
|
plain = scan_varga_segments(body, cache_dir=tmp_path)
|
|
assert "nearest_boundary" not in plain
|
|
assert set(plain["minutes"][0]["signs"]) == {"D1", "D9", "D10"}
|
|
edged = scan_varga_segments({
|
|
**body,
|
|
"boundary_charts": list(BOUNDARY_CHARTS),
|
|
"recorded_time": "19:15",
|
|
}, cache_dir=tmp_path)
|
|
assert plain["window_key"] != edged["window_key"]
|
|
assert set(edged["minutes"][0]["signs"]) == {"D1", "D9", "D10"}
|
|
assert "nearest_boundary" in edged
|
|
scan = window_scan({"static_contexts": _features(start, 7)})
|
|
assert nearest_chart_boundary(scan, "19:15") == edged["nearest_boundary"]
|
|
with pytest.raises(ValueError, match="invalid_target_charts"):
|
|
scan_varga_segments({**body, "target_charts": ["D12"]}, cache_dir=tmp_path)
|
|
with pytest.raises(ValueError, match="invalid_boundary_charts"):
|
|
scan_varga_segments({**body, "boundary_charts": ["D99"], "recorded_time": "19:15"}, cache_dir=tmp_path)
|
|
|
|
|
|
# sha256 of the omitted-ayanamsa public-chart body, captured before this whitelist.
|
|
OMITTED_AYANAMSA_WINDOW_KEY = "58c936a2ccdbb5e339c79f9c43b990d556b627230cd5d5d200f3fcaaea85fad4"
|
|
|
|
|
|
def _public_segment_body() -> dict:
|
|
start = datetime(1955, 2, 24, 19, 12)
|
|
return {
|
|
"start_at": start.strftime("%Y-%m-%dT%H:%M"),
|
|
"end_at": (start + timedelta(minutes=6)).strftime("%Y-%m-%dT%H:%M"),
|
|
"latitude": JOBS["lat"], "longitude": JOBS["lon"], "timezone_offset": JOBS["tz"],
|
|
"target_charts": ["D1", "D9", "D10"],
|
|
}
|
|
|
|
|
|
def test_omitted_ayanamsa_matches_raman_and_lahiri_moves(tmp_path):
|
|
from scripts.rectification_varga_segments_api import VERSION, _segment_ayanamsa
|
|
|
|
body = _public_segment_body()
|
|
omitted = scan_varga_segments(body, cache_dir=tmp_path)
|
|
assert omitted["version"] == VERSION == "rectification-varga-segments-v1"
|
|
assert omitted["ayanamsa"] == "raman"
|
|
assert omitted["node_mode"] == "mean"
|
|
assert omitted["window_key"] == OMITTED_AYANAMSA_WINDOW_KEY
|
|
assert "nearest_boundary" not in omitted
|
|
for same in (
|
|
{"ayanamsa": "raman"},
|
|
{"ayanamsa": " Raman "},
|
|
{"ayanamsa": " "},
|
|
{"ayanamsa": None},
|
|
{"node_mode": "true"},
|
|
):
|
|
assert scan_varga_segments({**body, **same}, cache_dir=tmp_path) == omitted
|
|
|
|
lahiri = scan_varga_segments({**body, "ayanamsa": "Lahiri"}, cache_dir=tmp_path)
|
|
assert lahiri["ayanamsa"] == "lahiri"
|
|
assert lahiri["node_mode"] == "mean"
|
|
assert lahiri["window_key"] != omitted["window_key"]
|
|
assert [row["signs"] for row in lahiri["minutes"]] != [row["signs"] for row in omitted["minutes"]]
|
|
assert scan_varga_segments({**body, "ayanamsa": "lahiri"}, cache_dir=tmp_path) == lahiri
|
|
|
|
kp = scan_varga_segments({**body, "ayanamsa": "kp"}, cache_dir=tmp_path)
|
|
assert kp["ayanamsa"] == "kp"
|
|
assert kp["window_key"] not in {omitted["window_key"], lahiri["window_key"]}
|
|
for alias in ("krishnamurti", "krishnamurti_paddhati", "krishnamurti/kp", "Krishnamurti-Paddhati"):
|
|
assert scan_varga_segments({**body, "ayanamsa": alias}, cache_dir=tmp_path) == kp
|
|
citra = scan_varga_segments({**body, "ayanamsa": "true_chitra"}, cache_dir=tmp_path)
|
|
assert citra["ayanamsa"] == "true_citra"
|
|
assert scan_varga_segments({**body, "ayanamsa": "true-citra"}, cache_dir=tmp_path) == citra
|
|
pushya = scan_varga_segments({**body, "ayanamsa": "true_pushya"}, cache_dir=tmp_path)
|
|
assert pushya["ayanamsa"] == "true_pushya"
|
|
fagan = scan_varga_segments({**body, "ayanamsa": "fagan-bradley"}, cache_dir=tmp_path)
|
|
assert fagan["ayanamsa"] == "fagan_bradley"
|
|
assert len({omitted["window_key"], lahiri["window_key"], kp["window_key"], citra["window_key"], pushya["window_key"], fagan["window_key"]}) == 6
|
|
|
|
cached = {path.name for path in tmp_path.glob("*.json")}
|
|
for bad in ("djwhal_khul", "sassanian", "nope", 1, True):
|
|
with pytest.raises(ValueError, match="^invalid_ayanamsa$"):
|
|
scan_varga_segments({**body, "ayanamsa": bad}, cache_dir=tmp_path)
|
|
assert {path.name for path in tmp_path.glob("*.json")} == cached
|
|
assert _segment_ayanamsa("true_chitra") == "true_citra"
|
|
assert _segment_ayanamsa(None) == "raman"
|