Merge remote-tracking branch 'origin/staging' into codex/synastry-full-report-20261018
Renumber the synastry records to BUG-1394 and BUG-1395 so they no longer collide with BUG-1393 from issue 84. Co-Authored-By: Grok (agent loop) Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -16124,18 +16124,20 @@ def cmd_full_reading(args):
|
||||
planet_lons,
|
||||
)
|
||||
report['modules']['navamsha_dasha'] = _public_secondary_dasha_module(secondary_dashas.get('navamsha'))
|
||||
report['modules']['niryaana_shoola_dasha'] = _public_secondary_dasha_module(
|
||||
secondary_dashas.get('niryaana_shoola')
|
||||
)
|
||||
except Exception as e:
|
||||
report['modules']['navamsha_dasha'] = {
|
||||
'status': 'blocked',
|
||||
'reason': f'navamsha_dasha_producer_failed:{e}',
|
||||
'periods': [],
|
||||
}
|
||||
# F2b: do not publish the current Niryaana Shoola table. It matches Shoola.
|
||||
# The synastry validator marks this row not applicable, so the pair task stays open.
|
||||
report['modules']['niryaana_shoola_dasha'] = {
|
||||
'execution_status': 'blocked',
|
||||
'reason': '口径更正中,见 TASK-niryaana-shoola-pvr-20261018',
|
||||
}
|
||||
report['modules']['niryaana_shoola_dasha'] = {
|
||||
'status': 'blocked',
|
||||
'reason': f'niryaana_shoola_dasha_producer_failed:{e}',
|
||||
'periods': [],
|
||||
}
|
||||
_record_stage_timing(
|
||||
stage_timings,
|
||||
'dasha_and_core_varga_stack',
|
||||
|
||||
@@ -140,6 +140,13 @@ def render_rashi_dasha_chapter(packet: Mapping) -> str:
|
||||
section = sections[key]
|
||||
title = title_en if language == "en" else title_zh
|
||||
lines.extend([f"### {title}", "", _method_line(section, language), ""])
|
||||
if key == "niryaana_shoola" and section.get("status") == "listed":
|
||||
lines.extend([
|
||||
"Child periods stay off this table: the cited book does not give a separate rule for them."
|
||||
if language == "en"
|
||||
else "子运规则未定,暂不列出。",
|
||||
"",
|
||||
])
|
||||
if key == "chara" and section.get("status") == "listed":
|
||||
lines.extend([
|
||||
"This section is the K.N. Rao Chara table. It is not the Narayana table."
|
||||
|
||||
@@ -38,6 +38,17 @@ except ImportError: # pragma: no cover - package import
|
||||
_MOVABLE = {0, 3, 6, 9}
|
||||
_FIXED = {1, 4, 7, 10}
|
||||
_DUAL = {2, 5, 8, 11}
|
||||
# Nine grahas only. Outer planets and upagrahas are ignored. Nodes are not weighted.
|
||||
_AYUR_RASI_STRENGTH_BODIES = (
|
||||
"Sun", "Moon", "Mars", "Mercury", "Jupiter", "Venus", "Saturn", "Rahu", "Ketu",
|
||||
)
|
||||
_EXALTED_SIGNS = {
|
||||
"Sun": {0}, "Moon": {1}, "Mars": {9}, "Mercury": {5},
|
||||
"Jupiter": {3}, "Venus": {11}, "Saturn": {6},
|
||||
}
|
||||
_NIRYAANA_SOURCE_ZH = "PVR《Vedic Astrology: An Integrated Approach》"
|
||||
_NIRYAANA_SOURCE_EN = "PVR, Vedic Astrology: An Integrated Approach"
|
||||
_NIRYAANA_PROFILE = "pvr_niryaana_shoola_v1"
|
||||
|
||||
|
||||
def _as_date(value: date | datetime | str) -> date:
|
||||
@@ -303,6 +314,171 @@ def _drig_children(md_sign: int, start: date, end: date, graha: Mapping, planet_
|
||||
return _equal_children(order, start, end)
|
||||
|
||||
|
||||
def niryaana_shoola_years(sign_idx: int) -> int:
|
||||
"""Movable, fixed, and dual signs last 7, 8, and 9 years."""
|
||||
sign_idx = int(sign_idx) % 12
|
||||
if sign_idx in _FIXED:
|
||||
return 8
|
||||
if sign_idx in _DUAL:
|
||||
return 9
|
||||
return 7
|
||||
|
||||
|
||||
def select_niryaana_shoola_start_sign(
|
||||
*,
|
||||
ascendant_longitude: float,
|
||||
planet_longitudes: Mapping[str, float],
|
||||
) -> dict:
|
||||
"""Pick the stronger of the second and eighth signs for a longevity dasha.
|
||||
|
||||
Compare occupant count, then every planet's Jaimini sign aspect (the sign
|
||||
lord is not weighted), then whether an occupant is exalted. A three-way
|
||||
tie stays off the table. Count only the nine grahas.
|
||||
"""
|
||||
lagna = sign_index(float(ascendant_longitude))
|
||||
second, eighth = (lagna + 1) % 12, (lagna + 7) % 12
|
||||
positions = {
|
||||
name: float(longitude)
|
||||
for name, longitude in planet_longitudes.items()
|
||||
if name in _AYUR_RASI_STRENGTH_BODIES and isinstance(longitude, (int, float))
|
||||
}
|
||||
candidates = {}
|
||||
for sign_idx in (second, eighth):
|
||||
occupants = sorted(
|
||||
name for name, longitude in positions.items()
|
||||
if sign_index(longitude) == sign_idx
|
||||
)
|
||||
aspecting = sorted(
|
||||
name for name, longitude in positions.items()
|
||||
if sign_index(longitude) != sign_idx and sign_idx in _aspecting_signs(sign_index(longitude))
|
||||
)
|
||||
exalted = sorted(
|
||||
name for name in occupants
|
||||
if sign_idx in _EXALTED_SIGNS.get(name, set())
|
||||
)
|
||||
candidates[sign_idx] = {
|
||||
"sign_index": sign_idx,
|
||||
"sign": SIGNS[sign_idx],
|
||||
"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",
|
||||
"included_bodies": list(_AYUR_RASI_STRENGTH_BODIES),
|
||||
"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 niryaana_shoola_applicability(
|
||||
*,
|
||||
ascendant_longitude: float,
|
||||
second_strength: float,
|
||||
eighth_strength: float,
|
||||
) -> dict:
|
||||
"""Start from the stronger given second or eighth strength. Equal strength blocks."""
|
||||
lagna = sign_index(float(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 niryaana_shoola_mahadashas(birth: date, start_sign: int) -> list[dict]:
|
||||
"""Twelve mahadashas only. Odd signs go forward; even signs go backward."""
|
||||
start_sign = int(start_sign) % 12
|
||||
order = _forward(start_sign) if start_sign % 2 == 0 else _reverse(start_sign)
|
||||
periods = []
|
||||
cursor = birth
|
||||
for sign_idx in order:
|
||||
years = niryaana_shoola_years(sign_idx)
|
||||
end = add_years(cursor, years)
|
||||
periods.append(_period(sign_idx, years, cursor, end))
|
||||
cursor = end
|
||||
return periods
|
||||
|
||||
|
||||
def _niryaana_shoola(birth: date, ascendant_longitude: float, planet_lons: Mapping[str, float]) -> dict:
|
||||
picked = select_niryaana_shoola_start_sign(
|
||||
ascendant_longitude=ascendant_longitude,
|
||||
planet_longitudes=planet_lons,
|
||||
)
|
||||
if picked.get("status") != "selected":
|
||||
return _omitted(
|
||||
_NIRYAANA_PROFILE,
|
||||
_NIRYAANA_SOURCE_ZH,
|
||||
_NIRYAANA_SOURCE_EN,
|
||||
"本轮不上表:二宫与八宫力量相同,原书没有给出进一步的裁决规则。",
|
||||
"This report does not include Niryaana Shoola: the second and eighth signs are equal, and the source gives no further tie-break.",
|
||||
)
|
||||
start_sign = int(picked["selected_sign_idx"])
|
||||
return _listed(
|
||||
_NIRYAANA_PROFILE,
|
||||
_NIRYAANA_SOURCE_ZH,
|
||||
_NIRYAANA_SOURCE_EN,
|
||||
niryaana_shoola_mahadashas(birth, start_sign),
|
||||
direction="forward" if start_sign % 2 == 0 else "reverse",
|
||||
strength_profile=picked["profile"],
|
||||
)
|
||||
|
||||
|
||||
def _nine_year(birth: date, lagna: int, planet_lons: Mapping[str, float], rashi: Mapping, profile: str, source_zh: str, source_en: str) -> dict:
|
||||
seventh = (lagna + 6) % 12
|
||||
picked = stronger_sign(lagna, seventh, rashi, planet_lons)
|
||||
@@ -397,15 +573,7 @@ def calculate_rashi_dashas(birth: date | datetime | str, ascendant_longitude: fl
|
||||
"Crux of Vedic Astrology, printed page 395",
|
||||
),
|
||||
"navamsha": _navamsha(birth_day, float(ascendant_longitude), longitudes),
|
||||
"niryaana_shoola": _nine_year(
|
||||
birth_day,
|
||||
lagna,
|
||||
longitudes,
|
||||
rashi,
|
||||
"goel_nirayana_shoola_v1",
|
||||
"Predicting through Jaimini Astrology,印页 162",
|
||||
"Predicting through Jaimini Astrology, printed page 162",
|
||||
),
|
||||
"niryaana_shoola": _niryaana_shoola(birth_day, float(ascendant_longitude), longitudes),
|
||||
"lagna_kendradi": _omitted(
|
||||
"",
|
||||
"",
|
||||
|
||||
Reference in New Issue
Block a user