feat(annual): localise the solar-return instant, relocated returns, compact PL9 export (BUG-1095..1097)
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
This commit is contained in:
co-authored by
Claude Fable 5.1
parent
15be6a0631
commit
7c5ea7ae5a
+49
-7
@@ -15,7 +15,8 @@ Jyotish Vedic Astrology Skill — v6.0.18
|
||||
"""
|
||||
|
||||
from typing import Dict, Optional, Tuple, List
|
||||
from datetime import datetime, timedelta
|
||||
from datetime import datetime, timedelta, timezone
|
||||
from zoneinfo import ZoneInfo
|
||||
import math
|
||||
import sys
|
||||
import os
|
||||
@@ -261,6 +262,7 @@ def _find_solar_return_approx(
|
||||
birth_sun_lon: float,
|
||||
target_year: int,
|
||||
tz: float,
|
||||
timezone_id: str | None = None,
|
||||
) -> Dict:
|
||||
"""
|
||||
近似计算太阳返照时间(不使用 swisseph)。
|
||||
@@ -272,7 +274,7 @@ def _find_solar_return_approx(
|
||||
except ValueError:
|
||||
approx_dt_utc = datetime(target_year, birth_dt.month, 1, 12, 0, 0)
|
||||
|
||||
dt_local = approx_dt_utc + timedelta(hours=tz)
|
||||
dt_local = _localize_return_datetime(approx_dt_utc, tz, timezone_id).replace(tzinfo=None)
|
||||
|
||||
return {
|
||||
'jd_ut': _datetime_to_jd_ut(approx_dt_utc),
|
||||
@@ -291,12 +293,27 @@ def _find_solar_return_approx(
|
||||
# 生成完整太阳返照盘
|
||||
# =========================================================================
|
||||
|
||||
def _localize_return_datetime(dt_ut, utc_offset, timezone_id=None):
|
||||
"""Convert the return instant using the zone's offset at that instant.
|
||||
|
||||
With an IANA ``timezone_id`` the offset is taken from the zone rules at the
|
||||
return instant (DST-aware). Without one, the fixed ``utc_offset`` is used.
|
||||
Upstream yinduzhanxing 41d1c740.
|
||||
"""
|
||||
zone = ZoneInfo(timezone_id) if timezone_id else timezone(timedelta(hours=float(utc_offset)))
|
||||
return dt_ut.replace(tzinfo=timezone.utc).astimezone(zone)
|
||||
|
||||
|
||||
def calc_solar_return_chart(
|
||||
birth_year: int, birth_month: int, birth_day: int,
|
||||
birth_hour: int, birth_minute: int,
|
||||
birth_lat: float, birth_lon: float, birth_tz: float,
|
||||
target_year: int,
|
||||
ayanamsa_name: str = 'raman',
|
||||
local_timezone_id: str | None = None,
|
||||
return_lat: float | None = None,
|
||||
return_lon: float | None = None,
|
||||
return_tz: float | None = None,
|
||||
) -> Dict:
|
||||
"""
|
||||
计算太阳返照盘(Varshaphala)。
|
||||
@@ -325,6 +342,13 @@ def calc_solar_return_chart(
|
||||
# → 改为由调用方传入 birth_sun_lon,或从出生盘数据里取
|
||||
# 这里我们先做:调用 engine 的 compute_chart_data 算出生盘
|
||||
try:
|
||||
if (return_lat is None) != (return_lon is None):
|
||||
raise ValueError('return_location_incomplete')
|
||||
if return_lat is not None and return_tz is None and not local_timezone_id:
|
||||
raise ValueError('return_location_timezone_missing')
|
||||
annual_lat = float(birth_lat if return_lat is None else return_lat)
|
||||
annual_lon = float(birth_lon if return_lon is None else return_lon)
|
||||
annual_tz = float(birth_tz if return_tz is None else return_tz)
|
||||
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
||||
from jyotish_engine import compute_chart_data, HAS_SWE as ENGINE_HAS_SWE
|
||||
|
||||
@@ -337,7 +361,8 @@ def calc_solar_return_chart(
|
||||
birth_hour, birth_minute, 0)),
|
||||
0, # birth_sun_lon unknown
|
||||
target_year,
|
||||
birth_tz
|
||||
annual_tz,
|
||||
timezone_id=local_timezone_id,
|
||||
),
|
||||
}
|
||||
|
||||
@@ -368,6 +393,14 @@ def calc_solar_return_chart(
|
||||
sr_jd_ut = sr_result['jd_ut']
|
||||
sr_dt_ut = sr_result['dt_ut']
|
||||
|
||||
# 返照时刻本地化:按返照瞬间的时区偏移(IANA 区支持夏令时),而不是
|
||||
# 把 UT 直接当本地时间(旧行为 dt_local == dt_ut)。
|
||||
local_dt = _localize_return_datetime(sr_dt_ut, annual_tz, local_timezone_id)
|
||||
annual_tz = local_dt.utcoffset().total_seconds() / 3600
|
||||
sr_result['dt_local'] = local_dt.replace(tzinfo=None)
|
||||
sr_result['annual_location'] = {'latitude': annual_lat, 'longitude': annual_lon,
|
||||
'utc_offset': annual_tz, 'timezone_id': local_timezone_id}
|
||||
|
||||
# Step 3: 以返照时刻计算星盘
|
||||
# compute_chart_data 接受 year/month/day/hour/minute
|
||||
sr_year, sr_month, sr_day = sr_dt_ut.year, sr_dt_ut.month, sr_dt_ut.day
|
||||
@@ -378,7 +411,7 @@ def calc_solar_return_chart(
|
||||
sr_chart, sr_asc_idx, sr_jd, sr_ayanamsa = compute_chart_data(
|
||||
sr_year, sr_month, sr_day,
|
||||
sr_hour_int, sr_minute_int,
|
||||
birth_lat, birth_lon, 0, # UT 时间,时区=0
|
||||
annual_lat, annual_lon, 0, # UT 时间,时区=0
|
||||
'mean',
|
||||
ayanamsa_name=ayanamsa_name,
|
||||
)
|
||||
@@ -404,6 +437,8 @@ def calc_solar_return_chart(
|
||||
'birth_asc_sign_idx': birth_asc_idx,
|
||||
'target_year': target_year,
|
||||
'birth_year': birth_year,
|
||||
'annual_location': {'latitude': annual_lat, 'longitude': annual_lon,
|
||||
'utc_offset': annual_tz, 'timezone_id': local_timezone_id},
|
||||
'age': target_year - birth_year,
|
||||
'note': f'太阳返照盘 {target_year} 年(年龄 {target_year-birth_year} 岁)',
|
||||
}
|
||||
@@ -422,6 +457,10 @@ def solar_return_full_report(
|
||||
birth_lat: float, birth_lon: float, birth_tz: float,
|
||||
target_year: int,
|
||||
ayanamsa_name: str = 'raman',
|
||||
local_timezone_id: str | None = None,
|
||||
return_lat: float | None = None,
|
||||
return_lon: float | None = None,
|
||||
return_tz: float | None = None,
|
||||
) -> Dict:
|
||||
"""
|
||||
太阳返照盘完整报告(Varshaphala 年运分析)。
|
||||
@@ -449,6 +488,8 @@ def solar_return_full_report(
|
||||
birth_lat, birth_lon, birth_tz,
|
||||
target_year,
|
||||
ayanamsa_name=ayanamsa_name,
|
||||
local_timezone_id=local_timezone_id,
|
||||
return_lat=return_lat, return_lon=return_lon, return_tz=return_tz,
|
||||
)
|
||||
|
||||
if 'error' in sr:
|
||||
@@ -469,6 +510,7 @@ def solar_return_full_report(
|
||||
}
|
||||
else:
|
||||
result['solar_return'] = sr['solar_return']
|
||||
result['annual_location'] = dict(sr.get('annual_location') or {})
|
||||
result['sr_chart_info'] = {
|
||||
'asc_sign': SIGNS[sr['asc_sign_idx']],
|
||||
'asc_sign_idx': sr['asc_sign_idx'],
|
||||
@@ -549,9 +591,9 @@ def solar_return_full_report(
|
||||
asc_lon,
|
||||
sr_dt_local,
|
||||
chart_type='varsha',
|
||||
lat=birth_lat,
|
||||
lon=birth_lon,
|
||||
tz=birth_tz,
|
||||
lat=sr.get('annual_location', {}).get('latitude', birth_lat),
|
||||
lon=sr.get('annual_location', {}).get('longitude', birth_lon),
|
||||
tz=sr.get('annual_location', {}).get('utc_offset', birth_tz),
|
||||
)
|
||||
result['sahams'] = sahams
|
||||
except Exception as e:
|
||||
|
||||
Reference in New Issue
Block a user