feat(upstream): merge a6f47abd engine, MCP, and orchestrator
Independent Staging Quality Gate / validate (push) Has been cancelled
Independent Staging Quality Gate / publish (push) Has been cancelled

Three-way-merge calculation modules and pl9-export into the product fork while keeping commercial API routes, Raman ayanamsa, and the consultation contract as a keypath superset.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Jesse_Chen
2026-09-03 20:28:57 +08:00
parent 45d132588f
commit f224146348
30 changed files with 17151 additions and 248 deletions
+118 -19
View File
@@ -300,6 +300,12 @@ _PLANET_DIGNITY_KNRAO = {
# Aquarius (sign 10): 传统主Saturn vs 共主Rahu — PyJHora用stronger_planet动态判定
# Scorpio (sign 7): 传统主Mars vs 共主Ketu — PyJHora用stronger_planet动态判定
_CHARA_DASHA_CO_LORD_SIGNS = {10, 7} # Aquarius, Scorpio 有共主争议
CHARA_DASHA_PROFILES = {
"kn_rao",
"goel_lagna_start",
"goel_gendered",
"goel_shastri_method2",
}
def _jaimini_planet_dignity_level(planet: str, sign_idx: int) -> int:
@@ -439,7 +445,12 @@ def _get_sign_lord_house(longitudes: Dict[str, float], sign_idx: int) -> int:
return _get_planet_house(longitudes, lord)
def _chara_dasha_duration_knrao(longitudes: Dict[str, float], sign_idx: int) -> int:
def _chara_dasha_duration_profile(
longitudes: Dict[str, float],
sign_idx: int,
*,
profile: str = "kn_rao",
) -> int:
"""
KN Rao Chara Dasha 大运时长计算 v6.1.12。
@@ -453,7 +464,31 @@ def _chara_dasha_duration_knrao(longitudes: Dict[str, float], sign_idx: int) ->
若宫主在所在宫位 ⟹ Exalted(+1)Debilitated(-1)
Mercury在Virgo/Gemini是own sign非exalted,不+1
"""
# 动态宫主判定:Aquarius→Saturn/Rahu比较,Scorpio→Mars/Ketu比较
if profile not in CHARA_DASHA_PROFILES:
raise ValueError(f"unsupported Chara Dasha profile: {profile}")
if profile == "goel_lagna_start":
lord = SIGN_LORDS[SIGNS[sign_idx]]
lord_house = _get_planet_house(longitudes, lord)
if lord_house == sign_idx:
return 12
return _count_rasis_forward(sign_idx, lord_house)
if profile == "goel_gendered":
lord = SIGN_LORDS[SIGNS[sign_idx]]
lord_house = _get_planet_house(longitudes, lord)
if lord_house == sign_idx:
return 12
if (lord_house - sign_idx) % 12 == 6:
return 10
if sign_idx % 2 == 0:
count = _count_rasis_forward(lord_house, sign_idx)
else:
count = _count_rasis_backward(lord_house, sign_idx)
return max(1, count - 1)
# KN Rao keeps Scorpio/Aquarius dual-lord separation; start/order rules are
# handled by the progression helper.
lord = _resolve_chara_dasha_lord(longitudes, sign_idx)
lord_house = _get_planet_house(longitudes, lord)
@@ -469,7 +504,9 @@ def _chara_dasha_duration_knrao(longitudes: Dict[str, float], sign_idx: int) ->
if years <= 0:
years = 12
# 尊贵调整(对齐PyJHora house_strengths_of_planets表)
# This remains the historical PyJHora-aligned local KN Rao profile. The
# source ledger keeps the no-extra-year K.N. Rao textual boundary visible
# for later formula reconciliation.
dignities = _PLANET_DIGNITY_KNRAO.get(lord, {})
if dignities:
if lord_house in dignities.get('exalted', set()):
@@ -480,7 +517,23 @@ def _chara_dasha_duration_knrao(longitudes: Dict[str, float], sign_idx: int) ->
return years
def _chara_progression_knrao(asc_sign_idx: int, longitudes: Dict[str, float]) -> list:
def _chara_dasha_duration_knrao(longitudes: Dict[str, float], sign_idx: int) -> int:
return _chara_dasha_duration_profile(longitudes, sign_idx, profile="kn_rao")
def _fourth_from_lagna_prakriti(asc_sign_idx: int) -> int:
if asc_sign_idx % 2 == 0:
return (asc_sign_idx + 3) % 12
return (asc_sign_idx - 3) % 12
def _chara_progression_profile(
asc_sign_idx: int,
longitudes: Dict[str, float],
*,
profile: str = "kn_rao",
gender: str | None = None,
) -> list:
"""
KN Rao Chara Dasha 星座序列生成。
@@ -489,11 +542,19 @@ def _chara_progression_knrao(asc_sign_idx: int, longitudes: Dict[str, float]) ->
2. 检查第9宫:若为偶数脚 → 逆向,否则正向
3. 生成12个星座的顺序
"""
if profile not in CHARA_DASHA_PROFILES:
raise ValueError(f"unsupported Chara Dasha profile: {profile}")
start_idx = asc_sign_idx
if profile == "goel_gendered" and (gender or "").lower().startswith("f"):
start_idx = _fourth_from_lagna_prakriti(asc_sign_idx)
ninth_idx = (asc_sign_idx + 8) % 12
if _sign_is_even_footed(ninth_idx):
return [(asc_sign_idx + 12 - i) % 12 for i in range(12)]
else:
return [(asc_sign_idx + i) % 12 for i in range(12)]
return [(start_idx + 12 - i) % 12 for i in range(12)]
return [(start_idx + i) % 12 for i in range(12)]
def _chara_progression_knrao(asc_sign_idx: int, longitudes: Dict[str, float]) -> list:
return _chara_progression_profile(asc_sign_idx, longitudes, profile="kn_rao")
def _jd_to_date_tuple(jd: float):
@@ -522,7 +583,9 @@ def _jd_to_date_tuple(jd: float):
def calc_chara_dasha(asc_sign_idx: int,
planet_longitudes: Dict[str, float],
birth_year: int, birth_month: int,
birth_day: int = 1) -> Dict:
birth_day: int = 1,
profile: str = "kn_rao",
gender: str | None = None) -> Dict:
"""
Chara Dasha计算(v6.1.11重写:KN Rao Method
@@ -534,13 +597,21 @@ def calc_chara_dasha(asc_sign_idx: int,
- 时长:基于宫主所在宫位而非行星计数
- 尊贵:Exalted +1年 / Debilitated -1年
"""
progression = _chara_progression_knrao(asc_sign_idx, planet_longitudes)
progression = _chara_progression_profile(
asc_sign_idx,
planet_longitudes,
profile=profile,
gender=gender,
)
dasha_sequence = []
for i, sign_idx in enumerate(progression):
sign_name = SIGNS[sign_idx]
lord = _resolve_chara_dasha_lord(planet_longitudes, sign_idx)
duration = _chara_dasha_duration_knrao(planet_longitudes, sign_idx)
if profile == "goel_lagna_start":
lord = SIGN_LORDS[sign_name]
else:
lord = _resolve_chara_dasha_lord(planet_longitudes, sign_idx)
duration = _chara_dasha_duration_profile(planet_longitudes, sign_idx, profile=profile)
# 宮主所在宫位
lord_house = _get_planet_house(planet_longitudes, lord)
@@ -549,7 +620,9 @@ def calc_chara_dasha(asc_sign_idx: int,
# 尊贵状态
dignities = _PLANET_DIGNITY_KNRAO.get(lord, {})
dignity_status = 'none'
if dignities:
if profile == "goel_lagna_start":
dignity_status = 'not_applied'
elif dignities:
exalted_set = dignities.get('exalted', set())
debil_set = dignities.get('debilitated', set())
if lord_house in exalted_set:
@@ -571,7 +644,10 @@ def calc_chara_dasha(asc_sign_idx: int,
total_years = sum(d['duration_years'] for d in dasha_sequence)
return {
'method': 'Chara Dasha (KN Rao Method, v6.1.11, PyJHora-aligned)',
'method': f'Chara Dasha ({profile})',
'variant_profile': profile,
'variant_profile_status': 'profiled',
'gender': gender,
'ascendant': SIGNS[asc_sign_idx],
'ascendant_idx': asc_sign_idx,
'progression_source': '9th_house_direction',
@@ -584,22 +660,45 @@ def calc_chara_dasha(asc_sign_idx: int,
def calc_chara_dasha_with_antardasha(asc_sign_idx: int,
planet_longitudes: Dict[str, float],
birth_year: int, birth_month: int,
birth_day: int = 1) -> Dict:
birth_day: int = 1,
profile: str = "kn_rao",
gender: str | None = None) -> Dict:
"""
Chara Dasha 完整3层计算(MD → AD → PD)
使用KN Rao方法(v6.1.11重写)。
Antardasha:等分法(parent/12),序列为Maha序列偏移1位。
"""
base = calc_chara_dasha(asc_sign_idx, planet_longitudes, birth_year, birth_month, birth_day)
progression = _chara_progression_knrao(asc_sign_idx, planet_longitudes)
# Antardasha序列:Mahadasha序列偏移1PyJHora method=2
antar_sequence = progression[1:] + progression[:1]
base = calc_chara_dasha(
asc_sign_idx,
planet_longitudes,
birth_year,
birth_month,
birth_day,
profile=profile,
gender=gender,
)
progression = _chara_progression_profile(
asc_sign_idx,
planet_longitudes,
profile=profile,
gender=gender,
)
for md in base['dasha_sequence']:
md_sign_idx = md['sign_idx']
md_duration_years = md['duration_years']
if profile == "goel_shastri_method2":
# Goel's Method 2 worked example gives the first sub-period to the
# mahadasha sign and then follows that sign's own direct/indirect order.
ninth_from_md = (md_sign_idx + 8) % 12
if _sign_is_even_footed(ninth_from_md):
antar_sequence = [(md_sign_idx + 12 - i) % 12 for i in range(12)]
else:
antar_sequence = [(md_sign_idx + i) % 12 for i in range(12)]
else:
# Antardasha序列:Mahadasha序列偏移1PyJHora method=2
antar_sequence = progression[1:] + progression[:1]
antardasha_list = []
for j, ad_sign_idx in enumerate(antar_sequence):