fix(vimsopaka): score sign-owner relationships on a 20-point scale (BUG-1261, upstream f32472b5)
This commit is contained in:
@@ -16932,4 +16932,20 @@
|
||||
- 防复发:上述手算例(11° 双子、19° 天蝎等)锁住四张分盘的目标星座
|
||||
- 相关记录:BUG-1261
|
||||
- 复发自:无
|
||||
- 修复版本:本分支 `db68e908`
|
||||
|
||||
## BUG-1261 | Vimsopaka 把落陷取消当成尊贵分,且十六分盘权重加总不是 20
|
||||
|
||||
- 状态:resolved
|
||||
- 首次发现:2026-10-07
|
||||
- 最近更新:2026-10-07
|
||||
- 影响面:`scripts/vimsopaka_calculator.py`;全盘报告里的 Vimsopaka 分数与分档
|
||||
- 用户现象:全自宫的十六分盘加总不是 20;落陷取消被记成 9 分的尊贵;分档名称借用 Vaiseshikamsa 的计数头衔
|
||||
- 触发条件:计算 Shodasavarga 或 Dasavarga 的 Vimsopaka
|
||||
- 根因:十六分盘 D9 权重是 3.5(合计 20.5),十分盘 D9 是 3.0(合计不是 20);尊贵分把自宫记成 15,并把落陷、落陷取消放进同一张表;最终分除以已给权重而不是 20。上游 `f32472b5` 已改,本仓未跟上
|
||||
- 修复:十六分盘 D9 改为 3.0、十分盘 D9 改为 1.5,两组权重都合计 20。尊贵分改为入庙 / 根本三角 / 自宫 20、极友 18、友 15、中性 10、敌 7、极敌 5。落陷和落陷取消不再入表,传入则报错。最终分除以 20。分档改为 High / Moderate / Limited / Low。全盘装配改传星主关系(`skip_debilitation`),盘面标签仍保留落陷取消。解释句只陈述加权关系,不写成功能吉凶或必然结果
|
||||
- 验证:`tests/test_dasha.py` 全自宫等于 20,以及落陷取消报错;`tests/test_cli_smoke.py` 中 Jupiter 的 Vimsopaka 不再写 Neecha Bhanga
|
||||
- 防复发:全自宫反例与 ValueError 断言
|
||||
- 相关记录:BUG-1260、BUG-1205
|
||||
- 复发自:无
|
||||
- 修复版本:本分支待发布
|
||||
|
||||
+15
-10
@@ -651,8 +651,11 @@ def _build_dignity_context(chart_data):
|
||||
}
|
||||
return context
|
||||
|
||||
def _get_dignity_level(planet, sign, deg_in_sign=None, planets_data=None):
|
||||
"""判断行星在某星座的尊严等级 (用于 Vimsopaka 映射), 包含五重敌友和落陷取消"""
|
||||
def _get_dignity_level(planet, sign, deg_in_sign=None, planets_data=None, skip_debilitation=False):
|
||||
"""判断行星在某星座的尊严等级, 包含五重敌友;默认也含落陷取消。
|
||||
|
||||
skip_debilitation 只给 Varga Viswa:落陷和落陷取消不是星主关系。
|
||||
"""
|
||||
if EXALTATION.get(planet) == sign:
|
||||
return 'EXALTED'
|
||||
|
||||
@@ -667,7 +670,7 @@ def _get_dignity_level(planet, sign, deg_in_sign=None, planets_data=None):
|
||||
if SIGN_LORDS.get(sign) == planet:
|
||||
return 'OWN_SIGN'
|
||||
|
||||
if DEBILITATION.get(planet) == sign:
|
||||
if not skip_debilitation and DEBILITATION.get(planet) == sign:
|
||||
if _check_neecha_bhanga(planet, sign, planets_data):
|
||||
return 'NEECHA_BHANGA'
|
||||
return 'DEBILITATED'
|
||||
@@ -706,7 +709,7 @@ def _get_dignity_level(planet, sign, deg_in_sign=None, planets_data=None):
|
||||
return 'NEUTRAL'
|
||||
|
||||
|
||||
def varga_dignity_level(planet, varga_planets):
|
||||
def varga_dignity_level(planet, varga_planets, skip_debilitation=False):
|
||||
"""Dignity of a body inside a divisional chart (Vimsopaka and the chart page alike).
|
||||
|
||||
The temporary half of the compound relationship is read from that varga's
|
||||
@@ -717,7 +720,10 @@ def varga_dignity_level(planet, varga_planets):
|
||||
if not isinstance(pinfo, dict) or pinfo.get('sign') not in SIGN_LORDS:
|
||||
return None
|
||||
deg_in_sign = pinfo.get('degree_in_sign', pinfo.get('degree', 0) % 30)
|
||||
return _get_dignity_level(planet, pinfo['sign'], deg_in_sign, _build_dignity_context(varga_planets))
|
||||
return _get_dignity_level(
|
||||
planet, pinfo['sign'], deg_in_sign, _build_dignity_context(varga_planets),
|
||||
skip_debilitation=skip_debilitation,
|
||||
)
|
||||
|
||||
|
||||
def _get_planet_status_label(planet, sign, deg_in_sign=None, planets_data=None):
|
||||
@@ -15756,7 +15762,6 @@ def cmd_full_reading(args):
|
||||
'OWN_SIGN': DignityLevel.OWN_SIGN, 'GREAT_FRIEND': DignityLevel.GREAT_FRIEND,
|
||||
'FRIEND': DignityLevel.FRIEND, 'NEUTRAL': DignityLevel.NEUTRAL,
|
||||
'ENEMY': DignityLevel.ENEMY, 'GREAT_ENEMY': DignityLevel.GREAT_ENEMY,
|
||||
'DEBILITATED': DignityLevel.DEBILITATED, 'NEECHA_BHANGA': DignityLevel.NEECHA_BHANGA,
|
||||
}
|
||||
# 建立 VargaType → varga_result key 的映射(修复:每个分盘独立计算尊贵)
|
||||
_vt_div = {VimsVargaType.D1:1, VimsVargaType.D2:2, VimsVargaType.D3:3,
|
||||
@@ -15785,15 +15790,15 @@ def cmd_full_reading(args):
|
||||
if div == 1:
|
||||
dl_key = _get_dignity_level(pn, pinfo['sign'],
|
||||
pinfo.get('degree_in_sign', pinfo.get('degree', 0) % 30),
|
||||
planets)
|
||||
planets, skip_debilitation=True)
|
||||
else:
|
||||
dl_key = varga_dignity_level(pn, vdata)
|
||||
dl_key = varga_dignity_level(pn, vdata, skip_debilitation=True)
|
||||
planet_vargas_input[pn][vt] = dignity_map.get(dl_key, DignityLevel.NEUTRAL)
|
||||
elif div == 1:
|
||||
# D1 直接用本命盘数据
|
||||
p_sign = SIGNS[int(planet_lons.get(pn, 0) / 30) % 12]
|
||||
d_in_s = planet_lons.get(pn, 0) % 30
|
||||
dl_key = _get_dignity_level(pn, p_sign, d_in_s, planets)
|
||||
dl_key = _get_dignity_level(pn, p_sign, d_in_s, planets, skip_debilitation=True)
|
||||
planet_vargas_input[pn][vt] = dignity_map.get(dl_key, DignityLevel.NEUTRAL)
|
||||
else:
|
||||
planet_vargas_input[pn][vt] = DignityLevel.NEUTRAL
|
||||
@@ -15801,7 +15806,7 @@ def cmd_full_reading(args):
|
||||
# fallback: D1 从 planet_lons 计算
|
||||
p_sign = SIGNS[int(planet_lons.get(pn, 0) / 30) % 12]
|
||||
d_in_s = planet_lons.get(pn, 0) % 30
|
||||
dl_key = _get_dignity_level(pn, p_sign, d_in_s, planets)
|
||||
dl_key = _get_dignity_level(pn, p_sign, d_in_s, planets, skip_debilitation=True)
|
||||
planet_vargas_input[pn][vt] = dignity_map.get(dl_key, DignityLevel.NEUTRAL)
|
||||
else:
|
||||
planet_vargas_input[pn][vt] = DignityLevel.NEUTRAL
|
||||
|
||||
@@ -60,7 +60,7 @@ class VimsopakaBalaCalculator:
|
||||
VargaType.D3: 1.0, # Drekkana
|
||||
VargaType.D4: 0.5, # Chaturthamsa
|
||||
VargaType.D7: 0.5, # Saptamsa
|
||||
VargaType.D9: 3.5, # Navamsa
|
||||
VargaType.D9: 3.0, # Navamsa;十六分盘权重合计 20
|
||||
VargaType.D10: 0.5, # Dasamsa
|
||||
VargaType.D12: 0.5, # Dwadasamsa
|
||||
VargaType.D16: 2.0, # Shodasamsa
|
||||
@@ -79,7 +79,7 @@ class VimsopakaBalaCalculator:
|
||||
VargaType.D2: 1.5,
|
||||
VargaType.D3: 1.5,
|
||||
VargaType.D7: 1.5,
|
||||
VargaType.D9: 3.0,
|
||||
VargaType.D9: 1.5,
|
||||
VargaType.D10: 1.5,
|
||||
VargaType.D12: 1.5,
|
||||
VargaType.D16: 1.5,
|
||||
@@ -87,31 +87,24 @@ class VimsopakaBalaCalculator:
|
||||
VargaType.D60: 5.0
|
||||
}
|
||||
|
||||
# 尊严等级对应的Virupas(力量单位)
|
||||
# 星主关系对应的 Virupas。落陷和落陷取消不是这项的输入。
|
||||
DIGNITY_VIRUPAS = {
|
||||
DignityLevel.EXALTED: 20.0,
|
||||
DignityLevel.MOOLATRIKONA: 18.0,
|
||||
DignityLevel.OWN_SIGN: 15.0,
|
||||
DignityLevel.GREAT_FRIEND: 12.5,
|
||||
DignityLevel.FRIEND: 10.0,
|
||||
DignityLevel.NEUTRAL: 7.5,
|
||||
DignityLevel.ENEMY: 5.0,
|
||||
DignityLevel.GREAT_ENEMY: 3.5,
|
||||
DignityLevel.DEBILITATED: 2.0,
|
||||
DignityLevel.NEECHA_BHANGA: 9.0,
|
||||
DignityLevel.MOOLATRIKONA: 20.0,
|
||||
DignityLevel.OWN_SIGN: 20.0,
|
||||
DignityLevel.GREAT_FRIEND: 18.0,
|
||||
DignityLevel.FRIEND: 15.0,
|
||||
DignityLevel.NEUTRAL: 10.0,
|
||||
DignityLevel.ENEMY: 7.0,
|
||||
DignityLevel.GREAT_ENEMY: 5.0,
|
||||
}
|
||||
|
||||
# 力量等级分类(基于总分)
|
||||
# 数值分档。不再借用 Vaiseshikamsa 的分盘计数名称。
|
||||
STRENGTH_CATEGORIES = {
|
||||
"Parijatamsa": (18.0, 20.0), # 极强(18-20)
|
||||
"Uttamamsa": (16.0, 18.0), # 优秀(16-18)
|
||||
"Gopuramsa": (12.0, 16.0), # 良好(12-16)
|
||||
"Simhasanamsa": (10.0, 12.0), # 中等偏上(10-12)
|
||||
"Paravatamsa": (8.0, 10.0), # 中等(8-10)
|
||||
"Devalokamsa": (6.0, 8.0), # 中等偏下(6-8)
|
||||
"Brahmalokamsa": (4.0, 6.0), # 较弱(4-6)
|
||||
"Airavata": (2.0, 4.0), # 弱(2-4)
|
||||
"Vaiseshikamsa": (0.0, 2.0) # 极弱(0-2)
|
||||
"High": (15.0, 20.000001),
|
||||
"Moderate": (10.0, 15.0),
|
||||
"Limited": (5.0, 10.0),
|
||||
"Low": (0.0, 5.0),
|
||||
}
|
||||
|
||||
def __init__(self, mode: str = "shodasavarga"):
|
||||
@@ -156,8 +149,10 @@ class VimsopakaBalaCalculator:
|
||||
# 计算每个分盘的得分
|
||||
for varga_type, dignity in vargas.items():
|
||||
if varga_type in self.weights:
|
||||
# 获取尊严对应的Virupas
|
||||
virupas = self.DIGNITY_VIRUPAS.get(dignity, 0.0)
|
||||
# 只接受星主关系。落陷或落陷取消不能当作尊贵分。
|
||||
if dignity not in self.DIGNITY_VIRUPAS:
|
||||
raise ValueError('Varga Viswa requires a sign-owner relationship, not debility/cancellation')
|
||||
virupas = self.DIGNITY_VIRUPAS[dignity]
|
||||
|
||||
# 获取分盘权重
|
||||
weight = self.weights[varga_type]
|
||||
@@ -175,9 +170,9 @@ class VimsopakaBalaCalculator:
|
||||
total_weighted_score += weighted_score
|
||||
total_weight += weight
|
||||
|
||||
# 计算最终得分(归一化到20分制)
|
||||
# 分母固定为 20,不按已提供分盘的权重再归一。
|
||||
if total_weight > 0:
|
||||
final_score = total_weighted_score / total_weight
|
||||
final_score = total_weighted_score / 20.0
|
||||
else:
|
||||
final_score = 0.0
|
||||
|
||||
@@ -193,7 +188,12 @@ class VimsopakaBalaCalculator:
|
||||
"category": category,
|
||||
"varga_scores": varga_scores,
|
||||
"interpretation": interpretation,
|
||||
"mode": self.mode
|
||||
"mode": self.mode,
|
||||
"calculation_profile": "bphs_7_21_27_varga_viswa_compound_relationships",
|
||||
"included_weight": total_weight,
|
||||
"complete": set(vargas).issuperset(self.weights),
|
||||
"missing_vargas": [v.value for v in self.weights if v not in vargas],
|
||||
"category_basis": "Numeric score band; not Vaiseshikamsa varga-count titles",
|
||||
}
|
||||
|
||||
def _get_strength_category(self, score: float) -> str:
|
||||
@@ -201,34 +201,21 @@ class VimsopakaBalaCalculator:
|
||||
for category, (min_score, max_score) in self.STRENGTH_CATEGORIES.items():
|
||||
if min_score <= score < max_score:
|
||||
return category
|
||||
return "Vaiseshikamsa" # 默认最低等级
|
||||
return "Unclassified"
|
||||
|
||||
def _generate_interpretation(self, planet: str, score: float, category: str, varga_scores: Dict) -> str:
|
||||
"""生成解读文本"""
|
||||
interpretation = f"{planet}的Vimsopaka Bala得分为{score:.2f}/20,属于{category}等级。\n\n"
|
||||
|
||||
# 根据等级给出解读
|
||||
if score >= 16:
|
||||
interpretation += "这是极强的行星力量,表示该行星在生活中能够充分发挥其积极作用,带来显著的成果和成功。"
|
||||
elif score >= 12:
|
||||
interpretation += "这是良好的行星力量,表示该行星能够有效发挥作用,在其主管领域带来正面影响。"
|
||||
elif score >= 8:
|
||||
interpretation += "这是中等的行星力量,表示该行星的作用较为平衡,既有积极面也有挑战。"
|
||||
elif score >= 4:
|
||||
interpretation += "这是较弱的行星力量,表示该行星在发挥作用时会遇到一些困难和阻碍。"
|
||||
else:
|
||||
interpretation += "这是极弱的行星力量,表示该行星难以充分发挥其作用,需要通过补救措施来增强。"
|
||||
|
||||
# 找出最强和最弱的分盘
|
||||
"""陈述加权后的星主关系,不把分数说成吉凶或应验。"""
|
||||
text = (
|
||||
f"{planet}的 Vimsopaka Bala 为 {score:.2f}/20,数值分档 {category}。"
|
||||
"这项只计分盘里的星主关系,不是功能吉凶,也不表示必然结果。"
|
||||
)
|
||||
if varga_scores:
|
||||
sorted_vargas = sorted(varga_scores.items(), key=lambda x: x[1]['weighted_score'], reverse=True)
|
||||
strongest = sorted_vargas[0]
|
||||
weakest = sorted_vargas[-1]
|
||||
|
||||
interpretation += f"\n\n最强分盘:{strongest[0]}({strongest[1]['dignity']},得分{strongest[1]['weighted_score']})"
|
||||
interpretation += f"\n最弱分盘:{weakest[0]}({weakest[1]['dignity']},得分{weakest[1]['weighted_score']})"
|
||||
|
||||
return interpretation
|
||||
ranked = sorted(varga_scores.items(), key=lambda item: item[1]['virupas'], reverse=True)
|
||||
text += f" 关系分最高:{ranked[0][0]}({ranked[0][1]['virupas']:g}/20)。"
|
||||
text += f" 关系分最低:{ranked[-1][0]}({ranked[-1][1]['virupas']:g}/20)。"
|
||||
if len(varga_scores) < len(self.weights):
|
||||
text += " 输入不完整,这里只是已计入分盘的合计,不是完整分数。"
|
||||
return text
|
||||
|
||||
def compare_planets(self, results: Dict[str, Dict]) -> List[Tuple[str, float]]:
|
||||
"""
|
||||
|
||||
@@ -757,9 +757,14 @@ def test_full_reading_maps_advanced_dignity_labels_into_vimsopaka_scores() -> No
|
||||
assert jupiter_d9["dignity"] == "NEECHA_BHANGA"
|
||||
|
||||
jupiter_navamsa = result["modules"]["vimsopaka"]["Jupiter"]["varga_scores"]["Navamsa"]
|
||||
assert jupiter_navamsa["dignity"] == "Neecha Bhanga"
|
||||
assert isinstance(jupiter_navamsa["virupas"], (int, float))
|
||||
assert jupiter_navamsa["virupas"] > 2.0
|
||||
# 原值: dignity == "Neecha Bhanga",virupas > 2.0
|
||||
# 新值: 星主关系之一,virupas 属于 {20, 18, 15, 10, 7, 5}
|
||||
# 原因: BUG-1261,Vimsopaka 不再把落陷取消当作尊贵分;D9 标签本身仍是 NEECHA_BHANGA
|
||||
assert jupiter_navamsa["dignity"] in {
|
||||
"Exalted", "Moolatrikona", "Own Sign", "Great Friend", "Friend", "Neutral", "Enemy", "Great Enemy",
|
||||
}
|
||||
assert jupiter_navamsa["virupas"] in {20.0, 18.0, 15.0, 10.0, 7.0, 5.0}
|
||||
assert "不是功能吉凶" in result["modules"]["vimsopaka"]["Jupiter"]["interpretation"]
|
||||
|
||||
|
||||
def test_varga_cli_outputs_d9_and_d10() -> None:
|
||||
|
||||
+31
-5
@@ -134,19 +134,45 @@ class TestDignityLevel(unittest.TestCase):
|
||||
calculator = VimsopakaBalaCalculator(mode="shodasavarga")
|
||||
results = calculator.calculate_vimsopaka_bala({
|
||||
'Jupiter': {
|
||||
VargaType.D9: DignityLevel.NEECHA_BHANGA,
|
||||
# 原值: D9 = NEECHA_BHANGA,virupas 9.0
|
||||
# 新值: D9 = FRIEND,virupas 15.0
|
||||
# 原因: BUG-1261,Varga Viswa 只收星主关系,不收落陷取消
|
||||
VargaType.D9: DignityLevel.FRIEND,
|
||||
VargaType.D1: DignityLevel.GREAT_FRIEND,
|
||||
VargaType.D10: DignityLevel.GREAT_ENEMY,
|
||||
}
|
||||
})
|
||||
|
||||
jupiter = results['Jupiter']
|
||||
assert jupiter['varga_scores']['Navamsa']['dignity'] == 'Neecha Bhanga'
|
||||
assert jupiter['varga_scores']['Navamsa']['virupas'] == 9.0
|
||||
# 原值: Neecha Bhanga / 9.0;Great Friend 12.5;Great Enemy 3.5
|
||||
# 新值: Friend / 15.0;Great Friend 18.0;Great Enemy 5.0
|
||||
# 原因: BUG-1261,尊贵分改为入旺自宫 20、极友 18、友 15、中 10、敌 7、极敌 5
|
||||
assert jupiter['varga_scores']['Navamsa']['dignity'] == 'Friend'
|
||||
assert jupiter['varga_scores']['Navamsa']['virupas'] == 15.0
|
||||
assert jupiter['varga_scores']['Rashi']['dignity'] == 'Great Friend'
|
||||
assert jupiter['varga_scores']['Rashi']['virupas'] == 12.5
|
||||
assert jupiter['varga_scores']['Rashi']['virupas'] == 18.0
|
||||
assert jupiter['varga_scores']['Dasamsa']['dignity'] == 'Great Enemy'
|
||||
assert jupiter['varga_scores']['Dasamsa']['virupas'] == 3.5
|
||||
assert jupiter['varga_scores']['Dasamsa']['virupas'] == 5.0
|
||||
with self.assertRaises(ValueError):
|
||||
calculator.calculate_vimsopaka_bala({'Jupiter': {VargaType.D9: DignityLevel.NEECHA_BHANGA}})
|
||||
with self.assertRaises(ValueError):
|
||||
calculator.calculate_vimsopaka_bala({'Jupiter': {VargaType.D1: DignityLevel.DEBILITATED}})
|
||||
|
||||
def test_all_own_sign_shodasavarga_scores_twenty(self):
|
||||
from vimsopaka_calculator import DignityLevel, VimsopakaBalaCalculator
|
||||
|
||||
calculator = VimsopakaBalaCalculator(mode="shodasavarga")
|
||||
own = {varga: DignityLevel.OWN_SIGN for varga in calculator.SHODASAVARGA_WEIGHTS}
|
||||
result = calculator.calculate_vimsopaka_bala({'Sun': own})['Sun']
|
||||
self.assertEqual(sum(calculator.SHODASAVARGA_WEIGHTS.values()), 20)
|
||||
self.assertEqual(sum(VimsopakaBalaCalculator.DASAVARGA_WEIGHTS.values()), 20)
|
||||
self.assertEqual(result['total_score'], 20)
|
||||
self.assertEqual(result['category'], 'High')
|
||||
self.assertTrue(result['complete'])
|
||||
self.assertEqual(result['missing_vargas'], [])
|
||||
self.assertEqual(result['calculation_profile'], 'bphs_7_21_27_varga_viswa_compound_relationships')
|
||||
self.assertIn('不是功能吉凶', result['interpretation'])
|
||||
self.assertNotIn('成功', result['interpretation'])
|
||||
|
||||
|
||||
class TestDashaCalculation(unittest.TestCase):
|
||||
|
||||
Reference in New Issue
Block a user