diff --git a/scripts/jyotish_engine.py b/scripts/jyotish_engine.py index 6b94f848..f745c2f2 100644 --- a/scripts/jyotish_engine.py +++ b/scripts/jyotish_engine.py @@ -735,12 +735,24 @@ def _birth_datetime_from_args(args): def _compute_chart_from_args(args): - return compute_chart_data( - args.year, args.month, args.day, args.hour, args.minute, - args.lat, args.lon, args.tz, getattr(args, 'node_mode', 'mean'), - second=_arg_second(args), - ayanamsa_name=_current_ayanamsa_name(args), - ) + from domain_calculation_service import compute_chart + + result = compute_chart({ + 'year': args.year, + 'month': args.month, + 'day': args.day, + 'hour': args.hour, + 'minute': args.minute, + 'second': _arg_second(args), + 'lat': args.lat, + 'lon': args.lon, + 'tz': args.tz, + 'node_mode': getattr(args, 'node_mode', 'mean'), + 'ayanamsa': _current_ayanamsa_name(args), + }) + asc_idx = SIGNS.index(result['ascendant']['sign']) + birth = result['birth_info'] + return result, asc_idx, birth['julian_day'], birth['ayanamsa'] def _current_ayanamsa_name(args=None): diff --git a/tests/test_calculation_p0_regressions.py b/tests/test_calculation_p0_regressions.py new file mode 100644 index 00000000..05338e08 --- /dev/null +++ b/tests/test_calculation_p0_regressions.py @@ -0,0 +1,117 @@ +from __future__ import annotations + +import sys +from datetime import datetime +from pathlib import Path +from types import SimpleNamespace + +import pytest + +SCRIPTS = Path(__file__).resolve().parents[1] / "scripts" +if str(SCRIPTS) not in sys.path: + sys.path.insert(0, str(SCRIPTS)) + +import domain_calculation_service as calculation_service # noqa: E402 +import jyotish_api_server # noqa: E402 +from jyotish_api_server import JyotishAPIHandler # noqa: E402 +from jyotish_engine import _compute_chart_from_args # noqa: E402 + +BIRTH = { + "year": 1990, + "month": 1, + "day": 1, + "hour": 12, + "minute": 0, + "second": 0, + "lat": 28.6139, + "lon": 77.2090, + "tz": 5.5, + "ayanamsa": "lahiri", +} + + +def test_true_node_changes_effective_rahu_and_contract() -> None: + mean = calculation_service.compute_chart({**BIRTH, "node_mode": "mean"}) + true = calculation_service.compute_chart({**BIRTH, "node_mode": "true"}) + + assert mean["planets"]["Rahu"]["lon"] != pytest.approx( + true["planets"]["Rahu"]["lon"], abs=1e-8 + ) + assert mean["calculation_contract"]["effective"]["node_mode"] == "mean" + assert true["calculation_contract"]["effective"]["node_mode"] == "true" + assert mean["result_hash"] != true["result_hash"] + + +def test_vimshottari_uses_birth_balance_as_canonical_timeline() -> None: + birth_dt = datetime(1990, 1, 1, 12, 0) + result = calculation_service.compute_vimshottari_timeline( + birth_dt=birth_dt, + moon_lon=100.0, + current_date=birth_dt, + ) + + first = result["periods"][0] + assert first["lord"] == "Saturn" + assert first["start"] == "1980-07-02" + assert first["end"] == "1999-07-02" + assert result["birth_balance"]["remaining_years"] == pytest.approx(9.5) + assert result["calculation_contract"]["algorithm"] == "vimshottari_birth_balance" + + +def test_sade_sati_uses_real_saturn_transit_for_reference_date() -> None: + result = calculation_service.compute_sade_sati( + moon_degree=300.0, + asc_degree=330.0, + reference_date="2026-07-11", + tz=5.5, + ayanamsa="lahiri", + ) + oracle = calculation_service.compute_transit_longitude( + planet="Saturn", + reference_date="2026-07-11", + tz=5.5, + ayanamsa="lahiri", + ) + + assert result["transit_saturn_lon"] == pytest.approx(oracle["longitude"], abs=1e-8) + assert result["provenance"]["data_layer"] == "true_transit_positions" + assert result["provenance"]["reference_date"] == "2026-07-11" + + +def test_timezone_inference_fails_closed(monkeypatch: pytest.MonkeyPatch) -> None: + monkeypatch.setattr( + calculation_service, + "_lookup_timezone_name", + lambda _lat, _lon: None, + ) + + with pytest.raises(calculation_service.TimezoneInferenceError, match="timezone inference"): + calculation_service.infer_timezone_offset( + lat=0.0, + lon=0.0, + local_datetime=datetime(1990, 1, 1, 12, 0), + ) + + +def test_chart_hash_matches_domain_cli_and_rest( + monkeypatch: pytest.MonkeyPatch, +) -> None: + monkeypatch.setenv("JYOTISH_API_CHART_CACHE_TTL_SECONDS", "0") + monkeypatch.setenv("VEDASTRO_ENABLE_NETWORK", "0") + monkeypatch.setattr( + jyotish_api_server, + "_attach_vedastro_main_entry_overview", + lambda result, _birth: result, + ) + expected = calculation_service.compute_chart({**BIRTH, "node_mode": "true"}) + cli, _asc_idx, _jd, _ayanamsa = _compute_chart_from_args( + SimpleNamespace(**BIRTH, node_mode="true") + ) + rest = JyotishAPIHandler.__new__(JyotishAPIHandler)._compute_chart_sync( + {**BIRTH, "node_mode": "true", "transit_date": "2026-07-11"} + ) + + assert cli["result_hash"] == expected["result_hash"] + assert rest["result_hash"] == expected["result_hash"] + assert rest["birth"]["node_mode"] == "true" + assert rest["calculation_contract"]["effective"]["node_mode"] == "true"