fix(aspects): pass planet speed into the three production callers (BUG-1479, upstream e628118d)

aspects.py itself is in d226b0e53 because it was already staged. Ketu and applying/separating behavior are covered by the geometry test. Callers pass numeric speeds, or None when speed is absent. calc_house_aspects does not take speed.

Co-Authored-By: Grok (agent loop)
This commit is contained in:
jesse-ux
2026-10-28 09:45:39 +08:00
co-authored by Grok (agent loop)
parent bd09485243
commit 8aed605a43
3 changed files with 69 additions and 4 deletions
+9 -2
View File
@@ -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',
+15 -2
View File
@@ -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')
@@ -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