feat(consult): rule engine always runs and merges with chart yogas (BUG-1174); closed-list traditional_yogas on general/career/wealth cards (BUG-1175)

Goldens regenerated by the capture scripts (PYTHONHASHSEED=0, cache TTL 0; two runs byte-identical).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01N4f2nya58RoRu4yEmJgRGE
This commit is contained in:
Jesse_Chen
2026-10-02 08:32:11 +08:00
co-authored by Claude Opus 5.5
parent 8e978cabc4
commit c74bfcc06b
7 changed files with 236 additions and 23 deletions
@@ -0,0 +1,92 @@
"""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 == "传统格局,本站未验证,只作参考"
assert layer["names"] == ["Sasa Yoga", "Gajakesari Yoga", "Neecha Bhanga Raja Yoga", "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"