POST /api/annual with mode=package (or /api/annual/package) returns one versioned package built from the existing producers: year charts with ascendant/lord, Muntha, Panchadhikari year lord, annual strength, Sahams, Tajika yogas, Tripataki, Mudda, native Patyayini, Tajika monthly charts; natal Vimshottari MD/AD/PD and Narayana (legacy + Rath); slow-planet ingresses, stations, retrograde loops and natal-point crossings; Tithi Pravesha as an independent view. Report range, year-chart validity and each period system's own year are separate, every boundary is an exact instant with timezone, and every block carries computed/blocked/not_applicable with a reason. The default /api/annual response is unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DbBXoyeZrYn6ryNcSe5Y2Y
This commit is contained in:
File diff suppressed because it is too large
Load Diff
@@ -7733,6 +7733,8 @@ class JyotishComputeService(VedastroEvidenceMixin, SynastryMixin):
|
||||
}
|
||||
|
||||
def _compute_annual(self, body):
|
||||
if isinstance(body, dict) and body.get('mode') == 'package':
|
||||
return self._compute_annual_package(body)
|
||||
target_year = self._get_int(body, 'target_year', datetime.now().year, 1800, 2400)
|
||||
birth_dt = self._parse_birth_datetime(body)
|
||||
lat = self._get_float(body, 'lat', body.get('birth_lat', 0), -90, 90)
|
||||
@@ -7760,9 +7762,60 @@ class JyotishComputeService(VedastroEvidenceMixin, SynastryMixin):
|
||||
tz,
|
||||
target_year,
|
||||
ayanamsa_name=_request_ayanamsa(body), birth_second=birth_dt.second,
|
||||
node_mode=_request_node_mode(body),
|
||||
)
|
||||
return {'success': True, 'endpoint': 'annual', 'report': report}
|
||||
|
||||
def _compute_annual_package(self, body):
|
||||
"""Annual data package v1 (#152 ruling 2026-11-01): `mode: "package"` or /api/annual/package.
|
||||
|
||||
The default `/api/annual` response (basic solar-return report) keeps its shape.
|
||||
"""
|
||||
if not isinstance(body, dict):
|
||||
raise BadRequest('JSON body must be an object')
|
||||
birth_dt = self._parse_birth_datetime(body)
|
||||
lat = self._get_float(body, 'lat', body.get('birth_lat', 0), -90, 90)
|
||||
lon = self._get_float(body, 'lon', body.get('birth_lon', 0), -180, 180)
|
||||
tz = self._parse_timezone(
|
||||
body, lat, lon, birth_dt.year, birth_dt.month, birth_dt.day,
|
||||
birth_dt.hour, birth_dt.minute, birth_dt.second,
|
||||
)
|
||||
timezone_id = None
|
||||
for key in ('timezone_id', 'timezone'):
|
||||
value = body.get(key)
|
||||
if isinstance(value, str) and '/' in value:
|
||||
try:
|
||||
from zoneinfo import ZoneInfo
|
||||
ZoneInfo(value.strip())
|
||||
except Exception as exc: # noqa: BLE001 - an unknown IANA name is a bad request
|
||||
raise BadRequest(f'{key} is not a known IANA timezone') from exc
|
||||
timezone_id = value.strip()
|
||||
break
|
||||
package_mod = _load_local_module('annual_package')
|
||||
try:
|
||||
request = package_mod.normalize_request(
|
||||
birth={
|
||||
'year': birth_dt.year, 'month': birth_dt.month, 'day': birth_dt.day,
|
||||
'hour': birth_dt.hour, 'minute': birth_dt.minute, 'second': birth_dt.second,
|
||||
'lat': lat, 'lon': lon, 'tz': tz, 'timezone_id': timezone_id,
|
||||
},
|
||||
ayanamsa=_request_ayanamsa(body),
|
||||
node_mode=_request_node_mode(body),
|
||||
report_range=body.get('report_range'),
|
||||
observation_date=body.get('observation_date') or body.get('today'),
|
||||
return_location=body.get('return_location'),
|
||||
)
|
||||
except package_mod.AnnualPackageInputError as exc:
|
||||
raise BadRequest(str(exc)) from exc
|
||||
package = package_mod.build_annual_package(request)
|
||||
return {
|
||||
'success': True,
|
||||
'endpoint': 'annual',
|
||||
'mode': 'package',
|
||||
'annual_package_version': package['annual_package_version'],
|
||||
'package': package,
|
||||
}
|
||||
|
||||
def _compute_tajika(self, body):
|
||||
result = self._compute_annual(body)
|
||||
return {
|
||||
@@ -10644,6 +10697,9 @@ class JyotishAPIHandler(BaseHTTPRequestHandler, JyotishComputeService):
|
||||
elif path == '/api/annual':
|
||||
result = self._compute_annual(body)
|
||||
self._json(result)
|
||||
elif path == '/api/annual/package':
|
||||
result = self._compute_annual_package(body)
|
||||
self._json(result)
|
||||
elif path == '/api/tajika':
|
||||
result = self._compute_tajika(body)
|
||||
self._json(result)
|
||||
@@ -10960,6 +11016,7 @@ def start_server(port=5200, host='127.0.0.1', allowed_origins=None, allowed_host
|
||||
print(f' POST /api/career — 事业分析')
|
||||
print(f' POST /api/relationship — 感情分析')
|
||||
print(f' POST /api/annual — 年运/Tajika')
|
||||
print(f' POST /api/annual/package — 年度数据包 v1(同 /api/annual + mode=package)')
|
||||
print(f' POST /api/muhurta — 择日')
|
||||
print(f' POST /api/panchanga_range — Panchanga日期范围')
|
||||
print(f' POST /api/declared_window_chart — 声明出生窗口探针比较')
|
||||
|
||||
Reference in New Issue
Block a user