From 9f5c0084db06c05b82e0d0ffe8a0eba446615ca8 Mon Sep 17 00:00:00 2001 From: Jesse_Chen Date: Sun, 18 Oct 2026 22:24:25 +0800 Subject: [PATCH] docs(tasks): Niryaana Shoola start sign follows upstream ayur rasi strength (9f08b57b) Co-Authored-By: Claude Opus 5.5 (1M context) Claude-Session: https://claude.ai/code/session_014Ad9rqAB8pXXvyzh7f4X8v --- .../README.md | 3 + .../niryaana_shoola_dasha.py.txt | 263 ++++++++++++++++++ .../test_niryaana_shoola_dasha.py.txt | 102 +++++++ .../TASK-niryaana-shoola-pvr-20261018.md | 8 +- 4 files changed, 372 insertions(+), 4 deletions(-) create mode 100644 docs/research/upstream_niryaana_shoola_9f08b57b/README.md create mode 100644 docs/research/upstream_niryaana_shoola_9f08b57b/niryaana_shoola_dasha.py.txt create mode 100644 docs/research/upstream_niryaana_shoola_9f08b57b/test_niryaana_shoola_dasha.py.txt diff --git a/docs/research/upstream_niryaana_shoola_9f08b57b/README.md b/docs/research/upstream_niryaana_shoola_9f08b57b/README.md new file mode 100644 index 00000000..8a87bf31 --- /dev/null +++ b/docs/research/upstream_niryaana_shoola_9f08b57b/README.md @@ -0,0 +1,3 @@ +# 上游 Niryaana Shoola @ 9f08b57b(2026-10-18) + +起运星座按 `pvr_niryaana_shoola_ayur_rasi_strength_v1` 选:比较第二宫与第八宫,依次看宫内行星数、所有行星的星座相位数(Jaimini 星座相位,宫主不加权)、宫内有无入旺行星;仍平手则 `blocked`,不硬选。行星集合与上游 full-reading 传入的一致(含罗睺、计都)。只出主运。 diff --git a/docs/research/upstream_niryaana_shoola_9f08b57b/niryaana_shoola_dasha.py.txt b/docs/research/upstream_niryaana_shoola_9f08b57b/niryaana_shoola_dasha.py.txt new file mode 100644 index 00000000..f5c8259f --- /dev/null +++ b/docs/research/upstream_niryaana_shoola_9f08b57b/niryaana_shoola_dasha.py.txt @@ -0,0 +1,263 @@ +#!/usr/bin/env python3 +"""Source-bounded Niryaana Shoola Dasha Mahadasha producer. + +Niryaana Shoola and Shoola are adjacent, but distinct, Jaimini systems. This +module implements the former only. The latter retains its Lagna/seventh, +fixed-nine-year implementation in ``scripts/shoola_dasha.py``. +""" + +from __future__ import annotations + +from datetime import datetime +from typing import Any + +from scripts.dasha_profile_arbitration import attach_profile_arbitration +from scripts.rashi_dasha_explicit_contract import add_calendar_years + + +PROFILE_ID = "pvr_niryaana_shoola_v1" +LEGACY_MISCLASSIFIED_PROFILE_ID = "goel_nirayana_shoola_v1" +PYJHORA_PROFILE_ID = "pyjhora_2nd_8th_observation_v1" +TOTAL_CYCLE = 96 +SIGNS = ( + "Aries", "Taurus", "Gemini", "Cancer", "Leo", "Virgo", "Libra", + "Scorpio", "Sagittarius", "Capricorn", "Aquarius", "Pisces", +) +SIGN_LORDS = { + 0: "Mars", 1: "Venus", 2: "Mercury", 3: "Moon", 4: "Sun", 5: "Mercury", + 6: "Venus", 7: "Mars", 8: "Jupiter", 9: "Saturn", 10: "Saturn", 11: "Jupiter", +} +_MOVABLE_SIGNS = {0, 3, 6, 9} +_FIXED_SIGNS = {1, 4, 7, 10} +_EXALTED_SIGNS = { + "Sun": {0}, "Moon": {1}, "Mars": {9}, "Mercury": {5}, + "Jupiter": {3}, "Venus": {11}, "Saturn": {6}, +} + + +def _sign_index(longitude: float) -> int: + return int(float(longitude) % 360.0 // 30.0) + + +def _forward_order(start: int) -> list[int]: + return [(int(start) + offset) % 12 for offset in range(12)] + + +def _reverse_order(start: int) -> list[int]: + return [(int(start) - offset) % 12 for offset in range(12)] + + +def _rasi_aspects(source_sign_index: int, target_sign_index: int) -> bool: + """Return the standard Jaimini sign aspect relation.""" + if source_sign_index in _MOVABLE_SIGNS: + return target_sign_index in _FIXED_SIGNS and target_sign_index != ( + (source_sign_index + 1) % 12 + ) + if source_sign_index in _FIXED_SIGNS: + return target_sign_index in _MOVABLE_SIGNS and target_sign_index != ( + (source_sign_index - 1) % 12 + ) + return target_sign_index in {2, 5, 8, 11} and target_sign_index != source_sign_index + + +def select_niryaana_shoola_start_sign( + *, ascendant_longitude: float, planet_longitudes: dict[str, float], +) -> dict[str, Any]: + """Select the stronger second/eighth rasi under PVR's ayur-dasha scope. + + PVR's general rasi rules are expressly adapted for ayur dashas: all rasi + aspects matter equally and the sign lord's aspect has no special weight. + The text does not provide a further longevity-specific tie-break, so a + tie remains blocked instead of borrowing Narayana's lord rules. + """ + lagna = _sign_index(ascendant_longitude) + second, eighth = (lagna + 1) % 12, (lagna + 7) % 12 + positions = { + name: float(longitude) + for name, longitude in planet_longitudes.items() + if isinstance(longitude, (int, float)) + } + candidates = {} + for sign_index in (second, eighth): + occupants = sorted( + name for name, longitude in positions.items() + if _sign_index(longitude) == sign_index + ) + aspecting = sorted( + name for name, longitude in positions.items() + if _rasi_aspects(_sign_index(longitude), sign_index) + ) + exalted = sorted( + name for name in occupants + if sign_index in _EXALTED_SIGNS.get(name, set()) + ) + candidates[sign_index] = { + "sign_index": sign_index, + "sign": SIGNS[sign_index], + "occupants": occupants, + "occupant_count": len(occupants), + "rasi_aspecting_planets": aspecting, + "rasi_aspect_count": len(aspecting), + "exalted_occupants": exalted, + } + second_score = ( + candidates[second]["occupant_count"], + candidates[second]["rasi_aspect_count"], + bool(candidates[second]["exalted_occupants"]), + ) + eighth_score = ( + candidates[eighth]["occupant_count"], + candidates[eighth]["rasi_aspect_count"], + bool(candidates[eighth]["exalted_occupants"]), + ) + payload = { + "profile": "pvr_niryaana_shoola_ayur_rasi_strength_v1", + "ascendant_sign_index": lagna, + "second_sign_index": second, + "eighth_sign_index": eighth, + "candidates": candidates, + "selection_levels": [ + "occupant_count", + "all_planet_rasi_aspect_count", + "exalted_occupant_presence", + ], + } + if second_score == eighth_score: + return { + **payload, + "status": "blocked", + "reason": "niryaana_shoola_ayur_rasi_strength_tie_requires_source_rule", + } + selected = second if second_score > eighth_score else eighth + return { + **payload, + "status": "selected", + "selected_sign_idx": selected, + "selected_sign": SIGNS[selected], + } + + +def resolve_lagna_seventh_tie_break( + *, + lagna_sign_index: int, + planets: dict[str, dict[str, Any]] | None = None, + planet_strengths: dict[str, float] | None = None, +) -> dict[str, Any]: + """Explicit Shoola-only tie-break retained for its separate producer.""" + planets = planets if isinstance(planets, dict) else {} + strengths = planet_strengths if isinstance(planet_strengths, dict) else {} + rows = [] + for label, sign in (("lagna", int(lagna_sign_index) % 12), ("seventh", (int(lagna_sign_index) + 6) % 12)): + lord = SIGN_LORDS[sign] + lord_data = planets.get(lord) if isinstance(planets.get(lord), dict) else {} + degree = lord_data.get("degree_in_sign", lord_data.get("degree")) + if not isinstance(degree, (int, float)) and isinstance(lord_data.get("lon"), (int, float)): + degree = float(lord_data["lon"]) % 30.0 + occupants = [ + name for name, data in planets.items() + if isinstance(data, dict) and int(data.get("sign_idx", -1)) % 12 == sign + ] + rows.append({ + "label": label, "sign_index": sign, "sign": SIGNS[sign], + "occupants": occupants, "occupant_count": len(occupants), "lord": lord, + "lord_strength": float(strengths[lord]) if isinstance(strengths.get(lord), (int, float)) else None, + "lord_degree_in_sign": float(degree) if isinstance(degree, (int, float)) else None, + }) + keys = [ + (row["occupant_count"], row["lord_strength"] if row["lord_strength"] is not None else float("-inf"), row["lord_degree_in_sign"] if row["lord_degree_in_sign"] is not None else float("-inf")) + for row in rows + ] + if keys[0] == keys[1]: + return {"status": "blocked", "reason": "Tie-break evidence remains equal after occupants, lord strength, and lord degree.", "rows": rows} + selected = rows[0] if keys[0] > keys[1] else rows[1] + return {"status": "selected", "selected": selected, "rows": rows, "rule": "occupants_then_sign_lord_strength_then_sign_lord_degree"} + + +def _sign_years(sign_index: int) -> int: + """PVR Niryaana Shoola: movable/fixed/dual = 7/8/9 years.""" + if int(sign_index) % 12 in {1, 4, 7, 10}: + return 8 + if int(sign_index) % 12 in {2, 5, 8, 11}: + return 9 + return 7 + + +def niryaana_shoola_applicability( + *, ascendant_longitude: float, second_strength: float, eighth_strength: float, +) -> dict[str, Any]: + """Select the stronger second/eighth sign; the source supplies no tie-break.""" + lagna = _sign_index(ascendant_longitude) + second, eighth = (lagna + 1) % 12, (lagna + 7) % 12 + second_strength, eighth_strength = float(second_strength), float(eighth_strength) + if second_strength == eighth_strength: + return { + "applicable": False, "execution_status": "blocked", "ascendant_sign_index": lagna, + "second_sign_index": second, "eighth_sign_index": eighth, + "second_strength": second_strength, "eighth_strength": eighth_strength, + "rule_family": "pvr_niryaana_shoola_2nd_8th_modality", + "reason": "The cited Niryaana Shoola rule does not provide a tie-break for equal second/eighth strength.", + } + start = second if second_strength > eighth_strength else eighth + return { + "applicable": True, "ascendant_sign_index": lagna, "second_sign_index": second, + "eighth_sign_index": eighth, "second_strength": second_strength, + "eighth_strength": eighth_strength, "starting_sign_index": start, + "starting_sign": SIGNS[start], "direction": "forward" if start % 2 == 0 else "reverse", + "rule_family": "pvr_niryaana_shoola_2nd_8th_modality", + "reason": "Niryaana Shoola starts from the stronger second/eighth sign; odd seeds run forward and even seeds reverse.", + } + + +def calculate_niryaana_shoola_dasha(birth_info: dict[str, Any]) -> dict[str, Any]: + """Calculate source-bounded Niryaana Shoola Mahadashas only. + + PVR's chapter proposes borrowing Narayana rules for children rather than + specifying its own AD/PD algorithm, so child periods remain deliberately + blocked. This system is for longevity analysis and is not relationship + timing evidence. + """ + profile_id = birth_info.get("profile_id") or PROFILE_ID + if profile_id == LEGACY_MISCLASSIFIED_PROFILE_ID: + return { + "applicable": False, "execution_status": "blocked", "confidence_status": "blocked", + "profile_id": profile_id, "major": [], "antardasha": [], "pratyantardasha": [], + "reason": "goel_nirayana_shoola_v1 conflates Niryaana Shoola with Shoola Dasha; choose pvr_niryaana_shoola_v1 or the separate Shoola producer.", + } + if profile_id not in {PROFILE_ID, PYJHORA_PROFILE_ID}: + return {"applicable": False, "execution_status": "blocked", "confidence_status": "blocked", "profile_id": profile_id, "major": [], "reason": "Unsupported Niryaana Shoola profile_id: " + str(profile_id)} + required = ("birth_datetime", "ascendant_longitude", "second_strength", "eighth_strength") + missing = [field for field in required if birth_info.get(field) is None] + if missing: + return {"applicable": False, "execution_status": "blocked", "confidence_status": "blocked", "profile_id": profile_id, "major": [], "reason": "Missing required input: " + ", ".join(missing)} + birth = birth_info["birth_datetime"] + if isinstance(birth, str): + birth = datetime.fromisoformat(birth) + applicability = niryaana_shoola_applicability( + ascendant_longitude=float(birth_info["ascendant_longitude"]), + second_strength=float(birth_info["second_strength"]), + eighth_strength=float(birth_info["eighth_strength"]), + ) + if not applicability["applicable"]: + return {**applicability, "profile_id": profile_id, "total_cycle": TOTAL_CYCLE, "major": [], "antardasha": [], "pratyantardasha": [], "confidence_status": "blocked"} + order = _forward_order(applicability["starting_sign_index"]) if applicability["direction"] == "forward" else _reverse_order(applicability["starting_sign_index"]) + try: + rows, cursor = [], birth + for sign_index in order: + years = _sign_years(sign_index) + end = add_calendar_years(cursor, years) + rows.append({"level": "mahadasha", "sign_index": sign_index, "sign": SIGNS[sign_index], "years": years, "start_date": cursor.isoformat(timespec="seconds"), "end_date": end.isoformat(timespec="seconds")}) + cursor = end + except ValueError: + return {**applicability, "profile_id": profile_id, "execution_status": "blocked", "confidence_status": "blocked", "major": [], "reason": "Calendar-year anniversary cannot be represented for this birth date."} + payload = { + **applicability, "profile_id": profile_id, "execution_status": "executed", + "confidence_status": "source_bounded", "verification_status": "pvr_rule_replay", + "period_depth_status": "mahadasha_executed_ad_pd_blocked_source_rule", + "total_cycle": TOTAL_CYCLE, "duration_basis": "calendar_years", "major": rows, + "antardasha": [], "pratyantardasha": [], + "relationship_applicability": "not_applicable_longevity_specific", + "reason": "PVR Niryaana Shoola MD replay. Its child-period rule is not independently specified, and longevity-specific periods are not relationship timing evidence.", + "source_locator": "PVR, An Integrated Approach to Vedic Astrology, ch. 22.2, source pages 285-292.", + } + attach_profile_arbitration(payload, "niryaana_shoola", profile_id) + return payload diff --git a/docs/research/upstream_niryaana_shoola_9f08b57b/test_niryaana_shoola_dasha.py.txt b/docs/research/upstream_niryaana_shoola_9f08b57b/test_niryaana_shoola_dasha.py.txt new file mode 100644 index 00000000..b4441170 --- /dev/null +++ b/docs/research/upstream_niryaana_shoola_9f08b57b/test_niryaana_shoola_dasha.py.txt @@ -0,0 +1,102 @@ +"""Regression tests for the distinct Niryaana Shoola rule family.""" + +from __future__ import annotations + +from datetime import datetime + +from scripts.niryaana_shoola_dasha import ( + PROFILE_ID, + calculate_niryaana_shoola_dasha, + niryaana_shoola_applicability, + select_niryaana_shoola_start_sign, +) + + +def test_niryaana_shoola_selects_stronger_second_or_eighth() -> None: + second = niryaana_shoola_applicability( + ascendant_longitude=0.0, second_strength=2.0, eighth_strength=1.0, + ) + eighth = niryaana_shoola_applicability( + ascendant_longitude=0.0, second_strength=1.0, eighth_strength=2.0, + ) + assert second["starting_sign"] == "Taurus" + assert second["direction"] == "reverse" + assert eighth["starting_sign"] == "Scorpio" + assert eighth["direction"] == "reverse" + + +def test_niryaana_shoola_uses_ayur_specific_rasi_strength() -> None: + selection = select_niryaana_shoola_start_sign( + ascendant_longitude=0.0, + planet_longitudes={"Mars": 31.0, "Sun": 91.0}, + ) + + assert selection["status"] == "selected" + assert selection["selected_sign"] == "Taurus" + assert selection["selection_levels"] == [ + "occupant_count", + "all_planet_rasi_aspect_count", + "exalted_occupant_presence", + ] + assert selection["candidates"][1]["occupants"] == ["Mars"] + assert selection["candidates"][7]["rasi_aspecting_planets"] == ["Sun"] + + +def test_niryaana_shoola_blocks_unresolved_ayur_rasi_strength_tie() -> None: + selection = select_niryaana_shoola_start_sign( + ascendant_longitude=0.0, + planet_longitudes={}, + ) + + assert selection["status"] == "blocked" + assert selection["reason"] == "niryaana_shoola_ayur_rasi_strength_tie_requires_source_rule" + + +def test_niryaana_shoola_blocks_equal_second_eighth_without_invented_tie_break() -> None: + result = niryaana_shoola_applicability( + ascendant_longitude=0.0, second_strength=1.0, eighth_strength=1.0, + ) + assert result["execution_status"] == "blocked" + assert "does not provide a tie-break" in result["reason"] + + +def test_niryaana_shoola_uses_modality_years_and_parity_direction() -> None: + result = calculate_niryaana_shoola_dasha({ + "profile_id": PROFILE_ID, + "birth_datetime": datetime(2000, 1, 1, 12, 0), + "ascendant_longitude": 15.0, + "second_strength": 2.0, + "eighth_strength": 1.0, + }) + assert result["execution_status"] == "executed" + assert result["total_cycle"] == 96 + assert [row["sign"] for row in result["major"][:3]] == ["Taurus", "Aries", "Pisces"] + assert [row["years"] for row in result["major"][:3]] == [8, 7, 9] + assert result["period_depth_status"] == "mahadasha_executed_ad_pd_blocked_source_rule" + assert result["antardasha"] == [] + assert result["relationship_applicability"] == "not_applicable_longevity_specific" + + +def test_legacy_goel_profile_is_blocked_as_misclassified() -> None: + result = calculate_niryaana_shoola_dasha({ + "profile_id": "goel_nirayana_shoola_v1", + "birth_datetime": datetime(2000, 1, 1, 12, 0), + "ascendant_longitude": 0.0, + "lagna_strength": 2.0, + "seventh_strength": 1.0, + }) + assert result["execution_status"] == "blocked" + assert "conflates Niryaana Shoola with Shoola Dasha" in result["reason"] + + +def test_pyjhora_observation_profile_replays_same_native_rule_shape() -> None: + result = calculate_niryaana_shoola_dasha({ + "profile_id": "pyjhora_2nd_8th_observation_v1", + "birth_datetime": "2000-01-01T00:00:00", + "ascendant_longitude": 15.0, + "second_strength": 2.0, + "eighth_strength": 1.0, + }) + assert result["execution_status"] == "executed" + assert result["starting_sign"] == "Taurus" + assert result["profile_arbitration"]["selection_status"] == "explicit_profile_selected" diff --git a/docs/tasks/TASK-niryaana-shoola-pvr-20261018.md b/docs/tasks/TASK-niryaana-shoola-pvr-20261018.md index 288c8513..88778a29 100644 --- a/docs/tasks/TASK-niryaana-shoola-pvr-20261018.md +++ b/docs/tasks/TASK-niryaana-shoola-pvr-20261018.md @@ -16,7 +16,7 @@ | 3 | 上游同时把 Niryaana Shoola 从恋爱婚姻合盘的应期里排除(寿元类大运,不用于关系时间) | 上游 `validate_full_synastry_packets.py`:该行固定 `not_applicable` | | 4 | 只在 staging:`origin/main` 还没有 `rashi_dashas_native.py`,生产不受影响 | — | -另:上游 `cba6a031`(10-18 晚)把 full-reading 接上了,二宫 / 八宫力量取 `bhava_bala.tri_component` 的 `total`。Claude 用两位公开名人复测:两张盘都能算出来,奇顺偶逆与 7 / 8 / 9 年都对;**但用我们的 Jaimini 星座力量(`stronger_sign`)比,两张盘选出的起运星座都和上游相反**(一张上游选双子、我们选射手;另一张上游选狮子、我们选水瓶)。上游所用的那套宫位力量在它自己的代码里标着「替代口径、待验证」。用哪种力量来比二宫 / 八宫,已请占星师确认(见 H4)。 +另:上游 `cba6a031` 一度用 Bhava Bala 总分比二宫 / 八宫,Claude 复测发现两张名人盘起运都与 Jaimini 星座力量相反并提出质疑;占星师随后在 `9f08b57b` 改为寿命类星座力量规则(H4),不再读 Bhava Bala,平手明确 `blocked`。Claude 10-18 复测 `9f08b57b`:专项测试全过;两张名人盘一张起射手、一张起水瓶,奇顺偶逆与 7 / 8 / 9 年都对。 ## 根因 @@ -29,20 +29,20 @@ BUG-1283(10-09)把旧 `niryaana_shoola_dasha.py` 换成 `goel_nirayana_shool | H1 | Niryaana Shoola 采用占星师 10-18 的 PVR 口径(`pvr_niryaana_shoola_v1`)。**推翻 BUG-1283 选定的 `goel_nirayana_shoola_v1`**;BUG-1283 的另一半(旧 `niryaana_shoola_dasha.py` 不得被新路径调用)保持不变 | | H2 | Shoola 保持现状(上升 / 七宫取强、每宫 9 年)——那本来就是 Shoola 的规则 | | H3 | 合盘不用 Niryaana Shoola 做关系应期(与上游一致);本单只影响个人完整数据版报告和 full-reading 的 `niryaana_shoola_dasha` 键 | -| H4 | **力量比较先用 Jaimini 星座力量**(本文件已有的 `rashi_strengths` / `stronger_sign`,与 Chara、Narayana 等星座大运同一套),不用上游的 Bhava Bala。理由:Niryaana Shoola 是 Jaimini 星座大运,上游所用 Bhava Bala 替代口径在其代码中标为待验证。**占星师若确认应该用 Bhava Bala,再改**;开工前 Claude 会在评论里同步她的答复 | +| H4 | **起运星座按上游 `9f08b57b` 的寿命类星座力量选**(`pvr_niryaana_shoola_ayur_rasi_strength_v1`,占星师 10-18 裁定,推翻本单上一版「用我们的 `stronger_sign`」与上游 `cba6a031` 的 Bhava Bala):比较第二宫与第八宫,依次看 ① 宫内行星数 ② 所有行星对它的 Jaimini 星座相位数(宫主不加权)③ 宫内有无入旺行星;三层都平手则「本轮不上表」并写明「二宫与八宫力量相同,原书没有给出进一步的裁决规则」,不得借用 Narayana 的宫主规则硬选。行星集合与上游一致(含罗睺、计都)。在 `rashi_dashas_native.py` 里按上游函数逐条实现,副本见 `docs/research/upstream_niryaana_shoola_9f08b57b/` | | H5 | **只出主运,不出子运**(与上游一致:上游说明 PVR 没有独立给出子运规则,子运标为 blocked)。报告里该表只列主运并写一句「子运规则未定,暂不列出」 | ## 硬红线 1. 在 `scripts/rashi_dashas_native.py` 内改,不 import 旧的 `scripts/niryaana_shoola_dasha.py`(BUG-1283 语法树断言保持绿);不整份搬上游文件(上游版本依赖我们没有的 `dasha_profile_arbitration` 等)。 -2. 力量比较用本文件已有的 `rashi_strengths` / `stronger_sign`,平手时按现有 tie-break 规则走,仍分不出就「本轮不上表」,不得默认挑一个。 +2. 力量比较只用 H4 的三层规则,**不用** `rashi_strengths` / `stronger_sign`(那套带宫主规则,PVR 说寿命类大运不适用);平手就「本轮不上表」,不得默认挑一个。 3. 全量 pytest 与基线逐条比对;改既有断言写三栏。 ## 任务分解 | # | 内容 | 验收标准 | | --- | --- | --- | -| T1 | `niryaana_shoola` 改为 PVR:第二宫与第八宫取强起运;起运星座奇数顺行、偶数逆行;动 / 固 / 双星座 7 / 8 / 9 年(12 宫一轮 96 年);**只出主运**(H5);profile 名 `pvr_niryaana_shoola_v1`,出处写 PVR《Vedic Astrology: An Integrated Approach》 | 新测试:① 同一张盘 Shoola 与 Niryaana Shoola 的首运或年数不再全同;② 白羊上升样例起运在金牛 / 天蝎中较强者;③ 奇偶方向各一例;④ 动 / 固 / 双年数各一例;⑤ 在上游测试样例上(样例直接给定二宫 / 八宫力量)与上游 `pvr_niryaana_shoola_v1` 的主运序列一致(把期望值抄过来作 golden,写明出处);⑥ 两位公开名人各一条:记录我们选出的起运星座与上游 `cba6a031` 的差异,写进 PROGRESS,不作为失败 | +| T1 | `niryaana_shoola` 改为 PVR:第二宫与第八宫取强起运;起运星座奇数顺行、偶数逆行;动 / 固 / 双星座 7 / 8 / 9 年(12 宫一轮 96 年);**只出主运**(H5);profile 名 `pvr_niryaana_shoola_v1`,出处写 PVR《Vedic Astrology: An Integrated Approach》 | 新测试:① 同一张盘 Shoola 与 Niryaana Shoola 的首运或年数不再全同;② 白羊上升样例起运在金牛 / 天蝎中较强者;③ 奇偶方向各一例;④ 动 / 固 / 双年数各一例;⑤ 在上游测试样例上(样例直接给定二宫 / 八宫力量)与上游 `pvr_niryaana_shoola_v1` 的主运序列一致(把期望值抄过来作 golden,写明出处);⑥ 两位公开名人各一条,起运星座与主运序列必须与上游 `9f08b57b` 一致(Claude 10-18 实测:一张起射手、一张起水瓶);⑦ 构造一张三层都平手的盘,结果为「本轮不上表」 | | T2 | full-reading 的 `niryaana_shoola_dasha` 键改用 T1 结果(替换 #81 的占位) | 上游合盘校验器读这一行仍为 `not_applicable`(合盘不用);个人报告节表显示新表 | | T3 | 记录:BUG_HISTORY(新编号,关联 BUG-1283,写明「推翻其所选 profile」)、CHANGELOG(报告里 Niryaana Shoola 大运表变了)、PROGRESS | 文档齐 |