Astrologer ruling 乙14. One condition source, yogas_doshas.calc_nicha_bhanga_raj_yoga (conditions A-F, now also structured). New scripts/main_yoga_detectors.py turns it into yoga_engine-shaped rows (zh + en twins) that YogaEngine.detect appends; the four neechabhanga_* rules are reference_only (kept, renamed, not evaluated). Card: traditional_yogas canonical 落陷取消(Neecha Bhanga) with 成立条件; compact yoga entries keep conditions/reductions. Report: category 落陷取消, mixed group, not raja; personal-report alias neecha_bhanga -> special. Changed assertion (test_consultation_yoga_detection_merge): canonical name 'Neecha Bhanga Raja Yoga' -> '落陷取消(Neecha Bhanga)' (ruling 乙14). Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01N4f2nya58RoRu4yEmJgRGE
94 lines
3.4 KiB
Python
94 lines
3.4 KiB
Python
"""T2 / T3 of TASK-consult-career-yoga-functional-20261002 (BUG-1174, BUG-1175)."""
|
||
|
||
from __future__ import annotations
|
||
|
||
from pathlib import Path
|
||
|
||
from scripts.consultation_native_layers import (
|
||
TRADITIONAL_YOGA_LABEL,
|
||
build_traditional_yogas,
|
||
collect_consultation_yogas,
|
||
merge_detected_yogas,
|
||
)
|
||
|
||
ROOT = Path(__file__).resolve().parents[1]
|
||
|
||
|
||
def _compact(item):
|
||
if isinstance(item, dict) and item.get("name"):
|
||
return {"name": item["name"]}
|
||
if isinstance(item, str) and item:
|
||
return {"name": item}
|
||
return None
|
||
|
||
|
||
class _Handler:
|
||
def __init__(self):
|
||
self.calls = 0
|
||
|
||
def _compute_yogas_api(self, body):
|
||
self.calls += 1
|
||
return {"result": {
|
||
"extended_yogas": [{"name": "Sasa Yoga"}, {"name": "Amala Yoga"}],
|
||
"rule_engine_yogas": [{"name": "Gajakesari Yoga"}],
|
||
}}
|
||
|
||
|
||
def test_rule_engine_runs_even_when_chart_already_has_yogas() -> None:
|
||
handler = _Handler()
|
||
chart = {"yogas": [{"name": "Amala Yoga"}, {"name": "Amala Yoga"}]}
|
||
merged = collect_consultation_yogas(handler, chart, {}, {"Sun": {}}, {"sign": "Aries"}, _compact)
|
||
assert handler.calls == 1
|
||
assert [row["name"] for row in merged] == ["Amala Yoga", "Sasa Yoga", "Gajakesari Yoga"]
|
||
|
||
|
||
def test_merge_dedupes_by_normalised_name() -> None:
|
||
merged = merge_detected_yogas([{"name": "Amala Yoga"}], [{"name": "amala yoga"}, {"name": "Amala"}, {"name": "Vosi Yoga"}])
|
||
assert [row["name"] for row in merged] == ["Amala Yoga", "Vosi Yoga"]
|
||
|
||
|
||
def test_engine_failure_keeps_the_chart_list() -> None:
|
||
class Broken:
|
||
def _compute_yogas_api(self, body):
|
||
raise RuntimeError("boom")
|
||
|
||
merged = collect_consultation_yogas(Broken(), {"yogas": ["Adhi Yoga"]}, {}, {}, None, _compact)
|
||
assert [row["name"] for row in merged] == ["Adhi Yoga"]
|
||
|
||
|
||
def test_server_no_longer_skips_the_rule_engine() -> None:
|
||
source = (ROOT / "scripts" / "jyotish_api_server.py").read_text(encoding="utf-8")
|
||
block = source[source.index("if not _consultation_layer_present(modules.get('yogas')):"):]
|
||
block = block[: block.index("routes = _consultation_declared_routes(body)")]
|
||
assert "collect_consultation_yogas" in block
|
||
assert "if not compact:" not in block
|
||
|
||
|
||
def _modules(hits, ungoverned):
|
||
return {"raman_support": {"yogas": {"status": "executed", "hits": hits, "ungoverned_detected": ungoverned}}}
|
||
|
||
|
||
def test_traditional_yogas_closed_list_only_and_fixed_label() -> None:
|
||
layer = build_traditional_yogas(_modules(
|
||
[{"name": "Gaja Kesari", "hit": True}],
|
||
[
|
||
{"name": "Sasa Yoga"},
|
||
{"name": "Neechabhanga Raja Yoga"},
|
||
{"name": "Neechabhanga Raja Yoga (Kendra)"},
|
||
{"name": "Budhaditya Yoga"},
|
||
{"name": "Surya-Budha Yoga"},
|
||
{"name": "Kemadruma Yoga"},
|
||
{"name": "Raja Yoga (Mutual Aspect)"},
|
||
],
|
||
))
|
||
assert layer["status"] == "executed"
|
||
assert layer["label"] == TRADITIONAL_YOGA_LABEL == "传统格局,本站未验证,只作参考"
|
||
# BUG-1205: the Neecha Bhanga canonical is no longer called Raja Yoga.
|
||
assert layer["names"] == ["Sasa Yoga", "Gajakesari Yoga", "落陷取消(Neecha Bhanga)", "Budha-Aditya Yoga"]
|
||
|
||
|
||
def test_traditional_yogas_skip_misses_and_empty() -> None:
|
||
layer = build_traditional_yogas(_modules([{"name": "Gajakesari Yoga", "hit": False}], []))
|
||
assert layer["names"] == []
|
||
assert build_traditional_yogas({})["status"] == "blocked"
|