让大运接口走完整解读的同一套算法,不再返回编出来的周期。
Ashtottari 在适用时把出生前已经走过的部分算进时间线。 Co-Authored-By: Grok (agent loop)
This commit is contained in:
co-authored by
Grok (agent loop)
parent
b9cbe9aa76
commit
5ac88e58f5
+74
-24
@@ -288,6 +288,55 @@ def _calc_candidate_balance_and_birth_chain(moon_longitude: float, starting_plan
|
||||
return balance, birth_chain
|
||||
|
||||
|
||||
def _timeline_start_with_birth_balance(
|
||||
birth_dt: datetime,
|
||||
moon_longitude,
|
||||
starting_planet: str,
|
||||
) -> tuple[datetime, bool]:
|
||||
"""Shift the first mahadasha so birth falls inside the elapsed portion.
|
||||
|
||||
Both applicability families use the starting lord's nakshatra group. When
|
||||
the Moon longitude is missing, or it does not fall in that group, the
|
||||
timeline stays on the birth instant.
|
||||
"""
|
||||
if moon_longitude is None:
|
||||
return birth_dt, False
|
||||
balance, _birth_chain = _calc_candidate_balance_and_birth_chain(
|
||||
moon_longitude=float(moon_longitude),
|
||||
starting_planet=starting_planet,
|
||||
)
|
||||
if balance is None:
|
||||
return birth_dt, False
|
||||
md_years = next(lord["years"] for lord in DASHA_SEQUENCE if lord["planet"] == starting_planet)
|
||||
elapsed_years = md_years - float(balance["years"])
|
||||
if elapsed_years == 0:
|
||||
return birth_dt, False
|
||||
return birth_dt - timedelta(days=elapsed_years * 365.25), True
|
||||
|
||||
|
||||
def _current_period_from_shifted_majors(major_periods: list, now: datetime) -> dict | None:
|
||||
if not major_periods:
|
||||
return None
|
||||
first = datetime.fromisoformat(major_periods[0]["start_date"])
|
||||
last = datetime.fromisoformat(major_periods[-1]["end_date"])
|
||||
span = last - first
|
||||
if span.total_seconds() <= 0 or now < first:
|
||||
return None
|
||||
cycles = int((now - first).total_seconds() // span.total_seconds())
|
||||
for period in major_periods:
|
||||
start = datetime.fromisoformat(period["start_date"]) + cycles * span
|
||||
end = datetime.fromisoformat(period["end_date"]) + cycles * span
|
||||
if start <= now < end:
|
||||
current = period.copy()
|
||||
current["start_date"] = start.isoformat()
|
||||
current["end_date"] = end.isoformat()
|
||||
current["elapsed_years"] = (now - start).days / 365.25
|
||||
current["remaining_years"] = (end - now).days / 365.25
|
||||
current["cycle_number"] = cycles + 1
|
||||
return current
|
||||
return None
|
||||
|
||||
|
||||
def _build_major_periods(start_lord_idx: int, birth_date: datetime) -> list:
|
||||
"""
|
||||
Build the full list of major periods (Mahadashas) given the starting lord.
|
||||
@@ -418,36 +467,37 @@ def calculate_ashtottari_dasha(birth_info: dict) -> dict:
|
||||
idx for idx, lord in enumerate(DASHA_SEQUENCE) if lord["planet"] == starting_planet
|
||||
)
|
||||
|
||||
major_periods = _build_major_periods(start_lord_idx, birth_dt)
|
||||
timeline_start, balance_shifted = _timeline_start_with_birth_balance(
|
||||
birth_dt, moon_longitude, starting_planet,
|
||||
)
|
||||
major_periods = _build_major_periods(start_lord_idx, timeline_start)
|
||||
antardasha_periods, pratyantardasha_periods = _attach_recursive_subperiods(major_periods)
|
||||
balance_at_birth = None
|
||||
dasha_at_birth = None
|
||||
if moon_longitude is not None and rule_family == RULE_FAMILY_RAHU_FROM_LAGNA_LORD:
|
||||
balance_at_birth, dasha_at_birth = _calc_candidate_balance_and_birth_chain(
|
||||
moon_longitude=float(moon_longitude),
|
||||
starting_planet=starting_planet,
|
||||
)
|
||||
|
||||
# Determine current period. Major periods repeat every 108 years; older natives
|
||||
# should still return a current period instead of None after the first cycle.
|
||||
# A shifted timeline is read from the period dates. An unshifted timeline keeps
|
||||
# the previous birth-anchored walk so charts without a Moon longitude stay put.
|
||||
now = datetime.now()
|
||||
current_period = None
|
||||
age_years = max((now - birth_dt).days / 365.25, 0)
|
||||
current_in_cycle = age_years % TOTAL_CYCLE
|
||||
cumulative = 0.0
|
||||
for p in major_periods:
|
||||
years = p["years"]
|
||||
if cumulative <= current_in_cycle < cumulative + years:
|
||||
cycle_start = birth_dt + timedelta(days=(age_years - current_in_cycle + cumulative) * 365.25)
|
||||
cycle_end = cycle_start + timedelta(days=years * 365.25)
|
||||
current_period = p.copy()
|
||||
current_period["start_date"] = cycle_start.isoformat()
|
||||
current_period["end_date"] = cycle_end.isoformat()
|
||||
current_period["elapsed_years"] = (now - cycle_start).days / 365.25
|
||||
current_period["remaining_years"] = (cycle_end - now).days / 365.25
|
||||
current_period["cycle_number"] = int(age_years // TOTAL_CYCLE) + 1
|
||||
break
|
||||
cumulative += years
|
||||
if balance_shifted:
|
||||
current_period = _current_period_from_shifted_majors(major_periods, now)
|
||||
else:
|
||||
age_years = max((now - birth_dt).days / 365.25, 0)
|
||||
current_in_cycle = age_years % TOTAL_CYCLE
|
||||
cumulative = 0.0
|
||||
for p in major_periods:
|
||||
years = p["years"]
|
||||
if cumulative <= current_in_cycle < cumulative + years:
|
||||
cycle_start = birth_dt + timedelta(days=(age_years - current_in_cycle + cumulative) * 365.25)
|
||||
cycle_end = cycle_start + timedelta(days=years * 365.25)
|
||||
current_period = p.copy()
|
||||
current_period["start_date"] = cycle_start.isoformat()
|
||||
current_period["end_date"] = cycle_end.isoformat()
|
||||
current_period["elapsed_years"] = (now - cycle_start).days / 365.25
|
||||
current_period["remaining_years"] = (cycle_end - now).days / 365.25
|
||||
current_period["cycle_number"] = int(age_years // TOTAL_CYCLE) + 1
|
||||
break
|
||||
cumulative += years
|
||||
|
||||
result = {
|
||||
"applicable": True,
|
||||
|
||||
@@ -12,6 +12,11 @@ from __future__ import annotations
|
||||
from datetime import datetime, timedelta
|
||||
from typing import Any
|
||||
|
||||
try:
|
||||
from scripts.dasha_common import nakshatra_seeded_birth_balance
|
||||
except ModuleNotFoundError: # pragma: no cover - scripts/ on sys.path
|
||||
from dasha_common import nakshatra_seeded_birth_balance
|
||||
|
||||
|
||||
YEAR_DAYS = 365.25636
|
||||
NAKSHATRA_SPAN = 360.0 / 27.0
|
||||
@@ -65,11 +70,9 @@ def _sequence_from(lord: str) -> list[tuple[str, int]]:
|
||||
|
||||
|
||||
def _birth_lord_and_balance(moon_longitude: float) -> tuple[str, float, int]:
|
||||
moon = float(moon_longitude) % 360.0
|
||||
nakshatra = int(moon // NAKSHATRA_SPAN)
|
||||
lord = DASHA_SEQUENCE[((nakshatra - SWATI_INDEX) % 27) % len(DASHA_SEQUENCE)][0]
|
||||
balance = _YEARS[lord] * (1.0 - (moon % NAKSHATRA_SPAN) / NAKSHATRA_SPAN)
|
||||
return lord, balance, nakshatra
|
||||
return nakshatra_seeded_birth_balance(
|
||||
moon_longitude, anchor_index=SWATI_INDEX, sequence=DASHA_SEQUENCE,
|
||||
)
|
||||
|
||||
|
||||
def calculate_chaturshitisama_dasha(birth_info: dict[str, Any]) -> dict[str, Any]:
|
||||
|
||||
@@ -1,193 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
额外Dasha系统模块 v1.0
|
||||
基于BPHS条件性Dasha体系
|
||||
|
||||
支持的Dasha系统:
|
||||
1. Dwisaptati Sama Dasha (72年周期) — 当Lagna lord在特定条件时触发
|
||||
2. Shattrimsa Sama Dasha (36年周期) — 当Moon在特定条件时触发
|
||||
3. Dwadashottari Dasha (112年周期) — 当Venus在特定Nakshatra时触发
|
||||
"""
|
||||
|
||||
from datetime import datetime, timedelta
|
||||
from typing import Dict, List, Tuple, Optional
|
||||
|
||||
YEAR_DAYS = 365.25636
|
||||
SIGNS = ['Aries','Taurus','Gemini','Cancer','Leo','Virgo',
|
||||
'Libra','Scorpio','Sagittarius','Capricorn','Aquarius','Pisces']
|
||||
NAKSHATRAS = ['Ashwini','Bharani','Krittika','Rohini','Mrigashira','Ardra',
|
||||
'Punarvasu','Pushya','Ashlesha','Magha','Purva Phalguni','Uttara Phalguni',
|
||||
'Hasta','Chitra','Swati','Vishakha','Anuradha','Jyeshtha',
|
||||
'Mula','Purva Ashadha','Uttara Ashadha','Shravana','Dhanishta','Shatabhisha',
|
||||
'Purva Bhadrapada','Uttara Bhadrapada','Revati']
|
||||
|
||||
VIMSHOTTARI_DURATION = {'Ketu':7,'Venus':20,'Sun':6,'Moon':10,'Mars':7,'Rahu':18,'Jupiter':16,'Saturn':19,'Mercury':17}
|
||||
DASHA_ORDER = ['Ketu','Venus','Sun','Moon','Mars','Rahu','Jupiter','Saturn','Mercury']
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# Dwisaptati Sama Dasha (72年周期)
|
||||
# 适用条件:Lagna lord在Lagna或7宫时激活
|
||||
# 主星序列:特定8星顺序,每9年一个主星
|
||||
# =============================================================================
|
||||
|
||||
DWISAPTATI_LORDS = ['Sun','Moon','Mars','Mercury','Jupiter','Venus','Saturn','Rahu']
|
||||
DWISAPTATI_DURATION = {l: 9 for l in DWISAPTATI_LORDS} # 每个9年,共72年
|
||||
DWISAPTATI_SUB_DURATION = {p: VIMSHOTTARI_DURATION[p]*9/120 for p in DWISAPTATI_LORDS}
|
||||
|
||||
|
||||
def check_dwisaptati_condition(lagna_lord: str, lagna_lord_house: int) -> bool:
|
||||
"""检查Dwisaptati触发条件:Lagna lord在1或7宫"""
|
||||
return lagna_lord_house in (1, 7)
|
||||
|
||||
|
||||
def calc_dwisaptati_dasha(birth_date: datetime, lagna_lord: str,
|
||||
lagna_lord_degree: float) -> List[Dict]:
|
||||
"""
|
||||
计算Dwisaptati Sama Dasha(72年周期)。
|
||||
|
||||
每个主星周期 = 9年,子周期按Vimshottari比例分配。
|
||||
"""
|
||||
start_idx = DWISAPTATI_LORDS.index(lagna_lord)
|
||||
sequence = []
|
||||
current = birth_date
|
||||
|
||||
for i in range(8):
|
||||
lord_idx = (start_idx + i) % 8
|
||||
lord = DWISAPTATI_LORDS[lord_idx]
|
||||
days = 9 * YEAR_DAYS
|
||||
end_date = current + timedelta(days=days)
|
||||
|
||||
# 子周期
|
||||
subs = []
|
||||
sub_start = current
|
||||
for j in range(8):
|
||||
sub_lord_idx = (lord_idx + j) % 8
|
||||
sub_lord = DWISAPTATI_LORDS[sub_lord_idx]
|
||||
sub_days = days * (VIMSHOTTARI_DURATION.get(sub_lord, 9) / 120)
|
||||
sub_end = sub_start + timedelta(days=sub_days)
|
||||
subs.append({'lord': sub_lord, 'start': sub_start.strftime('%Y-%m-%d'),
|
||||
'end': sub_end.strftime('%Y-%m-%d')})
|
||||
sub_start = sub_end
|
||||
|
||||
sequence.append({
|
||||
'lord': lord, 'years': 9,
|
||||
'start': current.strftime('%Y-%m-%d'),
|
||||
'end': end_date.strftime('%Y-%m-%d'),
|
||||
'sub_periods': subs[:4],
|
||||
})
|
||||
current = end_date
|
||||
|
||||
return sequence
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# Shattrimsa Sama Dasha (36年周期)
|
||||
# 适用条件:特定条件触发(Moon-based condition)
|
||||
# 主星序列:9星,每星4年
|
||||
# =============================================================================
|
||||
|
||||
SHATTRIMSA_DURATION = {l: 4 for l in DASHA_ORDER}
|
||||
|
||||
|
||||
def check_shattrimsa_condition(moon_sign: str) -> bool:
|
||||
"""Shattrimsa触发条件:Moon在特定星座/条件时"""
|
||||
return True # 简化:总可用
|
||||
|
||||
|
||||
def calc_shattrimsa_dasha(birth_date: datetime, moon_nakshatra_idx: int) -> List[Dict]:
|
||||
"""计算Shattrimsa Sama Dasha(36年周期)。每个主星=4年"""
|
||||
start_lord_idx = moon_nakshatra_idx % 9
|
||||
sequence = []
|
||||
current = birth_date
|
||||
|
||||
for i in range(9):
|
||||
lord_idx = (start_lord_idx + i) % 9
|
||||
lord = DASHA_ORDER[lord_idx]
|
||||
days = 4 * YEAR_DAYS
|
||||
end_date = current + timedelta(days=days)
|
||||
|
||||
sequence.append({
|
||||
'lord': lord, 'years': 4,
|
||||
'start': current.strftime('%Y-%m-%d'),
|
||||
'end': end_date.strftime('%Y-%m-%d'),
|
||||
})
|
||||
current = end_date
|
||||
|
||||
return sequence
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# Dwadashottari Dasha (112年周期)
|
||||
# 适用条件:当Venus在特定Nakshatra(Revati/UttaraBhadrapada等)时触发
|
||||
# 主星序列:特定8星,不同年限
|
||||
# =============================================================================
|
||||
|
||||
DWADASHOTTARI_LORDS = ['Sun','Jupiter','Ketu','Mercury','Rahu','Mars','Saturn','Moon']
|
||||
DWADASHOTTARI_YEARS = {'Sun':7,'Jupiter':9,'Ketu':11,'Mercury':13,'Rahu':15,'Mars':17,'Saturn':19,'Moon':21}
|
||||
|
||||
VENUS_TRIGGER_NAKSHATRAS = {24, 25, 26} # Shatabhisha, Purva Bhadra, Uttara Bhadra
|
||||
|
||||
|
||||
def check_dwadashottari_condition(venus_nakshatra_idx: int) -> bool:
|
||||
"""Dwadashottari触发条件:Venus在特定Nakshatra"""
|
||||
return venus_nakshatra_idx in VENUS_TRIGGER_NAKSHATRAS
|
||||
|
||||
|
||||
def calc_dwadashottari_dasha(birth_date: datetime, moon_degree: float) -> List[Dict]:
|
||||
"""计算Dwadashottari Dasha(112年周期)"""
|
||||
nak_idx = int(moon_degree / (360/27))
|
||||
start_lord_idx = nak_idx % 8
|
||||
sequence = []
|
||||
current = birth_date
|
||||
|
||||
for i in range(8):
|
||||
lord_idx = (start_lord_idx + i) % 8
|
||||
lord = DWADASHOTTARI_LORDS[lord_idx]
|
||||
years = DWADASHOTTARI_YEARS[lord]
|
||||
days = years * YEAR_DAYS
|
||||
end_date = current + timedelta(days=days)
|
||||
|
||||
sequence.append({
|
||||
'lord': lord, 'years': years,
|
||||
'start': current.strftime('%Y-%m-%d'),
|
||||
'end': end_date.strftime('%Y-%m-%d'),
|
||||
})
|
||||
current = end_date
|
||||
|
||||
return sequence
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# 条件性Dasha综合选择器
|
||||
# =============================================================================
|
||||
|
||||
def get_applicable_dashas(planets: Dict, lagna_sign: str, moon_degree: float) -> Dict:
|
||||
"""
|
||||
根据星盘条件推荐适用���Dasha系统。
|
||||
|
||||
Returns:
|
||||
各Dasha系统的触发状态和计算结果
|
||||
"""
|
||||
results = {'vimshottari': {'applicable': True, 'note': '始终适用(120年标准周期)'}}
|
||||
|
||||
# Dwisaptati
|
||||
lagna_lord = {'Aries':'Mars','Taurus':'Venus','Gemini':'Mercury','Cancer':'Moon',
|
||||
'Leo':'Sun','Virgo':'Mercury','Libra':'Venus','Scorpio':'Mars',
|
||||
'Sagittarius':'Jupiter','Capricorn':'Saturn','Aquarius':'Saturn','Pisces':'Jupiter'}.get(lagna_sign, '')
|
||||
lord_house = planets.get(lagna_lord, {}).get('house', 0)
|
||||
if check_dwisaptati_condition(lagna_lord, lord_house):
|
||||
results['dwisaptati'] = {'applicable': True, 'note': f'Lagna lord {lagna_lord}在{lord_house}宫——72年周期激活'}
|
||||
|
||||
# Shattrimsa
|
||||
if check_shattrimsa_condition(lagna_sign):
|
||||
results['shattrimsa'] = {'applicable': True, 'note': 'Moon-based condition——36年周期可用'}
|
||||
|
||||
# Dwadashottari
|
||||
venus_data = planets.get('Venus', {})
|
||||
venus_nak = int(venus_data.get('degree', 0) / (360/27))
|
||||
if check_dwadashottari_condition(venus_nak):
|
||||
results['dwadashottari'] = {'applicable': True, 'note': f'Venus在触发星宿——112年周期激活'}
|
||||
|
||||
return results
|
||||
@@ -6,18 +6,10 @@ Vedic Astrology Dasha Period Calculator
|
||||
This script helps calculate Dasha periods based on Nakshatra position.
|
||||
"""
|
||||
|
||||
# Standard Vimshottari Dasha periods in years
|
||||
VIMSHOTTARI_PERIODS = {
|
||||
'Ketu': 7,
|
||||
'Venus': 20,
|
||||
'Sun': 6,
|
||||
'Moon': 10,
|
||||
'Mars': 7,
|
||||
'Rahu': 18,
|
||||
'Jupiter': 16,
|
||||
'Saturn': 19,
|
||||
'Mercury': 17
|
||||
}
|
||||
try:
|
||||
from dasha_common import VIMSHOTTARI_YEARS as VIMSHOTTARI_PERIODS
|
||||
except ModuleNotFoundError: # pragma: no cover - package import
|
||||
from scripts.dasha_common import VIMSHOTTARI_YEARS as VIMSHOTTARI_PERIODS
|
||||
|
||||
# Planetary order in Vimshottari Dasha
|
||||
DASHA_ORDER = ['Ketu', 'Venus', 'Sun', 'Moon', 'Mars', 'Rahu', 'Jupiter', 'Saturn', 'Mercury']
|
||||
|
||||
@@ -21,18 +21,10 @@ import math
|
||||
# 恒星年(天)- 基于jyotishganit (MIT License),用于精确Dasha计算
|
||||
YEAR_DURATION_DAYS = 365.25636
|
||||
|
||||
# Vimshottari Dasha周期(年)- 总120年
|
||||
VIMSHOTTARI_PERIODS = {
|
||||
'Ketu': 7,
|
||||
'Venus': 20,
|
||||
'Sun': 6,
|
||||
'Moon': 10,
|
||||
'Mars': 7,
|
||||
'Rahu': 18,
|
||||
'Jupiter': 16,
|
||||
'Saturn': 19,
|
||||
'Mercury': 17
|
||||
}
|
||||
try:
|
||||
from dasha_common import VIMSHOTTARI_YEARS as VIMSHOTTARI_PERIODS
|
||||
except ModuleNotFoundError: # pragma: no cover - package import
|
||||
from scripts.dasha_common import VIMSHOTTARI_YEARS as VIMSHOTTARI_PERIODS
|
||||
|
||||
HUMAN_LIFE_SPAN_VIMSHOTTARI = 120.0
|
||||
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
"""Shared dasha arithmetic used by the conditional-dasha modules.
|
||||
|
||||
This module must not import the API server or PyJHora. PyJHora stays a test
|
||||
oracle only.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
NAKSHATRA_SPAN = 360.0 / 27.0
|
||||
|
||||
# Insertion order is the Vimshottari sequence. Callers compare items() before
|
||||
# replacing a local table, and rely on this order staying stable.
|
||||
VIMSHOTTARI_YEARS = {
|
||||
"Ketu": 7,
|
||||
"Venus": 20,
|
||||
"Sun": 6,
|
||||
"Moon": 10,
|
||||
"Mars": 7,
|
||||
"Rahu": 18,
|
||||
"Jupiter": 16,
|
||||
"Saturn": 19,
|
||||
"Mercury": 17,
|
||||
}
|
||||
VIMSHOTTARI_ORDER = list(VIMSHOTTARI_YEARS.keys())
|
||||
|
||||
|
||||
def nakshatra_seeded_birth_balance(
|
||||
moon_longitude: float,
|
||||
*,
|
||||
anchor_index: int,
|
||||
sequence: tuple | list,
|
||||
) -> tuple[str, float, int]:
|
||||
"""Birth lord and remaining years for a nakshatra-seeded conditional dasha.
|
||||
|
||||
The arithmetic matches the six copied helpers: offset from the anchor
|
||||
nakshatra, lord = sequence[offset % length], balance = years * (1 - fraction
|
||||
already elapsed inside the nakshatra). Operation order is fixed so the
|
||||
float matches the previous per-module copies.
|
||||
"""
|
||||
moon = float(moon_longitude) % 360.0
|
||||
nakshatra_index = int(moon // NAKSHATRA_SPAN)
|
||||
offset = (nakshatra_index - int(anchor_index)) % 27
|
||||
lord, full_years = sequence[offset % len(sequence)]
|
||||
fraction_elapsed = (moon % NAKSHATRA_SPAN) / NAKSHATRA_SPAN
|
||||
remaining_years = full_years * (1.0 - fraction_elapsed)
|
||||
return lord, remaining_years, nakshatra_index
|
||||
@@ -0,0 +1,342 @@
|
||||
"""HTTP dasha registry.
|
||||
|
||||
`DASHA_REGISTRY_METADATA` is the old `extended_dashas.DASHA_REGISTRY` moved
|
||||
verbatim so `/api/chart` `available_dashas` stays byte-identical.
|
||||
|
||||
`compute_dasha` calls the same modules `cmd_full_reading` calls, with the same
|
||||
birth-info fields. Systems without that module return "not implemented"
|
||||
instead of a fabricated timeline.
|
||||
|
||||
This module must not import `jyotish_api_server` or PyJHora.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from datetime import datetime
|
||||
from typing import Any
|
||||
|
||||
try:
|
||||
from source_bounded_child_periods import PYJHORA_PARENT_SEEDED_EQUAL_SPLIT_PROFILE
|
||||
except ModuleNotFoundError: # pragma: no cover - package import
|
||||
from scripts.source_bounded_child_periods import PYJHORA_PARENT_SEEDED_EQUAL_SPLIT_PROFILE
|
||||
|
||||
DASHA_REGISTRY_METADATA = {
|
||||
"vimshottari": {"name": "Vimshottari", "years": 120, "type": "nakshatra"},
|
||||
"ashtottari": {"name": "Ashtottari", "years": 108, "type": "nakshatra"},
|
||||
"yogini": {"name": "Yogini", "years": 36, "type": "nakshatra"},
|
||||
"kalachakra": {"name": "Kalachakra", "years": 94, "type": "nakshatra"},
|
||||
"tara": {"name": "Tara Dasha", "years": 81, "type": "nakshatra"},
|
||||
"shodasottari": {"name": "Shodasottari", "years": 128, "type": "nakshatra"},
|
||||
"panchottari": {"name": "Panchottari", "years": 105, "type": "nakshatra"},
|
||||
"satabdika": {"name": "Satabdika", "years": 100, "type": "nakshatra"},
|
||||
"chara": {"name": "Chara (Jaimini)", "years": 36, "type": "rasi"},
|
||||
"narayana": {"name": "Narayana", "years": 36, "type": "rasi"},
|
||||
"shoola": {"name": "Shoola Dasha", "years": 9, "type": "rasi"},
|
||||
"sudasa": {"name": "Sudasa", "years": 36, "type": "rasi"},
|
||||
"drig": {"name": "Drig Dasha", "years": 36, "type": "rasi"},
|
||||
"sthira": {"name": "Sthira Dasha", "years": 36, "type": "rasi"},
|
||||
"mandooka": {"name": "Mandooka Dasha", "years": 36, "type": "rasi"},
|
||||
"lagnamsaka": {"name": "Lagnamsaka Dasha", "years": 36, "type": "rasi"},
|
||||
"yogardha": {"name": "Yogardha Dasha", "years": 36, "type": "rasi"},
|
||||
"brahma": {"name": "Brahma Dasha", "years": 36, "type": "rasi"},
|
||||
"dwisaptati": {"name": "Dwisaptati Sama", "years": 72, "type": "conditional"},
|
||||
"shattrimsa": {"name": "Shattrimsa Sama", "years": 36, "type": "conditional"},
|
||||
"dwadashottari": {"name": "Dwadashottari", "years": 112, "type": "conditional"},
|
||||
"shasti_hayani": {"name": "Shasti-Hayani", "years": 60, "type": "conditional"},
|
||||
"chaturaaseeti": {"name": "Chaturaaseeti Sama", "years": 84, "type": "conditional"},
|
||||
"tithi_ashtottari": {"name": "Tithi Ashtottari", "years": 108, "type": "conditional"},
|
||||
"tithi_yogini": {"name": "Tithi Yogini", "years": 36, "type": "conditional"},
|
||||
"patyayini": {"name": "Patyayini", "years": 36, "type": "conditional"},
|
||||
"navamsa": {"name": "Navamsa Dasha", "years": 36, "type": "varga"},
|
||||
"kendradi": {"name": "Kendradi", "years": 38, "type": "bhav"},
|
||||
"paryaaya": {"name": "Paryaaya Dasha", "years": 36, "type": "varga"},
|
||||
"rashmi": {"name": "Rashmi Dasha", "years": 36, "type": "special"},
|
||||
"naisargika": {"name": "Naisargika Dasha", "years": 120, "type": "special"},
|
||||
"aayu": {"name": "Aayu Dasha", "years": 36, "type": "special"},
|
||||
"karaka": {"name": "Karaka Dasha", "years": 36, "type": "special"},
|
||||
"panchasvara": {"name": "Panchasvara Dasha", "years": 36, "type": "special"},
|
||||
"sudarsana": {"name": "Sudarsana Chakra Dasha", "years": 36, "type": "special"},
|
||||
}
|
||||
|
||||
_CONDITIONAL_CALLS = {
|
||||
"shodasottari": ("shodashottari_dasha", "calculate_shodashottari_dasha", {}),
|
||||
"dwadashottari": (
|
||||
"dwadashottari_dasha",
|
||||
"calculate_dwadashottari_dasha",
|
||||
{"sukramsaka_profile": "navamsa_lagna_taurus_or_libra"},
|
||||
),
|
||||
"dwisaptati": ("dwisaptatisama_dasha", "calculate_dwisaptatisama_dasha", {}),
|
||||
"shattrimsa": ("shattrimshatsama_dasha", "calculate_shattrimshatsama_dasha", {}),
|
||||
"panchottari": ("panchottari_dasha", "calculate_panchottari_dasha", {}),
|
||||
"satabdika": ("satabdika_dasha", "calculate_satabdika_dasha", {}),
|
||||
"chaturaaseeti": ("chaturshitisama_dasha", "calculate_chaturshitisama_dasha", {}),
|
||||
}
|
||||
|
||||
SIGNS = [
|
||||
"Aries", "Taurus", "Gemini", "Cancer", "Leo", "Virgo",
|
||||
"Libra", "Scorpio", "Sagittarius", "Capricorn", "Aquarius", "Pisces",
|
||||
]
|
||||
SIGN_LORDS = {
|
||||
"Aries": "Mars", "Taurus": "Venus", "Gemini": "Mercury", "Cancer": "Moon",
|
||||
"Leo": "Sun", "Virgo": "Mercury", "Libra": "Venus", "Scorpio": "Mars",
|
||||
"Sagittarius": "Jupiter", "Capricorn": "Saturn", "Aquarius": "Saturn", "Pisces": "Jupiter",
|
||||
}
|
||||
|
||||
|
||||
class DashaNotImplemented(Exception):
|
||||
"""The registry key exists, but there is no engine module behind it."""
|
||||
|
||||
|
||||
def available_dasha_rows() -> list[dict[str, Any]]:
|
||||
"""`/api/chart` `available_dashas` rows, same order and fields as before."""
|
||||
return [
|
||||
{"key": key, "name": info["name"], "years": info["years"], "type": info["type"]}
|
||||
for key, info in DASHA_REGISTRY_METADATA.items()
|
||||
]
|
||||
|
||||
|
||||
def _load(module_name: str):
|
||||
try:
|
||||
return __import__(module_name)
|
||||
except ModuleNotFoundError: # pragma: no cover - package import
|
||||
return __import__(f"scripts.{module_name}", fromlist=[module_name])
|
||||
|
||||
|
||||
def _longitude(value: Any) -> float | None:
|
||||
if isinstance(value, (int, float)):
|
||||
return float(value) % 360.0
|
||||
if not isinstance(value, dict):
|
||||
return None
|
||||
for key in ("lon", "longitude", "degree"):
|
||||
raw = value.get(key)
|
||||
if isinstance(raw, (int, float)):
|
||||
return float(raw) % 360.0
|
||||
sign_idx = value.get("sign_idx")
|
||||
if sign_idx is None and value.get("sign") in SIGNS:
|
||||
sign_idx = SIGNS.index(value["sign"])
|
||||
degree = value.get("degree_in_sign", value.get("degree", 0))
|
||||
try:
|
||||
if sign_idx is None:
|
||||
return None
|
||||
return (int(sign_idx) * 30 + float(degree or 0)) % 360.0
|
||||
except (TypeError, ValueError):
|
||||
return None
|
||||
|
||||
|
||||
def _planet_longitudes(planets: dict) -> dict[str, float]:
|
||||
longitudes = {}
|
||||
if not isinstance(planets, dict):
|
||||
return longitudes
|
||||
for name, data in planets.items():
|
||||
longitude = _longitude(data)
|
||||
if longitude is not None:
|
||||
longitudes[str(name)] = longitude
|
||||
return longitudes
|
||||
|
||||
|
||||
def _optional_float(body: dict, *keys: str) -> float | None:
|
||||
for key in keys:
|
||||
raw = body.get(key)
|
||||
if isinstance(raw, (int, float)):
|
||||
return float(raw)
|
||||
return None
|
||||
|
||||
|
||||
def _reference_datetime(birth_dt: datetime, body: dict) -> datetime:
|
||||
raw = body.get("today") or body.get("current_date")
|
||||
if isinstance(raw, datetime):
|
||||
return raw
|
||||
if isinstance(raw, str) and raw.strip():
|
||||
return datetime.strptime(raw.strip()[:10], "%Y-%m-%d")
|
||||
return datetime.now()
|
||||
|
||||
|
||||
def build_reading_birth_info(
|
||||
birth_dt: datetime,
|
||||
planets: dict,
|
||||
ascendant: dict | None,
|
||||
body: dict | None = None,
|
||||
) -> tuple[dict[str, Any], dict[str, Any], dict[str, float]]:
|
||||
"""Same fields `cmd_full_reading` puts in `conditional_birth_info`.
|
||||
|
||||
Returns `(alt_dasha_info, conditional_birth_info, planet_longitudes)`.
|
||||
Kept here, not extracted from the engine, so the full-reading dict
|
||||
construction and its output stay untouched.
|
||||
"""
|
||||
body = body or {}
|
||||
ascendant = ascendant or {}
|
||||
planet_lons = _planet_longitudes(planets)
|
||||
moon_lon = planet_lons.get("Moon", _longitude(body.get("moon_lon")) or 0.0)
|
||||
sun_lon = planet_lons.get("Sun", _longitude(body.get("sun_lon")) or 0.0)
|
||||
asc_lon = _longitude(ascendant)
|
||||
if asc_lon is None:
|
||||
asc_lon = _longitude(body.get("ascendant_longitude"))
|
||||
if asc_lon is None:
|
||||
sign_idx = ascendant.get("sign_idx", body.get("asc_sign_idx", 0))
|
||||
try:
|
||||
asc_idx = int(sign_idx) % 12
|
||||
except (TypeError, ValueError):
|
||||
asc_idx = 0
|
||||
asc_lon = float(asc_idx * 30)
|
||||
asc_idx = int(float(asc_lon) / 30) % 12
|
||||
moon_nakshatra_index = int(moon_lon / (360 / 27)) % 27
|
||||
moon_pada = int((moon_lon % (360 / 27)) / (360 / 108)) + 1
|
||||
tithi_number = int(((moon_lon - sun_lon) % 360) / 12) + 1
|
||||
lagna_lord_name = SIGN_LORDS[SIGNS[asc_idx]]
|
||||
tenth_lord_name = SIGN_LORDS[SIGNS[(asc_idx + 9) % 12]]
|
||||
try:
|
||||
varga = _load("varga")
|
||||
navamsa_ascendant_sign_index = int(varga.calc_varga(asc_lon, 9).get("sign_idx", 0)) % 12
|
||||
venus_navamsa_sign_index = int(varga.calc_varga(float(planet_lons.get("Venus", 0)), 9).get("sign_idx", 0)) % 12
|
||||
except Exception:
|
||||
navamsa_ascendant_sign_index = None
|
||||
venus_navamsa_sign_index = None
|
||||
degree_in_sign = float(asc_lon) % 30.0
|
||||
ascendant_hora_lord = (
|
||||
"Sun"
|
||||
if (asc_idx % 2 == 0 and degree_in_sign < 15.0)
|
||||
or (asc_idx % 2 == 1 and degree_in_sign >= 15.0)
|
||||
else "Moon"
|
||||
)
|
||||
birth_is_daytime = None
|
||||
lat = _optional_float(body, "lat", "latitude")
|
||||
lon = _optional_float(body, "lon", "longitude")
|
||||
tz = _optional_float(body, "tz", "timezone")
|
||||
if lat is not None and lon is not None and tz is not None:
|
||||
try:
|
||||
daynight = _load("saham_daynight")
|
||||
birth_is_daytime = bool(daynight.determine_daytime(birth_dt, lat=lat, lon=lon, tz=tz).get("is_daytime"))
|
||||
except Exception:
|
||||
birth_is_daytime = None
|
||||
applicability_rule_family = (
|
||||
"rahu_from_lagna_lord_kendra_trikona_plus_paksha_daynight"
|
||||
if birth_is_daytime is not None
|
||||
else "moon_nakshatra_plus_paksha"
|
||||
)
|
||||
alt = {
|
||||
"birth_datetime": birth_dt,
|
||||
"moon_nakshatra_index": moon_nakshatra_index,
|
||||
"moon_pada": moon_pada,
|
||||
"is_shukla_paksha": 1 <= tithi_number <= 15,
|
||||
"lagna_rashi_index": asc_idx,
|
||||
}
|
||||
conditional = {
|
||||
**alt,
|
||||
"ascendant_longitude": asc_lon,
|
||||
"sun_longitude": sun_lon,
|
||||
"moon_longitude": moon_lon,
|
||||
"birth_is_daytime": birth_is_daytime,
|
||||
"ascendant_sign_index": asc_idx,
|
||||
"ascendant_hora_lord": ascendant_hora_lord,
|
||||
"navamsa_ascendant_sign_index": navamsa_ascendant_sign_index,
|
||||
"venus_rasi_sign_index": int(float(planet_lons.get("Venus", 0)) / 30) % 12,
|
||||
"venus_navamsa_sign_index": venus_navamsa_sign_index,
|
||||
"tenth_lord_sign_index": int(float(planet_lons.get(tenth_lord_name, 0)) / 30) % 12,
|
||||
"rahu_sign_index": int(float(planet_lons.get("Rahu", 0)) / 30) % 12,
|
||||
"lagna_lord_sign_index": int(float(planet_lons.get(lagna_lord_name, 0)) / 30) % 12,
|
||||
"applicability_rule_family": applicability_rule_family,
|
||||
}
|
||||
return alt, conditional, planet_lons
|
||||
|
||||
|
||||
def _major_rows(payload: Any) -> list:
|
||||
if isinstance(payload, dict):
|
||||
majors = payload.get("major")
|
||||
if isinstance(majors, list):
|
||||
return majors
|
||||
periods = payload.get("periods")
|
||||
if isinstance(periods, list):
|
||||
return periods
|
||||
sequence = payload.get("mahadasha_sequence")
|
||||
if isinstance(sequence, list):
|
||||
return sequence
|
||||
return []
|
||||
if isinstance(payload, list):
|
||||
return payload
|
||||
return []
|
||||
|
||||
|
||||
def _conditional_payload(key: str, conditional: dict) -> dict:
|
||||
module_name, function_name, extra = _CONDITIONAL_CALLS[key]
|
||||
module = _load(module_name)
|
||||
birth_info = {
|
||||
**conditional,
|
||||
**extra,
|
||||
"child_period_profile": PYJHORA_PARENT_SEEDED_EQUAL_SPLIT_PROFILE,
|
||||
}
|
||||
return getattr(module, function_name)(birth_info)
|
||||
|
||||
|
||||
def compute_dasha(
|
||||
key: str,
|
||||
*,
|
||||
birth_dt: datetime,
|
||||
planets: dict,
|
||||
ascendant: dict | None,
|
||||
body: dict | None = None,
|
||||
) -> dict[str, Any]:
|
||||
"""Return engine major periods for one registry key.
|
||||
|
||||
Raises `DashaNotImplemented` when the key has no engine module. Vimshottari
|
||||
stays on the handler's existing canonical branch and is not computed here.
|
||||
"""
|
||||
if key not in DASHA_REGISTRY_METADATA:
|
||||
raise DashaNotImplemented("Unknown dasha system")
|
||||
if key == "chara":
|
||||
raise DashaNotImplemented("chara dasha is served at /api/dasha/chara")
|
||||
body = body or {}
|
||||
alt, conditional, planet_lons = build_reading_birth_info(birth_dt, planets or {}, ascendant, body)
|
||||
if key == "yogini":
|
||||
payload = _load("yogini_dasha").calculate_yogini_dasha({**alt, "moon_longitude": conditional["moon_longitude"]})
|
||||
return {"periods": _major_rows(payload)}
|
||||
if key == "ashtottari":
|
||||
payload = _load("ashtottari_dasha").calculate_ashtottari_dasha({
|
||||
**alt,
|
||||
"moon_longitude": conditional["moon_longitude"],
|
||||
})
|
||||
return {"periods": _major_rows(payload)}
|
||||
if key == "kalachakra":
|
||||
payload = _load("kalachakra_dasha").calculate_kalachakra_dasha(alt)
|
||||
return {"periods": _major_rows(payload)}
|
||||
if key == "shasti_hayani":
|
||||
birth_info = dict(conditional)
|
||||
birth_info["child_period_profile"] = PYJHORA_PARENT_SEEDED_EQUAL_SPLIT_PROFILE
|
||||
payload = _load("shastihayani_dasha").calculate_shastihayani_dasha(birth_info)
|
||||
return {"periods": _major_rows(payload)}
|
||||
if key in _CONDITIONAL_CALLS:
|
||||
return {"periods": _major_rows(_conditional_payload(key, conditional))}
|
||||
if key == "narayana":
|
||||
asc_idx = int(conditional["ascendant_sign_index"])
|
||||
reference = _reference_datetime(birth_dt, body)
|
||||
current_age = (reference - birth_dt).days / 365.25
|
||||
birth_year = int(body.get("year") or birth_dt.year)
|
||||
narayana = _load("narayana_dasha")
|
||||
report = narayana.narayana_dasha_full_report(
|
||||
lagna_sign_idx=asc_idx,
|
||||
planet_lons=planet_lons,
|
||||
current_age=current_age,
|
||||
birth_year=birth_year,
|
||||
)
|
||||
labeled = _load("narayana_legacy_label").label_legacy_narayana(
|
||||
report,
|
||||
lagna_sign_idx=asc_idx,
|
||||
planet_lons=planet_lons,
|
||||
current_age=current_age,
|
||||
birth_year=birth_year,
|
||||
)
|
||||
return {
|
||||
"periods": _major_rows(labeled),
|
||||
"algorithm_label": labeled.get("algorithm_label") if isinstance(labeled, dict) else None,
|
||||
"algorithm_note": labeled.get("algorithm_note") if isinstance(labeled, dict) else None,
|
||||
"profile": labeled.get("profile") if isinstance(labeled, dict) else None,
|
||||
}
|
||||
if key == "shoola":
|
||||
rashi = _load("rashi_dashas_native")
|
||||
try:
|
||||
section = rashi.calculate_rashi_dashas(
|
||||
birth_dt.date(), conditional["ascendant_longitude"], planet_lons
|
||||
)["shoola"]
|
||||
except ValueError as exc:
|
||||
raise DashaNotImplemented(str(exc)) from exc
|
||||
return {"periods": _major_rows(section)}
|
||||
raise DashaNotImplemented("dasha system not implemented")
|
||||
@@ -6,6 +6,11 @@ from __future__ import annotations
|
||||
from datetime import datetime, timedelta
|
||||
from typing import Any
|
||||
|
||||
try:
|
||||
from scripts.dasha_common import nakshatra_seeded_birth_balance
|
||||
except ModuleNotFoundError: # pragma: no cover - scripts/ on sys.path
|
||||
from dasha_common import nakshatra_seeded_birth_balance
|
||||
|
||||
|
||||
YEAR_DAYS = 365.25636
|
||||
NAKSHATRA_SPAN = 360.0 / 27.0
|
||||
@@ -63,11 +68,9 @@ def _sequence_from(lord: str) -> list[tuple[str, int]]:
|
||||
|
||||
|
||||
def _birth_lord_and_balance(moon_longitude: float) -> tuple[str, float, int]:
|
||||
moon = float(moon_longitude) % 360.0
|
||||
nakshatra = int(moon // NAKSHATRA_SPAN)
|
||||
lord = DASHA_SEQUENCE[((nakshatra - MULA_INDEX) % 27) % len(DASHA_SEQUENCE)][0]
|
||||
balance = _YEARS[lord] * (1.0 - (moon % NAKSHATRA_SPAN) / NAKSHATRA_SPAN)
|
||||
return lord, balance, nakshatra
|
||||
return nakshatra_seeded_birth_balance(
|
||||
moon_longitude, anchor_index=MULA_INDEX, sequence=DASHA_SEQUENCE,
|
||||
)
|
||||
|
||||
|
||||
def calculate_dwisaptatisama_dasha(birth_info: dict[str, Any]) -> dict[str, Any]:
|
||||
|
||||
@@ -101,11 +101,12 @@ EVENT_KARAKAS = {
|
||||
}
|
||||
|
||||
# Vimshottari Dasha 行星
|
||||
DASHA_PLANETS = ['Ketu', 'Venus', 'Sun', 'Moon', 'Mars', 'Rahu', 'Jupiter', 'Saturn', 'Mercury']
|
||||
DASHA_YEARS = {
|
||||
'Ketu': 7, 'Venus': 20, 'Sun': 6, 'Moon': 10, 'Mars': 7,
|
||||
'Rahu': 18, 'Jupiter': 16, 'Saturn': 19, 'Mercury': 17,
|
||||
}
|
||||
try:
|
||||
from dasha_common import VIMSHOTTARI_ORDER as DASHA_PLANETS
|
||||
from dasha_common import VIMSHOTTARI_YEARS as DASHA_YEARS
|
||||
except ModuleNotFoundError: # pragma: no cover - package import
|
||||
from scripts.dasha_common import VIMSHOTTARI_ORDER as DASHA_PLANETS
|
||||
from scripts.dasha_common import VIMSHOTTARI_YEARS as DASHA_YEARS
|
||||
|
||||
# Graha Drishti 相位
|
||||
PLANET_ASPECTS = {
|
||||
|
||||
@@ -1,408 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
扩展Dasha系统 v2.0 — 冲刺全球第一
|
||||
新增:Kalachakra, Narayana, Yogini, Shasti-Hayani, Navamsa, Kendradi, Tara, Shoola
|
||||
|
||||
Dasha总数:10 → 18
|
||||
"""
|
||||
|
||||
from datetime import datetime, timedelta
|
||||
from typing import Dict, List, Tuple
|
||||
|
||||
YEAR_DAYS = 365.25636
|
||||
SIGNS = ['Aries','Taurus','Gemini','Cancer','Leo','Virgo',
|
||||
'Libra','Scorpio','Sagittarius','Capricorn','Aquarius','Pisces']
|
||||
DASHA_ORDER = ['Ketu','Venus','Sun','Moon','Mars','Rahu','Jupiter','Saturn','Mercury']
|
||||
NAKSHATRAS = ['Ashwini','Bharani','Krittika','Rohini','Mrigashira','Ardra',
|
||||
'Punarvasu','Pushya','Ashlesha','Magha','PurvaPhalguni','UttaraPhalguni',
|
||||
'Hasta','Chitra','Swati','Vishakha','Anuradha','Jyeshtha',
|
||||
'Mula','PurvaAshadha','UttaraAshadha','Shravana','Dhanishta','Shatabhisha',
|
||||
'PurvaBhadrapada','UttaraBhadrapada','Revati']
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# 1. Kalachakra Dasha(时轮大运)
|
||||
# =============================================================================
|
||||
|
||||
KALACHAKRA_NAVAMSHA_MAP = {}
|
||||
KALACHAKRA_SAVYA = ['Aries','Taurus','Gemini','Cancer','Leo','Virgo','Libra','Scorpio','Sagittarius']
|
||||
KALACHAKRA_APASAVYA = ['Scorpio','Libra','Virgo','Cancer','Leo','Gemini','Taurus','Aries','Sagittarius']
|
||||
KALACHAKRA_YEARS = {'Aries':10,'Taurus':16,'Gemini':18,'Cancer':24,'Leo':20,'Virgo':22,'Libra':12,'Scorpio':14,'Sagittarius':8,'Capricorn':7,'Aquarius':9,'Pisces':11}
|
||||
|
||||
def calc_kalachakra_dasha(birth_date: datetime, moon_nak_idx: int,
|
||||
moon_pada: int) -> List[Dict]:
|
||||
"""Kalachakra Dasha(时轮大运)"""
|
||||
# 判定savya/apasavya序列
|
||||
savya_naks = {0,1,2,3,4,5,10,11,12,13,14,15,22,23,24,25,26}
|
||||
is_savya = moon_nak_idx in savya_naks
|
||||
sequence = KALACHAKRA_SAVYA if is_savya else KALACHAKRA_APASAVYA
|
||||
|
||||
# 起始点
|
||||
pada_map = {1:0, 2:3, 3:6, 4:0} # 简化映射
|
||||
start_offset = pada_map.get(moon_pada, 0)
|
||||
start_idx = (moon_nak_idx % 9 + start_offset) % 9
|
||||
|
||||
results = []
|
||||
current = birth_date
|
||||
for i in range(9):
|
||||
sign_idx = (start_idx + i) % 9
|
||||
sign = sequence[sign_idx]
|
||||
years = KALACHAKRA_YEARS.get(sign, 10)
|
||||
end_date = current + timedelta(days=years * YEAR_DAYS)
|
||||
results.append({
|
||||
'lord': sign, 'years': years,
|
||||
'start': current.strftime('%Y-%m-%d'),
|
||||
'end': end_date.strftime('%Y-%m-%d'),
|
||||
})
|
||||
current = end_date
|
||||
return results
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# 2. Narayana Dasha(那罗延大运)
|
||||
# =============================================================================
|
||||
|
||||
NARAYANA_ORDER = ['Aries','Scorpio','Sagittarius','Pisces','Capricorn','Aquarius','Taurus','Virgo','Leo','Cancer','Gemini','Libra']
|
||||
|
||||
def calc_narayana_dasha(birth_date: datetime, asc_sign_idx: int) -> List[Dict]:
|
||||
"""Narayana Dasha — 基于星座的推运系统"""
|
||||
start_idx = NARAYANA_ORDER.index(SIGNS[asc_sign_idx])
|
||||
results = []
|
||||
current = birth_date
|
||||
for i in range(12):
|
||||
idx = (start_idx + i) % 12
|
||||
sign = NARAYANA_ORDER[idx]
|
||||
years = (idx % 3 + 1) * 3 # movable=3, fixed=6, dual=9
|
||||
end_date = current + timedelta(days=years * YEAR_DAYS)
|
||||
results.append({
|
||||
'lord': sign, 'years': years,
|
||||
'start': current.strftime('%Y-%m-%d'),
|
||||
'end': end_date.strftime('%Y-%m-%d'),
|
||||
})
|
||||
current = end_date
|
||||
return results
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# 3. Yogini Dasha(瑜伽女神大运)— 36年周期
|
||||
# =============================================================================
|
||||
|
||||
YOGINI_LORDS = ['Mangala','Pingala','Dhanya','Bhramari','Bhadrika','Ulka','Siddha','Sankata']
|
||||
YOGINI_YEARS = [1,2,3,4,5,6,7,8] # 总和=36
|
||||
|
||||
def calc_yogini_dasha(birth_date: datetime, moon_nak_idx: int) -> List[Dict]:
|
||||
"""Yogini Dasha — 36年女神周期"""
|
||||
start_idx = moon_nak_idx % 8
|
||||
results = []
|
||||
current = birth_date
|
||||
for i in range(8):
|
||||
idx = (start_idx + i) % 8
|
||||
lord = YOGINI_LORDS[idx]
|
||||
years = YOGINI_YEARS[idx]
|
||||
end_date = current + timedelta(days=years * YEAR_DAYS)
|
||||
results.append({
|
||||
'lord': lord, 'years': years,
|
||||
'start': current.strftime('%Y-%m-%d'),
|
||||
'end': end_date.strftime('%Y-%m-%d'),
|
||||
})
|
||||
current = end_date
|
||||
return results
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# 4. Shasti-Hayani Dasha(六十哈亚尼大运)
|
||||
# =============================================================================
|
||||
|
||||
SHASTI_LORDS = ['Sun','Moon','Mars','Mercury','Jupiter','Venus','Saturn']
|
||||
SHASTI_YEARS = [6,8,10,12,14,16,18]
|
||||
|
||||
def calc_shasti_hayani_dasha(birth_date: datetime, sun_sign_idx: int) -> List[Dict]:
|
||||
"""Shasti-Hayani Dasha — 基于太阳位置的60年推运"""
|
||||
start_idx = sun_sign_idx % 7
|
||||
results = []
|
||||
current = birth_date
|
||||
for i in range(7):
|
||||
idx = (start_idx + i) % 7
|
||||
lord = SHASTI_LORDS[idx]
|
||||
years = SHASTI_YEARS[idx]
|
||||
end_date = current + timedelta(days=years * YEAR_DAYS)
|
||||
results.append({
|
||||
'lord': lord, 'years': years,
|
||||
'start': current.strftime('%Y-%m-%d'),
|
||||
'end': end_date.strftime('%Y-%m-%d'),
|
||||
})
|
||||
current = end_date
|
||||
return results
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# 5. Navamsa Dasha(九分盘大运)
|
||||
# =============================================================================
|
||||
|
||||
def calc_navamsa_dasha(birth_date: datetime, d9_asc_sign_idx: int) -> List[Dict]:
|
||||
"""Navamsa Dasha — 基于D9上升的星座推运"""
|
||||
results = []
|
||||
current = birth_date
|
||||
for i in range(12):
|
||||
sign_idx = (d9_asc_sign_idx + i) % 12
|
||||
sign = SIGNS[sign_idx]
|
||||
years = (i % 3 + 1) * 3
|
||||
end_date = current + timedelta(days=years * YEAR_DAYS)
|
||||
results.append({
|
||||
'lord': sign, 'years': years,
|
||||
'start': current.strftime('%Y-%m-%d'),
|
||||
'end': end_date.strftime('%Y-%m-%d'),
|
||||
})
|
||||
current = end_date
|
||||
return results
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# 6. Kendradi Dasha(角宫推运)
|
||||
# =============================================================================
|
||||
|
||||
KENDRADI_ORDER = [1,4,7,10,2,5,8,11,3,6,9,12]
|
||||
|
||||
def calc_kendradi_dasha(birth_date: datetime, asc_sign_idx: int) -> List[Dict]:
|
||||
"""Kendradi Dasha — 从角宫开始的宫位推运"""
|
||||
results = []
|
||||
current = birth_date
|
||||
for i, house_num in enumerate(KENDRADI_ORDER):
|
||||
sign_idx = (asc_sign_idx + house_num - 1) % 12
|
||||
sign = SIGNS[sign_idx]
|
||||
years = 5 if i < 4 else 3 # 角宫5年,其他3年
|
||||
end_date = current + timedelta(days=years * YEAR_DAYS)
|
||||
results.append({
|
||||
'lord': f'House {house_num} ({sign})', 'years': years,
|
||||
'start': current.strftime('%Y-%m-%d'),
|
||||
'end': end_date.strftime('%Y-%m-%d'),
|
||||
})
|
||||
current = end_date
|
||||
return results
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# 7. Tara Dasha(星宿推运)
|
||||
# =============================================================================
|
||||
|
||||
def calc_tara_dasha(birth_date: datetime, moon_nak_idx: int) -> List[Dict]:
|
||||
"""Tara Dasha — 从出生星宿开始的推运"""
|
||||
results = []
|
||||
current = birth_date
|
||||
for i in range(27):
|
||||
nak_idx = (moon_nak_idx + i) % 27
|
||||
nak = NAKSHATRAS[nak_idx]
|
||||
years = 3
|
||||
end_date = current + timedelta(days=years * YEAR_DAYS)
|
||||
results.append({
|
||||
'lord': nak, 'years': years,
|
||||
'start': current.strftime('%Y-%m-%d'),
|
||||
'end': end_date.strftime('%Y-%m-%d'),
|
||||
})
|
||||
current = end_date
|
||||
return results
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# 8. Shoola Dasha(尖刺推运)
|
||||
# =============================================================================
|
||||
|
||||
def calc_shoola_dasha(birth_date: datetime, moon_sign_idx: int) -> List[Dict]:
|
||||
"""Shoola Dasha — 基于月亮星座的9年周期"""
|
||||
results = []
|
||||
current = birth_date
|
||||
for i in range(9):
|
||||
sign_idx = (moon_sign_idx + i) % 12
|
||||
sign = SIGNS[sign_idx]
|
||||
years = 9 - i if i < 9 else 1
|
||||
end_date = current + timedelta(days=years * YEAR_DAYS)
|
||||
results.append({
|
||||
'lord': sign, 'years': years,
|
||||
'start': current.strftime('%Y-%m-%d'),
|
||||
'end': end_date.strftime('%Y-%m-%d'),
|
||||
})
|
||||
current = end_date
|
||||
return results
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# Dasha注册表
|
||||
# =============================================================================
|
||||
|
||||
DASHA_REGISTRY = {
|
||||
# Nakshatra-based
|
||||
'vimshottari': {'name': 'Vimshottari', 'years': 120, 'type': 'nakshatra'},
|
||||
'ashtottari': {'name': 'Ashtottari', 'years': 108, 'type': 'nakshatra'},
|
||||
'yogini': {'name': 'Yogini', 'years': 36, 'type': 'nakshatra'},
|
||||
'kalachakra': {'name': 'Kalachakra', 'years': 94, 'type': 'nakshatra'},
|
||||
'tara': {'name': 'Tara Dasha', 'years': 81, 'type': 'nakshatra'},
|
||||
'shodasottari': {'name': 'Shodasottari', 'years': 128, 'type': 'nakshatra'},
|
||||
'panchottari': {'name': 'Panchottari', 'years': 105, 'type': 'nakshatra'},
|
||||
'satabdika': {'name': 'Satabdika', 'years': 100, 'type': 'nakshatra'},
|
||||
# Rasi-based
|
||||
'chara': {'name': 'Chara (Jaimini)', 'years': 36, 'type': 'rasi'},
|
||||
'narayana': {'name': 'Narayana', 'years': 36, 'type': 'rasi'},
|
||||
'shoola': {'name': 'Shoola Dasha', 'years': 9, 'type': 'rasi'},
|
||||
'sudasa': {'name': 'Sudasa', 'years': 36, 'type': 'rasi'},
|
||||
'drig': {'name': 'Drig Dasha', 'years': 36, 'type': 'rasi'},
|
||||
'sthira': {'name': 'Sthira Dasha', 'years': 36, 'type': 'rasi'},
|
||||
'mandooka': {'name': 'Mandooka Dasha', 'years': 36, 'type': 'rasi'},
|
||||
'lagnamsaka': {'name': 'Lagnamsaka Dasha', 'years': 36, 'type': 'rasi'},
|
||||
'yogardha': {'name': 'Yogardha Dasha', 'years': 36, 'type': 'rasi'},
|
||||
'brahma': {'name': 'Brahma Dasha', 'years': 36, 'type': 'rasi'},
|
||||
# Conditional/Tithi-based
|
||||
'dwisaptati': {'name': 'Dwisaptati Sama', 'years': 72, 'type': 'conditional'},
|
||||
'shattrimsa': {'name': 'Shattrimsa Sama', 'years': 36, 'type': 'conditional'},
|
||||
'dwadashottari': {'name': 'Dwadashottari', 'years': 112, 'type': 'conditional'},
|
||||
'shasti_hayani': {'name': 'Shasti-Hayani', 'years': 60, 'type': 'conditional'},
|
||||
'chaturaaseeti': {'name': 'Chaturaaseeti Sama', 'years': 84, 'type': 'conditional'},
|
||||
'tithi_ashtottari': {'name': 'Tithi Ashtottari', 'years': 108, 'type': 'conditional'},
|
||||
'tithi_yogini': {'name': 'Tithi Yogini', 'years': 36, 'type': 'conditional'},
|
||||
'patyayini': {'name': 'Patyayini', 'years': 36, 'type': 'conditional'},
|
||||
# Varga/Bhava-based
|
||||
'navamsa': {'name': 'Navamsa Dasha', 'years': 36, 'type': 'varga'},
|
||||
'kendradi': {'name': 'Kendradi', 'years': 38, 'type': 'bhav'},
|
||||
'paryaaya': {'name': 'Paryaaya Dasha', 'years': 36, 'type': 'varga'},
|
||||
# Special
|
||||
'rashmi': {'name': 'Rashmi Dasha', 'years': 36, 'type': 'special'},
|
||||
'naisargika': {'name': 'Naisargika Dasha', 'years': 120, 'type': 'special'},
|
||||
'aayu': {'name': 'Aayu Dasha', 'years': 36, 'type': 'special'},
|
||||
'karaka': {'name': 'Karaka Dasha', 'years': 36, 'type': 'special'},
|
||||
'panchasvara': {'name': 'Panchasvara Dasha', 'years': 36, 'type': 'special'},
|
||||
'sudarsana': {'name': 'Sudarsana Chakra Dasha', 'years': 36, 'type': 'special'},
|
||||
}
|
||||
|
||||
def get_available_dashas() -> List[str]:
|
||||
return list(DASHA_REGISTRY.keys())
|
||||
|
||||
def get_dasha_info(name: str) -> Dict:
|
||||
return DASHA_REGISTRY.get(name, {})
|
||||
|
||||
# =============================================================================
|
||||
# v6.7.6: 通用Dasha函数 — 所有35种注册Dasha现在都可计算
|
||||
# =============================================================================
|
||||
|
||||
def calc_generic_dasha(birth_date: datetime, lords: List[str], years: List[int],
|
||||
lord_names: Dict = None) -> List[Dict]:
|
||||
"""通用Dasha计算器 — 任意lord序列和年限组合"""
|
||||
results = []
|
||||
current = birth_date
|
||||
for lord, yrs in zip(lords, years):
|
||||
end_date = current + timedelta(days=yrs * YEAR_DAYS)
|
||||
results.append({
|
||||
'lord': lord_names.get(lord, lord) if lord_names else lord,
|
||||
'years': yrs,
|
||||
'start': current.strftime('%Y-%m-%d'),
|
||||
'end': end_date.strftime('%Y-%m-%d'),
|
||||
})
|
||||
current = end_date
|
||||
return results
|
||||
|
||||
def calc_shodasottari(birth_date, moon_nak_idx):
|
||||
"""Shodasottari Dasha (128年周期) — 16种大运"""
|
||||
lords = DASHA_ORDER * 2
|
||||
return calc_generic_dasha(birth_date, lords[:16], [8]*16)
|
||||
|
||||
def calc_panchottari(birth_date, moon_nak_idx):
|
||||
"""Panchottari Dasha (105年周期)"""
|
||||
return calc_generic_dasha(birth_date, DASHA_ORDER, [12]*9)[:9]
|
||||
|
||||
def calc_satabdika(birth_date, moon_nak_idx):
|
||||
"""Satabdika Dasha (100年周期)"""
|
||||
return calc_generic_dasha(birth_date, DASHA_ORDER, [11]*9)[:9]
|
||||
|
||||
def calc_chaturaaseeti(birth_date, moon_nak_idx):
|
||||
"""Chaturaaseeti Sama Dasha (84年周期)"""
|
||||
return calc_generic_dasha(birth_date, DASHA_ORDER, [9]*9)[:9]
|
||||
|
||||
def calc_tithi_ashtottari(birth_date, tithi_num):
|
||||
"""Tithi Ashtottari Dasha (108年周期)"""
|
||||
return calc_generic_dasha(birth_date, DASHA_ORDER, [8,12,6,10,7,18,16,19,17])[:9]
|
||||
|
||||
def calc_tithi_yogini(birth_date, tithi_num):
|
||||
"""Tithi Yogini Dasha (36年周期)"""
|
||||
return calc_generic_dasha(birth_date, YOGINI_LORDS, YOGINI_YEARS)
|
||||
|
||||
def calc_patyayini(birth_date, asc_sign_idx):
|
||||
"""Patyayini Dasha (36年周期)"""
|
||||
return calc_generic_dasha(birth_date, DASHA_ORDER, [4]*9)
|
||||
|
||||
def calc_rashmi(birth_date, moon_nak_idx):
|
||||
"""Rashmi Dasha (36年周期)"""
|
||||
return calc_generic_dasha(birth_date, DASHA_ORDER, [4]*9)
|
||||
|
||||
def calc_naisargika(birth_date):
|
||||
"""Naisargika Dasha (120年周期) — 自然生命周期"""
|
||||
lords = ['Moon','Mars','Mercury','Venus','Jupiter','Sun','Saturn']
|
||||
years_list = [1,2,9,20,18,20,50]
|
||||
return calc_generic_dasha(birth_date, lords, years_list)
|
||||
|
||||
# Dasha计算函数注册表
|
||||
DASHA_CALCULATORS = {
|
||||
'kalachakra': calc_kalachakra_dasha,
|
||||
'narayana': calc_narayana_dasha,
|
||||
'yogini': calc_yogini_dasha,
|
||||
'shasti_hayani': calc_shasti_hayani_dasha,
|
||||
'navamsa': calc_navamsa_dasha,
|
||||
'kendradi': calc_kendradi_dasha,
|
||||
'tara': calc_tara_dasha,
|
||||
'shoola': calc_shoola_dasha,
|
||||
'shodasottari': calc_shodasottari,
|
||||
'panchottari': calc_panchottari,
|
||||
'satabdika': calc_satabdika,
|
||||
'chaturaaseeti': calc_chaturaaseeti,
|
||||
'tithi_ashtottari': calc_tithi_ashtottari,
|
||||
'tithi_yogini': calc_tithi_yogini,
|
||||
'patyayini': calc_patyayini,
|
||||
'rashmi': calc_rashmi,
|
||||
'naisargika': calc_naisargika,
|
||||
# 此下为简化映射(使用通用函数或现有函数)
|
||||
'dwisaptati': calc_generic_dasha,
|
||||
'shattrimsa': calc_generic_dasha,
|
||||
'dwadashottari': calc_generic_dasha,
|
||||
'sudasa': calc_generic_dasha,
|
||||
'drig': calc_generic_dasha,
|
||||
'sthira': calc_generic_dasha,
|
||||
'mandooka': calc_generic_dasha,
|
||||
'lagnamsaka': calc_generic_dasha,
|
||||
'yogardha': calc_generic_dasha,
|
||||
'brahma': calc_generic_dasha,
|
||||
'paryaaya': calc_generic_dasha,
|
||||
'aayu': calc_generic_dasha,
|
||||
'karaka': calc_generic_dasha,
|
||||
'panchasvara': calc_generic_dasha,
|
||||
'sudarsana': calc_generic_dasha,
|
||||
}
|
||||
|
||||
def calc_any_dasha(name: str, birth_date, **kwargs) -> List[Dict]:
|
||||
"""根据名称计算任意Dasha"""
|
||||
calc_fn = DASHA_CALCULATORS.get(name)
|
||||
if calc_fn == calc_generic_dasha:
|
||||
return calc_generic_dasha(birth_date, DASHA_ORDER, [4]*9)
|
||||
if calc_fn:
|
||||
try:
|
||||
if name == 'kalachakra':
|
||||
return calc_fn(birth_date, kwargs.get('moon_nak_idx', 0), kwargs.get('moon_pada', 1))
|
||||
if name in {'yogini', 'tara', 'shodasottari', 'panchottari', 'satabdika', 'chaturaaseeti', 'rashmi'}:
|
||||
return calc_fn(birth_date, kwargs.get('moon_nak_idx', 0))
|
||||
if name == 'narayana':
|
||||
return calc_fn(birth_date, kwargs.get('asc_sign_idx', 0))
|
||||
if name == 'shoola':
|
||||
return calc_fn(birth_date, kwargs.get('moon_sign_idx', 0))
|
||||
if name == 'shasti_hayani':
|
||||
return calc_fn(birth_date, kwargs.get('sun_sign_idx', 0))
|
||||
if name == 'navamsa':
|
||||
return calc_fn(birth_date, kwargs.get('d9_asc_sign_idx', kwargs.get('asc_sign_idx', 0)))
|
||||
if name == 'kendradi':
|
||||
return calc_fn(birth_date, kwargs.get('asc_sign_idx', 0))
|
||||
if name in {'tithi_ashtottari', 'tithi_yogini'}:
|
||||
return calc_fn(birth_date, kwargs.get('tithi_num', 1))
|
||||
if name == 'patyayini':
|
||||
return calc_fn(birth_date, kwargs.get('asc_sign_idx', 0))
|
||||
if name == 'naisargika':
|
||||
return calc_fn(birth_date)
|
||||
return calc_fn(birth_date, **kwargs)
|
||||
except Exception:
|
||||
return calc_generic_dasha(birth_date, DASHA_ORDER, [4]*9)
|
||||
return []
|
||||
@@ -6349,26 +6349,6 @@ class JyotishAPIHandler(BaseHTTPRequestHandler, VedastroEvidenceMixin, SynastryM
|
||||
except (TypeError, ValueError):
|
||||
return None
|
||||
|
||||
def _fallback_dasha_periods(self, birth_dt, dasha_key, info):
|
||||
extended_dashas = _load_local_module('extended_dashas')
|
||||
DASHA_ORDER = extended_dashas.DASHA_ORDER
|
||||
cycle_years = float(info.get('years') or 36)
|
||||
if cycle_years <= 0:
|
||||
cycle_years = 36
|
||||
period_years = round(cycle_years / len(DASHA_ORDER), 2)
|
||||
periods = []
|
||||
current = birth_dt
|
||||
for lord in DASHA_ORDER:
|
||||
end_date = current + timedelta(days=period_years * 365.25)
|
||||
periods.append({
|
||||
'lord': lord,
|
||||
'years': period_years,
|
||||
'start': current.strftime('%Y-%m-%d'),
|
||||
'end': end_date.strftime('%Y-%m-%d'),
|
||||
})
|
||||
current = end_date
|
||||
return periods
|
||||
|
||||
def _import_chart_text(self, body):
|
||||
filename = str(body.get('filename', ''))[:160]
|
||||
text = body.get('text')
|
||||
@@ -6534,10 +6514,9 @@ class JyotishAPIHandler(BaseHTTPRequestHandler, VedastroEvidenceMixin, SynastryM
|
||||
ayanamsa=ayanamsa_name,
|
||||
)
|
||||
|
||||
# Dasha清单
|
||||
extended_dashas = _load_local_module('extended_dashas')
|
||||
dashas = extended_dashas.get_available_dashas()
|
||||
dasha_list = [{'key': k, 'name': extended_dashas.DASHA_REGISTRY[k]['name'], 'years': extended_dashas.DASHA_REGISTRY[k]['years'], 'type': extended_dashas.DASHA_REGISTRY[k]['type']} for k in dashas]
|
||||
# Dasha清单(元数据与旧注册表逐项相同,计算不走占位表)
|
||||
dasha_registry = _load_local_module('dasha_registry')
|
||||
dasha_list = dasha_registry.available_dasha_rows()
|
||||
|
||||
try:
|
||||
jaimini = _load_local_module('jaimini')
|
||||
@@ -7266,8 +7245,8 @@ class JyotishAPIHandler(BaseHTTPRequestHandler, VedastroEvidenceMixin, SynastryM
|
||||
if not isinstance(dasha_key, str):
|
||||
raise BadRequest('dasha must be a string')
|
||||
dasha_key = dasha_key.strip().lower()
|
||||
extended_dashas = _load_local_module('extended_dashas')
|
||||
info = extended_dashas.DASHA_REGISTRY.get(dasha_key)
|
||||
dasha_registry = _load_local_module('dasha_registry')
|
||||
info = dasha_registry.DASHA_REGISTRY_METADATA.get(dasha_key)
|
||||
if not info:
|
||||
raise BadRequest('Unknown dasha system')
|
||||
|
||||
@@ -7280,24 +7259,10 @@ class JyotishAPIHandler(BaseHTTPRequestHandler, VedastroEvidenceMixin, SynastryM
|
||||
moon_lon = self._planet_lon(planets, 'Moon')
|
||||
if moon_lon is None:
|
||||
moon_lon = self._normalize_degree(body, 'moon_lon', 0)
|
||||
sun_lon = self._planet_lon(planets, 'Sun')
|
||||
if sun_lon is None:
|
||||
sun_lon = self._normalize_degree(body, 'sun_lon', 0)
|
||||
|
||||
nak_size = 360 / 27
|
||||
moon_nak_idx = int(moon_lon / nak_size) % 27
|
||||
moon_pada = int((moon_lon % nak_size) / (nak_size / 4)) + 1
|
||||
moon_sign_idx = int(moon_lon / 30) % 12
|
||||
sun_sign_idx = int(sun_lon / 30) % 12
|
||||
asc_sign_idx = self._sign_idx_from_value(
|
||||
body.get('asc_sign_idx', ascendant.get('sign_idx', ascendant.get('sign'))),
|
||||
0,
|
||||
)
|
||||
d9_asc_sign_idx = self._sign_idx_from_value(body.get('d9_asc_sign_idx', asc_sign_idx), asc_sign_idx)
|
||||
tithi_num = self._get_int(body, 'tithi_num', 1, 1, 30)
|
||||
|
||||
vimshottari_analysis = None
|
||||
canonical_dasha = None
|
||||
computed = None
|
||||
if dasha_key == 'vimshottari':
|
||||
calculation_service = _load_local_module('domain_calculation_service')
|
||||
canonical_dasha = calculation_service.compute_vimshottari_timeline(
|
||||
@@ -7317,22 +7282,18 @@ class JyotishAPIHandler(BaseHTTPRequestHandler, VedastroEvidenceMixin, SynastryM
|
||||
body.get('today') or body.get('current_date'),
|
||||
)
|
||||
else:
|
||||
periods = extended_dashas.calc_any_dasha(
|
||||
dasha_key,
|
||||
birth_dt,
|
||||
moon_nak_idx=moon_nak_idx,
|
||||
moon_pada=moon_pada,
|
||||
asc_sign_idx=asc_sign_idx,
|
||||
moon_sign_idx=moon_sign_idx,
|
||||
sun_sign_idx=sun_sign_idx,
|
||||
d9_asc_sign_idx=d9_asc_sign_idx,
|
||||
tithi_num=tithi_num,
|
||||
)
|
||||
calc_fn = extended_dashas.DASHA_CALCULATORS.get(dasha_key)
|
||||
precision = 'generic' if not calc_fn or calc_fn.__name__ == 'calc_generic_dasha' else 'calculator'
|
||||
if not periods:
|
||||
periods = self._fallback_dasha_periods(birth_dt, dasha_key, info)
|
||||
precision = 'generic'
|
||||
try:
|
||||
computed = dasha_registry.compute_dasha(
|
||||
dasha_key,
|
||||
birth_dt=birth_dt,
|
||||
planets=planets,
|
||||
ascendant=ascendant or {},
|
||||
body=body,
|
||||
)
|
||||
except dasha_registry.DashaNotImplemented as exc:
|
||||
raise BadRequest(str(exc)) from exc
|
||||
periods = computed['periods']
|
||||
precision = 'engine_module'
|
||||
|
||||
result = {
|
||||
'success': True,
|
||||
@@ -7350,6 +7311,10 @@ class JyotishAPIHandler(BaseHTTPRequestHandler, VedastroEvidenceMixin, SynastryM
|
||||
result['birth_balance'] = canonical_dasha['birth_balance']
|
||||
result['calculation_contract'] = canonical_dasha['calculation_contract']
|
||||
result['result_hash'] = canonical_dasha['result_hash']
|
||||
if isinstance(computed, dict):
|
||||
for field in ('algorithm_label', 'algorithm_note', 'profile'):
|
||||
if computed.get(field):
|
||||
result[field] = computed[field]
|
||||
return result
|
||||
|
||||
def _compute_vimshottari_analysis_layer(self, birth_dt, moon_lon, current_date=None):
|
||||
|
||||
@@ -291,7 +291,10 @@ NAKSHATRA_LIST = [
|
||||
("Purva Bhadrapada", "Jupiter", 16), ("Uttara Bhadrapada", "Saturn", 19), ("Revati", "Mercury", 17),
|
||||
]
|
||||
DASHA_ORDER = ["Ketu", "Venus", "Sun", "Moon", "Mars", "Rahu", "Jupiter", "Saturn", "Mercury"]
|
||||
DASHA_YEARS = {"Ketu": 7, "Venus": 20, "Sun": 6, "Moon": 10, "Mars": 7, "Rahu": 18, "Jupiter": 16, "Saturn": 19, "Mercury": 17}
|
||||
try:
|
||||
from dasha_common import VIMSHOTTARI_YEARS as DASHA_YEARS
|
||||
except ModuleNotFoundError: # pragma: no cover - package import
|
||||
from scripts.dasha_common import VIMSHOTTARI_YEARS as DASHA_YEARS
|
||||
SIGNS = ['Aries', 'Taurus', 'Gemini', 'Cancer', 'Leo', 'Virgo', 'Libra', 'Scorpio', 'Sagittarius', 'Capricorn', 'Aquarius', 'Pisces']
|
||||
SIGNS_CN = {'Aries': '白羊座', 'Taurus': '金牛座', 'Gemini': '双子座', 'Cancer': '巨蟹座', 'Leo': '狮子座', 'Virgo': '处女座', 'Libra': '天秤座', 'Scorpio': '天蝎座', 'Sagittarius': '射手座', 'Capricorn': '摩羯座', 'Aquarius': '水瓶座', 'Pisces': '双鱼座'}
|
||||
SIGN_LORDS = {'Aries': 'Mars', 'Taurus': 'Venus', 'Gemini': 'Mercury', 'Cancer': 'Moon', 'Leo': 'Sun', 'Virgo': 'Mercury', 'Libra': 'Venus', 'Scorpio': 'Mars', 'Sagittarius': 'Jupiter', 'Capricorn': 'Saturn', 'Aquarius': 'Saturn', 'Pisces': 'Jupiter'}
|
||||
@@ -16532,7 +16535,10 @@ def cmd_full_reading(args):
|
||||
|
||||
try:
|
||||
from ashtottari_dasha import calculate_ashtottari_dasha
|
||||
ashtottari_data = calculate_ashtottari_dasha(birth_info_for_alt_dasha)
|
||||
ashtottari_data = calculate_ashtottari_dasha({
|
||||
**birth_info_for_alt_dasha,
|
||||
'moon_longitude': moon_lon,
|
||||
})
|
||||
report['modules']['ashtottari_dasha'] = ashtottari_data
|
||||
except Exception as alt_e:
|
||||
ashtottari_data = {'error': str(alt_e)}
|
||||
|
||||
@@ -33,8 +33,12 @@ NAKSHATRAS = [
|
||||
]
|
||||
|
||||
# Vimshottari年限(KP系统使用相同比例划分sublord)
|
||||
VIMSHOTTARI_DURATION = [7, 20, 6, 10, 7, 18, 16, 19, 17]
|
||||
KP_LORDS = ["Ketu", "Venus", "Sun", "Moon", "Mars", "Rahu", "Jupiter", "Saturn", "Mercury"]
|
||||
try:
|
||||
from dasha_common import VIMSHOTTARI_YEARS as _SHARED_VIMSHOTTARI_YEARS
|
||||
except ModuleNotFoundError: # pragma: no cover - package import
|
||||
from scripts.dasha_common import VIMSHOTTARI_YEARS as _SHARED_VIMSHOTTARI_YEARS
|
||||
VIMSHOTTARI_DURATION = [_SHARED_VIMSHOTTARI_YEARS[lord] for lord in KP_LORDS]
|
||||
STAR_LORDS = KP_LORDS * 3 # 27 Nakshatras = 3 cycles of 9 lords
|
||||
VIMSHOTTARI_YEARS = dict(zip(KP_LORDS, VIMSHOTTARI_DURATION))
|
||||
|
||||
|
||||
@@ -305,8 +305,10 @@ def _calc_ashtottari_antardasha(md_lord: str, md_years: float) -> List[Dict]:
|
||||
# ============================================================================
|
||||
|
||||
# Vimshottari Dasha 顺序(9星循环)
|
||||
VIMSHOTTARI_ORDER = ['Ketu', 'Venus', 'Sun', 'Moon', 'Mars', 'Rahu', 'Jupiter', 'Saturn', 'Mercury']
|
||||
VIMSHOTTARI_YEARS = {'Ketu': 7, 'Venus': 20, 'Sun': 6, 'Moon': 10, 'Mars': 7, 'Rahu': 18, 'Jupiter': 16, 'Saturn': 19, 'Mercury': 17}
|
||||
try:
|
||||
from dasha_common import VIMSHOTTARI_ORDER, VIMSHOTTARI_YEARS
|
||||
except ModuleNotFoundError: # pragma: no cover - package import
|
||||
from scripts.dasha_common import VIMSHOTTARI_ORDER, VIMSHOTTARI_YEARS
|
||||
|
||||
|
||||
def calc_nakshatra_dasha_breakdown(
|
||||
|
||||
@@ -13,8 +13,10 @@ from typing import Any
|
||||
|
||||
try:
|
||||
from scripts.source_bounded_child_periods import expand_parent_seeded_equal_split_children
|
||||
from scripts.dasha_common import nakshatra_seeded_birth_balance
|
||||
except ModuleNotFoundError: # pragma: no cover - scripts/ on sys.path
|
||||
from source_bounded_child_periods import expand_parent_seeded_equal_split_children
|
||||
from dasha_common import nakshatra_seeded_birth_balance
|
||||
|
||||
|
||||
YEAR_DAYS = 365.25636
|
||||
@@ -67,13 +69,9 @@ def _sequence_from(starting_lord: str) -> list[tuple[str, int]]:
|
||||
|
||||
|
||||
def _birth_lord_and_balance(moon_longitude: float) -> tuple[str, float, int]:
|
||||
moon_longitude = float(moon_longitude) % 360.0
|
||||
nakshatra_index = int(moon_longitude // NAKSHATRA_SPAN)
|
||||
offset = (nakshatra_index - ANURADHA_INDEX) % 27
|
||||
starting_lord = DASHA_SEQUENCE[offset % len(DASHA_SEQUENCE)][0]
|
||||
fraction_elapsed = (moon_longitude % NAKSHATRA_SPAN) / NAKSHATRA_SPAN
|
||||
remaining_years = _YEARS_BY_LORD[starting_lord] * (1.0 - fraction_elapsed)
|
||||
return starting_lord, remaining_years, nakshatra_index
|
||||
return nakshatra_seeded_birth_balance(
|
||||
moon_longitude, anchor_index=ANURADHA_INDEX, sequence=DASHA_SEQUENCE,
|
||||
)
|
||||
|
||||
|
||||
def _iso(value: datetime) -> str:
|
||||
|
||||
+4
-2
@@ -235,8 +235,10 @@ NAK_SPAN = 360.0 / 27.0 # 13.333...° per nakshatra
|
||||
SUB_SPAN = NAK_SPAN / 9.0 # ~1.481° per sub (每个sub由nakshatra lord的一个行星段构成)
|
||||
|
||||
# Vimshottari年数用于计算sub比例
|
||||
VIM_DURATIONS = {'Ketu':7,'Venus':20,'Sun':6,'Moon':10,'Mars':7,
|
||||
'Rahu':18,'Jupiter':16,'Saturn':19,'Mercury':17}
|
||||
try:
|
||||
from dasha_common import VIMSHOTTARI_YEARS as VIM_DURATIONS
|
||||
except ModuleNotFoundError: # pragma: no cover - package import
|
||||
from scripts.dasha_common import VIMSHOTTARI_YEARS as VIM_DURATIONS
|
||||
VIM_TOTAL = 120.0
|
||||
|
||||
|
||||
|
||||
@@ -12,8 +12,10 @@ from typing import Any
|
||||
|
||||
try:
|
||||
from scripts.source_bounded_child_periods import expand_parent_seeded_equal_split_children
|
||||
from scripts.dasha_common import nakshatra_seeded_birth_balance
|
||||
except ModuleNotFoundError: # pragma: no cover - scripts/ on sys.path
|
||||
from source_bounded_child_periods import expand_parent_seeded_equal_split_children
|
||||
from dasha_common import nakshatra_seeded_birth_balance
|
||||
|
||||
|
||||
YEAR_DAYS = 365.25636
|
||||
@@ -50,11 +52,9 @@ def _sequence_from(lord: str) -> list[tuple[str, int]]:
|
||||
|
||||
|
||||
def _birth_lord_and_balance(moon_longitude: float) -> tuple[str, float, int]:
|
||||
moon = float(moon_longitude) % 360.0
|
||||
nakshatra = int(moon // NAKSHATRA_SPAN)
|
||||
lord = DASHA_SEQUENCE[((nakshatra - REVATI_INDEX) % 27) % len(DASHA_SEQUENCE)][0]
|
||||
balance = _YEARS[lord] * (1.0 - (moon % NAKSHATRA_SPAN) / NAKSHATRA_SPAN)
|
||||
return lord, balance, nakshatra
|
||||
return nakshatra_seeded_birth_balance(
|
||||
moon_longitude, anchor_index=REVATI_INDEX, sequence=DASHA_SEQUENCE,
|
||||
)
|
||||
|
||||
|
||||
def calculate_satabdika_dasha(birth_info: dict[str, Any]) -> dict[str, Any]:
|
||||
|
||||
@@ -6,6 +6,11 @@ from __future__ import annotations
|
||||
from datetime import datetime, timedelta
|
||||
from typing import Any
|
||||
|
||||
try:
|
||||
from scripts.dasha_common import nakshatra_seeded_birth_balance
|
||||
except ModuleNotFoundError: # pragma: no cover - scripts/ on sys.path
|
||||
from dasha_common import nakshatra_seeded_birth_balance
|
||||
|
||||
|
||||
YEAR_DAYS = 365.25636
|
||||
NAKSHATRA_SPAN = 360.0 / 27.0
|
||||
@@ -49,11 +54,9 @@ def _sequence_from(lord: str) -> list[tuple[str, int]]:
|
||||
|
||||
|
||||
def _birth_lord_and_balance(moon_longitude: float) -> tuple[str, float, int]:
|
||||
moon = float(moon_longitude) % 360.0
|
||||
nakshatra = int(moon // NAKSHATRA_SPAN)
|
||||
lord = DASHA_SEQUENCE[((nakshatra - SRAVANA_INDEX) % 27) % len(DASHA_SEQUENCE)][0]
|
||||
balance = _YEARS[lord] * (1.0 - (moon % NAKSHATRA_SPAN) / NAKSHATRA_SPAN)
|
||||
return lord, balance, nakshatra
|
||||
return nakshatra_seeded_birth_balance(
|
||||
moon_longitude, anchor_index=SRAVANA_INDEX, sequence=DASHA_SEQUENCE,
|
||||
)
|
||||
|
||||
|
||||
def calculate_shattrimshatsama_dasha(birth_info: dict[str, Any]) -> dict[str, Any]:
|
||||
|
||||
@@ -14,8 +14,10 @@ from typing import Any
|
||||
|
||||
try:
|
||||
from scripts.source_bounded_child_periods import expand_parent_seeded_equal_split_children
|
||||
from scripts.dasha_common import nakshatra_seeded_birth_balance
|
||||
except ModuleNotFoundError: # pragma: no cover - scripts/ on sys.path
|
||||
from source_bounded_child_periods import expand_parent_seeded_equal_split_children
|
||||
from dasha_common import nakshatra_seeded_birth_balance
|
||||
|
||||
|
||||
YEAR_DAYS = 365.25636
|
||||
@@ -79,13 +81,9 @@ def _sequence_from(starting_lord: str) -> list[tuple[str, int]]:
|
||||
|
||||
|
||||
def _birth_lord_and_balance(moon_longitude: float) -> tuple[str, float, int]:
|
||||
moon_longitude = float(moon_longitude) % 360.0
|
||||
nakshatra_index = int(moon_longitude // NAKSHATRA_SPAN)
|
||||
offset = (nakshatra_index - PUSHYA_INDEX) % 27
|
||||
starting_lord = DASHA_SEQUENCE[offset % len(DASHA_SEQUENCE)][0]
|
||||
fraction_elapsed = (moon_longitude % NAKSHATRA_SPAN) / NAKSHATRA_SPAN
|
||||
remaining_years = _YEARS_BY_LORD[starting_lord] * (1.0 - fraction_elapsed)
|
||||
return starting_lord, remaining_years, nakshatra_index
|
||||
return nakshatra_seeded_birth_balance(
|
||||
moon_longitude, anchor_index=PUSHYA_INDEX, sequence=DASHA_SEQUENCE,
|
||||
)
|
||||
|
||||
|
||||
def _iso(value: datetime) -> str:
|
||||
|
||||
+11
-4
@@ -26,6 +26,13 @@ import logging
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
try:
|
||||
from dasha_common import VIMSHOTTARI_ORDER as _VIMSHOTTARI_ORDER
|
||||
from dasha_common import VIMSHOTTARI_YEARS as _VIMSHOTTARI_YEARS
|
||||
except ModuleNotFoundError: # pragma: no cover - package import
|
||||
from scripts.dasha_common import VIMSHOTTARI_ORDER as _VIMSHOTTARI_ORDER
|
||||
from scripts.dasha_common import VIMSHOTTARI_YEARS as _VIMSHOTTARI_YEARS
|
||||
|
||||
SIGNS = ['Aries','Taurus','Gemini','Cancer','Leo','Virgo',
|
||||
'Libra','Scorpio','Sagittarius','Capricorn','Aquarius','Pisces']
|
||||
SIGN_LORDS = {'Aries':'Mars','Taurus':'Venus','Gemini':'Mercury','Cancer':'Moon',
|
||||
@@ -342,8 +349,8 @@ def calc_mudda_dasha(asc_sign_idx: int,
|
||||
规则: 从年度守护星开始,按Vimshottari顺序排列
|
||||
每个大运按比例分配12个月
|
||||
"""
|
||||
DASHA_ORDER = ["Ketu","Venus","Sun","Moon","Mars","Rahu","Jupiter","Saturn","Mercury"]
|
||||
DASHA_YEARS = {"Ketu":7,"Venus":20,"Sun":6,"Moon":10,"Mars":7,"Rahu":18,"Jupiter":16,"Saturn":19,"Mercury":17}
|
||||
DASHA_ORDER = list(_VIMSHOTTARI_ORDER)
|
||||
DASHA_YEARS = _VIMSHOTTARI_YEARS
|
||||
|
||||
start_idx = DASHA_ORDER.index(varsha_lord) if varsha_lord in DASHA_ORDER else 0
|
||||
|
||||
@@ -395,8 +402,8 @@ def calc_mudda_with_dasha_balance(
|
||||
producers rather than treating a different balance source as a label.
|
||||
"""
|
||||
|
||||
dasha_order = ["Ketu", "Venus", "Sun", "Moon", "Mars", "Rahu", "Jupiter", "Saturn", "Mercury"]
|
||||
dasha_years = {"Ketu": 7, "Venus": 20, "Sun": 6, "Moon": 10, "Mars": 7, "Rahu": 18, "Jupiter": 16, "Saturn": 19, "Mercury": 17}
|
||||
dasha_order = list(_VIMSHOTTARI_ORDER)
|
||||
dasha_years = _VIMSHOTTARI_YEARS
|
||||
valid_profiles = {"moon_at_birth_time", "moon_in_varshaphala"}
|
||||
if not isinstance(annual_start, datetime):
|
||||
return {"status": "blocked", "reason": "true_solar_return_datetime_required_for_mudda_balance", "balance_profile": balance_profile}
|
||||
|
||||
+4
-4
@@ -24,10 +24,10 @@ import math
|
||||
SIGNS = ['Aries', 'Taurus', 'Gemini', 'Cancer', 'Leo', 'Virgo',
|
||||
'Libra', 'Scorpio', 'Sagittarius', 'Capricorn', 'Aquarius', 'Pisces']
|
||||
|
||||
DASHA_YEARS = {
|
||||
"Ketu": 7, "Venus": 20, "Sun": 6, "Moon": 10,
|
||||
"Mars": 7, "Rahu": 18, "Jupiter": 16, "Saturn": 19, "Mercury": 17
|
||||
}
|
||||
try:
|
||||
from dasha_common import VIMSHOTTARI_YEARS as DASHA_YEARS
|
||||
except ModuleNotFoundError: # pragma: no cover - package import
|
||||
from scripts.dasha_common import VIMSHOTTARI_YEARS as DASHA_YEARS
|
||||
|
||||
EXPECTED_PLANETS = ['Sun', 'Moon', 'Mars', 'Mercury', 'Jupiter', 'Venus', 'Saturn', 'Rahu', 'Ketu']
|
||||
|
||||
|
||||
@@ -13,8 +13,12 @@ from typing import Any
|
||||
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
DASHA_ORDER = ["Ketu", "Venus", "Sun", "Moon", "Mars", "Rahu", "Jupiter", "Saturn", "Mercury"]
|
||||
DASHA_YEARS = {"Ketu": 7, "Venus": 20, "Sun": 6, "Moon": 10, "Mars": 7, "Rahu": 18, "Jupiter": 16, "Saturn": 19, "Mercury": 17}
|
||||
try:
|
||||
from dasha_common import VIMSHOTTARI_ORDER as DASHA_ORDER
|
||||
from dasha_common import VIMSHOTTARI_YEARS as DASHA_YEARS
|
||||
except ModuleNotFoundError: # pragma: no cover - package import
|
||||
from scripts.dasha_common import VIMSHOTTARI_ORDER as DASHA_ORDER
|
||||
from scripts.dasha_common import VIMSHOTTARI_YEARS as DASHA_YEARS
|
||||
|
||||
|
||||
def _parse_date(value: str) -> datetime:
|
||||
|
||||
Reference in New Issue
Block a user