Files
Jyotisha/tests/test_round6_self_reference.py
T
ClaudeandClaude Opus 5.5 b7ba7f4e4f fix(consult): R3 witness fields and Narayana profile_config reach the real card (BUG-1786, BUG-1783)
Found in the round-7 cross-branch check: on public charts (Zidane, Obama)
the Neecha Bhanga row never reached yogas_detail. The compact detector entry
dropped formation_evidence, and the row sat past the 30-row cap. Keep the
witness fields when compacting and never cut a row that carries them (the
cap stays 30). The card's Narayana method_status also dropped the engine's
profile_config (timing key allowlist); carry it through.

Evidence-card golden recaptured with TTL=0 (Obama: row 30 is now Neecha
Bhanga); the load-dependent VedAstro closure reason is kept as before.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DbBXoyn7Hj3LCW3BUs5bZ8
2026-11-09 00:03:21 +08:00

120 lines
7.0 KiB
Python

"""占星师 2026-11-08 第六轮 R3: Moon self-reference is visible and never independent confirmation.
Ported from her `tests/test_round6_self_reference.py` (yinduzhanxing 62791acb, 14 cases).
Her cases call `YogaEngine._eval_neecha_bhanga` (rule `neechabhanga_basic`); in this
product that JSON rule is reference_only and the one Neecha Bhanga verdict comes from
the main detector `yogas_doshas.calc_nicha_bhanga_raj_yoga` (BUG-1205), whose rows the
rule engine appends as `main_detector:neecha_bhanga`. Same inputs, same assertions, on
the detector the product runs. Synthetic inputs, not event validation.
"""
from pathlib import Path
import sys
import pytest
ROOT = Path(__file__).resolve().parents[1]
for entry in (str(ROOT), str(ROOT / 'scripts')):
if entry not in sys.path:
sys.path.insert(0, entry)
from scripts.yoga_engine import YogaEngine, SIGNS # noqa: E402
from scripts.yogas_doshas import calc_nicha_bhanga_raj_yoga # noqa: E402
def evidence(planets):
rows = YogaEngine(str(ROOT / 'references/yoga_rules.json')).detect(planets, 'Leo')
return next(r['formation_evidence'] for r in rows
if r.get('rule_id') == 'main_detector:neecha_bhanga'
and r['formation_evidence']['planet'] == 'Jupiter')
@pytest.mark.parametrize('moon_sign', SIGNS)
def test_jupiter_moon_self_reference_is_visible_for_every_moon_sign(moon_sign):
# Jupiter's exaltation sign is Cancer, ruled by Moon. Moon from itself
# is always H1; moving Moon cannot create independent corroboration.
result = evidence({'Jupiter': {'sign': 'Capricorn', 'house': 6},
'Moon': {'sign': moon_sign, 'house': (SIGNS.index(moon_sign) - 4) % 12 + 1}})
witness = next(r for r in result['cancellation_reasons']
if r['condition'] == 'exaltation_sign_lord_kendra_from_moon')
assert witness['self_reference'] is True
assert witness['independent_confirmation_allowed'] is False
assert witness['reference'] == 'Moon'
assert witness['relative_house'] == 1
def test_self_reference_only_is_not_hidden_or_promoted_to_nonself_support():
result = evidence({'Jupiter': {'sign': 'Capricorn', 'house': 6},
'Moon': {'sign': 'Aries', 'house': 9}})
assert result['self_reference_only'] is True
# Her `royal_yoga_candidate` is False here (Jupiter in H6); this detector never
# names a Raja Yoga (BUG-1205), so the equivalent is: no independent condition.
assert result['independent_condition_count'] == 0
assert result['profile'] == 'light_on_life_p295'
def test_distinct_geometric_condition_survives_beside_self_reference():
result = evidence({'Jupiter': {'sign': 'Capricorn', 'house': 6},
'Moon': {'sign': 'Aries', 'house': 9},
'Saturn': {'sign': 'Leo', 'house': 1}})
assert result['self_reference_only'] is False
assert any(r['condition'] == 'dispositor_kendra_from_lagna'
for r in result['cancellation_reasons'])
assert any(r.get('self_reference') for r in result['cancellation_reasons'])
def test_literal_branch_stays_formed_but_self_reference_does_not_raise_strength():
# The literal condition is not deleted: the yoga is still formed on it alone.
only = calc_nicha_bhanga_raj_yoga({'Jupiter': {'sign': 'Capricorn', 'house': 6},
'Moon': {'sign': 'Aries', 'house': 9}}, {})['yogas'][0]
assert only['cancellation_count'] == 1 and only['independent_cancellation_count'] == 0
assert only['self_reference_only'] is True and only['strength'] == 'moderate'
# Mars debilitated in Cancer: dispositor Moon from the Moon is the same tautology.
mars = calc_nicha_bhanga_raj_yoga({'Mars': {'sign': 'Cancer', 'house': 12},
'Moon': {'sign': 'Taurus', 'house': 10}}, {})['yogas'][0]
witness = next(c for c in mars['cancellation_conditions'] if c['condition'] == 'dispositor_kendra_from_moon')
assert witness['self_reference'] is True and witness['independent_confirmation_allowed'] is False
lagna = next(c for c in mars['cancellation_conditions'] if c['condition'] == 'dispositor_kendra_from_lagna')
assert lagna['self_reference'] is False and 'independent_confirmation_allowed' not in lagna
assert mars['independent_cancellation_count'] == mars['cancellation_count'] - 1
def test_card_layer_carries_the_witness_fields():
from scripts.main_yoga_detectors import neecha_bhanga_rows
row = neecha_bhanga_rows({'Jupiter': {'sign': 'Capricorn', 'house': 6},
'Moon': {'sign': 'Aries', 'house': 9}})[0]
assert row['formation_evidence']['self_reference_only'] is True
reason = row['formation_evidence']['cancellation_reasons'][0]
assert {k: reason[k] for k in ('reference', 'relative_house', 'self_reference',
'independent_confirmation_allowed')} == {
'reference': 'Moon', 'relative_house': 1, 'self_reference': True,
'independent_confirmation_allowed': False}
def test_witness_fields_survive_the_real_pipeline_compact_and_row_cap():
"""BUG-1786 补(round-7 integration 2026-11-08):公开盘 Zidane(火星落陷巨蟹,定位星月亮)
与 Obama(木星落陷,擢升星座主星月亮)的 Neecha Bhanga 行在主检测器里带见证字段,但
`_compact_yoga_entry` 把 formation_evidence 压掉了,且该行排在第 45 位,被 `rows[:30]` 截掉,
卡上的 yogas_detail 没有这一行。这里用同样的形状(火星落陷巨蟹、月亮为定位星)走压缩 → 筛 → 卡层。"""
from scripts.consultation_native_layers import YOGA_CONDITION_ROW_CAP, build_yoga_conditions
from scripts.jyotish_api_server import _compact_yoga_entry
from scripts.main_yoga_detectors import neecha_bhanga_rows
planets = {'Mars': {'sign': 'Cancer', 'house': 12}, 'Moon': {'sign': 'Taurus', 'house': 10}}
compact = _compact_yoga_entry(neecha_bhanga_rows(planets)[0])
evidence = compact['formation_evidence']
moon = next(r for r in evidence['cancellation_reasons'] if r['condition'] == 'dispositor_kendra_from_moon')
assert moon == {'condition': 'dispositor_kendra_from_moon', 'planet': 'Moon', 'reference': 'Moon',
'relative_house': 1, 'self_reference': True, 'independent_confirmation_allowed': False}
assert evidence['independent_condition_count'] == len(evidence['cancellation_reasons']) - 1
assert evidence['self_reference_only'] is False
filler = [{'name': f'Filler Yoga {i}', 'planets': ['Sun']} for i in range(YOGA_CONDITION_ROW_CAP + 15)]
modules = {'raman_support': {'yogas': {'status': 'executed', 'hits': [], 'ungoverned_detected': filler + [compact]}}}
rows = build_yoga_conditions({'planets': planets}, modules, {})['rows']
assert len(rows) == YOGA_CONDITION_ROW_CAP
neecha = next(row for row in rows if row['name'] == 'Neecha Bhanga')
assert neecha['formation_evidence'] == evidence
# The ordinary rows keep their order; only the last one gives way.
assert [row['name'] for row in rows[:-1]] == [f'Filler Yoga {i}' for i in range(YOGA_CONDITION_ROW_CAP - 1)]