Skip to content

Commit 2b3e535

Browse files
committed
11
1 parent fb66757 commit 2b3e535

12 files changed

Lines changed: 374 additions & 28 deletions

File tree

astro/annual/adapters/qimen.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
"""Qimen Lu Ming adapter for solar-return annual charts."""
2+
3+
from __future__ import annotations
4+
5+
from datetime import datetime
6+
from typing import Any
7+
8+
9+
def compute_qimen_at_sr(
10+
sr_local: datetime,
11+
*,
12+
method: int = 1,
13+
) -> dict[str, Any]:
14+
from astro.sanshi.qimen_luming import compute_qimen_luming
15+
16+
return compute_qimen_luming(
17+
year=sr_local.year,
18+
month=sr_local.month,
19+
day=sr_local.day,
20+
hour=sr_local.hour,
21+
minute=sr_local.minute,
22+
method=method,
23+
)

astro/annual/adapters/taiyi.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
"""Taiyi adapter for solar-return annual charts."""
2+
3+
from __future__ import annotations
4+
5+
from datetime import datetime
6+
from typing import Any
7+
8+
9+
def compute_taiyi_at_sr(
10+
sr_local: datetime,
11+
timezone: float,
12+
gender: str,
13+
) -> dict[str, Any]:
14+
from astro.sanshi.taiyi import compute_taiyi_chart
15+
16+
taiyi_gender = "male" if gender in ("male", "男", "M") else "female"
17+
return compute_taiyi_chart(
18+
year=sr_local.year,
19+
month=sr_local.month,
20+
day=sr_local.day,
21+
hour=sr_local.hour,
22+
minute=sr_local.minute,
23+
gender=taiyi_gender,
24+
timezone=timezone,
25+
)

astro/annual/models.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,8 @@ class AnnualFlowYear(KinAstroModel):
123123
liuren_chart: dict[str, Any] = Field(default_factory=dict)
124124
liuren_jixiong: LiurenJixiong | None = None
125125
liuren_lunming: dict[str, Any] = Field(default_factory=dict)
126+
taiyi_chart: dict[str, Any] = Field(default_factory=dict)
127+
qimen_chart: dict[str, Any] = Field(default_factory=dict)
126128
other_chinese_systems: dict[str, ChineseSystemSnapshot] = Field(default_factory=dict)
127129

128130
system_scores: dict[str, float] = Field(default_factory=dict)
@@ -149,8 +151,8 @@ def _validate_range(cls, value: int, info) -> int:
149151
start = info.data.get("start_year")
150152
if start is not None and value < start:
151153
raise ValueError("end_year must be >= start_year")
152-
if start is not None and value - start > 60:
153-
raise ValueError("year range cannot exceed 60 years")
154+
if start is not None and value - start > 100:
155+
raise ValueError("year range cannot exceed 100 years")
154156
return value
155157

156158

astro/annual/render/__init__.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
11
"""Render helpers for annual solar-return timelines."""
22

3+
from astro.annual.render.liuren_sr_subtab import render_liuren_sr_annual_subtab
34
from astro.annual.render.pdf_annual_report import generate_annual_sr_pdf
45
from astro.annual.render.streamlit_timeline import render_annual_sr_timeline_panel
56

6-
__all__ = ["generate_annual_sr_pdf", "render_annual_sr_timeline_panel"]
7+
__all__ = [
8+
"generate_annual_sr_pdf",
9+
"render_annual_sr_timeline_panel",
10+
"render_liuren_sr_annual_subtab",
11+
]
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
"""Liuren solar-return annual sub-tab: lazy chart per virtual age (1–120)."""
2+
3+
from __future__ import annotations
4+
5+
from datetime import datetime
6+
7+
import streamlit as st
8+
9+
from astro.annual.natal_context import virtual_age
10+
from astro.annual.timeline import compute_sr_liuren_for_virtual_age
11+
from astro.models import BirthData
12+
from astro.sanshi.liuren import render_liuren_chart, render_lunming_report
13+
from core.cached_computations import _birth_sig, compute_sr_liuren_age_cached
14+
from ui.helpers import t
15+
16+
MAX_VIRTUAL_AGE = 120
17+
18+
19+
def _sr_year_for_age(birth_year: int, age: int) -> int:
20+
return birth_year + age - 1
21+
22+
23+
def render_liuren_sr_annual_subtab(birth: BirthData, benming_zhi: str) -> None:
24+
"""Render on-demand solar-return Liu Ren charts for virtual ages 1–120."""
25+
st.caption(
26+
"依出生地計算每年太陽回歸精確時刻,以該時刻起大六壬課式與祿命論年。"
27+
"僅在您選定虛歲並按「排盤」後才計算該年,不會一次跑完 120 年。"
28+
)
29+
30+
ages = list(range(1, MAX_VIRTUAL_AGE + 1))
31+
current_age = min(max(virtual_age(birth.year, datetime.now().year), 1), MAX_VIRTUAL_AGE)
32+
default_index = current_age - 1
33+
34+
col_age, col_btn = st.columns([4, 1])
35+
with col_age:
36+
selected_age = st.selectbox(
37+
"虛歲",
38+
options=ages,
39+
index=default_index,
40+
format_func=lambda age: (
41+
f"虛歲 {age} 歲(SR {_sr_year_for_age(birth.year, age)} 年)"
42+
),
43+
key="liuren_sr_age_select",
44+
)
45+
with col_btn:
46+
st.write("")
47+
st.write("")
48+
run_chart = st.button("排盤", type="primary", key="liuren_sr_compute_btn")
49+
50+
sr_year = _sr_year_for_age(birth.year, selected_age)
51+
birth_sig = _birth_sig(birth.to_compute_kwargs())
52+
cache_slot = f"liuren_sr::{birth_sig}::{selected_age}"
53+
54+
if run_chart:
55+
with st.spinner(t("spinner_liuren_sr_annual")):
56+
flow_year = compute_sr_liuren_age_cached(birth_sig, selected_age, benming_zhi)
57+
st.session_state["liuren_sr_active_slot"] = cache_slot
58+
st.session_state[cache_slot] = flow_year
59+
60+
active_slot = st.session_state.get("liuren_sr_active_slot")
61+
flow_year = st.session_state.get(cache_slot) if active_slot == cache_slot else None
62+
63+
if flow_year is None:
64+
st.info(f"請選擇虛歲(1–{MAX_VIRTUAL_AGE}),再按「排盤」以顯示該年太陽回歸六壬盤。")
65+
return
66+
67+
st.markdown(
68+
f"**虛歲 {selected_age} 歲|SR {sr_year} 年|太陽回歸:** "
69+
f"`{flow_year.exact_sr_datetime_local.strftime('%Y-%m-%d %H:%M:%S')}`"
70+
)
71+
if flow_year.sr_day_gz or flow_year.sr_hour_gz:
72+
st.caption(
73+
f"日干支 {flow_year.sr_day_gz or '—'} "
74+
f"時干支 {flow_year.sr_hour_gz or '—'} "
75+
f"流年 {flow_year.liunian_gz or '—'}"
76+
)
77+
78+
if flow_year.liuren_chart:
79+
render_liuren_chart(
80+
flow_year.liuren_chart,
81+
benming_zhi=benming_zhi,
82+
)
83+
if flow_year.liuren_lunming:
84+
render_lunming_report(flow_year.liuren_lunming)

astro/annual/timeline.py

Lines changed: 45 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66
from typing import Iterable
77

88
from astro.annual.adapters.liuren import compute_liuren_at_sr
9+
from astro.annual.adapters.qimen import compute_qimen_at_sr
10+
from astro.annual.adapters.taiyi import compute_taiyi_at_sr
911
from astro.annual.models import (
1012
AnnualFlowYear,
1113
ReturnLocation,
@@ -33,7 +35,7 @@
3335
from astro.western.western import compute_western_chart
3436
from astro.ziwei import compute_ziwei_chart
3537

36-
SUPPORTED_SYSTEMS = {"western", "liuren", "ziwei", "bazi", "qizheng"}
38+
SUPPORTED_SYSTEMS = {"western", "liuren", "ziwei", "bazi", "qizheng", "taiyi", "qimen"}
3739
CAUTION_THRESHOLD = 45.0
3840

3941

@@ -192,10 +194,47 @@ def _compute_single_year(
192194
flow_year.other_chinese_systems["qizheng"] = snapshot
193195
flow_year.system_scores["qizheng"] = snapshot.score
194196

197+
if "taiyi" in include_systems:
198+
gender = birth.legacy_gender or "male"
199+
flow_year.taiyi_chart = compute_taiyi_at_sr(
200+
sr_local,
201+
return_location.timezone,
202+
gender,
203+
)
204+
205+
if "qimen" in include_systems:
206+
flow_year.qimen_chart = compute_qimen_at_sr(sr_local)
207+
195208
flow_year.integrated_interpretation = _build_integrated_summary(flow_year)
196209
return flow_year
197210

198211

212+
def compute_sr_liuren_for_virtual_age(
213+
birth_data: BirthData,
214+
virtual_age_years: int,
215+
*,
216+
return_location: ReturnLocation | None = None,
217+
benming_zhi: str | None = None,
218+
age_kind: str = "virtual",
219+
) -> AnnualFlowYear:
220+
"""Compute a single solar-return Liu Ren year for the given virtual age."""
221+
if virtual_age_years < 1 or virtual_age_years > 120:
222+
raise ValueError("virtual age must be between 1 and 120")
223+
sr_year = birth_data.year + virtual_age_years - 1
224+
timeline = compute_solar_return_flowyear_timeline(
225+
birth_data,
226+
sr_year,
227+
sr_year,
228+
return_location=return_location,
229+
include_systems=["liuren"],
230+
benming_zhi=benming_zhi,
231+
age_kind=age_kind,
232+
)
233+
if not timeline.years:
234+
raise ValueError(f"no solar-return year computed for virtual age {virtual_age_years}")
235+
return timeline.years[0]
236+
237+
199238
def compute_solar_return_flowyear_timeline(
200239
birth_data: BirthData,
201240
start_year: int,
@@ -287,6 +326,11 @@ def compute_solar_return_flowyear_timeline(
287326
f"共 {len(years)} 年,大六壬平均 {avg:.1f} 分;"
288327
f"最高 {max(liuren_scores):.0f},最低 {min(liuren_scores):.0f}。"
289328
)
329+
elif years and systems <= {"liuren", "taiyi", "qimen"}:
330+
trend_summary = (
331+
f"共 {len(years)} 個太陽回歸時刻,"
332+
f"自 {years[0].year}{years[-1].year} 年(虛歲 {years[0].age}{years[-1].age})。"
333+
)
290334

291335
return SolarReturnTimeline(
292336
request=request,

astro/data/i18n_translations.json

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3479,6 +3479,15 @@
34793479
"th": "**Qimen Destiny Analysis**\n\nThis method uses the Qi Men Dun Jia chart cast at the time of birth to analyze one's destiny. The year stem's palace becomes the Life Palace, and the Five Elements relationships with other stems reveal the Six Relations (parents, siblings, children, career, spouse/wealth, health), combined with Eight Doors, Eight Gods, and pattern analysis for a comprehensive life reading.",
34803480
"zh_cn": "**Qimen Destiny Analysis**\n\nThis method uses the Qi Men Dun Jia chart cast at the time of birth to analyze one's destiny. The year stem's palace becomes the Life Palace, and the Five Elements relationships with other stems reveal the Six Relations (parents, siblings, children, career, spouse/wealth, health), combined with Eight Doors, Eight Gods, and pattern analysis for a comprehensive life reading."
34813481
},
3482+
"desc_sr_sanshi": {
3483+
"en": "**Sanshi Solar Return Annual Charts**\n\nFrom your birth year to the present, each year's exact solar return moment at your birth location is used to cast Da Liu Ren, Taiyi Life Method, and Qimen Destiny charts — one set of Three Formulae boards per return year.",
3484+
"zh": "**三式太陽回歸流年**\n\n依您輸入的出生年月日時分與出生地,逐年計算太陽回歸的精確時刻(預設於出生地觀測),並以該時刻起盤:\n\n- **大六壬**:太陽回歸課式 + 祿命論年\n- **太乙命法**:以回歸時刻排太乙命盤\n- **奇門祿命**:以回歸時刻排奇門局\n\n可一次瀏覽自出生年至今年的全部流年盤,或自訂年份區間。",
3485+
"ko": "**Sanshi Solar Return Annual Charts**\n\nFrom your birth year to the present, each year's exact solar return moment at your birth location is used to cast Da Liu Ren, Taiyi Life Method, and Qimen Destiny charts — one set of Three Formulae boards per return year.",
3486+
"ja": "**Sanshi Solar Return Annual Charts**\n\nFrom your birth year to the present, each year's exact solar return moment at your birth location is used to cast Da Liu Ren, Taiyi Life Method, and Qimen Destiny charts — one set of Three Formulae boards per return year.",
3487+
"vi": "**Sanshi Solar Return Annual Charts**\n\nFrom your birth year to the present, each year's exact solar return moment at your birth location is used to cast Da Liu Ren, Taiyi Life Method, and Qimen Destiny charts — one set of Three Formulae boards per return year.",
3488+
"th": "**Sanshi Solar Return Annual Charts**\n\nFrom your birth year to the present, each year's exact solar return moment at your birth location is used to cast Da Liu Ren, Taiyi Life Method, and Qimen Destiny charts — one set of Three Formulae boards per return year.",
3489+
"zh_cn": "**三式太阳回归流年**\n\n依您输入的出生年月日时分与出生地,逐年计算太阳回归的精确时刻(默认于出生地观测),并以该时刻起盘:大六壬、太乙命法、奇门禄命。可一次浏览自出生年至今年的全部流年盘。"
3490+
},
34823491
"desc_rectification": {
34833492
"en": "🔮 **Birth Chart Rectification** — Combines Primary Directions, Solar Arcs, Secondary Progressions, Annual Profections, Zodiacal Releasing, and Transits with weighted scoring to rank candidate birth times. Based on Vettius Valens' Anthology and Lilly's Christian Astrology.",
34843493
"zh": "🔮 **出生時間校正** — 結合初級方向(Primary Directions)、太陽弧(Solar Arcs)、次進法(Secondary Progressions)、流年小限(Annual Profections)、黃道釋放(Zodiacal Releasing)及行星過境(Transits)六大技術,以加權評分算法對候選出生時間進行排名。依據 Vettius Valens《Anthology》、William Lilly《Christian Astrology》等古典文獻。",
@@ -6791,6 +6800,15 @@
67916800
"th": "Computing Da Liu Ren chart…",
67926801
"zh_cn": "正在计算大六壬排盤…"
67936802
},
6803+
"spinner_liuren_sr_annual": {
6804+
"en": "Computing solar-return Liu Ren chart for selected age…",
6805+
"zh": "正在計算所選虛歲的太陽回歸六壬盤…",
6806+
"ko": "Computing solar-return Liu Ren chart for selected age…",
6807+
"ja": "Computing solar-return Liu Ren chart for selected age…",
6808+
"vi": "Computing solar-return Liu Ren chart for selected age…",
6809+
"th": "Computing solar-return Liu Ren chart for selected age…",
6810+
"zh_cn": "正在计算所选虚岁的太阳回归六壬盘…"
6811+
},
67946812
"spinner_liuyao_lifetime": {
67956813
"en": "Computing Lifetime Liu Yao hexagram…",
67966814
"zh": "計算六爻終身卦…",
@@ -6932,6 +6950,15 @@
69326950
"th": "Computing Qimen Destiny…",
69336951
"zh_cn": "正在计算奇門祿命…"
69346952
},
6953+
"spinner_sr_sanshi": {
6954+
"en": "Computing solar-return annual charts for Liu Ren, Taiyi, and Qimen…",
6955+
"zh": "正在計算三式太陽回歸流年盤(大六壬、太乙、奇門)…",
6956+
"ko": "Computing solar-return annual charts for Liu Ren, Taiyi, and Qimen…",
6957+
"ja": "Computing solar-return annual charts for Liu Ren, Taiyi, and Qimen…",
6958+
"vi": "Computing solar-return annual charts for Liu Ren, Taiyi, and Qimen…",
6959+
"th": "Computing solar-return annual charts for Liu Ren, Taiyi, and Qimen…",
6960+
"zh_cn": "正在计算三式太阳回归流年盘(大六壬、太乙、奇门)…"
6961+
},
69356962
"spinner_rectification": {
69366963
"en": "Running rectification, please wait…",
69376964
"zh": "校正計算中,請稍候……",
@@ -7736,6 +7763,15 @@
77367763
"th": "Qimen Dunjia destiny analysis based on birth chart",
77377764
"zh_cn": "Qimen Dunjia destiny analysis based on birth chart"
77387765
},
7766+
"sys_hint_sr_sanshi": {
7767+
"en": "Annual Liu Ren, Taiyi, and Qimen charts at each solar return",
7768+
"zh": "自出生年起,每年太陽回歸時刻起大六壬、太乙、奇門流年盤",
7769+
"ko": "Annual Liu Ren, Taiyi, and Qimen charts at each solar return",
7770+
"ja": "Annual Liu Ren, Taiyi, and Qimen charts at each solar return",
7771+
"vi": "Annual Liu Ren, Taiyi, and Qimen charts at each solar return",
7772+
"th": "Annual Liu Ren, Taiyi, and Qimen charts at each solar return",
7773+
"zh_cn": "自出生年起,每年太阳回归时刻起大六壬、太乙、奇门流年盘"
7774+
},
77397775
"sys_hint_rectification": {
77407776
"en": "Multi-technique classical rectification: Primary Dirs · Solar Arcs · Progressions",
77417777
"zh": "多技術古典校正:初級方向 · 太陽弧 · 次進法 · 小限 · 黃道釋放",
@@ -8534,6 +8570,15 @@
85348570
"th": "🔮 Qimen Destiny",
85358571
"zh_cn": "🔮 Qimen Destiny"
85368572
},
8573+
"tab_sr_sanshi": {
8574+
"en": "🌞 Sanshi Solar Return Years",
8575+
"zh": "🌞 三式太陽回歸流年",
8576+
"ko": "🌞 Sanshi Solar Return Years",
8577+
"ja": "🌞 Sanshi Solar Return Years",
8578+
"vi": "🌞 Sanshi Solar Return Years",
8579+
"th": "🌞 Sanshi Solar Return Years",
8580+
"zh_cn": "🌞 三式太阳回归流年"
8581+
},
85378582
"tab_rectification": {
85388583
"en": "🔮 Rectification",
85398584
"zh": "🔮 出生時間校正",
@@ -9823,6 +9868,24 @@
98239868
"th": "Daxian / Liunian",
98249869
"zh_cn": "Daxian / Liunian"
98259870
},
9871+
"liuren_subtab_natal": {
9872+
"en": "Natal Destiny",
9873+
"zh": "本命祿命",
9874+
"ko": "Natal Destiny",
9875+
"ja": "Natal Destiny",
9876+
"vi": "Natal Destiny",
9877+
"th": "Natal Destiny",
9878+
"zh_cn": "本命禄命"
9879+
},
9880+
"liuren_subtab_sr_annual": {
9881+
"en": "Solar Return Years",
9882+
"zh": "太陽回歸流年",
9883+
"ko": "Solar Return Years",
9884+
"ja": "Solar Return Years",
9885+
"vi": "Solar Return Years",
9886+
"th": "Solar Return Years",
9887+
"zh_cn": "太阳回归流年"
9888+
},
98269889
"ziwei_subtab_natal": {
98279890
"en": "Natal",
98289891
"zh": "本命盤",

astro/system_registry.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -226,9 +226,14 @@ def _reg(system: System) -> System:
226226
desc_key="desc_liuren",
227227
spinner_key="spinner_liuren",
228228
hint_key="sys_hint_liuren",
229+
sub_tabs=[
230+
SubTab("liuren_subtab_natal", "natal"),
231+
SubTab("liuren_subtab_sr_annual", "solar_return"),
232+
],
229233
tags=["六壬", "三式", "liuren", "divination", "chinese", "timekeeping"],
230234
maturity="core",
231235
accent_color=_a("cat_sanshi"),
236+
supports_return=True,
232237
origin_culture="Chinese",
233238
tradition_period="Han Dynasty",
234239
ai_persona_key="info_liuren_prompt",
@@ -270,7 +275,6 @@ def _reg(system: System) -> System:
270275
ai_persona_key="info_qimen_luming_prompt",
271276
))
272277

273-
274278
# ═════════════════════════════════════════════════════════════════════════════
275279
# cat_chinese — 中華傳統 🏮
276280
# ═════════════════════════════════════════════════════════════════════════════

0 commit comments

Comments
 (0)