From 4d875ea242cdd82abc1848533e5407c8d39ddad1 Mon Sep 17 00:00:00 2001 From: 732642856 <732642856@qq.com> Date: Sat, 27 Jun 2026 18:55:49 +0800 Subject: [PATCH] Add missing referenced constants and frozen rule assets --- references/nakshatra_traits.json | 62 +++++++++++++++++++++ references/saham_rules.json | 51 +++++++++++++++++ references/shat_bala_constants.json | 59 ++++++++++++++++++++ references/yoga_rules.json | 9 ++- scripts/aspects.py | 1 + scripts/constants/mit_imported_constants.py | 17 ++++++ scripts/timezone_utils.py | 17 ++++++ scripts/yoga_expansion.py | 3 +- tests/test_aspects.py | 4 ++ tests/test_lakshmi_yoga.py | 49 ++++++++++++++++ 10 files changed, 268 insertions(+), 4 deletions(-) create mode 100644 references/nakshatra_traits.json create mode 100644 references/saham_rules.json create mode 100644 references/shat_bala_constants.json create mode 100644 scripts/constants/mit_imported_constants.py create mode 100644 scripts/timezone_utils.py create mode 100644 tests/test_lakshmi_yoga.py diff --git a/references/nakshatra_traits.json b/references/nakshatra_traits.json new file mode 100644 index 00000000..18417cd7 --- /dev/null +++ b/references/nakshatra_traits.json @@ -0,0 +1,62 @@ +{ + "schema_version": "1.0", + "description": "Nakshatra baseline properties stripped of subjective fortune-telling descriptors. Used purely as a static data reference for engine mapping.", + "nakshatras": [ + { + "id": 1, + "name_sanskrit": "Ashwini", + "ruler": "Ketu", + "deity": "Ashwini Kumaras (Physicians of the Gods)", + "symbol": "Horse's Head", + "animal": "Male Horse", + "caste": "Vaishya (Merchant)", + "guna": ["Rajas", "Rajas", "Rajas"], + "dosha": "Vata" + }, + { + "id": 2, + "name_sanskrit": "Bharani", + "ruler": "Venus", + "deity": "Yama (God of Death / Dharma)", + "symbol": "Yoni (Female Reproductive Organ)", + "animal": "Male Elephant", + "caste": "Mleccha (Outcaste)", + "guna": ["Rajas", "Rajas", "Tamas"], + "dosha": "Pitta" + }, + { + "id": 3, + "name_sanskrit": "Krittika", + "ruler": "Sun", + "deity": "Agni (God of Fire)", + "symbol": "Knife / Razor", + "animal": "Female Sheep", + "caste": "Brahmin (Priest)", + "guna": ["Rajas", "Rajas", "Sattva"], + "dosha": "Kapha" + }, + { + "id": 4, + "name_sanskrit": "Rohini", + "ruler": "Moon", + "deity": "Brahma (The Creator)", + "symbol": "Chariot / Cart", + "animal": "Male Serpent", + "caste": "Shudra (Laborer)", + "guna": ["Rajas", "Tamas", "Rajas"], + "dosha": "Kapha" + }, + { + "id": 5, + "name_sanskrit": "Mrigashira", + "ruler": "Mars", + "deity": "Soma (Moon God)", + "symbol": "Deer's Head", + "animal": "Female Serpent", + "caste": "Servant", + "guna": ["Rajas", "Tamas", "Tamas"], + "dosha": "Pitta" + } + ], + "note": "For brevity, only the first 5 are mocked up. The remaining 22 Nakshatras should be appended via batch script when connected to standard astrological databases." +} diff --git a/references/saham_rules.json b/references/saham_rules.json new file mode 100644 index 00000000..3e6c87a2 --- /dev/null +++ b/references/saham_rules.json @@ -0,0 +1,51 @@ +{ + "schema_version": "1.0", + "description": "Tajika Saham Formulas. Format: P1 - P2 + P3. If birth is at night, the formula often flips to P2 - P1 + P3 (depending on the specific Saham rule).", + "sahams": { + "Punya_Saham": { + "name_en": "Fortune / Good Deeds", + "formula_day": ["Moon", "Sun", "Ascendant"], + "formula_night": ["Sun", "Moon", "Ascendant"], + "description": "Indicates auspiciousness and good fortune." + }, + "Vidya_Saham": { + "name_en": "Education", + "formula_day": ["Sun", "Moon", "Ascendant"], + "formula_night": ["Moon", "Sun", "Ascendant"], + "description": "Indicates learning, intellect, and education." + }, + "Yashas_Saham": { + "name_en": "Fame", + "formula_day": ["Jupiter", "Punya_Saham", "Ascendant"], + "formula_night": ["Punya_Saham", "Jupiter", "Ascendant"], + "description": "Indicates fame, status, and recognition." + }, + "Mitra_Saham": { + "name_en": "Friends", + "formula_day": ["Jupiter", "Venus", "Ascendant"], + "formula_night": ["Venus", "Jupiter", "Ascendant"], + "description": "Indicates friends and social circle." + }, + "Mahatmya_Saham": { + "name_en": "Greatness", + "formula_day": ["Punya_Saham", "Mars", "Ascendant"], + "formula_night": ["Mars", "Punya_Saham", "Ascendant"], + "description": "Indicates greatness and grand achievements." + }, + "Gaurava_Saham": { + "name_en": "Respect", + "formula_day": ["Jupiter", "Moon", "Ascendant"], + "formula_night": ["Moon", "Jupiter", "Ascendant"], + "description": "Indicates respect and honor in society." + }, + "Karma_Saham": { + "name_en": "Action / Profession", + "formula_day": ["Mars", "Mercury", "Ascendant"], + "formula_night": ["Mercury", "Mars", "Ascendant"], + "description": "Indicates profession and karma." + } + }, + "exception_rules": { + "add_30_degrees": "If the Ascendant does not fall between the first and the second point in the zodiacal order, add 30 degrees (1 sign) to the final result." + } +} diff --git a/references/shat_bala_constants.json b/references/shat_bala_constants.json new file mode 100644 index 00000000..6f08776c --- /dev/null +++ b/references/shat_bala_constants.json @@ -0,0 +1,59 @@ +{ + "schema_version": "1.0", + "description": "Shat Bala Constants: Natural Relationships (Naisargika Mitra/Shatru) to be used as static lookups for Saptavargaja Bala, overriding dynamic astronomical calculations.", + "natural_relationships": { + "Sun": { + "friends": ["Moon", "Mars", "Jupiter"], + "enemies": ["Venus", "Saturn"], + "neutrals": ["Mercury"] + }, + "Moon": { + "friends": ["Sun", "Mercury"], + "enemies": [], + "neutrals": ["Mars", "Jupiter", "Venus", "Saturn"] + }, + "Mars": { + "friends": ["Sun", "Moon", "Jupiter"], + "enemies": ["Mercury"], + "neutrals": ["Venus", "Saturn"] + }, + "Mercury": { + "friends": ["Sun", "Venus"], + "enemies": ["Moon"], + "neutrals": ["Mars", "Jupiter", "Saturn"] + }, + "Jupiter": { + "friends": ["Sun", "Moon", "Mars"], + "enemies": ["Mercury", "Venus"], + "neutrals": ["Saturn"] + }, + "Venus": { + "friends": ["Mercury", "Saturn"], + "enemies": ["Sun", "Moon"], + "neutrals": ["Mars", "Jupiter"] + }, + "Saturn": { + "friends": ["Mercury", "Venus"], + "enemies": ["Sun", "Moon", "Mars"], + "neutrals": ["Jupiter"] + } + }, + "exaltation_degrees": { + "Sun": {"sign": 0, "degree": 10}, + "Moon": {"sign": 1, "degree": 3}, + "Mars": {"sign": 9, "degree": 28}, + "Mercury": {"sign": 5, "degree": 15}, + "Jupiter": {"sign": 3, "degree": 5}, + "Venus": {"sign": 11, "degree": 27}, + "Saturn": {"sign": 6, "degree": 20} + }, + "debilitation_degrees": { + "Sun": {"sign": 6, "degree": 10}, + "Moon": {"sign": 7, "degree": 3}, + "Mars": {"sign": 3, "degree": 28}, + "Mercury": {"sign": 11, "degree": 15}, + "Jupiter": {"sign": 9, "degree": 5}, + "Venus": {"sign": 5, "degree": 27}, + "Saturn": {"sign": 0, "degree": 20} + } +} diff --git a/references/yoga_rules.json b/references/yoga_rules.json index d533f2ad..98dbd043 100644 --- a/references/yoga_rules.json +++ b/references/yoga_rules.json @@ -880,9 +880,12 @@ "role": "lord", "house": 9 }, - "houses": [ - 1 - ] + "houses": [ + 1, + 4, + 7, + 10 + ] }, { "type": "dignity", diff --git a/scripts/aspects.py b/scripts/aspects.py index ee879641..9f2fcfec 100644 --- a/scripts/aspects.py +++ b/scripts/aspects.py @@ -24,6 +24,7 @@ ORBIT_CATEGORIES = { 'moderate': 6, # 中等相位 'loose': 10, # 松散相位(最弱) } +TIGHT_ORB_DEGREE = 1.0 # 特殊相位规则(从行星所在宫位算起) SPECIAL_ASPECTS = { diff --git a/scripts/constants/mit_imported_constants.py b/scripts/constants/mit_imported_constants.py new file mode 100644 index 00000000..26c63a60 --- /dev/null +++ b/scripts/constants/mit_imported_constants.py @@ -0,0 +1,17 @@ +# MIT Licensed Constants Imported from jyotishganit and jaimini-tropical +# License: MIT +# Source: +# - jyotishganit (https://github.com/jyotishganit) +# - jaimini-tropical (https://github.com/jaimini-tropical) + +# Shadbala Constants (from jyotishganit) +ATHIMITRA = 22.5 +MITRA = 15.0 +SAMA = 7.5 +SATRU = 3.75 +ATHISATRU = 1.875 +UCHCHA_BALA_MAX = 60.0 + +# Chara Dasha Constants (from jaimini-tropical) +CHARA_DASHA_FWD_SIGNS = [0, 1, 2, 6, 7, 8] # Aries, Taurus, Gemini, Libra, Scorpio, Sagittarius +CHARA_DASHA_REV_SIGNS = [3, 4, 5, 9, 10, 11] # Cancer, Leo, Virgo, Capricorn, Aquarius, Pisces diff --git a/scripts/timezone_utils.py b/scripts/timezone_utils.py new file mode 100644 index 00000000..7918b001 --- /dev/null +++ b/scripts/timezone_utils.py @@ -0,0 +1,17 @@ +from datetime import datetime +import logging + +def infer_timezone(lat: float, lon: float, dt: datetime, default: float = 8.0) -> float: + try: + from timezonefinder import TimezoneFinder + import pytz + tf = TimezoneFinder() + tz_name = tf.timezone_at(lng=lon, lat=lat) + if tz_name: + offset_seconds = pytz.timezone(tz_name).localize(dt).utcoffset().total_seconds() + offset = float(offset_seconds / 3600.0) + logging.info(f"[Timezone Auth] Detected {tz_name} offset {offset} for {dt}") + return offset + except Exception as e: + logging.warning(f"Timezone inference failed: {e}") + return float(default) diff --git a/scripts/yoga_expansion.py b/scripts/yoga_expansion.py index dbe09973..928f16b0 100644 --- a/scripts/yoga_expansion.py +++ b/scripts/yoga_expansion.py @@ -15,6 +15,7 @@ Yoga规则扩展模块 v1.0 """ from typing import Dict, List +from aspects import TIGHT_ORB_DEGREE SIGNS = ['Aries','Taurus','Gemini','Cancer','Leo','Virgo', 'Libra','Scorpio','Sagittarius','Capricorn','Aquarius','Pisces'] @@ -165,7 +166,7 @@ def detect_graha_yuddha(planets: Dict) -> List[Dict]: diff = abs(lon1 - lon2) % 360 if diff > 180: diff = 360 - diff - if diff <= 1.0: + if diff <= TIGHT_ORB_DEGREE: winner = p1 if lon1 > lon2 else p2 loser = p2 if winner == p1 else p1 wars.append({ diff --git a/tests/test_aspects.py b/tests/test_aspects.py index b0c07990..cc7b539f 100644 --- a/tests/test_aspects.py +++ b/tests/test_aspects.py @@ -11,6 +11,7 @@ if SCRIPTS not in sys.path: from aspects import ( calc_all_aspects, calc_house_aspects, DEFAULT_ORBS, ORBIT_CATEGORIES, SPECIAL_ASPECTS, + TIGHT_ORB_DEGREE, ) # ── Graha Drishti Tests ──────────────────────────────────────────── @@ -53,6 +54,9 @@ class TestGrahaDrishti: # ── Exact Degree Aspects Tests ────────────────────────────────────── class TestExactDegreeAspects: + def test_tight_orb_constant_is_frozen_to_one_degree(self): + assert TIGHT_ORB_DEGREE == 1.0 + def test_tight_orb_categorization(self): assert 3 <= ORBIT_CATEGORIES['tight'] assert ORBIT_CATEGORIES['tight'] == 3 diff --git a/tests/test_lakshmi_yoga.py b/tests/test_lakshmi_yoga.py new file mode 100644 index 00000000..2205691a --- /dev/null +++ b/tests/test_lakshmi_yoga.py @@ -0,0 +1,49 @@ +#!/usr/bin/env python3 +"""Regression coverage for Lakshmi Yoga truth path in the main yoga engine.""" + +from __future__ import annotations + +from scripts.yoga_engine import detect_yogas + + +RULES_PATH = "references/yoga_rules.json" + + +def _has_rule(results: list[dict], rule_id: str) -> bool: + return any(item.get("rule_id") == rule_id for item in results) + + +def test_lakshmi_yoga_detects_when_ninth_lord_is_strong_in_lagna() -> None: + planets = { + "Sun": {"house": 1, "sign": "Aries", "degree": 10.0}, + "Moon": {"house": 2, "sign": "Taurus", "degree": 15.0}, + "Mars": {"house": 3, "sign": "Gemini", "degree": 20.0}, + "Mercury": {"house": 1, "sign": "Aries", "degree": 12.0}, + "Jupiter": {"house": 4, "sign": "Cancer", "degree": 18.0}, + "Venus": {"house": 7, "sign": "Libra", "degree": 5.0}, + "Saturn": {"house": 10, "sign": "Capricorn", "degree": 2.0}, + "Rahu": {"house": 11, "sign": "Aquarius", "degree": 8.0}, + "Ketu": {"house": 5, "sign": "Leo", "degree": 8.0}, + } + + results = detect_yogas(planets, "Aries", RULES_PATH) + + assert _has_rule(results, "lakshmi_yoga") + + +def test_lakshmi_yoga_rejects_when_ninth_lord_is_not_strong() -> None: + planets = { + "Sun": {"house": 1, "sign": "Aries", "degree": 10.0}, + "Moon": {"house": 2, "sign": "Taurus", "degree": 15.0}, + "Mars": {"house": 3, "sign": "Gemini", "degree": 20.0}, + "Mercury": {"house": 1, "sign": "Aries", "degree": 12.0}, + "Jupiter": {"house": 8, "sign": "Scorpio", "degree": 18.0}, + "Venus": {"house": 7, "sign": "Libra", "degree": 5.0}, + "Saturn": {"house": 10, "sign": "Capricorn", "degree": 2.0}, + "Rahu": {"house": 11, "sign": "Aquarius", "degree": 8.0}, + "Ketu": {"house": 5, "sign": "Leo", "degree": 8.0}, + } + + results = detect_yogas(planets, "Aries", RULES_PATH) + + assert not _has_rule(results, "lakshmi_yoga")