Files
Jyotisha/scripts/ashtakavarga.py
T
jesse-uxGrok (agent loop)Cursor
e8a7f3ac22 fix(ashtakavarga): sum Pinda from the shared Sodhita reduction
Upstream 30b43643. Yoga Pinda multiplied only the bindu in the planet's own sign. It now uses the Trikona and Ekadhipatya rows, with PVR table 28 including Virgo 6. PVR example 43 is 152. BUG-1384.

Co-Authored-By: Grok (agent loop)
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-10-18 16:27:18 +08:00

608 lines
22 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Ashtakavarga 计算模块 v2.1(八分法)
基于 Brihat Parashara Hora Shastra (BPHS) 完整标准表,并经 PVR/PyJHora 书例校准
核心修正(v2.0 vs v1.0):
- v1.0: BAV_BASE 只有自贡献 + 少量跨源贡献 → SAV=94 ❌
- v2.0: 完整 8×8 贡献矩阵(每颗行星 × 8来源)→ SAV=337 ✅
架构:
- BAV_CONTRIBUTION[planet][source] = 有利宫位列表
- 每颗行星的 BAV = 所有 8 个来源(7行星+Lagna)的贡献之和
- SAV = 7 颗行星 BAV 之和(Lagna 自身 BAV 单独展示,不计入 SAV)
- SAV 总和 = 337(宇宙常数)
PVR/PyJHora 书例校准(v2.1):
- 第六轮 benchmark 使用 PyJHora `pvr_tests.py` 中公开书例仲裁,修正 Moon/Venus 7 个贡献表项。
- 固定不变量保持:7行星 SAV=337,含 Lagna full SAV=386。
"""
from typing import Dict, List, Tuple
SIGNS = ['Aries', 'Taurus', 'Gemini', 'Cancer', 'Leo', 'Virgo',
'Libra', 'Scorpio', 'Sagittarius', 'Capricorn', 'Aquarius', 'Pisces']
# ============================================================================
# 完整 BAV 贡献表(BPHS 标准法)
# BAV_CONTRIBUTION[planet][source] = 从 source 数起的有利宫位列表
# 每颗行星的 BAV 总和 = 所有 8 个来源的贡献数之和
# ============================================================================
# --- 太阳 BAV(总48) ---
# 来源: Sun(8) + Moon(4) + Mars(8) + Mercury(7) + Jupiter(4) + Venus(3) + Saturn(8) + Lagna(6) = 48
_SUN_BAV = {
'Sun': [1, 2, 4, 7, 8, 9, 10, 11],
'Moon': [3, 6, 10, 11],
'Mars': [1, 2, 4, 7, 8, 9, 10, 11],
'Mercury': [3, 5, 6, 9, 10, 11, 12],
'Jupiter': [5, 6, 9, 11],
'Venus': [6, 7, 12],
'Saturn': [1, 2, 4, 7, 8, 9, 10, 11],
'Lagna': [3, 4, 6, 10, 11, 12],
}
# --- 月亮 BAV(总49) ---
# 来源: Sun(6) + Moon(7) + Mars(6) + Mercury(8) + Jupiter(7) + Venus(7) + Saturn(4) + Lagna(4) = 49
# v2.1: PVR/PyJHora 书例校准 Moon/Mars/Jupiter/Lagna 贡献项。
_MOON_BAV = {
'Sun': [3, 6, 7, 8, 10, 11],
'Moon': [1, 3, 6, 7, 9, 10, 11],
'Mars': [2, 3, 5, 6, 10, 11],
'Mercury': [1, 3, 4, 5, 7, 8, 10, 11],
'Jupiter': [1, 2, 4, 7, 8, 10, 11],
'Venus': [3, 4, 5, 7, 9, 10, 11],
'Saturn': [3, 5, 6, 11],
'Lagna': [3, 6, 10, 11],
}
# --- 火星 BAV(总39) ---
# 来源: Sun(5) + Moon(3) + Mars(7) + Mercury(4) + Jupiter(4) + Venus(4) + Saturn(7) + Lagna(5) = 39
_MARS_BAV = {
'Sun': [3, 5, 6, 10, 11],
'Moon': [3, 6, 11],
'Mars': [1, 2, 4, 7, 8, 10, 11],
'Mercury': [3, 5, 6, 11],
'Jupiter': [6, 10, 11, 12],
'Venus': [6, 8, 11, 12],
'Saturn': [1, 4, 7, 8, 9, 10, 11],
'Lagna': [1, 3, 6, 10, 11],
}
# --- 水星 BAV(总54) ---
# 来源: Sun(5) + Moon(6) + Mars(8) + Mercury(8) + Jupiter(4) + Venus(8) + Saturn(8) + Lagna(7) = 54
_MERCURY_BAV = {
'Sun': [5, 6, 9, 11, 12],
'Moon': [2, 4, 6, 8, 10, 11],
'Mars': [1, 2, 4, 7, 8, 9, 10, 11],
'Mercury': [1, 3, 5, 6, 9, 10, 11, 12],
'Jupiter': [6, 8, 11, 12],
'Venus': [1, 2, 3, 4, 5, 8, 9, 11],
'Saturn': [1, 2, 4, 7, 8, 9, 10, 11],
'Lagna': [1, 2, 4, 6, 8, 10, 11],
}
# --- 木星 BAV(总56) ---
# 来源: Sun(9) + Moon(5) + Mars(7) + Mercury(8) + Jupiter(8) + Venus(6) + Saturn(4) + Lagna(9) = 56
_JUPITER_BAV = {
'Sun': [1, 2, 3, 4, 7, 8, 9, 10, 11],
'Moon': [2, 5, 7, 9, 11],
'Mars': [1, 2, 4, 7, 8, 10, 11],
'Mercury': [1, 2, 4, 5, 6, 9, 10, 11],
'Jupiter': [1, 2, 3, 4, 7, 8, 10, 11],
'Venus': [2, 5, 6, 9, 10, 11],
'Saturn': [3, 5, 6, 12],
'Lagna': [1, 2, 4, 5, 6, 7, 9, 10, 11],
}
# --- 金星 BAV(总52) ---
# 来源: Sun(3) + Moon(9) + Mars(6) + Mercury(5) + Jupiter(5) + Venus(9) + Saturn(7) + Lagna(8) = 52
# v2.1: PVR/PyJHora 书例校准 Mars/Mercury/Lagna 贡献项。
_VENUS_BAV = {
'Sun': [8, 11, 12],
'Moon': [1, 2, 3, 4, 5, 8, 9, 11, 12],
'Mars': [3, 4, 6, 9, 11, 12],
'Mercury': [3, 5, 6, 9, 11],
'Jupiter': [5, 8, 9, 10, 11],
'Venus': [1, 2, 3, 4, 5, 8, 9, 10, 11],
'Saturn': [3, 4, 5, 8, 9, 10, 11],
'Lagna': [1, 2, 3, 4, 5, 8, 9, 11],
}
# --- 土星 BAV(总39) ---
# 来源: Sun(7) + Moon(3) + Mars(6) + Mercury(6) + Jupiter(4) + Venus(3) + Saturn(4) + Lagna(6) = 39
_SATURN_BAV = {
'Sun': [1, 2, 4, 7, 8, 10, 11],
'Moon': [3, 6, 11],
'Mars': [3, 5, 6, 10, 11, 12],
'Mercury': [6, 8, 9, 10, 11, 12],
'Jupiter': [5, 6, 11, 12],
'Venus': [6, 11, 12],
'Saturn': [3, 5, 6, 11],
'Lagna': [1, 3, 4, 6, 10, 11],
}
# --- Lagna BAV(总49,不计入 SAV 337) ---
# 来源: Sun(6) + Moon(5) + Mars(5) + Mercury(7) + Jupiter(9) + Venus(7) + Saturn(6) + Lagna(4) = 49
_LAGNA_BAV = {
'Sun': [3, 4, 6, 10, 11, 12],
'Moon': [3, 6, 10, 11, 12],
'Mars': [1, 3, 6, 10, 11],
'Mercury': [1, 2, 4, 6, 8, 10, 11],
'Jupiter': [1, 2, 4, 5, 6, 7, 9, 10, 11],
'Venus': [1, 2, 3, 4, 5, 8, 9],
'Saturn': [1, 3, 4, 6, 10, 11],
'Lagna': [3, 6, 10, 11],
}
# 汇总为统一查找表
BAV_CONTRIBUTION = {
'Sun': _SUN_BAV,
'Moon': _MOON_BAV,
'Mars': _MARS_BAV,
'Mercury': _MERCURY_BAV,
'Jupiter': _JUPITER_BAV,
'Venus': _VENUS_BAV,
'Saturn': _SATURN_BAV,
'Lagna': _LAGNA_BAV,
}
# 各行星BAV固定总数(宇宙常数)
BAV_TOTALS = {
'Sun': 48, 'Moon': 49, 'Mars': 39, 'Mercury': 54,
'Jupiter': 56, 'Venus': 52, 'Saturn': 39, 'Lagna': 49
}
# 7颗行星 BAV 总和 = SAV 337
EXPECTED_SAV_TOTAL = 337
# Shodhya Pinda 行星权重
PLANET_WEIGHTS = {
'Sun': 5, 'Moon': 5, 'Mars': 8, 'Mercury': 5,
'Jupiter': 10, 'Venus': 7, 'Saturn': 5
}
# PVR §12.7 表 28。处女座是 6。不改用 Prasna Marga 的 5。
RASHI_PINDA_WEIGHTS = (7, 10, 8, 4, 10, 6, 7, 8, 9, 5, 11, 12)
# 行星列表
SEVEN_PLANETS = ['Sun', 'Moon', 'Mars', 'Mercury', 'Jupiter', 'Venus', 'Saturn']
ALL_SOURCES = SEVEN_PLANETS + ['Lagna']
def _get_sign_idx(sign_name: str) -> int:
"""星座名称转索引"""
return SIGNS.index(sign_name) if sign_name in SIGNS else 0
def calc_ashtakavarga(planets: Dict, asc_sign_idx: int) -> Dict:
"""
计算完整 Ashtakavarga(v2.0 BPHS标准法)
Args:
planets: 行星数据 dict,每颗行星需要 {sign, degree, ...}
sign 是星座名称
asc_sign_idx: 上升星座在 SIGNS 中的索引 (0-11)
Returns:
完整的 Ashtakavarga 计算结果,含 BAV、SAV、校验
"""
# 1. 确定每个来源(7行星+Lagna)的星座索引
source_sign_idx = {}
for pname in SEVEN_PLANETS:
if pname in planets:
sign = planets[pname].get('sign', '')
if sign in SIGNS:
source_sign_idx[pname] = SIGNS.index(sign)
source_sign_idx['Lagna'] = asc_sign_idx
# 2. 为每颗行星计算 BAV
# BAV[planet][sign_i] = count of sources that contribute bindhu to sign_i
bav_results = {}
sav = [0] * 12 # 12星座的SAV(仅统计7行星贡献)
lagna_sav = [0] * 12 # Lagna单独的贡献
for planet in ALL_SOURCES:
contribution_rules = BAV_CONTRIBUTION[planet]
bav = [0] * 12 # 12星座的 bindhu 数
for source in ALL_SOURCES:
if source not in source_sign_idx:
continue
# 获取该 source 对该 planet BAV 的有利宫位
favorable_houses = contribution_rules.get(source, [])
source_idx = source_sign_idx[source]
# 从 source 所在星座开始,将有利宫位映射到实际星座
for house in favorable_houses:
target_sign = (source_idx + house - 1) % 12
bav[target_sign] += 1
bav_total = sum(bav)
expected = BAV_TOTALS.get(planet, 0)
bav_results[planet] = {
'bindus': bav,
'total': bav_total,
'expected_total': expected,
'valid': bav_total == expected,
'deviation': abs(bav_total - expected),
}
# 累加到 SAV(仅7行星)
if planet in SEVEN_PLANETS:
for i in range(12):
sav[i] += bav[i]
else:
# Lagna 的贡献单独记录
for i in range(12):
lagna_sav[i] += bav[i]
# 3. 校验 SAV 总分
sav_total = sum(sav)
# 4. SAV 评估
sav_assessment = []
for i in range(12):
score = sav[i]
if score >= 30:
level = "极吉"
elif score >= 28:
level = "吉利"
elif score >= 25:
level = "中等"
else:
level = "挑战"
sav_assessment.append({
'sign': SIGNS[i],
'score': score,
'level': level,
})
# 5. 含 Lagna 的完整 SAV(386 = 337 + 49)
full_sav = [sav[i] + lagna_sav[i] for i in range(12)]
# 6. Shodhya/Yoga Pinda 计算
yoga_pinda = calc_yoga_pinda(bav_results, planets, asc_sign_idx)
shodhya = {
pname: {
'rashi_pinda': row['rashi_pinda'],
'graha_pinda': row['graha_pinda'],
'total_pinda': row['yoga_pinda'],
'bindu_at_own_sign': row['bindu_at_own_sign'],
}
for pname, row in yoga_pinda.get('planets', {}).items()
}
# 7. 排名
ranked_sav = sorted(enumerate(sav_assessment), key=lambda x: x[1]['score'], reverse=True)
# 8. BAV 校验报告
bav_validation = []
all_valid = True
for planet in ALL_SOURCES:
r = bav_results[planet]
status = "✅" if r['valid'] else f"❌ 偏差{r['deviation']}"
if not r['valid']:
all_valid = False
bav_validation.append({
'planet': planet,
'actual': r['total'],
'expected': r['expected_total'],
'status': status,
})
return {
'method': 'Ashtakavarga八分法(BPHS/PVR书例校准v2.1)',
'version': '2.1',
'bav': bav_results,
'sav': {
'scores': {SIGNS[i]: sav[i] for i in range(12)},
'full_scores_with_lagna': {SIGNS[i]: full_sav[i] for i in range(12)},
'assessment': sav_assessment,
'total': sav_total,
'expected_total': EXPECTED_SAV_TOTAL,
'valid': sav_total == EXPECTED_SAV_TOTAL,
'deviation': abs(sav_total - EXPECTED_SAV_TOTAL),
'full_total_with_lagna': sum(full_sav),
},
'bav_validation': bav_validation,
'all_bav_valid': all_valid,
'shodhya_pinda': shodhya,
'yoga_pinda': yoga_pinda,
'strongest_signs': [SIGNS[i] for i, _ in ranked_sav[:3]],
'weakest_signs': [SIGNS[i] for i, _ in ranked_sav[-3:]],
'house_scores': _map_to_houses(sav_assessment, asc_sign_idx),
'house_scores_full': _map_to_houses_values(full_sav, asc_sign_idx),
}
def _map_to_houses(sav_assessment: List[Dict], asc_sign_idx: int) -> Dict:
"""将 SAV 分数映射到宫位"""
houses = {}
for house_num in range(1, 13):
sign_idx = (asc_sign_idx + house_num - 1) % 12
score = sav_assessment[sign_idx]['score']
level = sav_assessment[sign_idx]['level']
houses[f"house_{house_num}"] = {
'sign': SIGNS[sign_idx],
'sav_score': score,
'level': level,
}
return houses
def _map_to_houses_values(scores: List[int], asc_sign_idx: int) -> Dict:
"""将含Lagna的完整SAV映射到宫位"""
houses = {}
for house_num in range(1, 13):
sign_idx = (asc_sign_idx + house_num - 1) % 12
houses[f"house_{house_num}"] = {
'sign': SIGNS[sign_idx],
'sav_score': scores[sign_idx],
}
return houses
def calc_prastara_av(planets: Dict, asc_sign_idx: int) -> Dict:
"""
计算 Prastara Ashtakavarga (PAV) — 展开式八分行星力量表
PAV 是一个 7×12×8 的三维矩阵:
- 7 颗行星 (Sun, Moon, Mars, Mercury, Jupiter, Venus, Saturn)
- 12 个宫位/星座
- 8 个贡献源 (7行星 + Lagna)
每格为 0 或 1,表示该贡献源是否对该行星的该宫位贡献 bindu。
Args:
planets: 行星数据 dict,每颗行星需要 {sign, degree, ...}
asc_sign_idx: 上升星座在 SIGNS 中的索引 (0-11)
Returns:
{
'pav': {planet: {source: [12个0/1值]}},
'pav_summary': {planet: {source: total_bindus}},
'validation': {...}
}
"""
# 1. 确定每个来源的星座索引
source_sign_idx = {}
for pname in SEVEN_PLANETS:
if pname in planets:
sign = planets[pname].get('sign', '')
if sign in SIGNS:
source_sign_idx[pname] = SIGNS.index(sign)
source_sign_idx['Lagna'] = asc_sign_idx
# 2. 计算 PAV 三维矩阵
pav = {}
pav_summary = {}
for planet in SEVEN_PLANETS:
contribution_rules = BAV_CONTRIBUTION[planet]
pav[planet] = {}
pav_summary[planet] = {}
for source in ALL_SOURCES:
if source not in source_sign_idx:
continue
# 12个宫位的bindu标记(0或1)
bindu_markers = [0] * 12
favorable_houses = contribution_rules.get(source, [])
source_idx = source_sign_idx[source]
for house in favorable_houses:
target_sign = (source_idx + house - 1) % 12
bindu_markers[target_sign] = 1
pav[planet][source] = bindu_markers
pav_summary[planet][source] = sum(bindu_markers)
# 3. 验证:每行的总和应等于BAV对应值
validation = []
all_valid = True
for planet in SEVEN_PLANETS:
for source in ALL_SOURCES:
if source not in pav_summary.get(planet, {}):
continue
actual = pav_summary[planet][source]
# 从BAV_CONTRIBUTION查期望的贡献数(该source对planet的宫位数)
expected = len(BAV_CONTRIBUTION[planet].get(source, []))
valid = actual == expected
if not valid:
all_valid = False
validation.append({
'planet': planet,
'source': source,
'actual': actual,
'expected': expected,
'valid': valid,
})
return {
'method': 'Prastara Ashtakavarga (PAV)',
'version': '1.0',
'matrix_shape': {'planets': len(SEVEN_PLANETS), 'signs': 12, 'sources': len(ALL_SOURCES)},
'pav': pav,
'pav_summary': pav_summary,
'validation': validation,
'all_valid': all_valid,
}
def calc_yoga_pinda(bav_results: Dict, planets: Dict, asc_sign_idx: int) -> Dict:
"""Yoga Pinda from the same Trikona and Ekadhipatya rows as the Sodhita table.
PVR §12.7 tables 28–29: twelve reduced signs times rashi weights (Virgo 6),
plus the seven planets' weights at the signs they occupy. A full seven-row
chart reuses ``sodhita_chain`` so the report cannot show two reductions.
"""
try:
from ashtakavarga_shodhana import occupied_signs, sodhita_chain
except ImportError:
from scripts.ashtakavarga_shodhana import occupied_signs, sodhita_chain
source_sign_idx = {}
for pname in SEVEN_PLANETS:
if pname in planets:
sign = planets[pname].get('sign', '')
if sign in SIGNS:
source_sign_idx[pname] = SIGNS.index(sign)
source_sign_idx['Lagna'] = asc_sign_idx
note = (
'PVR §12.7 表 28–29:十二座削减值乘星座权重(处女座 6),'
'再加七曜所在座的行星权重。与 Sodhita 表同一份削减。'
'同值且一占一空时按 Raman 15(c) 清空空宫。'
)
if any(pname not in source_sign_idx for pname in SEVEN_PLANETS):
return {
'method': 'Yoga Pinda', 'version': '2.0',
'profile': 'pvr_trikona_ekadhipatya_tables28_29',
'planets': {}, 'summary': {}, 'all_valid': False,
'validation_note': note + ' 七曜星座不全,不算 Graha Pinda。',
}
available = {}
for pname, row in bav_results.items():
bindus = row.get('bindus') if isinstance(row, dict) else None
if isinstance(bindus, list) and len(bindus) == 12:
available[pname] = {'bindus': bindus}
if set(SEVEN_PLANETS).issubset(available):
chain = sodhita_chain(available, planets)
trikona_rows = chain['trikona_bav']
reduced_rows = chain['sodhita_bav']
else:
trikona_rows = {
pname: calc_trikona_shodhana(row['bindus'])['reduced_scores']
for pname, row in available.items()
}
reduced_rows = calc_ekadhipatya_shodhana(trikona_rows, occupied_signs(planets))['rows']
rows = {}
all_valid = True
for pname in SEVEN_PLANETS:
reduced = reduced_rows.get(pname)
own_sign = source_sign_idx.get(pname)
bindus = (available.get(pname) or {}).get('bindus')
if reduced is None or own_sign is None or bindus is None:
all_valid = False
continue
rashi_pinda = sum(value * weight for value, weight in zip(reduced, RASHI_PINDA_WEIGHTS))
graha_pinda = sum(
reduced[source_sign_idx[body]] * PLANET_WEIGHTS[body] for body in SEVEN_PLANETS
)
rows[pname] = {
'sign': SIGNS[own_sign],
'bindu_at_own_sign': bindus[own_sign],
'rashi_pinda': rashi_pinda,
'graha_pinda': graha_pinda,
'yoga_pinda': rashi_pinda + graha_pinda,
}
ranked = sorted(rows.items(), key=lambda item: item[1]['yoga_pinda'], reverse=True)
strongest = ranked[0][0] if ranked else None
weakest = ranked[-1][0] if ranked else None
total = sum(item['yoga_pinda'] for item in rows.values())
return {
'method': 'Yoga Pinda',
'version': '2.0',
'profile': 'pvr_trikona_ekadhipatya_tables28_29',
'rashi_weights': list(RASHI_PINDA_WEIGHTS),
'planet_weights': dict(PLANET_WEIGHTS),
'trikona_rows': trikona_rows,
'sodhita_rows': reduced_rows,
'planets': rows,
'summary': {
'strongest_planet': strongest,
'weakest_planet': weakest,
'total_yoga_pinda': total,
'average_yoga_pinda': round(total / len(rows), 2) if rows else 0,
},
'all_valid': all_valid and len(rows) == len(SEVEN_PLANETS),
'validation_note': note,
}
# calc_sodhita_av (removed, BUG-1209): it subtracted the Sun, Mars and Saturn
# contributions from every BAV and was labelled "BPHS标准"; that is not the BPHS
# Sodhita. The standard chain (Trikona -> Ekadhipatya per BAV row) is
# scripts/ashtakavarga_shodhana.py, built on the two functions below.
TRIKONA_SIGN_GROUPS = (
(0, 4, 8), # Aries, Leo, Sagittarius
(1, 5, 9), # Taurus, Virgo, Capricorn
(2, 6, 10), # Gemini, Libra, Aquarius
(3, 7, 11), # Cancer, Scorpio, Pisces
)
def calc_trikona_shodhana(sav_scores: List[int]) -> Dict:
"""Apply the classical Trikona Shodhana reduction to a 12-sign SAV row.
The minimum value in each elemental trine is removed from all three signs;
the original row remains in the result for auditability.
"""
if len(sav_scores) != 12:
raise ValueError('SAV scores must contain exactly 12 signs')
original = [int(value) for value in sav_scores]
reduced = original[:]
groups = []
for indexes in TRIKONA_SIGN_GROUPS:
baseline = min(original[index] for index in indexes)
for index in indexes:
reduced[index] -= baseline
groups.append({'sign_indexes': list(indexes), 'baseline_removed': baseline})
return {
'method': 'Trikona Shodhana',
'original_scores': original,
'reduced_scores': reduced,
'trikona_groups': groups,
'status': 'executed',
'claim_boundary': 'Standalone classical reduction; Ekadhipatya Shodhana remains a separate layer.',
}
EKADHIPATYA_SIGN_PAIRS = ((0, 7), (1, 6), (2, 5), (8, 11), (9, 10))
def calc_ekadhipatya_shodhana(
rows: Dict[str, List[int]], occupied_signs: set[int]
) -> Dict:
"""Apply PyJHora/PVR Ekadhipatya Shodhana to post-Trikona rows."""
reduced = {planet: list(values) for planet, values in rows.items()}
audit = []
for planet, values in reduced.items():
if len(values) != 12:
raise ValueError('each BAV row must contain exactly 12 signs')
for first, second in EKADHIPATYA_SIGN_PAIRS:
first_value, second_value = values[first], values[second]
if first_value == 0 or second_value == 0:
continue
first_occupied = first in occupied_signs
second_occupied = second in occupied_signs
before = [first_value, second_value]
if first_occupied and second_occupied:
continue
if first_occupied != second_occupied:
occupied, vacant = (first, second) if first_occupied else (second, first)
values[vacant] = values[occupied] if values[occupied] < values[vacant] else 0
elif first_value == second_value:
values[first] = values[second] = 0
elif first_value > second_value:
values[first] = second_value
else:
values[second] = first_value
audit.append({'planet': planet, 'pair': [first, second], 'before': before,
'after': [values[first], values[second]]})
return {
'method': 'Ekadhipatya Shodhana',
'rows': reduced,
'occupied_signs': sorted(occupied_signs),
'audit': audit,
'status': 'executed',
'claim_boundary': 'Applied after Trikona Shodhana. One occupied and one vacant sign with the same value clears the vacant sign (Raman 15(c)).',
}