fix(narayana): show the source-aware seed beside the historical sequence (BUG-1264, upstream 1918ffaf)
This commit is contained in:
@@ -17062,3 +17062,19 @@
|
||||
- 相关记录:BUG-1205、BUG-1261
|
||||
- 复发自:无
|
||||
- 修复版本:本分支待发布
|
||||
|
||||
## BUG-1264 | Narayana 报告只给历史起运,不标明所选规则
|
||||
|
||||
- 状态:resolved
|
||||
- 首次发现:2026-10-07
|
||||
- 最近更新:2026-10-07
|
||||
- 影响面:`scripts/narayana_dasha.py` 的展示;全盘报告与 `cmd_narayana_dasha`。生时校正打分仍走原有默认序列
|
||||
- 用户现象:报告里的 Narayana 只有从上升顺排的历史序列,解释句把起运写成 Lagna,看不出实际用了哪条起运规则
|
||||
- 触发条件:生成 Narayana 大运报告
|
||||
- 根因:默认计算固定从上升顺排。上游 `1918ffaf` 另有按来源选择的起运、序列和双主星,用来对照,不替换历史序列。本仓序列表没有 Ketu 的第 12 表
|
||||
- 修复:展示层并列历史序列和来源对照。四个 profile 都是历史默认时,返回形状与原来的循环相同。来源对照写在 `method_comparison`,不替换 `mahadasha_sequence`,也不改 `score_candidates`。解释句写出实际选中的起运规则和星座。Ketu 落入需要第 12 表的情形时返回 blocked,原因 `rath_ketu_table12_v1_not_in_local_sequence_profiles`,不编序列。原有 Rath 对照保留
|
||||
- 验证:`tests/test_narayana_method_comparison.py`。默认调用与显式历史 profile 的序列相同;解释含所选规则名
|
||||
- 防复发:上述测试;打分调用仍是两参数的 `calc_narayana_mahadasha`
|
||||
- 相关记录:BUG-1215、BUG-1218
|
||||
- 复发自:无
|
||||
- 修复版本:本分支待发布
|
||||
|
||||
@@ -26,7 +26,7 @@ def cmd_narayana_dasha(args, chart_data):
|
||||
"Narayana Dasha 需要完整的行星经度数据。",
|
||||
])
|
||||
|
||||
from narayana_dasha import narayana_dasha_full_report
|
||||
from narayana_dasha import attach_narayana_method_comparison, narayana_dasha_full_report
|
||||
|
||||
planet_lons = {}
|
||||
for pn, pd in chart_data.get('planets', {}).items():
|
||||
@@ -59,6 +59,7 @@ def cmd_narayana_dasha(args, chart_data):
|
||||
from narayana_legacy_label import label_legacy_narayana # BUG-1215; Rath view BUG-1218
|
||||
label_legacy_narayana(result, lagna_sign_idx=asc_idx, planet_lons=planet_lons,
|
||||
current_age=current_age, birth_year=birth_year)
|
||||
attach_narayana_method_comparison(result, asc_idx, planet_lons, current_age, birth_year)
|
||||
|
||||
# 格式化输出
|
||||
lines = [
|
||||
@@ -99,6 +100,16 @@ def cmd_narayana_dasha(args, chart_data):
|
||||
for line in result.get('interpretation', []):
|
||||
lines.append(f" {line}")
|
||||
|
||||
source = (result.get('method_comparison') or {}).get('source_aware') or {}
|
||||
if source:
|
||||
lines.append("")
|
||||
lines.append("── 来源对照 ──")
|
||||
if source.get('status') == 'blocked':
|
||||
lines.append(f" 来源算法:blocked({source.get('reason')})")
|
||||
else:
|
||||
seed = source.get('seed_selection') or {}
|
||||
lines.append(f" 来源起运规则:{seed.get('profile')};起运星座:{seed.get('selected_sign')}")
|
||||
|
||||
rath = result.get('rath') or {}
|
||||
if rath:
|
||||
lines.append("")
|
||||
|
||||
@@ -66,6 +66,7 @@ except ModuleNotFoundError: # pragma: no cover - minimal environments
|
||||
return "\n".join(lines)
|
||||
from life_stage_hook import generate_life_stage_hooks
|
||||
from narayana_legacy_label import LEGACY_NOTE_ZH as LEGACY_NARAYANA_NOTE_ZH, rath_report_lines_zh # BUG-1215 / BUG-1218
|
||||
from narayana_dasha import narayana_comparison_lines_zh as _narayana_comparison_lines_zh
|
||||
from capability_evidence_pool import build_capability_evidence_pool_summary
|
||||
from guided_topic_discovery import build_guided_topics
|
||||
|
||||
@@ -9522,6 +9523,7 @@ def render_pl9_markdown(packet: dict) -> str:
|
||||
'',
|
||||
f'- 算法:{LEGACY_NARAYANA_NOTE_ZH}', # BUG-1215
|
||||
*rath_report_lines_zh(((worksheets.get('timing_and_predictive_systems') or {}).get('narayana_dasha') or {}).get('rath')), # BUG-1218
|
||||
*_narayana_comparison_lines_zh((worksheets.get('timing_and_predictive_systems') or {}).get('narayana_dasha')),
|
||||
'',
|
||||
'#### Patyayini 年内节奏支持',
|
||||
'',
|
||||
@@ -16244,7 +16246,7 @@ def cmd_full_reading(args):
|
||||
|
||||
# ── Step 4.13: Narayana Dasha(Rishi Dasha,v6.0.20)──
|
||||
try:
|
||||
from narayana_dasha import narayana_dasha_full_report
|
||||
from narayana_dasha import attach_narayana_method_comparison, narayana_dasha_full_report
|
||||
# 计算当前年龄(从出生到 today)
|
||||
birth_dt = _birth_datetime_from_args(args)
|
||||
today_dt = datetime.strptime(args.today, '%Y-%m-%d') if hasattr(args, 'today') and args.today else datetime.now()
|
||||
@@ -16256,8 +16258,10 @@ def cmd_full_reading(args):
|
||||
birth_year=args.year,
|
||||
)
|
||||
from narayana_legacy_label import label_legacy_narayana # BUG-1215
|
||||
report['modules']['narayana_dasha'] = label_legacy_narayana( # + Rath view, display only (BUG-1218)
|
||||
labeled = label_legacy_narayana( # + Rath view, display only (BUG-1218)
|
||||
narayana_result, lagna_sign_idx=asc_idx, planet_lons=planet_lons, current_age=current_age, birth_year=args.year)
|
||||
report['modules']['narayana_dasha'] = attach_narayana_method_comparison(
|
||||
labeled, asc_idx, planet_lons, current_age, args.year)
|
||||
except Exception as e:
|
||||
report['errors'].append(f"narayana-dasha: {e}")
|
||||
|
||||
|
||||
+176
-3
@@ -429,10 +429,64 @@ def _resolve_narayana_lord(
|
||||
return first if first_years >= second_years else second
|
||||
|
||||
|
||||
def _calc_narayana_mahadasha_profiled(
|
||||
lagna_sign_idx: int,
|
||||
planet_lons: Dict[str, float],
|
||||
start_year: float,
|
||||
*,
|
||||
duration_profile: str,
|
||||
sequence_profile: str,
|
||||
seed_profile: str,
|
||||
dual_lord_profile: str,
|
||||
) -> List[Dict]:
|
||||
"""Source-aware Mahadasha. The legacy two-argument path does not call this."""
|
||||
if seed_profile == "jaimini_lagna_seventh_strength_v1":
|
||||
selection = select_narayana_seed_sign(lagna_sign_idx, planet_lons)
|
||||
if selection.get("status") != "selected":
|
||||
raise ValueError(selection.get("reason") or "seed_not_selected")
|
||||
seed = int(selection["selected_sign_idx"])
|
||||
elif seed_profile == "legacy_lagna_v0":
|
||||
seed = lagna_sign_idx
|
||||
else:
|
||||
raise ValueError(f"unsupported Narayana seed profile: {seed_profile}")
|
||||
sequence = build_narayana_sign_sequence(seed, profile=sequence_profile)
|
||||
periods = []
|
||||
cum_years = start_year
|
||||
for sign_idx in sequence:
|
||||
lord = _resolve_narayana_lord(sign_idx, planet_lons, dual_lord_profile=dual_lord_profile)
|
||||
lord_sign_idx = _get_planet_sign(lord, planet_lons)
|
||||
if lord_sign_idx is None:
|
||||
lord_sign_idx = sign_idx
|
||||
years, _adjustment = calculate_narayana_period_years(
|
||||
sign_idx, lord, lord_sign_idx, profile=duration_profile,
|
||||
)
|
||||
periods.append({
|
||||
'sign': SIGNS[sign_idx],
|
||||
'sign_idx': sign_idx,
|
||||
'lord': lord,
|
||||
'lord_in_sign': SIGNS[lord_sign_idx],
|
||||
'lord_sign_idx': lord_sign_idx,
|
||||
'years': years,
|
||||
'count_from_to': f'{SIGNS[sign_idx]}({sign_idx})→{SIGNS[lord_sign_idx]}({lord_sign_idx})',
|
||||
'start_age': round(cum_years, 2),
|
||||
'end_age': round(cum_years + years, 2),
|
||||
'duration_profile': duration_profile,
|
||||
'sequence_profile': sequence_profile,
|
||||
'seed_profile': seed_profile,
|
||||
})
|
||||
cum_years += years
|
||||
return periods
|
||||
|
||||
|
||||
def calc_narayana_mahadasha(
|
||||
lagna_sign_idx: int,
|
||||
planet_lons: Dict[str, float],
|
||||
start_year: float = 0.0,
|
||||
*,
|
||||
duration_profile: str = "legacy_forward_v0",
|
||||
sequence_profile: str = "legacy_zodiacal_v0",
|
||||
seed_profile: str = "legacy_lagna_v0",
|
||||
dual_lord_profile: str = "legacy_primary_lord_v0",
|
||||
) -> List[Dict]:
|
||||
"""
|
||||
计算 Narayana Dasha 大运序列(第1周期)。
|
||||
@@ -447,7 +501,24 @@ def calc_narayana_mahadasha(
|
||||
'sign': str, 'sign_idx': int, 'lord': str,
|
||||
'years': int, 'start_age': float, 'end_age': float,
|
||||
}
|
||||
|
||||
默认四个 profile 走原来的 Lagna 顺行循环,返回字段与打分调用保持一致。
|
||||
"""
|
||||
if not (
|
||||
duration_profile == "legacy_forward_v0"
|
||||
and sequence_profile == "legacy_zodiacal_v0"
|
||||
and seed_profile == "legacy_lagna_v0"
|
||||
and dual_lord_profile == "legacy_primary_lord_v0"
|
||||
):
|
||||
return _calc_narayana_mahadasha_profiled(
|
||||
lagna_sign_idx,
|
||||
planet_lons,
|
||||
start_year,
|
||||
duration_profile=duration_profile,
|
||||
sequence_profile=sequence_profile,
|
||||
seed_profile=seed_profile,
|
||||
dual_lord_profile=dual_lord_profile,
|
||||
)
|
||||
periods = []
|
||||
cum_years = start_year
|
||||
|
||||
@@ -864,8 +935,23 @@ def narayana_dasha_full_report(
|
||||
"""
|
||||
result = {}
|
||||
|
||||
# 1. 完整大运序列
|
||||
mahadasha = calc_narayana_mahadasha(lagna_sign_idx, planet_lons)
|
||||
# 1. 完整大运序列。默认 profile 仍走原来的两参数调用。
|
||||
if (
|
||||
duration_profile == "legacy_forward_v0"
|
||||
and sequence_profile == "legacy_zodiacal_v0"
|
||||
and seed_profile == "legacy_lagna_v0"
|
||||
and dual_lord_profile == "legacy_primary_lord_v0"
|
||||
):
|
||||
mahadasha = calc_narayana_mahadasha(lagna_sign_idx, planet_lons)
|
||||
else:
|
||||
mahadasha = calc_narayana_mahadasha(
|
||||
lagna_sign_idx,
|
||||
planet_lons,
|
||||
duration_profile=duration_profile,
|
||||
sequence_profile=sequence_profile,
|
||||
seed_profile=seed_profile,
|
||||
dual_lord_profile=dual_lord_profile,
|
||||
)
|
||||
total_cycle = sum(p['years'] for p in mahadasha)
|
||||
result['mahadasha_sequence'] = mahadasha
|
||||
result['total_cycle_years'] = total_cycle
|
||||
@@ -912,6 +998,90 @@ def narayana_dasha_full_report(
|
||||
return result
|
||||
|
||||
|
||||
def narayana_report_with_variants(lagna_sign_idx, planet_lons, current_age=0, birth_year=0):
|
||||
"""Report the historical sequence and a source-aware sequence side by side.
|
||||
|
||||
The returned top-level Mahadasha stays the historical profile. Rectification
|
||||
scoring calls ``calc_narayana_mahadasha`` directly and does not read this.
|
||||
Rath Ketu table 12 is not in the local sequence profiles, so that case is
|
||||
blocked instead of replaced with another table.
|
||||
"""
|
||||
legacy = narayana_dasha_full_report(lagna_sign_idx, planet_lons, current_age, birth_year)
|
||||
selection = select_narayana_seed_sign(lagna_sign_idx, planet_lons)
|
||||
if selection.get('status') != 'selected':
|
||||
blocked = {'status': 'blocked', 'reason': selection.get('reason')}
|
||||
return {
|
||||
**legacy,
|
||||
'selected_method': 'legacy_comparison_only',
|
||||
'method_comparison': {'source_aware': blocked, 'legacy': legacy},
|
||||
'comparison_required': True,
|
||||
}
|
||||
seed = selection['selected_sign_idx']
|
||||
if _get_planet_sign('Saturn', planet_lons) == seed:
|
||||
sequence = 'rath_saturn_table11_v1'
|
||||
elif _get_planet_sign('Ketu', planet_lons) == seed:
|
||||
blocked = {'status': 'blocked', 'reason': 'rath_ketu_table12_v1_not_in_local_sequence_profiles'}
|
||||
return {
|
||||
**legacy,
|
||||
'selected_method': 'legacy_comparison_only',
|
||||
'method_comparison': {'source_aware': blocked, 'legacy': legacy},
|
||||
'comparison_required': True,
|
||||
}
|
||||
else:
|
||||
sequence = 'rath_general_table10_v1'
|
||||
settings = dict(
|
||||
duration_profile='jaimini_odd_footed_dignity_v2',
|
||||
sequence_profile=sequence,
|
||||
seed_profile='jaimini_lagna_seventh_strength_v1',
|
||||
dual_lord_profile='jaimini_dual_lord_source_v1',
|
||||
antardasha_profile='parashara_equal_v1',
|
||||
)
|
||||
try:
|
||||
native = narayana_dasha_full_report(
|
||||
lagna_sign_idx, planet_lons, current_age, birth_year, **settings,
|
||||
)
|
||||
except ValueError as exc:
|
||||
blocked = {'status': 'blocked', 'reason': str(exc), 'settings': settings}
|
||||
return {
|
||||
**legacy,
|
||||
'selected_method': 'legacy_comparison_only',
|
||||
'method_comparison': {'source_aware': blocked, 'legacy': legacy},
|
||||
'comparison_required': True,
|
||||
}
|
||||
return {
|
||||
**legacy,
|
||||
'selected_method': 'legacy_with_source_aware_comparison',
|
||||
'method_comparison': {'source_aware': native, 'legacy': legacy},
|
||||
'comparison_required': True,
|
||||
}
|
||||
|
||||
|
||||
def narayana_comparison_lines_zh(narayana) -> List[str]:
|
||||
"""One display line naming the source-aware seed rule beside the legacy sequence."""
|
||||
if not isinstance(narayana, dict):
|
||||
return []
|
||||
source = (narayana.get('method_comparison') or {}).get('source_aware')
|
||||
if not isinstance(source, dict):
|
||||
return []
|
||||
if source.get('status') == 'blocked':
|
||||
return [f"- 来源对照:blocked({source.get('reason')})"]
|
||||
seed = source.get('seed_selection') or {}
|
||||
return [f"- 来源对照:所选起运规则 {seed.get('profile')},起运星座 {seed.get('selected_sign')}"]
|
||||
|
||||
|
||||
def attach_narayana_method_comparison(result, lagna_sign_idx, planet_lons, current_age=0, birth_year=0):
|
||||
"""Attach the side-by-side view without replacing the historical sequence."""
|
||||
if not isinstance(result, dict):
|
||||
return result
|
||||
comparison = narayana_report_with_variants(
|
||||
lagna_sign_idx, planet_lons, current_age, birth_year,
|
||||
)
|
||||
result['method_comparison'] = comparison.get('method_comparison')
|
||||
result['comparison_required'] = True
|
||||
result['selected_method'] = comparison.get('selected_method')
|
||||
return result
|
||||
|
||||
|
||||
def _interpret_narayana(result: Dict, current_age: float) -> List[str]:
|
||||
"""Narayana Dasha 简要解读"""
|
||||
lines = []
|
||||
@@ -920,7 +1090,10 @@ def _interpret_narayana(result: Dict, current_age: float) -> List[str]:
|
||||
if md_seq:
|
||||
total = sum(p['years'] for p in md_seq)
|
||||
lines.append(f"Narayana Dasha 完整周期: {total} 年(12 星座 × 可变年数)")
|
||||
lines.append(f"起运星座: {result.get('lagna_sign', '?')}(Lagna)")
|
||||
seed = result.get('seed_selection') or {}
|
||||
rule = seed.get('profile') or result.get('seed_profile') or 'legacy_lagna_v0'
|
||||
sign = seed.get('selected_sign') or result.get('lagna_sign', '?')
|
||||
lines.append(f"起运星座: {sign}(所选起运规则 {rule})")
|
||||
|
||||
curr = result.get('current_dasha', {})
|
||||
md = curr.get('md')
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
"""Narayana display keeps the historical sequence and names the selected seed (BUG-1264)."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from scripts.narayana_dasha import (
|
||||
calc_narayana_mahadasha,
|
||||
narayana_dasha_full_report,
|
||||
narayana_report_with_variants,
|
||||
)
|
||||
|
||||
|
||||
LONGS = {
|
||||
"Sun": 10.0,
|
||||
"Moon": 40.0,
|
||||
"Mars": 70.0,
|
||||
"Mercury": 100.0,
|
||||
"Jupiter": 130.0,
|
||||
"Venus": 160.0,
|
||||
"Saturn": 15.0,
|
||||
"Rahu": 250.0,
|
||||
"Ketu": 70.0,
|
||||
}
|
||||
|
||||
|
||||
def test_default_calc_matches_the_explicit_legacy_profiles() -> None:
|
||||
plain = calc_narayana_mahadasha(0, LONGS)
|
||||
explicit = calc_narayana_mahadasha(
|
||||
0,
|
||||
LONGS,
|
||||
0.0,
|
||||
duration_profile="legacy_forward_v0",
|
||||
sequence_profile="legacy_zodiacal_v0",
|
||||
seed_profile="legacy_lagna_v0",
|
||||
dual_lord_profile="legacy_primary_lord_v0",
|
||||
)
|
||||
assert plain == explicit
|
||||
report = narayana_dasha_full_report(0, LONGS, current_age=30, birth_year=1990)
|
||||
assert [row["sign"] for row in report["mahadasha_sequence"]] == [row["sign"] for row in plain]
|
||||
assert report["interpretation"][1].startswith("起运星座: Aries(所选起运规则 legacy_lagna_v0)")
|
||||
assert "(Lagna)" not in report["interpretation"][1]
|
||||
|
||||
|
||||
def test_source_aware_comparison_does_not_replace_the_historical_sequence() -> None:
|
||||
compared = narayana_report_with_variants(0, LONGS, current_age=30, birth_year=1990)
|
||||
historical = narayana_dasha_full_report(0, LONGS, current_age=30, birth_year=1990)
|
||||
assert [row["years"] for row in compared["mahadasha_sequence"]] == [row["years"] for row in historical["mahadasha_sequence"]]
|
||||
source = compared["method_comparison"]["source_aware"]
|
||||
assert source["seed_selection"]["profile"] == "jaimini_lagna_seventh_strength_v1"
|
||||
assert "jaimini_lagna_seventh_strength_v1" in source["interpretation"][1]
|
||||
assert compared["comparison_required"] is True
|
||||
|
||||
|
||||
def test_ketu_in_the_seed_is_blocked_without_table_12() -> None:
|
||||
longs = dict(LONGS)
|
||||
longs["Saturn"] = 200.0
|
||||
longs["Ketu"] = 5.0
|
||||
compared = narayana_report_with_variants(0, longs, current_age=10, birth_year=1990)
|
||||
source = compared["method_comparison"]["source_aware"]
|
||||
assert source["status"] == "blocked"
|
||||
assert "rath_ketu_table12_v1" in source["reason"]
|
||||
assert compared["mahadasha_sequence"][0]["sign"] == "Aries"
|
||||
Reference in New Issue
Block a user