Files
Jyotisha/tests/test_chart_view_points_api.py
T
Jesse_ChenandClaude Opus 5.5 941effe803
Independent Staging Quality Gate / validate (push) Successful in 16m22s
Independent Staging Quality Gate / publish (push) Successful in 4m26s
Chart page data tables and point layers (issue #114, round 2)
Adds /api/chart_points, a thin route over existing producers (upagrahas,
Swiss-Ephemeris special lagnas, Yogi points, arudha padas, friendship), and
the chart page's 数据表 (planets, upagrahas, special lagnas, arudhas,
shadbala detail, friendship) plus a 标点 layer that marks every point of one
group on the board. The ashtakavarga matrix gains the ascendant's own row.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014Ad9rqAB8pXXvyzh7f4X8v
2026-10-27 13:31:35 +08:00

101 lines
4.2 KiB
Python

"""`/api/chart_points` (issue #114): the chart page's data tables come from existing producers."""
from __future__ import annotations
import json
import sys
from pathlib import Path
import pytest
ROOT = Path(__file__).resolve().parents[1]
SCRIPTS = ROOT / "scripts"
if str(SCRIPTS) not in sys.path:
sys.path.insert(0, str(SCRIPTS))
from chart_view_points.api import ChartPointsInputError, SIGNS, compute_chart_points # noqa: E402
# Real engine /api/chart output (1990-06-15 12:00 Beijing, raman), shared with the frontend tests.
GOLDEN = json.loads((ROOT / "frontend" / "tests" / "fixtures" / "chart-insight-golden.json").read_text(encoding="utf-8"))
BODY = {
"year": 1990, "month": 6, "day": 15, "hour": 12, "minute": 0, "second": 0,
"lat": 39.9042, "lon": 116.4074, "tz": 8, "ayanamsa": "raman",
"planets": GOLDEN["chart"]["planets"], "ascendant": GOLDEN["chart"]["ascendant"],
}
def _sign_index(longitude: float) -> int:
return int((longitude % 360) // 30)
def test_every_block_is_computed_from_the_engine_producers():
result = compute_chart_points(BODY)
assert result["success"] is True
assert result["upagrahas"]["status"] == "computed"
assert [row["key"] for row in result["upagrahas"]["rows"]][:5] == ["Dhuma", "Vyatipata", "Parivesha", "Indrachapa", "Upaketu"]
assert len(result["upagrahas"]["rows"]) == 11
assert result["special_lagnas"]["status"] == "computed"
assert len(result["special_lagnas"]["rows"]) == 8
assert [row["key"] for row in result["yogi_points"]["rows"]] == ["Yogi_Point", "Avayogi_Point"]
assert [row["key"] for row in result["arudha_padas"]["rows"]] == ["A1", "A2", "A3", "A4", "A5", "A6", "A7", "A8", "A9", "A10", "A11", "UL"]
assert len(result["friendship"]["rows"]) == 9
def test_points_carry_sign_and_house_counted_from_the_ascendant():
result = compute_chart_points(BODY)
asc = _sign_index(float(BODY["ascendant"]["lon"]))
for block in ("upagrahas", "special_lagnas", "yogi_points"):
for row in result[block]["rows"]:
sign = _sign_index(row["longitude"])
assert row["sign"] == SIGNS[sign]
assert row["house"] == (sign - asc) % 12 + 1
assert 0 <= row["degree_in_sign"] < 30
def test_solar_upagrahas_follow_the_sun_formula():
sun = float(BODY["planets"]["Sun"]["lon"])
rows = {row["key"]: row["longitude"] for row in compute_chart_points(BODY)["upagrahas"]["rows"]}
dhuma = (sun + 133 + 20 / 60) % 360
assert rows["Dhuma"] == pytest.approx(dhuma, abs=1e-3)
assert rows["Vyatipata"] == pytest.approx((360 - dhuma) % 360, abs=1e-3)
assert rows["Parivesha"] == pytest.approx((rows["Vyatipata"] + 180) % 360, abs=1e-3)
def test_avayogi_is_yogi_plus_186_40():
rows = {row["key"]: row["longitude"] for row in compute_chart_points(BODY)["yogi_points"]["rows"]}
assert (rows["Avayogi_Point"] - rows["Yogi_Point"]) % 360 == pytest.approx(186 + 40 / 60, abs=1e-3)
def test_friendship_compound_is_natural_plus_temporary():
rows = compute_chart_points(BODY)["friendship"]["rows"]
sun = next(row for row in rows if row["planet"] == "Sun")
for target in sun["compound"]["great_friends"]:
assert target in sun["natural"]["friends"] and target in sun["temporary"]["friends"]
for target in sun["compound"]["great_enemies"]:
assert target in sun["natural"]["enemies"] and target in sun["temporary"]["enemies"]
def test_one_failing_block_does_not_fail_the_response(monkeypatch):
import gulika
def broken(*_args, **_kwargs):
raise RuntimeError("boom")
monkeypatch.setattr(gulika, "calculate_upagrahas", broken)
result = compute_chart_points(BODY)
assert result["upagrahas"]["status"] == "blocked"
assert result["arudha_padas"]["status"] == "computed"
def test_missing_birth_or_chart_is_a_bad_request():
with pytest.raises(ChartPointsInputError):
compute_chart_points({**BODY, "planets": None})
with pytest.raises(ChartPointsInputError):
compute_chart_points({key: value for key, value in BODY.items() if key != "lat"})
def test_route_is_registered_thinly():
source = (SCRIPTS / "jyotish_api_server.py").read_text(encoding="utf-8")
assert "elif path == '/api/chart_points':" in source
assert "_load_local_module('chart_view_points.api')" in source