feat(tajika): year lord compared by the Tajika five-fold Panchavargiya (BUG-1217)

Ports _calc_native_panchavargiya_bala and its helpers verbatim from upstream
yinduzhanxing c7117f92 (astrologer round-5 Q5). calc_panchadhikari_year_lord
now scores candidates with it (engine tajika_native_five_fold); the
D1/D2/D3/D9/D12 dignity proxy no longer selects a year lord, and a missing
annual planet blocks instead of falling back to it. Basis note reads
「按 Tajika 五分力量」 / "by Tajika five-fold strength".

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-03 21:09:31 +08:00
co-authored by Claude Opus 5.5
parent 9d81f15afa
commit 1c39988b3c
6 changed files with 351 additions and 22 deletions
+2
View File
@@ -153,6 +153,8 @@ _PHRASES: dict[str, str] = {
# BUG-1214: tajika_year_lord.selection_basis_text (zh) → its en twin.
"年盘不全,暂用 Muntha 主星": "Annual chart incomplete; Muntha sign lord used for now",
"(Panchavargiya 为本地近似计算)": " (Panchavargiya is a local approximation)",
# BUG-1217: tajika_year_lord.ENGINE_NOTE["tajika_native_five_fold"]
"(按 Tajika 五分力量)": " (by Tajika five-fold strength)",
"(Panchavargiya 取 PyJHora 外部参照)": " (Panchavargiya from the PyJHora reference engine)",
"两种计算口径给出的年主不同,暂不合并": "The two methods give different year lords; not merged",
"(两者不同)": " (they differ)",
+179 -5
View File
@@ -14,6 +14,7 @@ Tajika/Varshaphala年运盘模块 v1.0
"""
from typing import Dict, List, Optional
from datetime import datetime, timedelta
from decimal import Decimal, ROUND_HALF_UP
import math
import json
import os
@@ -211,6 +212,13 @@ def calc_panchadhikari_year_lord(
('tri_rasi_lord', TRI_RASI_DAYTIME_LORDS[annual_asc_sign_idx] if is_daytime else TRI_RASI_NIGHTTIME_LORDS[annual_asc_sign_idx]),
]
# Local divergence (BUG-1217, astrologer round-5 Q5): candidates are
# compared by the Tajika five-fold Panchavargiya (upstream
# _calc_native_panchavargiya_bala), not the D1/D2/D3/D9/D12 dignity proxy.
# Upstream c7117f92 still falls back to the proxy here (upstream bug).
native_scores = _calc_native_panchavargiya_bala(
{planet: planet_lons[planet] for planet in CLASSICAL_PLANETS if planet in planet_lons}
)
candidates = []
seen = set()
for source, planet in raw_candidates:
@@ -226,7 +234,11 @@ def calc_panchadhikari_year_lord(
lon=lon,
tz=tz,
ayanamsa_name=ayanamsa_name,
native_scores=native_scores,
)
if pvb is None:
return {'status': 'blocked', 'reason': 'tajika_five_fold_panchavargiya_unavailable',
'detail': (native_scores.get(planet) or {}).get('reason')}
candidates.append(
{
'source': source,
@@ -236,6 +248,7 @@ def calc_panchadhikari_year_lord(
'benefic_aspects_asc': annual_asc_sign_idx in _benefic_aspect_targets(planet_house),
'malefic_aspects_asc': annual_asc_sign_idx in _malefic_aspect_targets(planet_house),
'panchavargiya_score': pvb['score'],
'panchavargiya_raw_total': pvb.get('raw_total'),
'panchavargiya_engine': pvb['engine'],
'panchavargiya_components': pvb['components'],
}
@@ -626,6 +639,154 @@ def calc_tajika_strength_layers(
}
# Ported verbatim from upstream yinduzhanxing c7117f92 (BUG-1217): Tajika five-fold
# Panchavargiya Bala (Kshetra/graha, Uchcha, Hadda, Drekkana, Navamsa; raw /80, score /20),
# rules in references/tajika_panchavargiya_rules.json. Astrologer round-5 Q5.
def _tajika_relation(planet: str, lord: str, annual_signs: Dict[str, int]) -> Dict:
"""Return the Tajika aspect relation used by Panchavargeeya Bala.
Tajika uses houses from the lord's annual-chart position to the planet's
annual-chart position: 3/5/9/11 friendly, 2/6/8/12 neutral, and
1/4/7/10 inimical. This intentionally does not use natural friendship or
Parashari Panchadha Maitri.
"""
if planet == lord:
return {'relation': 'own', 'house_from_lord': None}
house = ((annual_signs[planet] - annual_signs[lord]) % 12) + 1
relation_rules = _PANCHAVARGIYA_RULES['tajika_relation']
if house in relation_rules['friendly_houses']:
relation = 'friendly'
elif house in relation_rules['neutral_houses']:
relation = 'neutral'
else:
relation = 'inimical'
return {'relation': relation, 'house_from_lord': house}
def _panchavargiya_component(
planet: str,
lord: str,
annual_signs: Dict[str, int],
points: Dict[str, float],
*,
subdivision: str,
) -> Dict:
trace = _tajika_relation(planet, lord, annual_signs)
return {
'score': float(points[trace['relation']]),
'lord': lord,
'relation': trace['relation'],
'house_from_lord': trace['house_from_lord'],
'subdivision': subdivision,
}
def _hudda_lord(lon: float) -> str:
sign_idx = _sign_idx_from_lon(lon)
degree = float(lon) % 30.0
for boundary, lord in _PANCHAVARGIYA_RULES['hudda'][sign_idx]:
if degree < float(boundary) or math.isclose(degree, float(boundary)) and float(boundary) == 30.0:
return lord
raise ValueError(f'No Tajika Hudda lord for longitude {lon}')
def _tajika_drekkana_lord(lon: float) -> str:
sign_idx = _sign_idx_from_lon(lon)
division = min(2, int((float(lon) % 30.0) / 10.0))
return _PANCHAVARGIYA_RULES['drekkana_lords'][sign_idx][division]
def _navamsha_sign_idx(lon: float) -> int:
sign_idx = _sign_idx_from_lon(lon)
division = min(8, int((float(lon) % 30.0) / (30.0 / 9.0)))
if sign_idx in (0, 3, 6, 9):
start = sign_idx
elif sign_idx in (1, 4, 7, 10):
start = (sign_idx + 8) % 12
else:
start = (sign_idx + 4) % 12
return (start + division) % 12
def _tajika_uchcha_bala(planet: str, lon: float) -> float:
deep_debilitation = {
'Sun': 190.0, 'Moon': 213.0, 'Mars': 118.0, 'Mercury': 345.0,
'Jupiter': 275.0, 'Venus': 177.0, 'Saturn': 20.0,
}[planet]
distance = (float(lon) % 360.0 - deep_debilitation) % 360.0
if distance > 180.0:
distance = 360.0 - distance
return _round_half_up(distance / 9.0)
def _round_half_up(value: float, places: int = 2) -> float:
quantizer = Decimal('1').scaleb(-places)
return float(Decimal(str(value)).quantize(quantizer, rounding=ROUND_HALF_UP))
def _calc_native_panchavargiya_bala(planet_lons: Dict[str, float]) -> Dict[str, Dict]:
annual_signs = {planet: _sign_idx_from_lon(lon) for planet, lon in planet_lons.items()}
required = set(CLASSICAL_PLANETS)
if not required.issubset(annual_signs):
missing = sorted(required - set(annual_signs))
return {
planet: {'status': 'blocked', 'reason': f'annual_planet_positions_missing:{",".join(missing)}'}
for planet in planet_lons
}
result = {}
for planet, longitude in planet_lons.items():
sign_lord = SIGN_LORDS[SIGNS[annual_signs[planet]]]
graha = _panchavargiya_component(
planet, sign_lord, annual_signs,
{'own': 30.0, 'friendly': 22.5, 'neutral': 15.0, 'inimical': 7.5},
subdivision='rasi',
)
hudda = _panchavargiya_component(
planet, _hudda_lord(longitude), annual_signs,
{'own': 15.0, 'friendly': 11.25, 'neutral': 7.5, 'inimical': 3.75},
subdivision='hudda',
)
drekkana = _panchavargiya_component(
planet, _tajika_drekkana_lord(longitude), annual_signs,
{'own': 10.0, 'friendly': 7.5, 'neutral': 5.0, 'inimical': 2.5},
subdivision='tajika_drekkana',
)
navamsha_sign = _navamsha_sign_idx(longitude)
navamsha = _panchavargiya_component(
planet, SIGN_LORDS[SIGNS[navamsha_sign]], annual_signs,
{'own': 5.0, 'friendly': 3.75, 'neutral': 2.5, 'inimical': 1.25},
subdivision='navamsha',
)
components = {
'graha_bala': graha['score'],
'uchcha_bala': _tajika_uchcha_bala(planet, longitude),
'hudda_bala': hudda['score'],
'drekkana_bala': drekkana['score'],
'navamsha_bala': navamsha['score'],
}
raw_total = _round_half_up(sum(components.values()))
result[planet] = {
'status': 'native_rule_backed',
'engine': 'native_tajika_panchavargiya',
'score': _round_half_up(raw_total / 4.0),
'raw_total': raw_total,
'components': components,
'component_trace': {
'graha_bala': graha,
'hudda_bala': hudda,
'drekkana_bala': drekkana,
'navamsha_bala': {**navamsha, 'navamsha_sign': SIGNS[navamsha_sign]},
'uchcha_bala': {'score': components['uchcha_bala'], 'formula': 'shortest_arc_from_deep_debilitation_divided_by_9'},
},
'component_total_consistency': {
'status': 'consistent', 'formula': 'sum(Graha, Uchcha, Hudda, Drekkana, Navamsha) / 4',
'raw_total': raw_total, 'score': _round_half_up(raw_total / 4.0),
},
'rule_source': 'references/tajika_panchavargiya_rules.json',
}
return result
def _is_number(value) -> bool:
try:
float(value)
@@ -1215,7 +1376,8 @@ def _resolve_year_lord_panchavargiya_score(
lon: Optional[float],
tz: Optional[float],
ayanamsa_name: Optional[str],
) -> Dict:
native_scores: Optional[Dict[str, Dict]] = None,
) -> Optional[Dict]:
external = _external_pyjhora_panchavargiya_score(
planet,
annual_jd=annual_jd,
@@ -1226,11 +1388,23 @@ def _resolve_year_lord_panchavargiya_score(
)
if external is not None:
return external
proxy = _calc_panchavargiya_bala_for_planet(planet, lon_value)
if native_scores is None:
# Upstream c7117f92 behaviour (dignity proxy); no product caller.
proxy = _calc_panchavargiya_bala_for_planet(planet, lon_value)
return {
'score': proxy['score'],
'engine': 'native_proxy',
'components': proxy['components'],
}
# BUG-1217: Tajika five-fold Panchavargiya; None when it cannot be computed.
native = native_scores.get(planet) or {}
if native.get('status') != 'native_rule_backed':
return None
return {
'score': proxy['score'],
'engine': 'native_proxy',
'components': proxy['components'],
'score': native['score'],
'engine': 'tajika_native_five_fold',
'raw_total': native['raw_total'],
'components': native['components'],
}
+14 -8
View File
@@ -14,14 +14,18 @@ producers (`solar_return.solar_return_full_report` and
compares two runs of the same rule. Muntha stays natal Lagna + completed
years (BUG-1212); it is handed to the selector explicitly.
Panchavargiya engine (local divergence from upstream, BUG-1214): upstream
hands the annual JD to the selector, which then imports PyJHora in-process
when the interpreter has it. PyJHora is AGPL and stays an external reference
engine (requirements-reference-engines.txt); the API image and CI do not have
it, so a developer machine that does would pick a different year lord than
production. The product path therefore always uses the native proxy
(`engine: native_proxy`); set JYOTISH_YEAR_LORD_PYJHORA_REFERENCE=1 to pass the
annual JD and let the upstream PyJHora lookup run (research comparison only).
Panchavargiya engine (local divergence from upstream, BUG-1214 / BUG-1217):
upstream hands the annual JD to the selector, which then imports PyJHora
in-process when the interpreter has it, else falls back to a D1/D2/D3/D9/D12
dignity proxy. PyJHora is AGPL and stays an external reference engine
(requirements-reference-engines.txt); the API image and CI do not have it, so
the product path never hands it the JD. Since BUG-1217 (astrologer round-5
Q5) the candidates are compared by the Tajika five-fold Panchavargiya
(upstream `_calc_native_panchavargiya_bala`: Kshetra/graha, Uchcha, Hadda,
Drekkana, Navamsa; raw out of 80, score = raw / 4 out of 20;
`engine: tajika_native_five_fold`); the dignity proxy no longer selects a year
lord. Set JYOTISH_YEAR_LORD_PYJHORA_REFERENCE=1 to pass the annual JD and let
the upstream PyJHora lookup run (research comparison only).
"""
from __future__ import annotations
@@ -42,6 +46,7 @@ MUNTHA_BASIS = "natal_ascendant_plus_completed_years"
FALLBACK_BASIS = "fallback_muntha_sign_lord_after_panchadhikari_block"
ENGINE_NOTE = {
"tajika_native_five_fold": ("按 Tajika 五分力量", "by Tajika five-fold strength"),
"native_proxy": ("Panchavargiya 为本地近似计算", "Panchavargiya is a local approximation"),
"pyjhora": ("Panchavargiya 取 PyJHora 外部参照", "Panchavargiya from the PyJHora reference engine"),
"pyjhora_sidecar": ("Panchavargiya 取 PyJHora 外部参照", "Panchavargiya from the PyJHora reference engine"),
@@ -94,6 +99,7 @@ def _candidate_list(candidates: List[Dict[str, Any]], winner: Optional[str]) ->
"benefic_aspects_asc": bool(row.get("benefic_aspects_asc")),
"malefic_aspects_asc": bool(row.get("malefic_aspects_asc")),
"panchavargiya_score": row.get("panchavargiya_score"),
"panchavargiya_raw_total": row.get("panchavargiya_raw_total"),
"panchavargiya_engine": row.get("panchavargiya_engine"),
"selected": row.get("planet") == winner,
} for row in candidates if isinstance(row, dict)]
@@ -0,0 +1,139 @@
"""Tajika five-fold Panchavargiya chooses the year lord (BUG-1217).
Astrologer round-5 ruling 2026-10-03, Q5: compare the Panchadhikari candidates
by the Tajika five-fold Panchavargiya (Kshetra/graha, Uchcha, Hadda, Drekkana,
Navamsa; raw out of 80, score = raw / 4 out of 20), not the D1/D2/D3/D9/D12
dignity proxy. The five-fold function is ported verbatim from upstream
yinduzhanxing c7117f92 (`_calc_native_panchavargiya_bala`).
"""
from __future__ import annotations
import ast
import hashlib
import inspect
import random
import subprocess
import sys
from pathlib import Path
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 import tajika # noqa: E402
UPSTREAM = Path("/workspace/yinduzhanxing")
UPSTREAM_SHA = "c7117f92"
CAPS = {"graha_bala": 30.0, "uchcha_bala": 20.0, "hudda_bala": 15.0, "drekkana_bala": 10.0, "navamsha_bala": 5.0}
# Annual signs: Sun Aries 5deg, Moon Taurus, Mars Leo, Mercury Gemini,
# Jupiter Sagittarius, Venus Pisces, Saturn Libra.
CHART = {"Sun": 5.0, "Moon": 40.0, "Mars": 130.0, "Mercury": 70.0, "Jupiter": 250.0, "Venus": 340.0, "Saturn": 190.0}
@pytest.fixture(autouse=True)
def _no_reference_engine(monkeypatch):
monkeypatch.delenv("JYOTISH_YEAR_LORD_PYJHORA_REFERENCE", raising=False)
def test_hand_worked_sun() -> None:
"""Sun 5deg Aries, worked by hand from references/tajika_panchavargiya_rules.json.
graha: sign lord Mars (Leo) -> Sun is 9th from Mars -> friendly 22.5
uchcha: arc from deep debilitation 190deg = 175 -> 175 / 9 = 19.44
hudda: Aries 0-6 Jupiter (Sagittarius) -> Sun 5th from it -> friendly 11.25
drekkana: Aries 0-10 Mars -> friendly 7.5
navamsa: Aries 5deg -> Taurus, lord Venus (Pisces) -> Sun 2nd -> neutral 2.5
raw 63.19, score 63.19 / 4 = 15.7975 -> 15.80
"""
sun = tajika._calc_native_panchavargiya_bala(CHART)["Sun"]
assert sun["components"] == {"graha_bala": 22.5, "uchcha_bala": 19.44, "hudda_bala": 11.25,
"drekkana_bala": 7.5, "navamsha_bala": 2.5}
assert sun["raw_total"] == 63.19
assert sun["score"] == 15.8
assert sun["component_trace"]["hudda_bala"]["lord"] == "Jupiter"
assert sun["component_trace"]["navamsha_bala"]["navamsha_sign"] == "Taurus"
def test_components_caps_and_score_on_random_charts() -> None:
rng = random.Random(20261003)
for _ in range(300):
chart = {planet: rng.uniform(0, 360) for planet in tajika.CLASSICAL_PLANETS}
for planet, row in tajika._calc_native_panchavargiya_bala(chart).items():
assert set(row["components"]) == set(CAPS)
for key, cap in CAPS.items():
assert 0 <= row["components"][key] <= cap, (planet, key)
assert row["raw_total"] <= 80.0
assert row["raw_total"] == tajika._round_half_up(sum(row["components"].values()))
assert row["score"] == tajika._round_half_up(row["raw_total"] / 4.0)
assert row["score"] <= 20.0
def test_own_sign_lord_scores_full_kshetra() -> None:
chart = dict(CHART, Mars=10.0) # Mars in Aries, its own sign
assert tajika._calc_native_panchavargiya_bala(chart)["Mars"]["components"]["graha_bala"] == 30.0
def test_year_lord_candidates_use_the_five_fold_scores() -> None:
result = tajika.calc_panchadhikari_year_lord(CHART, annual_asc_sign_idx=0, natal_lagna_sign_idx=3,
years_from_dob=10, muntha_sign_idx=6)
assert result["status"] == "partial_verified"
native = tajika._calc_native_panchavargiya_bala(CHART)
for row in result["candidates"]:
assert row["panchavargiya_engine"] == "tajika_native_five_fold"
assert row["panchavargiya_score"] == native[row["planet"]]["score"]
assert row["panchavargiya_raw_total"] == native[row["planet"]]["raw_total"]
assert row["panchavargiya_components"] == native[row["planet"]]["components"]
def test_dignity_proxy_never_selects(monkeypatch) -> None:
def boom(*_args, **_kwargs):
raise AssertionError("dignity proxy must not be used for the year lord")
monkeypatch.setattr(tajika, "_calc_panchavargiya_bala_for_planet", boom)
result = tajika.calc_panchadhikari_year_lord(CHART, annual_asc_sign_idx=0, natal_lagna_sign_idx=3,
years_from_dob=10, muntha_sign_idx=6)
assert result["status"] == "partial_verified"
def test_missing_annual_planet_blocks_instead_of_using_the_proxy() -> None:
partial = {key: value for key, value in CHART.items() if key != "Saturn"}
result = tajika.calc_panchadhikari_year_lord(partial, annual_asc_sign_idx=0, natal_lagna_sign_idx=3,
years_from_dob=10, muntha_sign_idx=6)
assert result["status"] == "blocked"
assert result["reason"] == "tajika_five_fold_panchavargiya_unavailable"
def _function_source(source: str, name: str) -> str:
tree = ast.parse(source)
node = next(n for n in tree.body if isinstance(n, ast.FunctionDef) and n.name == name)
return "\n".join(source.splitlines()[node.lineno - 1:node.end_lineno])
# sha256 of each function text as ported from upstream c7117f92.
PORTED_SHA256 = {
"_tajika_relation": "c218f423ebb22ac24f841450db772a4c0d52865a6bbcba3c5d4fe0ca06576449",
"_panchavargiya_component": "f8725f04282b46e43df28158ae345ab02de8beb815fbdab000b227277a56d543",
"_hudda_lord": "1278496e99fd90c099021d20590a36f8666cab5a612bbe6bd75fb22a5fac1bd5",
"_tajika_drekkana_lord": "72128eea38e1e6ca343b45f5ae978b433c857739b425548e846704637a8bc50e",
"_navamsha_sign_idx": "79113057dbf0e5f026557c1d9af39bcd414e1b7fa0996ebab9f3e287de8c78fd",
"_tajika_uchcha_bala": "22768c89efcc794a8523252db1824f8521273fcfd0497bb27c426596101afa85",
"_round_half_up": "5defceacea3f799dd83e9a1b57e3bcc6251ede13fb5aa4d4dfb6419767f5722e",
"_calc_native_panchavargiya_bala": "9e7f3ae88c2f8dd04ea5479c732970bf912153f96a3a740a9999de13d6f7c052",
}
@pytest.mark.parametrize("name", sorted(PORTED_SHA256))
def test_ported_functions_are_verbatim(name: str) -> None:
ours = _function_source(inspect.getsource(tajika), name)
assert hashlib.sha256(ours.encode("utf-8")).hexdigest() == PORTED_SHA256[name]
if not (UPSTREAM / ".git").exists():
pytest.skip("upstream checkout not on this machine")
upstream = subprocess.run(["git", "show", f"{UPSTREAM_SHA}:scripts/tajika.py"], cwd=UPSTREAM,
capture_output=True, text=True, check=False)
if upstream.returncode != 0:
pytest.skip("upstream commit not available")
assert ours == _function_source(upstream.stdout, name)
+9 -5
View File
@@ -24,7 +24,7 @@ UPSTREAM_RULES_BLOB = "e8c15ac31246a1b923f2d108729c319f8731d179" # c7117f92:ref
@pytest.fixture(autouse=True)
def _native_panchavargiya(monkeypatch):
# Product path: native-proxy Panchavargiya unless the research opt-in is set.
# Product path: Tajika five-fold Panchavargiya unless the research opt-in is set.
monkeypatch.delenv("JYOTISH_YEAR_LORD_PYJHORA_REFERENCE", raising=False)
@@ -107,10 +107,13 @@ def test_muntha_candidate_uses_the_supplied_natal_muntha(monkeypatch) -> None:
"panchadhikari_malefic_shortlist_pvb", "panchadhikari_pvb_fallback_all_candidates",
])
def test_every_basis_has_a_sentence(basis: str) -> None:
zh, en = selection_basis_text({"selection_basis": basis, "year_lord": "Mars", "panchavargiya_engine": "native_proxy",
# 原值:engine native_proxy,句尾「(Panchavargiya 为本地近似计算)」/「(Panchavargiya is a local approximation)」
# 新值:engine tajika_native_five_fold,句尾「(按 Tajika 五分力量)」/「(by Tajika five-fold strength)」
# 原因:BUG-1217(占星师第五轮问 5)年主比强弱改用 Tajika 正式五分法
zh, en = selection_basis_text({"selection_basis": basis, "year_lord": "Mars", "panchavargiya_engine": "tajika_native_five_fold",
"candidates": [{"planet": "Mars", "benefic_aspects_asc": True, "malefic_aspects_asc": True}]})
assert "Mars" in zh and zh.endswith("(Panchavargiya 为本地近似计算)")
assert "Mars" in en and en.endswith("(Panchavargiya is a local approximation)")
assert "Mars" in zh and zh.endswith("(按 Tajika 五分力量)")
assert "Mars" in en and en.endswith("(by Tajika five-fold strength)")
FICTIONAL = {"birth": {"date": "1990-01-01", "time": "12:00:00", "latitude": 39.9, "longitude": 116.4, "utc_offset": "+08:00"},
@@ -128,7 +131,8 @@ def test_both_annual_producers_select_the_same_year_lord(target_year: int) -> No
assert value["selection_basis"].startswith("panchadhikari_")
assert value["muntha_basis"] == "natal_ascendant_plus_completed_years"
assert value["selection_basis_text"] and value["selection_basis_text_en"]
assert {row["panchavargiya_engine"] for row in value["candidate_list"]} == {"native_proxy"}
# 原值 {"native_proxy"} / 新值 {"tajika_native_five_fold"} / 原因 BUG-1217(第五轮问 5)
assert {row["panchavargiya_engine"] for row in value["candidate_list"]} == {"tajika_native_five_fold"}
assert sum(row["selected"] for row in value["candidate_list"]) == 1
assert values[0]["candidates"] == values[1]["candidates"]
assert values[0]["year_lord"] == values[1]["year_lord"]
+8 -4
View File
@@ -26,7 +26,7 @@ ROOT = Path(__file__).resolve().parents[1]
@pytest.fixture(autouse=True)
def _native_panchavargiya(monkeypatch):
# Product path: native-proxy Panchavargiya unless the research opt-in is set.
# Product path: Tajika five-fold Panchavargiya unless the research opt-in is set.
monkeypatch.delenv("JYOTISH_YEAR_LORD_PYJHORA_REFERENCE", raising=False)
@@ -42,8 +42,11 @@ def test_report_shows_the_basis_in_both_editions() -> None:
en = _pl9_export_markdown_for_edition(english, "reader_main")
zh_rows = [line for line in zh.splitlines() if line.startswith("| Selection basis |")]
en_rows = [line for line in en.splitlines() if line.startswith("| Selection basis |")]
assert zh_rows and all("年盘上升" in row and "Panchavargiya 为本地近似计算" in row for row in zh_rows)
assert en_rows and all("annual Lagna" in row and "local approximation" in row for row in en_rows)
# 原值:中文含「Panchavargiya 为本地近似计算」、英文含「local approximation」
# 新值:中文含「按 Tajika 五分力量」、英文含「Tajika five-fold strength」
# 原因:BUG-1217(占星师第五轮问 5)年主比强弱改用 Tajika 正式五分法
assert zh_rows and all("年盘上升" in row and "按 Tajika 五分力量" in row for row in zh_rows)
assert en_rows and all("annual Lagna" in row and "Tajika five-fold strength" in row for row in en_rows)
assert not re.search(r"[\u4e00-\u9fff]", "\n".join(en_rows))
assert "暂用 Muntha 主星" not in zh
assert zh.count("| Varshesha |") == zh.count("| Selection basis |")
@@ -61,7 +64,8 @@ def test_consultation_annual_card_carries_the_basis() -> None:
# 新值:varshesha_basis 是年主选法依据一句话(tajika_year_lord.selection_basis_text)
# 原因:BUG-1214(任务书 T2)
assert "年盘上升" in annual["varshesha_basis"] and "暂用" not in annual["varshesha_basis"]
assert annual["varshesha_basis"].endswith("(Panchavargiya 为本地近似计算)")
# 原值 endswith「(Panchavargiya 为本地近似计算)」/ 新值 endswith「(按 Tajika 五分力量)」/ 原因 BUG-1217
assert annual["varshesha_basis"].endswith("(按 Tajika 五分力量)")
card = (ROOT / "frontend/src/lib/consultation-evidence-card.ts").read_text(encoding="utf-8")
assert '"varshesha", "varshesha_basis"' in card
workflow = (ROOT / "frontend/src/mastra/consultation-workflow.ts").read_text(encoding="utf-8")