OUTDATED — historical implementation blueprint (6 October 2026). Preserve these source leads and design notes for provenance. Current remaining work is tracked in the Vedic remaining work register; the original absence claims below are not the current codebase state.
STATUS (2026-07-08): PHASE 1 COMPLETE. All seven systems below are implemented, constitutionalized, and served: varga (17 divisions), vedic_dignities, jaimini (karakas), panchanga, dasha_systems (Ashtottari + Yogini), ashtakavarga (incl. both Shodhanas), and shadbala — including Bhava Bala and Ishta/Kashta Phala, which §System 7 below had explicitly scoped out and which were closed on 2026-07-08. The “Codebase State” section below is the historical pre-Phase-1 snapshot, preserved for provenance. For remaining gaps, see Vedic remaining work register.
Present and usable:
moira/sidereal.py — tropical_to_sidereal, nakshatra_of, Ayanamsa, NAKSHATRA_LORDSmoira/varga.py — calculate_varga, wrappers for D7, D9, D10, D12, D30 onlymoira/dasha.py — Vimshottari only; no alternative systemsmoira/decanates.py — D3 via vedic_drekkana()moira/dignities.py — Western only; nothing Vedicmoira/cycles.py — _jd_to_weekday(), planetary_day_ruler() availablemoira/chart.py — ChartContext.planets, .nodes, .houses, .jd_ut, .is_dayNone of the 7 target systems exist in any form.
1. Shodashvarga Completion (varga.py extension) — no upstream deps
2. Vedic Planetary Dignities (new module) — no upstream deps
3. Jaimini Karakas (new module) — sidereal.py only
4. Panchanga (new module) — sidereal.py, cycles.py
5. Alternative Dasha Systems (new module) — sidereal.py, dasha.py patterns
6. Ashtakavarga (new module) — vedic_dignities.py (for Shodhana)
7. Shadbala (new module) — all of the above
Rationale: Shadbala is the most downstream — Saptavargaja Bala requires all 7 varga sign positions, Kala Bala requires Panchanga elements (Vara, Paksha, Hora), and Drig Bala optionally uses Ashtakavarga scores. Everything else is self-contained.
Parashara, Brihat Parashara Hora Shastra, Shodashavarga Adhyaya (Ch. 6–22).
Jhora (Jagannatha Hora), Parashara Light 9.x; B.V. Raman, How to Judge a Horoscope.
Extend moira/varga.py.
D2, D4, D6, D8, D16, D20, D24, D27, D40, D45, D60.
| Varga | Rule |
|---|---|
| D2 Hora | Odd signs: 1st half (0°–15°) → Leo, 2nd half → Cancer. Even signs reversed. |
| D4 Chaturthamsha | d4_sign = (sign_idx + segment_in_sign) % 12 — starts from own sign, not Aries. |
| D27 Saptavimshamsha | Start from Aries (fire), Cancer (earth), Libra (air), Capricorn (water). |
| D40 Khavedamsha | Odd signs start Aries; even signs start Libra. |
| D45 Akshavedamsha | Odd signs start Aries; even signs start Capricorn. |
Remaining (D6, D8, D16, D20, D24, D60): generic calculate_varga(lon, n).
Critical: The existing
calculate_varga(lon, 4)does not match Parashari D4. Thechaturthamsha()wrapper must use the sign-offset formula, not the generic call.
# D2 Hora
sign_idx = int(sidereal_lon // 30)
deg_in_sign = sidereal_lon % 30
half = 0 if deg_in_sign < 15 else 1
is_odd_sign = (sign_idx % 2 == 0) # 0-based: Aries=0 is 1st (odd) sign
if is_odd_sign:
hora_sign = 4 if half == 0 else 3 # Leo(4) or Cancer(3)
else:
hora_sign = 3 if half == 0 else 4 # Cancer(3) or Leo(4)
# D4 Chaturthamsha
segment_within_sign = int((sidereal_lon % 30) / 7.5) # 0–3
d4_sign = (sign_idx + segment_within_sign) % 12
# D27 Saptavimshamsha
TRIPLICITY_START = {"fire": 0, "earth": 3, "air": 6, "water": 9}
SIGN_ELEMENT = ["fire","earth","air","water"] * 3 # Aries→Pisces
start = TRIPLICITY_START[SIGN_ELEMENT[sign_idx]]
segment = int((sidereal_lon % 30) / (30/27))
d27_sign = (start + segment) % 12
# D40 Khavedamsha
seg_40 = int((sidereal_lon % 30) / 0.75)
start_40 = 0 if (sign_idx % 2 == 0) else 6 # Aries or Libra
d40_sign = (start_40 + seg_40) % 12
# D45 Akshavedamsha
seg_45 = int((sidereal_lon % 30) / (30/45))
start_45 = 0 if (sign_idx % 2 == 0) else 9 # Aries or Capricorn
d45_sign = (start_45 + seg_45) % 12
def hora(sidereal_longitude: float) -> VargaPoint: ... # D2
def chaturthamsha(sidereal_longitude: float) -> VargaPoint: ... # D4
def shashthamsha(sidereal_longitude: float) -> VargaPoint: ... # D6
def ashtamsha(sidereal_longitude: float) -> VargaPoint: ... # D8
def shodashamsha(sidereal_longitude: float) -> VargaPoint: ... # D16
def vimshamsha(sidereal_longitude: float) -> VargaPoint: ... # D20
def chaturvimshamsha(sidereal_longitude: float) -> VargaPoint: ... # D24
def saptavimshamsha(sidereal_longitude: float) -> VargaPoint: ... # D27
def khavedamsha(sidereal_longitude: float) -> VargaPoint: ... # D40
def akshavedamsha(sidereal_longitude: float) -> VargaPoint: ... # D45
def shashtiamsha(sidereal_longitude: float) -> VargaPoint: ... # D60
sign_idx % 2 == 0 means “odd sign” in 0-based indexing (Aries=0 is the 1st, which is odd). This is counterintuitive — document clearly.int(lon % 30 // (30/n)) not rounding at segment boundaries.Moderate. Most wrappers are trivial; D2/D4/D27/D40/D45 require careful offset arithmetic and Jhora spot-checks.
Parashara, BPHS Ch. 3, 26, 28. Kalyana Varma, Saravali Ch. 5–6.
Jhora (dignity display + Panchadha Maitri grid); Raman, A Manual of Hindu Astrology.
moira/vedic_dignities.py
| Planet | Exaltation | Deepest degree | Debilitation |
|---|---|---|---|
| Sun | Aries | 10° | Libra |
| Moon | Taurus | 3° | Scorpio |
| Mars | Capricorn | 28° | Cancer |
| Mercury | Virgo | 15° | Pisces |
| Jupiter | Cancer | 5° | Capricorn |
| Venus | Pisces | 27° | Virgo |
| Saturn | Libra | 20° | Aries |
| Planet | Sign | Range |
|---|---|---|
| Sun | Leo | 0°–20° |
| Moon | Taurus | 3°–30° |
| Mars | Aries | 0°–12° |
| Mercury | Virgo | 15°–20° |
| Jupiter | Sagittarius | 0°–10° |
| Venus | Libra | 0°–15° |
| Saturn | Aquarius | 0°–20° |
Exaltation > Mulatrikona > Swakshetra > Friend’s sign > Neutral’s sign > Enemy’s sign > Debilitation
| Planet | Friends | Neutral | Enemies |
|---|---|---|---|
| Sun | Moon, Mars, Jupiter | Mercury | Venus, Saturn |
| Moon | Sun, Mercury | Mars, Jupiter, Venus, Saturn | — |
| Mars | Sun, Moon, Jupiter | Venus, Saturn | Mercury |
| Mercury | Sun, Venus | Mars, Jupiter, Saturn | Moon |
| Jupiter | Sun, Moon, Mars | Saturn | Mercury, Venus |
| Venus | Mercury, Saturn | Mars, Jupiter, Moon | Sun |
| Saturn | Mercury, Venus | Jupiter | Sun, Moon, Mars |
distance = (sign_B - sign_A) % 12
| Natural + Temporary | Compound |
|---|---|
| Friend + Friend | Adhi Mitra (Great Friend) |
| Friend + Enemy | Sama (Neutral) |
| Neutral + Friend | Mitra (Friend) |
| Neutral + Enemy | Shatru (Enemy) |
| Enemy + Friend | Sama (Neutral) |
| Enemy + Enemy | Adhi Shatru (Great Enemy) |
EXALTATION_SIGN: dict[str, int]
EXALTATION_DEGREE: dict[str, float]
DEBILITATION_SIGN: dict[str, int]
MULATRIKONA_SIGN: dict[str, int]
MULATRIKONA_START: dict[str, float]
MULATRIKONA_END: dict[str, float]
OWN_SIGNS: dict[str, list[int]]
NATURAL_FRIENDS: dict[str, set[str]]
NATURAL_NEUTRALS: dict[str, set[str]]
NATURAL_ENEMIES: dict[str, set[str]]
class VedicDignityRank:
EXALTATION = "exaltation"
MULATRIKONA = "mulatrikona"
OWN_SIGN = "own_sign"
FRIEND_SIGN = "friend_sign"
NEUTRAL_SIGN = "neutral_sign"
ENEMY_SIGN = "enemy_sign"
DEBILITATION = "debilitation"
class CompoundRelationship:
GREAT_FRIEND = "adhi_mitra"
FRIEND = "mitra"
NEUTRAL = "sama"
ENEMY = "shatru"
GREAT_ENEMY = "adhi_shatru"
@dataclass(frozen=True, slots=True)
class VedicDignityResult:
planet: str
sidereal_longitude: float
sign_index: int
sign: str
dignity_rank: str
is_exalted: bool
is_debilitated: bool
is_mulatrikona: bool
is_own_sign: bool
exaltation_score: float # 0.0 at debilitation, 1.0 at deepest exaltation
@dataclass(frozen=True, slots=True)
class PlanetaryRelationship:
from_planet: str
to_planet: str
natural: str # "friend" / "neutral" / "enemy"
temporary: str # "friend" / "enemy"
compound: str # CompoundRelationship constant
def vedic_dignity(
planet: str,
sidereal_longitude: float,
) -> VedicDignityResult: ...
def planetary_relationships(
sidereal_longitudes: dict[str, float],
) -> list[PlanetaryRelationship]: ...
Moderate.
Jaimini, Jaimini Sutras, Adhyaya 1, Pada 1, Sutras 1–14.
Jhora (7/8-karaka mode); Sanjay Rath, Jaimini Maharishi’s Upadesa Sutras (2002).
moira/jaimini.py
# 7-karaka scheme
planets_7 = ["Sun", "Moon", "Mars", "Mercury", "Jupiter", "Venus", "Saturn"]
degrees = {p: sidereal_lon[p] % 30.0 for p in planets_7}
sorted_planets = sorted(planets_7, key=lambda p: degrees[p], reverse=True)
# sorted_planets[0] = Atmakaraka, [1] = Amatyakaraka, ..., [6] = Darakaraka
Rahu (8-karaka scheme only): degree_in_sign = 30 - (rahu_sidereal_lon % 30) — inverted because Rahu moves retrograde.
| Rank | 7-karaka | 8-karaka |
|---|---|---|
| 1 | Atmakaraka (AK) | Atmakaraka (AK) |
| 2 | Amatyakaraka (AmK) | Amatyakaraka (AmK) |
| 3 | Bhratrikaraka (BK) | Bhratrikaraka (BK) |
| 4 | Matrikaraka (MaK) | Matrikaraka (MaK) |
| 5 | Pitrikaraka (PiK) | Pitrikaraka (PiK) |
| 6 | Gnatikaraka (GK) | Putrakaraka (PuK) |
| 7 | Darakaraka (DK) | Gnatikaraka (GK) |
| 8 | — | Darakaraka (DK) |
KARAKA_NAMES_7: list[str]
KARAKA_NAMES_8: list[str]
@dataclass(frozen=True, slots=True)
class KarakaAssignment:
karaka_name: str
karaka_rank: int # 1-based
planet: str
degree_in_sign: float # effective degree used for sorting
sidereal_longitude: float
is_rahu_inverted: bool
@dataclass(frozen=True, slots=True)
class JaiminiKarakaResult:
assignments: list[KarakaAssignment]
scheme: int # 7 or 8
atmakaraka: str # convenience accessor
tie_warnings: list[tuple[str, str]] # planet pairs with equal degrees
def jaimini_karakas(
sidereal_longitudes: dict[str, float],
scheme: int = 7,
) -> JaiminiKarakaResult: ...
def atmakaraka(
sidereal_longitudes: dict[str, float],
scheme: int = 7,
) -> str: ...
tie_warning and apply a deterministic tiebreaker (document as indeterminate).% 30 arithmetic handles it correctly.Simple. Core is a single sort with one special case (Rahu inversion).
Parashara, BPHS (Muhurta chapters). Varahamihira, Brihat Samhita Ch. 98–104.
Jhora, Kala software.
moira/panchanga.py
| Element | Formula | Span |
|---|---|---|
| Tithi | int((moon_sid - sun_sid) % 360 / 12) + 1 |
12° each, 30 total |
| Vara | int(jd + 1.5) % 7 → Sun, Moon, Mars, Mercury, Jupiter, Venus, Saturn |
1 day |
| Nakshatra | delegate to sidereal.nakshatra_of(moon_tropical_lon, jd) |
13.33° each |
| Yoga | int((sun_sid + moon_sid) % 360 / (360/27)) + 1 |
13.33° each, 27 total |
| Karana | half-Tithi (int(diff / 6)); 60 per month |
6° each |
Karana mapping: position 0 = Kimstughna (fixed); positions 1–56 cycle 7 movable Karanas; positions 57–59 = Shakuni, Chatushpada, Naga (fixed).
Each element should return degrees_elapsed and degrees_remaining to enable muhurta “time until next X” calculations.
TITHI_NAMES: list[str] # 30 names: Pratipada … Amavasya
YOGA_NAMES: list[str] # 27 names: Vishkumbha … Vaidhriti
KARANA_NAMES: list[str] # 11 names
VARA_LORDS: list[str] # 7 lords, Sunday-origin
@dataclass(frozen=True, slots=True)
class PanchangaElement:
name: str
index: int # 0-based
number: int # 1-based
degrees_elapsed: float
degrees_remaining: float
@dataclass(frozen=True, slots=True)
class PanchangaResult:
jd: float
tithi: PanchangaElement
vara: PanchangaElement
vara_lord: str
nakshatra: NakshatraPosition # from sidereal.nakshatra_of
yoga: PanchangaElement
karana: PanchangaElement
ayanamsa_system: str
def panchanga_at(
sun_tropical_lon: float,
moon_tropical_lon: float,
jd: float,
ayanamsa_system: str = Ayanamsa.LAHIRI,
) -> PanchangaResult: ...
Moderate. Arithmetic is simple; Karana fixed/movable boundary requires careful table construction and validation.
Jhora, Kala software. Kalachakra: Jhora is the primary cross-check.
moira/dasha_systems.py
Sun:6, Moon:15, Mars:8, Mercury:17, Saturn:10, Jupiter:19, Rahu:12, Venus:21
Starting lord and balance use alternating four- and three-nakshatra groups beginning at Ardra. The 28-place count includes Abhijit; the exact Abhijit arc is an explicitly separate traditional boundary convention, not wording found in BPHS 46.17–22. The complete applicability statements require more than an Ascendant sign alone, so incomplete policy input must not be treated as proof.
Mangala (Moon):1, Pingala (Sun):2, Dhanya (Jupiter):3, Bhramari (Mars):4,
Bhadrika (Mercury):5, Ulka (Saturn):6, Siddha (Venus):7, Sankata (Rahu):8
Starting Yogini: add three to the one-based birth-nakshatra number and reduce
to a one-through-eight remainder; equivalently, with a zero-based index,
(nakshatra_index + 3) % 8. No eligibility condition. Sub-periods remain
proportional in the current implementation.
Navamsha-based, Savya/Apasavya traversal direction, variable cycle length. Defer to Phase 2 within this module.
@dataclass(frozen=True, slots=True)
class AlternateDashaPeriod:
system: str # "ashtottari" | "yogini" | "kalachakra"
level: int
lord: str
start_jd: float
end_jd: float
sub: list["AlternateDashaPeriod"]
@dataclass(frozen=True, slots=True)
class AshtottariPolicy:
year_basis: str = "julian_365.25"
ayanamsa_system: str = Ayanamsa.LAHIRI
bypass_eligibility: bool = False
lagna_sign_index: int | None = None # required if bypass_eligibility=False
@dataclass(frozen=True, slots=True)
class YoginiPolicy:
year_basis: str = "julian_365.25"
ayanamsa_system: str = Ayanamsa.LAHIRI
def ashtottari(
moon_tropical_lon: float,
natal_jd: float,
levels: int = 2,
policy: AshtottariPolicy | None = None,
) -> list[AlternateDashaPeriod]: ...
def yogini_dasha(
moon_tropical_lon: float,
natal_jd: float,
levels: int = 2,
policy: YoginiPolicy | None = None,
) -> list[AlternateDashaPeriod]: ...
Parashara, BPHS Ashtakavarga Adhyaya. Vaidyanatha Dikshita, Jataka Parijata (14th c.).
B.V. Raman, Ashtakavarga System of Prediction (1981) — the canonical English-language reference with verified tables. Commit to Raman as the authoritative table source; document it explicitly.
Jhora (Bhinnashtakavarga display), Kala software.
moira/ashtakavarga.py
for planet_P in seven_planets:
bhinna_AV[planet_P] = [0] * 12
for sign_i in range(12):
for ref in [*seven_planets, "Lagna"]:
distance = (sign_i - sign_index[ref]) % 12 + 1 # 1–12
if distance in REKHA_TABLES[planet_P][ref]:
bhinna_AV[planet_P][sign_i] += 1
sarva[sign_i] = sum(bhinna_AV[P][sign_i] for P in seven_planets)
The REKHA_TABLES are 7 × 8 fixed lookup tables — 672 individual rekha/no-rekha values that must be encoded verbatim from Raman’s reference and verified sign-by-sign against Jhora.
Phase 1: Unreduced Bhinnashtakavarga + Sarvashtakavarga.
Phase 2: Trikona Shodhana and Ekadhipatya Shodhana (reductions).
REKHA_TABLES: dict[str, dict[str, frozenset[int]]] # 7 planets × 8 references
@dataclass(frozen=True, slots=True)
class BhinnashtakavargaResult:
planet: str
rekhas: tuple[int, ...] # 12 counts, 0=Aries…11=Pisces
total_rekhas: int # sum (always 0–56)
@dataclass(frozen=True, slots=True)
class AshtakavargaResult:
jd: float
ayanamsa_system: str
bhinnashtakavarga: dict[str, BhinnashtakavargaResult]
sarvashtakavarga: tuple[int, ...] # 12 aggregate counts
def bhinnashtakavarga(
planet: str,
sign_indices: dict[str, int], # planet/lagna → sign index (0–11)
) -> BhinnashtakavargaResult: ...
def ashtakavarga(
sidereal_longitudes: dict[str, float], # includes "Lagna" key
) -> AshtakavargaResult: ...
def transit_strength(
planet: str,
transit_sign_index: int,
bhinna: BhinnashtakavargaResult,
) -> int: ... # 0–8
Moderate. Algorithm is simple; table transcription is the primary failure mode.
Parashara, BPHS Shadbala Adhyaya.
B.V. Raman, Graha and Bhava Balas (1959) — fully worked example chart verifiable to 2 decimal places in Rupas. Use this as the primary test fixture.
Jhora full Shadbala display.
moira/shadbala.py
Shashtiamsas (Sha). 60 Sha = 1 Rupa.
(a) Uchcha Bala
dist = abs(sidereal_lon - exaltation_lon)
if dist > 180: dist = 360 - dist
uchcha_bala = (180 - dist) / 3.0 # Sha, max 60
(b) Saptavargaja Bala — dignity in each of D1, D2, D3, D7, D9, D12, D30
| Dignity in varga | Shashtiamsas |
|---|---|
| Exaltation | 20 |
| Own sign | 15 |
| Adhi Mitra (great friend) | 10 |
| Mitra (friend) | 7.5 |
| Sama (neutral) | 5 |
| Shatru (enemy) | 2.5 |
| Adhi Shatru (great enemy) | 1.25 |
| Debilitation | 0 |
Sum across 7 vargas. Max = 140 Sha.
(c) Ojayugmarasyamsa Bala — odd/even sign strength
Sun, Mars, Jupiter, Saturn: 15 Sha in odd D1/D9 signs. Moon, Venus: 15 Sha in even signs.
(d) Kendradi Bala — angular house
Kendra (1,4,7,10): 60 Sha. Panapara (2,5,8,11): 30 Sha. Apoklima (3,6,9,12): 15 Sha.
(e) Drekkana Bala
Male planets (Sun, Mars, Jupiter) in 1st drekkana: 15 Sha. Hermaphrodite (Mercury, Saturn) in 2nd: 15 Sha. Female (Moon, Venus) in 3rd: 15 Sha.
Strong direction by planet:
Sun, Mars: 10th house cusp
Jupiter, Mercury: 1st house cusp (Ascendant)
Moon, Venus: 4th house cusp
Saturn: 7th house cusp
dist = abs(sidereal_lon - strong_cusp_lon)
if dist > 180: dist = 360 - dist
dig_bala = (180 - dist) / 3.0 # Sha, max 60
| Sub-component | Key |
|---|---|
| Nathonnatha | Day planets (Sun, Jupiter, Venus) max at noon; night planets (Moon, Mars, Saturn) max at midnight; Mercury always strong. |
| Paksha | Tithi 1–15: shukla = tithi. Tithi 16–30: shukla = 30 - tithi. Benefics: shukla * 4 Sha. Malefics: (15 - shukla) * 4 Sha. |
| Tribhaga | Jupiter: 1st third of day; Sun: 2nd; Saturn: 3rd. Moon: 1st third of night; Venus: 2nd; Mars: 3rd. Mercury: always. Value: 60 Sha in strong period, 0 otherwise. |
| Abda/Masa/Vara/Hora | Lord of current solar year, month, weekday, hour each receive 15 Sha. |
| Ayana | ayana_bala = 24 * sin(Sun_declination). sin(dec) = sin(obliquity) × sin(sun_lon). |
| Yuddha | Planets within 1° in same sign: victor (smaller longitude) gains loser’s Yuddha Bala. |
Speed-based score vs mean speed. Retrograde = maximum (60 Sha). Sun and Moon: distance from apogee/perigee.
| Planet | Sha |
|---|---|
| Sun | 60.00 |
| Moon | 51.43 |
| Venus | 42.85 |
| Jupiter | 34.28 |
| Mercury | 25.70 |
| Mars | 17.14 |
| Saturn | 8.57 |
Sum weighted benefic aspects received; subtract weighted malefic aspects. Vedic aspect weights: full (7th sign) = 1.0; three-quarter (Mars: 4th/8th; Jupiter: 5th/9th; Saturn: 3rd/10th) = 0.75; half (5th/9th regular) = 0.5; quarter (3rd/10th regular) = 0.25.
| Planet | Min Rupas |
|---|---|
| Sun | 6.5 |
| Moon | 6.0 |
| Mars | 5.0 |
| Mercury | 7.0 |
| Jupiter | 6.5 |
| Venus | 5.5 |
| Saturn | 5.0 |
@dataclass(frozen=True, slots=True)
class SthanaBala:
uchcha: float; saptavargaja: float; ojayugma: float
kendradi: float; drekkana: float; total: float
@dataclass(frozen=True, slots=True)
class KalaBala:
nathonnatha: float; paksha: float; tribhaga: float
abda_masa_vara_hora: float; ayana: float; yuddha: float; total: float
@dataclass(frozen=True, slots=True)
class PlanetShadbala:
planet: str
sthana_bala: SthanaBala
dig_bala: float
kala_bala: KalaBala
chesta_bala: float
naisargika_bala: float
drig_bala: float
total_shashtiamsas: float
total_rupas: float
required_rupas: float
is_sufficient: bool
@dataclass(frozen=True, slots=True)
class ShadbalaResult:
jd: float
ayanamsa_system: str
planets: dict[str, PlanetShadbala] # 7 planets
def shadbala(
sidereal_longitudes: dict[str, float],
planet_speeds: dict[str, float],
houses: "HouseCusps",
jd: float,
latitude: float,
longitude: float,
ayanamsa_system: str = Ayanamsa.LAHIRI,
) -> ShadbalaResult: ...
# Individual sub-component functions for testing:
def sthana_bala(planet: str, sidereal_lon: float, ...) -> SthanaBala: ...
def dig_bala(planet: str, sidereal_lon: float, houses: "HouseCusps") -> float: ...
def kala_bala(planet: str, jd: float, latitude: float, longitude: float, ...) -> KalaBala: ...
def chesta_bala(planet: str, speed: float, mean_speed: float) -> float: ...
def drig_bala(planet: str, sidereal_longitudes: dict[str, float]) -> float: ...
moira.rise_set.sin(dec) = sin(obliquity) × sin(sun_lon). Use moira.obliquity.mean_obliquity(jd).moira.planets speed data or moira.nodes.Complex. Highest sub-component count and most input dependencies of all 7 systems. Implement each sub-component as an isolated function with its own test fixture from Raman’s Graha and Bhava Balas reference chart.
moira/sidereal.py — complete; provides tropical_to_sidereal, nakshatra_of, Ayanamsa
moira/varga.py — extend with 11 missing wrappers (D2/D4/D6/D8/D16/D20/D24/D27/D40/D45/D60)
moira/cycles.py — complete; weekday ruler available
moira/rise_set.py — complete; sunrise/sunset (required by Shadbala Kala Bala)
moira/panchanga.py ← sidereal.py, cycles.py
moira/vedic_dignities.py ← sidereal.py, constants.py
moira/jaimini.py ← sidereal.py
moira/dasha_systems.py ← sidereal.py (patterns from dasha.py)
moira/ashtakavarga.py ← vedic_dignities.py (needed for Shodhana; not for base)
moira/shadbala.py ← varga.py (all 7), vedic_dignities.py, panchanga.py,
ashtakavarga.py (optional Drig Bala variant),
rise_set.py, nodes.py (Moon apogee for Chesta Bala)
| System | Primary Canon | Engineering Reference |
|---|---|---|
| Shodashvarga | BPHS Ch. 6–22 | Jhora, Parashara Light 9.x |
| Vedic Dignities | BPHS Ch. 3, 26, 28; Saravali Ch. 5–6 | Raman, A Manual of Hindu Astrology |
| Jaimini Karakas | Jaimini Sutras, Adhyaya 1, Pada 1 | Sanjay Rath commentary; Jhora |
| Panchanga | BPHS Muhurta chapters; Brihat Samhita Ch. 98–104 | Jhora, Kala |
| Alt. Dashas | BPHS (Ashtottari/Kalachakra chapters); Rao, Yogini Dasha | Jhora, Kala |
| Ashtakavarga | BPHS Ashtakavarga Adhyaya; Jataka Parijata | Raman, Ashtakavarga System of Prediction (1981) |
| Shadbala | BPHS Shadbala Adhyaya | Raman, Graha and Bhava Balas (1959) |