#!/usr/bin/env python3 """Regression: API float hour/minute must not crash consultation_workflow.""" from __future__ import annotations from datetime import datetime import os import sys import pytest SCRIPTS = os.path.join(os.path.dirname(__file__), "..", "scripts") if SCRIPTS not in sys.path: sys.path.insert(0, SCRIPTS) import flexible_birth_time_profile as profile_module # noqa: E402 from jyotish_engine import _birth_datetime_from_args # noqa: E402 from scripts.consultation_domain_registry import CANONICAL_DOMAINS # noqa: E402 from scripts.jyotish_api_server import JyotishAPIHandler, _load_local_module # noqa: E402 _SMOKE_BIRTH = { "year": 1993, "month": 6, "day": 15, "hour": 10.0, "minute": 30.0, "lat": 36.42, "lon": 114.21, "tz": 8, } def _handler() -> JyotishAPIHandler: return JyotishAPIHandler.__new__(JyotishAPIHandler) def _stub_consultation_runtime(monkeypatch, handler: JyotishAPIHandler) -> None: chart = { "success": True, "birth_info": {"date": "1993-06-15", "time": "10:30", "tz": 8}, "planets": {}, "ascendant": {}, "modules": {}, "ai_prompt_pack": { "evidence_snapshot": { "strict_workflow_contracts": { domain: {"status": "available", "domain": domain} for domain in CANONICAL_DOMAINS } } }, } monkeypatch.setattr(handler, "_compute_chart", lambda body: chart) monkeypatch.setattr( handler, "_compute_rectification_gate", lambda body: { "success": True, "endpoint": "rectification_gate", "summary": {"recommended_events": [], "warned": [], "disabled": []}, }, ) monkeypatch.setattr( handler, "_compute_muhurta_panchanga", lambda body: {"status": "ok", "scope": "muhurta_panchanga"}, ) monkeypatch.setattr( handler, "_compute_vedastro_gateway_run", lambda body: { "scope": "vedastro_gateway_run", "status": "official_blocked", "official_closure_state": "official_blocked", "official_closure_reason": "test_stub", }, ) monkeypatch.setattr( profile_module, "_recast_candidate_layers", lambda candidate, **_kwargs: { "ascendant": {"sign": "Aries" if candidate.minute < 30 else "Taurus"}, "varga_lagna": {"D9": {"sign": "Gemini"}, "D10": {"sign": "Cancer"}}, "arudha": {"A7": {"sign": "Leo"}, "A10": {"sign": "Virgo"}, "UL": {"sign": "Libra"}}, "kp_cusps": {"house_10": {"sub_lord": "Saturn"}}, }, ) def _provisional_body(theme: str) -> dict: return { "entry_mode": "direct_chart", "question": theme, "theme": [theme], **_SMOKE_BIRTH, "birth_time_accuracy": "provisional", "representative_time": "10:30", "western_mode": False, "defer_optional_external_evidence": True, } def test_quality_gate_runs_this_file() -> None: from scripts.run_quality_gate import CORE_PYTEST_TARGETS assert "tests/test_consultation_workflow_birth_time_sensitivity.py" in CORE_PYTEST_TARGETS def test_high_rigor_birth_payload_keeps_hour_minute_as_float() -> None: payload = _handler()._high_rigor_birth_payload(_SMOKE_BIRTH) assert isinstance(payload["hour"], float) assert isinstance(payload["minute"], float) assert payload["hour"] == 10.0 assert payload["minute"] == 30.0 def test_birth_datetime_from_args_accepts_float_clock_fields() -> None: args = type("Args", (), { "year": 1993, "month": 6, "day": 15, "hour": 10.0, "minute": 30.0, "second": 0.0, })() assert _birth_datetime_from_args(args) == datetime(1993, 6, 15, 10, 30, 0) @pytest.mark.parametrize("theme", ["career", "marriage", "wealth", "timing", "health"]) def test_consultation_workflow_accepts_float_hour_minute_with_provisional_accuracy( monkeypatch, theme: str, ) -> None: handler = _handler() _stub_consultation_runtime(monkeypatch, handler) result = handler._compute_consultation_workflow(_provisional_body(theme)) assert result["success"] is True packet = result["birth_time_sensitivity"] assert packet["schema"] == "jyotish.report_birth_time_sensitivity.v1" assert packet["status"] == "candidate_window_only" assert packet["accuracy"] == "provisional" assert packet["window"]["representative_time"] == "10:30" def test_consultation_workflow_without_sensitivity_fields_still_succeeds(monkeypatch) -> None: handler = _handler() _stub_consultation_runtime(monkeypatch, handler) result = handler._compute_consultation_workflow({ "entry_mode": "direct_chart", "question": "career", "theme": ["career"], **_SMOKE_BIRTH, "western_mode": False, "defer_optional_external_evidence": True, }) assert result["success"] is True packet = result["birth_time_sensitivity"] assert packet["schema"] == "jyotish.report_birth_time_sensitivity.v1" assert packet["status"] in {"not_applicable", "candidate_window_only"} def test_consultation_workflow_degrades_sensitivity_failure_instead_of_raising(monkeypatch) -> None: handler = _handler() _stub_consultation_runtime(monkeypatch, handler) engine = _load_local_module("jyotish_engine") def _boom(_args): raise TypeError("'float' object cannot be interpreted as an integer") monkeypatch.setattr(engine, "_build_birth_time_sensitivity", _boom) result = handler._compute_consultation_workflow(_provisional_body("career")) assert result["success"] is True assert result["birth_time_sensitivity"] == { "schema": "jyotish.report_birth_time_sensitivity.v1", "status": "not_applicable", "availability": "not_available", "blocked": True, "reason": "birth_time_sensitivity_unavailable", "error_type": "TypeError", }