Upstream yinduzhanxing 41d1c740 / 59c9139f, hand-merged; 089858c0 verified not applicable. - solar_return: dt_local was the UT instant verbatim (no caller ever added the offset). Localise with ZoneInfo at the return instant (IANA id) or the fixed offset; expose annual_location; Sahams day/night at the true instant; optional current_location return charts. - cmd_solar_return / annual_tajika_pack / engine: pass birth IANA zone and --solar-return-location-mode / --current-* through; external PyJHora replay blocked when relocated. - output_json keeps the upstream stream= signature; PL9 exports written compact via dumps (measured faster than json.dump streaming), 9.57 -> 4.61 MB. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017eEAG8HD3mm8gsKXgk8uU8
105 lines
4.2 KiB
Python
105 lines
4.2 KiB
Python
#!/usr/bin/env python3
|
|
# -*- coding: utf-8 -*-
|
|
"""
|
|
cmd_solar_return: Solar Return (Varshaphala) CLI 子命令
|
|
Jyotish Vedic Astrology Skill — v6.0.18
|
|
|
|
用法:
|
|
python3 jyotish_engine.py solar-return --year REDACTED_YEAR --month 4 --day 17 \
|
|
--hour 14 --minute 45 --lat 36.4667 --lon 114.2 --tz 8 --target-year 2026
|
|
"""
|
|
|
|
import sys
|
|
import os
|
|
from typing import Dict, Any
|
|
|
|
|
|
def cmd_solar_return(args: Any) -> Dict[str, Any]:
|
|
"""
|
|
solar-return 子命令:计算太阳返照盘 + 完整 Varshaphala 分析。
|
|
|
|
参数 (来自 args):
|
|
year, month, day, hour, minute: 出生时间
|
|
lat, lon, tz: 出生地点/时区
|
|
target_year: 目标年份(计算该年太阳返照)
|
|
|
|
返回:
|
|
dict: solar_return_full_report() 的结果
|
|
"""
|
|
try:
|
|
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
|
from ayanamsa_utils import DEFAULT_AYANAMSA_NAME
|
|
from solar_return import solar_return_full_report
|
|
except ImportError as e:
|
|
return {"error": f"solar_return 模块导入失败: {e}"}
|
|
|
|
if not hasattr(args, 'target_year') or args.target_year is None:
|
|
return {"error": "solar-return 需要 --target-year 参数",
|
|
"hint": "指定目标年份,如 --target-year 2026"}
|
|
|
|
return solar_return_full_report(
|
|
args.year, args.month, args.day,
|
|
args.hour, args.minute,
|
|
args.lat, args.lon, args.tz,
|
|
args.target_year,
|
|
ayanamsa_name=getattr(args, 'ayanamsa', None) or DEFAULT_AYANAMSA_NAME,
|
|
**solar_return_location_kwargs(args),
|
|
)
|
|
|
|
|
|
def solar_return_location_kwargs(args: Any) -> Dict[str, Any]:
|
|
"""Return-location kwargs for ``solar_return_full_report``.
|
|
|
|
Two arg shapes are accepted: the annual-pack namespace (``return_lat`` /
|
|
``return_lon`` / ``return_tz`` / ``return_timezone_id``, built by
|
|
``annual_tajika_pack._args_from_payload``) and the CLI namespace
|
|
(``--solar-return-location-mode`` / ``--current-*`` / ``--timezone-id``).
|
|
Upstream yinduzhanxing 41d1c740.
|
|
"""
|
|
if getattr(args, 'return_lat', None) is not None or getattr(args, 'return_timezone_id', None):
|
|
return {
|
|
'local_timezone_id': getattr(args, 'return_timezone_id', None),
|
|
'return_lat': getattr(args, 'return_lat', None),
|
|
'return_lon': getattr(args, 'return_lon', None),
|
|
'return_tz': getattr(args, 'return_tz', None),
|
|
}
|
|
mode = getattr(args, 'solar_return_location_mode', None) or 'birth_place'
|
|
if mode == 'current_location':
|
|
if any(getattr(args, key, None) is None for key in ('current_lat', 'current_lon', 'current_tz')):
|
|
raise ValueError('current_location requires current_lat, current_lon and current_tz')
|
|
return {
|
|
'local_timezone_id': getattr(args, 'current_timezone_id', None),
|
|
'return_lat': float(args.current_lat),
|
|
'return_lon': float(args.current_lon),
|
|
'return_tz': float(args.current_tz),
|
|
}
|
|
# CLI passes ``--timezone-id``; API payloads reach here as ``timezone`` /
|
|
# ``timezone_name`` (calculation_profile_contract keys). Only IANA ids count.
|
|
for key in ('timezone_id', 'timezone', 'timezone_name'):
|
|
value = getattr(args, key, None)
|
|
if isinstance(value, str) and '/' in value:
|
|
return {'local_timezone_id': value.strip()}
|
|
return {'local_timezone_id': None}
|
|
|
|
|
|
if __name__ == '__main__':
|
|
# 独立测试入口
|
|
import argparse
|
|
from datetime import datetime
|
|
|
|
parser = argparse.ArgumentParser(description='Solar Return 测试')
|
|
parser.add_argument('--year', type=int, required=True)
|
|
parser.add_argument('--month', type=int, required=True)
|
|
parser.add_argument('--day', type=int, required=True)
|
|
parser.add_argument('--hour', type=int, default=12)
|
|
parser.add_argument('--minute', type=int, default=0)
|
|
parser.add_argument('--lat', type=float, required=True)
|
|
parser.add_argument('--lon', type=float, required=True)
|
|
parser.add_argument('--tz', type=float, default=0.0)
|
|
parser.add_argument('--target-year', type=int, required=True)
|
|
a = parser.parse_args()
|
|
|
|
result = cmd_solar_return(a)
|
|
import json
|
|
print(json.dumps(result, ensure_ascii=False, indent=2, default=str))
|