HomeDocsAPI Reference

API Reference

The admitted Python import surface, public vessels, policy controls, and failure contracts for Moira 6.1.0.

Moira API Reference

Document revision: 2.1.0 Engine baseline: 6.1.0 Last verified: 2026-07-27 Coverage: 13 200 BC → 17 191 AD (JPL DE441) Import surface: import moira provides the curated stable root, while from moira.facade import ... exposes the complete admitted facade surface.

This is the Python engine/import reference. The HTTP transport surface is documented separately in wiki/02_services/REST_API_REFERENCE.md.

For the Hellenistic surface, use the generated capability matrix for current export-tier receipts and the source validation audit for evidence qualifications. Those artifacts distinguish admitted engine truth from research-only, closed-exclusion, out-of-contract, and non-Hellenistic branches.


Table of Contents

  1. Quick Start
  2. API Architecture — Four-Tier Entry Points
  3. Core Types
  4. Moira Facade
  5. Ephemeris & Positions
  6. Alternative Reference Frames
  7. Chart Structure
  8. Classical Techniques
  9. Timing Techniques
  10. Planetary Cycles Engine
  11. Huber Method
  12. Relational Techniques
  13. Geography
  14. Fixed Stars
  15. Eclipses & Phenomena
  16. Harmograms — Spectral Harmonic Analysis
  17. Constellation Oracle
  18. Calendar & Time
  19. Policy Objects
  20. moira.sky — Strict Astronomy API
  21. moira.vedic — Vedic Astrology Surface

Conventions

  • Sections labeled fields are intended to be exhaustive for the documented vessel unless explicitly marked otherwise.
  • Rows or examples that use ... are abbreviated for width only; they are shorthand, not alternate signatures.
  • When a section says summary, that label is intentional and means the section is highlighting the most important fields rather than restating every implementation detail inline.
  • Unless a section explicitly targets moira.essentials, moira.classical, moira.predictive, or a specialty submodule, direct symbol imports in this reference should be read as from moira.facade import ...; the top-level moira package does not re-export the full low-level surface.
  • This reference prioritizes callable surfaces, principal vessels, and major policy types. moira.facade.__all__ also includes many public truth/classification/profile dataclasses and enums that are not all restated inline section-by-section.

1. Quick Start

Installation & Kernel

Moira requires an installed JPL planetary kernel before kernel-dependent computations can run. Moira() auto-detects the first installed planetary kernel from the supported set de430.bsp, de440.bsp, de441.bsp, de432.bsp, de431.bsp, searched in this order of locations:

  1. ~/.moira/kernels/
  2. moira/kernels/ inside the installed package
  3. kernels/ in a development checkout

The package does not read a MOIRA_KERNEL_PATH environment variable. Large kernels such as de441.bsp still need to be present locally; use moira-download-kernels or Moira.download_missing_kernels() to install the missing files into the user kernel directory.

from moira.facade import Moira
from datetime import datetime, timezone

m = Moira()                       # auto-detects the first installed planetary kernel
# or
m = Moira(kernel_path="/data/de441.bsp")

First chart

from moira.facade import Moira, Body, HouseSystem
from datetime import datetime, timezone

m = Moira()
dt = datetime(1988, 4, 4, 14, 30, tzinfo=timezone.utc)

chart = m.chart(dt)
for name, planet in chart.planets.items():
    print(f"{name:10s}  {planet.longitude:.4f}°  speed {planet.speed:+.4f}°/day")

houses = m.houses(dt, latitude=51.5074, longitude=-0.1278, system=HouseSystem.PLACIDUS)
print(f"ASC {houses.asc:.3f}°  MC {houses.mc:.3f}°")

Aspects

aspects = m.aspects(chart)
for a in aspects:
    print(f"{a.body1} {a.aspect} {a.body2}  orb {a.orb:+.2f}°")

Transits to natal point

from moira.facade import jd_from_datetime, utc_to_ut1
from datetime import datetime, timezone

natal_sun = chart.planets["Sun"].longitude          # e.g. 14.7°
jd_start  = utc_to_ut1(jd_from_datetime(datetime(2024, 1, 1, tzinfo=timezone.utc)))
jd_end    = utc_to_ut1(jd_from_datetime(datetime(2025, 1, 1, tzinfo=timezone.utc)))

for event in m.transits(Body.JUPITER, natal_sun, jd_start, jd_end):
    print(event.jd_ut, event.relation.relation_kind)

2. API Architecture — Four-Tier Entry Points

Moira exposes its admitted public surface through four distinct import points. The tier modules are cumulative. The root package is intentionally curated, while moira.facade is the complete admitted facade surface. Deliberately module-direct research or validation products can remain outside these tiers.

moira.essentials   ←  Beginner surface: chart, houses, aspects, sidereal
       ↓
moira.classical    ←  Adds: dignities, lots, fixed stars, time lords,
                           profections, Vedic, midpoints, mansions
       ↓
moira.predictive   ←  Adds: transits, progressions, synastry, eclipses,
                           returns, stations, void-of-course, electional
       ↓
moira.facade      ←  Complete admitted facade surface

moira            ←  Curated stable root: Moira, core types, JD/sidereal helpers,
            selected visibility, harmogram, orbital, and policy surfaces

moira.essentials — Beginner surface

from moira.essentials import Moira, Chart, Body, HouseSystem
from moira.essentials import PlanetData, SkyPosition, CartesianPosition
from moira.essentials import NodeData, HouseCusps, AspectData
from moira.essentials import CalendarDateTime, DeltaTPolicy
from moira.essentials import julian_day, jd_from_datetime, datetime_from_jd
from moira.essentials import calendar_from_jd, calendar_datetime_from_jd
from moira.essentials import format_jd_utc, safe_datetime_from_jd, delta_t
from moira.essentials import calculate_houses, assign_house
from moira.essentials import find_aspects, AspectPolicy, DEFAULT_POLICY
from moira.essentials import Ayanamsa, ayanamsa, tropical_to_sidereal
from moira.essentials import sidereal_to_tropical, list_ayanamsa_systems
from moira.essentials import DeltaTBreakdown, delta_t_breakdown

Use this when you only need: natal chart positions, house cusps, basic aspects, and sidereal conversions. It is suitable for first-time users and lightweight integrations.

moira.classical — Traditional astrology surface

from moira.classical import *   # includes everything from essentials, plus:

Adds the full classical and traditional toolkit:

Added domainKey symbols
Houses (full)HouseSystemFamily, HouseSystemCuspBasis, classify_house_system, HousePlacement, HouseBoundaryProfile, HouseAngularity, compare_systems, compare_placements, distribute_points, Quadrant, quadrant_emphasis, DiurnalQuadrant, diurnal_emphasis
Aspects (full)AspectDefinition, ASPECT_TIERS, CANONICAL_ASPECTS, AspectDomain, AspectFamily, AspectTier, MotionState, AspectClassification, HellenisticSuperiorityTruth, hellenistic_superiority_truth, find_whole_sign_aspects, aspect_strength, aspect_motion_state, find_declination_aspects, declination_aspects_from_declinations, declination_aspect_motion_witness, find_patterns
Dignitiescalculate_dignities, calculate_receptions, EssentialDignityKind, PlanetaryDignity, DignityHorizonFrame, PlanetarySolarPhaseTruth, SolarProximityTruth, BesiegingTruth, planetary_solar_phase_truth, solar_proximity_truth, besieging_truth
Arabic Partscalculate_lots, evaluate_lots, ArabicPart, LotsEvaluation, LotNotEvaluable, ArabicPartsService, list_parts
Unified Hellenistic profilehellenistic_chart_profile, HellenisticChartProfile, HellenisticProfilePolicy, HellenisticProfileProvenance
Midpointscalculate_midpoints, Midpoint, MidpointsService, midpoint_tree, planetary_pictures
Antisciafind_antiscia, AntisciaAspect, antiscion, contra_antiscion
Fixed starsstar_at, all_stars_at, FixedStar, list_stars, find_stars, star_magnitude
Lunar mansionsmansion_of, all_mansions_at, MANSIONS
Profectionsannual_profection, monthly_profection, profection_schedule
Time lordsfirdaria, zodiacal_releasing, vimshottari
Vedic divisionalnavamsa, saptamsa, dashamansa, dwadashamsa, trimshamsa
Vedic dignitiesvedic_dignity, planetary_relationships, VedicDignityResult, VedicDignityPolicy
Panchangapanchanga_at, PanchangaResult, PanchangaPolicy, tithi_condition_profile
Jaiminijaimini_karakas, atmakaraka, JaiminiKarakaResult, JaiminiPolicy
Ashtakavargabhinnashtakavarga, ashtakavarga, AshtakavargaResult, AshtakavargaPolicy
Shadbalashadbala, hora_lord_at, ShadbalaResult, ShadbalaPolicy
Alternate dashasashtottari, yogini_dasha, AlternateDashaPeriod, AshtottariPolicy, YoginiPolicy
Planetary hoursplanetary_hours
Huberhouse_zones, age_point, chart_intensity_profile (also importable directly from moira.huber)

moira.predictive — Forecasting surface

from moira.predictive import *   # includes everything from classical, plus:

Adds the complete forecasting and relationship toolkit:

Added domainKey symbols
Transitsfind_transits, next_transit, find_ingresses, next_ingress, TransitEvent, IngressEvent, TransitSearchPolicy
Progressionssecondary_progression, solar_arc, solar_arc_right_ascension, naibod_longitude, tertiary_progression, tertiary_ii_progression, converse_* variants, minor_progression, daily_house_frame
Primary directionsspeculum, find_primary_arcs, SpeculumEntry, PrimaryArc
Synastrysynastry_aspects, house_overlay, mutual_house_overlays, composite_chart, davison_chart, CompositeChart, DavisonChart
EclipsesEclipseData, EclipseEvent, EclipseCalculator, LunarEclipseAnalysis
Returnssolar_return, lunar_return, planet_return
Stationsfind_stations, is_retrograde, retrograde_periods, StationEvent
Void of coursevoid_of_course_window, is_void_of_course, next_void_of_course, void_periods_in_range
Phenomenagreatest_elongation, perihelion, aphelion, next_moon_phase, moon_phases_in_range, next_conjunction

Note: next_solar_eclipse_at_location is available from moira.facade and moira.eclipse directly but is not re-exported through moira.predictive.

moira.facade and moira — Admitted facade vs curated root

import moira                 # curated stable root, includes Moira and core types
from moira.facade import *   # complete admitted facade surface

moira.facade adds every remaining admitted subsystem not exposed by the predictive tier. The top-level moira package does not mirror the entire facade export list; use it when you want the primary facade class plus the curated stable root, and use moira.facade when you want direct imports for the admitted low-level API.

Topography-conditioned lunar contact chronology is a deliberate exception: it remains available only from moira.lunar_occultation_contacts and moira.lunar_limb while its validation boundary matures. It is not a facade, package-root, or REST surface.

The complete admitted facade surface adds every remaining subsystem not exposed by the predictive tier:

  • Heliacal visibility (5-criterion model)
  • Parans and paran field analysis
  • Church of Light natal Astrodynes
  • AstroCartoGraphy, local space, geodetic charts
  • Galactic coordinates
  • Uranian / Hamburg School bodies
  • Occultations and close approaches
  • Harmograms (spectral harmonic analysis)
  • Solar System Barycenter chart
  • Planetocentric positions
  • Received-light (light-cone) positions
  • Variable and multiple star systems
  • Constellations oracle (48 IAU constellations)
  • Sothic cycle, Egyptian calendar
  • Longevity (hyleg/alcocoden)

moira.vedic — Vedic astrology surface

from moira.vedic import *   # includes everything from essentials, plus the full Vedic stack

A parallel surface for Vedic work. Inherits all of moira.essentials and adds:

Added domainKey symbols
Sidereal & NakshatrasUserDefinedAyanamsa, NakshatraPosition, nakshatra_of, all_nakshatras_at
Panchangapanchanga_at, sankranti_at, PanchangaResult, TithiPaksha, PanchangaPolicy
Pancha Pakshiavailable_pancha_pakshi_profiles, pancha_pakshi_uromarisi_constitution_status, pancha_pakshi_profile_info, pancha_pakshi_identity_from_initial_vowel, pancha_pakshi_schedule, pancha_pakshi_first_eat_bird_mapping, pancha_pakshi_astronomical_paksha_at, pancha_pakshi_nakshatra_bird_mapping, pancha_pakshi_natal_moon_identity_at, pancha_pakshi_padu_bird_mapping, pancha_pakshi_sookshma_temporal_selection, pancha_pakshi_schedule_sookshma_temporal_selection, pancha_pakshi_civil_time_sookshma_selection_at, pancha_pakshi_local_solar_context_at, pancha_pakshi_fixed_clock_materialization_at, pancha_pakshi_fixed_clock_current_cell_at, pancha_pakshi_solar_proportional_materialization_at, pancha_pakshi_solar_proportional_current_cell_at, pancha_pakshi_directed_relationship
Vedic dignitiesvedic_dignity, planetary_relationships, VedicDignityResult, DignityConditionProfile, ChartDignityProfile
Varga (divisional)navamsa, saptamsa, dashamansa, dwadashamsa, trimshamsa + 11 more vargas, VargaPoint
Vimshottari Dashavimshottari, current_dasha, dasha_balance, dasha_active_line, DashaPeriod, VimshottariComputationPolicy
Alternate dashasashtottari, yogini_dasha, AlternateDashaPeriod, AshtottariPolicy, YoginiPolicy
Jaimini karakasjaimini_karakas, atmakaraka, JaiminiKarakaResult, JaiminiPolicy
Ashtakavargabhinnashtakavarga, ashtakavarga, transit_strength, AshtakavargaResult, AshtakavargaPolicy
Shadbalashadbala, sthana_bala, dig_bala, kala_bala, ShadbalaResult, ShadbalaPolicy

moira.vedic does not include the Western classical surface (Arabic lots, Firdaria, Zodiacal Releasing, Huber). For that, use moira.classical. See Section 21 for the full export reference.

moira.sky — Strict astronomy API

moira.sky is a sovereign low-level astronomy surface that exposes Moira's computational substrate without astrological coupling. It is organized into ten submodules:

SubmodulePurpose
moira.sky.timeUT/TT/TDB conversions, ERA, GMST, GAST, LAST, ΔT decomposition
moira.sky.positionFive-stage astrometric correction pipeline (light-time → aberration → deflection → frame bias → topocentric)
moira.sky.framesICRF/ecliptic/equatorial/horizontal transforms, precession and nutation matrices
moira.sky.visibilityHeliacal events, Yallop lunar crescent, arcus visionis, atmospheric extinction
moira.sky.bodiesGeocentric, heliocentric, SSB, planetocentric, and topocentric body positions; nodes and apsides
moira.sky.observationPhase angle, illuminated fraction, apparent magnitude, elongation, Moon phases, apsides, conjunctions
moira.sky.galacticGalactic coordinate transforms (Liu, Zhu & Zhang 2011), reference point catalog
moira.sky.eventsRise/set/transit, twilight times, stations and retrograde periods
moira.sky.eclipseEclipse prediction, contact times, geographic paths, Saros/Metonic identification
moira.sky.occultationLunar occultations of planets and stars, graze geometry, close approaches

See Section 20 for the full submodule reference.


3. Core Types

Body — celestial body constants

from moira.facade import Body

Body.SUN       Body.MOON      Body.MERCURY   Body.VENUS
Body.MARS      Body.JUPITER   Body.SATURN    Body.URANUS
Body.NEPTUNE   Body.PLUTO

Body.TRUE_NODE   Body.MEAN_NODE   Body.LILITH

Body.EARTH       # for heliocentric computations

HouseSystem — house system constants

from moira.facade import HouseSystem

HouseSystem.PLACIDUS       HouseSystem.KOCH         HouseSystem.CAMPANUS
HouseSystem.REGIOMONTANUS  HouseSystem.EQUAL        HouseSystem.WHOLE_SIGN
HouseSystem.PORPHYRY       HouseSystem.MORINUS      HouseSystem.ALCABITIUS
HouseSystem.TOPOCENTRIC    HouseSystem.MERIDIAN     HouseSystem.VEHLOW
HouseSystem.SUNSHINE       HouseSystem.AZIMUTHAL    HouseSystem.CARTER
HouseSystem.KRUSINSKI      HouseSystem.APC

Ayanamsa — sidereal reference frame

from moira.facade import Ayanamsa

Ayanamsa.LAHIRI         # IAU standard; default for Vedic work
Ayanamsa.FAGAN_BRADLEY
Ayanamsa.RAMAN
Ayanamsa.TRUE_CHITRAPAKSHA
Ayanamsa.KRISHNAMURTI
Ayanamsa.SASSANIAN
# + dozens more — see list_ayanamsa_systems()

AspectDefinition and ASPECT_TIERS

AspectDefinition specifies a single aspect angle with its name, symbol, orb, and tier. Used to add custom aspects or override defaults.

from moira.facade import AspectDefinition, ASPECT_TIERS

custom = AspectDefinition(name="Quintile", symbol="Q", angle=72.0, orb=2.0, tier=3)

ASPECT_TIERS: dict[int, str] mapping tier number → descriptive label (e.g. {1: "Major", 2: "Minor", 3: "Harmonic"}). Used to filter aspects by significance level via AspectPolicy.

Chart — planetary snapshot vessel

Produced by Moira.chart(). Carries the full positional state of the sky at one Julian Day.

FieldTypeDescription
jd_utfloatLegacy field name: UTC-coded Julian Day of the civil snapshot; internal reductions resolve UT1 explicitly, preserving the historical proxy before the monotonic final-day handoff into the admitted atomic UTC era
planetsdict[str, PlanetData]Geocentric ecliptic positions
nodesdict[str, NodeData]Lunar nodes and Lilith
obliquityfloatTrue obliquity of the ecliptic (°)
delta_tfloatΔT = TT − UT1 in seconds at the resolved astronomical instant

Properties:

PropertyReturnsDescription
datetime_utcdatetimeUTC datetime for this snapshot
calendar_utcCalendarDateTimeBCE-safe calendar breakdown

Methods:

MethodReturnsDescription
longitudes(include_nodes=True)dict[str, float]Flat dict of body → ecliptic longitude
speeds()dict[str, float]Body → daily longitude speed (°/day)

PlanetData — single planet position

FieldTypeDescription
longitudefloatEcliptic longitude, tropical (°)
latitudefloatEcliptic latitude (°)
speedfloatDaily motion in longitude (negative = retrograde)
distancefloatDistance from Earth (km)

NodeData — lunar node position

FieldTypeDescription
longitudefloatEcliptic longitude (°)
speedfloatDaily motion (°/day)

SkyPosition — topocentric equatorial/horizontal

FieldTypeDescription
right_ascensionfloatApparent RA (°)
declinationfloatApparent Dec (°)
altitudefloatAltitude above horizon (°)
azimuthfloatAzimuth, North = 0° (°)
distancefloatDistance (km)

HouseCusps — computed house frame

FieldTypeDescription
cuspstuple[float, ...]12 house cusp longitudes (°), index 0 = cusp 1
ascfloatAscendant (°)
mcfloatMidheaven (°)
armcfloatARMC — Sidereal time × 15 (°)
east_pointfloatEast Point / Equatorial Ascendant longitude (°)
vertexfloatVertex longitude (°)
systemstrRequested house system code
effective_systemstrEffective system code after policy resolution
fallbackboolWhether fallback policy altered the requested system
fallback_reasonstr | NoneHuman-readable fallback reason, if any
classificationHouseSystemClassificationClassification of the effective house system
policyHousePolicyGoverning house policy used to compute the result

4. Moira Facade

Moira(kernel_path=None) is the primary entry point. All methods convert datetime inputs to JD internally. datetime arguments must be timezone-aware; naïve datetimes are rejected.

Construction

m = Moira()
m = Moira(kernel_path="/path/to/de441.bsp")

Construction is tolerant: if no planetary kernel is currently available, the instance still initializes and defers failure until a kernel-dependent method is called. Those calls raise MissingEphemerisKernelError with a diagnostic message.

Kernel readiness & management

MemberReturnsDescription
is_kernel_available()boolWhether a planetary kernel is ready right now
get_kernel_status()strHuman-readable kernel readiness message
kernel_statusstrProperty alias of get_kernel_status()
available_kernelslist[str]Installed planetary and supplemental kernel filenames
configure_kernel_path(path)NoneConfigure and validate an explicit planetary kernel path
download_missing_kernels(interactive=False)NoneDownload missing kernels into the standard user directory

Core chart methods

MethodReturnsDescription
chart(dt, bodies=None, include_nodes=True, observer_lat=None, observer_lon=None, observer_elev_m=0.0)ChartComplete planetary snapshot; supply observer coords for topocentric Moon
houses(dt, latitude, longitude, system=HouseSystem.PLACIDUS, policy=None)HouseCuspsHouse cusps, angles, ARMC under explicit house policy when supplied
sky_position(dt, body, latitude, longitude, elevation_m=0.0)SkyPositionApparent topocentric RA/Dec + altitude/azimuth
sidereal_chart(dt, ayanamsa_system=Ayanamsa.LAHIRI, bodies=None)dict[str, float]Body → sidereal longitude
heliocentric(dt, bodies=None)dict[str, HeliocentricData]Heliocentric ecliptic positions
phase(body, dt)dictPhase angle, illumination, angular diameter, apparent magnitude
twilight(dt, latitude, longitude)TwilightTimesCivil/nautical/astronomical twilight times

Aspects & patterns

MethodReturnsDescription
aspects(chart, orbs=None, include_minor=True, *, tier=None, orb_factor=1.0, policy=None)list[AspectData]All natal aspects with the complete owning-module policy surface forwarded
hellenistic_superiority_truth(longitude1, longitude2, aspect_angle=None, *, body1="body1", body2="body2")HellenisticSuperiorityTruthRaw direction applicability plus tenth-sign overcoming for one ordered pair
patterns(chart, orb_factor=1.0, dominant_only=False)list[AspectPattern]Named aspect patterns built from the chart's positions and aspects, with optional maximal-structure filtering
midpoints(chart, planet_set="classic")list[Midpoint]Planetary midpoints for the requested body set
midpoints_to_point(chart, longitude, orb=1.5)list[tuple[Midpoint, float]]Midpoints falling at a given longitude, paired with absolute orb
harmonic(chart, number)list[HarmonicPosition]Integer cyclic harmonic or zero-Aries-anchored positive-real continuous multiplier
harmonic_transit_forecast(natal_longitudes, transit_samples, policy)HarmonicTransitForecastSampled VA-informed complete mixed-origin triples; no interpolation or exact-contact claim
antiscia(chart, orb=1.0)list[AntisciaAspect]Antiscia and contra-antiscia aspects

Dignities & essential condition

MethodReturnsDescription
dignities(chart, houses, *, policy=None)list[PlanetaryDignity]Essential and accidental dignities with lossless policy forwarding
lots(chart, houses, *, policy=None, syzygy=None, prenatal_new_moon=None, prenatal_full_moon=None, lord_of_hour=None)list[ArabicPart]Arabic Parts / Hermetic Lots with nodes and optional external references preserved
evaluate_lots(chart, houses, *, policy=None, syzygy=None, prenatal_new_moon=None, prenatal_full_moon=None, lord_of_hour=None)LotsEvaluationLossless lot catalogue evaluation including typed unresolved entries
hellenistic_chart_profile(chart, houses, natal_dt, current_dt, *, civil_timezone=None, policy=None, ...)HellenisticChartProfileScore-free composition of admitted Hellenistic atomic receipts from an explicit no-fallback Whole Sign chart
solar_proximity_truth(...), planetary_solar_phase_truth(...), besieging_truth(...)typed raw receiptsRaw dignity geometry before compatibility labels or score assembly
mutual_receptions(chart, by_exaltation=False)list[tuple](planet_a, planet_b, type) mutual reception triples
astrodynes(body_inputs, cusp_signs, intercepted_signs_by_house=None, policy=None)AstrodyneChartResultKernel-free Church of Light natal Astrodynes from explicit chart geometry

Classical techniques

MethodReturnsDescription
profection(natal_asc, natal_dt, current_dt, natal_positions=None, *, civil_timezone=None, leap_day_policy=None, ambiguous_time_policy=None, interval_policy=..., activation_orb=5.0)ProfectionResultAnnual profection plus the exact dated monthly chronology
profection_chronology(natal_asc, natal_dt, current_dt, *, civil_timezone=None, leap_day_policy=None, ambiguous_time_policy=None, interval_policy=...)ProfectionChronologyRaw dated monthly profection receipt
nakshatras(chart, ayanamsa_system=Ayanamsa.LAHIRI)dict[str, NakshatraPosition]Nakshatra for each planet
planetary_hours(dt, latitude, longitude)PlanetaryHoursDayDay and night planetary hour rulers

Timing techniques

MethodReturnsDescription
transits(body, target_lon, jd_start, jd_end)list[TransitEvent]All transits of a body to a natal point
ingresses(body, jd_start, jd_end)list[IngressEvent]All sign ingresses in a date range
next_ingress(body, jd_start, max_days=None)IngressEvent | NoneNext sign ingress of any kind
next_ingress_into(body, sign, jd_start, max_days=None)IngressEvent | NoneNext entry into a specific sign
solar_return(natal_sun_lon, year)floatJD UT of the Solar Return in a calendar year
lunar_return(natal_moon_lon, jd_start)floatJD UT of the next Lunar Return
planet_return(body, natal_lon, jd_start, direction="direct")floatJD UT of the next planetary return
syzygy(jd)tuple[float, str](jd_ut, kind) of prenatal syzygy
stations(body, jd_start, jd_end)list[StationEvent]Retrograde and direct stations
retrograde_periods(body, jd_start, jd_end)list[tuple[float, float]]List of (jd_start, jd_end) retrograde intervals
moon_void_of_course(dt, ...) / is_moon_void_of_course(dt, ...)See Void of Course Moon subsection below
electional_windows(dt_start, dt_end, ...)See Electional search subsection below
ramesey_moon_condition_at(jd_ut, latitude, longitude, *, house_system, ...)RameseyMoonConditionEvaluationOne bounded Ramesey v1 Moon-condition evaluation; no search, score, or recommendation

Progressions & directions

MethodReturnsDescription
progression(natal_dt, target_dt, bodies=None)ProgressedChartSecondary progression (1 day = 1 year)
solar_arc_directions(natal_dt, target_dt, bodies=None)ProgressedChartSolar Arc directed chart
solar_arc_directions_ra(natal_dt, target_dt, bodies=None)ProgressedChartSolar Arc in right ascension
naibod_in_longitude(natal_dt, target_dt, bodies=None)ProgressedChartNaibod directions in ecliptic longitude
naibod_in_right_ascension(natal_dt, target_dt, bodies=None)ProgressedChartNaibod directions in right ascension
tertiary_progression(natal_dt, target_dt, bodies=None)ProgressedChartTertiary progression (1 day = 1 lunar month)
tertiary_ii_progression(natal_dt, target_dt, bodies=None)ProgressedChartTertiary II / Klaus Wessel
minor_progression(natal_dt, target_dt, bodies=None)ProgressedChartMinor progression (1 lunar month = 1 year)
ascendant_arc_directions(natal_dt, target_dt, latitude, longitude, bodies=None)ProgressedChartAscendant Arc directed chart
daily_house_frame(natal_dt, target_dt, latitude, longitude, system=...)HouseCuspsDaily Houses progressed frame
converse_progression(natal_dt, target_dt, bodies=None)ProgressedChartConverse Secondary Progressed
converse_solar_arc(natal_dt, target_dt, bodies=None)ProgressedChartConverse Solar Arc
converse_solar_arc_ra(natal_dt, target_dt, bodies=None)ProgressedChartConverse Solar Arc in RA
converse_naibod_in_longitude(natal_dt, target_dt, bodies=None)ProgressedChartConverse Naibod in longitude
converse_naibod_in_right_ascension(natal_dt, target_dt, bodies=None)ProgressedChartConverse Naibod in RA
converse_tertiary_progression(natal_dt, target_dt, bodies=None)ProgressedChartConverse Tertiary
converse_tertiary_ii_progression(natal_dt, target_dt, bodies=None)ProgressedChartConverse Tertiary II
converse_minor_progression(natal_dt, target_dt, bodies=None)ProgressedChartConverse Minor
duodenary_progression(natal_dt, target_dt, bodies=None)ProgressedChartDuodenary progression
converse_duodenary_progression(natal_dt, target_dt, bodies=None)ProgressedChartConverse duodenary progression
quotidian_solar_progression(natal_dt, target_dt, bodies=None)ProgressedChartSolar quotidian progression
converse_quotidian_solar_progression(natal_dt, target_dt, bodies=None)ProgressedChartConverse solar quotidian progression
quotidian_lunar_progression(natal_dt, target_dt, bodies=None)ProgressedChartLunar quotidian progression
converse_quotidian_lunar_progression(natal_dt, target_dt, bodies=None)ProgressedChartConverse lunar quotidian progression
planetary_arc_directions(natal_dt, target_dt, arc_body, bodies=None)ProgressedChartPlanetary-arc directed chart
converse_planetary_arc_directions(natal_dt, target_dt, arc_body, bodies=None)ProgressedChartConverse planetary-arc directed chart
speculum(chart, houses, geo_lat, *, obliquity=None, bodies=None)list[SpeculumEntry]Speculum with established positional compatibility and additive explicit inputs
primary_directions(chart, houses, geo_lat, max_arc=90.0, include_converse=True, significators=None, promissors=None, *, solar_speed=None, obliquity=None, policy=None)list[PrimaryArc]Primary-direction arcs under an explicit optional policy; established positional inputs are unchanged
primary_directions_policy_preset(preset, **kwargs)PrimaryDirectionsPolicyBuild one canonical typed policy preset
primary_direction_relations(arc, *, policy=None)PrimaryDirectionRelationProfileEvaluate one arc's admitted/scored relation truth
primary_direction_condition(arcs, *, policy=None)PrimaryDirectionsSignificatorProfileEvaluate one significator's directed condition
primary_directions_profile(arcs, *, policy=None)PrimaryDirectionsAggregateProfileEvaluate a chart-wide primary-directions aggregate
primary_directions_network(arcs, *, policy=None)PrimaryDirectionsNetworkProfileEvaluate the directed promissor-to-significator graph

Hellenistic & Vedic time lords

MethodReturnsDescription
firdaria(natal_dt, natal_chart, natal_houses)list[FirdarPeriod]Persian Firdaria sequence; explicit houses and Sun are required for sect
decennials(natal_dt, natal_chart, natal_houses, *, levels=2, policy=None)list[DecennialPeriod]Admitted L1/L2 Decennials sequence with explicit sect and time-basis truth
current_decennials(natal_dt, current_dt, natal_chart, natal_houses, *, levels=2, policy=None)tuple[DecennialPeriod, DecennialPeriod]Active Decennials major/sub-period on the elapsed lived-day coordinate
zodiacal_releasing(lot_longitude, natal_dt, levels=4, *, lot_name="Spirit", fortune_longitude=None, use_loosing_of_bond=True, policy=None)list[ReleasingPeriod]Zodiacal Releasing with all doctrine inputs forwarded
vimshottari_dasha(natal_chart, natal_dt, levels=2, ayanamsa_system=Ayanamsa.LAHIRI)list[DashaPeriod]Vimshottari Dasha sequence from Moon nakshatra

Synastry & relationship charts

MethodReturnsDescription
synastry_aspects(chart_a, chart_b, tier=2, orbs=None, orb_factor=1.0, include_nodes=True)list[AspectData]Inter-aspects between two natal charts
house_overlay(chart_source, target_houses, include_nodes=True, source_label="A", target_label="B")SynastryHouseOverlayPlace chart_source planets in target_houses
mutual_house_overlays(chart_a, houses_a, chart_b, houses_b, include_nodes=True)MutualHouseOverlayBoth overlay directions in one call
composite_chart(chart_a, chart_b, houses_a=None, houses_b=None)CompositeChartMidpoint composite
aspects_from_longitudes(longitudes, *, tier=1, orb_factor=1.0, include_nodes=True)LongitudeAspectAnalysisCanonical position-only aspect analysis for a derived chart; no speeds or moment inferred
composite_chart_reference_place(chart_a, chart_b, houses_a, houses_b, reference_latitude, house_system=..., policy=None)CompositeChartReference-place composite house method with explicit synastry policy when supplied
davison_chart(dt_a, lat_a, lon_a, dt_b, lat_b, lon_b, house_system=..., policy=None)DavisonChartDavison Relationship Chart (spherical midpoint time + location)
davison_chart_uncorrected(...)DavisonChartDavison with arithmetic midpoints
davison_chart_reference_place(dt_a, dt_b, ref_lat, ref_lon, house_system=...)DavisonChartDavison with midpoint time and explicit place
davison_chart_spherical_midpoint(...)DavisonChartDavison with midpoint time and spherical geographic midpoint
davison_chart_corrected(...)DavisonChartDavison with midpoint location and corrected time

The REST forms POST /v1/composite/chart and POST /v1/davison/chart include a required aspects analysis of the returned chart's own longitudes. The request's tier, orb_factor, and include_nodes fields govern that nested analysis, whose computation truth preserves the absence of speed-derived motion semantics.

Geography

MethodReturnsDescription
astrocartography(chart, observer_lat=0.0, observer_lon=0.0, bodies=None, lat_step=2.0)list[ACGLine]ACG lines (MC/IC/ASC/DSC) for all planets
local_space(chart, latitude, longitude, bodies=None)list[LocalSpacePosition]Horizon azimuth and altitude for each planet
gauquelin_sectors(chart, latitude, longitude, bodies=None)list[GauquelinPosition]Gauquelin sector placements for chart bodies at a location

Fixed stars, mansions & parans

MethodReturnsDescription
fixed_star(name, dt)FixedStarUnified star position enriched with Gaia DR3 data
heliacal_rising(star_name, dt, latitude, longitude)float | NoneJD UT of the next heliacal rising
heliacal_setting(star_name, dt, latitude, longitude)float | NoneJD UT of the next heliacal setting
heliacal_rising_event(star_name, dt, latitude, longitude)HeliacalEventFull heliacal-rising event vessel with classification metadata
heliacal_setting_event(star_name, dt, latitude, longitude)HeliacalEventFull heliacal-setting event vessel with classification metadata
lunar_mansions(chart)dict[str, MansionPosition]Arabic lunar mansion placement for chart bodies
parans(natal_dt, latitude, longitude, bodies=None, orb_minutes=4.0)list[Paran]Paran crossings for the chart date and location

Alternative frames & specialty coordinates

MethodReturnsDescription
planetary_nodes(dt)dict[str, OrbitalNode]Heliocentric orbital nodes and apsides for the planets
planetocentric(observer, dt, bodies=None)dict[str, PlanetocentricData]Positions as seen from the center of the observer body
ssb_chart(dt, bodies=None)dict[str, SSBPosition]Solar System barycenter positions in the standard of-date ecliptic frame
received_light(dt, bodies=None)dict[str, ReceivedLightPosition]Apparent received-light positions with explicit light-cone geometry
galactic_chart(chart, bodies=None)list[GalacticPosition]Galactic longitude/latitude for chart bodies
galactic_angles(chart)dict[str, tuple[float, float]]Ecliptic long/lat of major galactic reference points
uranian(dt)dict[str, UranianPosition]Positions of the current nine Uranian/Hamburg School and Transpluto hypothetical bodies
geodetic(chart, zodiac="tropical", ayanamsa_system=None)GeodeticChartGeodetic chart frame derived from planetary longitudes
geodetic_planet_equivalents(chart, bodies=None, zodiac="tropical", ayanamsa_system=None)dict[str, float]Geodetic longitude equivalents for selected bodies
synodic_phase(body1, body2, dt)dict[str, float | str]Synodic separation, cycle fraction, and phase label for two bodies
galactic_houses(dt, latitude, longitude)GalacticHouseCuspsGalactic Porphyry house cusps for a time and observer location; cusps_gal gives native galactic membership, cusps_ecl provides ecliptic interoperability

Phenomena & occultations

MethodReturnsDescription
phenomena(body, jd_start, jd_end)list[PhenomenonEvent]Greatest elongations, perihelion, and aphelion events in a range
moon_phases(jd_start, jd_end)list[PhenomenonEvent]All eight standard Moon phases in a date range
next_conjunction(body1, body2, jd_start, max_days=1200.0)PhenomenonEvent | NoneNext conjunction of two bodies
conjunctions(body1, body2, jd_start, jd_end)list[PhenomenonEvent]All conjunctions of two bodies in a date range
resonance(body1, body2)OrbitalResonanceBest-fit orbital resonance relation between two bodies
occultations(jd_start, jd_end, targets=None)list[LunarOccultation]Lunar occultations of the default planet set or supplied targets
close_approaches(body1, body2, jd_start, jd_end, max_sep_deg=1.0)list[CloseApproach]Close approaches between two bodies in a date range

Traditional, historical & diagnostic methods

MethodReturnsDescription
longevity(chart, houses)HylegResultTraditional hyleg and alcocoden longevity analysis
sothic_cycle(latitude, longitude, year_start, year_end, arcus_visionis=10.0)list[SothicEntry]Year-by-year heliacal risings of Sirius across a date span
sothic_epoch_finder(latitude, longitude, year_start, year_end, tolerance_days=1.0)list[SothicEpoch]Candidate Sothic epochs across a year range
egyptian_date(dt, epoch_jd=None)EgyptianDateEgyptian civil calendar date for a datetime

Variable & multiple stars

MethodReturnsDescription
variable_star_phase(name, dt)floatCurrent variable-star phase at a datetime
variable_star_magnitude(name, dt)floatEstimated V magnitude at a datetime
variable_star_next_minimum(name, dt)float | NoneJD of the next primary minimum
variable_star_next_maximum(name, dt)float | NoneJD of the next maximum
variable_star_minima(name, jd_start, jd_end)list[float]All minima JDs in a range
variable_star_maxima(name, jd_start, jd_end)list[float]All maxima JDs in a range
variable_star_quality(name, dt)dict[str, float | bool]Phase, magnitude, benefic/malefic quality metrics, and eclipse state
multiple_star_separation(name, dt, aperture_mm=100.0)dictSeparation, PA, resolvability, and brightness summary
multiple_star_components(name, dt)dictFull component snapshot for a multiple star system

Void of Course Moon

MethodReturnsDescription
moon_void_of_course(dt, modern=False)VoidOfCourseWindowVoid-of-course window for the Moon's current sign
is_moon_void_of_course(dt, modern=False)boolWhether the Moon is void of course at the given datetime
MethodReturnsDescription
electional_windows(dt_start, dt_end, latitude, longitude, predicate, policy=None)list[ElectionalWindow]Search a date range for windows whose chart context satisfies the predicate

Low-level JD-based electional search is also public:

from moira.facade import ElectionalPolicy, ElectionalWindow
from moira.facade import find_electional_windows, find_electional_moments
FunctionReturnsDescription
find_electional_windows(jd_start, jd_end, latitude, longitude, predicate, policy=None, reader=None)list[ElectionalWindow]Window search directly on Julian dates
find_electional_moments(jd_start, jd_end, latitude, longitude, predicate, policy=None, reader=None)list[float]Exact candidate JDs for matching electional moments

Western electional moment evaluation

Source-owned Western profiles are separate from generic electional search:

from moira import (
    RAMESEY_MOON_CONDITION_V1,
    RameseyMoonConditionEvaluation,
    ramesey_moon_condition_at,
)

evaluation = engine.ramesey_moon_condition_at(
    jd_ut,
    latitude,
    longitude,
    house_system="R",
    unavoidable_time_urgency=None,
)
SurfaceReturnsDescription
Moira.ramesey_moon_condition_at(jd_ut, latitude, longitude, *, house_system, unavoidable_time_urgency=None, house_policy=None, policy=None)RameseyMoonConditionEvaluationReader-backed single-moment facade entry point; defaults to the immutable Ramesey v1 policy
ramesey_moon_condition_at(jd_ut, latitude, longitude, *, house_system, unavoidable_time_urgency=None, reader=None, house_policy=None, policy=RAMESEY_MOON_CONDITION_V1)RameseyMoonConditionEvaluationLow-level JD evaluator with explicit reader support
evaluate_ramesey_moon_condition(chart, *, ...)RameseyMoonConditionEvaluationEvaluate the named profile from an explicitly compatible chart product

The same public ladder now includes:

SurfaceReturnsDescription
Moira.sahl_moon_condition_at(...)SahlMoonConditionEvaluationSingle-moment Sahl section 22 Moon-condition evaluation
Moira.dorotheus_moon_condition_at(...)DorotheusMoonConditionEvaluationSingle-moment Dorotheus V.6 Moon-condition and remedy witness
Moira.dorotheus_rooted_context_at(...)DorotheusRootedContextEvaluationV.6/V.31 root, outcome, matter-significator, and natal-context evidence
Moira.dorotheus_construction_at(...)DorotheusConstructionEvaluationComplete V.2-V.7 construction matter profile without scoring or recommendation
Moira.dorotheus_matter_profile_at(..., profile_id=...)DorotheusMatterProfileEvaluationNamed Dorotheus Book V matter profile, including V.20 partnership, V.21 debt/payment, V.22 travel, V.24–V.26 ship work, and V.43 wills; V.26.39–43 land/sea profiles require an explicit sign-nature variant
Moira.western_electional_profile_windows(...)WesternElectionalProfileScanBounded discrete status-window scan for one admitted Moon profile

dorotheus_construction_v1 exposes the IERS-backed signed lunar equation in its first construction clause while retaining the source-unspecified ecliptic-crossing clause as not_evaluable. Its nested rooted context evaluates V.31 bad-place membership as whole-sign places 3, 6, 8, and 12. The corresponding REST transports are documented in wiki/02_services/REST_API_REFERENCE.md. The matter-profile facade accepts an explicit DorotheusMatterProfileId and uses one stable result vessel for every admitted topic. The profile id, matter, source-ordered clauses, whole-sign angular witnesses, policy, and completeness flags remain explicit; V.9 lunar flow is not inferred from the forward connection alone. V.20 partnership and V.21 debt/payment retain their Mercurial V.31 context. V.22 travel, V.24 acquisition, V.25 construction, V.26 launch, and V.43 will writing deliberately have none; V.26 alone may receive a complete radical chart for the chapter-owned Saturn overlay. Each source-open term remains not_evaluable rather than receiving an invented sign list, degree interval, or historical score.

The package root and moira.facade export the admitted profile constants, evaluator functions, policy/evaluation vessels, and their named witnesses and enums. Results retain their source-ordered rules and explicit unresolved semantics. They deliberately report complete_electional_judgement=False, with no score, advice, recommendation, generic search integration, or remedy-fulfillment assessment.

Julian Day utilities

MethodReturnsDescription
jd(year, month, day, hour=0.0)floatJD from a proleptic Gregorian calendar date
from_jd(jd)datetimeUTC datetime from a JD
calendar_from_jd(jd)CalendarDateTimeBCE-safe calendar breakdown from a JD

Eclipse

MethodReturnsDescription
eclipse(dt)EclipseDataFull eclipse geometry and classification for a datetime
solar_eclipse_footprint(jd_start, *, kind="any", backward=False, sample_count=181)SolarEclipseVisibilityFootprintComplete zero-elevation WGS 84 mean-limb penumbral visibility boundary
solar_global_circumstances(jd_start, *, kind="any", backward=False)SolarEclipseGlobalCircumstancesScale-explicit global contacts, conjunctions, GE/GD, body states, and provenance
solar_eclipse_cartography(jd_start, *, kind="any", backward=False, magnitude_levels=..., obscuration_levels=..., mesh_depth=1, time_samples=17, angular_tolerance_deg=8.0, field_tolerance=0.01)SolarEclipseCartographyNumPy-free adaptive spherical maximum-visible magnitude and obscuration contours
lunar_global_circumstances(jd_start, *, kind="any", backward=False, mode="native")LunarEclipseGlobalCircumstancesMode-pure geocentric contacts, parameters, body states, and durations
lunar_eclipse_visibility_map(jd_start, *, kind="any", backward=False, mode="native", sample_count=181)LunarEclipseVisibilityMapGlobal contact-horizon limits for lunar-eclipse map rendering

5. Ephemeris & Positions

Planetary positions — low-level functions

from moira.facade import planet_at, all_planets_at, sky_position_at
from moira.spk_reader import get_reader

reader = get_reader()
jd     = 2451545.0   # J2000.0

pos = planet_at("Jupiter", jd, reader=reader)
# PlanetData(longitude, latitude, speed, distance)

sky = sky_position_at("Mars", jd, observer_lat=51.5, observer_lon=-0.1, reader=reader)
# SkyPosition(right_ascension, declination, altitude, azimuth, distance)

chart_dict = all_planets_at(jd, reader=reader)
# dict[str, PlanetData] for all ten classical planets

The unified planet_at, all_planets_at, and sky_position_at body namespace also admits cataloged asteroids and comets. A globally unique name resolves directly. A name present in both families must be qualified, for example "asteroid:Halley" or "comet:Halley"; an unqualified collision raises AmbiguousSmallBodyNameError instead of selecting a family by lookup order. Canonical comet designations such as "1P/Halley" remain globally unambiguous. Dedicated asteroid_at and comet_at calls retain their family-local name rules.

FunctionReturnsDescription
planet_at(body, jd_ut, reader=None, observer_lat=None, observer_lon=None, observer_elev_m=0.0)PlanetDataSingle planet geocentric ecliptic position
all_planets_at(jd_ut, bodies=None, reader=None, ...)dict[str, PlanetData]All (or specified) planets at one JD
sky_position_at(body, jd_ut, observer_lat, observer_lon, observer_elev_m=0.0, reader=None)SkyPositionApparent topocentric equatorial + horizontal coords
planet_reduction_breakdown_at(body, jd_ut, reader=None, ...)PlanetReductionBreakdownOrdered visible longitude-reduction stages as PlanetReductionStage records
sun_longitude(jd_ut, reader=None)floatSun ecliptic longitude only (faster than planet_at)

Lunar nodes

FunctionReturnsDescription
true_node(jd_ut, reader=None)NodeDataTrue (osculating) lunar node
mean_node(jd_ut, *, nutation=True)NodeDataIERS 2003 mean lunar node; true equinox of date by default, raw mean equinox with nutation=False
mean_lilith(jd_ut, *, nutation=True)NodeDataMean Black Moon Lilith; true equinox of date by default, raw mean equinox with nutation=False

Nodes & apsides bundle

from moira.facade import NodesAndApsides, nodes_and_apsides_at, next_moon_node_crossing
FunctionReturnsDescription
nodes_and_apsides_at(body, jd_ut)NodesAndApsidesCombined node/apsides vessel for the Moon or supported orbital bodies
next_moon_node_crossing(jd_start, reader=None, ascending=True)floatJD UT of the next ascending or descending lunar node crossing

Heliocentric positions

from moira.facade import heliocentric_planet_at, all_heliocentric_at, HeliocentricData
FunctionReturnsDescription
heliocentric_planet_at(body, jd_ut, reader=None)HeliocentricDataHeliocentric ecliptic longitude, latitude, distance
all_heliocentric_at(jd_ut, bodies=None, reader=None)dict[str, HeliocentricData]All planets heliocentrically

Asteroids

from moira.facade import asteroid_at, all_asteroids_at, list_asteroids
from moira.facade import load_asteroid_kernel   # for non-DE441 bodies
FunctionReturnsDescription
asteroid_at(name_or_id, jd_ut, reader=None)AsteroidDataSingle asteroid geocentric ecliptic position
all_asteroids_at(jd_ut, reader=None)dict[str, AsteroidData]All loaded asteroids
list_asteroids()list[str]Names of currently loaded asteroids
available_in_kernel(kernel_path)list[str]Asteroid names available in a kernel
load_asteroid_kernel(path)Load a supplementary SPK kernel
load_secondary_kernel(path)Load second SPK kernel
load_tertiary_kernel(path)Load third SPK kernel

Planetary nodes (apsides)

FunctionReturnsDescription
planetary_node(body, jd_ut)OrbitalNodeAscending node and perihelion for a planet
all_planetary_nodes(jd_ut)dict[str, OrbitalNode]All planetary nodes

Uranian planets (Hamburg School)

from moira.facade import uranian_at, all_uranian_at, list_uranian, UranianBody, UranianPosition
FunctionReturnsDescription
uranian_at(body, jd_ut)UranianPositionSingle Uranian body position
all_uranian_at(jd_ut)dict[str, UranianPosition]All current nine Uranian/Hamburg School and Transpluto hypothetical bodies
list_uranian()list[str]Uranian body names (Cupido through Poseidon, plus Transpluto)

UranianBody constants: CUPIDO HADES ZEUS KRONOS APOLLON ADMETOS VULKANUS POSEIDON TRANSPLUTO

Galactic coordinates

from moira.facade import (
    galactic_position_of, all_galactic_positions, galactic_reference_points,
    equatorial_to_galactic, galactic_to_equatorial,
    ecliptic_to_galactic, galactic_to_ecliptic,
    GalacticPosition,
)
FunctionReturnsDescription
galactic_position_of(body, ecliptic_lon, ecliptic_lat, obliquity, jd_tt)GalacticPositionGalactic longitude and latitude (IAU 1958) for one body from true-of-date ecliptic coordinates
all_galactic_positions(body_data, obliquity, jd_tt)list[GalacticPosition]Galactic positions for a dict of body -> (lon, lat) using the chart's TT epoch
galactic_reference_points(obliquity, jd_tt)dict[str, tuple[float, float]]GC, anti-GC, NGP, SGP, and super-galactic center in true ecliptic-of-date coordinates
equatorial_to_galactic(ra, dec)tuple[float, float]RA/Dec -> galactic (l, b)
galactic_to_equatorial(l, b)tuple[float, float]Galactic -> RA/Dec
ecliptic_to_galactic(lon, lat, obliquity, jd_tt)tuple[float, float]True ecliptic-of-date -> galactic, with TT epoch used for the J2000 frame bridge
galactic_to_ecliptic(l, b, obliquity, jd_tt)tuple[float, float]Galactic -> true ecliptic-of-date, with TT epoch used for the of-date frame bridge

Gauquelin sectors

from moira.facade import (
    GauquelinHorizonStatus,
    GauquelinPosition,
    gauquelin_sector,
    all_gauquelin_sectors,
)
FunctionReturnsDescription
gauquelin_sector(body_ra, body_dec, lat, lst, body="", horizon_altitude=0.0, sectors=36)GauquelinPositionGauquelin sector for a single apparent RA/Dec position at geographic latitude and local sidereal time; non-rising geometry returns an explicit status with nullable sector-derived fields
all_gauquelin_sectors(planet_ra_dec, lat, lst, horizon_altitude=0.0, sectors=36)list[GauquelinPosition]Gauquelin sectors for a dict of body -> (ra, dec)

GauquelinPosition: body, sector, zone, diurnal_position, sectors, degree_in_sector, is_plus_zone, horizon_status. sector, diurnal_position, and degree_in_sector are None when the body does not have an ordinary rise/set pair at the selected horizon.

GauquelinHorizonStatus: NORMAL, CIRCUMPOLAR, NEVER_RISES, HORIZON_COINCIDENT.

Coordinate utilities

from moira.facade import (
    icrf_to_ecliptic, icrf_to_equatorial, ecliptic_to_equatorial,
    equatorial_to_horizontal, horizontal_to_equatorial,
    cotrans_sp,
    atmospheric_refraction, atmospheric_refraction_extended,
    equation_of_time,
    angular_distance, normalize_degrees,
)
FunctionSignatureDescription
ecliptic_to_equatorial(lon, lat, obliquity) -> (ra, dec)Ecliptic -> equatorial (degrees)
equatorial_to_horizontal(ha, dec, lat) -> (az, alt)Hour angle/Dec -> azimuth/altitude
horizontal_to_equatorial(azimuth_deg, altitude_deg, lst_deg, lat_deg) -> (ra, dec)Horizontal coordinates -> equatorial coordinates
cotrans_sp(lon, lat, dist, lon_speed, lat_speed, dist_speed, obliquity) -> tuple[...]Simultaneous spherical coordinate and speed transformation
atmospheric_refraction(altitude_deg, *, pressure_mbar=..., temperature_c=...) -> floatStandard apparent-altitude refraction correction (degrees)
atmospheric_refraction_extended(altitude_deg, *, pressure_mbar=..., temperature_c=..., relative_humidity=..., observer_height_m=..., wavelength_micron=...) -> tuple[float, float]Extended refraction model with environmental parameters
equation_of_time(jd_tt) -> floatEquation of time in minutes at the TT epoch
angular_distance(lon1, lat1, lon2, lat2) -> floatGreat-circle distance (degrees)
normalize_degrees(d) -> floatMap any angle to [0, 360)

Phase & apparent magnitude

from moira.facade import angular_diameter

ang_diam_arcsec = angular_diameter("Moon", jd)

# For full phase metrics use Moira.phase():
result = m.phase("Venus", dt)
# keys: phase_angle, illumination, angular_diameter_arcsec, apparent_magnitude

Twilight

from moira.facade import twilight_times, TwilightTimes

t = twilight_times(jd, latitude=51.5, longitude=-0.1)
# TwilightTimes: civil_dawn, civil_dusk, nautical_dawn, nautical_dusk,
#                astro_dawn, astro_dusk, sunrise, sunset  (all JD UT)

Relative-motion, orbital, and event helpers

from moira.facade import (
    planet_relative_to, next_heliocentric_transit,
    PlanetPhenomena, planet_phenomena_at,
    KeplerianElements, DistanceExtremes,
    orbital_elements_at, distance_extremes_at,
)
FunctionReturnsDescription
planet_relative_to(body, center_body, jd_ut, reader=None)PlanetDataBody position relative to another physical center body
next_heliocentric_transit(body, target_lon, jd_start, reader=None, max_days=400.0)floatNext heliocentric longitude crossing of a target longitude
planet_phenomena_at(body, jd_ut)PlanetPhenomenaInstantaneous elongation/phase-style observational summary for one body
orbital_elements_at(body, jd_ut, reader)KeplerianElementsOsculating orbital elements at one epoch
distance_extremes_at(body, jd_ut, reader)DistanceExtremesPerihelion/aphelion-style distance-extrema summary at one epoch

6. Alternative Reference Frames

Moira's default position products are geocentric ecliptic. Three additional engines surface different physical origins or light-cone geometry, each exposing true-of-date ecliptic orientation for direct comparison while preserving its own origin doctrine.

Solar System Barycenter (SSB) Chart

from moira.ssb import SSBPosition, SSB_BODIES, ssb_position_at, all_ssb_positions_at

The SSB is the true inertial center-of-mass of the solar system. The Sun wanders up to ~2.2 solar radii (~0.010 AU) from the SSB, driven mainly by Jupiter's mass. Positions use the SSB as origin and true-of-date ecliptic orientation (precession + nutation applied); they are not geocentric positions.

SymbolTypeDescription
SSB_BODIESfrozenset[str]Bodies with well-defined barycentric state in DE441
ssb_position_at(body, jd_ut)SSBPositionSSB-relative position of one body
all_ssb_positions_at(jd_ut)dict[str, SSBPosition]SSB-relative positions of all supported bodies

SSBPosition fields

FieldTypeDescription
namestrBody name
longitudefloatEcliptic longitude (°), [0°, 360°)
latitudefloatEcliptic latitude (°)
distancefloatDistance from SSB (km)
speedfloatLongitudinal speed (°/day)
retrogradeboolTrue when speed < 0
signstrZodiac sign (derived)
sign_symbolstrSign glyph (derived)
sign_degreefloatDegree within sign (derived)

Property: distance_au → distance from SSB in Astronomical Units.

from moira.ssb import ssb_position_at, all_ssb_positions_at
from moira.facade import jd_from_datetime, utc_to_ut1
from datetime import datetime, timezone

jd = utc_to_ut1(jd_from_datetime(datetime(2000, 1, 1, 12, tzinfo=timezone.utc)))

sun_ssb = ssb_position_at("Sun", jd)
print(f"Sun from SSB: {sun_ssb.longitude:.4f}°  dist {sun_ssb.distance_au:.6f} AU")

all_pos = all_ssb_positions_at(jd)
for name, pos in all_pos.items():
    print(f"{name:10s}  {pos.longitude:.4f}°")

Planetocentric Positions

from moira.planetocentric import (
    PlanetocentricData, VALID_OBSERVER_BODIES,
    planetocentric_at, all_planetocentric_at,
)

Geometric positions of celestial bodies measured from the center of a specified observer planet other than Earth. Any body with a barycentric state in the DE441 kernel can serve as the observer — including the Sun (heliocentric) and the Moon. Output preserves the requested observer-origin vector and uses true-of-date ecliptic orientation.

Valid observers: Body.SUN, Body.MOON, Body.MERCURY, Body.VENUS, Body.EARTH, Body.MARS, Body.JUPITER, Body.SATURN, Body.URANUS, Body.NEPTUNE, Body.PLUTO

SymbolTypeDescription
VALID_OBSERVER_BODIESfrozenset[str]Bodies that may serve as observer or target
planetocentric_at(observer, target, jd_ut)PlanetocentricDataPosition of target as seen from observer
all_planetocentric_at(observer, jd_ut)dict[str, PlanetocentricData]All visible bodies from the observer

PlanetocentricData fields

FieldTypeDescription
observerstrObserver body name
namestrTarget body name
longitudefloatEcliptic longitude (°), [0°, 360°)
latitudefloatEcliptic latitude (°)
distancefloatObserver–target distance (km)
speedfloatLongitudinal speed (°/day)
retrogradeboolTrue when speed < 0
signstrZodiac sign (derived)
sign_symbolstrSign glyph (derived)
sign_degreefloatDegree within sign (derived)

Property: distance_au → observer–target distance in Astronomical Units.

from moira.planetocentric import planetocentric_at, all_planetocentric_at

# Saturn as seen from Jupiter:
sat_from_jup = planetocentric_at("Jupiter", "Saturn", jd)

# All planets as seen from Mars:
mars_sky = all_planetocentric_at("Mars", jd)

Received-Light (Light-Cone) Positions

from moira.light_cone import (
    ReceivedLightPosition, RECEIVED_LIGHT_BODIES,
    received_light_at, all_received_light_at,
)

Standard astrological positions already incorporate light-time correction (the body's position is computed for t − τ, where τ is the one-way light travel time). This engine makes the light-cone geometry explicit by surfacing both the apparent position (where the body was when it emitted the arriving light) and the geometric position (where the body physically is at the birth moment).

Typical light travel times and longitude displacements:

BodyLight timeMax displacement
Moon~1.3 s< 0.0001°
Sun~8.3 min~0.02°
Jupiter~35–52 min~0.06°
Saturn~68–84 min~0.10°
Pluto~5.3 h~0.35°
SymbolTypeDescription
RECEIVED_LIGHT_BODIESfrozenset[str]Physical bodies for which light-cone is meaningful (excludes computed points)
received_light_at(body, jd_ut)ReceivedLightPositionReceived-light position for one body
all_received_light_at(jd_ut)dict[str, ReceivedLightPosition]Received-light positions for all supported bodies

ReceivedLightPosition fields

FieldTypeDescription
namestrBody name
apparent_longitudefloatWhere body was when light emitted (°) — the standard astrological position
apparent_latitudefloatEcliptic latitude at emission instant (°)
geometric_longitudefloatWhere body physically is at birth moment (°)
geometric_latitudefloatEcliptic latitude at birth moment (°)
distance_kmfloatEarth–body distance at emission instant (km)
light_travel_daysfloatOne-way light travel time (τ) in days
emission_jdfloatJulian Date when photons were emitted (jd_ut − τ)
speedfloatApparent longitudinal speed (°/day)
retrogradeboolTrue when apparent speed < 0
signstrSign of apparent longitude (derived)
sign_symbolstrSign glyph (derived)
sign_degreefloatDegree within sign (derived)

Properties:

  • light_travel_minutes → one-way light travel time in minutes
  • longitude_displacement → angular shift between apparent and geometric longitude (°), in (−180°, +180°]
  • distance_au → Earth–body distance in Astronomical Units
from moira.light_cone import received_light_at, all_received_light_at

pluto = received_light_at("Pluto", jd)
print(f"Pluto apparent:  {pluto.apparent_longitude:.4f}°")
print(f"Pluto geometric: {pluto.geometric_longitude:.4f}°")
print(f"Light travel:    {pluto.light_travel_minutes:.1f} min")
print(f"Displacement:    {pluto.longitude_displacement:+.4f}°")

7. Chart Structure

Houses

from moira.facade import calculate_houses, HouseCusps, HouseSystem
from moira.facade import (
    assign_house, describe_boundary, describe_angularity,
    compare_systems, compare_placements, distribute_points,
    HouseSystemFamily, HouseSystemCuspBasis, HouseSystemClassification,
    classify_house_system, HousePolicy,
    HousePlacement, HouseBoundaryProfile,
    HouseAngularity, HouseAngularityProfile,
    HouseSystemComparison, HousePlacementComparison,
    HouseOccupancy, HouseDistributionProfile,
)
FunctionReturnsDescription
calculate_houses(jd_ut, latitude, longitude, system=HouseSystem.PLACIDUS, *, policy=None, ayanamsa_offset=None)HouseCuspsCompute house cusps and angles under explicit house policy when supplied
assign_house(longitude, cusps)HousePlacementFind which house a longitude falls in
describe_boundary(longitude, cusps, orb=2.0)HouseBoundaryProfileProximity to house cusp boundaries
describe_angularity(longitude, cusps, orb=5.0)HouseAngularityAngular/succedent/cadent classification
compare_systems(jd_ut, latitude, longitude, systems)HouseSystemComparisonSide-by-side comparison of multiple systems
compare_placements(body_lon, systems_cusps)HousePlacementComparisonHow a body's house changes across systems
distribute_points(longitudes, cusps)HouseDistributionProfileCount of points per house
classify_house_system(system)HouseSystemClassificationFamily, cusp basis, polar behavior for a recognized code; raises ValueError on unknown codes

House system families (HouseSystemFamily): ECLIPTIC_BASED EQUATORIAL SPACE_BASED TIME_BASED EQUAL_HOUSE

UnknownSystemPolicy: controls behavior when an unrecognized house system is passed — RAISE (raises ValueError) or FALLBACK_TO_PLACIDUS (silently returns Placidus). Set via HousePolicy.

PolarFallbackPolicy: controls behavior at polar latitudes where certain systems are not supported by default — FALLBACK_TO_PORPHYRY, RAISE, or EXPERIMENTAL_SEARCH. The experimental mode is explicit and currently attempts branch-aware high-latitude Placidus only. Set via HousePolicy.

Aspects

from moira.facade import (
    find_aspects, aspects_between, aspects_to_point,
    aspects_from_longitudes,
    find_declination_aspects, declination_aspects_from_declinations,
    declination_aspect_motion_witness,
    find_patterns, build_aspect_graph,
    aspect_strength, aspect_motion_state, aspect_harmonic_profile,
    AspectData, AspectPolicy, AspectStrength, DeclinationAspect,
    DeclinationAspectAnalysis, LongitudeAspectAnalysis,
    DeclinationAspectKind, DeclinationAspectMotionWitness,
    DeclinationAspectPolicy, DeclinationMotionState,
    AspectFamily, AspectDomain, AspectTier, MotionState,
    AspectGraph, AspectGraphNode, AspectFamilyProfile, AspectHarmonicProfile,
    CANONICAL_ASPECTS, DEFAULT_POLICY,
)

AspectData fields

FieldTypeDescription
body1strFirst body name
body2strSecond body name
aspectstrHuman name, e.g. "Conjunction", "Sextile"
symbolstrGlyph or short symbol for the aspect
anglefloatExact aspect angle in degrees, e.g. 0, 60, 90, 120, 180
separationfloatActual angular separation between the bodies
orbfloatNon-negative deviation from the exact aspect angle
allowed_orbfloatMaximum allowed orb for this aspect
applyingbool | NoneApplying/separating truth; None when speeds are unavailable
stationaryboolTrue if a stationary motion state affects the aspect
classificationAspectClassificationDomain, family, tier, motion state, and strength metadata
directionAspectDirection | NoneSinister/dexter casting direction from body1's perspective when defined
hellenistic_superiority_truthHellenisticSuperiorityTruth | NoneRaw direction-applicability and whole-sign overcoming receipt for the ordered body pair
sign_degree1int | NoneInteger degree number within body1's sign, used for strict partile truth
sign_degree2int | NoneInteger degree number within body2's sign, used for strict partile truth

Phase 3 introduced HellenisticAspectEvaluationStatus, HellenisticOvercomingRelation, HellenisticDirectionTruth, HellenisticOvercomingTruth, HellenisticSuperiorityTruth, and hellenistic_superiority_truth() in moira.aspects. AspectData.direction and overcoming() remain compatibility projections; the aggregate receipt is the source of raw truth and carries no synthetic score. Phase 4 forwards these exact objects through the root, classical, and facade exports; Moira.hellenistic_superiority_truth() delegates without recomputing or flattening the receipt.

AspectData convenience properties

PropertyTypeDescription
is_majorboolTrue when the aspect belongs to the major Ptolemaic tier
is_minorboolTrue when the aspect is not in the major tier
is_zodiacalboolTrue when the aspect domain is zodiacal
is_applyingboolTrue only when applying is True
is_separatingboolTrue only when applying is False
orb_surplusfloatRemaining orb headroom: allowed_orb - orb
is_partileboolTrue when a major Ptolemaic aspect has both bodies in the same degree number of their signs
is_platicboolTrue when a major Ptolemaic aspect is admitted but not partile

Core aspect functions

FunctionReturnsDescription
find_aspects(longitudes, orbs=None, include_minor=True, speeds=None, tier=None, orb_factor=1.0, policy=None)list[AspectData]Low-level detector over a longitude dict with optional speed and policy inputs
aspects_from_longitudes(longitudes, *, tier=1, orb_factor=1.0, include_nodes=True)LongitudeAspectAnalysisValidated first-class derived-position analysis; normalized deterministic inputs plus canonical AspectData results
aspects_between(lons_a, lons_b, orbs=None, include_minor=True)list[AspectData]Cross-set aspects (synastry / transits)
aspects_to_point(longitudes, point, orbs=None)list[AspectData]Aspects to a single longitude
find_declination_aspects(bodies_dec, orb=1.0)list[DeclinationAspect]Parallel and contra-parallel aspects
declination_aspects_from_declinations(bodies_dec, *, reference_frame, timescale, orb=1.0)DeclinationAspectAnalysisValidated deterministic caller-supplied declination analysis with explicit coordinate provenance
declination_aspect_motion_witness(body1, dec1, body2, dec2, aspect, *, speed1_deg_per_day=None, speed2_deg_per_day=None, orb=1.0, exact_tolerance_deg=1e-9, rate_tolerance_deg_per_day=1e-12, reference_frame, timescale)DeclinationAspectMotionWitnessInstantaneous signed declination error/rate witness for applying, exact, separating, stationary, or indeterminate truth
build_aspect_graph(aspects)AspectGraphGraph structure of the aspect network
aspect_strength(aspect)AspectStrengthGeometric orb exactness, or categorical whole-sign exactness
aspect_motion_state(aspect)MotionStateAPPLYING / EXACT / SEPARATING / STATIONARY / INDETERMINATE / NONE
aspect_harmonic_profile(longitudes, harmonic)AspectHarmonicProfileAspects visible at a given harmonic

aspects_from_longitudes and LongitudeAspectAnalysis are exported from both the curated package root and moira.facade. Moira.aspects_from_longitudes is the equivalent facade method. The analysis records normalized longitudes, the effective tier/orb multiplier, excluded engine node names, and motion_semantics="not_computed_without_speeds". Its 355°/5° wrap separation is 10°, and aspect admission remains inclusive at the applied orb boundary.

declination_aspects_from_declinations and DeclinationAspectAnalysis are likewise exported from the package root and moira.facade, with Moira.declination_aspects_from_declinations as the facade method. The analysis records normalized declinations, the effective orb, and the hemisphere-qualified Parallel/Contra-Parallel results. The caller must declare the shared equatorial reference_frame and timescale; Moira records rather than infers that provenance.

moira.declination_aspects is the governing module for these relationships. moira.aspects retains compatibility re-exports and adapts the historical AspectPolicy.declination_orb field to the new DeclinationAspectPolicy. declination_aspect_motion_witness and Moira.declination_aspect_motion_witness require declination rates to resolve applying or separating state. A single declination snapshot without rates is indeterminate unless already exact. Parallel motion uses the signed error dec1 - dec2; Contra-Parallel motion uses dec1 + dec2. The witness is instantaneous and does not claim future perfection before a reversal.

Aspect Patterns

from moira.facade import (
    find_all_patterns, find_t_squares, find_grand_trines, find_grand_crosses,
    find_yods, find_mystic_rectangles, find_kites, find_stelliums,
    find_minor_grand_trines, find_grand_sextiles, find_thors_hammers,
    find_boomerang_yods, find_wedges, find_cradles, find_trapezes,
    find_eyes, find_irritation_triangles, find_hard_wedges,
    find_dominant_triangles, find_grand_quintiles, find_quintile_triangles,
    find_septile_triangles,
    AspectPattern, PatternClassification,
)

The individual detectors accept their documented aspect list (or positions for Stellium) and orb policy. They return list[AspectPattern].

find_all_patterns(longitudes, aspects=None, orb_factor=1.0, include=None, policy=None, dominant_only=False) runs the registered detectors in one call. An explicit PatternComputationPolicy takes precedence over the legacy direct policy arguments. Moira.patterns(chart, orb_factor=1.0, dominant_only=False) exposes the same opt-in containment choice.

With dominant_only=True, a smaller aspect pattern is removed only when its body set and preserved aspect set are both contained in another admitted aspect pattern, with at least one of those inclusions strict. For example, an embedded Grand Trine is omitted when its Kite is admitted, and a same-body Trapeze edge-subgraph is omitted when its Cradle is admitted. Equal-body patterns with equal or incomparable edge sets remain; position-based Stelliums retain their independent maximal-cluster doctrine.

AspectPattern fields

FieldTypeDescription
namestrPattern name, e.g. "T-Square", "Grand Trine", "Yod"
bodieslist[str]Bodies participating in the pattern
aspectslist[AspectData]Aspects forming the pattern
apexstr | NoneApex body (for Yods, T-Squares, etc.)
classificationPatternClassificationPattern classification metadata
detection_truthPatternDetectionTruthDetection-trace metadata for the pattern
all_contributionslist[PatternAspectContribution]Full aspect/body contribution set
contributionslist[PatternAspectContribution]Primary contribution set used for display
condition_profilePatternConditionProfileConsolidated pattern condition profile

Pattern condition is structural role-resolution truth. reinforced means all preserved aspect contributions have detector-owned roles, mixed means at least one remains member_link, and weakened means there are no aspect contributions. These states do not report aspect motion, exactness, harmony, or interpretive strength. Grand Trine uses cycle_member/cycle_link; Minor Grand Trine uses base/support; and Cradle/Trapeze use axis/support roles without arbitrary left/right labels.

Chart shape (Jones types)

from moira.facade import classify_chart_shape, ChartShape, ChartShapeType

shape = classify_chart_shape(chart.longitudes(include_nodes=False))
# ChartShape(type, description, focal_point)

ChartShapeType constants: BUNDLE BOWL BUCKET LOCOMOTIVE FAN SEESAW SPLASH SPLAY

Midpoints

from moira.facade import calculate_midpoints, midpoints_to_point, Midpoint, MidpointsService

mps = calculate_midpoints(chart.longitudes(), orb=1.5)
# list[Midpoint(body1, body2, midpoint_lon, activated_by)]

hits = midpoints_to_point(chart.longitudes(), target_lon=15.0, orb=1.5)

# Using the service class for chained access:
svc = MidpointsService(chart.longitudes(), orb=1.5)
all_mps   = svc.all()               # list[Midpoint]
at_point  = svc.to_point(15.0)      # midpoints within orb of 15°
dial      = svc.dial_90()           # midpoints projected to 90° dial
tree      = svc.tree(15.0)          # midpoints equidistant from 15°

Harmonics

from moira.facade import (
    HARMONIC_PRESETS,
    HarmonicOrbPolicy,
    HarmonicPosition,
    HarmonicTransitSample,
    MixedOriginHarmonicTransitForecastPolicy,
    MixedOriginHarmonicTransitMode,
    calculate_harmonic,
    harmonic_conjunctions,
    mixed_origin_harmonic_transit_forecast,
)

natal = chart.longitudes(include_nodes=False)
h55 = calculate_harmonic(natal, 5.5)
# list[HarmonicPosition]; 5.5 is preserved and is not truncated to 5

# The orb is the configurable H1 reference and the projected-chart limit.
orb_policy = HarmonicOrbPolicy(reference_orb_deg=1.0)
truth = orb_policy.resolve(5.5)
# truth.projected_orb_limit_deg == 1.0
# truth.source_orb_limit_deg == 1.0 / 5.5

hits = harmonic_conjunctions(natal, 5.5, orb_policy=orb_policy)

# Sampled VA-informed forecast: caller owns the transit positions and JDs.
samples = (
    HarmonicTransitSample(2461000.0, {"Mars": 144.0, "Venus": 216.0}),
    HarmonicTransitSample(2461000.5, {"Mars": 144.1, "Venus": 216.1}),
)
forecast_policy = MixedOriginHarmonicTransitForecastPolicy(
    harmonics=(5, 7),  # forecast harmonics remain positive integers
    modes=(MixedOriginHarmonicTransitMode.ONE_TRANSIT_TWO_NATAL,),
    orb_policy=orb_policy,
    maximum_sample_gap_days=1.0,
)
forecast = mixed_origin_harmonic_transit_forecast(
    natal,
    samples,
    forecast_policy,
)

HARMONIC_PRESETS is dict[int, tuple[str, str]], mapping an integer harmonic to its descriptive name and summary, for example {4: ("Square", "Tension, challenges, action"), ...}.

Direct single-harmonic projection, conjunction, score, and composite functions accept any positive finite real H. Inputs are first reduced to the canonical zero-Aries [0, 360) branch and then projected as (normalized_longitude * H) mod 360. Integer H is the ordinary cyclic harmonic; fractional H is an explicitly origin-anchored continuous multiplier. Sweep, aspect-decoding, fingerprint, and transit-forecast harmonic collections remain integer.

HarmonicOrbPolicy exposes the Addey inverse-H relation O_H = O_1/H without double scaling. Its reference_orb_deg is applied as the projected harmonic chart limit; HarmonicOrbTruth.source_orb_limit_deg reports the locally equivalent source-circle allowance. The legacy positional orb remains available as an adapter to this same H1-reference policy, but an engine call may not supply both orb and orb_policy.

mixed_origin_harmonic_transit_forecast admits only complete triples containing one transit plus two natal members or two transits plus one natal member. A triple must fit inside one minimum circular covering arc. Returned window times are first/peak/last supplied samples: the function neither interpolates nor claims exact ingress, perfection, egress, or Sirius parity. See HARMONIC_TRANSIT_FORECAST_STANDARD.md for the complete doctrine.

Antiscia

from moira.facade import find_antiscia, antiscia_to_point, AntisciaAspect

antiscia = find_antiscia(chart.longitudes(), orb=1.0)
# AntisciaAspect(body1, body2, kind, orb)
# kind: "antiscion" (solstice axis) or "contra-antiscion" (equinox axis)

Void of Course Moon

from moira.facade import (
    void_of_course_window, is_void_of_course,
    next_void_of_course, void_periods_in_range,
    LastAspect, VoidOfCourseWindow,
)

voc = void_of_course_window(jd_ut)
# VoidOfCourseWindow(start_jd, end_jd, last_aspect, ingress_sign)

voc_periods = void_periods_in_range(jd_start, jd_end)

8. Classical Techniques

Dignities

from moira.facade import (
    calculate_dignities, calculate_receptions,
    calculate_condition_profiles, calculate_chart_condition_profile,
    calculate_condition_network_profile,
    PlanetaryDignity, EssentialDignityKind, AccidentalConditionKind,
    DignitiesService,
    sect_light, is_day_chart, almuten_figuris, find_phasis,
    is_in_hayz, is_in_sect,
)

Quick helpers

FunctionReturnsDescription
is_day_chart(sun_lon, asc_lon)boolTrue if Sun is above the horizon (diurnal sect)
sect_light(sun_lon, asc_lon)str"Sun" for day charts, "Moon" for night charts
is_in_hayz(planet, sun_lon, asc_lon, chart_lons)boolTrue if planet is in hayz
is_in_sect(planet, sun_lon, asc_lon)boolTrue if planet is in its preferred sect
almuten_figuris(chart_lons, cusps, is_day)strAlmuten figuris (planet with most dignities at ASC/MC/prenatal syzygy)
find_phasis(body, jd_start, jd_end, reader=None)list[float]JDs of phasis (first/last visibility) for a body

EssentialDignityKind values

DOMICILE EXALTATION TRIPLICITY TERM FACE DETRIMENT FALL PEREGRINE

AccidentalConditionKind values

DIRECT RETROGRADE STATIONARY ORIENTAL OCCIDENTAL CAZIMI COMBUST UNDER_BEAMS FREE_OF_BEAMS SWIFT SLOW IN_HAYZ OUT_OF_HAYZ

PlanetaryDignity fields

FieldTypeDescription
planetstrPlanet name
signstrSign occupied by the planet
degreefloatDegree within the sign
houseintHouse placement
essential_dignityEssentialDignityKindPrimary essential dignity/debility
essential_scoreintEssential dignity score
accidental_dignitieslist[AccidentalDignityCondition]Active accidental dignity conditions
accidental_scoreintAccidental dignity score
total_scoreintCombined dignity score
is_retrogradeboolRetrograde flag
receptionslist[PlanetaryReception]Active receptions involving the planet
condition_profilePlanetaryConditionProfileConsolidated dignity/condition profile
essential_truthEssentialDignityTruthEssential dignity computation truth data
accidental_truthAccidentalDignityTruthAccidental dignity truth data
sect_truthSectTruthSect evaluation truth data
solar_truthSolarConditionTruthSolar condition truth data
all_receptionslist[PlanetaryReception]Full reception set prior to filtering
mutual_reception_truthMutualReceptionTruthMutual reception truth data
essential_classificationEssentialDignityClassificationEssential dignity classification metadata
accidental_classificationAccidentalDignityClassificationAccidental dignity classification metadata
sect_classificationSectClassificationSect classification metadata
solar_classificationSolarConditionClassificationSolar condition classification metadata
reception_classificationReceptionClassificationReception classification metadata

Condition profiles & networks

profiles = calculate_condition_profiles(chart_lons, house_cusps, is_day)
# list[PlanetaryConditionProfile]

chart_profile = calculate_chart_condition_profile(chart_lons, house_cusps, is_day)
# ChartConditionProfile

network = calculate_condition_network_profile(chart_lons, house_cusps, is_day)
# ConditionNetworkProfile — graph of planetary condition relationships

Phase 4 forwards the typed essential-component, solar-phase, solar-proximity, besieging, horizon, Mercury-phase, and sect receipts through the root, classical, facade, and Moira surfaces. REST serializers preserve the same components with concrete OpenAPI models; policy suppression may remove an assembled label or score contribution but does not erase available raw geometry.

Unified Hellenistic chart profile

moira.hellenistic owns the Phase 5 non-interpretive composition surface. The public function is also exported unchanged through moira, moira.classical, and moira.facade.

from moira import HellenisticProfilePolicy, hellenistic_chart_profile

profile = hellenistic_chart_profile(
    natal_positions,
    natal_speeds,
    whole_sign_cusps,
    asc_longitude,
    mc_longitude,
    natal_dt,
    current_dt,
    policy=HellenisticProfilePolicy(),
    observer_latitude=latitude,
    observer_longitude=longitude,
    observer_elevation_m=elevation_m,
    position_frame=(
        "apparent_geocentric_true_ecliptic_of_date_"
        "positions_and_longitude_rates"
    ),
)

The raw function requires:

  • all seven classical planets and one finite speed for each;
  • twelve exact zodiac-boundary Whole Sign cusps;
  • the actual Ascendant and Midheaven, distinct from Whole Sign cusp 1/10;
  • timezone-aware natal and current datetimes, with current not before natal.

HellenisticChartProfile contains:

  • score-free planetary essential components, sect, joy, solar proximity, solar phase, besieging, receptions, Dorothean triplicity, admitted bound, and Chaldean-face receipts;
  • Whole Sign aspect classification and HellenisticSuperiorityTruth;
  • Fortune, Spirit, Valens Eros, and Valens Necessity computation, dependency-completeness, and astrological-condition truth;
  • annual profection plus typed activation truth;
  • current Decennial L1/L2 periods with sequence-assembly truth;
  • current Zodiacal Releasing periods for the selected foundational lot;
  • included/excluded component registries and complete composition provenance.

The composer never infers a frame from observer coordinates alone. A raw or facade caller must label its supplied position/rate frame; otherwise provenance marks that frame unverified. The REST composition service constructs the seven classical planets through the default apparent-geocentric, true-ecliptic-of-date position and longitude-rate product. Observer coordinates are used independently for the strict Whole Sign house figure and exact angles.

There is no chart-wide score, ranking, recommendation, or interpretation. Atomic non-evaluable states survive composition. The explicit exclusions are Firdaria, medieval almutens, later electional rules, unscoped primary directions, Decennial L3/L4, Hermetic-decan geometry, Valens distribution interpretation, and Triacontaeteris. These are closed/out-of-contract boundaries rather than incomplete profile fields.

Moira.hellenistic_chart_profile() accepts a previously constructed Chart and HouseCusps. It requires requested and effective Whole Sign systems with no fallback, forwards exact angles, derives engine/kernel provenance when available, and delegates to the owning function without recomputing doctrine.

Church of Light Natal Astrodynes

Astrodynes use a distinct Hermetic dignity table and must not be mixed with the conventional dignities above. The full stable low-level surface lives in moira.astrodynes; the root and facade expose the high-level constitutional vessels and chart builder.

from moira import (
    AstrodyneBodyInput,
    AstrodynePolicy,
    AstrodyneSummaryFamily,
    AstrodyneSummaryProfile,
    AstrodyneChartResult,
    astrodynes_summary,
    natal_astrodynes_from_geometry,
    natal_astrodynes,
)

result = natal_astrodynes(
    body_inputs,                 # ten planets plus M.C. and Asc.
    cusp_signs,                  # twelve cusp signs in house order
    intercepted_signs_by_house={2: ("Gemini",)},
)
SurfaceReturnsDescription
natal_astrodynes(body_inputs, cusp_signs, *, intercepted_signs_by_house=None, policy=None)AstrodyneChartResultFull natal profiles, relations, aggregates, Class 5 summaries, network, and checksum truth
natal_astrodynes_from_geometry(planet_longitudes, declinations, cusp_longitudes, mc_longitude, asc_longitude, *, policy=None)AstrodyneChartResultDerive body inputs, houses, interpolation geometry, cusp signs, and interceptions from a complete explicit tropical figure
astrodynes_summary(aggregate)AstrodyneSummaryProfileSociety, trinity, element, and quality partitions of an existing chart aggregate
validate_astrodynes_output(result)tuple[str, ...]Deterministic cross-layer invariant failures
Moira.astrodynes(...)AstrodyneChartResultKernel-free facade delegate
Moira.astrodynes_from_geometry(...)AstrodyneChartResultKernel-free explicit-geometry facade delegate

AstrodyneBodyInput preserves longitude, optional declination, house number, house class, and the house-interpolation geometry required by the manual. Full-chart assembly requires the ten planets plus M.C. and Asc..

Principal result layers:

TypeMeaning
AstrodyneRelationSetDetected, admitted, and scored zodiacal, parallel, and mutual-reception relations
AstrodyneBodyConditionProfileIntegrated power, harmony, discord, dignity, and named contributions for one body
AstrodyneSignAggregateAverage-ruler share plus occupants for one sign
AstrodyneHouseAggregateCusp/interception ruler shares plus house occupants
AstrodyneChartAggregateTwelve signs, twelve houses, and sign/house checksums
AstrodyneSummaryEntryOne named Class 5 group with members, power, percentage, and harmony/discord truth
AstrodyneSummaryProfileSociety, trinity, element, and quality families over one chart total
AstrodyneNetworkNodes aligned with body profiles and edges aligned with admitted relations
AstrodyneChartResultPolicy, inputs, relations, profiles, aggregate, summary, and network

The only admitted AstrodynePolicy is the confirmed Church of Light doctrine: one-degree exaltation/fall emphasis, 60-arcminute magnitude parallels, Mercury's ordinary presence orb plus luminary scoring orb, and a +5 mutual reception bonus. Unsupported alternatives raise ValueError.

See ASTRODYNES_BACKEND_STANDARD for the full doctrine, constitutional layers, invariants, and validation boundary.

Arabic Parts / Lots

from moira.facade import (
    calculate_lots, evaluate_lots,
    calculate_lot_dependencies, calculate_all_lot_dependencies,
    calculate_lot_condition_profiles, calculate_lot_chart_condition_profile,
    calculate_lot_condition_network_profile,
    ArabicPart, LotsEvaluation, LotNotEvaluable,
    ArabicPartsService, list_parts, LotReversalKind,
)
FunctionReturnsDescription
calculate_lots(lons, cusps, is_day, *, asc_longitude=None, mc_longitude=None, ...)list[ArabicPart]All classical Arabic Parts; explicit angles are authoritative when supplied
evaluate_lots(lons, cusps, is_day, *, asc_longitude=None, mc_longitude=None, ...)LotsEvaluationComputed parts plus typed not_evaluable catalogue entries; explicit angles remain distinct from house cusps
list_parts()list[str]Names of all available parts

ArabicPart fields

FieldTypeDescription
namestrPart name, e.g. "Fortune", "Spirit"
longitudefloatEcliptic longitude (degrees)
formulastrFormula used to derive the part
categorystrPart category/classification label
descriptionstrShort textual description
computation_truthArabicPartComputationTruthTruth data for the part computation
classificationArabicPartClassificationClassification metadata
all_dependencieslist[LotDependency]Full dependency graph slice for the part
dependencieslist[LotDependency]Direct dependencies used by the part
dependency_completenessLotDependencyCompletenessTruthSeparate dependency-resolution receipt
astrological_condition_truthLotAstrologicalConditionTruthSeparate condition boundary; currently not_evaluable without admitted doctrine
condition_profileLotConditionProfileComputed condition profile
signstrSign occupied by the part
sign_symbolstrSign glyph/symbol
sign_degreefloatDegree within the sign

Using ArabicPartsService

svc    = ArabicPartsService(lons, cusps, is_day)
fortune = svc.fortune()    # ArabicPart
spirit  = svc.spirit()
exalt   = svc.exaltation()

Phase 4 makes evaluate_lots and its aggregate/status vessels identical objects across root, classical, and facade imports, with Moira.evaluate_lots() as the chart-backed delegate. The REST chart route uses this lossless aggregate and never treats an unresolved entry as an evaluated absence.

Profections

from moira.facade import (
    annual_profection, monthly_profection, profection_chronology,
    profection_schedule, LeapDayAnniversaryPolicy,
    MonthlyProfectionIntervalPolicy, ProfectionAmbiguousTimePolicy,
    ProfectionResult,
)

result = profection_schedule(
    natal_asc_lon,
    natal_dt,
    current_dt,
    civil_timezone="America/New_York",
    activation_orb=0.75,
    leap_day_policy=LeapDayAnniversaryPolicy.FEBRUARY_28,
)
# result.age_years, result.profected_house, result.lord_of_year
FunctionReturnsDescription
annual_profection(natal_asc, age_years, natal_positions=None, activation_orb=5.0)ProfectionResultWhole-sign annual profection for an explicit completed age
monthly_profection(natal_asc, age_years, month_index)tuple[float, str, str]Monthly subdivision for an explicit age and month index
profection_chronology(natal_asc, natal_dt, current_dt, *, civil_timezone=None, leap_day_policy=None, ambiguous_time_policy=None, interval_policy=...)ProfectionChronologyExact civil-anniversary anchors and twelve engine-owned dated monthly intervals
profection_schedule(natal_asc, natal_dt, current_dt, natal_positions=None, *, civil_timezone=None, leap_day_policy=None, ambiguous_time_policy=None, interval_policy=..., activation_orb=5.0)ProfectionResultCivil-anniversary age, activation truth, and query-specific monthly chronology

Both chronology functions require timezone-aware datetimes and reject pre-birth instants. REST-normalized UTC callers should supply an authoritative IANA civil_timezone; the exact timezone-data source and version are preserved in the receipt. February 29 nativities require an explicit february_28 or march_1 policy. A repeated local anniversary requires explicit earlier_occurrence or later_occurrence; no fold is guessed.

The admitted interval policy divides the exact elapsed UTC duration between consecutive local civil anniversaries into twelve contiguous, half-open intervals whose lengths differ by at most one microsecond. The method is explicitly classified as a computational projection. It is not fixed 30-day arithmetic, a 365.25-day quotient, civil-calendar months, or Valens IV.28's separate luminary-distance method. annual_profection() has no query chronology and returns chronology=None.

Phase 3 introduced ProfectionActivationStatus, ProfectionActivationBodyTruth, ProfectionActivationTruth, and profection_activation_truth() in moira.profections. ProfectionResult.activation_truth distinguishes absent natal positions (not_evaluable, reason="natal_positions_not_supplied") from an explicitly supplied empty mapping and from an evaluated chart with no activations. The legacy activated_planets list is derived from evaluated raw truth and remains empty in all three cases. Phase 4 forwards those names through root, classical, and facade, adds the raw Moira helper, and preserves activation_orb through Moira.profection(), schedule service composition, REST serialization, and OpenAPI.

Nakshatras (Vedic lunar mansions)

from moira.facade import nakshatra_of, all_nakshatras_at, NakshatraPosition

pos = nakshatra_of(moon_longitude, jd_ut, ayanamsa_system=Ayanamsa.LAHIRI)
# NakshatraPosition(name, number, pada, lord, remaining_fraction)

all_naks = all_nakshatras_at(chart.longitudes(include_nodes=False), jd_ut)
# dict[str, NakshatraPosition]

Arabic Lunar Mansions (Manazil)

from moira.facade import mansion_of, all_mansions_at, moon_mansion, MansionPosition, MANSIONS

pos = mansion_of(moon_longitude)
# MansionPosition(number, name, start_lon, end_lon, ruling_planet)

moon_man = moon_mansion(moon_longitude)   # same, convenience alias
all_m    = all_mansions_at(chart.longitudes())

MANSIONS: tuple of 28 MansionInfo entries.

Longevity (Hyleg / Alcocoden)

from moira.facade import find_hyleg, calculate_longevity, HylegResult

hyleg = find_hyleg(chart_lons, cusps, is_day)
# HylegResult(hyleg, alcocoden, projected_years)

result = calculate_longevity(chart_lons, cusps, is_day)
print(result.projected_years)

Gauquelin sectors

See Section 4 (Ephemeris & Positions).

Planetary Hours (moira.planetary_hours)

from moira.facade import planetary_hours, PlanetaryHoursDay, PlanetaryHour

day = planetary_hours(jd_ut, latitude, longitude, reader=None)
# PlanetaryHoursDay(date, day_hours: list[PlanetaryHour], night_hours: list[PlanetaryHour])
# PlanetaryHour(ruler, start_jd, end_jd)

Note: This is moira.planetary_hours.PlanetaryHour. A distinct vessel, moira.cycles.PlanetaryHour, is documented in Section 10 with additional fields (hour_number, is_day_hour). The two classes serve different engines and must not be conflated.

Varga (Vedic divisional charts)

from moira.facade import calculate_varga, navamsa, saptamsa, dashamansa, dwadashamsa, trimshamsa

d9  = navamsa(longitude)          # D9  — ninth division
d7  = saptamsa(longitude)         # D7  — seventh division
d10 = dashamansa(longitude)       # D10 — tenth division
d12 = dwadashamsa(longitude)      # D12 — twelfth division
d30 = trimshamsa(longitude)       # D30 — thirtieth division

# Generic:
pos = calculate_varga(longitude, divisor=9)
# VargaPoint(divisor, position_in_sign, sign_number, sign_name)

Decanates

from moira.facade import chaldean_face, triplicity_decan, vedic_drekkana, DecanatePosition

face = chaldean_face(longitude)
trip = triplicity_decan(longitude)
d3   = vedic_drekkana(longitude, jd, ayanamsa_system=Ayanamsa.LAHIRI)
FunctionReturnsDescription
chaldean_face(longitude)DecanatePositionClassical Chaldean face for a tropical longitude
triplicity_decan(longitude)DecanatePositionWestern triplicity decan for a tropical longitude
vedic_drekkana(longitude, jd, ayanamsa_system=Ayanamsa.LAHIRI)DecanatePositionVedic D3 drekkana for a sidereally normalized longitude

DecanatePosition fields

FieldTypeDescription
systemstrDecan system name: chaldean_face, triplicity, or vedic_drekkana
decan_numberintDecan number within the sign, 1-3
ruling_planetstrPlanetary ruler of the decan
ruling_signstr | NoneGoverning sign for triplicity or Vedic drekkana; None for Chaldean face
signstrZodiac sign containing the longitude used
sign_symbolstrZodiac sign glyph/symbol
degree_in_decanfloatDegrees elapsed within the 10° decan span
longitude_usedfloatLongitude actually classified after any required normalization

Hermetic Decans — Research-Only Closed Product Exclusion

The names, sign order, planetary faces, and source pages in moira.hermetic_decans are reconstructed from Gundel's 1936 edition of the Harley MS 3731 list. The source supports Aries-starting 10-degree segmentation; the module's modern tropical-frame and rising projections remain unadmitted, and the unsupported fixed-star table fails closed. The former decan_hours() experiment and its result vessels have been removed. Nothing from this module is exported from moira or moira.facade; direct import remains a research surface, not part of the supported Python API contract.

This is a completed boundary, not unfinished Hellenistic engine work.


9. Timing Techniques

Transits

from moira.facade import (
    find_transits, next_transit, find_ingresses, next_ingress, next_ingress_into,
    solar_return, lunar_return, planet_return,
    last_new_moon, last_full_moon, prenatal_syzygy,
    transit_relations, ingress_relations,
    transit_condition_profiles, ingress_condition_profiles,
    transit_chart_condition_profile, transit_condition_network_profile,
    TransitEvent, IngressEvent, TransitSearchPolicy, TransitComputationPolicy,
)

TransitEvent fields

FieldTypeDescription
bodystrTransiting body
longitudefloatExact longitude of the event
jd_utfloatJD UT of the exact transit
directionstrSearch direction / crossing direction
computation_truthTransitComputationTruthSearch/computation truth data
classificationTransitComputationClassificationTransit classification metadata
relationTransitRelationTarget relation metadata
condition_profileTransitConditionProfileTransit condition profile

IngressEvent fields

FieldTypeDescription
bodystrBody entering the sign
signstrSign entered
jd_utfloatJD UT of the ingress
directionstrIngress direction
computation_truthIngressComputationTruthSearch/computation truth data
classificationIngressComputationClassificationIngress classification metadata
relationTransitRelationSign-ingress relation metadata
condition_profileTransitConditionProfileIngress condition profile

Core functions

events = find_transits(Body.SATURN, natal_sun_lon, jd_start, jd_end, reader=reader)
ev     = next_transit(Body.JUPITER, natal_moon_lon, jd_now, reader=reader)

ingr   = find_ingresses(Body.SATURN, jd_start, jd_end, reader=reader)
next_i = next_ingress(Body.JUPITER, jd_now, reader=reader)
into   = next_ingress_into(Body.SATURN, "Aquarius", jd_now, reader=reader)

jd_sr  = solar_return(natal_sun_lon, year=2025, reader=reader)
jd_lr  = lunar_return(natal_moon_lon, jd_now, reader=reader)
jd_pr  = planet_return(Body.JUPITER, natal_jup_lon, jd_now, reader=reader)
jd_nm  = last_new_moon(jd_now, reader=reader)
jd_fm  = last_full_moon(jd_now, reader=reader)
jd_syn, kind = prenatal_syzygy(jd_natal, reader=reader)

Stations & Retrograde

from moira.facade import find_stations, next_station, is_retrograde, retrograde_periods, StationEvent

stations = find_stations(Body.MARS, jd_start, jd_end, reader=reader)
# StationEvent(jd, body, kind)  kind: "retrograde" | "direct"

retro_intervals = retrograde_periods(Body.MERCURY, jd_start, jd_end, reader=reader)
# list[(jd_start, jd_end)]

Progressions & Directions

All progression functions share the signature: (jd_natal, target_dt, bodies=None, reader=None) → ProgressedChart

from moira.facade import (
    secondary_progression, solar_arc, solar_arc_right_ascension,
    naibod_longitude, naibod_right_ascension,
    tertiary_progression, tertiary_ii_progression,
    minor_progression, ascendant_arc, daily_houses,
    converse_secondary_progression, converse_solar_arc,
    converse_solar_arc_right_ascension,
    converse_naibod_longitude, converse_naibod_right_ascension,
    converse_tertiary_progression, converse_tertiary_ii_progression,
    converse_minor_progression,
    ProgressedChart, ProgressedPosition,
    ProgressionTimeKeyPolicy, ProgressionDirectionPolicy,
    ProgressionComputationPolicy,
)
TechniqueFunctionKey rate
Secondary Progressionsecondary_progression1 day = 1 year
Solar Arcsolar_arcSun's progressed daily motion applied to all bodies
Solar Arc (RA)solar_arc_right_ascensionSolar Arc in right ascension
Naibod (longitude)naibod_longitude0°59′08″/year
Naibod (RA)naibod_right_ascensionNaibod in right ascension
Tertiarytertiary_progression1 day = 1 lunar month
Tertiary IItertiary_ii_progressionKlaus Wessel variant
Minorminor_progression1 lunar month = 1 year
Ascendant Arcascendant_arcASC arc applied to all bodies

All converse variants (moving backward) are prefixed with converse_.

ProgressedChart fields

FieldTypeDescription
chart_typestrProgression technique identifier
natal_jd_utfloatNatal JD UT
progressed_jd_utfloatProgressed JD UT used for the positions
target_datedatetimeTarget date requested by the user
solar_arc_degfloatSolar arc applied when relevant
positionsdict[str, ProgressedPosition]Body → progressed position
computation_truthProgressionComputationTruthProgression computation truth data
classificationProgressionComputationClassificationProgression classification metadata
relationProgressionRelationRelation metadata for natal/progressed comparison
condition_profileProgressionConditionProfileConsolidated progression profile

ProgressedPosition: longitude, latitude, speed, natal_longitude.

Primary Directions

from moira.facade import speculum, find_primary_arcs, SpeculumEntry, PrimaryArc, DIRECT, CONVERSE

spec  = speculum(chart, houses, geo_lat=51.5)
arcs  = find_primary_arcs(chart, houses, geo_lat=51.5, max_arc=90.0, include_converse=True)
# list[PrimaryArc(significator, promissor, arc, direction, method, space,
#                 motion, solar_rate, relational_kind)]
# arc.years()             → years by key "naibod" (default)
# arc.years("ptolemy")    → years by Ptolemy key
# arc.solar_rate_explicit → whether solar_rate is natal/generated provenance

PrimaryArc.relational_kind is the actual positional relation. Relation profiles retain the historical relation_kind field for perfection kind and provide perfection_kind as its explicit alias. The solar key is a static conversion by one explicit positive natal solar rate; it is not a dynamic integration and fails closed without that rate. For compatibility, PrimaryArc.solar_rate remains numeric when a caller omits the rate, but solar_rate_explicit is then False and solar-key conversion does not treat that value as natal provenance. Engine-generated arcs and explicitly supplied rates report True.

Method/space capability is enforced: PLACIDUS_MUNDANE and PLACIDIAN_CLASSIC_SEMI_ARC are not accepted with IN_ZODIACO. Placidian-classic geometry uses the equatorial horizon identity OA(ASC) = (ARMC + 90 degrees) mod 360. Fixed-star targets require conjunction admission, while rapt-parallel motion remains specific to the configured rapt relation/target. Aspectual points sourced from a house cusp materialize that cusp before projection. Supplied Morinus aspect contexts are normalized and unique by exact source identity.

Relation vessels bind perfection kind to the arc's space. Relation profiles must contain relations owned by their stated arc, and significator profiles preserve arc/profile order one-for-one. The method, perfection, relation, and target ordered transition-network vessels additionally require a connected, degree-valid Euler path or lawfully linearizable circuit; aggregate counts alone cannot attest a possible sequence.

Firdaria (Persian Time Lords)

from moira.facade import (
    firdaria, current_firdaria, group_firdaria,
    firdar_condition_profile, firdar_sequence_profile, firdar_active_pair,
    validate_firdaria_output,
    FirdarPeriod, FirdarMajorGroup, FirdarConditionProfile,
    FirdarSequenceProfile, FirdarActivePair,
    FirdarSequenceKind, FirdarYearPolicy, TimelordComputationPolicy,
    DEFAULT_TIMELORD_POLICY,
    FIRDARIA_DIURNAL, FIRDARIA_NOCTURNAL, FIRDARIA_NOCTURNAL_BONATTI,
    CHALDEAN_ORDER, MINOR_YEARS,
)
FunctionReturnsDescription
firdaria(jd_natal, is_day, policy=None)list[FirdarPeriod]Full Firdaria sequence from birth
current_firdaria(jd_natal, jd_now, is_day, policy=None)FirdarPeriodActive Firdaria period at jd_now
group_firdaria(periods)list[FirdarMajorGroup]Periods grouped by major lord
firdar_condition_profile(period, chart_lons)FirdarConditionProfileCondition analysis for one period
firdar_sequence_profile(jd_natal, is_day, jd_now)FirdarSequenceProfileFull condition profile across sequence
firdar_active_pair(jd_natal, jd_now, is_day)FirdarActivePairMajor + minor lord pair at jd_now

FirdarPeriod fields (moira.timelords)

This is moira.timelords.FirdarPeriod. A distinct vessel, moira.cycles.FirdarPeriod, is documented in Section 10 with different fields (ruler, start_jd, end_jd, duration_years, ordinal, sub_periods). The two classes serve different engines and must not be conflated.

FieldTypeDescription
levelintPeriod level
planetstrActive period lord
start_jdfloatStart JD
end_jdfloatEnd JD
yearsfloatDuration in years
major_planetstrParent major lord
is_day_chartboolTrue for diurnal sect sequence
variantstrVariant used for the sequence
sequence_kindFirdarSequenceKindSequence family metadata
is_node_periodboolWhether the period belongs to the nodal sequence

Decennials

from moira.facade import (
    decennials, current_decennials, group_decennials,
    decennial_condition_profile, decennial_sequence_profile,
    decennial_active_pair, decennial_active_path,
    validate_decennials_output,
    DecennialPeriod, DecennialPolicy, DecennialTimeBasis,
)
FunctionReturnsDescription
decennials(jd_natal, natal_positions, is_day_chart, levels=2, policy=None)list[DecennialPeriod]Admitted L1/L2 sequence from the sect light
current_decennials(..., jd_now, levels=2, policy=None)tuple[DecennialPeriod, DecennialPeriod]Active major and L2 period
group_decennials(periods)list[DecennialMajorGroup]Major periods with their L2 children
decennial_active_path(periods, jd)DecennialActivePath | NoneActive admitted L1/L2 lineage

Phase 3 introduced TimelordEvaluationStatus, DecennialSequenceBodyTruth, DecennialSequenceAssemblyTruth, and decennial_sequence_truth() in moira.timelords. The assembly receipt preserves the Classic 7 dependency geometry and fails closed on a non-sect-light longitude tie instead of using private planet order. Every generated period carries the same evaluated sequence_truth. Phase 4 forwards the exact raw types/function through root, classical, facade, and Moira, and serializes the receipt on every REST period with a concrete OpenAPI schema.

The public engine accepts only levels 1–2. Any L3/L4 request or non-None DecennialPolicy.deep_subdivision_method fails closed; the named Valens and Hephaistio deep policies are closed exclusions, not admitted API behavior or release backlog. The REST request schema exposes no deep-method selector.

Every period preserves time_basis, calendar_projection_basis, sequence_origin_jd, start_distribution_day, end_distribution_day, and distribution_years. start_jd/end_jd are elapsed-day projections from the natal instant; they do not mean that schematic 30-day months were added as civil calendar months.

Zodiacal Releasing

from moira.facade import (
    zodiacal_releasing, current_releasing, group_releasing,
    zr_condition_profile, zr_sequence_profile, zr_level_pair,
    validate_releasing_output,
    ReleasingPeriod, ZRPeriodGroup, ZRConditionProfile,
    ZRSequenceProfile, ZRLevelPair,
    ZRAngularityClass, ZRYearPolicy,
)
FunctionReturnsDescription
zodiacal_releasing(lot_lon, jd_natal, levels=4, *, lot_name="Spirit", fortune_longitude=None, use_loosing_of_bond=True, policy=None)list[ReleasingPeriod]Full ZR sequence with explicit Lot, Fortune, bond, and time-basis policy
current_releasing(lot_lon, jd_natal, jd_now, fortune_longitude=None)list[ReleasingPeriod]Active period at each available level; rejects the exact 211-symbolic-year endpoint and later
group_releasing(periods)list[ZRPeriodGroup]Grouped by Level 1 sign
zr_level_pair(lot_lon, jd_natal, jd_now)ZRLevelPairActive Level 1 + Level 2 pair

zr_sequence_profile(periods, level) rejects an empty input and a requested level absent from the generated period list. REST callers must keep profile_level <= levels.

ReleasingPeriod fields

FieldTypeDescription
levelintPeriod level (1-4)
signstrReleasing sign
rulerstrSign ruler
start_jdfloatStart JD
end_jdfloatEnd JD
yearsfloatPeriod length in years
lot_namestrLot used for the releasing sequence
is_loosing_of_bondboolWhether the period begins with a Loosing of the Bond
is_peak_periodboolTrue only in places 1, 4, 7, or 10 from Fortune
angularity_from_fortuneint | NoneInclusive place 1–12 from Fortune; None when Fortune is omitted
use_loosing_of_bondboolWhether Loosing of the Bond is enabled
angularity_classZRAngularityClass | NoneAngular / succedent / cadent class; None only when Fortune is omitted
fortune_angularity_truthZRFortuneAngularityTruth | NoneRaw Fortune dependency, place, class, and peak receipt

zr_fortune_angularity_truth() and ZRFortuneAngularityTruth are direct moira.timelords Phase 3 surfaces. With no Fortune, raw status is not_evaluable and raw peak truth is None; the legacy ReleasingPeriod.is_peak_period projection remains False. Phase 4 forwards the raw helper/type through root, classical, facade, and Moira; every REST period now carries the typed Fortune-angularity receipt.

Vimshottari Dasha

from moira.facade import (
    vimshottari, current_dasha, dasha_balance,
    dasha_active_line, dasha_condition_profile, dasha_sequence_profile,
    dasha_lord_pair, validate_vimshottari_output,
    DashaPeriod, DashaActiveLine, DashaConditionProfile,
    DashaSequenceProfile, DashaLordPair, DashaLordType,
    VimshottariComputationPolicy, DEFAULT_VIMSHOTTARI_POLICY,
    VIMSHOTTARI_YEARS, VIMSHOTTARI_SEQUENCE, VIMSHOTTARI_TOTAL,
    VIMSHOTTARI_YEAR_BASIS, VIMSHOTTARI_LEVEL_NAMES,
)
FunctionReturnsDescription
vimshottari(moon_tropical_lon, natal_jd, levels=2, ayanamsa_system=None, *, year_basis=None, policy=None)list[DashaPeriod]Full Vimshottari sequence
current_dasha(moon_tropical_lon, natal_jd, current_jd, ayanamsa_system=None, *, year_basis=None, levels=5, policy=None)list[DashaPeriod]One active period per generated level
dasha_balance(moon_tropical_lon, natal_jd, ayanamsa_system=None, *, year_basis=None, policy=None)tuple[str, float]Natal Mahadasha lord and remaining balance in Vimshottari years
dasha_active_line(active_periods)DashaActiveLineNamed active chain from the list returned by current_dasha
dasha_condition_profile(period)DashaConditionProfileCondition profile for one generated period
dasha_sequence_profile(periods)DashaSequenceProfileAggregate profile for a generated sequence
dasha_lord_pair(line)DashaLordPairMahadasha + Antardasha lords from a DashaActiveLine

DashaPeriod fields

FieldTypeDescription
levelint1 = Mahadasha, 2 = Antardasha, 3 = Pratyantardasha
planetstrDasha lord (planet name)
start_jdfloatStart JD
end_jdfloatEnd JD
year_daysfloatDuration expressed in days/year-basis units
sublist[DashaPeriod]Nested sub-periods (if levels > 1)
year_basisstrYear basis used for the sequence
birth_nakshatrastrNatal Moon nakshatra
nakshatra_fractionfloatFraction of nakshatra elapsed at birth
lord_typeDashaLordTypeLord classification metadata

VIMSHOTTARI_YEARS: dict of lord → years (Ketu=7, Venus=20, Sun=6, ...).


Ashtottari & Yogini Dasha

from moira import (
    ashtottari, yogini_dasha,
    AlternateDashaPeriod, AlternatePeriodProfile, AlternateDashaSequenceProfile,
    AshtottariPolicy, YoginiPolicy,
    validate_alternate_dasha_output,
)
FunctionReturnsDescription
ashtottari(moon_tropical_lon, natal_jd, levels=2, policy=None)list[AlternateDashaPeriod]Full Ashtottari dasha sequence
yogini_dasha(moon_tropical_lon, natal_jd, levels=2, policy=None)list[AlternateDashaPeriod]Full Yogini dasha sequence
alternate_period_profile(period)AlternatePeriodProfileInspect one alternate-dasha period
alternate_sequence_profile(periods)AlternateDashaSequenceProfileAggregate profile for an alternate-dasha sequence
validate_alternate_dasha_output(periods)NoneValidate alternate-dasha output structure

AlternateDashaPeriod fields

FieldTypeDescription
systemstrDasha system name
levelintPeriod level within the nested sequence
lordstrPeriod lord
start_jdfloatStart JD
end_jdfloatEnd JD
sublist[AlternateDashaPeriod]Nested sub-periods

AlternatePeriodProfile fields

FieldTypeDescription
systemstrDasha system name
levelintPeriod level
lordstrPeriod lord name
planetstrNormalized planetary identity of the lord
yearsfloatNominal period length in years
is_node_lordboolTrue when the lord is a node
is_luminary_lordboolTrue when the lord is Sun or Moon

AlternateDashaSequenceProfile fields

FieldTypeDescription
systemstrDasha system name
total_yearsintTotal sequence span in years
mahadasha_countintNumber of top-level periods
profileslist[AlternatePeriodProfile]Profile for each Mahadasha lord

Alternate Dasha policy & constants

Public symbolKindDescription
AshtottariPolicydataclassyear_basis, ayanamsa_system, bypass_eligibility, lagna_sign_index
YoginiPolicydataclassyear_basis, ayanamsa_system
ASHTOTTARI_YEARSdict[str, int]Ashtottari lord → years table
ASHTOTTARI_SEQUENCEtuple[str, ...]Ashtottari lord order
ASHTOTTARI_NAKSHATRA_LORDdict[int, str]Nakshatra-index → Ashtottari lord mapping
ASHTOTTARI_TOTALintTotal Ashtottari cycle years
YOGINI_YEARSdict[str, int]Yogini lord → years table
YOGINI_SEQUENCEtuple[str, ...]Yogini lord order
YOGINI_PLANETSdict[str, str]Yogini name → planetary identity mapping
YOGINI_TOTALintTotal Yogini cycle years

Panchanga

from moira import (
    panchanga_at, tithi_condition_profile, panchanga_profile,
    PanchangaResult, TithiConditionProfile, PanchangaProfile, PanchangaPolicy,
    TithiPaksha, YogaClass, KaranaType, VaraLordType,
    validate_panchanga_output,
)
FunctionReturnsDescription
panchanga_at(sun_tropical_lon, moon_tropical_lon, jd, ayanamsa_system=Ayanamsa.LAHIRI, policy=None)PanchangaResultCompute tithi, nakshatra, yoga, karana, and vara for a moment
tithi_condition_profile(result)TithiConditionProfileTithi waxing/waning and condition profile
panchanga_profile(result)PanchangaProfileAggregate Panchanga condition summary
validate_panchanga_output(result)NoneValidate Panchanga result invariants

PanchangaResult fields

FieldTypeDescription
jdfloatJulian day of the computation moment
tithiPanchangaElementTithi element
varaPanchangaElementWeekday element
vara_lordstrPlanetary lord of the weekday
nakshatraobjectNakshatra result for the Moon
yogaPanchangaElementYoga element
karanaPanchangaElementKarana element
ayanamsa_systemstrGoverning ayanamsa system

TithiConditionProfile fields

FieldTypeDescription
tithi_namestrTithi name
tithi_indexintZero-based tithi index
tithi_numberintTraditional tithi number
pakshastrWaxing or waning half
is_purnimaboolTrue at Full Moon tithi
is_amavasyaboolTrue at New Moon tithi
degrees_elapsedfloatDegrees elapsed within the tithi
degrees_remainingfloatDegrees remaining in the tithi

PanchangaProfile fields

FieldTypeDescription
jdfloatJulian day of the computation moment
pakshastrWaxing or waning half
is_purnimaboolFull Moon flag
is_amavasyaboolNew Moon flag
yoga_classstrClassified yoga family
karana_typestrClassified karana family
vara_lordstrWeekday lord
vara_lord_typestrBenefic/malefic or related lord classification
ayanamsa_systemstrGoverning ayanamsa system

PanchangaElement fields

FieldTypeDescription
namestrElement name
indexintZero-based element index
numberintTraditional 1-based element number
degrees_elapsedfloatDegrees elapsed within the element
degrees_remainingfloatDegrees remaining in the element

Panchanga policy & constants

Public symbolKindDescription
PanchangaPolicydataclassayanamsa_system
TithiPakshaenum-like classSHUKLA, KRISHNA
YogaClassenum-like classAUSPICIOUS, INAUSPICIOUS
KaranaTypeenum-like classMOVABLE, FIXED
VaraLordTypeenum-like classLUMINARY, INNER, OUTER
TITHI_NAMEStuple[str, ...]Traditional tithi names
YOGA_NAMEStuple[str, ...]Traditional yoga names
KARANA_NAMEStuple[str, ...]Traditional karana names
VARA_LORDStuple[str, ...]Weekday lord sequence
VARA_NAMEStuple[str, ...]Weekday names

Jaimini Karakas

from moira import (
    jaimini_karakas, atmakaraka, karaka_condition_profile,
    jaimini_chart_profile, karaka_pair,
    JaiminiKarakaResult, KarakaConditionProfile, JaiminiChartProfile, KarakaPair,
    JaiminiPolicy, validate_jaimini_output,
)
FunctionReturnsDescription
jaimini_karakas(sidereal_longitudes, scheme=7, policy=None)JaiminiKarakaResultAssign Jaimini karaka roles from sidereal longitudes
atmakaraka(sidereal_longitudes, scheme=7)strPlanet holding the Atmakaraka role
karaka_condition_profile(assignment, scheme)KarakaConditionProfileInspect one karaka assignment in context
jaimini_chart_profile(result)JaiminiChartProfileAggregate Jaimini karaka chart profile
karaka_pair(result, role_a, role_b)KarakaPairCompare two named karaka roles
validate_jaimini_output(result)NoneValidate Jaimini assignment output

JaiminiKarakaResult fields

FieldTypeDescription
assignmentslist[KarakaAssignment]Ordered karaka assignments
schemeint7- or 8-karaka assignment scheme
atmakarakastrPlanet holding the Atmakaraka role
tie_warningslist[tuple[str, str]]Tie diagnostics emitted during assignment

KarakaConditionProfile fields

FieldTypeDescription
karaka_namestrKaraka role name
karaka_rankintPositional rank in the assignment order
planetstrAssigned planet
planet_typestrPlanet or node type classification
degree_in_signfloatDegrees traversed within the sign
sidereal_longitudefloatFull sidereal longitude
is_rahu_invertedboolTrue when Rahu inversion governs the assignment
is_atmakarakaboolTrue for the Atmakaraka
is_darakarakaboolTrue for the Darakaraka

JaiminiChartProfile fields

FieldTypeDescription
schemeint7- or 8-karaka assignment scheme
atmakaraka_planetstrAtmakaraka planet
darakaraka_planetstrDarakaraka planet
has_node_atmakarakaboolTrue when a node becomes Atmakaraka
has_node_darakarakaboolTrue when a node becomes Darakaraka
has_tiesboolTrue when assignment ties were detected
tie_countintNumber of tie warnings
profileslist[KarakaConditionProfile]Per-role diagnostic profiles

KarakaPair fields

FieldTypeDescription
role_astrFirst requested role
role_bstrSecond requested role
planet_astrPlanet assigned to the first role
planet_bstrPlanet assigned to the second role
type_astrType classification for the first role holder
type_bstrType classification for the second role holder
involves_nodeboolTrue when either role holder is a node
both_are_nodesboolTrue when both role holders are nodes

KarakaAssignment fields

FieldTypeDescription
karaka_namestrAssigned karaka role
karaka_rankintRole rank in the ordered assignment
planetstrAssigned planet
degree_in_signfloatDegrees traversed within the sign
sidereal_longitudefloatFull sidereal longitude
is_rahu_invertedboolTrue when Rahu inversion governs the assignment

Jaimini policy & enums

Public symbolKindDescription
JaiminiPolicydataclassscheme, ayanamsa_system
KarakaRoleenum-like classAtmakaraka through Darakaraka role constants
KarakaPlanetTypeenum-like classLUMINARY, INNER, OUTER, NODE
KARAKA_NAMES_7tuple[str, ...]Canonical 7-karaka role sequence
KARAKA_NAMES_8tuple[str, ...]Canonical 8-karaka role sequence

Vedic Dignities

from moira import (
    vedic_dignity, planetary_relationships,
    dignity_condition_profile, chart_dignity_profile,
    VedicDignityResult, PlanetaryRelationship,
    DignityConditionProfile, ChartDignityProfile, VedicDignityPolicy,
    validate_dignity_output,
)
FunctionReturnsDescription
vedic_dignity(planet, sidereal_longitude)VedicDignityResultCompute exaltation, debilitation, own-sign, and Mulatrikona condition
planetary_relationships(sidereal_longitudes)list[PlanetaryRelationship]Natural/compound relationship diagnostics across a chart
dignity_condition_profile(result)DignityConditionProfileInspect one Vedic dignity result
chart_dignity_profile(dignity_results)ChartDignityProfileAggregate chart-level dignity profile
validate_dignity_output(result)NoneValidate Vedic dignity output invariants

VedicDignityResult fields

FieldTypeDescription
planetstrPlanet name
sidereal_longitudefloatSidereal longitude used for evaluation
sign_indexintZero-based sidereal sign index
signstrSidereal sign name
dignity_rankstrResulting dignity rank
is_exaltedboolExaltation flag
is_debilitatedboolDebilitation flag
is_mulatrikonaboolMulatrikona flag
is_own_signboolOwn-sign flag
exaltation_scorefloatContinuous exaltation-strength score

PlanetaryRelationship fields

FieldTypeDescription
from_planetstrSource planet
to_planetstrTarget planet
naturalstrNatural relationship
temporarystrTemporary relationship
compoundstrCombined relationship result

DignityConditionProfile fields

FieldTypeDescription
planetstrPlanet name
dignity_rankstrDignity rank
tierstrInterpreted dignity tier
exaltation_scorefloatExaltation-strength score
sign_indexintSign index
signstrSign name

ChartDignityProfile fields

FieldTypeDescription
strong_countintCount of strong dignities
neutral_countintCount of neutral dignities
weak_countintCount of weak dignities
strongest_planetstrStrongest planet by dignity tier
weakest_planetstrWeakest planet by dignity tier
planet_tiersdict[str, str]Planet-to-tier mapping
exaltation_scoresdict[str, float]Planet-to-exaltation-score mapping

Vedic dignity policy, enums, and constants

Public symbolKindDescription
VedicDignityPolicydataclassayanamsa_system
VedicDignityRankenum-like classEXALTATION, MULATRIKONA, OWN_SIGN, FRIEND_SIGN, NEUTRAL_SIGN, ENEMY_SIGN, DEBILITATION
CompoundRelationshipenum-like classGREAT_FRIEND, FRIEND, NEUTRAL, ENEMY, GREAT_ENEMY
DignityTierenum-like classSTRONG, NEUTRAL, WEAK
EXALTATION_SIGNdict[str, int]Planet → exaltation sign index
EXALTATION_DEGREEdict[str, float]Planet → deepest exaltation degree
DEBILITATION_SIGNdict[str, int]Planet → debilitation sign index
MULATRIKONA_SIGNdict[str, int]Planet → Mulatrikona sign index
MULATRIKONA_STARTdict[str, float]Planet → Mulatrikona start degree
MULATRIKONA_ENDdict[str, float]Planet → Mulatrikona end degree
OWN_SIGNSdict[str, tuple[int, ...]]Planet → own-sign indices
NATURAL_FRIENDSdict[str, tuple[str, ...]]Planet → natural friends
NATURAL_NEUTRALSdict[str, tuple[str, ...]]Planet → natural neutrals
NATURAL_ENEMIESdict[str, tuple[str, ...]]Planet → natural enemies

Ashtakavarga

from moira import (
    bhinnashtakavarga, ashtakavarga,
    sign_strength_profile, transit_strength, ashtakavarga_chart_profile,
    BhinnashtakavargaResult, AshtakavargaResult,
    SignStrengthProfile, AshtakavargaChartProfile, AshtakavargaPolicy,
    validate_ashtakavarga_output,
)
FunctionReturnsDescription
bhinnashtakavarga(planet, sign_indices)BhinnashtakavargaResultPer-planet rekha distribution across the 12 signs
ashtakavarga(sidereal_longitudes, ayanamsa_system=None, policy=None)AshtakavargaResultFull Sarvashtakavarga and Bhinnashtakavarga result
sign_strength_profile(bhinna, sign_idx, policy=None)SignStrengthProfileInterpret one sign within a Bhinnashtakavarga result
transit_strength(planet, transit_sign_index, bhinna)intRekha strength of a transit through a sign
ashtakavarga_chart_profile(result, policy=None)AshtakavargaChartProfileAggregate Ashtakavarga chart profile
validate_ashtakavarga_output(result)NoneValidate Ashtakavarga output invariants

BhinnashtakavargaResult fields

FieldTypeDescription
planetstrPlanet name
rekhastuple[int, ...]12-sign rekha vector
total_rekhasintSum of rekhas across all signs

AshtakavargaResult fields

FieldTypeDescription
ayanamsa_systemstrGoverning ayanamsa system
bhinnashtakavargadict[str, BhinnashtakavargaResult]Per-planet bhinna results
sarvashtakavargatuple[int, ...]Aggregate 12-sign Sarvashtakavarga vector
shodhana_bhinnashtakavargadict[str, BhinnashtakavargaResult] | NoneShodhana-adjusted bhinna results when enabled
shodhana_sarvashtakavargatuple[int, ...] | NoneShodhana-adjusted Sarvashtakavarga when enabled

SignStrengthProfile fields

FieldTypeDescription
planetstrPlanet name
sign_idxintZero-based sign index
rekha_countintRekha count in the sign
tierstrStrength tier under the active policy

AshtakavargaChartProfile fields

FieldTypeDescription
sarva_totalintTotal Sarvashtakavarga points
sarva_maxintMaximum sign score
sarva_max_sign_idxintSign index of the maximum score
sarva_minintMinimum sign score
sarva_min_sign_idxintSign index of the minimum score
strong_planet_sign_countsdict[str, int]Count of strong signs by planet
ayanamsa_systemstrGoverning ayanamsa system

Ashtakavarga policy & constants

Public symbolKindDescription
AshtakavargaPolicydataclassayanamsa_system, strong_threshold, apply_trikona_shodhana, apply_ekadhipatya_shodhana
RekhaTierenum-like classStrength-tier constants for rekha interpretation
REKHA_TABLESdict[str, dict[str, tuple[int, ...]]]Classical rekha tables used to build Bhinnashtakavarga

Shadbala

from moira import (
    sthana_bala, dig_bala, kala_bala, chesta_bala, drig_bala,
    shadbala, hora_lord_at,
    shadbala_condition_profile, shadbala_chart_profile,
    PlanetShadbala, ShadbalaResult, ShadbalaConditionProfile, ShadbalaChartProfile,
    ShadbalaPolicy, validate_shadbala_output,
)
FunctionReturnsDescription
sthana_bala(planet, sidereal_lon, houses, jd, ayanamsa_system=Ayanamsa.LAHIRI)SthanaBalaPositional strength components for one planet
dig_bala(planet, sidereal_lon, houses, jd, ayanamsa_system=Ayanamsa.LAHIRI)floatDirectional strength for one planet
kala_bala(planet, sidereal_lon, sun_sidereal_lon, jd, tithi_number, is_day, vara_lord, planet_speeds, hora_lord=None, ayanamsa_system=Ayanamsa.LAHIRI, local_day_frac=None)KalaBalaTemporal strength components for one planet
chesta_bala(planet, speed, planet_sidereal_lon=None, mandoccha_sidereal_lon=None)floatMotional strength for one planet
drig_bala(planet, sidereal_longitudes)floatAspect-based strength contribution for one planet
shadbala(sidereal_longitudes, planet_speeds, houses, jd, tithi_number, vara_lord, is_day, ayanamsa_system=Ayanamsa.LAHIRI, hora_lord=None, planet_latitudes=None)ShadbalaResultFull Shadbala computation for the seven classical planets
hora_lord_at(birth_jd, sunrise_jd)strPlanetary hora lord at birth
shadbala_condition_profile(planet_result)ShadbalaConditionProfileInspect one planet’s Shadbala result
shadbala_chart_profile(result)ShadbalaChartProfileAggregate chart-level Shadbala profile
validate_shadbala_output(result)NoneValidate Shadbala output invariants

PlanetShadbala fields

FieldTypeDescription
planetstrPlanet name
sthana_balaSthanaBalaPositional strength components
dig_balafloatDirectional strength
kala_balaKalaBalaTemporal strength components
chesta_balafloatMotional strength
naisargika_balafloatNatural strength constant
drig_balafloatAspect-based strength
total_shashtiamsasfloatTotal strength in shashtiamsas
total_rupasfloatTotal strength in rupas
required_rupasfloatRequired threshold for sufficiency
is_sufficientboolSufficiency flag

ShadbalaResult fields

FieldTypeDescription
jdfloatJulian day of the computation moment
ayanamsa_systemstrGoverning ayanamsa system
planetsdict[str, PlanetShadbala]Per-planet Shadbala results

ShadbalaConditionProfile fields

FieldTypeDescription
planetstrPlanet name
tierstrInterpreted strength tier
total_rupasfloatTotal strength in rupas
required_rupasfloatRequired threshold
strength_ratiofloatRatio of actual to required strength
is_sufficientboolSufficiency flag

ShadbalaChartProfile fields

FieldTypeDescription
sufficient_countintCount of planets meeting the threshold
insufficient_countintCount of planets below threshold
strongest_planetstrStrongest planet by ratio
weakest_planetstrWeakest planet by ratio
planet_tiersdict[str, str]Planet-to-tier mapping
strength_ratiosdict[str, float]Planet-to-ratio mapping
ayanamsa_systemstrGoverning ayanamsa system

SthanaBala fields

FieldTypeDescription
uchchafloatExaltation-proximity component
saptavargajafloatSeven-varga dignity component
ojayugmafloatOdd/even sign-parity component
kendradifloatAngularity component
drekkanafloatDecan-gender component
totalfloatTotal positional strength

KalaBala fields

FieldTypeDescription
nathonnathafloatDay/night strength component
pakshafloatLunar-phase strength component
tribhagafloatThird-of-day/night component
abda_masa_vara_horafloatYear/month/weekday/hour-lord component
ayanafloatSolstitial component
yuddhafloatPlanetary-war bonus component
totalfloatTotal temporal strength

Shadbala policy, tiers, and constants

Public symbolKindDescription
ShadbalaPolicydataclassayanamsa_system
ShadbalaTierenum-like classSUFFICIENT, INSUFFICIENT
NAISARGIKA_BALAdict[str, float]Natural fixed-strength constants in shashtiamsas
REQUIRED_RUPASdict[str, float]Required sufficiency thresholds in rupas
MEAN_DAILY_MOTIONdict[str, float]Mean daily motion constants used in Chesta Bala

10. Planetary Cycles Engine

from moira.cycles import (
    # Enums
    SynodicPhase, GreatMutationElement, PlanetaryAgeName,
    # Return series
    ReturnEvent, ReturnSeries,
    return_series, half_return_series, lifetime_returns,
    # Synodic cycles
    SynodicCyclePosition, synodic_cycle_position,
    # Great conjunctions
    GreatConjunction, GreatConjunctionSeries, MutationPeriod,
    great_conjunctions, mutation_period_at,
    # Planetary ages
    PlanetaryAgePeriod, PlanetaryAgeProfile,
    planetary_age_at, planetary_age_profile,
    # Firdar
    FirdarPeriod, FirdarSubPeriod, FirdarSeries,
    firdar_series, firdar_at,
    # Planetary days and hours
    PlanetaryDayInfo, PlanetaryHour, PlanetaryHoursProfile,
    planetary_day_ruler, planetary_hours_for_day,
)

moira.cycles governs cyclical timing frameworks grounded in astronomical periodicity. It is distinct from moira.timelords (Firdaria, Zodiacal Releasing, Vimshottari) and moira.transits (sign ingresses, transit events). The cycles engine focuses on the long-arc structure of planetary time: returns, synodic phases, the Jupiter–Saturn great conjunction doctrine, and Ptolemaic planetary ages.

Return Series

A complete series of returns (or half-returns) for one body across a date range.

FunctionReturnsDescription
return_series(body, natal_lon, jd_start, jd_end)ReturnSeriesAll direct returns of a body to its natal longitude
half_return_series(body, natal_lon, jd_start, jd_end)ReturnSeriesReturns and half-returns (oppositions) interleaved
lifetime_returns(body, natal_lon, jd_natal, age_years=90.0)ReturnSeriesFull-lifetime return sequence from birth

ReturnEvent fields

FieldTypeDescription
bodystrThe returning body
return_numberintOrdinal (1 = first return)
jd_utfloatJD UT of exact return
longitudefloatNatal longitude returned to (°)
is_halfboolTrue for a half-return (opposition to natal)

ReturnSeries fields

FieldTypeDescription
bodystrBody name
natal_longitudefloatNatal longitude (°)
jd_startfloatStart of search window
jd_endfloatEnd of search window
returnstuple[ReturnEvent, ...]All returns, chronological
countintNumber of returns found

Synodic Cycles

pos = synodic_cycle_position(body1, body2, jd_ut)
# SynodicCyclePosition

SynodicCyclePosition fields

FieldTypeDescription
body1strFirst body
body2strSecond body
jd_utfloatMoment of evaluation
phase_anglefloatPhase angle from body1 to body2 (°), [0°, 360°) — 0° = conjunction
phaseSynodicPhaseEight-fold phase classification
is_waxingboolTrue if the phase angle is increasing (0°–180°)
lon1floatEcliptic longitude of body1 at evaluation (°)
lon2floatEcliptic longitude of body2 at evaluation (°)

SynodicPhase values: NEW WAXING_CRESCENT FIRST_QUARTER WAXING_GIBBOUS FULL WANING_GIBBOUS LAST_QUARTER WANING_CRESCENT

SynodicPhase.from_angle(angle_deg) classifies an arbitrary phase angle into one of the eight phases.

Great Conjunctions

The Jupiter–Saturn 20/200/800-year conjunction doctrine (Abu Ma'shar, Kepler).

FunctionReturnsDescription
great_conjunctions(jd_start, jd_end)GreatConjunctionSeriesAll Jupiter–Saturn conjunctions in a range
mutation_period_at(longitude)GreatMutationElementElemental trigon (FIRE / EARTH / AIR / WATER) for a given ecliptic longitude

GreatConjunction fields

FieldTypeDescription
jd_utfloatJD UT of exact conjunction
longitudefloatConjunction longitude (°)
signstrZodiac sign name
sign_symbolstrZodiac sign glyph
degree_in_signfloatDegree within the sign
elementGreatMutationElementElemental trigon: FIRE / EARTH / AIR / WATER

GreatMutationElement values: FIRE EARTH AIR WATER

GreatConjunctionSeries fields

FieldTypeDescription
jd_startfloatStart of search window
jd_endfloatEnd of search window
conjunctionstuple[GreatConjunction, ...]All conjunctions found, chronological
countintNumber of conjunctions
elements_representedtuple[GreatMutationElement, ...]Distinct elements present, in order of first occurrence

MutationPeriod fields

FieldTypeDescription
elementGreatMutationElementDominant element for this ~200-year period
start_conjunctionGreatConjunctionFirst conjunction that inaugurated this element period
end_conjunctionGreatConjunction | NoneFinal conjunction in this element before mutation (None if period extends beyond the search window)
conjunction_countintNumber of conjunctions in this element during this period

Planetary Ages (Ptolemy)

The seven-age model from Tetrabiblos I.10. Each planet governs a developmental stage.

FunctionReturnsDescription
planetary_age_at(age_years)PlanetaryAgePeriodWhich planet governs a given age
planetary_age_profile(age_years=None)PlanetaryAgeProfileFull seven-period model; pass age_years to identify the current period

PlanetaryAgePeriod fields

FieldTypeDescription
rulerPlanetaryAgeNameGoverning planet
start_agefloatAge when this period begins (years)
end_agefloat | NoneAge when this period ends (None for Saturn, which is open-ended)
labelstrHuman-readable stage label (e.g. "Childhood", "Prime")

PlanetaryAgeName values: MOON MERCURY VENUS SUN MARS JUPITER SATURN

Standard Ptolemaic durations: Moon 0–4, Mercury 4–14, Venus 14–22, Sun 22–41, Mars 41–56, Jupiter 56–68, Saturn 68+.

PlanetaryAgeProfile fields

FieldTypeDescription
periodstuple[PlanetaryAgePeriod, ...]All seven age periods in order
currentPlanetaryAgePeriod | NoneActive period for the queried age, or None if not queried
queried_agefloat | NoneThe age that was queried (years), or None

Firdar (cycles.py variant)

moira.cycles provides a streamlined Firdar engine. It is distinct from moira.timelords.firdaria, which adds condition profiles and reception network analysis on top of the same foundation.

Diurnal sequence (day births): Sun(10) → Venus(8) → Mercury(13) → Moon(9) → Saturn(11) → Jupiter(12) → Mars(7) → North Node(3) → South Node(2) = 75 years

Nocturnal sequence (night births): Moon(9) → Saturn(11) → Jupiter(12) → Mars(7) → Sun(10) → Venus(8) → Mercury(13) → North Node(3) → South Node(2) = 75 years

FunctionReturnsDescription
firdar_series(jd_natal, is_day)FirdarSeriesComplete 75-year Firdar sequence from birth
firdar_at(jd_natal, jd_now, is_day)FirdarPeriodActive Firdar period (major + sub) at jd_now

FirdarSeries fields

FieldTypeDescription
birth_jdfloatBirth Julian Day
is_day_birthboolTrue for diurnal nativity
periodstuple[FirdarPeriod, ...]All 9 firdar major periods in sequence
total_yearsfloatSum of all periods (~75 Julian years)

FirdarPeriod fields (cycles.py vessel)

FieldTypeDescription
rulerstrThe planet (or node) governing this firdar
start_jdfloatStart JD UT of the major period
end_jdfloatEnd JD UT
duration_yearsfloatDuration in Julian years
ordinalintPosition in the sequence (1–9)
sub_periodstuple[FirdarSubPeriod, ...] | None7 planetary sub-periods (None for nodal firdars)

FirdarSubPeriod fields

FieldTypeDescription
sub_rulerstrGoverning planet for the sub-period
start_jdfloatStart JD UT
end_jdfloatEnd JD UT
duration_yearsfloatDuration in Julian years

Planetary Days and Hours

FunctionReturnsDescription
planetary_day_ruler(jd_ut)PlanetaryDayInfoChaldean day ruler for the given JD
planetary_hours_for_day(jd_ut, latitude, longitude)PlanetaryHoursProfileFull day and night planetary hour schedule

PlanetaryDayInfo fields

FieldTypeDescription
rulerstrPlanet ruling this day (Chaldean order)
weekday_namestrName of the weekday
weekday_numberintISO weekday (1 = Monday, 7 = Sunday)

PlanetaryHour fields

FieldTypeDescription
hour_numberint1–24 (1–12 = day hours, 13–24 = night hours)
rulerstrPlanet governing this hour
start_jdfloatStart of this hour (JD UT)
end_jdfloatEnd of this hour (JD UT)
is_day_hourboolTrue for a daytime (diurnal) hour

PlanetaryHoursProfile fields

FieldTypeDescription
day_infoPlanetaryDayInfoThe day's ruler and weekday metadata
sunrise_jdfloatSunrise JD used for the day's hours
sunset_jdfloatSunset JD used
next_sunrise_jdfloatNext sunrise JD (used for nighttime hour duration)
hourstuple[PlanetaryHour, ...]All 24 hours in order (1–24)
day_hour_lengthfloatDuration of one daytime hour (days)
night_hour_lengthfloatDuration of one nighttime hour (days)

11. Huber Method

from moira.huber import (
    HouseZone,
    PHI, PHI_COMPLEMENT, CYCLE_YEARS, YEARS_PER_HOUSE,
    HouseZoneProfile, AgePointPosition, DynamicIntensity,
    PlanetIntensityScore, ChartIntensityProfile,
    house_zones, age_point, age_point_contacts,
    dynamic_intensity, intensity_at, chart_intensity_profile,
)

The Huber symbols are also available via the cumulative tier modules: moira.classical, moira.predictive, and moira.facade. The moira.huber import path is the direct low-level surface; the tier modules re-export the same symbols.

Implements the computational apparatus of the Huber method (Bruno and Louise Huber, Astrological Psychology Institute). Koch houses are prescribed by Huber doctrine; all functions accept any HouseCusps but note the doctrinal preference.

Constants

ConstantValueDescription
PHI0.6180...Golden ratio fractional part
PHI_COMPLEMENT0.3819...Complement of phi (1 − phi)
CYCLE_YEARS72.0Full Age Point cycle in years
YEARS_PER_HOUSE6.0Years the Age Point spends per house

HouseZone — golden-section zones

Each house is divided by the golden ratio into three developmental zones:

ZoneFractionQuality
CARDINAL0.000 – 0.382Outward initiative, environmental engagement
FIXED0.382 – 0.618Consolidation, stable expression
MUTABLE0.618 – 1.000Transition, preparation for the next house

House Zone Analysis

zones = house_zones(houses)
# list[HouseZoneProfile]  — one per house

HouseZoneProfile fields

FieldTypeDescription
houseintHouse number (1–12)
cusp_longitudefloatOpening cusp longitude (°)
next_cusp_longitudefloatNext cusp longitude (°)
house_sizefloatAngular size of the house (°)
balance_point_longitudefloatBalance Point longitude (cusp + 0.382 × size)
low_point_longitudefloatLow Point longitude (cusp + 0.618 × size)
balance_point_fractionfloatAlways PHI_COMPLEMENT (~0.382)
low_point_fractionfloatAlways PHI (~0.618)

Age Point

ap = age_point(houses, jd_natal, jd_now)
# AgePointPosition

The Age Point progresses counterclockwise through the 12 houses over 72 years (6 years per house), starting from the Ascendant.

AgePointPosition fields

FieldTypeDescription
age_yearsfloatAge in years from birth
cycleintWhich 72-year cycle (1 = first life, 2 = second…)
houseintHouse number currently occupied (1–12)
fraction_through_housefloat0.0 at cusp, 1.0 at next cusp
longitudefloatEcliptic longitude of the Age Point (°)
zoneHouseZoneCARDINAL / FIXED / MUTABLE zone
years_into_housefloatYears elapsed since entering this house
intensityfloatDynamic Intensity Curve value (0.0–1.0)
contacts = age_point_contacts(houses, jd_natal, jd_now, chart_longitudes, orb=2.0)
# list of bodies the Age Point is conjunct within orb

Dynamic Intensity Curve

di = dynamic_intensity(houses, longitude)
# DynamicIntensity(house, zone, fraction, intensity)

score = intensity_at(houses, longitude)
# float in [0.0, 1.0] — 1.0 at any cusp, minimum at the Low Point

Chart Intensity Profile

profile = chart_intensity_profile(houses, planet_longitudes)
# ChartIntensityProfile

Scores all natal planets against the Dynamic Intensity Curve and produces a chart-level summary.

ChartIntensityProfile fields

FieldTypeDescription
scoreslist[PlanetIntensityScore]Per-planet scores, highest first
mean_intensityfloatMean intensity across all scored planets
dominant_planetstrPlanet with the highest intensity score
dominant_zoneHouseZoneZone of the dominant planet

PlanetIntensityScore: planet, longitude, house, zone, fraction_through_house, intensity.

from moira.huber import house_zones, age_point, chart_intensity_profile
from moira.facade import Moira, HouseSystem
from datetime import datetime, timezone

m = Moira()
dt_birth = datetime(1988, 4, 4, 14, 30, tzinfo=timezone.utc)
dt_now   = datetime(2026, 4, 7, tzinfo=timezone.utc)

# Koch houses (Huber doctrine)
houses = m.houses(dt_birth, latitude=51.5, longitude=-0.1, system=HouseSystem.KOCH)
chart  = m.chart(dt_birth)

zones   = house_zones(houses)
ap      = age_point(houses, chart.jd_ut, m.jd(2026, 4, 7))
profile = chart_intensity_profile(houses, chart.longitudes())
print(f"Age Point: House {ap.house} ({ap.zone.value}), intensity {ap.intensity:.2f}")
print(f"Dominant planet: {profile.dominant_planet}")

12. Relational Techniques

Synastry

from moira.facade import (
    synastry_aspects, synastry_contacts,
    house_overlay, mutual_house_overlays,
    synastry_contact_relations, mutual_overlay_relations,
    synastry_condition_profiles, synastry_chart_condition_profile,
    synastry_condition_network_profile,
    SynastryHouseOverlay, MutualHouseOverlay,
    SynastryAspectTruth, SynastryAspectContact,
    SynastryOverlayTruth, SynastryRelation,
    SynastryConditionState, SynastryConditionProfile,
    SynastryChartConditionProfile,
    SynastryConditionNetworkProfile,
    SynastryAspectPolicy, SynastryOverlayPolicy,
    SynastryComputationPolicy,
)
FunctionReturnsDescription
synastry_aspects(chart_a, chart_b, tier=2, orbs=None, orb_factor=1.0, include_nodes=True)list[AspectData]Inter-chart aspects
synastry_contacts(chart_a, chart_b, ...)list[SynastryAspectContact]Contacts with classification
house_overlay(chart_source, target_houses, ...)SynastryHouseOverlaychart_source planets in target_houses
mutual_house_overlays(chart_a, houses_a, chart_b, houses_b, ...)MutualHouseOverlayBoth overlay directions

Composite Charts

from moira.facade import (
    composite_chart, composite_chart_reference_place,
    CompositeChart,
)

comp = composite_chart(chart_a, chart_b, houses_a, houses_b)
# CompositeChart(planets/nodes: dict[str, float], cusps: list[float], asc, mc)

The pure midpoint method midpoint-combines both source house frames when both are supplied. Its computation truth reports the common requested/effective source house system when one exists and echoes the resulting composite MC; reference_latitude and composite_armc remain None because that method does not construct a reference-place ARMC frame.

Davison Relationship Charts

from moira.facade import (
    davison_chart, davison_chart_uncorrected,
    davison_chart_reference_place, davison_chart_spherical_midpoint,
    davison_chart_corrected,
    DavisonChart, DavisonInfo,
)

Four variants differing in how the geographic and temporal midpoints are computed:

VariantFunctionMidpoint timeMidpoint location
Standarddavison_chartJD arithmetic meanSpherical midpoint
Uncorrecteddavison_chart_uncorrectedArithmetic meanArithmetic mean
Reference Placedavison_chart_reference_placeArithmetic meanSupplied explicitly
Sphericaldavison_chart_spherical_midpointArithmetic meanGreat-circle midpoint
Correcteddavison_chart_correctedCorrected for JD midpointSpherical midpoint

DavisonChart: chart (Chart), info (DavisonInfo — midpoint JD, lat, lon).


13. Geography

AstroCartoGraphy

from moira.facade import acg_lines, acg_from_chart, ACGLine
FunctionReturnsDescription
acg_lines(planet_ra_dec, gmst_deg, lat_step=2.0)list[ACGLine]ACG lines given a pre-built RA/Dec dict and GMST
acg_from_chart(chart, bodies=None, lat_step=2.0)list[ACGLine]ACG lines directly from a Chart

acg_lines is the low-level engine. acg_from_chart is a convenience wrapper that handles GAST extraction and calls sky_position_at for each body.

ACGLine fields

FieldTypeDescription
planetstrBody name
line_typestr"MC" / "IC" / "ASC" / "DSC"
longitudefloat | NoneGeographic longitude for MC/IC meridians
pointslist[tuple[float, float]](lat, lon) curve points for ASC/DSC

MC/IC lines are meridians: longitude is set, points is empty. ASC/DSC lines are curves: points is set, longitude is None.

from moira.facade import Moira, Body
from datetime import datetime, timezone

m = Moira()
dt = datetime(1988, 4, 4, 14, 30, tzinfo=timezone.utc)
chart = m.chart(dt)

lines = m.astrocartography(chart, observer_lat=51.5, observer_lon=-0.1)
for line in lines:
    if line.line_type == "MC":
        print(f"{line.planet} MC meridian: {line.longitude:.2f}°E")
    else:
        print(f"{line.planet} {line.line_type}: {len(line.points)} points")

Local Space

from moira.facade import local_space_positions, local_space_from_chart, LocalSpacePosition
FunctionReturnsDescription
local_space_positions(planet_ra_dec, latitude, lst_deg)list[LocalSpacePosition]Azimuth/altitude from RA/Dec and LST
local_space_from_chart(chart, observer_lat, observer_lon, bodies=None)list[LocalSpacePosition]Convenience wrapper for a Chart

LocalSpacePosition fields

FieldTypeDescription
bodystrBody name
azimuthfloatCompass bearing 0-360 degrees (North = 0, East = 90)
altitudefloatElevation above (+) or below (-) horizon
is_aboveboolTrue when altitude >= 0

Method: compass_direction() -> str - returns an 8-point compass label (N/NE/E/SE/S/SW/W/NW).

ls = m.local_space(chart, latitude=51.5, longitude=-0.1)
for pos in ls:
    arrow = "↑" if pos.is_above else "↓"
    print(f"{pos.body:10s}  Az {pos.azimuth:.1f}° {pos.compass_direction():2s}  "
          f"Alt {pos.altitude:+.1f}° {arrow}")

Parans

Parans identify simultaneous horizon and meridian crossings shared by two stars or planets — a complementary layer to ACG.

from moira.facade import (
    find_parans, find_parans_with_inventory,
    natal_parans, natal_parans_with_inventory, natal_angular_contacts,
    evaluate_paran_site, sample_paran_field, analyze_paran_field,
    evaluate_paran_stability, extract_paran_field_contours,
    consolidate_paran_contours, analyze_paran_field_structure,
    Paran, ParanCrossing, ParanSignature, ParanStrength,
    ParanSiteResult, ParanFieldSample, ParanFieldAnalysis,
    ParanContourPathSet, ParanFieldStructure,
    DEFAULT_PARAN_POLICY, PARAN_POLICY_PRESETS, ParanPolicyPreset,
    PARAN_STAR_CANON, ParanStarTier, list_paran_stars,
    CIRCLE_TYPES,
)
FunctionReturnsDescription
find_parans(bodies, jd_day, lat, lon, orb_minutes=4.0, policy=None)list[Paran]Paran crossings for a supplied body-name list at a location
find_parans_with_inventory(...)ParanSearchResultParan events plus four-circle availability for every requested body
natal_parans(bodies, natal_jd, lat, lon, orb_minutes=4.0, policy=None)list[Paran]Full birth-day paran search
natal_parans_with_inventory(...)ParanSearchResultBirth-day parans plus crossing availability
natal_angular_contacts(bodies, natal_jd, lat, lon, orb_minutes=2.0)list[NatalAngularContact]Individual crossings near the explicit birth moment; not a two-body paran search
evaluate_paran_site(target, jd_day, lat, lon, ...)ParanSiteResultRecompute one paran identity at a location
sample_paran_field(target, jd_day, latitudes, longitudes, ...)list[ParanFieldSample]Grid of site results for one paran identity
analyze_paran_field(samples, metric, threshold)ParanFieldAnalysisIdentify active regions, peaks, and threshold crossings
evaluate_paran_stability(paran, jd_day, lat, lon, ...)ParanStabilityRecompute one paran under time-anchor perturbations
extract_paran_field_contours(samples, metric, threshold)ParanContourExtractionExtract contour segments from a rectangular sampled field
consolidate_paran_contours(extraction)ParanContourPathSetStitch contour segments into paths and report orphans
analyze_paran_field_structure(analysis, path_set)ParanFieldStructureDerive path hierarchy and region/peak associations
list_paran_stars(tiers=None, available_only=True)tuple[ParanStarCanonEntry, ...]Engine-owned working canon with Royal, Behenian, and Ptolemaic memberships

REST consumers use:

  • GET /v1/parans/star-canon
  • POST /v1/parans/search
  • POST /v1/parans/natal
  • POST /v1/parans/natal-angular-contacts
  • POST /v1/parans/site
  • POST /v1/parans/field/{samples,analysis,contours,paths,structure}
  • POST /v1/website/parans/packet

Search and natal requests may set include_crossing_inventory=true. All paran request families accept policy_preset, currently permissive or star_planet_only. The website packet composes existing engine truth; it does not own a second paran or heliacal implementation. Star-star paran computation is kernel-free. Optional heliacal inclusion requires planetary-kernel access for solar ephemeris truth; an unavailable kernel is returned as an explicit packet warning rather than an approximation.

Paran fields

FieldTypeDescription
body1strFirst body
body2strSecond body
circle1strCircle type for the first body
circle2strCircle type for the second body
jd1floatEvent JD for the first body crossing
jd2floatEvent JD for the second body crossing
orb_minfloatDifference between the crossings in minutes of time
crossing1ParanCrossingCrossing details for the first body
crossing2ParanCrossingCrossing details for the second body
signatureParanSignatureCombined paran signature metadata

14. Fixed Stars

Unified fixed-star surface (star_registry.csv + metadata sidecars)

from moira.facade import (
    star_at, all_stars_at,
    list_named_stars, find_named_stars, list_stars, find_stars, star_magnitude,
    load_catalog,
    heliacal_rising_event, heliacal_setting_event, heliacal_rising, heliacal_setting,
    heliacal_catalog_batch,
    star_chart_condition_profile, star_condition_network_profile,
    FixedStar, HeliacalEvent, HeliacalBatchResult,
    FixedStarLookupPolicy, HeliacalSearchPolicy, FixedStarComputationPolicy,
    FixedStarTruth, FixedStarClassification,
    UnifiedStarRelation, UnifiedStarMergePolicy, UnifiedStarComputationPolicy,
    StarConditionState, StarConditionProfile,
    StarChartConditionProfile, StarConditionNetworkProfile,
)
FunctionReturnsDescription
star_at(name, jd_tt, policy=None)FixedStarPublic fixed-star lookup, with sovereign registry data and Gaia-derived enrichment fields when available
all_stars_at(jd_tt)dict[str, FixedStar]All named stars at one epoch
list_named_stars() / list_stars()list[str]All named stars in the sovereign registry
find_named_stars(query) / find_stars(query)list[str]Fuzzy search across named stars and nomenclature aliases
star_magnitude(name)floatVisual magnitude
load_catalog()NoneReload the sovereign fixed-star registry and indexes
heliacal_rising(name, jd_ut, latitude, longitude)float | NoneJD of heliacal rising
heliacal_setting(name, jd_ut, latitude, longitude)float | NoneJD of heliacal setting
heliacal_rising_event(name, jd_ut, lat, lon)HeliacalEventHeliacal rising with classification
heliacal_setting_event(name, jd_ut, lat, lon)HeliacalEventHeliacal setting with classification
heliacal_catalog_batch(event_kind, jd_start, latitude, longitude, *, max_magnitude=6.5, names=None, search_days=400, policy=None)HeliacalBatchResultBatch heliacal search across the fixed-star registry

There is no separate public fixed_star_at function in 1.0.3. The public lookup surface is star_at(...), which returns a FixedStar vessel.

Specialty module helper: from moira.stars import star_light_time_split returns (observed, true) fixed-star positions separated by stellar light-time, but it is not re-exported by moira.facade in 1.0.3.

Catalog convenience sets

royal_stars.py and behenian_stars.py are standalone sub-modules — not re-exported at the moira top-level. Import them directly:

from moira.royal_stars import (
    list_royal_stars, available_royal_stars, royal_star_at,
    ALDEBARAN, REGULUS, ANTARES, FOMALHAUT,
)
from moira.behenian_stars import (
    list_behenian_stars, available_behenian_stars, behenian_star_at,
    ALGOL, ALCYONE, SIRIUS, SPICA, ARCTURUS, ALPHECCA, VEGA,  # + 8 more constants
)

Search helpers and merged fields

FunctionReturnsDescription
star_at(name, jd_tt)FixedStarNamed star with Gaia enrichment when available
stars_near(longitude, orb, jd_tt)list[FixedStar]Stars within orb° of a longitude
stars_by_magnitude(max_mag, jd_tt)list[FixedStar]Stars brighter than max_mag
list_named_stars()list[str]All traditionally-named stars
find_named_stars(query)list[str]Fuzzy name search across named stars

FixedStar fields

FieldTypeDescription
namestrTraditional name
nomenclaturestr | NoneCatalog designation or alternate nomenclature
longitudefloatEcliptic longitude (°)
latitudefloatEcliptic latitude (°)
magnitudefloatVisual magnitude
bp_rpfloat | NoneGaia BP−RP colour index
teff_kfloat | NoneEffective temperature (K) from Gaia
parallax_masfloat | NoneGaia parallax (mas)
distance_lyfloat | NoneDistance in light-years
qualityStellarQuality | NoneStellar classification from Gaia colours
sourcestrData source used for the merged record
is_topocentricboolWhether topocentric correction was applied
computation_truthFixedStarTruthComputation truth data
classificationFixedStarClassificationClassification metadata
relationUnifiedStarRelationRelation metadata
condition_profileStarConditionProfileConsolidated star condition profile

Gaia enrichment status

FixedStar records may expose Gaia-derived fields such as bp_rp, parallax_mas, distance_ly, and quality, but Moira 1.0.3 does not export a standalone public Gaia loader/query surface. Gaia enrichment is internal to the merged fixed-star API rather than a separate public subsystem.

General visibility engine

Moira also exposes a generalized observational visibility layer that is broader than the fixed-star heliacal helpers. This is the public surface used for criterion-based visibility judgments and event searches.

from moira.facade import (
    HeliacalEventKind, VisibilityTargetKind,
    LightPollutionClass, LightPollutionDerivationMode,
    ObserverAid, ObserverVisibilityEnvironment,
    VisibilityCriterionFamily, VisibilityExtinctionModel, VisibilityTwilightModel,
    ExtinctionCoefficient, MoonlightPolicy,
    VisibilityPolicy, VisibilitySearchPolicy,
    LunarCrescentVisibilityClass, LunarCrescentDetails,
    VisibilityAssessment, GeneralVisibilityEvent,
    visibility_assessment, visual_limiting_magnitude, visibility_event,
)
FunctionReturnsDescription
visibility_assessment(body, jd_ut, lat, lon, *, policy=None)VisibilityAssessmentCriterion-based visibility judgment at one observing moment
visual_limiting_magnitude(jd_ut, lat, lon, *, policy=None)floatEstimated naked-eye limiting magnitude at the site and time
visibility_event(body, event_kind, jd_start, lat, lon, *, heliacal_policy=None, visibility_policy=None, search_policy=None)GeneralVisibilityEvent | NoneSearch for the next generalized visibility event matching the requested kind

Key policy and vessel types: VisibilityPolicy, VisibilitySearchPolicy, VisibilityAssessment, GeneralVisibilityEvent, ObserverVisibilityEnvironment, LunarCrescentDetails.

Variable Stars

from moira.facade import (
    variable_star, list_variable_stars, variable_stars_by_type,
    phase_at, magnitude_at, next_minimum, next_maximum,
    minima_in_range, maxima_in_range,
    malefic_intensity, benefic_strength, is_in_eclipse,
    algol_phase, algol_magnitude, algol_next_minimum, algol_is_eclipsed,
    star_phase_state, star_condition_profile, catalog_profile, star_state_pair,
    validate_variable_star_catalog,
    VariableStar, VarType, VarStarPolicy, DEFAULT_VAR_STAR_POLICY,
    StarPhaseState, StarConditionProfile, CatalogProfile, StarStatePair,
)

VarType — variable star classification

ConstantMeaning
VarType.ECLIPSING_ALGOLAlgol-type (EA) — sharp minima
VarType.ECLIPSING_BETABeta Lyrae-type (EB) — continuous variation
VarType.ECLIPSING_W_UMAW Ursae Maj.-type (EW) — contact binaries
VarType.CEPHEIDDelta Cephei-type pulsating
VarType.RR_LYRAERR Lyrae pulsating
VarType.MIRAMira-type long-period
VarType.SEMI_REG_SGSemi-regular supergiant
VarType.SEMI_REGSemi-regular

VariableStar fields

FieldTypeDescription
namestrStar name
designationstr | NoneCatalog designation
var_typeVarTypeVariability classification
period_daysfloatPeriod in days (0 if irregular)
epoch_jdfloatReference epoch JD
epoch_is_minimumboolTrue when the epoch is a minimum, False when it is a maximum
mag_maxfloatMagnitude at maximum brightness
mag_minfloatMagnitude at minimum brightness
mag_min2float | NoneSecondary minimum magnitude when applicable
eclipse_widthfloatEclipse duration as fraction of period (EA only)
classical_qualitystr"malefic" / "benefic" / "neutral" / "mixed"
notestrShort catalog note

Derived properties: amplitude, is_eclipsing, is_pulsating, is_long_period, is_irregular, is_malefic, is_benefic, type_class.

Core functions

FunctionReturnsDescription
variable_star(name)VariableStarLook up a star by name
list_variable_stars()list[str]All catalog star names (20 stars)
variable_stars_by_type(var_type)list[VariableStar]Filter catalog by type
phase_at(star, jd)floatPhase in [0, 1) at a given JD
magnitude_at(star, jd)floatInterpolated visual magnitude
malefic_intensity(star, jd, policy=None)floatMalefic score [0, 1]
benefic_strength(star, jd, policy=None)floatBenefic score [0, 1]
is_in_eclipse(star, jd, policy=None)boolTrue when near minimum for eclipsing type
next_minimum(star, jd)floatJD of next minimum
next_maximum(star, jd)floatJD of next maximum
minima_in_range(star, jd_start, jd_end)list[float]All minima in range
maxima_in_range(star, jd_start, jd_end)list[float]All maxima in range

Algol convenience functions

FunctionReturnsDescription
algol_phase(jd)floatAlgol phase at JD
algol_magnitude(jd)floatAlgol magnitude at JD
algol_next_minimum(jd)floatJD of next Algol minimum
algol_is_eclipsed(jd, policy=None)boolTrue when Algol is near minimum

Condition profile API

state   = star_phase_state(star, jd)
# StarPhaseState(star, jd, phase, magnitude, malefic_score, benefic_score, in_eclipse)

profile = star_condition_profile(star, jd)
# StarConditionProfile — catalog truth + dynamic state in one record

cat     = catalog_profile(jd)
# CatalogProfile — aggregate over all 20 catalog stars

pair    = star_state_pair(star_a, star_b, jd)
# StarStatePair(primary, secondary) with structural relationship properties

Multiple Star Systems

from moira.facade import (
    multiple_star, list_multiple_stars, multiple_stars_by_type,
    angular_separation_at, position_angle_at,
    is_resolvable, dominant_component, combined_magnitude, components_at,
    sirius_ab_separation_at, sirius_b_resolvable,
    castor_separation_at, alpha_cen_separation_at,
    MultipleStarSystem, StarComponent, OrbitalElements, MultiType,
)
FunctionReturnsDescription
multiple_star(name)MultipleStarSystemRetrieve a multiple-star system by name
list_multiple_stars()list[str]All catalog system names
multiple_stars_by_type(multi_type)list[MultipleStarSystem]Filter by system type
angular_separation_at(system, jd_tt)floatCurrent angular separation (arcsec)
position_angle_at(system, jd_tt)floatCurrent position angle (°)
is_resolvable(system, jd_tt, aperture_mm)boolTrue if resolvable with aperture
dominant_component(system)StarComponentBrighter/primary component
combined_magnitude(system)floatCombined visual magnitude
components_at(system, jd_tt)dictFull component/separation snapshot for the system
sirius_ab_separation_at(jd_tt)floatSirius A–B separation (arcsec)
sirius_b_resolvable(jd_tt, aperture_mm=200)boolTrue if Sirius B is resolvable
castor_separation_at(jd_tt)floatCastor AB separation (arcsec)
alpha_cen_separation_at(jd_tt)floatα Cen A–B separation (arcsec)

MultiType constants: VISUAL WIDE SPECTROSCOPIC OPTICAL


15. Eclipses & Phenomena

Solar & Lunar Eclipses

from moira.facade import (
    EclipseData, EclipseEvent, EclipseType, EclipseCalculator,
    SolarBodyCircumstances, SolarEclipseLocalCircumstances,
    LocalContactCircumstances, LunarEclipseAnalysis, LunarEclipseLocalCircumstances,
)

calc = EclipseCalculator(reader=get_reader())
data = calc.calculate(dt)           # EclipseData

EclipseData fields

FieldTypeDescription
sun_longitudefloatSun longitude at the evaluated moment
moon_longitudefloatMoon longitude at the evaluated moment
node_longitudefloatNode longitude at the evaluated moment
moon_latitudefloatMoon latitude relative to the ecliptic
eclipse_typeEclipseTypeTOTAL_SOLAR / ANNULAR / PARTIAL_SOLAR / PENUMBRAL / PARTIAL_LUNAR / TOTAL_LUNAR
is_eclipse_seasonboolWhether the Sun is close enough to the nodes for eclipse season
is_solar_eclipseboolSolar eclipse flag
is_lunar_eclipseboolLunar eclipse flag
eclipse_magnitudefloatComputed eclipse magnitude
saros_indexfloatSaros cycle position/index
metonic_yearfloatMetonic cycle position
moon_distance_kmfloatGeocentric Moon distance in kilometers
galactic_center_longitudefloatGalactic center longitude reference
sun_apparent_radiusfloatApparent solar radius
moon_apparent_radiusfloatApparent lunar radius
earth_shadow_apparent_radiusfloatApparent umbral radius
earth_penumbra_apparent_radiusfloatApparent penumbral radius
sun_stoneintAubrey-stone style solar index
moon_stoneintAubrey-stone style lunar index
node_stoneintAubrey-stone style node index
south_node_stoneintAubrey-stone style south-node index
angular_separation_3dfloat3D Sun/Moon angular separation
solar_topocentric_separationfloatTopocentric Sun/Moon separation
sun_node_distancefloatDistance from Sun to node
metonic_is_resetboolWhether the Metonic cycle resets here
moon_parallaxfloatLunar parallax
sun_sideintStonehenge side index for the Sun
sun_pos_in_sideintPosition of the Sun within the side index

NASA-compatible lunar eclipse API

from moira.facade import (
    NasaLunarEclipseContacts, NasaLunarEclipseEvent,
    next_nasa_lunar_eclipse, previous_nasa_lunar_eclipse,
    translate_lunar_eclipse_event,
)

event = next_nasa_lunar_eclipse(jd_start, reader=reader)
prev  = previous_nasa_lunar_eclipse(jd_start, reader=reader)

The default compatibility method is nasa_shadow_axis_apparent_sun_moon. It evaluates the Sun and Moon from the same reception-epoch Earth state, applies reception light-time and then annual aberration to both directions, and omits gravitational deflection, topocentric parallax, and atmospheric refraction. NasaLunarEclipseEvent.canon_method and .source_model identify this policy. The legacy geometric and retarded method identifiers remain explicitly selectable on the lower-level canon functions; they are not ambient fallbacks.

This method is validated as a DE441/LE441 compatibility computation against NASA/GSFC products that declare VSOP87/ELP2000-85. It does not claim exact ephemeris parity. Existing function signatures and result-vessel fields are unchanged, while the compatibility method label and numerical results intentionally reflect the repaired apparent reduction.

Planetary Phenomena

from moira.facade import (
    greatest_elongation, perihelion, aphelion,
    next_moon_phase, moon_phases_in_range,
    PhenomenonEvent,
)
FunctionReturnsDescription
greatest_elongation(body, jd_start, direction="east", reader=None, max_days=600.0)PhenomenonEvent | NoneNext greatest elongation of Mercury or Venus in the requested direction
perihelion(body, jd_start, reader=None, max_days=None)PhenomenonEvent | NoneNext perihelion passage
aphelion(body, jd_start, reader=None, max_days=None)PhenomenonEvent | NoneNext aphelion passage
next_moon_phase(phase_name, jd_start, reader=None)PhenomenonEventNext exact named moon phase ("New Moon", "First Quarter", "Full Moon", etc.)
moon_phases_in_range(jd_start, jd_end, reader=None)list[PhenomenonEvent]All eight standard moon phases in a date range

PhenomenonEvent: body, phenomenon, jd_ut, value.

Occultations

from moira.facade import (
    close_approaches, lunar_occultation, lunar_star_occultation, all_lunar_occultations,
    lunar_occultation_path_topology, lunar_occultation_path_topology_at,
    lunar_star_occultation_path_topology, lunar_star_occultation_path_topology_at,
    CloseApproach, LunarOccultation, OccultationPathTopology,
)
FunctionReturnsDescription
close_approaches(body_a, body_b, jd_start, jd_end, max_sep_deg=1.0, step_days=0.5, reader=None)list[CloseApproach]Close conjunctions within the requested separation threshold
lunar_occultation(body, jd_start, jd_end, reader=None)list[LunarOccultation]Moon occultation events for a planet
lunar_star_occultation(star_lon, star_lat, star_name, jd_start, jd_end, step_days=0.25, observer_lat=None, observer_lon=None, observer_elev_m=0.0, reader=None)list[LunarOccultation]Moon occultation of a fixed star at a supplied ecliptic position
all_lunar_occultations(jd_start, jd_end, planets=None, reader=None)list[LunarOccultation]Lunar occultations for the default planet set or a supplied planet list
lunar_occultation_path_topology(target, jd_start, jd_end, step_days=0.25, sample_count=65, observer_elev_m=0.0, reader=None)list[OccultationPathTopology]Detailed nominal mean-limb planetary bands with intrinsic left/right limits and exact-pole contacts
lunar_occultation_path_topology_at(target, jd_mid, *, sample_count=65, observer_elev_m=0.0, reader=None)OccultationPathTopologyDetailed nominal mean-limb planetary topology at one greatest epoch
lunar_star_occultation_path_topology(star_lon, star_lat, star_name, jd_start, jd_end, step_days=0.25, sample_count=65, observer_elev_m=0.0, reader=None)list[OccultationPathTopology]Detailed nominal mean-limb fixed-star bands with polar-safe topology
lunar_star_occultation_path_topology_at(star_lon, star_lat, star_name, jd_mid, *, sample_count=65, observer_elev_m=0.0, reader=None)OccultationPathTopologyDetailed nominal mean-limb fixed-star topology at one greatest epoch

OccultationPathTopology.observer_elevation_m preserves the requested observer elevation used by every center and boundary solve. Its intrinsic left and right identities follow increasing UT1 along the center track and are not geographic north/south labels. This detailed two-sided product admits only lunar_limb_model="SPHERICAL_MEAN_LIMB"; profile-conditioned graze products remain separate.

The two range searches admit at most 400 days, 0 < step_days <= 0.25, and 4096 coarse cells; step_days is a maximum cell width. Their fixed internal contact search is independent of the output sample_count. Planetary topology accepts Mercury through Pluto except Earth and excludes the Sun; use the solar eclipse surfaces for Sun/Moon occultation geometry. The summary duration is the local duration at the fixed greatest site, not global footprint lifetime. Range results require an unconstrained greatest instant inside the requested interval by more than max(4e-8 d, 8 binary64 ULP) at each boundary. Optimizer witnesses coalesce only through overlapping open positive-clearance support; tangent-only contact remains separate. Each connected component's strongest time-local maximum is resolved on a private at-most-30-minute lattice with a 128-cell fail-closed budget before the range gate is reapplied. Fixed-star labels are canonical nonblank labels without surrounding whitespace or Solar System body identities. Observer elevation must be at least -6378.137 * (1 - 1/298.257223563) * 1000 m, the negative WGS 84 semi-minor axis. That is a computational-envelope floor, not an observing-site claim. At positive heights large enough that the observer sphere reaches a body, candidate admission switches from asin(R/d) to the conservative 180 degree direction-reversal bound; no arbitrary upper-height policy is hidden.

Sothic Cycle (Egyptian calendar)

from moira.facade import (
    sothic_rising, sothic_epochs, sothic_drift_rate,
    egyptian_civil_date, days_from_1_thoth, predicted_sothic_epoch_year,
    sothic_chart_condition_profile, sothic_condition_network_profile,
    EgyptianDate, SothicEntry, SothicEpoch,
    EGYPTIAN_MONTHS, EGYPTIAN_SEASONS, EPAGOMENAL_BIRTHS,
    HISTORICAL_SOTHIC_EPOCHS,
    SothicComputationPolicy,
)
FunctionReturnsDescription
sothic_rising(latitude, longitude, year_start, year_end, epoch_jd=1772027.5, arcus_visionis=10.0, policy=None)list[SothicEntry]Sirius heliacal rising entries across a year range
sothic_epochs(latitude, longitude, year_start, year_end, epoch_jd=1772027.5, tolerance_days=1.0, arcus_visionis=10.0, policy=None)list[SothicEpoch]New Year coincidences across a year range
sothic_drift_rate(entries)floatDrift rate derived from a list[SothicEntry]
egyptian_civil_date(jd, epoch_jd=1772027.5, policy=None)EgyptianDateWandering civil calendar date
days_from_1_thoth(jd, epoch_jd=1772027.5)floatDays elapsed since the last 1 Thoth
predicted_sothic_epoch_year(known_epoch_year, n_cycles, cycle_length_years=1460.0, policy=None)floatPredicted year after one or more Sothic cycles

HISTORICAL_SOTHIC_EPOCHS: list of known historical epoch dates. EPAGOMENAL_BIRTHS: five epagomenal days and their mythological births.


16. Harmograms — Spectral Harmonic Analysis

from moira.harmograms import (
    # Core computation
    harmonic_vector, point_set_harmonic_vector,
    zero_aries_parts_harmonic_vector, parts_from_zero_aries,
    intensity_function_spectrum, project_harmogram_strength,
    harmogram_trace,
    # Vessels
    HarmonicDomain,
    HarmonicVectorComponent, PointSetHarmonicVector,
    ZeroAriesPart, ZeroAriesPartsSet, ZeroAriesPartsHarmonicVector,
    IntensitySpectrumComponent, IntensityFunctionSpectrum,
    HarmogramProjectionTerm, HarmogramProjection, HarmogramDominantTerm,
    HarmogramTraceSample, HarmogramTraceSeries, HarmogramTrace,
    IntensitySpectrumComparisonTerm, IntensitySpectrumComparison,
    HarmogramTraceSeriesComparisonSample, HarmogramTraceSeriesComparison,
    # Policies
    HarmogramPolicy, HarmogramIntensityPolicy, HarmogramSamplingPolicy,
    PointSetHarmonicVectorPolicy, ZeroAriesPartsPolicy,
    # Enums
    HarmonicVectorNormalizationMode, ZeroAriesPairConstructionMode, SelfPairMode,
    HarmogramIntensityFamily, HarmogramOrbMode, GaussianWidthParameterMode,
    HarmogramOrbScalingMode, HarmogramSymmetryMode,
    IntensityNormalizationMode, IntensitySpectrumRealizationMode,
    HarmogramProjectionRealizationMode, HarmogramSamplingMode,
    HarmogramOutputMode, HarmogramChartDomain, HarmogramTraceFamily,
    # Research tools
    dominant_harmonic_contributors, compare_intensity_spectra, compare_trace_series,
)

The Harmograms engine is a research-facing spectral harmonic analysis subsystem. It is deliberately distinct from moira.harmonics (which computes classical harmonic chart positions for individual bodies). This package deals with harmonic spectra — the Fourier-style decomposition of an entire point set's angular distribution, not single-body positions.

This module does not generate astrological positions. It analyzes collections of longitudes (already computed by the position engines) for their harmonic structure.

The central concepts:

  • Harmonic vector — the resultant vector of a point set projected onto the unit circle at harmonic H. Amplitude near 1.0 means the points cluster at H-fold symmetry; near 0.0 means they are uniformly distributed.
  • Zero-Aries parts — pairwise angular differences between all bodies, projected to [0°, 360°). These are the raw material for the harmogram spectrum.
  • Intensity function spectrum — the spectral distribution of harmonic energy across a defined harmonic domain.
  • Harmogram trace — a time-domain trace of harmonic strength as the sky moves.
  • Harmogram projection — decomposes a total strength value back onto per-harmonic contributions.

HarmonicDomain — harmonic range specifier

domain = HarmonicDomain(harmonic_start=1, harmonic_stop=12)
# .harmonics → tuple of all integers in [start, stop]
FieldTypeDefaultDescription
harmonic_startint1First harmonic (must be ≥ 1)
harmonic_stopint12Last harmonic (must be ≥ start)

Property: harmonicstuple[int, ...] of all harmonics in the range.


Core Data Vessels

HarmonicVectorComponent — single harmonic result

FieldTypeDescription
harmonicintHarmonic number (≥ 1)
amplitudefloatResultant amplitude (≥ 0); 1.0 = perfect clustering at this harmonic
phase_degfloatPhase of the resultant vector (°), [0°, 360°)

Property: amplitude_squared.

PointSetHarmonicVector — harmonic vector for a named point set

FieldTypeDescription
policyPointSetHarmonicVectorPolicyNormalization and domain policy used
body_namestuple[str, ...]Names of the contributing points
point_countintNumber of points
harmonic_zero_amplitudefloatH=0 amplitude (reflects normalization)
componentstuple[HarmonicVectorComponent, ...]One component per harmonic in the domain

Method: get_component(harmonic)HarmonicVectorComponent.

ZeroAriesPart — one pairwise angular difference

FieldTypeDescription
source_namestrSource body
target_namestrTarget body
longitude_degfloatAngular difference projected to [0°, 360°)

ZeroAriesPartsSet — collection of pairwise parts

FieldTypeDescription
policyZeroAriesPartsPolicyConstruction policy
source_body_namestuple[str, ...]Source body names
target_body_namestuple[str, ...]Target body names
partstuple[ZeroAriesPart, ...]All constructed parts

Properties: source_point_count, target_point_count, parts_count.

ZeroAriesPartsHarmonicVector — harmonic vector for a parts set

FieldTypeDescription
vector_policyPointSetHarmonicVectorPolicyNormalization policy
parts_policyZeroAriesPartsPolicyParts construction policy
source_body_namestuple[str, ...]Source bodies
target_body_namestuple[str, ...]Target bodies
parts_countintNumber of parts contributing
harmonic_zero_amplitudefloatH=0 amplitude
componentstuple[HarmonicVectorComponent, ...]Per-harmonic components

Method: get_component(harmonic)HarmonicVectorComponent.


Intensity Function Spectrum

The intensity function spectrum evaluates how strongly the point set concentrates at each harmonic. Unlike the harmonic vector (which uses raw resultants), the intensity function applies a bell-shaped orb around each aspect, making it sensitive to near-aspect clustering.

spectrum = intensity_function_spectrum(longitudes, harmonic, policy=...)
# IntensityFunctionSpectrum

IntensitySpectrumComponent fields

FieldTypeDescription
harmonicintHarmonic number
amplitudefloatIntensity amplitude at this harmonic (≥ 0)
phase_degfloatPhase (°), [0°, 360°)

IntensityFunctionSpectrum fields

FieldTypeDescription
policyHarmogramIntensityPolicyIntensity policy used
harmonic_numberintThe primary harmonic being analyzed
realization_modeIntensitySpectrumRealizationModeComputation method
harmonic_zero_amplitudefloatH=0 reference amplitude
componentstuple[IntensitySpectrumComponent, ...]Per-harmonic components

Method: get_component(harmonic)IntensitySpectrumComponent.


Harmogram Projection

Projects a harmogram strength back onto per-harmonic contributions, showing which harmonics drive the total score.

proj = project_harmogram_strength(source_vector, intensity_spectrum, policy=...)
# HarmogramProjection

HarmogramProjectionTerm fields (one per harmonic)

FieldTypeDescription
harmonicintHarmonic number
source_amplitudefloatSource vector amplitude at this harmonic
source_phase_degfloatSource vector phase at this harmonic
intensity_amplitudefloatIntensity spectrum amplitude at this harmonic
intensity_phase_degfloatIntensity spectrum phase at this harmonic
signed_contributionfloatSigned contribution to total strength (positive = reinforcing)

HarmogramProjection fields

FieldTypeDescription
source_vectorPointSetHarmonicVector | ZeroAriesPartsHarmonicVectorSource point set
intensity_spectrumIntensityFunctionSpectrumIntensity spectrum used
normalization_modeHarmonicVectorNormalizationModeNormalization applied
realization_modeHarmogramProjectionRealizationModeComputation method
harmonic_zero_contributionfloatH=0 contribution
total_strengthfloatTotal projected strength (sum of all term contributions)
termstuple[HarmogramProjectionTerm, ...]One term per harmonic

Method: get_term(harmonic)HarmogramProjectionTerm.


Harmogram Trace

A time-domain trace of projected harmonic strength across a sequence of sky epochs.

samples = [
    {
        "time": 2451545.0,
        "positions": [
            {"name": "Sun", "degree": 280.0},
            {"name": "Moon", "degree": 223.0},
        ],
    },
    {
        "time": 2451546.0,
        "positions": [
            {"name": "Sun", "degree": 281.0},
            {"name": "Moon", "degree": 236.0},
        ],
    },
]
trace = harmogram_trace(samples, harmonic_numbers=(1, 2, 3, 4, 5))
# HarmogramTrace; each trace.series item exposes .harmonic_number and .strengths

HarmogramTraceSample fields (one per epoch)

FieldTypeDescription
sample_indexintIndex into the epoch sequence (≥ 0)
sample_timefloatJD of this sample
source_vectorZeroAriesPartsHarmonicVectorParts vector for this epoch
projectionHarmogramProjectionFull projection at this epoch
total_strengthfloatTotal projected strength at this epoch

HarmogramTraceSeries fields

FieldTypeDescription
harmonic_numberintThe harmonic being traced
intensity_spectrumIntensityFunctionSpectrumShared intensity spectrum
samplestuple[HarmogramTraceSample, ...]All samples in epoch order

Property: strengthstuple[float, ...] of total_strength per sample.

HarmogramTrace fields

FieldTypeDescription
policyHarmogramPolicyGoverning policy
interval_startfloatJD start of the trace interval
interval_stopfloatJD end of the trace interval
sample_timestuple[float, ...]All epoch JDs in order
seriestuple[HarmogramTraceSeries, ...]One series per harmonic in the output

Research Tools

dominant = dominant_harmonic_contributors(projection, top_n=5)
# list[HarmogramDominantTerm]  — harmonics ranked by |signed_contribution|

HarmogramDominantTerm fields

FieldTypeDescription
harmonicintHarmonic number
absolute_contributionfloatAbsolute value of the contribution (≥ 0)
signed_contributionfloatSigned contribution (positive = reinforcing)
cmp = compare_intensity_spectra(spectrum_a, spectrum_b)
# IntensitySpectrumComparison

IntensitySpectrumComparison fields

FieldTypeDescription
leftIntensityFunctionSpectrumFirst spectrum
rightIntensityFunctionSpectrumSecond spectrum
max_absolute_deltafloatMaximum per-harmonic amplitude difference
termstuple[IntensitySpectrumComparisonTerm, ...]Per-harmonic delta terms

IntensitySpectrumComparisonTerm: harmonic, left_amplitude, right_amplitude, amplitude_delta.

trace_cmp = compare_trace_series(series_a, series_b)
# HarmogramTraceSeriesComparison

HarmogramTraceSeriesComparison: left, right, max_absolute_delta, samples (each: sample_index, sample_time, left_strength, right_strength, delta).


Policy Objects

HarmogramPolicy — master policy

FieldTypeDefaultDescription
point_set_policyPointSetHarmonicVectorPolicydefaultNormalization and domain for point sets
parts_policyZeroAriesPartsPolicydefaultZero-Aries parts construction
intensity_policyHarmogramIntensityPolicydefaultIntensity function shape
sampling_policyHarmogramSamplingPolicydefaultTrace sampling
output_modeHarmogramOutputModeMULTI_HARMONIC_FAMILYSingle vs multi-harmonic output
chart_domainHarmogramChartDomainDYNAMIC_SKY_ONLY_TRACEWhat the trace represents
trace_familyHarmogramTraceFamilyDYNAMIC_ZERO_ARIES_PARTSParts construction family

chart_domain and trace_family must be consistent (enforced by __post_init__).

HarmogramIntensityPolicy — intensity function shape

FieldTypeDefaultDescription
familyHarmogramIntensityFamilyCOSINE_BELL_HARMONIC_ASPECTSAspect weighting shape
include_conjunctionboolTrueWhether to include H-fold conjunctions
orb_modeHarmogramOrbModeCOSINE_BELLOrb weighting function (must match family)
orb_scaling_modeHarmogramOrbScalingModeEQUATED_TO_HARMONIC_ONEHow orb scales with harmonic
symmetry_modeHarmogramSymmetryModeSTAR_SYMMETRICStar-symmetric or conjunction-excluded
normalization_modeIntensityNormalizationModePEAK_ONESpectrum normalization
harmonic_domainHarmonicDomainH1–H12Harmonics evaluated
orb_width_degfloat24.0Orb width at H=1 (°)
gaussian_width_parameter_modeGaussianWidthParameterModeFWHMGaussian width interpretation
gaussian_width_degfloat | NoneNoneGaussian width (required for Gaussian family)
sample_countint4096Quadrature sample count (≥ 256)

HarmogramIntensityFamily values: COSINE_BELL_HARMONIC_ASPECTS TOP_HAT_HARMONIC_ASPECTS TRIANGULAR_HARMONIC_ASPECTS GAUSSIAN_HARMONIC_ASPECTS

HarmogramOrbMode must match the family: COSINE_BELL TOP_HAT TRIANGULAR GAUSSIAN

HarmogramSymmetryMode values: STAR_SYMMETRIC (includes conjunction) CONJUNCTION_EXCLUDED

PointSetHarmonicVectorPolicy

FieldTypeDefaultDescription
normalization_modeHarmonicVectorNormalizationModeMEAN_RESULTANTRAW_SUM or MEAN_RESULTANT
harmonic_domainHarmonicDomainH1–H12Harmonics computed

ZeroAriesPartsPolicy

FieldTypeDefaultDescription
pair_construction_modeZeroAriesPairConstructionModeORDEREDORDERED (A→B ≠ B→A) or UNORDERED
self_pair_modeSelfPairModeINCLUDEWhether to include self-pairs (A→A = 0°)

HarmogramChartDomain and HarmogramTraceFamily constraints

chart_domainCompatible trace_family
DYNAMIC_SKY_ONLY_TRACEDYNAMIC_ZERO_ARIES_PARTS
TRANSIT_TO_NATAL_TRACETRANSIT_TO_NATAL_ZERO_ARIES_PARTS
DIRECTED_OR_PROGRESSED_TRACEDIRECTED_TO_NATAL_ZERO_ARIES_PARTS or PROGRESSED_TO_NATAL_ZERO_ARIES_PARTS

Example: natal chart H4 spectral analysis

from moira.harmograms import (
    parts_from_zero_aries, zero_aries_parts_harmonic_vector,
    intensity_function_spectrum, project_harmogram_strength,
    dominant_harmonic_contributors,
    HarmonicDomain, HarmogramIntensityPolicy, PointSetHarmonicVectorPolicy,
)
from moira.facade import Moira
from datetime import datetime, timezone

m = Moira()
chart = m.chart(datetime(1988, 4, 4, 14, 30, tzinfo=timezone.utc))
lons = chart.longitudes(include_nodes=False)  # dict[str, float]

domain = HarmonicDomain(harmonic_start=1, harmonic_stop=16)

# Build Zero-Aries parts from the natal chart
parts = parts_from_zero_aries(lons)

# Harmonic vector for H4
vec_policy = PointSetHarmonicVectorPolicy(harmonic_domain=domain)
hvec = zero_aries_parts_harmonic_vector(parts, 4, policy=vec_policy)
print(f"H4 amplitude: {hvec.get_component(4).amplitude:.4f}")

# Intensity spectrum at H4
int_policy = HarmogramIntensityPolicy(harmonic_domain=domain, orb_width_deg=18.0)
spectrum = intensity_function_spectrum(lons, 4, policy=int_policy)

# Project — which harmonics contribute most?
proj = project_harmogram_strength(hvec, spectrum)
print(f"Total strength: {proj.total_strength:.4f}")

dominant = dominant_harmonic_contributors(proj, top_n=4)
for term in dominant:
    print(f"  H{term.harmonic}: {term.signed_contribution:+.4f}")

17. Constellation Oracle

# Import directly from the sub-module for the constellation you need:
from moira.constellations.stars_orion import (
    RIGEL, BETELGEUSE, BELLATRIX, ALNILAM, ALNITAK, MINTAKA, SAIPH,
    stars_in_orion, orion_star_at,
    rigel_at, betelgeuse_at, bellatrix_at,  # etc.
)

The Constellation Oracle groups the fixed-star catalog by IAU constellation. Each of the 48 sub-modules provides:

  • Named string constants for each catalogued star (e.g. RIGEL = "Rigel")
  • A constellation-scoped dispatcher (orion_star_at(name, jd_tt)) that validates names against the constellation and calls moira.stars.star_at
  • Per-star convenience functions (rigel_at(jd_tt), betelgeuse_at(jd_tt))
  • List helpers (stars_in_orion(), available_in_orion())

No symbols are re-exported from moira.constellations.__init__; always import from the specific sub-module.

Module naming convention

Sub-modules follow the pattern moira.constellations.stars_<constellation>, where <constellation> is the IAU abbreviation in lowercase:

stars_andromeda    stars_aquarius    stars_aquila      stars_aries
stars_bootes       stars_cancer      stars_canis_major stars_canis_minor
stars_capricorn    stars_carina      stars_cassiopeia  stars_centaurus
stars_corvus       stars_crater      stars_crux        stars_cygnus
stars_draco        stars_gemini      stars_hercules    stars_hydra
stars_leo          stars_libra       stars_lyra        stars_ophiuchus
stars_orion        stars_pegasus     stars_perseus     stars_pisces
stars_sagittarius  stars_scorpius    stars_taurus      stars_ursa_major
stars_ursa_minor   stars_virgo       ... (48 total)

Per-module interface pattern

Every constellation module exposes the same interface pattern:

from moira.constellations.stars_scorpius import (
    ANTARES, SHAULA, LESATH, DSCHUBBA, GRAFFIAS,
    # ... other star name constants

    stars_in_scorpius,       # list[str] — all catalogued star names
    available_in_scorpius,   # list[str] — names resolvable right now
    scorpius_star_at,        # (name, jd_tt) → FixedStar

    antares_at,              # (jd_tt) → FixedStar
    shaula_at,
    # ... one function per catalogued star
)

Usage

from moira.facade import jd_from_datetime, utc_to_tt
from moira.constellations.stars_taurus import (
    ALDEBARAN, ALCYONE, PLEIADES_CLUSTER,
    taurus_star_at, aldebaran_at, alcyone_at,
    stars_in_taurus,
)
from datetime import datetime, timezone

jd = utc_to_tt(jd_from_datetime(datetime(2026, 4, 7, tzinfo=timezone.utc)))

# By name via dispatcher:
ald = taurus_star_at(ALDEBARAN, jd)
print(f"Aldebaran: {ald.longitude:.4f}°  mag {ald.magnitude}")

# Via convenience function:
alc = alcyone_at(jd)

# List all Taurus stars:
print(stars_in_taurus())

All position computation delegates to moira.stars.star_at — the constellation oracle adds no positional logic of its own.


18. Calendar & Time

from moira.facade import (
    jd_from_datetime, datetime_from_jd, julian_day, calendar_from_jd,
    calendar_datetime_from_jd, format_jd_utc, safe_datetime_from_jd,
    greenwich_mean_sidereal_time, local_sidereal_time, delta_t,
    CalendarDateTime,
)
FunctionSignatureDescription
jd_from_datetime(dt: datetime) → floattimezone-aware datetime → UTC-coded civil JD; naïve datetimes raise ValueError
datetime_from_jd(jd: float) → datetimeUTC-coded civil JD → UTC datetime
julian_day(year, month, day, hour=0.0) → floatCalendar date → JD
calendar_from_jd(jd: float) → CalendarDateTimeJD → BCE-safe calendar breakdown
calendar_datetime_from_jd(jd: float) → CalendarDateTimeAlias for calendar_from_jd
format_jd_utc(jd: float) → strHuman-readable UTC string
safe_datetime_from_jd(jd: float) → datetime | NoneReturns None for out-of-range JDs
greenwich_mean_sidereal_time(jd_ut: float) → floatGMST in degrees
local_sidereal_time(jd_ut, longitude, dpsi=None, obliq=None) → floatLAST in degrees
delta_t(year: float) → floatΔT in seconds for a decimal year

CalendarDateTime fields

FieldTypeDescription
yearintProleptic Gregorian year (negative for BCE)
monthintMonth (1–12)
dayintDay (1–31)
hourfloatDecimal UT hour
is_bceboolTrue when year < 1 (BCE convention)
from moira.facade import jd_from_datetime, calendar_from_jd
import datetime

jd = jd_from_datetime(datetime.datetime(1988, 4, 4, 14, 30,
                                        tzinfo=datetime.timezone.utc))
print(jd)           # 2447255.104166...

cal = calendar_from_jd(jd)
print(cal.year, cal.month, cal.day, cal.hour)

# For BCE dates (negative year numbers):
jd_cleopatra = 1705426.0   # approx 69 BCE
cal2 = calendar_from_jd(jd_cleopatra)
print(cal2.is_bce, cal2.year)   # True, -68 (astronomical year numbering)

Obliquity & nutation

from moira.facade import mean_obliquity, true_obliquity, nutation

# All take jd_tt (Terrestrial Time)
from moira.facade import jd_from_datetime
from moira.julian import utc_to_tt
jd_utc = jd_from_datetime(dt)
jd_tt = utc_to_tt(jd_utc)

obl_mean = mean_obliquity(jd_tt)         # degrees
obl_true = true_obliquity(jd_tt)         # degrees (mean + nutation correction)
dpsi, deps = nutation(jd_tt)             # nutation in longitude and obliquity (arcsec)

Ayanamsa

from moira.facade import ayanamsa, tropical_to_sidereal, sidereal_to_tropical, list_ayanamsa_systems, Ayanamsa

offset       = ayanamsa(jd_ut, Ayanamsa.LAHIRI)                       # degrees to subtract
sidereal_lon = tropical_to_sidereal(tropical_lon, jd_ut, Ayanamsa.LAHIRI)
tropical_lon = sidereal_to_tropical(sidereal_lon, jd_ut, Ayanamsa.LAHIRI)
all_systems  = list_ayanamsa_systems()                                 # list of all Ayanamsa.* constants

19. Policy Objects

Every computational pillar that has configurable behavior exposes a frozen dataclass policy. Policies use sensible defaults and can be constructed with keyword arguments for the parameters you want to override.

Pattern

# Using the default:
result = some_function(inputs)

# Customizing:
from moira.facade import AspectPolicy
policy = AspectPolicy(include_minor=False)
result = some_function(inputs, policy=policy)

Pillar policies reference

Policy classModuleKey parameters
AspectPolicyaspectsorb_table, min_tier, include_minor, motion_threshold
HousePolicyhousespolar_fallback, unknown_system
DignityComputationPolicydignitiesessential doctrine (traditional_classic_7 default, modern_co_rulers opt-in), mercury_sect_model, independent include_hayz / include_halb, include_oriental_occidental, solar condition, accidental dignity
VedicDignityPolicyvedic dignitiesplanetary friendship and compound-relationship policy
AstrodynePolicyChurch of Light Astrodynesfixed one-degree dignity band, magnitude parallels, Mercury two-stage orb, mutual-reception bonus
LotsComputationPolicylotsreversal_kind, derived_reference, external_reference
ProgressionComputationPolicyprogressionstime_key, direction, house_frame
TransitComputationPolicytransitssearch, return_search, syzygy_search
TransitSearchPolicytransitsstep_days, max_iterations, exact_threshold
SynastryComputationPolicysynastryaspect_policy, overlay_policy, composite_policy, davison_policy
JaiminiPolicyjaiminikaraka assignment and tie-break policy
PanchangaPolicypanchangapanchanga computation and classification policy
AshtottariPolicyalternate dashaAshtottari year-basis and sequence policy
YoginiPolicyalternate dashaYogini year-basis and sequence policy
AshtakavargaPolicyashtakavargashodhana and sign-strength interpretation policy
ShadbalaPolicyshadbalasufficiency thresholds and component interpretation policy
PatternComputationPolicypatternsselection, stellium, orb_factor, dominant_only
TimelordComputationPolicytimelordsfirdaria_year_policy, zr_year_policy
VimshottariComputationPolicydashayear_policy, ayanamsa_policy
FixedStarComputationPolicyfixed_starslookup_policy, heliacal_search_policy
VarStarPolicyvariable_starseclipse_threshold
UnifiedStarComputationPolicystarsmerge_policy
SothicComputationPolicysothiccalendar_policy, heliacal_policy, epoch_policy

Doctrine constants

from moira.facade import CANONICAL_ASPECTS, DEFAULT_POLICY, ASPECT_TIERS
from moira.facade import FIRDARIA_DIURNAL, FIRDARIA_NOCTURNAL, FIRDARIA_NOCTURNAL_BONATTI
from moira.facade import CHALDEAN_ORDER, MINOR_YEARS
from moira.facade import VIMSHOTTARI_YEARS, VIMSHOTTARI_SEQUENCE, VIMSHOTTARI_TOTAL
from moira.facade import VIMSHOTTARI_YEAR_BASIS, VIMSHOTTARI_LEVEL_NAMES
from moira.facade import EGYPTIAN_MONTHS, EGYPTIAN_SEASONS, EPAGOMENAL_BIRTHS
from moira.facade import HISTORICAL_SOTHIC_EPOCHS
from moira.facade import CIRCLE_TYPES, DEFAULT_PARAN_POLICY
from moira.facade import HARMONIC_PRESETS, MANSIONS

20. moira.sky — Strict Astronomy API

moira.sky is a sovereign astronomical computation surface. Every quantity is derived from Moira's own verified pipeline — the DE441 ephemeris, IAU 2006 precession, IAU 2000A nutation, and IERS ΔT. It exposes the low-level astronomy substrate that underpins the astrological tier modules without any application-layer coupling.

import moira.sky
from moira.sky import time, position, frames, visibility
from moira.sky import bodies, observation, galactic, events, eclipse, occultation

Each submodule is a self-contained import surface. Submodules do not import from each other.

Note: Submodules marked [stub] in the moira.sky package docstring raise NotImplementedError for functions not yet fully implemented. They are documented and importable, but do not silently return incorrect results.


moira.sky.time — Time Systems and ΔT

from moira.sky.time import (
    CalendarDateTime, julian_day, calendar_from_jd, calendar_datetime_from_jd,
    jd_from_datetime, decimal_year, decimal_year_from_jd, centuries_from_j2000,
    utc_to_tt, utc_to_ut1, ut_to_tt, tt_to_ut, tt_to_tdb,
    earth_rotation_angle, greenwich_mean_sidereal_time,
    apparent_sidereal_time, apparent_sidereal_time_at, local_sidereal_time,
    DeltaTPolicy, delta_t, delta_t_from_jd,
    DeltaTBreakdown, DeltaTDistribution,
    delta_t_breakdown, delta_t_distribution,
    secular_trend, core_delta_t, cryo_delta_t, fluid_lowfreq,
)

Calendar and Julian Day

FunctionSignatureDescription
julian_day(year, month, day, hour=0.0) → floatCalendar date → JD
calendar_from_jd(jd: float) → CalendarDateTimeJD → BCE-safe calendar breakdown
calendar_datetime_from_jd(jd: float) → CalendarDateTimeAlias for calendar_from_jd
jd_from_datetime(dt: datetime) → floattimezone-aware datetime → UTC-coded civil JD; naïve datetimes raise ValueError
decimal_year(year, month=1) → floatNASA-compatible month-midpoint decimal year
decimal_year_from_jd(jd: float) → floatJD → NASA-compatible month-midpoint decimal year
centuries_from_j2000(jd_tt: float) → floatJulian centuries from J2000.0

Time scale conversions

FunctionSignatureDescription
utc_to_tt(jd_utc: float) → floatUTC → TT through TAI and the leap-second table
utc_to_ut1(jd_utc: float) → floatUTC → UT1 through admitted DUT1 data, with explicit historical/fallback policy
ut_to_tt(jd_ut: float) → floatUT1 → Terrestrial Time (adds ΔT)
tt_to_ut(jd_tt: float) → floatTT → UT1
tt_to_tdb(jd_tt: float) → floatTT → Barycentric Dynamical Time

Hybrid and physical JD transforms use an exact private fraction between successive January 1 boundaries; they do not use the public NASA month-midpoint coordinate. Before the final civil day of 1971, civil JDs retain Moira's historical UT1-proxy interpretation. That final day uses a monotonic smoothstep into the 1972-01-01 atomic rule, with the private inverse solving the same handoff. Private UT1-to-UTC formatting inverts the within-day UT1-TAI relation so a positive leap second is not smeared across the preceding day.

Earth rotation and sidereal time

FunctionSignatureDescription
earth_rotation_angle(jd_ut: float) → floatERA in degrees
greenwich_mean_sidereal_time(jd_ut: float) → floatGMST in degrees
apparent_sidereal_time(jd_ut: float) → floatGAST in degrees
apparent_sidereal_time_at(jd_ut, dpsi, obliq) → floatGAST with supplied nutation terms
local_sidereal_time(jd_ut, longitude, dpsi=None, obliq=None) → floatLAST in degrees

ΔT — standard

FunctionSignatureDescription
delta_t(year: float) → floatSource-priority ΔT in seconds for a decimal year, preserving each admitted source's published basis
delta_t_from_jd(jd_ut: float) → floatΔT in seconds from a UT1 JD; admitted EOP rows govern in coverage, outer reconciliation tapers locally over one Julian year, and internal gaps reconcile only their boundaries

ΔT — source-priority accounting and scenario policy

bd = delta_t_breakdown(year)
dist = delta_t_distribution(year)
FunctionSignatureDescription
delta_t_breakdown(year: float) → DeltaTBreakdownSource/scenario total in the stable additive accounting vessel
delta_t_distribution(year: float) → DeltaTDistributionMean plus source-error or uncalibrated policy scale in a normal-shaped convenience vessel
secular_trend(year: float) → floatCompatibility helper for the declared future curvature scenario
core_delta_t(year: float) → floatCompatibility surface; zero while the C04 proxy is quarantined
cryo_delta_t(year: float) → floatCompatibility surface; zero while the GRACE derivative is quarantined
fluid_lowfreq(year: float) → floatCompatibility surface; zero while AAM/OAM proxies are quarantined

Generic delta_t() preserves the raw HPIERS DE430/LE430 source basis; it does not ambiently retarget the clock product to DE441. From -2100 to the first HPIERS row at -2000, it exposes an explicit 100-year C0 source-floor bridge. The physical-policy surface is admitted from decimal year -2000.0; earlier physical requests raise ValueError. Through the final aggregate representative epoch (currently 2026.123287671233) the total follows admitted source tables. That final value is a Jan–Apr partial mean, and the slope formed from the 2025 and 2026 aggregate representative epochs is provisional scenario policy rather than an observed instantaneous derivative. After that source-owned boundary the mean is the explicit boundary-anchored scenario documented in DELTA_T_HYBRID_MODEL.md. Values beyond 2150 remain computable scenario extrapolations, not authority-validated forecasts.

Packaged EOP rows are runtime-admitted, but the transform did not retain their source observed/predicted flags, so they must not all be called measured or definitive observations. Generic Delta-T years are computationally guarded to [-100000, +100000]; JD-aware time transforms are guarded to [-40000000, +40000000]. These are representability limits, not scientific coverage claims.

DeltaTBreakdown fields

FieldTypeDescription
yearfloatDecimal year requested
totalfloatTotal ΔT in seconds
secularfloatDeclared curvature baseline; not a measured causal decomposition in source-backed eras
corefloatReserved compatibility component; currently zero
cryofloatReserved compatibility component; currently zero
fluidfloatReserved compatibility component; currently zero
bridgefloatExplicit arithmetic reconciliation from the curvature baseline to the admitted total or boundary-conditioned scenario
residualfloatReserved compatibility component; currently zero
erastrCompatibility category: pre-1840, historical, measured, or future; not source-row provenance

The additive invariant is secular + core + cryo + fluid + bridge + residual == total.

DeltaTDistribution fields and methods

MemberTypeDescription
yearfloatDecimal year requested
meanfloatSame mean returned by the physical-policy total
sigmafloatHPIERS source-error scale where aligned with that source; otherwise a modern floor or uncalibrated future policy scale in seconds
variancefloatsigma²
pdf(delta_t_seconds)floatNormal-approximation density
interval(sigma=1.0)tuple[float, float]Symmetric policy interval around the mean

The normal vessel is a computational approximation. It does not assert that ancient or future Earth-rotation errors are empirically Gaussian. Future values do not have calibrated coverage and do not propagate unquantified uncertainty in the handoff value or final-row slope.


moira.sky.position — Astrometric Correction Pipeline

from moira.sky.position import (
    apply_light_time, apply_aberration, apply_deflection, apply_frame_bias,
    topocentric_correction, apply_refraction,
    atmospheric_refraction, atmospheric_refraction_extended,
)

The five-stage pipeline converts geometric ICRF coordinates into observable apparent positions. Apply corrections in the order listed:

StageFunctionDescription
1apply_light_time(body, jd_tt, reader, earth_ssb, barycentric_fn)Iterative geometric delay; returns (corrected_xyz: Vec3, light_time_days: float)
2apply_aberration(xyz, observer_vel)Relativistic stellar aberration due to observer velocity
3apply_deflection(xyz, jd_tt, reader)Gravitational light deflection by Sun, Jupiter, and Saturn
4apply_frame_bias(xyz)IAU 2006 FK5/ICRS frame alignment (~17 mas offset)
5topocentric_correction(xyz_gcrs, jd_tt, lat, lon, elev_m=0.0)WGS-84 observer parallax shift

Atmospheric refraction (applied after stage 5):

FunctionSignatureDescription
apply_refraction(alt_deg, policy)→ floatApply the policy-selected refraction model to an altitude in degrees
atmospheric_refraction(altitude_deg)→ floatBennett (1982) refraction in arcminutes — fast, altitude only
atmospheric_refraction_extended(altitude_deg, temperature_C, pressure_mbar, humidity, wavelength_um, elevation_m)→ floatFull physical refraction model

moira.sky.frames — Coordinate Frame Transforms

from moira.sky.frames import (
    Vec3, Mat3,
    icrf_to_ecliptic, icrf_to_true_ecliptic, true_ecliptic_latitude, icrf_to_equatorial,
    ecliptic_to_equatorial, equatorial_to_ecliptic,
    equatorial_to_horizontal, horizontal_to_equatorial,
    precession_matrix_equatorial, nutation_matrix_equatorial,
    rot_x, rot_y, rot_z,
    cotrans_sp, aberration_correction,
    normalize_degrees, angular_distance, signed_angular_distance,
    equation_of_time,
    LocalSpacePosition, local_space_positions,
)

Vec3 = tuple[float, float, float] — Cartesian triple or angular triple. Mat3 = tuple[Vec3, Vec3, Vec3] — 3×3 rotation matrix as three row vectors.

Direct frame transforms

FunctionSignatureDescription
icrf_to_ecliptic(xyz: Vec3, obliquity: float) → (lon, lat)ICRF Cartesian → ecliptic lon/lat using supplied obliquity (degrees)
icrf_to_true_ecliptic(xyz: Vec3, jd_tt: float) → (lon, lat)ICRF → true ecliptic with full precession + nutation
true_ecliptic_latitude(xyz: Vec3, jd_tt: float) → floatEcliptic latitude only (P+N applied)
icrf_to_equatorial(xyz: Vec3) → (ra_rad, dec_rad)ICRF Cartesian → RA/Dec in radians
ecliptic_to_equatorial(lon, lat, obliquity) → (ra, dec)Ecliptic → equatorial (degrees)
equatorial_to_ecliptic(ra, dec, obliquity) → (lon, lat)Equatorial → ecliptic (degrees)
equatorial_to_horizontal(ra, dec, lst, lat) → (az, alt)Equatorial → azimuth/altitude; azimuth 0=N, 90=E
horizontal_to_equatorial(az, alt, lst, lat) → (ra, dec)Horizontal → equatorial (degrees)

Rotation matrices

FunctionSignatureDescription
precession_matrix_equatorial(jd_tt: float) → Mat3IAU 2006 precession rotation matrix
nutation_matrix_equatorial(jd_tt: float) → Mat3IAU 2000A nutation rotation matrix
rot_x / rot_y / rot_z(angle_rad: float) → Mat3Single-axis rotation matrices (astronomical sign convention)
cotrans_sp(xyz: Vec3, matrix: Mat3) → Vec3Apply a rotation matrix to a Cartesian triple
aberration_correction(xyz: Vec3, observer_vel: Vec3) → Vec3Annual aberration (geometric model)

Utility

FunctionSignatureDescription
normalize_degrees(deg: float) → floatFold angle into [0, 360)
angular_distance(a, b: float) → floatUnsigned shortest arc between two angles (degrees)
signed_angular_distance(a, b: float) → floatSigned angular difference (degrees)
equation_of_time(jd_tt: float) → floatSolar time correction in minutes

Horizon frame — batch

from moira.sky.frames import local_space_positions, LocalSpacePosition

positions = local_space_positions(chart_positions, jd_ut, lat, lon, reader)
# list[LocalSpacePosition] — each has .azimuth, .altitude, .is_above, .compass_direction()

moira.sky.bodies — Celestial Body Positions

from moira.sky.bodies import (
    PlanetData, SkyPosition, HeliocentricData, CartesianPosition,
    SSBPosition, SSB_BODIES, PlanetocentricData, VALID_OBSERVER_BODIES,
    NodeData, NodesAndApsides,
    planet_at, sky_position_at, all_planets_at, sun_longitude,
    planet_relative_to, next_heliocentric_transit,
    heliocentric_planet_at, all_heliocentric_at,
    ssb_position_at, all_ssb_positions_at,
    planetocentric_at, all_planetocentric_at,
    mean_node, true_node, mean_lilith, true_lilith,
    next_moon_node_crossing, nodes_and_apsides_at,
)
FunctionReturnsDescription
planet_at(body, jd_ut, reader=None, ...)PlanetDataGeocentric ecliptic position
sky_position_at(body, jd_ut, observer_lat, observer_lon, observer_elev_m=0.0, reader=None)SkyPositionTopocentric apparent RA/Dec + alt/az
all_planets_at(jd_ut, bodies=None, reader=None)dict[str, PlanetData]All planets at one epoch
sun_longitude(jd_ut, reader=None)floatSolar ecliptic longitude (geocentric, true-of-date)
planet_relative_to(body, jd_ut, reference)PlanetDataBody position measured from reference body
next_heliocentric_transit(body, jd_start, longitude)floatNext JD when body's heliocentric longitude equals target
heliocentric_planet_at(body, jd_ut, reader=None)HeliocentricDataHeliocentric ecliptic position
all_heliocentric_at(jd_ut, bodies=None, reader=None)dict[str, HeliocentricData]All planets heliocentrically
ssb_position_at(body, jd_ut, reader=None)SSBPositionSSB-origin ecliptic position in true-of-date orientation
all_ssb_positions_at(jd_ut, reader=None)dict[str, SSBPosition]All SSB_BODIES from SSB
planetocentric_at(observer_body, target_body, jd_ut)PlanetocentricDataGeometric target position measured from the center of observer body
all_planetocentric_at(observer_body, jd_ut)dict[str, PlanetocentricData]All targets from observer body
mean_node(jd_ut, *, nutation=True)NodeDataIERS 2003 mean lunar ascending node; true equinox of date by default
true_node(jd_ut, reader=None)NodeDataTrue (osculating) ascending node
mean_lilith(jd_ut, *, nutation=True)NodeDataMean Black Moon Lilith (mean apogee); true equinox of date by default
true_lilith(jd_ut)NodeDataTrue Lilith (osculating apogee)
next_moon_node_crossing(jd_start, reader=None, ascending=True)floatNext Moon ecliptic crossing (JD UT)
nodes_and_apsides_at(jd_ut)NodesAndApsidesCombined node/apsides vessel (mean/true node, Lilith, perigee, apogee)

Note: moira.sky.bodies is the strict astronomy surface for solar system bodies. Asteroids, Uranian planets, and fixed stars are not included; use moira.facade for those.


moira.sky.observation — Observational Quantities and Phenomena

from moira.sky.observation import (
    phase_angle, illuminated_fraction, elongation,
    synodic_phase_angle, synodic_phase_state,
    angular_diameter, apparent_magnitude,
    PhenomenonEvent, OrbitalResonance, PlanetPhenomena, MOON_PHASE_ANGLES,
    greatest_elongation, perihelion, aphelion,
    next_moon_phase, moon_phases_in_range,
    next_conjunction, conjunctions_in_range,
    resonance, planet_phenomena_at,
)

Phase quantities

FunctionSignatureDescription
phase_angle(body, jd_ut)→ floatSun–Planet–Earth angle in degrees
illuminated_fraction(phase_angle)→ floatIlluminated disk fraction (0.0–1.0)
elongation(body, jd_ut)→ floatAngular separation from the Sun (0°–180°)
synodic_phase_angle(body1, body2, jd_ut)→ floatEcliptic phase angle between any two bodies (0°–360°)
synodic_phase_state(angle_deg)→ strCoarse phase label (e.g. "waxing", "full", "waning")
angular_diameter(body, jd_ut)→ floatApparent angular diameter in arcseconds
apparent_magnitude(body, jd_ut)→ floatApparent visual magnitude (V band)

Phenomena search

FunctionSignatureDescription
greatest_elongation(body, jd_start)→ PhenomenonEventNext greatest elongation of Mercury or Venus
perihelion(body, jd_start)→ PhenomenonEventNext perihelion passage
aphelion(body, jd_start)→ PhenomenonEventNext aphelion passage
next_moon_phase(phase_name, jd_start)→ floatNext named Moon phase (JD UT); keys from MOON_PHASE_ANGLES
moon_phases_in_range(jd_start, jd_end)→ list[PhenomenonEvent]All eight Moon phases in a date range
next_conjunction(body1, body2, jd_start)→ PhenomenonEventNext geocentric conjunction
conjunctions_in_range(body1, body2, jd_start, jd_end)→ list[PhenomenonEvent]All conjunctions in a date range
resonance(body1, body2)→ OrbitalResonanceBest harmonic ratio P/Q between sidereal periods
planet_phenomena_at(body, jd_ut)→ PlanetPhenomenaFull phenomena snapshot at epoch

MOON_PHASE_ANGLES — dict mapping phase name → target elongation: new_moon, first_quarter, full_moon, last_quarter, waxing_crescent, waxing_gibbous, waning_gibbous, waning_crescent.


moira.sky.galactic — Galactic Coordinate System

from moira.sky.galactic import (
    GalacticPosition,
    equatorial_to_galactic, galactic_to_equatorial,
    ecliptic_to_galactic, galactic_to_ecliptic,
    galactic_reference_points, galactic_position_of, all_galactic_positions,
)

Rotation matrix authority: Liu, Zhu & Zhang (2011, A&A 526, A16) — IAU 1958 galactic pole definition rigorously tied to ICRS/J2000. Galactic longitude increases eastward; 0° points toward the Galactic Center.

FunctionSignatureDescription
equatorial_to_galactic(ra_deg, dec_deg)→ (l, b)RA/Dec → galactic l/b (J2000, degrees)
galactic_to_equatorial(l_deg, b_deg)→ (ra, dec)Galactic l/b → RA/Dec (J2000, degrees)
ecliptic_to_galactic(lon, lat, obliquity, jd_tt)→ (l, b)Ecliptic → galactic (bridges via equatorial)
galactic_to_ecliptic(l, b, obliquity, jd_tt)→ (lon, lat)Galactic → ecliptic (bridges via equatorial)
galactic_reference_points(obliquity, jd_tt)→ dict[str, (lon, lat)]Ecliptic coordinates of GC, NGP, GAC, SGP, SGC at the given epoch
galactic_position_of(body, jd_ut, ...)→ GalacticPositionGalacticPosition for one body
all_galactic_positions(chart_lons, jd_ut, ...)→ list[GalacticPosition]GalacticPosition for all bodies in a chart

Named reference points: GC (Galactic Center / Sgr A*), NGP (North Galactic Pole, Coma Berenices), GAC (Galactic Anti-Center, Gemini/Auriga), SGP (South Galactic Pole), SGC (Super-Galactic Center, Virgo/M87).


moira.sky.events — Rise, Set, Transit, Twilight, and Stations

from moira.sky.events import (
    RiseSetPolicy, TwilightTimes,
    find_phenomena, get_transit, twilight_times,
    StationEvent, find_stations, next_station, is_retrograde, retrograde_periods,
)

Rise / set / transit / twilight

FunctionSignatureDescription
find_phenomena(body, jd_day, lat, lon, policy)→ dictRise, set, transit dict with JD_UT values (or None when event does not occur that day)
get_transit(body, jd_day, lat, lon, upper=True)→ floatJD_UT of upper (or lower) meridian transit
twilight_times(jd_day, lat, lon)→ TwilightTimesCivil / nautical / astronomical twilight table for a day and location

TwilightTimes fields

FieldDescription
civil_dusk, civil_dawnSun at −6°
nautical_dusk, nautical_dawnSun at −12°
astro_dusk, astro_dawnSun at −18°
sunset, sunriseLimb at geometric horizon
solar_noonUpper meridian transit

Stations

FunctionSignatureDescription
find_stations(body, jd_start, jd_end)→ list[StationEvent]All SR/SD stations for a body in a date range
next_station(body, jd_start)→ StationEventFirst station (SR or SD) after jd_start
is_retrograde(body, jd_ut)→ boolTrue if the body is retrograde at the given epoch
retrograde_periods(body, jd_start, jd_end)→ list[tuple[float, float]](jd_SR, jd_SD) pairs — begin and end of each retrograde arc

StationEvent fields: body, kind ("SR" = stationary retrograde / "SD" = stationary direct), jd_ut, datetime (UTC), longitude.


moira.sky.eclipse — Solar and Lunar Eclipses

from moira.sky.eclipse import (
    EclipseCalculator, EclipseType,
    EclipseData, EclipseEvent,
    SolarBesselianElements,
    SolarEclipseFootprintBoundaryKind, SolarEclipsePenumbralContactKind,
    SolarEclipseFootprintTopology, SolarEclipseFootprintPoint,
    SolarEclipsePenumbralContact, SolarEclipseFootprintContacts,
    SolarEclipseLimitTrack, SolarEclipseVisibilityFootprint,
    EclipseEpoch, EclipseGeocentricBodyState,
    SolarEclipseGlobalCircumstances, SolarEclipseCartography,
    LunarEclipseGlobalCircumstances,
    SolarEclipsePath, SolarEclipseLocalCircumstances,
    SolarBodyCircumstances, LocalContactCircumstances,
    LunarEclipseAnalysis, LunarEclipseLocalCircumstances, LunarEclipseContacts,
    LunarEclipseVisibilityContactKind, LunarEclipseVisibilityPoint,
    LunarEclipseVisibilityLimit, LunarEclipseVisibilityMap,
    next_solar_eclipse_at_location, find_lunar_contacts,
)

Primary engine

calc = EclipseCalculator()
jd_start = 2460676.5
lat, lon = 40.7, -74.0

# Solar eclipse search and local circumstances
ev        = calc.next_solar_eclipse(jd_start, kind="any")
circ      = calc.solar_local_circumstances(
    jd_start, lat, lon, elevation_m=0.0, kind="any"
)
path      = calc.solar_eclipse_path(jd_start, kind="any", sample_count=9)
footprint = calc.solar_eclipse_footprint(
    jd_start, kind="any", sample_count=181
)
solar_global = calc.solar_global_circumstances(jd_start, kind="any")
solar_map = calc.solar_eclipse_cartography(
    jd_start,
    kind="any",
    magnitude_levels=(0.2, 0.4, 0.6, 0.8, 0.9),
    obscuration_levels=(0.2, 0.4, 0.6, 0.8, 0.9),
    mesh_depth=1,
    time_samples=17,
    angular_tolerance_deg=8.0,
    field_tolerance=0.01,
)
full      = calc.next_solar_eclipse_at_location(
    jd_start, lat, lon, elevation_m=0.0, kind="any"
)  # search + circumstances combined

# Lunar eclipse search and analysis
lunar_event = calc.next_lunar_eclipse(jd_start, kind="any")
ana = calc.analyze_lunar_eclipse(jd_start, kind="any", mode="native")
nasa_ana = calc.analyze_lunar_eclipse(
    jd_start, kind="any", mode="nasa_compat"
)
lc = calc.lunar_local_circumstances(
    jd_start, lat, lon, elevation_m=0.0, kind="any", mode="native"
)
lunar_map = calc.lunar_eclipse_visibility_map(
    jd_start, kind="any", mode="native", sample_count=181
)
lunar_global = calc.lunar_global_circumstances(
    jd_start, kind="any", mode="native"
)

# Inclusive bulk ranges (global maxima in UT1)
solar_range = calc.solar_eclipses_in_range(jd_start, jd_start + 3652.5)
lunar_range = calc.lunar_eclipses_in_range(jd_start, jd_start + 3652.5)

# Geometry snapshot at any epoch
snap = calc.calculate_jd(jd_start)   # → EclipseData

solar_eclipses_in_range(jd_start, jd_end) and lunar_eclipses_in_range(jd_start, jd_end) include maxima on both interval boundaries and return time-ordered EclipseEvent lists. With a compatible content-identified planetary reader, C++ scans a padded TT interval for a conservative two-degree syzygy-candidate superset. Python remains authoritative for reader-bound TT/UT1 conversion, physical maximum refinement, eclipse classification, deduplication, inclusive filtering, and public vessel assembly. If one native evaluator cannot cover the padded interval, the same methods use the explicit Python manuscript. The older native generic-event scanners are not used as public classification or contact products.

Lunar visibility-map engine signatures

SurfaceExact signatureReturns
EclipseCalculatorlunar_eclipse_visibility_map(jd_start, *, kind="any", backward=False, mode="native", sample_count=181)LunarEclipseVisibilityMap
Moira facadelunar_eclipse_visibility_map(jd_start, *, kind="any", backward=False, mode="native", sample_count=181)LunarEclipseVisibilityMap

The map contains one LunarEclipseVisibilityLimit for every contact that exists for the selected eclipse, in chronological order. Each limit carries its contact kind, UT1 epoch, closed geographic ring, and sublunar point; the visible side is the side containing the sublunar point. Total eclipses expose P1/U1/U2/greatest/U3/U4/P4, partial eclipses omit U2/U3, and penumbral-only eclipses expose P1/greatest/P4.

The boundary is an exact ellipsoid-tangency product in scaled WGS-84 space, using the retarded DE441/LE441 Moon-center reception vector. It deliberately excludes atmosphere, terrain, observer elevation, and lunar-limb relief and therefore does not replace observer-local apparent circumstances. The Python layer owns contact policy and public semantics; no native C++ port is used for this seven-contact assembly.

Global-circumstances and cartography signatures

SurfaceExact signatureReturns
EclipseCalculator / Moirasolar_global_circumstances(jd_start, *, kind="any", backward=False)SolarEclipseGlobalCircumstances
EclipseCalculator / Moirasolar_eclipse_cartography(jd_start, *, kind="any", backward=False, magnitude_levels=(0.2, 0.4, 0.6, 0.8, 0.9), obscuration_levels=(0.2, 0.4, 0.6, 0.8, 0.9), mesh_depth=1, time_samples=17, angular_tolerance_deg=8.0, field_tolerance=0.01)SolarEclipseCartography
EclipseCalculator / Moiralunar_global_circumstances(jd_start, *, kind="any", backward=False, mode="native")LunarEclipseGlobalCircumstances

Solar global circumstances distinguish greatest eclipse from greatest duration, carry separate equatorial/ecliptic conjunction epochs, expose U1-U4 only for central events, and preserve explicit TT/UT1/Delta-T metadata. Lunar global circumstances retain mode-pure shadow geometry, contacts, phase durations, and geocentric body parameters. The cartography method emits observer-local maximum magnitude and obscuration as distinct contour families. Contour identity is (component_id, segment_id); antimeridian splits are open transport-safe segments of one spherical component. Duration contours are not admitted. The result reports the achieved conforming refinement depth, triangle count, maximum angular edge, and unresolved-edge convergence state.

Solar footprint engine signatures

SurfaceExact signatureReturns
EclipseCalculatorsolar_eclipse_footprint(jd_start, *, kind="any", backward=False, sample_count=181)SolarEclipseVisibilityFootprint
Moira facadesolar_eclipse_footprint(jd_start, *, kind="any", backward=False, sample_count=181)SolarEclipseVisibilityFootprint

kind accepts "any", "total", "annular", "partial", "central", or "hybrid". sample_count is an integer in 9..721; it controls only presentation density. The governing solve, contacts, horizon incidences, temporal folds, and (kind, component_id, segment_id) graph do not change with that requested density.

Solar footprint vessel contract

VesselFields
SolarEclipseFootprintPointjd_ut, latitude_deg, longitude_deg; computed datetime_utc and BCE-safe calendar_utc
SolarEclipsePenumbralContactkind, point
SolarEclipseFootprintContactsrequired p1/p4, paired optional p2/p3
SolarEclipseLimitTrackkind, component_id, segment_id, immutable time-ordered points
SolarEclipseVisibilityFootprintevent, greatest, topology, contacts, tracks, ephemeris, surface_model, limb_model, time_scale, atmospheric_refraction

Boundary kinds are penumbral_north, penumbral_south, sunrise, and sunset. Every penumbral kind admitted by the topology contains exactly one connected component, identified as component_id=0. A component that folds in UT1 is emitted as strictly time-ordered tracks with contiguous segment_id values 0..n-1; adjacent folded segments share the solver-refined fold point. Each penumbral component has exactly two incidences on the sunrise/sunset graph. In two_limit_two_loop, the north and south incidence sets are disjoint, the event is globally central rather than partial, and every horizon track lies wholly within P1-P2 or P3-P4 rather than crossing P2-P3. Topology is one_limit_connected or two_limit_two_loop.

Convenience function

from moira.sky.eclipse import next_solar_eclipse_at_location
result = next_solar_eclipse_at_location(
    jd_start, lat, lon, elevation_m=0.0, kind="any", max_lunations=360
)

Key vessel fields

EclipseData: eclipse_type, eclipse_magnitude, saros_index, metonic_year, metonic_is_reset, moon_parallax, solar_diameter, moon_diameter, separation, phase_angle.

EclipseEvent: jd_ut, eclipse_type, datetime_utc (computed property).

LunarEclipseContacts: precise UT1 contact times P1 (1st penumbral), U1 (1st umbral), U2 (start of totality), U3 (end of totality), U4 (last umbral), P4 (last penumbral), plus the separate greatest-eclipse instant.

For mode="nasa_compat", LunarEclipseAnalysis.canon_method is nasa_shadow_axis_apparent_sun_moon; source_model names the same declared reduction. Native mode and its contact policy are unchanged.

Note: next_solar_eclipse_at_location is also available directly from moira.facade and moira.eclipse. It is not re-exported through moira.predictive.


moira.sky.occultation — Occultations, Grazes, and Close Approaches

from moira.sky.occultation import (
    CloseApproach, LunarOccultation, OccultationPathGeometry, OccultationPathTopology,
    OccultationPathPoint, OccultationPathBoundaryPoint, OccultationPathBoundaryTrack,
    OccultationPathBoundarySide, OccultationPathTopologyKind,
    OccultationGeographicPole, OccultationPoleCrossingPhase, OccultationPoleCrossing,
    GrazeCircumstances, GrazeTableRow, GrazeProductGeometry, GrazeProductTrack,
    close_approaches, lunar_occultation, lunar_occultation_path_at, lunar_occultation_path,
    lunar_occultation_path_topology_at, lunar_occultation_path_topology,
    all_lunar_occultations,
    lunar_star_occultation, lunar_star_occultation_path_at, lunar_star_occultation_path,
    lunar_star_occultation_path_topology_at, lunar_star_occultation_path_topology,
    lunar_star_graze_circumstances, lunar_star_graze_latitude,
    lunar_star_practical_graze_latitude, lunar_star_graze_line,
    lunar_star_graze_table, lunar_star_graze_product_at, lunar_star_graze_product_track,
)
FunctionSignatureDescription
close_approaches(body1, body2, jd_start, jd_end)→ list[CloseApproach]All minimum-separation events between two bodies
lunar_occultation(body, jd_start, jd_end)→ list[LunarOccultation]Moon occultations of a named planet
all_lunar_occultations(jd_start, jd_end, planets=None, reader=None)→ list[LunarOccultation]Occultations of the default visible-planet set or caller-supplied targets
lunar_occultation_path_at(target, jd_mid, *, sample_count=9, observer_elev_m=0.0, limb_profile_provider=None, reader=None)→ OccultationPathGeometryCompatibility center/width/duration summary at a supplied greatest epoch
lunar_occultation_path(target, jd_start, jd_end, step_days=0.25, sample_count=9, observer_elev_m=0.0, limb_profile_provider=None, reader=None)→ list[OccultationPathGeometry]Compatibility summaries for planetary events in a search interval
lunar_occultation_path_topology_at(target, jd_mid)→ OccultationPathTopologyPolar-safe planetary path band at one greatest epoch
lunar_occultation_path_topology(target, jd_start, jd_end)→ list[OccultationPathTopology]Detailed planetary path bands in a search interval
lunar_star_occultation(star, jd_start, jd_end)→ list[LunarOccultation]Moon occultations of a named fixed star
lunar_star_occultation_path_at(star_lon, star_lat, star_name, jd_mid, *, sample_count=9, observer_elev_m=0.0, limb_profile_provider=None, reader=None)→ OccultationPathGeometryCompatibility center/width/duration summary at a supplied greatest epoch
lunar_star_occultation_path(star_lon, star_lat, star_name, jd_start, jd_end, step_days=0.25, sample_count=9, observer_elev_m=0.0, limb_profile_provider=None, reader=None)→ list[OccultationPathGeometry]Compatibility summaries for fixed-star events in a search interval
lunar_star_occultation_path_topology_at(star_lon, star_lat, star_name, jd_mid)→ OccultationPathTopologyPolar-safe fixed-star path band at one greatest epoch
lunar_star_occultation_path_topology(star_lon, star_lat, star_name, jd_start, jd_end)→ list[OccultationPathTopology]Detailed fixed-star path bands in a search interval
lunar_star_graze_table(star, event)→ list[GrazeTableRow]Latitude-keyed graze contact table
lunar_star_graze_product_track(star, event)→ GrazeProductTrackFull graze track across a latitude band

moira.sky.visibility — Observational Visibility Doctrine

from moira.sky.visibility import (
    HeliacalEventKind, VisibilityTargetKind, LightPollutionClass,
    LightPollutionDerivationMode, ObserverAid, VisibilityCriterionFamily,
    LunarCrescentVisibilityClass, VisibilityExtinctionModel, VisibilityTwilightModel,
    MoonlightPolicy,
    ExtinctionCoefficient, ObserverVisibilityEnvironment,
    VisibilityPolicy, VisibilitySearchPolicy, VisibilityModel, HeliacalPolicy,
    LunarCrescentDetails, VisibilityAssessment,
    GeneralVisibilityEvent, PlanetHeliacalEvent,
    visibility_assessment, visual_limiting_magnitude, visibility_event,
    planet_heliacal_rising, planet_heliacal_setting,
    planet_acronychal_rising, planet_acronychal_setting,
)
FunctionSignatureDescription
visibility_assessment(body, jd_ut, lat, lon, *, policy=None)→ VisibilityAssessmentSingle-epoch observability check
visual_limiting_magnitude(jd_ut, lat, lon, *, policy=None)→ floatEffective limiting magnitude for the observer/epoch
visibility_event(body, event_kind, jd_start, lat, lon, *, heliacal_policy=None, ...)→ GeneralVisibilityEvent | NoneSearch for next heliacal event
planet_heliacal_rising(body, jd_start, lat, lon, ...)→ PlanetHeliacalEvent | NoneMorning first visibility
planet_heliacal_setting(body, jd_start, lat, lon, ...)→ PlanetHeliacalEvent | NoneEvening last visibility
planet_acronychal_rising(body, jd_start, lat, lon, ...)→ PlanetHeliacalEvent | NoneEvening first visibility
planet_acronychal_setting(body, jd_start, lat, lon, ...)→ PlanetHeliacalEvent | NoneMorning last visibility

LunarCrescentVisibilityClass values: A (easily visible), B (visible under perfect conditions), C (may need optical aid), D (only with optical aid), E (not with optical aid), F (below the horizon).

HeliacalEventKind: MorningFirst, EveningLast, EveningFirst, MorningLast.


21. moira.vedic — Vedic Astrology Surface

moira.vedic collects every Vedic-domain subsystem into a single coherent import surface. It inherits the full moira.essentials surface and adds: sidereal positioning, nakshatras, panchanga, source-scoped Pancha Pakshi, all 16 vargas, Vedic dignities, Vimshottari and alternate dasha systems (Ashtottari, Yogini), Jaimini karakas, Ashtakavarga, and Shadbala.

It does not include the Western classical surface (Arabic lots, Firdaria, Zodiacal Releasing, Huber, etc.). For that, use moira.classical. For the complete surface, use moira.facade.

from moira.vedic import *

moment = datetime(1985, 3, 21, 6, 0, tzinfo=timezone.utc)
m      = Moira()
chart  = m.chart(moment)

# Sidereal and nakshatra positions
ayan = ayanamsa(chart.jd_ut, Ayanamsa.LAHIRI)
naks = all_nakshatras_at(chart.longitudes(), chart.jd_ut)

# Panchanga
pg = panchanga_at(
    chart.planets['Sun'].longitude,
    chart.planets['Moon'].longitude,
    chart.jd_ut,
)

# Source-scoped nominal Pancha Pakshi; no datetime, location, or default profile
pp = pancha_pakshi_schedule(
    'agastya_madras_1879_akshara_fixed_clock',
    paksha=PanchaPakshiPaksha.PURVA,
    half=PanchaPakshiHalf.DAY,
    weekday=PanchaPakshiWeekday.SUNDAY,
)

# Standalone source-mapped lunar-half inference; no location or schedule routing
pp_paksha = m.pancha_pakshi_astronomical_paksha(
    'agastya_madras_1879_akshara_fixed_clock',
    datetime(1985, 3, 21, 6, 0, tzinfo=timezone.utc),
)

# Modern local-solar context; caller still supplies the source-label paksha
pp_context = m.pancha_pakshi_local_solar_context(
    'agastya_madras_1879_akshara_fixed_clock',
    datetime(1985, 3, 21, 6, 0, tzinfo=timezone.utc),
    28.6,
    77.2,
    paksha=PanchaPakshiPaksha.PURVA,
)

# Explicit modern fixed-clock materialization; no current-cell selection
pp_fixed = m.pancha_pakshi_fixed_clock_materialization(
    'agastya_madras_1879_akshara_fixed_clock',
    datetime(1985, 3, 21, 6, 0, tzinfo=timezone.utc),
    28.6,
    77.2,
    paksha=PanchaPakshiPaksha.PURVA,
)

# Separate solar-half-first current-cell selection; paksha remains explicit
pp_current = m.pancha_pakshi_fixed_clock_current_cell(
    'agastya_madras_1879_akshara_fixed_clock',
    datetime(1985, 3, 21, 6, 0, tzinfo=timezone.utc),
    28.6,
    77.2,
    paksha=PanchaPakshiPaksha.PURVA,
)

# Vedic dignities
d    = vedic_dignity('Mars', chart.longitudes()['Mars'])

# Vimshottari dasha
periods = vimshottari(
    chart.planets['Moon'].longitude,
    chart.jd_ut,
    levels=2,
)

# Shadbala convenience method on the facade
houses = m.houses(moment, latitude=28.6, longitude=77.2)
strength = m.shadbala_for_chart(chart, houses)

Sidereal, Ayanamsa, and Nakshatras

from moira.vedic import (
    UserDefinedAyanamsa, NakshatraPosition,
    nakshatra_of, all_nakshatras_at,
    # ayanamsa, tropical_to_sidereal, sidereal_to_tropical already in essentials
)
FunctionSignatureDescription
nakshatra_of(tropical_longitude, jd, ayanamsa_system=Ayanamsa.LAHIRI)→ NakshatraPositionNakshatra position for a tropical ecliptic longitude
all_nakshatras_at(positions: dict[str, float], jd, ayanamsa_system=Ayanamsa.LAHIRI)→ dict[str, NakshatraPosition]Nakshatra positions for all bodies in a chart

NakshatraPosition fields

FieldTypeDescription
nakshatrastrNakshatra name
nakshatra_indexint0–26
nakshatra_lordstrDasha lord of this nakshatra
padaint1–4
degrees_infloatDegrees into this nakshatra (0–13.333°)
sidereal_lonfloatSidereal longitude used for the calculation

UserDefinedAyanamsa — define a custom ayanamsa by specifying an offset in degrees or a reference epoch.


Pancha Pakshi (Named Source-Scoped Profiles)

from moira.vedic import (
    available_pancha_pakshi_profiles,
    pancha_pakshi_profile_info,
    pancha_pakshi_identity_from_initial_vowel,
    pancha_pakshi_schedule,
    pancha_pakshi_first_eat_bird_mapping,
    pancha_pakshi_astronomical_paksha_at,
    pancha_pakshi_nakshatra_bird_mapping,
    pancha_pakshi_natal_moon_identity_at,
    pancha_pakshi_padu_bird_mapping,
    pancha_pakshi_sookshma_temporal_selection,
    pancha_pakshi_schedule_sookshma_temporal_selection,
    pancha_pakshi_civil_time_sookshma_selection_at,
    pancha_pakshi_local_solar_context_at,
    pancha_pakshi_fixed_clock_materialization_at,
    pancha_pakshi_fixed_clock_current_cell_at,
    pancha_pakshi_solar_proportional_materialization_at,
    pancha_pakshi_solar_proportional_current_cell_at,
    pancha_pakshi_directed_relationship,
    PanchaPakshiCurrentCellSelectionStatus,
    PanchaPakshiFixedClockCell,
    PanchaPakshiFixedClockCurrentCellSelection,
    PanchaPakshiFixedClockCurrentCellSelectionPolicy,
    PanchaPakshiFixedClockMaterialization,
    PanchaPakshiFixedClockMaterializationPolicy,
    PanchaPakshiLocalSolarContext,
    PanchaPakshiLocalSolarContextPolicy,
    PanchaPakshiMaterializedCellRelation,
    PanchaPakshiSolarProportionalCell,
    PanchaPakshiSolarProportionalCurrentCellSelection,
    PanchaPakshiSolarProportionalCurrentCellSelectionPolicy,
    PanchaPakshiSolarProportionalMaterialization,
    PanchaPakshiSolarProportionalMaterializationPolicy,
    PanchaPakshiSolarBoundaryRelation,
    PanchaPakshiAstronomicalPaksha,
    PanchaPakshiAstronomicalPakshaInference,
    PanchaPakshiAstronomicalPakshaInferencePolicy,
    PanchaPakshiNakshatraBirdMapping,
    PanchaPakshiNatalMoonIdentity,
    PanchaPakshiNatalMoonIdentityPolicy,
    PanchaPakshiFirstEatBirdMapping,
    PanchaPakshiPaduBirdMapping,
    PanchaPakshiSookshmaInterval,
    PanchaPakshiSookshmaSelection,
    PanchaPakshiSookshmaSelectorPolicy,
    PanchaPakshiSookshmaSelectorPolicyId,
    PanchaPakshiSookshmaTimingPolicyId,
    PanchaPakshiScheduleSookshmaCompositionPolicy,
    PanchaPakshiScheduleSookshmaSelection,
    PanchaPakshiCivilTimeSookshmaRoutingPolicy,
    PanchaPakshiCivilTimeSookshmaSelection,
    PanchaPakshiBird,
    PanchaPakshiPaksha,
    PanchaPakshiHalf,
    PanchaPakshiWeekday,
)
FunctionReturnDescription
available_pancha_pakshi_profiles()tuple[PanchaPakshiProfileDescriptor, ...]List public named profiles without selecting a default
pancha_pakshi_uromarisi_constitution_status()PanchaPakshiUromarisiConstitutionStatusReturn immutable SCP closure and admission metadata without exposing private historical/network research
pancha_pakshi_profile_info(profile_id)PanchaPakshiProfileInfoInspect admission, capabilities, source, locators, conflicts, and omissions
pancha_pakshi_identity_from_initial_vowel(profile_id, initial_vowel)PanchaPakshiInitialVowelIdentityResolve one explicitly listed aksara/query-or-name initial; not natal Moon identity
pancha_pakshi_schedule(profile_id, *, paksha, half, weekday)PanchaPakshiScheduleMaterialize one exact nominal fixed-clock schedule from explicit source labels
pancha_pakshi_first_eat_bird_mapping(profile_id, *, profile_paksha, half, weekday)PanchaPakshiFirstEatBirdMappingReturn one named generator's directly attested first-samam EAT seed without materializing its schedule or assigning whole-day/authority semantics
pancha_pakshi_astronomical_paksha_at(profile_id, jd_ut1, *, reader=None)PanchaPakshiAstronomicalPakshaInferenceClassify apparent geocentric Moon-minus-Sun elongation on one reader-bound TT and map Shukla/waxing to Purva or Krishna/waning to Amara for the named profile; no location or schedule routing
pancha_pakshi_nakshatra_bird_mapping(profile_id, *, profile_paksha, nakshatra_index)PanchaPakshiNakshatraBirdMappingReturn one directly attested source-table cell without computing or claiming a natal Moon
pancha_pakshi_natal_moon_identity_at(profile_id, jd_ut1, *, reader=None)PanchaPakshiNatalMoonIdentityApply the named Bogamuni table through the fixed modern apparent-geocentric, Lahiri-true, equal-27-sector natal-Moon policy while exposing every intermediate and source mapping
pancha_pakshi_padu_bird_mapping(profile_id, *, profile_paksha, weekday)PanchaPakshiPaduBirdMappingReturn one directly attested Padu bird from the explicit Paksha-by-weekday table; no day/night, instant, schedule, or activity conversion
pancha_pakshi_sookshma_temporal_selection(profile_id, *, policy_id, parent_activity, elapsed_nazhigai)PanchaPakshiSookshmaSelectionSelect one exact half-open interval within a six-nazhigai samam under a mandatory weighted or equal-fifths policy; no default, clock, astronomy, schedule, Uromarisi outcome, or prognostic interpretation
pancha_pakshi_schedule_sookshma_temporal_selection(schedule_profile_id, selector_profile_id, *, profile_paksha, half, weekday, samam_index, subject_bird, selector_policy_id, elapsed_nazhigai)PanchaPakshiScheduleSookshmaSelectionCompose one explicit nominal schedule samam and subject bird with one explicit selector policy and exact elapsed fraction; no clock or outcome binding
pancha_pakshi_civil_time_sookshma_selection_at(schedule_profile_id, selector_profile_id, jd_ut1, latitude, longitude, *, profile_paksha, subject_bird, timing_policy_id, selector_policy_id, reader=None)PanchaPakshiCivilTimeSookshmaSelectionRoute one instant through an explicit fixed-clock or solar-proportional materialization, derive samam and exact elapsed fraction, and invoke Stage 2N without policy fallback or outcome interpretation
pancha_pakshi_local_solar_context_at(profile_id, jd_ut1, latitude, longitude, *, paksha, reader=None)PanchaPakshiLocalSolarContextResolve topocentric local-solar half and local-mean-solar weekday from UT1, retain caller-supplied paksha, and select the existing nominal schedule
pancha_pakshi_fixed_clock_materialization_at(profile_id, jd_ut1, latitude, longitude, *, paksha, reader=None)PanchaPakshiFixedClockMaterializationAnchor the selected nominal schedule at governing sunrise or sunset, apply its exact offsets on reader-bound TT, project endpoints to UT1, and report unclipped solar-boundary topology without selecting a current cell
pancha_pakshi_fixed_clock_current_cell_at(profile_id, jd_ut1, latitude, longitude, *, paksha, reader=None)PanchaPakshiFixedClockCurrentCellSelectionResolve the governing solar half first, then return its unique half-open fixed-clock cell or the explicit unmaterialized long-half-tail status
pancha_pakshi_solar_proportional_materialization_at(profile_id, jd_ut1, latitude, longitude, *, paksha, reader=None)PanchaPakshiSolarProportionalMaterializationMap every exact nominal offset fraction independently across the actual governing solar half on reader-bound TT, publish TT and UT1 endpoints, and return the complete 25-cell half-open schedule without current-cell selection
pancha_pakshi_solar_proportional_current_cell_at(profile_id, jd_ut1, latitude, longitude, *, paksha, reader=None)PanchaPakshiSolarProportionalCurrentCellSelectionResolve the governing solar half first, materialize its complete Stage 2D proportional schedule, and return the unique cell containing the requested reader-bound TT instant under exact half-open ownership
pancha_pakshi_directed_relationship(profile_id, subject, target)PanchaPakshiDirectedRelationshipReturn one stored ordered non-self relation without reciprocal inference

PanchaPakshiAstronomicalPakshaInferencePolicy is immutable and has no caller-configurable switches. Its only policy ID is apparent_geocentric_moon_sun_longitude_paksha_half_open_v1: UT1 is converted once to reader-bound TT, and apparent geocentric Sun and Moon longitudes are evaluated in the true ecliptic of date on that shared TT coordinate with aberration, gravitational deflection, and nutation enabled. The normalized Moon - Sun longitude occupies [0, 360) degrees. Exact half-open ownership is [0, 180) Shukla and [180, 360) Krishna with 0.0-degree tolerance and no snapping. A common ayanamsa is not applied because it cancels from this difference.

The named profile's source-attested mapping is Shukla/waxing to Purva at IA leaf n16, and Krishna/waning to Amara at n26. That machine-assisted visual reading remains source-scoped with explicit uncertainty and no human-review dependency; it is not an independent-witness or universal-canon claim.

Stage 2F vesselPublic contract
PanchaPakshiAstronomicalPakshaFinite astronomical phase-half enum: shukla or krishna; it remains distinct from the profile-owned purva/amara enum
PanchaPakshiAstronomicalPakshaInferencePolicyOrigin, frame, corrections, time scales, elongation definition, exact half-open boundary ownership, source mapping basis and locators, plus explicit non-performance of schedule selection, materialization, and natal identity
PanchaPakshiAstronomicalPakshaInferenceProfile ID, requested UT1 and TT, Sun/Moon longitudes, normalized elongation, astronomical and profile labels, exactly one direct mapping locator, immutable policy, and route-specific provenance

The inference accepts no location or caller-supplied paksha. It never selects or materializes a schedule, identifies a current cell, feeds the result into another operation, or infers natal identity.

PanchaPakshiNakshatraBirdMapping is the pure Stage 2G source-table vessel. The named bogamuni_chennai_2024_nakshatra_natal_identity profile contains exactly 54 cells: two profile Paksha labels by 27 named nakshatras. Purva mappings cite the rendered original at IA leaf n52; Amara mappings cite the complete verse at n64. The adjacent Amara commentary duplicates Shravana and omits Revati, so assembly_policy="verse_precedence_for_nakshatra_partition" preserves that commentary as rejected conflict evidence and does not repair or blend it. The source-table vessel states source_table_semantics="nakshatra_bird_table_not_explicitly_natal_moon". The strict transport equivalent is POST /v1/pancha-pakshi/mappings/nakshatra-bird; it accepts only explicit profile, source Paksha, and zero-based nakshatra index.

PanchaPakshiNatalMoonIdentityPolicy is fixed, immutable, and has policy ID bogamuni_2024_apparent_lahiri_natal_moon_identity_v1. One UT1 instant becomes one reader-bound TT epoch. Apparent geocentric Sun and Moon longitudes in the true ecliptic of date determine the half-open Shukla/Krishna phase; the source binding at Bogamuni leaf n167 maps that half to Purva or Amara. The same TT epoch supplies Lahiri true ayanamsa and the sidereal Moon, which is classified into 27 equal half-open 40/3-degree sectors. Exact internal boundaries belong to the following nakshatra, with maximum-one-ULP-below recovery only for the binary representation of an exact mathematical boundary. The policy's public token is exactly ayanamsa_system="Lahiri", matching Ayanamsa.LAHIRI and the strict sidereal transport vocabulary.

The policy explicitly reports composition_status="modern_moira_policy_not_source_claim" and ayanamsa_status="fixed_modern_moira_policy_not_source_attested": the source attests phase labels and nakshatra birds, not birth-Moon application, Lahiri, or the equal-sector computational partition. The result exposes requested UT1/TT, Sun and tropical Moon, elongation, astronomical and profile Paksha, phase locator, ayanamsa, sidereal Moon, nakshatra index/name/degrees, nested bird mapping and locator, full policy, and provenance. It performs no schedule selection, materialization, current-cell selection, scoring, or forecast.

Stage 2G vesselPublic contract
PanchaPakshiNakshatraBirdMappingPure source-table Paksha, nakshatra index/name, bird, direct-attestation status, declared verse precedence, one mapping locator, and profile provenance; no epoch or natal claim
PanchaPakshiNatalMoonIdentityPolicyFixed astronomical origin/frame/corrections/time scales, half-open phase and nakshatra ownership, source phase binding, Lahiri-true modern composition, verse precedence, and explicit non-performance fields
PanchaPakshiNatalMoonIdentityAll astronomical and sidereal intermediates, phase mapping, nakshatra placement, nested source-table mapping, immutable policy, and route-specific provenance

PanchaPakshiPaduBirdMapping is the pure Stage 2H source-table vessel. The separate bogamuni_chennai_2024_padu_bird_mapping profile contains exactly fourteen cells: two explicit source Paksha labels by seven weekdays, with no day/night axis. Purva cells cite the governing n52 stanza; Amara cells cite n60; every result also cites the repeated combined table at n157 and its restating commentary at n158. Its assembly policy is paksha_stanzas_govern_repeated_combined_table_confirms, and its source semantics are profile_paksha_weekday_death_or_inoperative_bird_not_schedule_rule_activity. The lookup accepts no instant, location, half, schedule, natal, condition, score, or forecast input. It neither converts Padu to RULE nor relabels first_eat_bird, authority day, Adhikara, or Bharana.

Stage 2H vesselPublic contract
PanchaPakshiPaduBirdMappingExplicit profile Paksha and weekday, one directly attested Padu bird, three canonical locators, stanza-precedence assembly policy, source-table semantics, and profile provenance; no temporal or schedule claim

PanchaPakshiFirstEatBirdMapping is the pure Stage 2I source-table vessel on the unchanged 1879 profile. Its 28 possible cells cover two explicit profile Pakshas, two day/night halves, and seven weekdays. Each result identifies the canonical generator, its first-samam EAT bird, direct-attestation status, fixed source semantics, complete generator locator tuple, and profile provenance. It accepts no instant, location, inferred Paksha, schedule-materialization, Padu/authority/Adhikara/Bharana, natal, condition, score, or forecast input.

Stage 2I vesselPublic contract
PanchaPakshiFirstEatBirdMappingExplicit profile, Paksha, half, and weekday; canonical generator ID; one directly attested first_eat_bird; full generator locator tuple and provenance; no temporal, whole-day, authority, or materialized-schedule claim

Stage 2K adds the separate bogamuni_chennai_2024_sookshma_temporal_selector profile. Its sole capability is sookshma_temporal_selection; default_selection_allowed remains false. Every call names one PanchaPakshiSookshmaSelectorPolicyId, a parent activity, and an exact Fraction offset in [0, 6). The weighted policy rotates the source-attested activity-duration vector from the parent activity. The equal-fifths policy returns five exact 6/5-nazhigai ordinal cells with activity=None, because the source does not attest a subactivity assignment. Both policies use exact [start, end) ownership and return their five cells, the unique selected ordinal and interval, source locators, and provenance. They perform no datetime, astronomy, schedule, Uromarisi outcome, condition, score, electional, or forecast composition and have no human-review dependency.

Stage 2K vesselPublic contract
PanchaPakshiSookshmaSelectorPolicyMandatory policy ID, exact container and duration doctrine, half-open ownership, explicit no-default/no-Uromarisi-composition statuses, and two source locators
PanchaPakshiSookshmaIntervalOrdinal, optional source-attested activity, and exact rational start, end, and duration
PanchaPakshiSookshmaSelectionExplicit profile, policy, parent activity, exact elapsed offset, all five intervals, unique selected interval, source locators, and provenance; no outcome semantics

PanchaPakshiLocalSolarContextPolicy is fixed and inspectable: policy_id="local_solar_day_explicit_paksha_v1", caller-supplied source-label paksha, topocentric sunrise-to-next-sunrise day, -0.833-degree solar-event altitude, fixed 0 m observer elevation, unrefracted altitude signal with conventional standard refraction and solar semidiameter incorporated in the threshold, sunrise/sunset half, local-mean-solar weekday at governing sunrise, and offset_materialization_status="not_performed". The result exposes the requested UT1 JD, sunrise/sunset/next-sunrise UT1 JDs, location, paksha, half, weekday, policy, selected nominal schedule, and provenance. Its provenance routing status is local_solar_half_and_weekday_performed_paksha_caller_supplied.

PanchaPakshiFixedClockMaterializationPolicy admits only policy_id="fixed_24_minute_nazhigai_from_local_solar_half_start_v1". Day anchors at governing topocentric sunrise and night at governing topocentric sunset. One nazhigai is exactly 1440 SI seconds; the fixed 30-nazhigai half is 43200 seconds. Exact offsets are added on reader-bound TT, endpoints are projected to UT1, intervals are half-open, and the fixed end is never clipped or stretched to the solar end. The result reports fixed_end_jd_tt_minus_solar_end_jd_tt with 0.0001 s numerical coalescence, per-cell solar-half relations, and routing status fixed_clock_materialization_performed_paksha_caller_supplied_no_current_cell. It performs neither current-cell selection nor solar-proportional scaling.

PanchaPakshiFixedClockCurrentCellSelectionPolicy admits only policy_id="fixed_clock_current_cell_half_open_solar_precedence_v1" and binds the Stage 2B materialization policy. The governing solar half is resolved before selection; membership uses reader-bound TT, exact half-open ownership, and 0.0 s tolerance. Shared endpoints belong to the following cell. Cells from a prior short half are ineligible after its solar boundary, while a long half that outlasts the fixed span returns selection_status="unmaterialized_solar_half_tail" and current_cell=None. The selector performs no clipping, wrapping, repeating, proportional scaling, or astronomical paksha inference. Its other finite status is selected.

PanchaPakshiSolarProportionalMaterializationPolicy is the separate Stage 2D policy and admits only policy_id="solar_proportional_nominal_offsets_over_governing_half_tt_v1". Each exact source nominal offset is divided by the complete 30-nazhigai nominal span, and every resulting rational endpoint fraction is mapped independently as anchor plus that fraction of the actual governing local-solar half on reader-bound TT. Interior endpoints are projected to UT1 through the same reader; the outer endpoints close exactly on the TT and UT1 anchor and governing solar-half end. The result contains exactly 25 contiguous, positive, half-open cells and performs no clipping, wrapping, repetition, fixed 1,440-second nazhigai conversion, current-cell selection, or astronomical paksha inference.

Stage 2D vesselPublic contract
PanchaPakshiSolarProportionalMaterializationStage 2A context, immutable policy, TT/UT1 anchor and governing-half-end fields, solar_half_duration_seconds_tt, 25 cells, and profile-owned provenance
PanchaPakshiSolarProportionalMaterializationPolicyExplicit caller-supplied-paksha, topocentric solar-half, reader-bound-TT mapping, UT1 publication, exact endpoint-closure, and half-open ownership doctrine, with fixed-clock seconds and current-cell selection marked not used or not performed
PanchaPakshiSolarProportionalCellOrdered schedule index, unchanged nominal cell, exact start_offset_fraction, end_offset_fraction, and span_fraction, TT/UT1 endpoints, and TT duration

PanchaPakshiSolarProportionalCurrentCellSelectionPolicy admits only policy_id="solar_proportional_current_cell_half_open_solar_precedence_v1" and binds the Stage 2D materialization policy. Stage 2A resolves the governing solar half before selection; the requested instant is converted to reader-bound TT once; and membership is exactly start_jd_tt <= requested_jd_tt < end_jd_tt with 0.0 s tolerance. The anchor belongs to cell zero, shared endpoints belong to the following cell, and exact sunrise or sunset belongs to the new half. Complete Stage 2D coverage makes selection_status="selected" and one non-null materialization member the only lawful result. Zero or multiple matches fail closed. The selector admits no tail, fallback, fixed-clock mixing, clipping, wrapping, borrowing, astronomical paksha inference, or natal identity.

Stage 2E vesselPublic contract
PanchaPakshiSolarProportionalCurrentCellSelectionComplete governing Stage 2D materialization, immutable selection policy, requested TT witness, selected-only status, one non-null materialization member, and route-specific provenance
PanchaPakshiSolarProportionalCurrentCellSelectionPolicyStage 2D policy binding, caller-supplied paksha, reader-bound-TT selection, exact half-open ownership, solar-half-first precedence, zero tolerance, complete-coverage requirement, exactly-one-match failure policy, and explicit non-use of fixed-clock mixing or paksha inference

The current agastya_madras_1879_akshara_fixed_clock profile is source_scoped_public and can never be selected implicitly. It performs no natal mapping, subdivision, or scoring computation. Its separately admitted Stage 2F product performs only explicit astronomical paksha inference and source-label mapping; it does not alter any schedule route. Its Stage 2I capability exposes only one named generator's first-samam EAT seed and performs no time routing, schedule materialization, or authority-role inference. The modern local_solar_day_explicit_paksha_v1 policy derives only topocentric sunrise/sunset context, day/night half, and local-mean-solar weekday while the paksha remains explicit. It returns the nominal schedule, not a current cell or clock-time interval. Stage 2B separately materializes fixed 1,440-second nazhigai offsets, and Stage 2C selects a current cell only from that fixed-clock materialization. Stage 2D is a distinct explicit modern Moira policy that maps the exact nominal fractions across the actual solar half. Stage 2E separately selects the unique current cell from that complete materialization. The named 1879 witness attests the nominal schedule, rational offsets, bird/activity assignments, chronology, locators, and the waxing/Purva and waning/Amara mapping; it does not attest Moira's exact numerical phase boundaries or proportional sunrise-to-sunset timing. Every result carries immutable profile-owned provenance and declared omissions. See the governing admission standard for the source and evidence boundary.

The separate bogamuni_chennai_2024_nakshatra_natal_identity profile is also source_scoped_public and can never be selected implicitly. It admits only the pure 54-cell nakshatra_bird_mapping product and its explicitly modern natal_identity composition. It supplies no aksara identity, operating schedule, relationship, materialization, current cell, authority bird, subdivision, condition, score, or window search. It does not alter or extend the 1879 profile.

The separate bogamuni_chennai_2024_padu_bird_mapping profile is likewise source_scoped_public, no-default, and Padu-only. It admits exactly one Paksha-by-weekday lookup capability and supplies no day/night axis, identity, schedule, materialization, current cell, authority-bird alias, condition, score, or forecast. The primary evidence distinguishes eating bird and authority day rather than attesting an Adhikara Pakshi table; Bharana remains secondary-only terminology. Neither term aliases the public Padu vessel.

The Moira facade supplies these ten kernel-free operations: pancha_pakshi_profiles, pancha_pakshi_profile_info, pancha_pakshi_identity_from_initial_vowel, pancha_pakshi_schedule, pancha_pakshi_directed_relationship, pancha_pakshi_nakshatra_bird_mapping, and pancha_pakshi_padu_bird_mapping, pancha_pakshi_first_eat_bird_mapping, and pancha_pakshi_sookshma_temporal_selection, and pancha_pakshi_schedule_sookshma_temporal_selection. It additionally supplies the kernel-backed pancha_pakshi_astronomical_paksha(profile_id, dt), pancha_pakshi_natal_moon_identity(profile_id, dt), pancha_pakshi_local_solar_context(profile_id, dt, latitude, longitude, *, paksha) and pancha_pakshi_fixed_clock_materialization(profile_id, dt, latitude, longitude, *, paksha), plus the pancha_pakshi_fixed_clock_current_cell(profile_id, dt, latitude, longitude, *, paksha) and pancha_pakshi_solar_proportional_materialization(profile_id, dt, latitude, longitude, *, paksha) and pancha_pakshi_solar_proportional_current_cell(profile_id, dt, latitude, longitude, *, paksha) and pancha_pakshi_civil_time_sookshma_selection(schedule_profile_id, selector_profile_id, dt, latitude, longitude, *, profile_paksha, subject_bird, timing_policy_id, selector_policy_id) adapters for aware datetimes. The low-level engine functions accept UT1 JD, while the facade preserves UTC civil anchoring before the UT1 conversion. Fixed-clock and solar-proportional offset arithmetic use reader-bound TT under their distinct policies, with both TT and projected UT1 endpoints returned. The Stage 2D and Stage 2E engine functions, result, policy, and cell vessels are first-class exports from moira, moira.facade, and moira.vedic; the Stage 2F function, enum, result, and policy vessels are exported through those same surfaces, as are the Stage 2G mapping, natal result, policy, and functions. The astronomical-paksha and natal-Moon facades accept an aware datetime but no location or caller-supplied paksha. The former's inferred label is never ambiently routed into the six location-bearing operations, and the latter returns an identity only without routing into any schedule family. The Stage 2H function, vessel, and facade lookup are also exported through all three Python surfaces and perform no clock or kernel access. The Stage 2I function, vessel, and facade lookup are likewise exported through all three surfaces and return only the named generator's first-samam EAT seed. The Stage 2K selector function, policy ID, policy, interval, result vessel, and facade method are exported through the same surfaces. The method requires an explicit policy and exact elapsed Fraction; it performs no clock, astronomy, schedule, Uromarisi outcome, condition, score, or forecast composition. The separate Stage 2N method requires both profile IDs, explicit schedule axes, samam, subject bird, selector policy, and exact elapsed Fraction. It derives the subject bird's parent activity from the named schedule samam under the modern explicit_schedule_samam_subject_bird_sookshma_v1 policy. It still performs no clock, astronomy, Uromarisi outcome, condition, score, or forecast operation. The matching strict route is POST /v1/pancha-pakshi/sookshma/schedule-select. The Stage 2O method additionally requires an aware datetime, location, source Paksha, subject bird, explicit timing policy, and explicit selector policy. It derives samam and elapsed nazhigai from the selected materialized reader-bound TT interval under civil_time_materialized_samam_to_stage2n_v1. A fixed-clock long-half tail remains an explicit null composition and never falls back to solar-proportional timing. The matching strict route is POST /v1/pancha-pakshi/sookshma/civil-time-select.


Panchanga

from moira.vedic import (
    panchanga_at, sankranti_at, tithi_condition_profile, panchanga_profile,
    PanchangaResult, SankrantiResult, TithiConditionProfile, PanchangaProfile,
    TithiPaksha, YogaClass, KaranaType, VaraLordType, PanchangaPolicy,
    TITHI_NAMES, YOGA_NAMES, KARANA_NAMES, VARA_LORDS, VARA_NAMES, RASHI_NAMES,
)
FunctionSignatureDescription
panchanga_at(sun_tropical_lon, moon_tropical_lon, jd, ayanamsa_system='Lahiri', policy=None)→ PanchangaResultFive Panchanga elements at a given JD
sankranti_at(jd_start, jd_end, reader=None)→ SankrantiResultSolar ingress into each rashi in a date range
tithi_condition_profile(result)→ TithiConditionProfileTithi quality assessment
panchanga_profile(result)→ PanchangaProfileAggregate Panchanga quality profile

Tithi and Karana share the normalized tropical Moon - Sun phase coordinate. The common ayanamsa would cancel from that difference, so Moira does not derive these boundaries by subtracting two separately rounded sidereal longitudes. This preserves exact conjunction, opposition, tithi, and half-tithi ownership; the Panchanga result still publishes the selected sidereal longitudes for the products that use them.

PanchangaResult fields

FieldTypeDescription
tithiintLunar day (1–30)
tithi_namestrName from TITHI_NAMES
pakshaTithiPakshaShukla (waxing) or Krishna (waning)
yogaintYoga index (0–26)
yoga_namestrName from YOGA_NAMES
yoga_classYogaClassAuspicious / Inauspicious / Neutral
karanaintKarana index
karana_namestrName from KARANA_NAMES
karana_typeKaranaTypeFixed or Movable
varaintWeekday (0 = Sunday)
vara_lordVaraLordTypePlanetary ruler of the day

Vedic Dignities

from moira.vedic import (
    vedic_dignity, planetary_relationships, dignity_condition_profile, chart_dignity_profile,
    VedicDignityResult, PlanetaryRelationship, VedicDignityPolicy,
    DignityConditionProfile, ChartDignityProfile,
    VedicDignityRank, CompoundRelationship, DignityTier,
)
FunctionSignatureDescription
vedic_dignity(planet, longitude, policy=None)→ VedicDignityResultEssential Vedic dignity for a planet at a longitude
planetary_relationships(planet, jd_ut, policy=None)→ PlanetaryRelationshipNatural + temporary + compound friendship
dignity_condition_profile(planet, chart)→ DignityConditionProfileDignity assessment with tier classification
chart_dignity_profile(chart)→ ChartDignityProfileFull chart-wide dignity analysis

Dignity constants: EXALTATION_SIGN, EXALTATION_DEGREE, DEBILITATION_SIGN, MULATRIKONA_SIGN, OWN_SIGNS, NATURAL_FRIENDS, NATURAL_NEUTRALS, NATURAL_ENEMIES.


Varga — Divisional Charts

from moira.vedic import (
    VargaPoint, calculate_varga,
    navamsa, saptamsa, dashamansa, dwadashamsa, trimshamsa,
    hora, chaturthamsha, shashthamsha, ashtamsha, shodashamsha,
    vimshamsha, chaturvimshamsha, saptavimshamsha,
    khavedamsha, akshavedamsha, shashtiamsha,
)

calculate_varga(longitude, divisor) → VargaPoint — low-level entry point for any divisor.

Named functions follow the pattern navamsa(longitude) → VargaPoint:

FunctionDivisionTraditional name
horaD-2Hora chart
saptamsaD-7Children / fertility
navamsaD-9Marriage / dharma
dashamansaD-10Career / profession
dwadashamsaD-12Ancestors / parents
chaturthamshaD-4Property
shashthamshaD-6Enemies / health
ashtamshaD-8Longevity
shodashamshaD-16Vehicles, comforts
vimshamshaD-20Spirituality
chaturvimshamshaD-24Education, learning
saptavimshamshaD-27Strength
trimshamsaD-30Evils, illness
khavedamshaD-40Auspicious/inauspicious effects
akshavedamshaD-45All matters
shashtiamshaD-60Past life karma

Vimshottari Dasha

from moira.vedic import (
    vimshottari, current_dasha, dasha_balance, dasha_active_line,
    dasha_condition_profile, dasha_sequence_profile, dasha_lord_pair,
    DashaPeriod, DashaActiveLine, DashaConditionProfile,
    DashaSequenceProfile, DashaLordPair,
    VimshottariComputationPolicy, DEFAULT_VIMSHOTTARI_POLICY,
    VIMSHOTTARI_YEARS, VIMSHOTTARI_SEQUENCE, VIMSHOTTARI_TOTAL,
)
FunctionSignatureDescription
vimshottari(moon_tropical_lon, natal_jd, levels=2, ayanamsa_system=None, *, year_basis=None, policy=None)→ list[DashaPeriod]Full generated sequence from birth
current_dasha(moon_tropical_lon, natal_jd, current_jd, ayanamsa_system=None, *, year_basis=None, levels=5, policy=None)→ list[DashaPeriod]Active period at each requested level
dasha_balance(moon_tropical_lon, natal_jd, ayanamsa_system=None, *, year_basis=None, policy=None)→ tuple[str, float](lord, years_remaining) at birth
dasha_active_line(active_periods)→ DashaActiveLineNamed active chain from current_dasha(...) output
dasha_condition_profile(period)→ DashaConditionProfileCondition assessment for one DashaPeriod
dasha_sequence_profile(periods)→ DashaSequenceProfileAggregate profile for vimshottari(...) output
dasha_lord_pair(line)→ DashaLordPairMahadasha and Antardasha lords from an active line

VIMSHOTTARI_YEARS — dict of dasha lord → years in the 120-year cycle. VIMSHOTTARI_SEQUENCE — canonical lord sequence (Ketu, Venus, Sun, Moon, …).


Alternate Dasha Systems — Ashtottari and Yogini

from moira.vedic import (
    ashtottari, yogini_dasha,
    AlternateDashaPeriod, AshtottariPolicy, YoginiPolicy,
    AlternatePeriodProfile, AlternateDashaSequenceProfile,
    ASHTOTTARI_YEARS, ASHTOTTARI_SEQUENCE, ASHTOTTARI_TOTAL,
    YOGINI_YEARS, YOGINI_SEQUENCE, YOGINI_PLANETS, YOGINI_TOTAL,
)
FunctionSignatureDescription
ashtottari(moon_lon, birth_jd, policy=None)→ list[AlternateDashaPeriod]108-year Ashtottari sequence
yogini_dasha(moon_lon, birth_jd, policy=None)→ list[AlternateDashaPeriod]36-year Yogini sequence

AshtottariPolicy and YoginiPolicy control year-basis and sequence parameters.


Jaimini Karakas

from moira.vedic import (
    jaimini_karakas, atmakaraka, karaka_condition_profile, jaimini_chart_profile,
    JaiminiKarakaResult, KarakaAssignment, KarakaConditionProfile, JaiminiChartProfile,
    JaiminiPolicy, KarakaRole, KarakaPlanetType,
    KARAKA_NAMES_7, KARAKA_NAMES_8,
)
FunctionSignatureDescription
jaimini_karakas(chart_longitudes, policy=None)→ JaiminiKarakaResultFull 7- or 8-karaka assignment by degree
atmakaraka(chart_longitudes, policy=None)→ KarakaAssignmentHighest-degree planet (soul significator)
karaka_condition_profile(chart_longitudes, policy=None)→ KarakaConditionProfileDignity condition for each karaka
jaimini_chart_profile(chart_longitudes, policy=None)→ JaiminiChartProfileFull chart karaka profile

JaiminiPolicy controls the 7-karaka vs. 8-karaka scheme and Rahu tie-break behaviour.


Ashtakavarga

from moira.vedic import (
    bhinnashtakavarga, ashtakavarga, transit_strength,
    sign_strength_profile, ashtakavarga_chart_profile,
    BhinnashtakavargaResult, AshtakavargaResult,
    SignStrengthProfile, AshtakavargaChartProfile,
    AshtakavargaPolicy, RekhaTier, REKHA_TABLES,
)
FunctionSignatureDescription
bhinnashtakavarga(planet, chart_longitudes, policy=None)→ BhinnashtakavargaResultIndividual planet's bindus per sign (8-source table)
ashtakavarga(chart_longitudes, policy=None)→ AshtakavargaResultSarvashtakavarga — combined bindus per sign
transit_strength(planet, transit_lon, natal_chart_longitudes)→ floatAshtakavarga bindus for a transit position
sign_strength_profile(chart_longitudes, policy=None)→ SignStrengthProfilePer-sign bindu strengths with RekhaTier labels
ashtakavarga_chart_profile(chart_longitudes, policy=None)→ AshtakavargaChartProfileFull chart Ashtakavarga profile

Shadbala

from moira.vedic import (
    shadbala, sthana_bala, dig_bala, kala_bala, chesta_bala, drig_bala,
    hora_lord_at, shadbala_condition_profile, shadbala_chart_profile,
    ShadbalaResult, PlanetShadbala, ShadbalaPolicy,
    ShadbalaConditionProfile, ShadbalaChartProfile,
    SthanaBala, KalaBala, ShadbalaTier,
    NAISARGIKA_BALA, REQUIRED_RUPAS,
)
FunctionSignatureDescription
shadbala(sidereal_longitudes, planet_speeds, houses, jd, tithi_number, vara_lord, is_day, ayanamsa_system='Lahiri', hora_lord=None, planet_latitudes=None)→ ShadbalaResultFull six-fold strength for the seven classical planets
sthana_bala(planet, sidereal_lon, houses, jd, ayanamsa_system='Lahiri')→ SthanaBalaPositional strength components
dig_bala(planet, sidereal_lon, houses, jd, ayanamsa_system='Lahiri')→ floatDirectional strength
kala_bala(planet, sidereal_lon, sun_sidereal_lon, jd, tithi_number, is_day, vara_lord, planet_speeds, ...)→ KalaBalaTemporal strength components
chesta_bala(planet, speed, planet_sidereal_lon=None, mandoccha_sidereal_lon=None)→ floatMotional strength
drig_bala(planet, sidereal_longitudes)→ floatAspectual strength
hora_lord_at(birth_jd, sunrise_jd)→ strPlanetary hour ruler from a caller-supplied sunrise
shadbala_condition_profile(planet_result)→ ShadbalaConditionProfileCondition assessment for one PlanetShadbala
shadbala_chart_profile(result)→ ShadbalaChartProfileAggregate profile for one ShadbalaResult

NAISARGIKA_BALA — natural strength constants (Saturn lowest, Sun highest). REQUIRED_RUPAS — minimum Shadbala rupas required for each planet.


Appendix A — Stability Tiers

TierMeaningExamples
FrozenSignature and semantics will not change without a major version bumpMoira, Chart, Body, HouseSystem, AspectData, HouseCusps, all __all__ members
ProvisionalNone currently designated

Appendix B — Kernel & Reader

from moira.spk_reader import get_reader, set_kernel_path, SpkReader

# Set path globally (persists for the process lifetime):
set_kernel_path("/data/de441.bsp")

# Get the shared singleton reader:
reader = get_reader()

# Or construct a private reader:
reader = get_reader("/data/de441.bsp")

The reader argument accepted by most low-level functions defaults to the global singleton if None. Pass an explicit reader only when you need isolation from the global state (e.g., in tests or multi-tenant contexts).


Appendix C — Coverage & Accuracy

PropertyValue
EphemerisJPL DE441
Date coverage13 200 BC → 17 191 AD
Coordinate frameGeocentric ecliptic, tropical (J2000.0 mean equinox)
Sidereal optionAny ayanamsa via ayanamsa() / tropical_to_sidereal()
ΔT modelHybrid IERS/Morrison-Stephenson/extrapolation
NutationIAU 2000A (1365 terms)
ObliquityLaskar 1986 / IAU 2006 combined
Topocentric MoonParallax-corrected RA/Dec and altitude
Fixed starsSovereign registry (star_registry.csv + JSON sidecars)
Variable stars20-star classical catalog with period/epoch/magnitude data