feat: 完整数据版加入六种星座大运
Sthira、Drig、Shoola、Navamsha、Niryaana Shoola 与 K.N. Rao 的 Chara 从本盘算出。Lagna Kendradi 不上表。未推送。
This commit is contained in:
@@ -0,0 +1,216 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Goel / BVB short graha and rashi strength for the report rashi dashas.
|
||||
|
||||
Dignity, amsha, and kendradi units follow the short method in
|
||||
Predicting through Jaimini Astrology, printed pages 100-108.
|
||||
This is not Shadbala and not a house-strength table from another program.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Dict, Mapping
|
||||
|
||||
try:
|
||||
from jaimini import FRIENDSHIP, KARAKA_7, SIGNS, calc_chara_karaka_7
|
||||
from narayana_dasha import SIGN_LORDS, _rasi_aspects
|
||||
except ImportError: # pragma: no cover - package import
|
||||
from scripts.jaimini import FRIENDSHIP, KARAKA_7, SIGNS, calc_chara_karaka_7
|
||||
from scripts.narayana_dasha import SIGN_LORDS, _rasi_aspects
|
||||
|
||||
CLASSICAL_PLANETS = ("Sun", "Moon", "Mars", "Mercury", "Jupiter", "Venus", "Saturn")
|
||||
_EXALT_SIGN = {
|
||||
"Sun": 0,
|
||||
"Moon": 1,
|
||||
"Mars": 9,
|
||||
"Mercury": 5,
|
||||
"Jupiter": 3,
|
||||
"Venus": 11,
|
||||
"Saturn": 6,
|
||||
}
|
||||
_DEBIL_SIGN = {
|
||||
"Sun": 6,
|
||||
"Moon": 7,
|
||||
"Mars": 3,
|
||||
"Mercury": 11,
|
||||
"Jupiter": 9,
|
||||
"Venus": 5,
|
||||
"Saturn": 0,
|
||||
}
|
||||
_OWN_SIGNS = {
|
||||
"Sun": {4},
|
||||
"Moon": {3},
|
||||
"Mars": {0, 7},
|
||||
"Mercury": {2, 5},
|
||||
"Jupiter": {8, 11},
|
||||
"Venus": {1, 6},
|
||||
"Saturn": {9, 10},
|
||||
}
|
||||
# Moolatrikona bands are degree-in-sign, end exclusive.
|
||||
_MOOLATRIKONA = {
|
||||
"Sun": ((4, 0.0, 20.0),),
|
||||
"Moon": ((1, 3.0, 30.0),),
|
||||
"Mars": ((0, 0.0, 12.0),),
|
||||
"Mercury": ((5, 15.0, 20.0),),
|
||||
"Jupiter": ((8, 0.0, 10.0),),
|
||||
"Venus": ((6, 0.0, 15.0),),
|
||||
"Saturn": ((10, 0.0, 20.0),),
|
||||
}
|
||||
_AMSHA_BY_RANK = {1: 70, 2: 60, 3: 50, 4: 40, 5: 30, 6: 20, 7: 10}
|
||||
_CHARA_RASHI = {0: 20, 1: 40, 2: 60}
|
||||
|
||||
|
||||
def sign_index(longitude: float) -> int:
|
||||
return int(float(longitude) % 360.0 // 30.0)
|
||||
|
||||
|
||||
def degree_in_sign(longitude: float) -> float:
|
||||
return float(longitude) % 30.0
|
||||
|
||||
|
||||
def _in_moolatrikona(planet: str, sign_idx: int, degree: float) -> bool:
|
||||
for sign, start, end in _MOOLATRIKONA.get(planet, ()):
|
||||
if sign == sign_idx and start <= degree < end:
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def dignity_units(planet: str, sign_idx: int, degree: float) -> int:
|
||||
"""Short-method dignity. Whole exalt and debil signs, except Mercury in Virgo."""
|
||||
if planet not in CLASSICAL_PLANETS:
|
||||
raise KeyError(planet)
|
||||
if sign_idx == _DEBIL_SIGN[planet]:
|
||||
return 10
|
||||
if planet == "Mercury" and sign_idx == 5:
|
||||
if degree < 15.0:
|
||||
return 70
|
||||
if degree < 20.0:
|
||||
return 60
|
||||
return 50
|
||||
if sign_idx == _EXALT_SIGN[planet]:
|
||||
return 70
|
||||
if _in_moolatrikona(planet, sign_idx, degree):
|
||||
return 60
|
||||
if sign_idx in _OWN_SIGNS[planet]:
|
||||
return 50
|
||||
lord = SIGN_LORDS[SIGNS[sign_idx]]
|
||||
relation = FRIENDSHIP.get(planet, {})
|
||||
if lord in relation.get("friend", []):
|
||||
return 40
|
||||
if lord in relation.get("enemy", []):
|
||||
return 20
|
||||
return 30
|
||||
|
||||
|
||||
def kendradi_units(sign_idx: int, ak_sign_idx: int) -> int:
|
||||
offset = (int(sign_idx) - int(ak_sign_idx)) % 12
|
||||
if offset % 3 == 0:
|
||||
return 60
|
||||
if offset % 3 == 1:
|
||||
return 40
|
||||
return 20
|
||||
|
||||
|
||||
def karaka_ranks(planet_lons: Mapping[str, float]) -> Dict[str, int]:
|
||||
degrees = {
|
||||
planet: degree_in_sign(planet_lons[planet])
|
||||
for planet in CLASSICAL_PLANETS
|
||||
if planet in planet_lons
|
||||
}
|
||||
table = calc_chara_karaka_7(degrees).get("karaka_table") or {}
|
||||
ranks = {}
|
||||
for rank, name in KARAKA_7.items():
|
||||
row = table.get(name) or {}
|
||||
planet = row.get("planet")
|
||||
if planet:
|
||||
ranks[str(planet)] = int(rank)
|
||||
return ranks
|
||||
|
||||
|
||||
def graha_strengths(planet_lons: Mapping[str, float]) -> Dict[str, Dict[str, int]]:
|
||||
ranks = karaka_ranks(planet_lons)
|
||||
if "Mars" not in ranks and len(ranks) < 7:
|
||||
raise ValueError("short graha strength needs the seven classical planets")
|
||||
ak_planet = next(planet for planet, rank in ranks.items() if rank == 1)
|
||||
ak_sign = sign_index(planet_lons[ak_planet])
|
||||
rows = {}
|
||||
for planet in CLASSICAL_PLANETS:
|
||||
sign_idx = sign_index(planet_lons[planet])
|
||||
degree = degree_in_sign(planet_lons[planet])
|
||||
dignity = dignity_units(planet, sign_idx, degree)
|
||||
amsha = _AMSHA_BY_RANK[ranks[planet]]
|
||||
kendradi = kendradi_units(sign_idx, ak_sign)
|
||||
rows[planet] = {
|
||||
"sign_index": sign_idx,
|
||||
"dignity": dignity,
|
||||
"amsha": amsha,
|
||||
"kendradi": kendradi,
|
||||
"total": dignity + amsha + kendradi,
|
||||
"karaka_rank": ranks[planet],
|
||||
}
|
||||
return rows
|
||||
|
||||
|
||||
def _aspect_units(sign_idx: int, planet_lons: Mapping[str, float], lord: str) -> int:
|
||||
contributors = []
|
||||
for planet in (lord, "Jupiter", "Mercury"):
|
||||
if planet not in contributors:
|
||||
contributors.append(planet)
|
||||
total = 0
|
||||
for planet in contributors:
|
||||
if planet not in planet_lons:
|
||||
continue
|
||||
source = sign_index(planet_lons[planet])
|
||||
if source != sign_idx and not _rasi_aspects(source, sign_idx):
|
||||
continue
|
||||
if planet == lord and planet in {"Jupiter", "Mercury"}:
|
||||
total += 120
|
||||
else:
|
||||
total += 60
|
||||
return total
|
||||
|
||||
|
||||
def rashi_strengths(
|
||||
planet_lons: Mapping[str, float],
|
||||
graha: Mapping[str, Mapping[str, int]] | None = None,
|
||||
) -> Dict[int, Dict[str, int]]:
|
||||
graha_rows = graha if graha is not None else graha_strengths(planet_lons)
|
||||
occupants: Dict[int, int] = {index: 0 for index in range(12)}
|
||||
for planet in CLASSICAL_PLANETS:
|
||||
occupants[sign_index(planet_lons[planet])] += 1
|
||||
rows = {}
|
||||
for sign_idx in range(12):
|
||||
lord = SIGN_LORDS[SIGNS[sign_idx]]
|
||||
chara = _CHARA_RASHI[sign_idx % 3]
|
||||
sthira = 10 * occupants[sign_idx]
|
||||
aspect = _aspect_units(sign_idx, planet_lons, lord)
|
||||
lord_graha = int(graha_rows[lord]["total"])
|
||||
rows[sign_idx] = {
|
||||
"chara": chara,
|
||||
"sthira": sthira,
|
||||
"aspect": aspect,
|
||||
"lord_graha": lord_graha,
|
||||
"total": chara + sthira + aspect + lord_graha,
|
||||
}
|
||||
return rows
|
||||
|
||||
|
||||
def stronger_sign(left: int, right: int, strengths: Mapping[int, Mapping[str, int]], planet_lons: Mapping[str, float]) -> Dict[str, object]:
|
||||
"""Pick the stronger sign. Equal totals use the higher chara-karaka lord."""
|
||||
left_total = int(strengths[left]["total"])
|
||||
right_total = int(strengths[right]["total"])
|
||||
if left_total != right_total:
|
||||
selected = left if left_total > right_total else right
|
||||
return {"status": "selected", "sign_index": selected, "tie_break": "rashi_total"}
|
||||
ranks = karaka_ranks(planet_lons)
|
||||
left_lord = SIGN_LORDS[SIGNS[left]]
|
||||
right_lord = SIGN_LORDS[SIGNS[right]]
|
||||
left_rank = ranks.get(left_lord)
|
||||
right_rank = ranks.get(right_lord)
|
||||
if left_rank is None or right_rank is None or left_rank == right_rank:
|
||||
return {
|
||||
"status": "unresolved",
|
||||
"reason": "equal_rashi_strength_and_karaka",
|
||||
"candidates": (left, right),
|
||||
}
|
||||
selected = left if left_rank < right_rank else right
|
||||
return {"status": "selected", "sign_index": selected, "tie_break": "lord_karaka_rank"}
|
||||
@@ -2185,6 +2185,18 @@ def _apply_local_report_conventions(markdown: str, *, language: str = "zh") -> s
|
||||
return text
|
||||
|
||||
|
||||
def _append_native_rashi_dashas(markdown: str, packet: dict) -> str:
|
||||
"""Full-data edition only. The parity reader and the consult card do not call this."""
|
||||
try:
|
||||
from rashi_dasha_report import render_rashi_dasha_chapter
|
||||
except ModuleNotFoundError: # pragma: no cover - package import compatibility
|
||||
from scripts.rashi_dasha_report import render_rashi_dasha_chapter
|
||||
chapter = render_rashi_dasha_chapter(packet if isinstance(packet, dict) else {})
|
||||
if not chapter.strip():
|
||||
return markdown
|
||||
return markdown.rstrip() + "\n\n" + chapter
|
||||
|
||||
|
||||
def _insert_opening_focus(markdown: str, focus: str) -> str:
|
||||
"""Put the current-period focus directly under the title."""
|
||||
first = next((line.strip() for line in focus.splitlines() if line.strip()), "")
|
||||
@@ -2221,7 +2233,10 @@ def render_full_data_markdown(packet: dict) -> str:
|
||||
count=1,
|
||||
flags=re.DOTALL,
|
||||
).strip()
|
||||
english_markdown = _insert_opening_focus(render_pl9_parity_markdown(packet), focus)
|
||||
english_markdown = _append_native_rashi_dashas(
|
||||
_insert_opening_focus(render_pl9_parity_markdown(packet), focus),
|
||||
packet,
|
||||
)
|
||||
if structured_appendix:
|
||||
english_markdown = (
|
||||
english_markdown.rstrip()
|
||||
@@ -2246,7 +2261,10 @@ def render_full_data_markdown(packet: dict) -> str:
|
||||
count=1,
|
||||
flags=re.DOTALL,
|
||||
).strip()
|
||||
zh_markdown = _insert_opening_focus(render_pl9_parity_markdown(packet), focus)
|
||||
zh_markdown = _append_native_rashi_dashas(
|
||||
_insert_opening_focus(render_pl9_parity_markdown(packet), focus),
|
||||
packet,
|
||||
)
|
||||
if zh_structured_appendix and "## 结构化资料附录" not in zh_markdown:
|
||||
zh_markdown = (
|
||||
zh_markdown.rstrip()
|
||||
|
||||
@@ -0,0 +1,168 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Markdown chapter for the seven report-only rashi dashas."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Mapping
|
||||
|
||||
try:
|
||||
from rashi_dashas_native import calculate_rashi_dashas
|
||||
except ImportError: # pragma: no cover - package import
|
||||
from scripts.rashi_dashas_native import calculate_rashi_dashas
|
||||
|
||||
_SIGN_CN = {
|
||||
"Aries": "白羊座",
|
||||
"Taurus": "金牛座",
|
||||
"Gemini": "双子座",
|
||||
"Cancer": "巨蟹座",
|
||||
"Leo": "狮子座",
|
||||
"Virgo": "处女座",
|
||||
"Libra": "天秤座",
|
||||
"Scorpio": "天蝎座",
|
||||
"Sagittarius": "射手座",
|
||||
"Capricorn": "摩羯座",
|
||||
"Aquarius": "水瓶座",
|
||||
"Pisces": "双鱼座",
|
||||
}
|
||||
# Chinese headings avoid the token Dasha. The report cleaner turns that token
|
||||
# into 大运 and then deletes the linebreak before the next Chinese sentence.
|
||||
_ORDER = (
|
||||
("sthira", "Sthira", "Sthira Dasha"),
|
||||
("niryaana_shoola", "Niryaana Shoola", "Niryaana Shoola Dasha"),
|
||||
("drig", "Drig", "Drig Dasha"),
|
||||
("navamsha", "Navamsha", "Navamsha Dasha"),
|
||||
("shoola", "Shoola", "Shoola Dasha"),
|
||||
("chara", "Chara", "Chara Dasha"),
|
||||
("lagna_kendradi", "Lagna Kendradi", "Lagna Kendradi Rashi Dasha"),
|
||||
)
|
||||
_CLASSICAL = ("Sun", "Moon", "Mars", "Mercury", "Jupiter", "Venus", "Saturn")
|
||||
|
||||
|
||||
def _language(packet: Mapping) -> str:
|
||||
raw = packet.get("report_language") or packet.get("language") or "zh"
|
||||
return "en" if str(raw).lower().startswith("en") else "zh"
|
||||
|
||||
|
||||
def _longitude(row: Mapping) -> float | None:
|
||||
# Ascendant "degree" is the degree inside the sign. Full longitude is degree_raw / lon.
|
||||
for key in ("degree_raw", "lon", "longitude"):
|
||||
value = row.get(key)
|
||||
if isinstance(value, (int, float)):
|
||||
return float(value)
|
||||
sign = row.get("sign")
|
||||
degree = row.get("degree_in_sign")
|
||||
if sign in _SIGN_CN and isinstance(degree, (int, float)):
|
||||
signs = list(_SIGN_CN)
|
||||
return signs.index(str(sign)) * 30.0 + float(degree)
|
||||
return None
|
||||
|
||||
|
||||
def _birth_date(packet: Mapping) -> str | None:
|
||||
birth = packet.get("birth_info")
|
||||
if not isinstance(birth, Mapping):
|
||||
return None
|
||||
if birth.get("date"):
|
||||
return str(birth.get("date"))
|
||||
year, month, day = birth.get("year"), birth.get("month"), birth.get("day")
|
||||
if all(isinstance(part, int) for part in (year, month, day)):
|
||||
return f"{int(year):04d}-{int(month):02d}-{int(day):02d}"
|
||||
return None
|
||||
|
||||
|
||||
def chart_inputs(packet: Mapping) -> dict | None:
|
||||
core = packet.get("core_chart")
|
||||
if not isinstance(core, Mapping):
|
||||
return None
|
||||
asc = core.get("ascendant")
|
||||
planets = core.get("planets")
|
||||
if not isinstance(asc, Mapping) or not isinstance(planets, Mapping):
|
||||
return None
|
||||
asc_lon = _longitude(asc)
|
||||
birth = _birth_date(packet)
|
||||
if asc_lon is None or not birth:
|
||||
return None
|
||||
longitudes = {}
|
||||
for name in list(_CLASSICAL) + ["Rahu", "Ketu"]:
|
||||
row = planets.get(name)
|
||||
if isinstance(row, Mapping):
|
||||
value = _longitude(row)
|
||||
if value is not None:
|
||||
longitudes[name] = value
|
||||
if any(name not in longitudes for name in _CLASSICAL):
|
||||
return None
|
||||
return {"birth": birth, "ascendant_longitude": asc_lon, "planet_lons": longitudes}
|
||||
|
||||
|
||||
def _sign_label(sign: str, language: str) -> str:
|
||||
if language == "zh":
|
||||
return _SIGN_CN.get(sign, sign)
|
||||
return sign
|
||||
|
||||
|
||||
def _method_line(section: Mapping, language: str) -> str:
|
||||
if section.get("status") != "listed":
|
||||
return str(section.get("reason_en") if language == "en" else section.get("reason_zh") or "")
|
||||
profile = section.get("profile_id") or ""
|
||||
if language == "en":
|
||||
return (
|
||||
f"Method used on this site: {profile}, source: {section.get('source_en')}; "
|
||||
"not checked one-by-one against external software."
|
||||
)
|
||||
return f"本站选用的方法:{profile},出处:{section.get('source_zh')};未与外部软件逐一核对。"
|
||||
|
||||
|
||||
def render_rashi_dasha_chapter(packet: Mapping) -> str:
|
||||
"""Return the chapter, or an empty string when the packet has no longitudes."""
|
||||
inputs = chart_inputs(packet)
|
||||
if inputs is None:
|
||||
return ""
|
||||
language = _language(packet)
|
||||
sections = calculate_rashi_dashas(inputs["birth"], inputs["ascendant_longitude"], inputs["planet_lons"])
|
||||
if language == "en":
|
||||
lines = [
|
||||
"## Sign dashas calculated on this site",
|
||||
"",
|
||||
"These tables list signs and dates only.",
|
||||
"",
|
||||
]
|
||||
sign_header = "| Index | Sign | Start | End | Years |"
|
||||
child_header = "| Index | Maha | Antara | Start | End |"
|
||||
else:
|
||||
lines = [
|
||||
"## 星座大运(本站计算)",
|
||||
"",
|
||||
"以下只列出星座和日期。",
|
||||
"",
|
||||
]
|
||||
sign_header = "| 序号 | 星座 | 起始 | 结束 | 年数 |"
|
||||
child_header = "| 序号 | 主运 | 子运 | 起始 | 结束 |"
|
||||
for key, title_zh, title_en in _ORDER:
|
||||
section = sections[key]
|
||||
title = title_en if language == "en" else title_zh
|
||||
lines.extend([f"### {title}", "", _method_line(section, language), ""])
|
||||
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."
|
||||
if language == "en"
|
||||
else "这一节是 K.N. Rao 的 Chara 表,不是 Narayana 表。",
|
||||
"",
|
||||
])
|
||||
periods = section.get("periods") or []
|
||||
if not periods:
|
||||
continue
|
||||
lines.extend([sign_header, "|------|------|------|------|------|"])
|
||||
child_rows = []
|
||||
for index, period in enumerate(periods, start=1):
|
||||
label = _sign_label(str(period.get("sign")), language)
|
||||
lines.append(
|
||||
f"| {index} | {label} | {period.get('start')} | {period.get('end')} | {period.get('years')} |"
|
||||
)
|
||||
for child in period.get("antardasha") or []:
|
||||
child_rows.append((label, _sign_label(str(child.get("sign")), language), child.get("start"), child.get("end")))
|
||||
lines.append("")
|
||||
if child_rows:
|
||||
lines.extend([child_header, "|------|------|------|------|------|"])
|
||||
for index, (parent, child, start, end) in enumerate(child_rows, start=1):
|
||||
lines.append(f"| {index} | {parent} | {child} | {start} | {end} |")
|
||||
lines.append("")
|
||||
return "\n".join(lines).rstrip() + "\n"
|
||||
@@ -0,0 +1,417 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Report-only rashi dashas computed from this site's longitudes.
|
||||
|
||||
The seven sections are Sthira, Drig, Shoola, Navamsha, Niryaana Shoola,
|
||||
Lagna Kendradi, and Chara. Lagna Kendradi has no safe default and stays off
|
||||
the table. Chara dates the existing kn_rao rows; it does not recalculate them.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import calendar
|
||||
from datetime import date, datetime, timedelta
|
||||
from typing import Dict, Mapping, Sequence
|
||||
|
||||
try:
|
||||
from goel_short_strength import (
|
||||
CLASSICAL_PLANETS,
|
||||
degree_in_sign,
|
||||
graha_strengths,
|
||||
rashi_strengths,
|
||||
sign_index,
|
||||
stronger_sign,
|
||||
)
|
||||
from jaimini import SIGNS, calc_chara_dasha_with_antardasha
|
||||
from narayana_dasha import SIGN_LORDS, calculate_narayana_period_years
|
||||
except ImportError: # pragma: no cover - package import
|
||||
from scripts.goel_short_strength import (
|
||||
CLASSICAL_PLANETS,
|
||||
degree_in_sign,
|
||||
graha_strengths,
|
||||
rashi_strengths,
|
||||
sign_index,
|
||||
stronger_sign,
|
||||
)
|
||||
from scripts.jaimini import SIGNS, calc_chara_dasha_with_antardasha
|
||||
from scripts.narayana_dasha import SIGN_LORDS, calculate_narayana_period_years
|
||||
|
||||
_MOVABLE = {0, 3, 6, 9}
|
||||
_FIXED = {1, 4, 7, 10}
|
||||
_DUAL = {2, 5, 8, 11}
|
||||
|
||||
|
||||
def _as_date(value: date | datetime | str) -> date:
|
||||
if isinstance(value, datetime):
|
||||
return value.date()
|
||||
if isinstance(value, date):
|
||||
return value
|
||||
text = str(value).strip()
|
||||
if "T" in text:
|
||||
text = text.split("T", 1)[0]
|
||||
if " " in text:
|
||||
text = text.split(" ", 1)[0]
|
||||
year, month, day = (int(part) for part in text[:10].split("-"))
|
||||
return date(year, month, day)
|
||||
|
||||
|
||||
def add_years(day: date, years: int) -> date:
|
||||
try:
|
||||
return day.replace(year=day.year + int(years))
|
||||
except ValueError:
|
||||
return day.replace(year=day.year + int(years), day=28)
|
||||
|
||||
|
||||
def add_months(day: date, months: int) -> date:
|
||||
month_index = day.month - 1 + int(months)
|
||||
year = day.year + month_index // 12
|
||||
month = month_index % 12 + 1
|
||||
last = calendar.monthrange(year, month)[1]
|
||||
return date(year, month, min(day.day, last))
|
||||
|
||||
|
||||
def _iso(day: date) -> str:
|
||||
return day.isoformat()
|
||||
|
||||
|
||||
def _forward(sign_idx: int, count: int = 12) -> list[int]:
|
||||
return [(int(sign_idx) + offset) % 12 for offset in range(count)]
|
||||
|
||||
|
||||
def _reverse(sign_idx: int, count: int = 12) -> list[int]:
|
||||
return [(int(sign_idx) - offset) % 12 for offset in range(count)]
|
||||
|
||||
|
||||
def _aspecting_signs(sign_idx: int) -> set[int]:
|
||||
sign_idx = int(sign_idx) % 12
|
||||
if sign_idx in _MOVABLE:
|
||||
return {candidate for candidate in _FIXED if candidate != (sign_idx + 1) % 12}
|
||||
if sign_idx in _FIXED:
|
||||
return {candidate for candidate in _MOVABLE if candidate != (sign_idx - 1) % 12}
|
||||
return {candidate for candidate in _DUAL if candidate != sign_idx}
|
||||
|
||||
|
||||
def drig_aspect_sequence(lagna_sign: int) -> list[int]:
|
||||
"""Ninth, tenth, and eleventh anchors, each followed by the signs it aspects.
|
||||
|
||||
Crux printed page 395: an even-footed ninth is followed in reverse.
|
||||
Zero-based even indexes are the odd zodiac signs, so they go forward.
|
||||
"""
|
||||
rows: list[int] = []
|
||||
for offset in (8, 9, 10):
|
||||
anchor = (int(lagna_sign) + offset) % 12
|
||||
scan = _forward(anchor) if anchor % 2 == 0 else _reverse(anchor)
|
||||
allowed = _aspecting_signs(anchor)
|
||||
rows.extend([anchor] + [sign for sign in scan[1:] if sign in allowed])
|
||||
return rows[:12]
|
||||
|
||||
|
||||
def navamsha_sign(longitude: float) -> int:
|
||||
longitude = float(longitude) % 360.0
|
||||
rashi = int(longitude // 30.0)
|
||||
pada = int((longitude % 30.0) / (10.0 / 3.0))
|
||||
if pada > 8:
|
||||
pada = 8
|
||||
start = (0, 9, 6, 3)[rashi % 4]
|
||||
return (start + pada) % 12
|
||||
|
||||
|
||||
def navamsha_years(sign_idx: int, lord_sign_idx: int) -> int:
|
||||
"""Odd signs count forward, even signs backward. No dual lords and no minus one."""
|
||||
if int(sign_idx) % 2 == 0:
|
||||
distance = (int(lord_sign_idx) - int(sign_idx)) % 12
|
||||
else:
|
||||
distance = (int(sign_idx) - int(lord_sign_idx)) % 12
|
||||
if distance == 0:
|
||||
return 12
|
||||
return distance + 1
|
||||
|
||||
|
||||
def sthira_years(sign_idx: int) -> int:
|
||||
return (7, 8, 9)[int(sign_idx) % 3]
|
||||
|
||||
|
||||
def _period(sign_idx: int, years: int, start: date, end: date, children: list[dict] | None = None) -> dict:
|
||||
row = {
|
||||
"sign": SIGNS[int(sign_idx) % 12],
|
||||
"sign_index": int(sign_idx) % 12,
|
||||
"years": int(years),
|
||||
"start": _iso(start),
|
||||
"end": _iso(end),
|
||||
}
|
||||
if children is not None:
|
||||
row["antardasha"] = children
|
||||
return row
|
||||
|
||||
|
||||
def _calendar_children(parent_sign: int, start: date, *, months_each: int, count: int = 12) -> list[dict]:
|
||||
rows = []
|
||||
cursor = start
|
||||
for offset in range(count):
|
||||
child = (int(parent_sign) + offset) % 12
|
||||
nxt = add_months(cursor, months_each)
|
||||
rows.append({
|
||||
"sign": SIGNS[child],
|
||||
"sign_index": child,
|
||||
"start": _iso(cursor),
|
||||
"end": _iso(nxt),
|
||||
})
|
||||
cursor = nxt
|
||||
return rows
|
||||
|
||||
|
||||
def _equal_children(order: Sequence[int], start: date, end: date) -> list[dict]:
|
||||
if not order:
|
||||
return []
|
||||
total = (datetime.combine(end, datetime.min.time()) - datetime.combine(start, datetime.min.time())).total_seconds()
|
||||
if total <= 0:
|
||||
return []
|
||||
origin = datetime.combine(start, datetime.min.time())
|
||||
finish = datetime.combine(end, datetime.min.time())
|
||||
rows = []
|
||||
for offset, sign_idx in enumerate(order):
|
||||
child_end = finish if offset == len(order) - 1 else origin + timedelta(seconds=total * (offset + 1) / len(order))
|
||||
child_start = origin if offset == 0 else origin + timedelta(seconds=total * offset / len(order))
|
||||
rows.append({
|
||||
"sign": SIGNS[int(sign_idx) % 12],
|
||||
"sign_index": int(sign_idx) % 12,
|
||||
"start": child_start.date().isoformat(),
|
||||
"end": child_end.date().isoformat(),
|
||||
})
|
||||
return rows
|
||||
|
||||
|
||||
def _require_planets(planet_lons: Mapping[str, float]) -> None:
|
||||
missing = [planet for planet in CLASSICAL_PLANETS if planet not in planet_lons]
|
||||
if missing:
|
||||
raise ValueError("missing planets: " + ", ".join(missing))
|
||||
|
||||
|
||||
def select_brahma(lagna_sign: int, planet_lons: Mapping[str, float], graha: Mapping, rashi: Mapping) -> dict:
|
||||
seventh = (int(lagna_sign) + 6) % 12
|
||||
picked = stronger_sign(lagna_sign, seventh, rashi, planet_lons)
|
||||
if picked.get("status") != "selected":
|
||||
return {"status": "unresolved", "reason": picked.get("reason") or "lagna_seventh_unresolved", "step": "start_sign"}
|
||||
anchor = int(picked["sign_index"])
|
||||
candidates = []
|
||||
for house in (6, 8, 12):
|
||||
sign_idx = (anchor + house - 1) % 12
|
||||
lord = SIGN_LORDS[SIGNS[sign_idx]]
|
||||
if lord == "Saturn":
|
||||
continue
|
||||
candidates.append((
|
||||
int(graha[lord]["total"]),
|
||||
degree_in_sign(planet_lons[lord]),
|
||||
lord,
|
||||
sign_index(planet_lons[lord]),
|
||||
))
|
||||
if not candidates:
|
||||
return {"status": "unresolved", "reason": "no_brahma_lord_after_excluding_saturn", "step": "start_sign"}
|
||||
candidates.sort(key=lambda row: (row[0], row[1]), reverse=True)
|
||||
best = candidates[0]
|
||||
tied = [row for row in candidates if row[0] == best[0] and row[1] == best[1] and row[2] != best[2]]
|
||||
if tied:
|
||||
return {"status": "unresolved", "reason": "brahma_lord_tie", "step": "start_sign"}
|
||||
return {
|
||||
"status": "selected",
|
||||
"planet": best[2],
|
||||
"sign_index": best[3],
|
||||
"anchor_sign_index": anchor,
|
||||
"tie_break": picked.get("tie_break"),
|
||||
}
|
||||
|
||||
|
||||
def _listed(profile_id: str, source_zh: str, source_en: str, periods: list[dict], **extra) -> dict:
|
||||
row = {
|
||||
"profile_id": profile_id,
|
||||
"source_zh": source_zh,
|
||||
"source_en": source_en,
|
||||
"status": "listed",
|
||||
"periods": periods,
|
||||
}
|
||||
row.update(extra)
|
||||
return row
|
||||
|
||||
|
||||
def _omitted(profile_id: str, source_zh: str, source_en: str, reason_zh: str, reason_en: str) -> dict:
|
||||
return {
|
||||
"profile_id": profile_id,
|
||||
"source_zh": source_zh,
|
||||
"source_en": source_en,
|
||||
"status": "omitted",
|
||||
"reason_zh": reason_zh,
|
||||
"reason_en": reason_en,
|
||||
"periods": [],
|
||||
}
|
||||
|
||||
|
||||
def _sthira(birth: date, lagna: int, planet_lons: Mapping[str, float], graha: Mapping, rashi: Mapping) -> dict:
|
||||
source_zh = "Predicting through Jaimini Astrology,印页 100–108"
|
||||
source_en = "Predicting through Jaimini Astrology, printed pages 100-108"
|
||||
profile = "goel_rao_short_brahma_method_v1"
|
||||
picked = select_brahma(lagna, planet_lons, graha, rashi)
|
||||
if picked.get("status") != "selected":
|
||||
return _omitted(profile, source_zh, source_en, "本轮不上表:Brahma 星座没有选出。", "This report does not include Sthira: the Brahma sign was not selected.")
|
||||
start_sign = int(picked["sign_index"])
|
||||
periods = []
|
||||
cursor = birth
|
||||
for sign_idx in _forward(start_sign):
|
||||
years = sthira_years(sign_idx)
|
||||
end = add_years(cursor, years)
|
||||
children = _calendar_children(sign_idx, cursor, months_each=years)
|
||||
periods.append(_period(sign_idx, years, cursor, end, children))
|
||||
cursor = end
|
||||
return _listed(profile, source_zh, source_en, periods, brahma_planet=picked["planet"], direction="direct")
|
||||
|
||||
|
||||
def _narayana_years(sign_idx: int, planet_lons: Mapping[str, float]) -> int:
|
||||
lord = SIGN_LORDS[SIGNS[sign_idx]]
|
||||
lord_sign = sign_index(planet_lons[lord])
|
||||
years, _adjustment = calculate_narayana_period_years(
|
||||
sign_idx,
|
||||
lord,
|
||||
lord_sign,
|
||||
profile="jaimini_odd_footed_v1",
|
||||
)
|
||||
return int(years)
|
||||
|
||||
|
||||
def _drig(birth: date, lagna: int, planet_lons: Mapping[str, float], graha: Mapping) -> dict:
|
||||
source_zh = "Crux of Vedic Astrology,印页 288、395"
|
||||
source_en = "Crux of Vedic Astrology, printed pages 288 and 395"
|
||||
profile = "drig_rath_aspect_sequence_v1"
|
||||
periods = []
|
||||
cursor = birth
|
||||
for sign_idx in drig_aspect_sequence(lagna):
|
||||
years = _narayana_years(sign_idx, planet_lons)
|
||||
end = add_years(cursor, years)
|
||||
children = _drig_children(sign_idx, cursor, end, graha, planet_lons)
|
||||
periods.append(_period(sign_idx, years, cursor, end, children))
|
||||
cursor = end
|
||||
return _listed(profile, source_zh, source_en, periods, direction="aspect_groups")
|
||||
|
||||
|
||||
def _drig_children(md_sign: int, start: date, end: date, graha: Mapping, planet_lons: Mapping[str, float]) -> list[dict]:
|
||||
lord = SIGN_LORDS[SIGNS[md_sign]]
|
||||
seventh_lord = SIGN_LORDS[SIGNS[(md_sign + 6) % 12]]
|
||||
left = int(graha[lord]["total"])
|
||||
right = int(graha[seventh_lord]["total"])
|
||||
if left == right:
|
||||
return []
|
||||
chosen = lord if left > right else seventh_lord
|
||||
seed = sign_index(planet_lons[chosen])
|
||||
order = _forward(seed) if md_sign % 2 == 0 else _reverse(seed)
|
||||
return _equal_children(order, start, end)
|
||||
|
||||
|
||||
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)
|
||||
if picked.get("status") != "selected":
|
||||
return _omitted(
|
||||
profile,
|
||||
source_zh,
|
||||
source_en,
|
||||
"本轮不上表:上升与第七宫的短法力量没有分出强弱。",
|
||||
"This report does not include this dasha: Lagna and the seventh are equal in the short-method strength.",
|
||||
)
|
||||
start_sign = int(picked["sign_index"])
|
||||
periods = []
|
||||
cursor = birth
|
||||
for sign_idx in _forward(start_sign):
|
||||
end = add_years(cursor, 9)
|
||||
children = _calendar_children(sign_idx, cursor, months_each=9)
|
||||
periods.append(_period(sign_idx, 9, cursor, end, children))
|
||||
cursor = end
|
||||
return _listed(profile, source_zh, source_en, periods, direction="direct", start_tie_break=picked.get("tie_break"))
|
||||
|
||||
|
||||
def _navamsha(birth: date, asc_lon: float, planet_lons: Mapping[str, float]) -> dict:
|
||||
source_zh = "Predicting through Jaimini Astrology,印页 139"
|
||||
source_en = "Predicting through Jaimini Astrology, printed page 139"
|
||||
profile = "goel_method_1_v1"
|
||||
d9_lagna = navamsha_sign(asc_lon)
|
||||
d9_planets = {planet: navamsha_sign(planet_lons[planet]) for planet in CLASSICAL_PLANETS}
|
||||
start_sign = d9_lagna if d9_lagna % 2 == 0 else (d9_lagna + 6) % 12
|
||||
periods = []
|
||||
cursor = birth
|
||||
for sign_idx in _forward(start_sign):
|
||||
lord = SIGN_LORDS[SIGNS[sign_idx]]
|
||||
years = navamsha_years(sign_idx, d9_planets[lord])
|
||||
end = add_years(cursor, years)
|
||||
children = _calendar_children(sign_idx, cursor, months_each=years)
|
||||
periods.append(_period(sign_idx, years, cursor, end, children))
|
||||
cursor = end
|
||||
return _listed(
|
||||
profile,
|
||||
source_zh,
|
||||
source_en,
|
||||
periods,
|
||||
direction="direct",
|
||||
d9_lagna_index=d9_lagna,
|
||||
)
|
||||
|
||||
|
||||
def _chara(birth: date, lagna: int, planet_lons: Mapping[str, float]) -> dict:
|
||||
source_zh = "Predicting through Jaimini's Chara Dasha,印页 32–36"
|
||||
source_en = "Predicting through Jaimini's Chara Dasha, printed pages 32-36"
|
||||
profile = "kn_rao"
|
||||
longitudes = {planet: float(value) for planet, value in planet_lons.items()}
|
||||
calculated = calc_chara_dasha_with_antardasha(
|
||||
lagna,
|
||||
longitudes,
|
||||
birth.year,
|
||||
birth.month,
|
||||
birth.day,
|
||||
profile="kn_rao",
|
||||
)
|
||||
periods = []
|
||||
cursor = birth
|
||||
for row in calculated.get("dasha_sequence") or []:
|
||||
years = int(row["duration_years"])
|
||||
end = add_years(cursor, years)
|
||||
order = [int(child["sign_idx"]) for child in row.get("antardashas") or []]
|
||||
children = _equal_children(order, cursor, end)
|
||||
periods.append(_period(int(row["sign_idx"]), years, cursor, end, children))
|
||||
cursor = end
|
||||
return _listed(profile, source_zh, source_en, periods, direction="ninth_house_even_footed")
|
||||
|
||||
|
||||
def calculate_rashi_dashas(birth: date | datetime | str, ascendant_longitude: float, planet_lons: Mapping[str, float]) -> Dict[str, dict]:
|
||||
"""Return the seven report sections. Strength and seeds come from these longitudes."""
|
||||
_require_planets(planet_lons)
|
||||
birth_day = _as_date(birth)
|
||||
longitudes = {planet: float(planet_lons[planet]) for planet in planet_lons}
|
||||
lagna = sign_index(float(ascendant_longitude))
|
||||
graha = graha_strengths(longitudes)
|
||||
rashi = rashi_strengths(longitudes, graha)
|
||||
return {
|
||||
"sthira": _sthira(birth_day, lagna, longitudes, graha, rashi),
|
||||
"drig": _drig(birth_day, lagna, longitudes, graha),
|
||||
"shoola": _nine_year(
|
||||
birth_day,
|
||||
lagna,
|
||||
longitudes,
|
||||
rashi,
|
||||
"shoola_forward_md_direct_ad_v1",
|
||||
"Crux of Vedic Astrology,印页 395",
|
||||
"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",
|
||||
),
|
||||
"lagna_kendradi": _omitted(
|
||||
"",
|
||||
"",
|
||||
"",
|
||||
"本轮不上表:没有选定的安全默认算法,故本报告不列出该大运。",
|
||||
"This report does not include Lagna Kendradi: no safe default method is selected.",
|
||||
),
|
||||
"chara": _chara(birth_day, lagna, longitudes),
|
||||
}
|
||||
@@ -0,0 +1,196 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Freeze PyJHora 4.8.7 rashi-dasha rows for four public or fictional charts.
|
||||
|
||||
Run with the Anaconda interpreter that has PyJHora 4.8.7. Product code does
|
||||
not import this module. Tests read the JSON only.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import importlib
|
||||
import importlib.metadata
|
||||
import io
|
||||
import json
|
||||
import sys
|
||||
from contextlib import redirect_stdout
|
||||
from pathlib import Path
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[2]
|
||||
sys.path[:0] = [str(ROOT), str(ROOT / "scripts")]
|
||||
|
||||
OUT = ROOT / "tests" / "fixtures" / "rashi_dasha_pyjhora_20261008.json"
|
||||
CASE_IDS = (
|
||||
"steve_jobs_1955_aa",
|
||||
"barack_obama_1961_aa",
|
||||
"einstein_1879_aa",
|
||||
"fictional_reader_main",
|
||||
)
|
||||
|
||||
|
||||
def _births() -> dict:
|
||||
public = json.loads((ROOT / "references" / "public_oracle_cases.json").read_text(encoding="utf-8"))
|
||||
births = {case["id"]: case["birth"] for case in public["cases"]}
|
||||
special = json.loads((ROOT / "tests" / "fixtures" / "special_lagnas_pyjhora_487.json").read_text(encoding="utf-8"))
|
||||
births["fictional_reader_main"] = special["cases"]["fictional_reader_main"]["birth"]
|
||||
return births
|
||||
|
||||
|
||||
def _parse_row(row):
|
||||
if not isinstance(row, (list, tuple)) or len(row) < 3:
|
||||
return None
|
||||
if (
|
||||
len(row) >= 4
|
||||
and isinstance(row[0], int)
|
||||
and isinstance(row[1], int)
|
||||
and not isinstance(row[2], (int, float))
|
||||
):
|
||||
signs = [int(row[0]), int(row[1])]
|
||||
start, years = row[2], row[3]
|
||||
else:
|
||||
lord, start, years = row[0], row[1], row[2]
|
||||
signs = [int(item) for item in lord] if isinstance(lord, (list, tuple)) else [int(lord)]
|
||||
if isinstance(start, (list, tuple)):
|
||||
start_iso = f"{int(start[0]):04d}-{int(start[1]):02d}-{int(start[2]):02d}"
|
||||
else:
|
||||
start_iso = str(start)[:10]
|
||||
years_n = float(years)
|
||||
years_out = int(round(years_n)) if abs(years_n - round(years_n)) < 1e-6 else years_n
|
||||
return signs, start_iso, years_out
|
||||
|
||||
|
||||
def _call(fn, args):
|
||||
with redirect_stdout(io.StringIO()):
|
||||
return fn(*args)
|
||||
|
||||
|
||||
def _pyjhora_case(birth: dict, modules, swe, drik, charts, Place) -> dict:
|
||||
local_hour = birth["hour"] + birth["minute"] / 60.0 + birth.get("second", 0) / 3600.0
|
||||
with redirect_stdout(io.StringIO()):
|
||||
drik.set_ayanamsa_mode("LAHIRI")
|
||||
jd = swe.julday(birth["year"], birth["month"], birth["day"], local_hour)
|
||||
place = Place("birth_place", birth["lat"], birth["lon"], birth["tz"])
|
||||
positions = charts.rasi_chart(jd, place)
|
||||
dob = (birth["year"], birth["month"], birth["day"])
|
||||
tob = (birth["hour"], birth["minute"], birth["second"])
|
||||
calls = {
|
||||
"sthira": lambda level: modules["sthira"].get_dhasa_antardhasa(dob, tob, place, dhasa_level_index=level),
|
||||
"shoola": lambda level: modules["shoola"].shoola_dhasa(positions, dob, tob, dhasa_level_index=level),
|
||||
"niryaana_shoola": lambda level: modules["nirayana"].nirayana_shoola_dhasa(positions, dob, tob, dhasa_level_index=level),
|
||||
"drig": lambda level: modules["drig"].drig_dhasa(positions, dob, tob, dhasa_level_index=level),
|
||||
"navamsha": lambda level: modules["navamsa"].get_dhasa_antardhasa(dob, tob, place, dhasa_level_index=level),
|
||||
"chara": lambda level: modules["chara"].get_dhasa_antardhasa(dob, tob, place, dhasa_level_index=level),
|
||||
"lagna_kendradi": lambda level: modules["lagna_kendraadhi"].lagna_kendradhi_rasi_dhasa(dob, tob, place, dhasa_level_index=level),
|
||||
}
|
||||
parsed = {}
|
||||
for key, call in calls.items():
|
||||
level_1 = [_parse_row(row) for row in _call(call, (1,))]
|
||||
level_2 = [_parse_row(row) for row in _call(call, (2,))]
|
||||
majors = [row for row in level_1 if row and len(row[0]) == 1]
|
||||
if not majors:
|
||||
majors = [row for row in level_2 if row and len(row[0]) == 1]
|
||||
subs = [row for row in level_2 if row and len(row[0]) == 2]
|
||||
first_sign = majors[0][0][0] if majors else None
|
||||
first_subs = [row[0][1] for row in subs if row[0][0] == first_sign]
|
||||
parsed[key] = {
|
||||
"sign_indexes": [row[0][0] for row in majors],
|
||||
"years": [row[2] for row in majors],
|
||||
"second_start": majors[1][1] if len(majors) > 1 else None,
|
||||
"first_sub_sign_indexes": first_subs,
|
||||
}
|
||||
return parsed
|
||||
|
||||
|
||||
def _succession(signs: list[int]) -> tuple[int, ...]:
|
||||
return tuple((signs[index] - signs[index - 1]) % 12 for index in range(1, len(signs)))
|
||||
|
||||
|
||||
def _diff_steps(section: dict, pyjhora: dict) -> list[str]:
|
||||
our_signs = [row["sign_index"] for row in section["periods"]]
|
||||
their_signs = pyjhora["sign_indexes"]
|
||||
our_years = [row["years"] for row in section["periods"]]
|
||||
their_years = pyjhora["years"]
|
||||
steps = []
|
||||
if not our_signs or not their_signs or our_signs[0] != their_signs[0]:
|
||||
steps.append("start_sign")
|
||||
if _succession(our_signs) != _succession(their_signs):
|
||||
steps.append("direction")
|
||||
if sorted(zip(our_signs, our_years)) != sorted(zip(their_signs, their_years)):
|
||||
steps.append("years")
|
||||
our_sub = [row["sign_index"] for row in (section["periods"][0].get("antardasha") or [])] if section["periods"] else []
|
||||
if our_sub != (pyjhora.get("first_sub_sign_indexes") or []):
|
||||
steps.append("subperiod")
|
||||
if our_signs == their_signs and our_years == their_years and len(section["periods"]) > 1:
|
||||
if section["periods"][1]["start"] != pyjhora.get("second_start"):
|
||||
steps.append("date_basis")
|
||||
return steps
|
||||
|
||||
|
||||
def main() -> None:
|
||||
with redirect_stdout(io.StringIO()):
|
||||
import swisseph as swe
|
||||
from jhora.horoscope.chart import charts
|
||||
from jhora.panchanga import drik
|
||||
from jhora.panchanga.drik import Place
|
||||
|
||||
modules = {
|
||||
name: importlib.import_module("jhora.horoscope.dhasa.raasi." + module_name)
|
||||
for name, module_name in (
|
||||
("sthira", "sthira"),
|
||||
("shoola", "shoola"),
|
||||
("nirayana", "nirayana"),
|
||||
("drig", "drig"),
|
||||
("navamsa", "navamsa"),
|
||||
("chara", "chara"),
|
||||
("lagna_kendraadhi", "lagna_kendraadhi"),
|
||||
)
|
||||
}
|
||||
from jyotish_engine import compute_chart_data
|
||||
from rashi_dashas_native import calculate_rashi_dashas
|
||||
|
||||
payload = {
|
||||
"pyjhora_version": importlib.metadata.version("PyJHora"),
|
||||
"generator_python_has_pyjhora": True,
|
||||
"generator": "scripts/research/freeze_rashi_dasha_pyjhora_fixture.py",
|
||||
"note": "Anaconda PyJHora 4.8.7 produced the external rows. Pytest reads this file and does not import PyJHora.",
|
||||
"cases": {},
|
||||
}
|
||||
births = _births()
|
||||
for case_id in CASE_IDS:
|
||||
birth = births[case_id]
|
||||
chart, _asc, _jd, _ayanamsa = compute_chart_data(
|
||||
birth["year"], birth["month"], birth["day"], birth["hour"], birth["minute"],
|
||||
birth["lat"], birth["lon"], birth["tz"], second=birth.get("second") or 0,
|
||||
)
|
||||
longitudes = {
|
||||
name: float(row["degree_raw"])
|
||||
for name, row in chart["planets"].items()
|
||||
if isinstance(row, dict) and "degree_raw" in row
|
||||
}
|
||||
native = calculate_rashi_dashas(chart["birth_info"]["date"], float(chart["ascendant"]["degree_raw"]), longitudes)
|
||||
external = _pyjhora_case(birth, modules, swe, drik, charts, Place)
|
||||
native_view = {}
|
||||
diff_steps = {}
|
||||
for key, section in native.items():
|
||||
if key == "lagna_kendradi":
|
||||
diff_steps[key] = ["start_sign", "direction", "years", "subperiod"]
|
||||
native_view[key] = {"status": "omitted", "sign_indexes": [], "years": []}
|
||||
continue
|
||||
native_view[key] = {
|
||||
"sign_indexes": [row["sign_index"] for row in section["periods"]],
|
||||
"years": [row["years"] for row in section["periods"]],
|
||||
}
|
||||
diff_steps[key] = _diff_steps(section, external[key])
|
||||
payload["cases"][case_id] = {
|
||||
"birth_source": birth,
|
||||
"pyjhora": external,
|
||||
"native": native_view,
|
||||
"diff_steps": diff_steps,
|
||||
}
|
||||
print(case_id, {key: value for key, value in diff_steps.items()}, flush=True)
|
||||
OUT.parent.mkdir(parents=True, exist_ok=True)
|
||||
OUT.write_text(json.dumps(payload, ensure_ascii=False, indent=2) + "\n", encoding="utf-8")
|
||||
print(OUT)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user