diff --git a/scripts/jyotish_api_server.py b/scripts/jyotish_api_server.py index 863f9367..65843e1b 100644 --- a/scripts/jyotish_api_server.py +++ b/scripts/jyotish_api_server.py @@ -7674,11 +7674,18 @@ class JyotishComputeService(VedastroEvidenceMixin, SynastryMixin): return {} def _compute_aspects(self, body): - _, planet_lons = self._planet_lons_from_body(body) + planets, planet_lons = self._planet_lons_from_body(body) if len(planet_lons) < 2: raise BadRequest('planets must include at least two longitude values') asc_lon = self._asc_lon_from_body(body) - result = _load_local_module('aspects').calc_all_aspects(planet_lons, asc_lon) + planet_speeds = { + name: float(row['speed']) + for name, row in (planets or {}).items() + if isinstance(row, dict) and isinstance(row.get('speed'), (int, float)) + } + result = _load_local_module('aspects').calc_all_aspects( + planet_lons, asc_lon, planet_speeds=planet_speeds or None, + ) return { 'success': True, 'endpoint': 'aspects', diff --git a/scripts/jyotish_engine.py b/scripts/jyotish_engine.py index 166d34c0..1fa1c27c 100644 --- a/scripts/jyotish_engine.py +++ b/scripts/jyotish_engine.py @@ -14376,11 +14376,19 @@ def cmd_aspects(args): return {"error": f"aspects模块导入失败: {e}"} planets = chart.get('planets', {}) planet_lons = {} + planet_speeds = {} for pn, pd in planets.items(): if isinstance(pd, dict) and 'degree' in pd: planet_lons[pn] = pd['degree'] + if isinstance(pd, dict) and isinstance(pd.get('speed'), (int, float)): + planet_speeds[pn] = float(pd['speed']) asc_deg = chart.get('ascendant', {}).get('lon', chart.get('ascendant', {}).get('degree', 0)) - return _build_response_envelope('aspects', calc_all_aspects(planet_lons, asc_deg), args=args, execution_status='executed') + return _build_response_envelope( + 'aspects', + calc_all_aspects(planet_lons, asc_deg, planet_speeds=planet_speeds or None), + args=args, + execution_status='executed', + ) # ============================================================================ @@ -16235,7 +16243,12 @@ def cmd_full_reading(args): stage_started = time.perf_counter() try: from aspects import calc_all_aspects, calc_house_aspects - aspects_result = calc_all_aspects(planet_lons, asc_deg) + aspect_speeds = { + name: float(row['speed']) + for name, row in (chart.get('planets') or {}).items() + if isinstance(row, dict) and isinstance(row.get('speed'), (int, float)) + } + aspects_result = calc_all_aspects(planet_lons, asc_deg, planet_speeds=aspect_speeds or None) aspects_result['house_aspects'] = [ calc_house_aspects(float(planet_lons[planet]), planet, asc_deg) for planet in ('Sun', 'Moon', 'Mars', 'Mercury', 'Jupiter', 'Venus', 'Saturn') diff --git a/tests/test_upstream_sync9_aspect_geometry_regressions.py b/tests/test_upstream_sync9_aspect_geometry_regressions.py new file mode 100644 index 00000000..0a9ff587 --- /dev/null +++ b/tests/test_upstream_sync9_aspect_geometry_regressions.py @@ -0,0 +1,45 @@ +"""Directed drishti geometry and actual relative-motion regressions.""" +import sys +from pathlib import Path +import pytest + +sys.path.insert(0, str(Path(__file__).resolve().parents[1] / 'scripts')) +from aspects import calc_all_aspects + + +@pytest.mark.parametrize('planet,house,angle', [ + ('Mars', 4, 90), ('Mars', 8, 210), ('Jupiter', 5, 120), + ('Jupiter', 9, 240), ('Saturn', 3, 60), ('Saturn', 10, 270), +]) +def test_directed_special_aspect_survives_pair_order(planet, house, angle): + for positions in ({planet: 350, 'Venus': (350 + angle) % 360}, + {'Venus': (350 + angle) % 360, planet: 350}): + rows = calc_all_aspects(positions, 150)['aspects'] + row = next(r for r in rows if r['type'] == f'{planet}_aspect_{house}') + assert (row['planet1'], row['planet2'], row['aspect_degree'], row['orb']) == (planet, 'Venus', angle, 0) + + +def test_mars_reverse_fourth_is_not_fourth(): + rows = calc_all_aspects({'Mars': 0, 'Venus': 270}, 150)['aspects'] + assert not any(r['type'] == 'Mars_aspect_4' for r in rows) + + +@pytest.mark.parametrize('speed,expected', [(1, False), (-1, True)]) +def test_phase_uses_relative_velocity(speed, expected): + row = calc_all_aspects({'Sun': 359, 'Mercury': 1}, 180, + planet_speeds={'Sun': 0, 'Mercury': speed})['aspects'][0] + assert row['applying'] is expected + + +def test_missing_speed_is_unknown_not_a_direction_guess(): + assert calc_all_aspects({'Sun': 10, 'Moon': 11}, 180)['aspects'][0]['applying'] is None + + +def test_explicit_ketu_without_rahu_is_retained(): + rows = calc_all_aspects({'Sun': 10, 'Ketu': 190}, 100)['aspects'] + assert len(rows) == 1 and rows[0]['type'] == 'opposition' + + +def test_exact_node_opposition_has_no_applying_direction(): + row = calc_all_aspects({'Rahu': 10}, 100, planet_speeds={'Rahu': -.05})['aspects'][0] + assert row['phase'] == 'exact' and row['applying'] is None