fix(avastha): keep simultaneous conditions and use the PVR activity formula
Upstream e628118d, 7d545039, f75a6e77, and ac565836. Dignity follows the sign, Deeptadi keeps every condition that applies, and Shayanadi hours come from the previous Hindu sunrise. BUG-1389. Co-Authored-By: Grok (agent loop) Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
co-authored by
Grok (agent loop)
Cursor
parent
0bd254cf18
commit
83c97a8976
+160
-46
@@ -19,6 +19,60 @@ Avastha Calculator - 行星状态计算器
|
||||
|
||||
from typing import Dict, List, Tuple
|
||||
from enum import Enum
|
||||
import json
|
||||
import math
|
||||
from bisect import bisect_right
|
||||
from pathlib import Path
|
||||
|
||||
with (Path(__file__).resolve().parents[1] / 'references' / 'shat_bala_constants.json').open(encoding='utf-8') as _handle:
|
||||
NATURAL_RELATIONSHIPS = json.load(_handle)['natural_relationships']
|
||||
SIGNS = ['Aries', 'Taurus', 'Gemini', 'Cancer', 'Leo', 'Virgo',
|
||||
'Libra', 'Scorpio', 'Sagittarius', 'Capricorn', 'Aquarius', 'Pisces']
|
||||
|
||||
|
||||
def calculate_pvr_shayanadi(planet, longitude, moon_longitude, asc_longitude, hours_since_sunrise):
|
||||
"""PVR §15.4.4/Table 36; running ghati, not elapsed fractional ghatis.
|
||||
|
||||
Activity intensity requires the name's Sanskrit first-sound index and is
|
||||
deliberately not guessed from an English display name.
|
||||
"""
|
||||
order = ('Sun', 'Moon', 'Mars', 'Mercury', 'Jupiter', 'Venus', 'Saturn', 'Rahu', 'Ketu')
|
||||
if planet not in order:
|
||||
raise ValueError('Unsupported Shayanadi planet')
|
||||
values = (longitude, moon_longitude, asc_longitude, hours_since_sunrise)
|
||||
if any(isinstance(value, bool) or not math.isfinite(float(value)) for value in values):
|
||||
raise ValueError('Finite numeric Shayanadi inputs required')
|
||||
if hours_since_sunrise < 0:
|
||||
raise ValueError('Use the preceding sunrise, not a future sunrise')
|
||||
longitude, moon_longitude, asc_longitude = (float(value) % 360 for value in values[:3])
|
||||
boundaries = [i * 40 / 3 for i in range(1, 27)]
|
||||
c = bisect_right(boundaries, longitude) + 1
|
||||
p = order.index(planet) + 1
|
||||
a = bisect_right([i * 10 / 3 for i in range(1, 9)], longitude % 30) + 1
|
||||
m = bisect_right(boundaries, moon_longitude) + 1
|
||||
g = math.floor(hours_since_sunrise * 2.5) + 1
|
||||
l = int(asc_longitude // 30) + 1
|
||||
total = c * p * a + m + g + l
|
||||
index = (total - 1) % 12 + 1
|
||||
states = ('Shayana', 'Upaveshana', 'Netrapani', 'Prakashana', 'Gamana', 'Agamana',
|
||||
'Sabha', 'Agama', 'Bhojana', 'Nrityalipsa', 'Kautuka', 'Nidra')
|
||||
return {
|
||||
'state': states[index - 1],
|
||||
'state_index': index,
|
||||
'sign': SIGNS[int(longitude // 30)],
|
||||
'meaning': 'Activity state from the PVR constellation, navamsa and running-ghati formula.',
|
||||
'method_status': 'pvr_15_4_4_running_ghati',
|
||||
'components': {
|
||||
'planet_nakshatra': c,
|
||||
'planet_index': p,
|
||||
'navamsa_part': a,
|
||||
'moon_nakshatra': m,
|
||||
'running_ghati': g,
|
||||
'lagna_sign_number': l,
|
||||
'formula_sum': total,
|
||||
},
|
||||
'activity_intensity_status': 'requires_name_first_sound_index',
|
||||
}
|
||||
|
||||
class BalaAvastha(Enum):
|
||||
"""年龄状态(5种生命阶段)"""
|
||||
@@ -39,11 +93,12 @@ class DeeptadiAvastha(Enum):
|
||||
DEEPTA = "Deepta" # 光辉(Exalted)
|
||||
SWASTHA = "Swastha" # 舒适(Own Sign)
|
||||
MUDITA = "Mudita" # 快乐(Friend's Sign)
|
||||
SHANTA = "Shanta" # 平静(Neutral Sign)
|
||||
DINA = "Dina" # 沮丧(Enemy Sign)
|
||||
VIKALA = "Vikala" # 残缺(Debilitated)
|
||||
KHALA = "Khala" # 恶意(Combust)
|
||||
KOPA = "Kopa" # 愤怒(Defeated in War)
|
||||
SHANTA = "Shanta" # 平静(Friend's Sign)
|
||||
DINA = "Dina" # 低落(Neutral Sign)
|
||||
DUHKHITA = "Duhkhita" # 困苦(Enemy Sign)
|
||||
VIKALA = "Vikala" # 残缺(Joined by malefics)
|
||||
KHALA = "Khala" # 负担(Debilitated; Science of Light)
|
||||
KOPA = "Kopa" # 愤怒(Combust)
|
||||
GARVITA = "Garvita" # 骄傲(Victorious in War)
|
||||
KSHUDITA = "Kshudita" # 饥饿(In Sandhi)
|
||||
TRUSHITA = "Trushita" # 口渴(In Gandanta)
|
||||
@@ -127,7 +182,7 @@ class AvasthaCalculator:
|
||||
def calculate_all_avasthas(self, planet: str, sign: str, degree: float,
|
||||
house: int, conjunctions: List[str] = None,
|
||||
aspects: List[str] = None, is_combust: bool = False,
|
||||
is_retrograde: bool = False) -> Dict[str, any]:
|
||||
is_retrograde: bool = False, activity_context: Dict = None) -> Dict[str, any]:
|
||||
"""
|
||||
计算行星的所有Avastha状态
|
||||
|
||||
@@ -153,7 +208,7 @@ class AvasthaCalculator:
|
||||
}
|
||||
|
||||
# 1. Bala Avastha(年龄状态)
|
||||
result["avasthas"]["Bala"] = self._calculate_bala_avastha(degree)
|
||||
result["avasthas"]["Bala"] = self._calculate_bala_avastha(degree, sign)
|
||||
|
||||
# 2. Jagrat Avastha(警觉状态)
|
||||
result["avasthas"]["Jagrat"] = self._calculate_jagrat_avastha(planet, sign, house)
|
||||
@@ -170,7 +225,25 @@ class AvasthaCalculator:
|
||||
|
||||
# 5. Shayanadi Avastha(姿态状态)
|
||||
result["avasthas"]["Shayanadi"] = self._calculate_shayanadi_avastha(planet, sign)
|
||||
|
||||
for name in ('Deeptadi', 'Lajjitadi', 'Shayanadi'):
|
||||
result['avasthas'][name]['method_status'] = 'simplified_heuristic'
|
||||
if activity_context is not None:
|
||||
result['avasthas']['Shayanadi'] = calculate_pvr_shayanadi(
|
||||
planet,
|
||||
SIGNS.index(sign) * 30 + degree,
|
||||
activity_context['moon_longitude'],
|
||||
activity_context['asc_longitude'],
|
||||
activity_context['hours_since_sunrise'],
|
||||
)
|
||||
result['inputs'] = {
|
||||
'conjunctions': conjunctions or [],
|
||||
'aspects': aspects or [],
|
||||
'is_combust': is_combust,
|
||||
'is_retrograde': is_retrograde,
|
||||
}
|
||||
if activity_context is not None:
|
||||
result['inputs']['activity_context'] = dict(activity_context)
|
||||
|
||||
# 6. 综合解读
|
||||
result["interpretation"] = self._generate_interpretation(result["avasthas"])
|
||||
|
||||
@@ -179,11 +252,17 @@ class AvasthaCalculator:
|
||||
|
||||
return result
|
||||
|
||||
def _calculate_bala_avastha(self, degree: float) -> Dict:
|
||||
def _calculate_bala_avastha(self, degree: float, sign: str = 'Aries') -> Dict:
|
||||
"""计算Bala Avastha(年龄状态)"""
|
||||
if not 0 <= degree < 30:
|
||||
raise ValueError('Avastha degree must be within [0, 30)')
|
||||
original_degree = degree
|
||||
even_sign = SIGNS.index(sign) % 2 == 1
|
||||
band = int(degree // 6)
|
||||
degree = (4 - band if even_sign else band) * 6
|
||||
if degree < 6:
|
||||
state = BalaAvastha.INFANT
|
||||
strength = 0.2
|
||||
strength = 0.25
|
||||
meaning = "婴儿期:行星力量微弱,潜力未开发,需要时间成长"
|
||||
elif degree < 12:
|
||||
state = BalaAvastha.YOUNG
|
||||
@@ -195,40 +274,46 @@ class AvasthaCalculator:
|
||||
meaning = "青年期:行星力量最强,充满活力,能够充分发挥作用"
|
||||
elif degree < 24:
|
||||
state = BalaAvastha.OLD
|
||||
strength = 0.7
|
||||
strength = None
|
||||
meaning = "老年期:行星力量开始衰退,经验丰富但活力下降"
|
||||
else:
|
||||
state = BalaAvastha.DEAD
|
||||
strength = 0.3
|
||||
strength = 0.0
|
||||
meaning = "死亡期:行星力量极弱,难以发挥作用,需要补救"
|
||||
|
||||
|
||||
return {
|
||||
"state": state.value,
|
||||
"degree_range": f"{int(degree//6)*6}-{int(degree//6)*6+6}°",
|
||||
"degree_range": f"{int(original_degree//6)*6}-{int(original_degree//6)*6+6}°",
|
||||
"strength": strength,
|
||||
"result_grade": 'negligible' if state == BalaAvastha.OLD else 'fraction',
|
||||
"sign_parity": 'even' if even_sign else 'odd',
|
||||
"method_status": 'bphs_age_sign_parity',
|
||||
"meaning": meaning
|
||||
}
|
||||
|
||||
def _calculate_jagrat_avastha(self, planet: str, sign: str, house: int) -> Dict:
|
||||
"""计算Jagrat Avastha(警觉状态)"""
|
||||
# 简化规则:
|
||||
# Awake: 在角宫(1,4,7,10)
|
||||
# Dreaming: 在续宫(2,5,8,11)
|
||||
# Deep Sleep: 在果宫(3,6,9,12)
|
||||
|
||||
if house in [1, 4, 7, 10]:
|
||||
if planet not in self.PLANET_SIGNS:
|
||||
return {
|
||||
'state': 'Not classified',
|
||||
'house': house,
|
||||
'method_status': 'node_dignity_not_assigned',
|
||||
'meaning': '交点未指定尊贵度流派,不套用七曜警觉状态规则',
|
||||
}
|
||||
if self._is_own_sign(planet, sign) or self._is_exalted(planet, sign, 0):
|
||||
state = JagratAvastha.AWAKE
|
||||
meaning = "清醒状态:行星完全活跃,能够主动发挥作用,影响显著"
|
||||
elif house in [2, 5, 8, 11]:
|
||||
elif not self._is_enemy_sign(planet, sign) and not self._is_debilitated(planet, sign, 0):
|
||||
state = JagratAvastha.DREAMING
|
||||
meaning = "梦境状态:行星半活跃,作用不稳定,时强时弱"
|
||||
else:
|
||||
state = JagratAvastha.DEEP_SLEEP
|
||||
meaning = "深睡状态:行星不活跃,难以发挥作用,影响微弱"
|
||||
|
||||
|
||||
return {
|
||||
"state": state.value,
|
||||
"house": house,
|
||||
"method_status": 'bphs_dignity_natural_relationships',
|
||||
"meaning": meaning
|
||||
}
|
||||
|
||||
@@ -236,29 +321,50 @@ class AvasthaCalculator:
|
||||
is_combust: bool, aspects: List[str] = None) -> Dict:
|
||||
"""计算Deeptadi Avastha(情绪状态)"""
|
||||
aspects = aspects or []
|
||||
|
||||
# 检查入庙/落陷
|
||||
if planet not in self.PLANET_SIGNS:
|
||||
return {
|
||||
'state': 'Not classified',
|
||||
'meaning': '该口径未定义此天体的状态',
|
||||
'aspect_effects': [],
|
||||
'label_profile': 'science_of_light_deeptadi_natural_relationship_subset',
|
||||
'claim_boundary': 'Classical seven-planet profile; no nodal dignity is assumed.',
|
||||
}
|
||||
conditions = {
|
||||
'exalted': self._is_exalted(planet, sign, degree),
|
||||
'debilitated': self._is_debilitated(planet, sign, degree),
|
||||
'combust': bool(is_combust),
|
||||
'own_sign': self._is_own_sign(planet, sign),
|
||||
'natural_friend_sign': self._is_friend_sign(planet, sign),
|
||||
'natural_enemy_sign': self._is_enemy_sign(planet, sign),
|
||||
}
|
||||
applicable_states = [label for key, label in (
|
||||
('exalted', 'Deepta'), ('debilitated', 'Khala'), ('combust', 'Kopa'),
|
||||
('own_sign', 'Swastha'), ('natural_friend_sign', 'Shanta'),
|
||||
('natural_enemy_sign', 'Duhkhita')) if conditions[key]]
|
||||
if not any(conditions[key] for key in ('own_sign', 'natural_friend_sign', 'natural_enemy_sign')):
|
||||
applicable_states.append('Dina')
|
||||
# 单标签顺序是兼容字段,无经典出处。并存条件全部留在 applicable_states。
|
||||
if self._is_exalted(planet, sign, degree):
|
||||
state = DeeptadiAvastha.DEEPTA
|
||||
meaning = "光辉状态:行星处于最佳位置,能量充沛,带来积极结果"
|
||||
elif self._is_debilitated(planet, sign, degree):
|
||||
state = DeeptadiAvastha.VIKALA
|
||||
meaning = "残缺状态:行星处于最弱位置,能量受损,带来挑战"
|
||||
elif is_combust:
|
||||
state = DeeptadiAvastha.KHALA
|
||||
meaning = "恶意状态:行星被太阳燃烧,能量被压制,难以正常发挥"
|
||||
meaning = "负担状态:行星落陷;采用 Science of Light 的 Khala 定义"
|
||||
elif is_combust:
|
||||
state = DeeptadiAvastha.KOPA
|
||||
meaning = "愤怒状态:行星受太阳燃烧;对应 Kopa"
|
||||
elif self._is_own_sign(planet, sign):
|
||||
state = DeeptadiAvastha.SWASTHA
|
||||
meaning = "舒适状态:行星在自己的星座,感觉自在,能够稳定发挥"
|
||||
elif self._is_friend_sign(planet, sign):
|
||||
state = DeeptadiAvastha.MUDITA
|
||||
meaning = "快乐状态:行星在友好星座,得到支持,能够良好发挥"
|
||||
elif self._is_enemy_sign(planet, sign):
|
||||
state = DeeptadiAvastha.DINA
|
||||
meaning = "沮丧状态:行星在敌对星座,受到压制,发挥受限"
|
||||
else:
|
||||
state = DeeptadiAvastha.SHANTA
|
||||
meaning = "平静状态:行星在中性星座,不强不弱,平稳发挥"
|
||||
meaning = "平静状态:行星在自然友星的星座;对应 Shanta"
|
||||
elif self._is_enemy_sign(planet, sign):
|
||||
state = DeeptadiAvastha.DUHKHITA
|
||||
meaning = "困苦状态:行星在自然敌星的星座;对应 Duhkhita"
|
||||
else:
|
||||
state = DeeptadiAvastha.DINA
|
||||
meaning = "低落状态:行星在自然中性星的星座;对应 Dina"
|
||||
|
||||
# 检查相位影响
|
||||
aspect_effects = []
|
||||
@@ -274,7 +380,15 @@ class AvasthaCalculator:
|
||||
return {
|
||||
"state": state.value,
|
||||
"meaning": meaning,
|
||||
"aspect_effects": aspect_effects
|
||||
"aspect_effects": aspect_effects,
|
||||
"label_profile": "science_of_light_deeptadi_natural_relationship_subset",
|
||||
"conditions": conditions,
|
||||
"applicable_states": applicable_states,
|
||||
"legacy_single_label_note": "无经典出处",
|
||||
"uncomputed_conditions": ["great_friend", "malefic_conjunction"],
|
||||
"uncomputed_label": "未计算",
|
||||
"source": "Science of Light, source p475; Khala is debility in this profile",
|
||||
"claim_boundary": "Single-label priority is a legacy heuristic. Natural friendship only; great-friend and simultaneous conjunction states are not fully calculated. Khala follows Science of Light debility, not the alternative PVR malefic-sign definition.",
|
||||
}
|
||||
|
||||
def _calculate_lajjitadi_avastha(self, planet: str, sign: str, house: int,
|
||||
@@ -357,14 +471,14 @@ class AvasthaCalculator:
|
||||
"""检查是否入庙"""
|
||||
if planet in self.EXALTATION:
|
||||
exalt_sign, exalt_degree = self.EXALTATION[planet]
|
||||
return sign == exalt_sign and abs(degree - exalt_degree) < 5
|
||||
return sign == exalt_sign
|
||||
return False
|
||||
|
||||
def _is_debilitated(self, planet: str, sign: str, degree: float) -> bool:
|
||||
"""检查是否落陷"""
|
||||
if planet in self.DEBILITATION:
|
||||
debil_sign, debil_degree = self.DEBILITATION[planet]
|
||||
return sign == debil_sign and abs(degree - debil_degree) < 5
|
||||
return sign == debil_sign
|
||||
return False
|
||||
|
||||
def _is_own_sign(self, planet: str, sign: str) -> bool:
|
||||
@@ -372,14 +486,14 @@ class AvasthaCalculator:
|
||||
return sign in self.PLANET_SIGNS.get(planet, [])
|
||||
|
||||
def _is_friend_sign(self, planet: str, sign: str) -> bool:
|
||||
"""检查是否在友好星座(简化版)"""
|
||||
# 这里需要完整的行星友好关系表,暂时简化
|
||||
return False
|
||||
"""检查是否在自然友星星座。"""
|
||||
lord = next((name for name, signs in self.PLANET_SIGNS.items() if sign in signs), None)
|
||||
return lord in NATURAL_RELATIONSHIPS.get(planet, {}).get('friends', [])
|
||||
|
||||
def _is_enemy_sign(self, planet: str, sign: str) -> bool:
|
||||
"""检查是否在敌对星座(简化版)"""
|
||||
# 这里需要完整的行星敌对关系表,暂时简化
|
||||
return False
|
||||
"""检查是否在自然敌星星座。"""
|
||||
lord = next((name for name, signs in self.PLANET_SIGNS.items() if sign in signs), None)
|
||||
return lord in NATURAL_RELATIONSHIPS.get(planet, {}).get('enemies', [])
|
||||
|
||||
def _is_enemy_planet(self, planet1: str, planet2: str) -> bool:
|
||||
"""检查两个行星是否敌对(简化版)"""
|
||||
@@ -437,7 +551,7 @@ class AvasthaCalculator:
|
||||
|
||||
# 根据Bala Avastha
|
||||
bala_strength = avasthas["Bala"]["strength"]
|
||||
if bala_strength < 0.5:
|
||||
if bala_strength is not None and bala_strength < 0.5:
|
||||
remedies.append(f"行星处于弱势年龄阶段,建议佩戴{planet}对应的宝石")
|
||||
remedies.append(f"在{planet}主管的日子进行布施和祈祷")
|
||||
|
||||
@@ -447,7 +561,7 @@ class AvasthaCalculator:
|
||||
|
||||
# 根据Deeptadi Avastha
|
||||
deeptadi_state = avasthas["Deeptadi"]["state"]
|
||||
if deeptadi_state in ["Vikala", "Khala", "Dina"]:
|
||||
if deeptadi_state in ["Vikala", "Khala", "Duhkhita", "Kopa"]:
|
||||
remedies.append(f"行星处于{deeptadi_state}状态,需要特别补救")
|
||||
remedies.append(f"念诵{planet}的Beeja Mantra(种子咒)")
|
||||
|
||||
|
||||
@@ -16369,6 +16369,15 @@ def cmd_full_reading(args):
|
||||
# ── Step 10.5: Avasthas 行星状态 (v4.4.0) ──
|
||||
try:
|
||||
from avastha_calculator import AvasthaCalculator
|
||||
from saham_daynight import determine_daytime
|
||||
avastha_solar_day = determine_daytime(
|
||||
_birth_datetime_from_args(args), lat=args.lat, lon=args.lon, tz=args.tz, rising='hindu')
|
||||
hours_since_sunrise = (avastha_solar_day['julian_day_ut'] - avastha_solar_day['sunrise_jd_ut']) * 24
|
||||
avastha_activity_context = {
|
||||
'moon_longitude': planet_lons['Moon'],
|
||||
'asc_longitude': asc_deg,
|
||||
'hours_since_sunrise': hours_since_sunrise,
|
||||
}
|
||||
avast_calc = AvasthaCalculator()
|
||||
avast_result = {}
|
||||
for pn, pd in planets.items():
|
||||
@@ -16381,7 +16390,10 @@ def cmd_full_reading(args):
|
||||
if isinstance(pd2, dict) and pd2.get('house') == house and p2 != pn]
|
||||
avast_result[pn] = avast_calc.calculate_all_avasthas(
|
||||
planet=pn, sign=sign, degree=deg_in_sign,
|
||||
house=house, conjunctions=conjunctions)
|
||||
house=house, conjunctions=conjunctions,
|
||||
is_combust=bool(pd.get('combust', False)),
|
||||
is_retrograde=bool(pd.get('retrograde', False)),
|
||||
activity_context=avastha_activity_context)
|
||||
report['modules']['avasthas'] = avast_result
|
||||
except Exception as e:
|
||||
report['errors'].append(f"avasthas: {e}")
|
||||
|
||||
@@ -1442,6 +1442,9 @@ def render_pl9_parity_markdown(packet: dict) -> str:
|
||||
continue
|
||||
def _state(name: str) -> str:
|
||||
state = _dict(states.get(name))
|
||||
applicable = state.get('applicable_states') if name == 'Deeptadi' else None
|
||||
if isinstance(applicable, list) and applicable:
|
||||
return _cell('、'.join(str(item) for item in applicable))
|
||||
return _cell(state.get('state'))
|
||||
lines.append(
|
||||
f"| {_cell(_token(planet))} | {_state('Bala')} | {_state('Jagrat')} | {_state('Deeptadi')} | "
|
||||
|
||||
Reference in New Issue
Block a user