from __future__ import annotations import sys from datetime import datetime 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)) import domain_calculation_service as calculation_service # noqa: E402 import jyotish_api_server # noqa: E402 from jyotish_api_server import JyotishAPIHandler # 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", "node_mode": "true", } def test_domain_chart_exposes_effective_parameters_and_result_hash() -> None: from domain_calculation_service import compute_chart result = compute_chart(BIRTH) assert result["calculation_contract"]["effective"]["node_mode"] == "true" assert result["calculation_contract"]["effective"]["ayanamsa"] == "lahiri" assert result["result_hash"] def test_historical_timezone_resolution_distinguishes_new_york_dst() -> None: winter = calculation_service.resolve_timezone_context( lat=40.7128, lon=-74.006, local_datetime=datetime(1990, 1, 15, 12, 0), ) summer = calculation_service.resolve_timezone_context( lat=40.7128, lon=-74.006, local_datetime=datetime(1990, 7, 15, 12, 0), ) assert winter == { "timezone_id": "America/New_York", "timezone_offset": -5.0, "local_time_status": "resolved", } assert summer["timezone_id"] == "America/New_York" assert summer["timezone_offset"] == -4.0 def test_timezone_resolution_refuses_dst_fold_and_gap_offsets() -> None: folded = calculation_service.resolve_timezone_context( lat=40.7128, lon=-74.006, local_datetime=datetime(2020, 11, 1, 1, 30), ) gap = calculation_service.resolve_timezone_context( lat=40.7128, lon=-74.006, local_datetime=datetime(2020, 3, 8, 2, 30), ) assert folded["timezone_offset"] is None assert folded["local_time_status"] == "ambiguous" assert gap["timezone_offset"] is None assert gap["local_time_status"] == "nonexistent" def test_domain_chart_exposes_inferred_timezone_id() -> None: payload = {key: value for key, value in BIRTH.items() if key != "tz"} result = calculation_service.compute_chart(payload) assert result["calculation_contract"]["effective"]["timezone_id"] == "Asia/Kolkata" assert result["calculation_contract"]["effective"]["timezone_source"] == "iana_inferred" def test_location_timezone_api_contract_preserves_unknown_local_time() -> None: result = jyotish_api_server.resolve_location_timezone_payload({ "latitude": 51.5074, "longitude": -0.1278, "birthDate": "1990-01-01", }) assert result["timezoneId"] == "Europe/London" assert result["timezoneOffset"] is None assert result["localTimeStatus"] == "not_provided" def test_api_chart_uses_same_domain_contract_and_preserves_shape( monkeypatch: pytest.MonkeyPatch, ) -> None: import domain_calculation_service import jyotish_api_server from jyotish_api_server import JyotishAPIHandler 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 = domain_calculation_service.compute_chart(BIRTH) result = JyotishAPIHandler.__new__(JyotishAPIHandler)._compute_chart_sync( {**BIRTH, "transit_date": "2026-07-11"} ) assert result["result_hash"] == expected["result_hash"] assert result["calculation_contract"] == expected["calculation_contract"] assert result["birth"]["node_mode"] == "true" assert result["planets"] assert result["ascendant"] assert "houses" in result def test_domain_chart_exposes_effective_params_and_result_hash() -> None: mean = calculation_service.compute_chart({**BIRTH, "node_mode": "mean"}) true = calculation_service.compute_chart({**BIRTH, "node_mode": "true"}) assert mean["calculation_contract"]["effective"]["ayanamsa"] == "lahiri" assert true["calculation_contract"]["effective"]["node_mode"] == "true" assert mean["planets"]["Rahu"]["lon"] != pytest.approx(true["planets"]["Rahu"]["lon"], abs=1e-8) assert mean["result_hash"] != true["result_hash"] def test_api_chart_response_uses_domain_contract_hash(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"}) rest = JyotishAPIHandler.__new__(JyotishAPIHandler)._compute_chart_sync( {**BIRTH, "node_mode": "true", "transit_date": "2026-07-11"} ) assert rest["result_hash"] == expected["result_hash"] assert rest["birth"]["node_mode"] == "true" assert rest["calculation_contract"]["effective"]["node_mode"] == "true" def test_api_visible_chart_values_come_from_domain_service(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, ) request = {**BIRTH, "node_mode": "true", "transit_date": "2026-07-11"} expected = calculation_service.compute_chart(request) rest = JyotishAPIHandler.__new__(JyotishAPIHandler)._compute_chart_sync(request) assert rest["ascendant"]["lon"] == pytest.approx(expected["ascendant"]["lon"], abs=1e-8) for planet in ("Sun", "Moon", "Rahu", "Ketu"): assert rest["planets"][planet]["sign"] == expected["planets"][planet]["sign"] assert rest["planets"][planet]["lon"] == pytest.approx( expected["planets"][planet]["lon"], abs=1e-8 ) def test_api_sade_sati_uses_domain_true_saturn_transit(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, ) request = {**BIRTH, "node_mode": "true", "transit_date": "2026-07-11"} chart = calculation_service.compute_chart(request) expected = calculation_service.compute_sade_sati( moon_degree=chart["planets"]["Moon"]["lon"], asc_degree=chart["ascendant"]["lon"], reference_date="2026-07-11", tz=BIRTH["tz"], ayanamsa=BIRTH["ayanamsa"], ) rest = JyotishAPIHandler.__new__(JyotishAPIHandler)._compute_chart_sync(request) assert rest["sade_sati"]["transit_saturn_lon"] == pytest.approx( expected["transit_saturn_lon"], abs=1e-8 ) assert rest["sade_sati"]["provenance"]["data_layer"] == "true_transit_positions" assert rest["sade_sati"]["calculation_contract"]["algorithm"] == "sade_sati_true_saturn_transit" def test_api_dasha_boundary_comes_from_domain_service(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, ) request = {**BIRTH, "node_mode": "true", "transit_date": "2026-07-11"} chart = calculation_service.compute_chart(request) expected = calculation_service.compute_vimshottari_timeline( birth_dt=datetime(1990, 1, 1, 12, 0), moon_lon=chart["planets"]["Moon"]["lon"], ) rest = JyotishAPIHandler.__new__(JyotishAPIHandler)._compute_chart_sync(request) assert rest["dasha"]["current_md"] == expected["birth_balance"]["lord"] assert rest["dasha"]["remaining_years"] == pytest.approx( expected["birth_balance"]["remaining_years"], abs=1e-8 ) assert rest["dasha"]["start_date"] == expected["periods"][0]["start"] assert rest["dasha"]["result_hash"] == expected["result_hash"]