From 1c39988b3c72777c3da23448b2a1043313bfd69f Mon Sep 17 00:00:00 2001 From: Jesse_Chen Date: Sat, 3 Oct 2026 19:20:14 +0800 Subject: [PATCH] feat(tajika): year lord compared by the Tajika five-fold Panchavargiya (BUG-1217) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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) Claude-Session: https://claude.ai/code/session_01N4f2nya58RoRu4yEmJgRGE --- scripts/pl9_reader_english_terms.py | 2 + scripts/tajika.py | 184 ++++++++++++++++++- scripts/tajika_year_lord.py | 22 ++- tests/test_tajika_five_fold_panchavargiya.py | 139 ++++++++++++++ tests/test_tajika_panchadhikari_year_lord.py | 14 +- tests/test_year_lord_basis_label.py | 12 +- 6 files changed, 351 insertions(+), 22 deletions(-) create mode 100644 tests/test_tajika_five_fold_panchavargiya.py diff --git a/scripts/pl9_reader_english_terms.py b/scripts/pl9_reader_english_terms.py index 61853c44..a2e1dee4 100644 --- a/scripts/pl9_reader_english_terms.py +++ b/scripts/pl9_reader_english_terms.py @@ -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)", diff --git a/scripts/tajika.py b/scripts/tajika.py index 590476af..f1db4076 100644 --- a/scripts/tajika.py +++ b/scripts/tajika.py @@ -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'], } diff --git a/scripts/tajika_year_lord.py b/scripts/tajika_year_lord.py index 05d3e96a..ec9f0ac4 100644 --- a/scripts/tajika_year_lord.py +++ b/scripts/tajika_year_lord.py @@ -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)] diff --git a/tests/test_tajika_five_fold_panchavargiya.py b/tests/test_tajika_five_fold_panchavargiya.py new file mode 100644 index 00000000..db73ed98 --- /dev/null +++ b/tests/test_tajika_five_fold_panchavargiya.py @@ -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) diff --git a/tests/test_tajika_panchadhikari_year_lord.py b/tests/test_tajika_panchadhikari_year_lord.py index 7efc3ce5..90bb7bef 100644 --- a/tests/test_tajika_panchadhikari_year_lord.py +++ b/tests/test_tajika_panchadhikari_year_lord.py @@ -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"] diff --git a/tests/test_year_lord_basis_label.py b/tests/test_year_lord_basis_label.py index cb773372..af7a70cb 100644 --- a/tests/test_year_lord_basis_label.py +++ b/tests/test_year_lord_basis_label.py @@ -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")