# Moira API Reference **Document revision:** 2.3.0 **Engine baseline:** 6.9.9 **Last verified:** 2026-09-26 **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](HELLENISTIC_CAPABILITY_MATRIX.generated.md) for current export-tier receipts and the [source validation audit](HELLENISTIC_SOURCE_VALIDATION_2026-07.md) 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](#1-quick-start) 2. [API Architecture — Four-Tier Entry Points](#2-api-architecture--four-tier-entry-points) 3. [Core Types](#3-core-types) 4. [Moira Facade](#4-moira-facade) 5. [Ephemeris & Positions](#5-ephemeris--positions) 6. [Alternative Reference Frames](#6-alternative-reference-frames) 7. [Chart Structure](#7-chart-structure) 8. [Classical Techniques](#8-classical-techniques) 9. [Timing Techniques](#9-timing-techniques) 10. [Planetary Cycles Engine](#10-planetary-cycles-engine) 11. [Huber Method](#11-huber-method) 12. [Relational Techniques](#12-relational-techniques) 13. [Geography](#13-geography) 14. [Fixed Stars](#14-fixed-stars) 15. [Eclipses & Phenomena](#15-eclipses--phenomena) 16. [Harmograms — Spectral Harmonic Analysis](#16-harmograms--spectral-harmonic-analysis) 17. [Constellation Oracle](#17-constellation-oracle) 18. [Calendar & Time](#18-calendar--time) 19. [Policy Objects](#19-policy-objects) 20. [`moira.sky` — Strict Astronomy API](#20-moirasky--strict-astronomy-api) 21. [`moira.vedic` — Vedic Astrology Surface](#21-moiravedic--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()` first honors `MOIRA_KERNEL_PATH` when it names an existing file, then auto-detects the first installed planetary kernel from the supported precedence `de441.bsp`, `de440.bsp`, `de432.bsp`, `de431.bsp`, `de430.bsp`, searched in this order of locations: 1. `MOIRA_KERNELS_DIR`, when configured 2. `~/.moira/kernels/` 3. `moira/kernels/` inside the installed package 4. `kernels/` in a development checkout 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. ```python 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 ```python 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 ```python aspects = m.aspects(chart) for a in aspects: print(f"{a.body1} {a.aspect} {a.body2} orb {a.orb:+.2f}°") ``` ### Transits to natal point ```python 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) # Many natal points and aspects at once: one native scan of the mover. natal_points = [p.longitude for p in chart.planets.values()] for hit in m.natal_aspect_transits( Body.SATURN, natal_points, [0.0, 90.0, 180.0], jd_start, jd_end, aspect_orbs=[1.0, 1.0, 1.0] ): print(hit.jd_exact, hit.target, hit.angle, hit.is_retrograde_hit) ``` --- ## 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 ```python 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 ```python from moira.classical import * # includes everything from essentials, plus: ``` Adds the full classical and traditional toolkit: | Added domain | Key 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` | | Dignities | `calculate_dignities`, `calculate_receptions`, `EssentialDignityKind`, `PlanetaryDignity`, `DignityHorizonFrame`, `PlanetarySolarPhaseTruth`, `SolarProximityTruth`, `BesiegingTruth`, `planetary_solar_phase_truth`, `solar_proximity_truth`, `besieging_truth` | | Arabic Parts | `calculate_lots`, `evaluate_lots`, `ArabicPart`, `LotsEvaluation`, `LotNotEvaluable`, `ArabicPartsService`, `list_parts` | | Unified Hellenistic profile | `hellenistic_chart_profile`, `HellenisticChartProfile`, `HellenisticProfilePolicy`, `HellenisticProfileProvenance` | | Midpoints | `calculate_midpoints`, `Midpoint`, `MidpointsService`, `midpoint_tree`, `planetary_pictures` | | Antiscia | `find_antiscia`, `AntisciaAspect`, `antiscion`, `contra_antiscion` | | Fixed stars | `star_at`, `all_stars_at`, `FixedStar`, `list_stars`, `find_stars`, `star_magnitude` | | Lunar mansions | `mansion_of`, `all_mansions_at`, `MANSIONS` | | Profections | `annual_profection`, `monthly_profection`, `profection_schedule` | | Time lords | `firdaria`, `zodiacal_releasing`, `vimshottari` | | Vedic divisional | `navamsa`, `saptamsa`, `dashamansa`, `dwadashamsa`, `trimshamsa` | | Vedic dignities | `vedic_dignity`, `planetary_relationships`, `VedicDignityResult`, `VedicDignityPolicy` | | Panchanga | `panchanga_at`, `PanchangaResult`, `PanchangaPolicy`, `tithi_condition_profile` | | Jaimini | `jaimini_karakas`, `atmakaraka`, `JaiminiKarakaResult`, `JaiminiPolicy` | | Ashtakavarga | `bhinnashtakavarga`, `ashtakavarga`, `AshtakavargaResult`, `AshtakavargaPolicy` | | Shadbala | `shadbala`, `hora_lord_at`, `ShadbalaResult`, `ShadbalaPolicy` | | Alternate dashas | `ashtottari`, `yogini_dasha`, `AlternateDashaPeriod`, `AshtottariPolicy`, `YoginiPolicy` | | Planetary hours | `planetary_hours` | | Huber | `house_zones`, `age_point`, `chart_intensity_profile` (also importable directly from moira.huber) | ### `moira.predictive` — Forecasting surface ```python from moira.predictive import * # includes everything from classical, plus: ``` Adds the complete forecasting and relationship toolkit: | Added domain | Key symbols | |---|---| | Transits | `find_transits`, `next_transit`, `find_ingresses`, `next_ingress`, `TransitEvent`, `IngressEvent`, `TransitSearchPolicy` | | Progressions | `secondary_progression`, `solar_arc`, `solar_arc_right_ascension`, `naibod_longitude`, `tertiary_progression`, `tertiary_ii_progression`, `converse_*` variants, `minor_progression`, `daily_house_frame` | | Primary directions | `speculum`, `find_primary_arcs`, `SpeculumEntry`, `PrimaryArc` | | Synastry | `synastry_aspects`, `house_overlay`, `mutual_house_overlays`, `composite_chart`, `davison_chart`, `CompositeChart`, `DavisonChart` | | Eclipses | `EclipseData`, `EclipseEvent`, `EclipseCalculator`, `LunarEclipseAnalysis` | | Returns | `solar_return`, `lunar_return`, `planet_return` | | Stations | `find_stations`, `is_retrograde`, `retrograde_periods`, `StationEvent` | | Void of course | `void_of_course_window`, `is_void_of_course`, `next_void_of_course`, `void_periods_in_range` | | Phenomena | `greatest_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`. Track A relationship transits, relocated returns, fixed-star Astrocartography, and explicit-epoch transiting Astrocartography are admitted through `moira`, `moira.facade`, their owning modules, and reader-bound `Moira` methods. They are intentionally not re-exported by `moira.predictive`; their exact contracts are documented in Sections 12 and 13 below. ### `moira.facade` and `moira` — Admitted facade vs curated root ```python 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. Track A admits the following 35 symbols through each owning module, `moira.facade`, and the curated `moira` root with identical object identity. They are not `moira.predictive` exports. | Owning module | Admitted symbols | |---|---| | `moira.relationship_forecasting` | `RelationshipChartKind`, `RelationshipTargetKind`, `RelationshipChartIdentity`, `RelationshipTransitTarget`, `RelationshipChartTargetSet`, `RelationshipTransitEvent`, `RelationshipTransitSearchTruth`, `RelationshipTransitSearchResult`, `relationship_chart_targets`, `find_relationship_transits`, `find_composite_transits`, `find_davison_transits` | | `moira.astrocartography` | `FixedStarAstrocartographySubject`, `FixedStarAstrocartographyTruth`, `FixedStarAstrocartographyResult`, `fixed_star_equatorial_subject`, `fixed_star_astrocartography`, `fixed_star_astrocartography_from_chart` | | `moira.locational_forecasting` | `ReturnKind`, `ReturnSearchPolicyTruth`, `ReturnMomentTruth`, `ReturnRelocationTruth`, `RelocatedReturnChart`, `DynamicAstrocartographyMode`, `DynamicAstrocartographyPosition`, `DynamicAstrocartographySnapshotTruth`, `DynamicAstrocartographySnapshot`, `AstrocartographyCurvePointShift`, `DynamicAstrocartographyLineTransition`, `DynamicAstrocartographySeriesTruth`, `DynamicAstrocartographySeries`, `relocated_solar_return`, `relocated_lunar_return`, `relocated_planetary_return`, `transiting_astrocartography` | Track B admits a deliberately smaller root/facade surface. Concrete input, search, clock, consideration, and event receipts remain available from their owning modules instead of being dumped into the curated tiers. | Owning module | Curated root/facade symbols | |---|---| | `moira.horary` | `HoraryQuestionTimeBasis`, `HorarySourceCalendar`, `HoraryEvidenceProfile`, `LILLY_1647_HORARY_V1`, `horary_evidence_at` | | `moira.mundane` | `MundaneEvaluationStatus`, `MundaneEventType`, `MundaneEventChartProfile`, `MundaneProfileProvenance`, `compose_mundane_event_chart_profile`, `eclipse_receipt_from_event`, `jupiter_saturn_sequence_from_series` | These Track B names have identical object identity through `moira`, `moira.facade`, and their owning modules. They are intentionally absent from the cumulative `moira.classical` and `moira.predictive` tiers. 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 - Astro*Carto*Graphy, 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 ```python 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: The unified surface also includes the already-curated yoga, avastha, extended Jaimini, Muhurta, upagraha and Sade Sati families; Vimshopaka/Vargottama, Kakshya/Shodhya Pinda and Bhava Bala helpers; and the source-bound Gochara snapshot and doctrine catalogue, plus reader-bound epoch/aware-datetime Gochar composition. See the [dated Gochar standard](GOCHARA_DATE_DERIVED_STANDARD.md) for `gochara_at`, `gochara_for_datetimes`, astronomical policies, BAV provenance and typed errors. Imports share the owning engine objects. Bounded Muhurta search and chart scoring also share the owning `muhurta_search` objects: `find_muhurta_windows`, `muhurta_score_for_chart` and their typed policies, receipts and errors. See the [personalized Muhurta/search standard](MUHURTA_PERSONAL_SEARCH_STANDARD.md) for reader binding, sampling limits, JD-weekday Vara and explicit exclusions. Varga products already exposed through the facade are also available at the package root. This curation does not admit standalone Sayanadi, special Lagnas or Kalachakra. Daily sunrise-owned Panchanga has its separately admitted policy and [standard](DAILY_PANCHANGA_STANDARD.md). See the [Vedic surface ledger](VEDIC_SURFACE_REST_COMPLETENESS_2026-10-06.md) for the completed package and the [Vedic remaining work register](VEDIC_REMAINING_WORK_REGISTER.md) for current integration and enrichment gaps. | Added domain | Key symbols | |---|---| | Sidereal & Nakshatras | `UserDefinedAyanamsa`, `NakshatraPosition`, `nakshatra_of`, `all_nakshatras_at` | | Panchanga | `panchanga_at`, `sankranti_at`, `PanchangaResult`, `TithiPaksha`, `PanchangaPolicy` | | Pancha Pakshi | `available_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 dignities | `vedic_dignity`, `planetary_relationships`, `VedicDignityResult`, `DignityConditionProfile`, `ChartDignityProfile` | | Varga (divisional) | `navamsa`, `saptamsa`, `dashamansa`, `dwadashamsa`, `trimshamsa` + 11 more vargas, `VargaPoint` | | Vimshottari Dasha | `vimshottari`, `current_dasha`, `dasha_balance`, `dasha_active_line`, `DashaPeriod`, `VimshottariComputationPolicy` | | Alternate dashas | `ashtottari`, `yogini_dasha`, `AlternateDashaPeriod`, `AshtottariPolicy`, `YoginiPolicy` | | Jaimini karakas | `jaimini_karakas`, `atmakaraka`, `JaiminiKarakaResult`, `JaiminiPolicy` | | Ashtakavarga | `bhinnashtakavarga`, `ashtakavarga`, `transit_strength`, `AshtakavargaResult`, `AshtakavargaPolicy` | | Shadbala | `shadbala`, `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](#21-moiravedic--vedic-astrology-surface) 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: | Submodule | Purpose | |---|---| | `moira.sky.time` | UT/TT/TDB conversions, ERA, GMST, GAST, LAST, ΔT decomposition | | `moira.sky.position` | Five-stage astrometric correction pipeline (light-time → aberration → deflection → frame bias → topocentric) | | `moira.sky.frames` | ICRF/ecliptic/equatorial/horizontal transforms, precession and nutation matrices | | `moira.sky.visibility` | Heliacal events, Yallop lunar crescent, arcus visionis, atmospheric extinction | | `moira.sky.bodies` | Geocentric, heliocentric, SSB, planetocentric, and topocentric body positions; nodes and apsides | | `moira.sky.observation` | Phase angle, illuminated fraction, apparent magnitude, elongation, Moon phases, apsides, conjunctions | | `moira.sky.galactic` | Galactic coordinate transforms (Liu, Zhu & Zhang 2011), legacy five-point compatibility table | | `moira.cosmic_references` | Typed physical-object, coordinate-landmark, and proxy reference directions with explicit provenance | | `moira.sky.events` | Rise/set/transit, twilight times, stations and retrograde periods | | `moira.sky.eclipse` | Eclipse prediction, contact times, geographic paths, Saros/Metonic identification | | `moira.sky.occultation` | Lunar occultations of planets and stars, graze geometry, close approaches | See [Section 20](#20-moirasky--strict-astronomy-api) for the full submodule reference. --- ## 3. Core Types ### `Body` — celestial body constants ```python 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 ```python 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 ```python 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. ```python 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. | Field | Type | Description | |---|---|---| | `jd_ut` | `float` | Legacy 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 | | `planets` | `dict[str, PlanetData]` | Geocentric ecliptic positions | | `nodes` | `dict[str, NodeData]` | Lunar nodes and Lilith | | `obliquity` | `float` | True obliquity of the ecliptic (°) | | `delta_t` | `float` | ΔT = TT − UT1 in seconds at the resolved astronomical instant | **Properties:** | Property | Returns | Description | |---|---|---| | `datetime_utc` | `datetime` | UTC datetime for this snapshot | | `calendar_utc` | `CalendarDateTime` | BCE-safe calendar breakdown | **Methods:** | Method | Returns | Description | |---|---|---| | `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 | Field | Type | Description | |---|---|---| | `longitude` | `float` | Ecliptic longitude, tropical (°) | | `latitude` | `float` | Ecliptic latitude (°) | | `speed` | `float` | Daily motion in longitude (negative = retrograde) | | `distance` | `float` | Distance from Earth (km) | ### `NodeData` — lunar node position | Field | Type | Description | |---|---|---| | `longitude` | `float` | Ecliptic longitude (°) | | `speed` | `float` | Daily motion (°/day) | ### `SkyPosition` — topocentric equatorial/horizontal | Field | Type | Description | |---|---|---| | `right_ascension` | `float` | Apparent RA (°) | | `declination` | `float` | Apparent Dec (°) | | `altitude` | `float` | Altitude above horizon (°) | | `azimuth` | `float` | Azimuth, North = 0° (°) | | `distance` | `float` | Distance (km) | ### `HouseCusps` — computed house frame | Field | Type | Description | |---|---|---| | `cusps` | `tuple[float, ...]` | 12 house cusp longitudes (°), index 0 = cusp 1 | | `asc` | `float` | Ascendant (°) | | `mc` | `float` | Midheaven (°) | | `armc` | `float` | ARMC — Sidereal time × 15 (°) | | `east_point` | `float` | East Point / Equatorial Ascendant longitude (°) | | `vertex` | `float` | Vertex longitude (°) | | `system` | `str` | Requested house system code | | `effective_system` | `str` | Effective system code after policy resolution | | `fallback` | `bool` | Whether fallback policy altered the requested system | | `fallback_reason` | `str \| None` | Human-readable fallback reason, if any | | `classification` | `HouseSystemClassification` | Classification of the effective house system | | `policy` | `HousePolicy` | Governing 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 ```python 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 | Member | Returns | Description | |---|---|---| | `is_kernel_available()` | `bool` | Whether a planetary kernel is ready right now | | `get_kernel_status()` | `str` | Human-readable kernel readiness message | | `kernel_status` | `str` | Property alias of `get_kernel_status()` | | `available_kernels` | `list[str]` | Installed planetary and supplemental kernel filenames | | `configure_kernel_path(path)` | `None` | Configure and validate an explicit planetary kernel path | | `download_missing_kernels(interactive=False)` | `None` | Download missing kernels into the standard user directory | ### Core chart methods | Method | Returns | Description | |---|---|---| | `chart(dt, bodies=None, include_nodes=True, observer_lat=None, observer_lon=None, observer_elev_m=0.0)` | `Chart` | Complete planetary snapshot; supply observer coords for topocentric Moon | | `houses(dt, latitude, longitude, system=HouseSystem.PLACIDUS, policy=None)` | `HouseCusps` | House cusps, angles, ARMC under explicit house policy when supplied | | `sky_position(dt, body, latitude, longitude, elevation_m=0.0)` | `SkyPosition` | Apparent 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)` | `dict` | Phase angle, illumination, angular diameter, apparent magnitude | | `lunar_orientation(dt, observer=None)` | `LunarOrientation` | Total apparent lunar libration and visible-disc orientation; optional WGS-84 topocentric observer | | `twilight(dt, latitude, longitude)` | `TwilightTimes` | Civil/nautical/astronomical twilight times | ### Aspects & patterns | Method | Returns | Description | |---|---|---| | `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")` | `HellenisticSuperiorityTruth` | Raw 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)` | `HarmonicTransitForecast` | Sampled 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 | Method | Returns | Description | |---|---|---| | `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)` | `LotsEvaluation` | Lossless lot catalogue evaluation including typed unresolved entries | | `hellenistic_chart_profile(chart, houses, natal_dt, current_dt, *, civil_timezone=None, policy=None, ...)` | `HellenisticChartProfile` | Score-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 receipts | Raw 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)` | `AstrodyneChartResult` | Kernel-free Church of Light natal Astrodynes from explicit chart geometry | ### Classical techniques | Method | Returns | Description | |---|---|---| | `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)` | `ProfectionResult` | Annual 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=...)` | `ProfectionChronology` | Raw dated monthly profection receipt | | `nakshatras(chart, ayanamsa_system=Ayanamsa.LAHIRI)` | `dict[str, NakshatraPosition]` | Nakshatra for each planet | | `planetary_hours(dt, latitude, longitude)` | `PlanetaryHoursDay` | Day and night planetary hour rulers | | `horary_evidence_at(question, *, house_policy, perfection_jd_end=None)` | `HoraryEvidenceProfile` | Reader-bound Lilly 1647 evidence composition with explicit question, house, and optional bounded perfection-search inputs; no judgement or inferred topic | ### Timing techniques | Method | Returns | Description | |---|---|---| | `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 | | `assess_transit_cardinal_ingress(event)` | `MundaneEventEvidence` | Revalidate one exact cardinal-ingress event against the facade reader and preserve neutral event truth | | `assess_transit_primary_syzygy(anchor_event, *, policy=None)` | `PrecedingSyzygyEvidence` | Solve and bind the strictly preceding primary syzygy to one admitted ingress receipt | | `next_ingress(body, jd_start, max_days=None)` | `IngressEvent \| None` | Next sign ingress of any kind | | `next_ingress_into(body, sign, jd_start, max_days=None)` | `IngressEvent \| None` | Next entry into a specific sign | | `solar_return(natal_sun_lon, year)` | `float` | JD UT of the Solar Return in a calendar year | | `lunar_return(natal_moon_lon, jd_start)` | `float` | JD UT of the next Lunar Return | | `planet_return(body, natal_lon, jd_start, direction="direct")` | `float` | JD UT of the next planetary return | | `relocated_solar_return(natal_sun_longitude, year, source_latitude, source_longitude, relocated_latitude, relocated_longitude, **policy)` | `RelocatedReturnChart` | Solve one canonical solar return and recast its unchanged sky into two local house frames | | `relocated_lunar_return(natal_moon_longitude, jd_start, source_latitude, source_longitude, relocated_latitude, relocated_longitude, **policy)` | `RelocatedReturnChart` | Canonical next lunar-return composition at two locations | | `relocated_planetary_return(body, natal_longitude, jd_start, source_latitude, source_longitude, relocated_latitude, relocated_longitude, **policy)` | `RelocatedReturnChart` | Canonical admitted planetary-return composition at two locations | | `syzygy(jd)` | `tuple[float, str]` | `(jd_ut, kind)` of prenatal syzygy | | `eclipse_receipt_from_event(event, *, eclipse_id)` | `EclipseEventReceipt` | Revalidate one engine eclipse while retaining explicit named epoch meanings and reader-derived identity | | `jupiter_saturn_sequence_from_series(series)` | `JupiterSaturnConjunctionSequenceReceipt` | Recompute and preserve every exact Jupiter-Saturn ecliptic-longitude root in the supplied interval | | `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](#void-of-course-moon) subsection below | | `electional_windows(dt_start, dt_end, ...)` | — | See [Electional search](#electional-search) subsection below | | `ramesey_moon_condition_at(jd_ut, latitude, longitude, *, house_system, ...)` | `RameseyMoonConditionEvaluation` | One bounded Ramesey v1 Moon-condition evaluation; no search, score, or recommendation | ### Progressions & directions | Method | Returns | Description | |---|---|---| | `progression(natal_dt, target_dt, bodies=None)` | `ProgressedChart` | Secondary progression (1 day = 1 year) | | `solar_arc_directions(natal_dt, target_dt, bodies=None)` | `ProgressedChart` | Solar Arc directed chart | | `solar_arc_directions_ra(natal_dt, target_dt, bodies=None)` | `ProgressedChart` | Solar Arc in right ascension | | `naibod_in_longitude(natal_dt, target_dt, bodies=None)` | `ProgressedChart` | Naibod directions in ecliptic longitude | | `naibod_in_right_ascension(natal_dt, target_dt, bodies=None)` | `ProgressedChart` | Naibod directions in right ascension | | `tertiary_progression(natal_dt, target_dt, bodies=None)` | `ProgressedChart` | Tertiary progression (1 day = 1 lunar month) | | `tertiary_ii_progression(natal_dt, target_dt, bodies=None)` | `ProgressedChart` | Tertiary II / Klaus Wessel | | `minor_progression(natal_dt, target_dt, bodies=None)` | `ProgressedChart` | Minor progression (1 lunar month = 1 year) | | `ascendant_arc_directions(natal_dt, target_dt, latitude, longitude, bodies=None)` | `ProgressedChart` | Ascendant Arc directed chart | | `daily_house_frame(natal_dt, target_dt, latitude, longitude, system=...)` | `HouseCusps` | Daily Houses progressed frame | | `converse_progression(natal_dt, target_dt, bodies=None)` | `ProgressedChart` | Converse Secondary Progressed | | `converse_solar_arc(natal_dt, target_dt, bodies=None)` | `ProgressedChart` | Converse Solar Arc | | `converse_solar_arc_ra(natal_dt, target_dt, bodies=None)` | `ProgressedChart` | Converse Solar Arc in RA | | `converse_naibod_in_longitude(natal_dt, target_dt, bodies=None)` | `ProgressedChart` | Converse Naibod in longitude | | `converse_naibod_in_right_ascension(natal_dt, target_dt, bodies=None)` | `ProgressedChart` | Converse Naibod in RA | | `converse_tertiary_progression(natal_dt, target_dt, bodies=None)` | `ProgressedChart` | Converse Tertiary | | `converse_tertiary_ii_progression(natal_dt, target_dt, bodies=None)` | `ProgressedChart` | Converse Tertiary II | | `converse_minor_progression(natal_dt, target_dt, bodies=None)` | `ProgressedChart` | Converse Minor | | `duodenary_progression(natal_dt, target_dt, bodies=None)` | `ProgressedChart` | Duodenary progression | | `converse_duodenary_progression(natal_dt, target_dt, bodies=None)` | `ProgressedChart` | Converse duodenary progression | | `quotidian_solar_progression(natal_dt, target_dt, bodies=None)` | `ProgressedChart` | Solar quotidian progression | | `converse_quotidian_solar_progression(natal_dt, target_dt, bodies=None)` | `ProgressedChart` | Converse solar quotidian progression | | `quotidian_lunar_progression(natal_dt, target_dt, bodies=None)` | `ProgressedChart` | Lunar quotidian progression | | `converse_quotidian_lunar_progression(natal_dt, target_dt, bodies=None)` | `ProgressedChart` | Converse lunar quotidian progression | | `planetary_arc_directions(natal_dt, target_dt, arc_body, bodies=None)` | `ProgressedChart` | Planetary-arc directed chart | | `converse_planetary_arc_directions(natal_dt, target_dt, arc_body, bodies=None)` | `ProgressedChart` | Converse 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)` | `PrimaryDirectionsPolicy` | Build one canonical typed policy preset | | `primary_direction_relations(arc, *, policy=None)` | `PrimaryDirectionRelationProfile` | Evaluate one arc's admitted/scored relation truth | | `primary_direction_condition(arcs, *, policy=None)` | `PrimaryDirectionsSignificatorProfile` | Evaluate one significator's directed condition | | `primary_directions_profile(arcs, *, policy=None)` | `PrimaryDirectionsAggregateProfile` | Evaluate a chart-wide primary-directions aggregate | | `primary_directions_network(arcs, *, policy=None)` | `PrimaryDirectionsNetworkProfile` | Evaluate the directed promissor-to-significator graph | ### Hellenistic & Vedic time lords | Method | Returns | Description | |---|---|---| | `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 | Method | Returns | Description | |---|---|---| | `synastry_aspects(chart_a, chart_b, tier=0, orbs=None, orb_factor=1.0, include_nodes=True)` | `list[AspectData]` | Inter-aspects between two natal charts (default tier 0: the five major aspects; 6.9.9) | | `house_overlay(chart_source, target_houses, include_nodes=True, source_label="A", target_label="B")` | `SynastryHouseOverlay` | Place chart_source planets in target_houses | | `mutual_house_overlays(chart_a, houses_a, chart_b, houses_b, include_nodes=True)` | `MutualHouseOverlay` | Both overlay directions in one call | | `composite_chart(chart_a, chart_b, houses_a=None, houses_b=None)` | `CompositeChart` | Midpoint composite | | `aspects_from_longitudes(longitudes, *, tier=1, orb_factor=1.0, include_nodes=True)` | `LongitudeAspectAnalysis` | Canonical 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)` | `CompositeChart` | Reference-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)` | `DavisonChart` | Davison Relationship Chart (spherical midpoint time + location) | | `davison_chart_uncorrected(...)` | `DavisonChart` | Davison with arithmetic midpoints | | `davison_chart_reference_place(dt_a, dt_b, ref_lat, ref_lon, house_system=...)` | `DavisonChart` | Davison with midpoint time and explicit place | | `davison_chart_spherical_midpoint(...)` | `DavisonChart` | Davison with midpoint time and spherical geographic midpoint | | `davison_chart_corrected(...)` | `DavisonChart` | Davison with midpoint location and corrected time | | `relationship_chart_targets(chart, *, include_nodes=True, include_angles=False, include_cusps=False, target_names=None)` | `RelationshipChartTargetSet` | Extract identity-bound static targets without requiring a reader | | `relationship_transits(chart, moving_bodies, jd_start, jd_end, **search_options)` | `RelationshipTransitSearchResult` | Exact canonical transit perfections to composite or Davison targets | | `composite_transits(chart, moving_bodies, jd_start, jd_end, **search_options)` | `RelationshipTransitSearchResult` | Composite-restricted exact transit search | | `davison_transits(chart, moving_bodies, jd_start, jd_end, **search_options)` | `RelationshipTransitSearchResult` | Davison-restricted exact transit search | 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 | Method | Returns | Description | |---|---|---| | `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 | | `transiting_astrocartography(epochs_jd_ut, bodies, **geometry_options)` | `DynamicAstrocartographySeries` | Reader-bound explicit-epoch snapshots and adjacent line-displacement receipts | | `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 | Method | Returns | Description | |---|---|---| | `fixed_star(name, dt)` | `FixedStar` | Unified star position enriched with Gaia DR3 data | | `fixed_star_astrocartography(star_names, jd_ut, jd_tt, *, lat_step=2.0, refraction=False)` | `FixedStarAstrocartographyResult` | Source-resolved fixed-star lines and zenith/nadir points; facade convenience without reader injection | | `heliacal_rising(star_name, dt, latitude, longitude)` | `float \| None` | JD UT of the next heliacal rising | | `heliacal_setting(star_name, dt, latitude, longitude)` | `float \| None` | JD UT of the next heliacal setting | | `heliacal_rising_event(star_name, dt, latitude, longitude)` | `HeliacalEvent` | Full heliacal-rising event vessel with classification metadata | | `heliacal_setting_event(star_name, dt, latitude, longitude)` | `HeliacalEvent` | Full 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 | Method | Returns | Description | |---|---|---| | `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 the legacy five 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)` | `GeodeticChart` | Geodetic 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)` | `GalacticHouseCusps` | Galactic Porphyry house cusps for a time and observer location; `cusps_gal` gives native galactic membership, `cusps_ecl` provides ecliptic interoperability | ### Phenomena & occultations | Method | Returns | Description | |---|---|---| | `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 \| None` | Next 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)` | `OrbitalResonance` | Best-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 | Method | Returns | Description | |---|---|---| | `longevity(chart, houses)` | `HylegResult` | Traditional 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_cycle_result(latitude, longitude, year_start, year_end, arcus_visionis=10.0)` | `SothicRisingSeries` | Exhaustive found/not-found-within-window outcomes for every requested year | | `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)` | `EgyptianDate` | Egyptian civil calendar date for a datetime | ### Variable & multiple stars | Method | Returns | Description | |---|---|---| | `variable_star_phase(name, dt)` | `float` | Current variable-star phase at a datetime | | `variable_star_magnitude(name, dt)` | `float` | Estimated V magnitude at a datetime | | `variable_star_next_minimum(name, dt)` | `float \| None` | JD of the next primary minimum | | `variable_star_next_maximum(name, dt)` | `float \| None` | JD 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)` | `dict` | Separation, PA, resolvability, and brightness summary | | `multiple_star_components(name, dt)` | `dict` | Full component snapshot for a multiple star system | ### Void of Course Moon | Method | Returns | Description | |---|---|---| | `moon_void_of_course(dt, modern=False)` | `VoidOfCourseWindow` | Void-of-course window for the Moon's current sign | | `is_moon_void_of_course(dt, modern=False)` | `bool` | Whether the Moon is void of course at the given datetime | ### Electional search | Method | Returns | Description | |---|---|---| | `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: ```python from moira.facade import ElectionalPolicy, ElectionalWindow from moira.facade import find_electional_windows, find_electional_moments ``` | Function | Returns | Description | |---|---|---| | `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: ```python 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, ) ``` | Surface | Returns | Description | |---|---|---| | `Moira.ramesey_moon_condition_at(jd_ut, latitude, longitude, *, house_system, unavoidable_time_urgency=None, house_policy=None, policy=None)` | `RameseyMoonConditionEvaluation` | Reader-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)` | `RameseyMoonConditionEvaluation` | Low-level JD evaluator with explicit reader support | | `evaluate_ramesey_moon_condition(chart, *, ...)` | `RameseyMoonConditionEvaluation` | Evaluate the named profile from an explicitly compatible chart product | The same public ladder now includes: | Surface | Returns | Description | |---|---|---| | `Moira.sahl_moon_condition_at(...)` | `SahlMoonConditionEvaluation` | Single-moment Sahl section 22 Moon-condition evaluation | | `Moira.dorotheus_moon_condition_at(...)` | `DorotheusMoonConditionEvaluation` | Single-moment Dorotheus V.6 Moon-condition and remedy witness | | `Moira.dorotheus_rooted_context_at(...)` | `DorotheusRootedContextEvaluation` | V.6/V.31 root, outcome, matter-significator, and natal-context evidence | | `Moira.dorotheus_construction_at(...)` | `DorotheusConstructionEvaluation` | Complete V.2-V.7 construction matter profile without scoring or recommendation | | `Moira.dorotheus_matter_profile_at(..., profile_id=...)` | `DorotheusMatterProfileEvaluation` | Named 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(...)` | `WesternElectionalProfileScan` | Bounded 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 | Method | Returns | Description | |---|---|---| | `jd(year, month, day, hour=0.0)` | `float` | JD from a proleptic Gregorian calendar date | | `from_jd(jd)` | `datetime` | UTC datetime from a JD | | `calendar_from_jd(jd)` | `CalendarDateTime` | BCE-safe calendar breakdown from a JD | ### Eclipse | Method | Returns | Description | |---|---|---| | `eclipse(dt)` | `EclipseData` | Full eclipse geometry and classification for a datetime | | `solar_eclipse_footprint(jd_start, *, kind="any", backward=False, sample_count=181)` | `SolarEclipseVisibilityFootprint` | Complete zero-elevation WGS 84 mean-limb penumbral visibility boundary | | `solar_global_circumstances(jd_start, *, kind="any", backward=False)` | `SolarEclipseGlobalCircumstances` | Scale-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)` | `SolarEclipseCartography` | NumPy-free adaptive spherical maximum-visible magnitude and obscuration contours | | `lunar_global_circumstances(jd_start, *, kind="any", backward=False, mode="native")` | `LunarEclipseGlobalCircumstances` | Mode-pure geocentric contacts, parameters, body states, and durations | | `lunar_eclipse_visibility_map(jd_start, *, kind="any", backward=False, mode="native", sample_count=181)` | `LunarEclipseVisibilityMap` | Global contact-horizon limits for lunar-eclipse map rendering | --- ## 5. Ephemeris & Positions ### Planetary positions — low-level functions ```python 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. | Function | Returns | Description | |---|---|---| | `planet_at(body, jd_ut, reader=None, observer_lat=None, observer_lon=None, observer_elev_m=0.0)` | `PlanetData` | Single 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)` | `SkyPosition` | Apparent topocentric equatorial + horizontal coords | | `planet_reduction_breakdown_at(body, jd_ut, reader=None, ...)` | `PlanetReductionBreakdown` | Ordered visible longitude-reduction stages as `PlanetReductionStage` records | | `sun_longitude(jd_ut, reader=None)` | `float` | Sun ecliptic longitude only (faster than planet_at) | ### Lunar nodes | Function | Returns | Description | |---|---|---| | `true_node(jd_ut, reader=None)` | `NodeData` | True (osculating) lunar node; speed is the differentiated true-of-date longitude, not the mean-node rate | | `mean_node(jd_ut, *, nutation=True)` | `NodeData` | IERS 2003 mean lunar node; true equinox of date by default, raw mean equinox with `nutation=False` | | `mean_lilith(jd_ut, *, nutation=True)` | `NodeData` | IERS 2003 mean lunar apogee (F+Ω−l+180°); true equinox of date by default, raw mean equinox with `nutation=False` | ### Nodes & apsides bundle ```python from moira.facade import NodesAndApsides, nodes_and_apsides_at, next_moon_node_crossing ``` | Function | Returns | Description | |---|---|---| | `nodes_and_apsides_at(body, jd_ut)` | `NodesAndApsides` | Combined node/apsides vessel for the Moon or supported orbital bodies | | `next_moon_node_crossing(jd_start, reader=None, ascending=True)` | `float` | JD UT of the next ascending or descending lunar node crossing | ### Heliocentric positions ```python from moira.facade import heliocentric_planet_at, all_heliocentric_at, HeliocentricData ``` | Function | Returns | Description | |---|---|---| | `heliocentric_planet_at(body, jd_ut, reader=None)` | `HeliocentricData` | Heliocentric ecliptic longitude, latitude, distance | | `all_heliocentric_at(jd_ut, bodies=None, reader=None)` | `dict[str, HeliocentricData]` | All planets heliocentrically | ### Asteroids ```python from moira.facade import asteroid_at, all_asteroids_at, list_asteroids from moira.facade import load_asteroid_kernel # for non-DE441 bodies ``` | Function | Returns | Description | |---|---|---| | `asteroid_at(name_or_id, jd_ut, reader=None)` | `AsteroidData` | Single 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) | Function | Returns | Description | |---|---|---| | `planetary_node(planet, jd)` | `OrbitalNode` | Kernel-free Meeus/Simon mean node and apsides for an admitted planet | | `all_planetary_nodes(jd)` | `dict[str, OrbitalNode]` | Mean nodes and apsides for all eight admitted planets | | `geometric_node(body, jd_ut, reader=None)` | `OrbitalNode` | Strict-core instantaneous Sun-centered node and projected apsidal direction in the true ecliptic of date for a planet, Pluto, or loaded asteroid/comet; admitted only for JD(TT) 2415020.0--2488070.0 | ### Uranian planets (Hamburg School) ```python from moira.facade import uranian_at, all_uranian_at, list_uranian, UranianBody, UranianPosition ``` | Function | Returns | Description | |---|---|---| | `uranian_at(body, jd_ut, reader=None)` | `UranianPosition` | Single apparent geocentric hypothetical-body position; requires an explicit or active planetary reader | | `all_uranian_at(jd_ut, reader=None)` | `dict[str, UranianPosition]` | All current nine Hamburg School and Transpluto hypothetical-body positions | | `list_uranian()` | `list[str]` | Uranian body names (Cupido through Poseidon, plus Transpluto) | `UranianBody` constants: `CUPIDO HADES ZEUS KRONOS APOLLON ADMETOS VULKANUS POSEIDON TRANSPLUTO` `UranianPosition` reports apparent geocentric true-ecliptic-of-date `longitude`, `latitude`, `distance_au`, signed longitude `speed`, `retrograde`, sign fields, and separate `body_group` / `source_family` provenance. The hypothetical orbit comes from source elements; the bound DE kernel supplies only Earth/Sun observer geometry. ### Galactic coordinates ```python 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, CosmicReferenceKind, CosmicReferenceDefinition, CosmicReferencePosition, cosmic_reference_definition, cosmic_reference_at, all_cosmic_references_at, list_cosmic_references, ) ``` | Function | Returns | Description | |---|---|---| | `galactic_position_of(body, ecliptic_lon, ecliptic_lat, obliquity, jd_tt)` | `GalacticPosition` | Galactic 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]]` | Frozen five-entry compatibility table; its historical SGC entry is the astrological M87 convention | | `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 | | `cosmic_reference_definition(name)` | `CosmicReferenceDefinition` | Resolve a stable ID, canonical name, or exact alias without collapsing its semantic class | | `cosmic_reference_at(name, jd_tt)` | `CosmicReferencePosition` | Project one fixed ICRS/J2000 reference direction to the true ecliptic of date | | `all_cosmic_references_at(jd_tt, kind=None)` | `dict[str, CosmicReferencePosition]` | Return all 12 references, or the selected physical, landmark, or proxy class | | `list_cosmic_references(kind=None)` | `list[str]` | List canonical names, optionally filtered by `CosmicReferenceKind` | The 12-entry typed registry contains 2 physical objects, 7 coordinate landmarks, and 3 declared proxies. In particular, the formal supergalactic longitude origin is distinct from both M87 and the Virgo/M87 astrological SGC. The Great Attractor and Shapley entries disclose their Norma/ACO 3627 and ACO 3558 cluster anchors; no Local Group barycenter is admitted without a mass model. ### Gauquelin sectors ```python from moira.facade import ( GauquelinHorizonStatus, GauquelinPosition, gauquelin_sector, all_gauquelin_sectors, ) ``` | Function | Returns | Description | |---|---|---| | `gauquelin_sector(body_ra, body_dec, lat, lst, body="", horizon_altitude=0.0, sectors=36)` | `GauquelinPosition` | Gauquelin 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 ```python 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, ) ``` | Function | Signature | Description | |---|---|---| | `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=...) -> float` | Standard 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) -> float` | Equation of time in minutes at the TT epoch | | `angular_distance` | `(lon1, lat1, lon2, lat2) -> float` | Great-circle distance (degrees) | | `normalize_degrees` | `(d) -> float` | Map any angle to [0, 360) | ### Phase & apparent magnitude ```python 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 ```python 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 ```python from moira.facade import ( planet_relative_to, next_heliocentric_transit, PlanetPhenomena, planet_phenomena_at, OrbitalCenter, OrbitalFrame, OrbitShape, ApsidalDirection, ApsidalPassageStatus, OsculatingElements, ApsidalPassages, KeplerianElements, DistanceExtremes, osculating_elements, apsidal_passages, orbital_elements_at, distance_extremes_at, ) ``` | Function | Returns | Description | |---|---|---| | `planet_relative_to(body, center_body, jd_ut, reader=None)` | `PlanetData` | Body position relative to another physical center body | | `next_heliocentric_transit(body, target_lon, jd_start, reader=None, max_days=400.0)` | `float` | Next heliocentric longitude crossing of a target longitude | | `planet_phenomena_at(body, jd_ut)` | `PlanetPhenomena` | Instantaneous elongation/phase-style observational summary for one body | | `osculating_elements(body, jd_ut, *, center, frame, reader=None)` | `OsculatingElements` | Strict source-receipted conic at one UT1 epoch; SPK state is evaluated in TDB and frame models in TT | | `apsidal_passages(body, jd_ut, *, center, direction, max_days=None, reader=None)` | `ApsidalPassages` | Inclusive next/previous live distance extrema from TDB radial-velocity roots on one frozen source route | | `orbital_elements_at(body, jd_ut, reader)` | `KeplerianElements` | Planet-only compatibility adapter using Sun center and fixed J2000 ecliptic; `epoch_jd` is TT | | `distance_extremes_at(body, jd_ut, reader)` | `DistanceExtremes` | Planet-only `NEXT`/`SUN` adapter; both passage JD fields are TT | `osculating_elements` requires an explicit `OrbitalCenter` (`SUN` or `EARTH`) and `OrbitalFrame` (`J2000_ECLIPTIC`, `MEAN_ECLIPTIC_OF_DATE`, or `TRUE_ECLIPTIC_OF_DATE`). It admits planets and EMB about the Sun, the Moon about Earth, and installed sovereign asteroid/comet catalog members about the Sun. Its result includes body identity, UT1/TT/TDB epochs, shape-safe optional elements, undefined-field reasons, and complete state-source, gravity, frame, time, and singularity provenance. True-of-date is limited to TT JD 2415020.0–2488070.0 inclusive. `apsidal_passages` requires explicit `OrbitalCenter` and `ApsidalDirection` (`NEXT` or `PREVIOUS`). Each pericenter/apocenter outcome is `FOUND`, `BEYOND_COVERAGE`, or `NOT_IN_WINDOW`; absent outcomes do not carry invented event coordinates. A found event exposes TDB, TT, verified inverse UT1, distance, and a two-sided-root detail. Provenance freezes the algorithm, gravity/clock identity, route schedule and source usage, seam continuity, sampling constants, evaluation budget, and exact searched interval. The start and bounds are inclusive, but an event on a route edge still needs witnesses on both sides. An initially open osculating conic controls only the search scale and does not suppress live trajectory search. `OrbitalSearchError` (`ArithmeticError`) reports a failed bracket refinement or exhausted fixed evaluation budget with bracket, iteration/evaluation counts, tolerance, final residual, and algorithm version. The legacy complete-pair adapter raises `OrbitalPassageUnavailableError` (`ValueError`) with the full `ApsidalPassages` result and ordered missing kinds. Both remain members of the structured `OrbitalError` family. --- ## 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 ```python 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. | Symbol | Type | Description | |---|---|---| | `SSB_BODIES` | `frozenset[str]` | Bodies with well-defined barycentric state in DE441 | | `ssb_position_at(body, jd_ut)` | `SSBPosition` | SSB-relative position of one body | | `all_ssb_positions_at(jd_ut)` | `dict[str, SSBPosition]` | SSB-relative positions of all supported bodies | #### `SSBPosition` fields | Field | Type | Description | |---|---|---| | `name` | `str` | Body name | | `longitude` | `float` | Ecliptic longitude (°), [0°, 360°) | | `latitude` | `float` | Ecliptic latitude (°) | | `distance` | `float` | Distance from SSB (km) | | `speed` | `float` | Longitudinal speed (°/day) | | `retrograde` | `bool` | True when speed < 0 | | `sign` | `str` | Zodiac sign (derived) | | `sign_symbol` | `str` | Sign glyph (derived) | | `sign_degree` | `float` | Degree within sign (derived) | **Property:** `distance_au` → distance from SSB in Astronomical Units. ```python 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 ```python 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` | Symbol | Type | Description | |---|---|---| | `VALID_OBSERVER_BODIES` | `frozenset[str]` | Bodies that may serve as observer or target | | `planetocentric_at(observer, target, jd_ut)` | `PlanetocentricData` | Position of `target` as seen from `observer` | | `all_planetocentric_at(observer, jd_ut)` | `dict[str, PlanetocentricData]` | All visible bodies from the observer | #### `PlanetocentricData` fields | Field | Type | Description | |---|---|---| | `observer` | `str` | Observer body name | | `name` | `str` | Target body name | | `longitude` | `float` | Ecliptic longitude (°), [0°, 360°) | | `latitude` | `float` | Ecliptic latitude (°) | | `distance` | `float` | Observer–target distance (km) | | `speed` | `float` | Longitudinal speed (°/day) | | `retrograde` | `bool` | True when speed < 0 | | `sign` | `str` | Zodiac sign (derived) | | `sign_symbol` | `str` | Sign glyph (derived) | | `sign_degree` | `float` | Degree within sign (derived) | **Property:** `distance_au` → observer–target distance in Astronomical Units. ```python 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 ```python 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: | Body | Light time | Max 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° | | Symbol | Type | Description | |---|---|---| | `RECEIVED_LIGHT_BODIES` | `frozenset[str]` | Physical bodies for which light-cone is meaningful (excludes computed points) | | `received_light_at(body, jd_ut)` | `ReceivedLightPosition` | Received-light position for one body | | `all_received_light_at(jd_ut)` | `dict[str, ReceivedLightPosition]` | Received-light positions for all supported bodies | #### `ReceivedLightPosition` fields | Field | Type | Description | |---|---|---| | `name` | `str` | Body name | | `apparent_longitude` | `float` | Where body was when light emitted (°) — the standard astrological position | | `apparent_latitude` | `float` | Ecliptic latitude at emission instant (°) | | `geometric_longitude` | `float` | Where body physically is at birth moment (°) | | `geometric_latitude` | `float` | Ecliptic latitude at birth moment (°) | | `distance_km` | `float` | Earth–body distance at emission instant (km) | | `light_travel_days` | `float` | One-way light travel time (τ) in days | | `emission_jd` | `float` | Julian Date when photons were emitted (jd_ut − τ) | | `speed` | `float` | Apparent longitudinal speed (°/day) | | `retrograde` | `bool` | True when apparent speed < 0 | | `sign` | `str` | Sign of apparent longitude (derived) | | `sign_symbol` | `str` | Sign glyph (derived) | | `sign_degree` | `float` | Degree 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 ```python 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 ```python 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, ) ``` | Function | Returns | Description | |---|---|---| | `calculate_houses(jd_ut, latitude, longitude, system=HouseSystem.PLACIDUS, *, policy=None, ayanamsa_offset=None)` | `HouseCusps` | Compute house cusps and angles under explicit house policy when supplied | | `assign_house(longitude, cusps)` | `HousePlacement` | Find which house a longitude falls in | | `describe_boundary(longitude, cusps, orb=2.0)` | `HouseBoundaryProfile` | Proximity to house cusp boundaries | | `describe_angularity(longitude, cusps, orb=5.0)` | `HouseAngularity` | Angular/succedent/cadent classification | | `compare_systems(jd_ut, latitude, longitude, systems)` | `HouseSystemComparison` | Side-by-side comparison of multiple systems | | `compare_placements(body_lon, systems_cusps)` | `HousePlacementComparison` | How a body's house changes across systems | | `distribute_points(longitudes, cusps)` | `HouseDistributionProfile` | Count of points per house | | `classify_house_system(system)` | `HouseSystemClassification` | Family, 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 ```python 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 | Field | Type | Description | |---|---|---| | `body1` | `str` | First body name | | `body2` | `str` | Second body name | | `aspect` | `str` | Human name, e.g. "Conjunction", "Sextile" | | `symbol` | `str` | Glyph or short symbol for the aspect | | `angle` | `float` | Exact aspect angle in degrees, e.g. 0, 60, 90, 120, 180 | | `separation` | `float` | Actual angular separation between the bodies | | `orb` | `float` | Non-negative deviation from the exact aspect angle | | `allowed_orb` | `float` | Maximum allowed orb for this aspect | | `applying` | `bool \| None` | Applying/separating truth; `None` when speeds are unavailable | | `stationary` | `bool` | True if a stationary motion state affects the aspect | | `classification` | `AspectClassification` | Domain, family, tier, motion state, and strength metadata | | `direction` | `AspectDirection \| None` | Sinister/dexter casting direction from `body1`'s perspective when defined | | `hellenistic_superiority_truth` | `HellenisticSuperiorityTruth \| None` | Raw direction-applicability and whole-sign overcoming receipt for the ordered body pair | | `sign_degree1` | `int \| None` | Integer degree number within `body1`'s sign, used for strict partile truth | | `sign_degree2` | `int \| None` | Integer 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 | Property | Type | Description | |---|---|---| | `is_major` | `bool` | True when the aspect belongs to the major Ptolemaic tier | | `is_minor` | `bool` | True when the aspect is not in the major tier | | `is_zodiacal` | `bool` | True when the aspect domain is zodiacal | | `is_applying` | `bool` | True only when `applying is True` | | `is_separating` | `bool` | True only when `applying is False` | | `orb_surplus` | `float` | Remaining orb headroom: `allowed_orb - orb` | | `is_partile` | `bool` | True when a major Ptolemaic aspect has both bodies in the same degree number of their signs | | `is_platic` | `bool` | True when a major Ptolemaic aspect is admitted but not partile | #### Core aspect functions | Function | Returns | Description | |---|---|---| | `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)` | `LongitudeAspectAnalysis` | Validated 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)` | `DeclinationAspectAnalysis` | Validated 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)` | `DeclinationAspectMotionWitness` | Instantaneous signed declination error/rate witness for applying, exact, separating, stationary, or indeterminate truth | | `build_aspect_graph(aspects)` | `AspectGraph` | Graph structure of the aspect network | | `aspect_strength(aspect)` | `AspectStrength` | Geometric orb exactness, or categorical whole-sign exactness | | `aspect_motion_state(aspect)` | `MotionState` | APPLYING / EXACT / SEPARATING / STATIONARY / INDETERMINATE / NONE | | `aspect_harmonic_profile(longitudes, harmonic)` | `AspectHarmonicProfile` | Aspects 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. ### Planet-first stellium analysis (independent product) ```python from moira import analyze_stelliums, StelliumAnalysisPolicy, StelliumSelection evidence = analyze_stelliums( {"Sun": 120.0, "Moon": 122.0, "Mercury": 124.0, "Venus": 126.0, "North Node": 123.0}, policy=StelliumAnalysisPolicy(preset="strict", max_span_degrees=8.0), selection=StelliumSelection( core=("Sun", "Moon", "Mercury", "Venus"), associated=("North Node",)), ) ``` `moira.stellium.v1` independently returns sign, actual-house and tight total-arc matches. Strict requires four core planets; Broad three. Only the canonical Sun-through-Pluto ten qualify. Associated factors never count or expand the established core arc. In the example, sign/tight evidence is valid and house analysis is explicitly not evaluable because no house context was supplied. No new ephemeris calculation is performed. The Python surface includes `StelliumAnalysisPolicy`, `StelliumContext`, `StelliumSelection`, `StelliumHouseContext`, `StelliumArc`, `StelliumAssociation`, `StelliumMatch`, `StelliumGroup`, `StelliumEvaluation`, `StelliumCoverage`, `StelliumHouseReceipt`, `StelliumAnalysis`, `STELLIUM_SCHEMA_VERSION`, `STELLIUM_CORE` and `analyze_stelliums`. `Moira.analyze_stelliums` forwards the same keyword-only snapshot contract. REST equivalent: `POST /v1/stelliums/analyze`. See [the complete stellium contract](STELLIUM_ANALYSIS_STANDARD.md) for same-frame houses, partial coverage, associations, IDs and legacy migration. Existing centroid/clique stellium APIs below retain their old semantics. ### Legacy Aspect Patterns ```python 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 | Field | Type | Description | |---|---|---| | `name` | `str` | Pattern name, e.g. "T-Square", "Grand Trine", "Yod" | | `bodies` | `list[str]` | Bodies participating in the pattern | | `aspects` | `list[AspectData]` | Aspects forming the pattern | | `apex` | `str \| None` | Apex body (for Yods, T-Squares, etc.) | | `classification` | `PatternClassification` | Pattern classification metadata | | `detection_truth` | `PatternDetectionTruth` | Detection-trace metadata for the pattern | | `all_contributions` | `list[PatternAspectContribution]` | Full aspect/body contribution set | | `contributions` | `list[PatternAspectContribution]` | Primary contribution set used for display | | `condition_profile` | `PatternConditionProfile` | Consolidated 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) ```python from moira.facade import classify_chart_shape, ChartShape, ChartShapeType shape = classify_chart_shape(chart.longitudes(include_nodes=False)) # ChartShape(shape, occupied_arc, largest_gap, leading_planet, # handle_planet, clusters, handle_bodies) ``` `ChartShapeType` constants: `BUNDLE BOWL BUCKET LOCOMOTIVE SEESAW SPLAY SPLASH` Jones classification requires exactly the ten canonical bodies from Sun through Pluto; nodes, angles, asteroids, omissions, and additions are rejected. The deterministic detector follows Marc Edmund Jones's primary definitions in *Essentials of Astrological Analysis*: a Bucket is a one-nine pattern, Seesaw group boundaries use his functioning-sextile or greater-than-70-degree rule, and a Splay may have a singleton reins group. Jones also treats borderline classification as approximate, so the ordered detector precedence and final Splash tie-break are Moira engine policy, not claimed as source-supplied rigid bins. See [`PROVENANCE.md`](https://github.com/TheDaniel166/moira/blob/main/PROVENANCE.md#jones-chart-shape-doctrine) for the primary-source ledger and the source/policy boundary. ### Midpoints ```python from moira.facade import ( activated_midpoints, calculate_midpoints, dial_90_midpoints, midpoint_clusters, midpoint_tree, midpoint_weighting, midpoints_to_point, planetary_pictures, to_dial_90, ) lons = chart.longitudes(include_nodes=False) mps = calculate_midpoints(lons, planet_set="modern") hits = midpoints_to_point(15.0, lons, orb=1.5, planet_set="modern") dial_position = to_dial_90(135.0) # 45.0; 135° and 45° coincide dial_midpoints = dial_90_midpoints(lons, planet_set="modern") tree = midpoint_tree(15.0, lons, orb=1.5, planet_set="modern", dial=90) pictures = planetary_pictures(lons, orb=1.5, planet_set="modern", dial=90) weights = midpoint_weighting(lons, orb=1.5, planet_set="modern", dial=90) active = activated_midpoints(135.0, mps, orb=1.5, dial=90) clusters = midpoint_clusters(lons, cluster_orb=1.0, min_size=3, planet_set="modern", dial=90) ``` Dial coordinates retain the degree labels printed on the selected dial: `to_dial(longitude, harmonic) = longitude mod (360 / harmonic)`. This is not the same representation as a 360° harmonic chart, whose transformed longitude is `(longitude * harmonic) mod 360`. On a 90° dial, longitudes separated by 90° coincide while points 22.5° or 45° apart remain distinct. Orbs are measured directly in these dial-labelled degrees. The `extended` midpoint set admits the ten modern planets, `True Node`, `Mean Node`, the caller-facing `North Node` alias, Chiron, Asc, and MC when those positions are supplied. ### Harmonics ```python 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 ```python 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 ```python 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 ### Horary evidence profile Track B admits one bounded, non-interpretive Lilly 1647 evidence surface. The high-level adapter owns the strict house calculation, traditional-seven positions, planetary-hour receipt, finite consideration inputs, and optional Lilly perfection search on one explicit reader. ```python from moira import ( HoraryEvidenceProfile, HoraryQuestionTimeBasis, HorarySourceCalendar, LILLY_1647_HORARY_V1, horary_evidence_at, ) ``` | Surface | Returns | Contract | |---|---|---| | `horary_evidence_at(question, *, house_policy, perfection_jd_end=None, reader)` | `HoraryEvidenceProfile` | Low-level reader-explicit composition from an evaluated `HoraryQuestionReceipt` and exact `HoraryHousePolicy` | | `Moira.horary_evidence_at(question, *, house_policy, perfection_jd_end=None)` | `HoraryEvidenceProfile` | Same composition bound to the facade's reader | | `POST /v1/horary/evidence-profile` | typed Horary profile response | Strict transport for an aware question instant, explicit location, house system, perspective path, topic house, and optional perfection end | `perfection_jd_end` is optional. When present, it is a UT1 endpoint constrained by Moira's explicit 31-day computational safety receipt; that cap is not historical doctrine. Missing or incompatible evidence remains typed `not_evaluable`. The profile does not infer the question topic and emits no yes/no answer, confidence, outcome, timing prose, advice, or score. Detailed question, geometry, hour, consideration, and perfection receipts remain module-owned under `moira.horary`. ### Neutral Mundane event-chart profile Track B admits a neutral event-and-local-chart composition layer for four separate event families: cardinal ingresses, the strictly preceding primary syzygy, solar/lunar eclipses with explicitly named epochs, and exact Jupiter-Saturn ecliptic-longitude conjunction roots. ```python from moira import ( MundaneEvaluationStatus, MundaneEventChartProfile, MundaneEventType, MundaneProfileProvenance, compose_mundane_event_chart_profile, eclipse_receipt_from_event, jupiter_saturn_sequence_from_series, ) ``` | Surface | Returns | Contract | |---|---|---| | `compose_mundane_event_chart_profile(...)` | `MundaneEventChartProfile` | Pure composition of one tagged anchor, typed ingress/syzygy evidence, and strict local-projection evidence | | `eclipse_receipt_from_event(event, *, eclipse_id, reader)` | `EclipseEventReceipt` | Reader-explicit adapter that keeps greatest eclipse, conjunctions, and contacts distinct | | `jupiter_saturn_sequence_from_series(series, *, reader)` | `JupiterSaturnConjunctionSequenceReceipt` | Reader-explicit adapter that recomputes the complete interval and retains every root | | `POST /v1/mundane/event-chart-profile` | typed Mundane profile envelope | Strict four-way discriminated transport that also retains the complete event-selection context | The reader-bound facade methods are listed in the Timing section above. Global event truth and local projection have separate states: missing, invalid, or out-of-validity location evidence does not rewrite an evaluated global event. Coordinates, location role/source/validity, house system, ingress policy, eclipse chart epoch, and Jupiter-Saturn root selection are explicit. No capital is inferred, triple conjunctions are not collapsed, and the contract emits no political, economic, conflict, disaster, weather, national-fate, judgement, score, outcome, or advice surface. Concrete event, clock, search, and location receipts remain module-owned under `moira.mundane`. ### Dignities ```python 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 | Function | Returns | Description | |---|---|---| | `is_day_chart(sun_lon, asc_lon)` | `bool` | True 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)` | `bool` | True if planet is in hayz | | `is_in_sect(planet, sun_lon, asc_lon)` | `bool` | True if planet is in its preferred sect | | `almuten_figuris(chart_lons, cusps, is_day)` | `str` | Almuten 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 | Field | Type | Description | |---|---|---| | `planet` | `str` | Planet name | | `sign` | `str` | Sign occupied by the planet | | `degree` | `float` | Degree within the sign | | `house` | `int` | House placement | | `essential_dignity` | `EssentialDignityKind` | Primary essential dignity/debility | | `essential_score` | `int` | Essential dignity score | | `accidental_dignities` | `list[AccidentalDignityCondition]` | Active accidental dignity conditions | | `accidental_score` | `int` | Accidental dignity score | | `total_score` | `int` | Combined dignity score | | `is_retrograde` | `bool` | Retrograde flag | | `receptions` | `list[PlanetaryReception]` | Active receptions involving the planet | | `condition_profile` | `PlanetaryConditionProfile` | Consolidated dignity/condition profile | | `essential_truth` | `EssentialDignityTruth` | Essential dignity computation truth data | | `accidental_truth` | `AccidentalDignityTruth` | Accidental dignity truth data | | `sect_truth` | `SectTruth` | Sect evaluation truth data | | `solar_truth` | `SolarConditionTruth` | Solar condition truth data | | `all_receptions` | `list[PlanetaryReception]` | Full reception set prior to filtering | | `mutual_reception_truth` | `MutualReceptionTruth` | Mutual reception truth data | | `essential_classification` | `EssentialDignityClassification` | Essential dignity classification metadata | | `accidental_classification` | `AccidentalDignityClassification` | Accidental dignity classification metadata | | `sect_classification` | `SectClassification` | Sect classification metadata | | `solar_classification` | `SolarConditionClassification` | Solar condition classification metadata | | `reception_classification` | `ReceptionClassification` | Reception classification metadata | #### Condition profiles & networks ```python 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`. ```python 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. ```python 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",)}, ) ``` | Surface | Returns | Description | |---|---|---| | `natal_astrodynes(body_inputs, cusp_signs, *, intercepted_signs_by_house=None, policy=None)` | `AstrodyneChartResult` | Full 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)` | `AstrodyneChartResult` | Derive body inputs, houses, interpolation geometry, cusp signs, and interceptions from a complete explicit tropical figure | | `astrodynes_summary(aggregate)` | `AstrodyneSummaryProfile` | Society, trinity, element, and quality partitions of an existing chart aggregate | | `validate_astrodynes_output(result)` | `tuple[str, ...]` | Deterministic cross-layer invariant failures | | `Moira.astrodynes(...)` | `AstrodyneChartResult` | Kernel-free facade delegate | | `Moira.astrodynes_from_geometry(...)` | `AstrodyneChartResult` | Kernel-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: | Type | Meaning | |---|---| | `AstrodyneRelationSet` | Detected, admitted, and scored zodiacal, parallel, and mutual-reception relations | | `AstrodyneBodyConditionProfile` | Integrated power, harmony, discord, dignity, and named contributions for one body | | `AstrodyneSignAggregate` | Average-ruler share plus occupants for one sign | | `AstrodyneHouseAggregate` | Cusp/interception ruler shares plus house occupants | | `AstrodyneChartAggregate` | Twelve signs, twelve houses, and sign/house checksums | | `AstrodyneSummaryEntry` | One named Class 5 group with members, power, percentage, and harmony/discord truth | | `AstrodyneSummaryProfile` | Society, trinity, element, and quality families over one chart total | | `AstrodyneNetwork` | Nodes aligned with body profiles and edges aligned with admitted relations | | `AstrodyneChartResult` | Policy, 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](ASTRODYNES_BACKEND_STANDARD.md) for the full doctrine, constitutional layers, invariants, and validation boundary. ### Arabic Parts / Lots ```python 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, ) ``` | Function | Returns | Description | |---|---|---| | `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, ...)` | `LotsEvaluation` | Computed 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 | Field | Type | Description | |---|---|---| | `name` | `str` | Part name, e.g. "Fortune", "Spirit" | | `longitude` | `float` | Ecliptic longitude (degrees) | | `formula` | `str` | Formula used to derive the part | | `category` | `str` | Part category/classification label | | `description` | `str` | Short textual description | | `computation_truth` | `ArabicPartComputationTruth` | Truth data for the part computation | | `classification` | `ArabicPartClassification` | Classification metadata | | `all_dependencies` | `list[LotDependency]` | Full dependency graph slice for the part | | `dependencies` | `list[LotDependency]` | Direct dependencies used by the part | | `dependency_completeness` | `LotDependencyCompletenessTruth` | Separate dependency-resolution receipt | | `astrological_condition_truth` | `LotAstrologicalConditionTruth` | Separate condition boundary; currently `not_evaluable` without admitted doctrine | | `condition_profile` | `LotConditionProfile` | Computed condition profile | | `sign` | `str` | Sign occupied by the part | | `sign_symbol` | `str` | Sign glyph/symbol | | `sign_degree` | `float` | Degree within the sign | #### Using `ArabicPartsService` ```python 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 ```python 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 ``` | Function | Returns | Description | |---|---|---| | `annual_profection(natal_asc, age_years, natal_positions=None, activation_orb=5.0)` | `ProfectionResult` | Whole-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=...)` | `ProfectionChronology` | Exact 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)` | `ProfectionResult` | Civil-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) ```python 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) ```python 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) ```python 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) ```python 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) ```python 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 ```python 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) ``` | Function | Returns | Description | |---|---|---| | `chaldean_face(longitude)` | `DecanatePosition` | Classical Chaldean face for a tropical longitude | | `triplicity_decan(longitude)` | `DecanatePosition` | Western triplicity decan for a tropical longitude | | `vedic_drekkana(longitude, jd, ayanamsa_system=Ayanamsa.LAHIRI)` | `DecanatePosition` | Vedic D3 drekkana for a sidereally normalized longitude | #### `DecanatePosition` fields | Field | Type | Description | |---|---|---| | `system` | `str` | Decan system name: `chaldean_face`, `triplicity`, or `vedic_drekkana` | | `decan_number` | `int` | Decan number within the sign, 1-3 | | `ruling_planet` | `str` | Planetary ruler of the decan | | `ruling_sign` | `str \| None` | Governing sign for triplicity or Vedic drekkana; `None` for Chaldean face | | `sign` | `str` | Zodiac sign containing the longitude used | | `sign_symbol` | `str` | Zodiac sign glyph/symbol | | `degree_in_decan` | `float` | Degrees elapsed within the 10° decan span | | `longitude_used` | `float` | Longitude 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 ```python 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 | Field | Type | Description | |---|---|---| | `body` | `str` | Transiting body | | `longitude` | `float` | Exact longitude of the event | | `jd_ut` | `float` | JD UT of the exact transit | | `direction` | `str` | Search direction / crossing direction | | `computation_truth` | `TransitComputationTruth` | Search/computation truth data | | `classification` | `TransitComputationClassification` | Transit classification metadata | | `relation` | `TransitRelation` | Target relation metadata | | `condition_profile` | `TransitConditionProfile` | Transit condition profile | #### `IngressEvent` fields | Field | Type | Description | |---|---|---| | `body` | `str` | Body entering the sign | | `sign` | `str` | Sign entered | | `jd_ut` | `float` | JD UT of the ingress | | `direction` | `str` | Ingress direction | | `computation_truth` | `IngressComputationTruth` | Search/computation truth data | | `classification` | `IngressComputationClassification` | Ingress classification metadata | | `relation` | `TransitRelation` | Sign-ingress relation metadata | | `condition_profile` | `TransitConditionProfile` | Ingress condition profile | #### Core functions ```python 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) ``` #### Natal aspect grid ```python from moira.facade import find_aspect_transits, find_aspect_transits_to_longitudes # One mover, one target (body name or frozen longitude), one aspect. hits = find_aspect_transits(Body.SATURN, natal_moon_lon, 90.0, 1.0, jd_start, jd_end, reader=reader) # One mover against many frozen longitudes and aspects from a single native # longitude scan; specs are (longitude_deg, aspect_angle_deg, orb_deg). grid = find_aspect_transits_to_longitudes( Body.SATURN, [(natal_sun_lon, 0.0, 1.0), (natal_sun_lon, 90.0, 1.0), (natal_moon_lon, 180.0, 1.0)], jd_start, jd_end, reader=reader, ) ``` Both return `AspectTransitEvent` objects (`body`, `target`, `angle`, `orb`, `jd_exact`, `jd_entering`, `jd_leaving`, `is_retrograde_hit`, `search_motion`) ordered by `jd_exact`. `Moira.natal_aspect_transits(body, natal_longitudes, aspect_angles, jd_start, jd_end, aspect_orbs=None)` builds the same specs as the full product of longitudes and angles. `NATAL_ASPECT_MOVERS` lists every admitted mover. Planets and asteroids are scanned natively; nodes and Liliths are sampled through the transit resolver at the engine's step for that body. ### Stations & Retrograde ```python 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` ```python 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, ) ``` | Technique | Function | Key rate | |---|---|---| | Secondary Progression | `secondary_progression` | 1 day = 1 year | | Solar Arc | `solar_arc` | Sun's progressed daily motion applied to all bodies | | Solar Arc (RA) | `solar_arc_right_ascension` | Solar Arc in right ascension | | Naibod (longitude) | `naibod_longitude` | 0°59′08″/year | | Naibod (RA) | `naibod_right_ascension` | Naibod in right ascension | | Tertiary | `tertiary_progression` | 1 day = 1 lunar month | | Tertiary II | `tertiary_ii_progression` | Klaus Wessel variant | | Minor | `minor_progression` | 1 lunar month = 1 year | | Ascendant Arc | `ascendant_arc` | ASC arc applied to all bodies | All converse variants (moving backward) are prefixed with `converse_`. #### `ProgressedChart` fields | Field | Type | Description | |---|---|---| | `chart_type` | `str` | Progression technique identifier | | `natal_jd_ut` | `float` | Natal JD UT | | `progressed_jd_ut` | `float` | Progressed JD UT used for the positions | | `target_date` | `datetime` | Target date requested by the user | | `solar_arc_deg` | `float` | Solar arc applied when relevant | | `positions` | `dict[str, ProgressedPosition]` | Body → progressed position | | `computation_truth` | `ProgressionComputationTruth` | Progression computation truth data | | `classification` | `ProgressionComputationClassification` | Progression classification metadata | | `relation` | `ProgressionRelation` | Relation metadata for natal/progressed comparison | | `condition_profile` | `ProgressionConditionProfile` | Consolidated progression profile | `ProgressedPosition`: `longitude`, `latitude`, `speed`, `natal_longitude`. ### Primary Directions ```python 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) ```python 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, ) ``` | Function | Returns | Description | |---|---|---| | `firdaria(jd_natal, is_day, policy=None)` | `list[FirdarPeriod]` | Full Firdaria sequence from birth | | `current_firdaria(jd_natal, jd_now, is_day, policy=None)` | `FirdarPeriod` | Active Firdaria period at jd_now | | `group_firdaria(periods)` | `list[FirdarMajorGroup]` | Periods grouped by major lord | | `firdar_condition_profile(period, chart_lons)` | `FirdarConditionProfile` | Condition analysis for one period | | `firdar_sequence_profile(jd_natal, is_day, jd_now)` | `FirdarSequenceProfile` | Full condition profile across sequence | | `firdar_active_pair(jd_natal, jd_now, is_day)` | `FirdarActivePair` | Major + 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. | Field | Type | Description | |---|---|---| | `level` | `int` | Period level | | `planet` | `str` | Active period lord | | `start_jd` | `float` | Start JD | | `end_jd` | `float` | End JD | | `years` | `float` | Duration in years | | `major_planet` | `str` | Parent major lord | | `is_day_chart` | `bool` | True for diurnal sect sequence | | `variant` | `str` | Variant used for the sequence | | `sequence_kind` | `FirdarSequenceKind` | Sequence family metadata | | `is_node_period` | `bool` | Whether the period belongs to the nodal sequence | ### Decennials ```python 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, ) ``` | Function | Returns | Description | |---|---|---| | `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 \| None` | Active 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 ```python 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, ) ``` | Function | Returns | Description | |---|---|---| | `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)` | `ZRLevelPair` | Active 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 | Field | Type | Description | |---|---|---| | `level` | `int` | Period level (1-4) | | `sign` | `str` | Releasing sign | | `ruler` | `str` | Sign ruler | | `start_jd` | `float` | Start JD | | `end_jd` | `float` | End JD | | `years` | `float` | Period length in years | | `lot_name` | `str` | Lot used for the releasing sequence | | `is_loosing_of_bond` | `bool` | Whether the period begins with a Loosing of the Bond | | `is_peak_period` | `bool` | True only in places 1, 4, 7, or 10 from Fortune | | `angularity_from_fortune` | `int \| None` | Inclusive place 1–12 from Fortune; `None` when Fortune is omitted | | `use_loosing_of_bond` | `bool` | Whether Loosing of the Bond is enabled | | `angularity_class` | `ZRAngularityClass \| None` | Angular / succedent / cadent class; `None` only when Fortune is omitted | | `fortune_angularity_truth` | `ZRFortuneAngularityTruth \| None` | Raw 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 ```python 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, ) ``` | Function | Returns | Description | |---|---|---| | `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)` | `DashaActiveLine` | Named active chain from the list returned by `current_dasha` | | `dasha_condition_profile(period)` | `DashaConditionProfile` | Condition profile for one generated period | | `dasha_sequence_profile(periods)` | `DashaSequenceProfile` | Aggregate profile for a generated sequence | | `dasha_lord_pair(line)` | `DashaLordPair` | Mahadasha + Antardasha lords from a `DashaActiveLine` | #### `DashaPeriod` fields | Field | Type | Description | |---|---|---| | `level` | `int` | 1 = Mahadasha, 2 = Antardasha, 3 = Pratyantardasha | | `planet` | `str` | Dasha lord (planet name) | | `start_jd` | `float` | Start JD | | `end_jd` | `float` | End JD | | `year_days` | `float` | Duration expressed in days/year-basis units | | `sub` | `list[DashaPeriod]` | Nested sub-periods (if levels > 1) | | `year_basis` | `str` | Year basis used for the sequence | | `birth_nakshatra` | `str` | Natal Moon nakshatra | | `nakshatra_fraction` | `float` | Fraction of nakshatra elapsed at birth | | `lord_type` | `DashaLordType` | Lord classification metadata | `VIMSHOTTARI_YEARS`: dict of lord → years (Ketu=7, Venus=20, Sun=6, ...). --- ### Ashtottari & Yogini Dasha ```python from moira import ( ashtottari, yogini_dasha, AlternateDashaPeriod, AlternatePeriodProfile, AlternateDashaSequenceProfile, AshtottariPolicy, YoginiPolicy, validate_alternate_dasha_output, ) ``` | Function | Returns | Description | |---|---|---| | `ashtottari(moon_tropical_lon, natal_jd, levels=2, policy=None)` | `list[AlternateDashaPeriod]` | BPHS 108-year sequence using the Ardra-first 28-place allocation and three/four-place balance | | `yogini_dasha(moon_tropical_lon, natal_jd, levels=2, policy=None)` | `list[AlternateDashaPeriod]` | BPHS 36-year sequence using the add-three/remainder-eight entry rule | | `alternate_period_profile(period)` | `AlternatePeriodProfile` | Inspect one alternate-dasha period | | `alternate_sequence_profile(periods)` | `AlternateDashaSequenceProfile` | Aggregate profile for an alternate-dasha sequence | | `validate_alternate_dasha_output(periods)` | `None` | Validate alternate-dasha output structure | #### `AlternateDashaPeriod` fields | Field | Type | Description | |---|---|---| | `system` | `str` | Dasha system name | | `level` | `int` | Period level within the nested sequence | | `lord` | `str` | Period lord | | `start_jd` | `float` | Start JD | | `end_jd` | `float` | End JD | | `sub` | `list[AlternateDashaPeriod]` | Nested sub-periods | #### `AlternatePeriodProfile` fields | Field | Type | Description | |---|---|---| | `system` | `str` | Dasha system name | | `level` | `int` | Period level | | `lord` | `str` | Period lord name | | `planet` | `str` | Normalized planetary identity of the lord | | `years` | `float` | Nominal period length in years | | `is_node_lord` | `bool` | True when the lord is a node | | `is_luminary_lord` | `bool` | True when the lord is Sun or Moon | #### `AlternateDashaSequenceProfile` fields | Field | Type | Description | |---|---|---| | `system` | `str` | Dasha system name | | `total_years` | `int` | Total sequence span in years | | `mahadasha_count` | `int` | Number of top-level periods | | `profiles` | `list[AlternatePeriodProfile]` | Profile for each Mahadasha lord | #### Alternate Dasha policy & constants | Public symbol | Kind | Description | |---|---|---| | `AshtottariPolicy` | dataclass | `year_basis`, `ayanamsa_system`, `bypass_eligibility`, `lagna_sign_index` | | `YoginiPolicy` | dataclass | `year_basis`, `ayanamsa_system` | | `ASHTOTTARI_YEARS` | `dict[str, int]` | Ashtottari lord → years table | | `ASHTOTTARI_SEQUENCE` | `list[str]` | Ashtottari lord order | | `ASHTOTTARI_NAKSHATRA_LORD` | `list[str]` | 27-entry ordinary-nakshatra projection of the BPHS lord mapping; `ashtottari()` separately accounts for Abhijit | | `ASHTOTTARI_TOTAL` | `int` | Total Ashtottari cycle years | | `YOGINI_YEARS` | `dict[str, int]` | Yogini lord → years table | | `YOGINI_SEQUENCE` | `list[str]` | Yogini lord order | | `YOGINI_PLANETS` | `dict[str, str]` | Yogini name → planetary identity mapping | | `YOGINI_TOTAL` | `int` | Total Yogini cycle years | ### Panchanga ```python from moira import ( panchanga_at, tithi_condition_profile, panchanga_profile, PanchangaResult, TithiConditionProfile, PanchangaProfile, PanchangaPolicy, TithiPaksha, YogaClass, KaranaType, VaraLordType, validate_panchanga_output, ) ``` | Function | Returns | Description | |---|---|---| | `panchanga_at(sun_tropical_lon, moon_tropical_lon, jd, ayanamsa_system=Ayanamsa.LAHIRI, policy=None)` | `PanchangaResult` | Compute tithi, nakshatra, yoga, karana, and vara for a moment | | `tithi_condition_profile(result)` | `TithiConditionProfile` | Tithi waxing/waning and condition profile | | `panchanga_profile(result)` | `PanchangaProfile` | Aggregate Panchanga condition summary | | `validate_panchanga_output(result)` | `None` | Validate Panchanga result invariants | `Moira.lunar_month_at(jd_ut1, *, policy=None)` binds the engine reader. `LunarMonthPolicy` selects the month system, ayanamsa and solver tolerance. Exceptional Purnimanta mappings remain explicitly unsupported. See the [lunar-month and festival policy surface](LUNAR_MONTH_AND_FESTIVAL_POLICY.md) for all eight exports, the REST contract, sources and remaining admission gates. #### `PanchangaResult` fields | Field | Type | Description | |---|---|---| | `jd` | `float` | Julian day of the computation moment | | `tithi` | `PanchangaElement` | Tithi element | | `vara` | `PanchangaElement` | Weekday element | | `vara_lord` | `str` | Planetary lord of the weekday | | `nakshatra` | `object` | Nakshatra result for the Moon | | `yoga` | `PanchangaElement` | Yoga element | | `karana` | `PanchangaElement` | Karana element | | `ayanamsa_system` | `str` | Governing ayanamsa system | #### `TithiConditionProfile` fields | Field | Type | Description | |---|---|---| | `tithi_name` | `str` | Tithi name | | `tithi_index` | `int` | Zero-based tithi index | | `tithi_number` | `int` | Traditional tithi number | | `paksha` | `str` | Waxing or waning half | | `is_purnima` | `bool` | True at Full Moon tithi | | `is_amavasya` | `bool` | True at New Moon tithi | | `degrees_elapsed` | `float` | Degrees elapsed within the tithi | | `degrees_remaining` | `float` | Degrees remaining in the tithi | #### `PanchangaProfile` fields | Field | Type | Description | |---|---|---| | `jd` | `float` | Julian day of the computation moment | | `paksha` | `str` | Waxing or waning half | | `is_purnima` | `bool` | Full Moon flag | | `is_amavasya` | `bool` | New Moon flag | | `yoga_class` | `str` | Classified yoga family | | `karana_type` | `str` | Classified karana family | | `vara_lord` | `str` | Weekday lord | | `vara_lord_type` | `str` | Benefic/malefic or related lord classification | | `ayanamsa_system` | `str` | Governing ayanamsa system | #### `PanchangaElement` fields | Field | Type | Description | |---|---|---| | `name` | `str` | Element name | | `index` | `int` | Zero-based element index | | `number` | `int` | Traditional 1-based element number | | `degrees_elapsed` | `float` | Degrees elapsed within the element | | `degrees_remaining` | `float` | Degrees remaining in the element | #### Panchanga policy & constants | Public symbol | Kind | Description | |---|---|---| | `PanchangaPolicy` | dataclass | `ayanamsa_system` | | `TithiPaksha` | enum-like class | `SHUKLA`, `KRISHNA` | | `YogaClass` | enum-like class | `AUSPICIOUS`, `INAUSPICIOUS` | | `KaranaType` | enum-like class | `MOVABLE`, `FIXED` | | `VaraLordType` | enum-like class | `LUMINARY`, `INNER`, `OUTER` | | `TITHI_NAMES` | `tuple[str, ...]` | Traditional tithi names | | `YOGA_NAMES` | `tuple[str, ...]` | Traditional yoga names | | `KARANA_NAMES` | `tuple[str, ...]` | Traditional karana names | | `VARA_LORDS` | `tuple[str, ...]` | Weekday lord sequence | | `VARA_NAMES` | `tuple[str, ...]` | Weekday names | ### Jaimini Karakas ```python from moira import ( jaimini_karakas, atmakaraka, karaka_condition_profile, jaimini_chart_profile, karaka_pair, JaiminiKarakaResult, KarakaConditionProfile, JaiminiChartProfile, KarakaPair, JaiminiPolicy, validate_jaimini_output, ) ``` | Function | Returns | Description | |---|---|---| | `jaimini_karakas(sidereal_longitudes, scheme=7, policy=None)` | `JaiminiKarakaResult` | Assign Jaimini karaka roles from sidereal longitudes | | `atmakaraka(sidereal_longitudes, scheme=7)` | `str` | Planet holding the Atmakaraka role | | `karaka_condition_profile(assignment, scheme)` | `KarakaConditionProfile` | Inspect one karaka assignment in context | | `jaimini_chart_profile(result)` | `JaiminiChartProfile` | Aggregate Jaimini karaka chart profile | | `karaka_pair(result, role_a, role_b)` | `KarakaPair` | Compare two named karaka roles | | `validate_jaimini_output(result)` | `None` | Validate Jaimini assignment output | #### `JaiminiKarakaResult` fields | Field | Type | Description | |---|---|---| | `assignments` | `list[KarakaAssignment]` | Ordered karaka assignments | | `scheme` | `int` | 7- or 8-karaka assignment scheme | | `atmakaraka` | `str` | Planet holding the Atmakaraka role | | `tie_warnings` | `list[tuple[str, str]]` | Tie diagnostics emitted during assignment | #### `KarakaConditionProfile` fields | Field | Type | Description | |---|---|---| | `karaka_name` | `str` | Karaka role name | | `karaka_rank` | `int` | Positional rank in the assignment order | | `planet` | `str` | Assigned planet | | `planet_type` | `str` | Planet or node type classification | | `degree_in_sign` | `float` | Degrees traversed within the sign | | `sidereal_longitude` | `float` | Full sidereal longitude | | `is_rahu_inverted` | `bool` | True when Rahu inversion governs the assignment | | `is_atmakaraka` | `bool` | True for the Atmakaraka | | `is_darakaraka` | `bool` | True for the Darakaraka | #### `JaiminiChartProfile` fields | Field | Type | Description | |---|---|---| | `scheme` | `int` | 7- or 8-karaka assignment scheme | | `atmakaraka_planet` | `str` | Atmakaraka planet | | `darakaraka_planet` | `str` | Darakaraka planet | | `has_node_atmakaraka` | `bool` | True when a node becomes Atmakaraka | | `has_node_darakaraka` | `bool` | True when a node becomes Darakaraka | | `has_ties` | `bool` | True when assignment ties were detected | | `tie_count` | `int` | Number of tie warnings | | `profiles` | `list[KarakaConditionProfile]` | Per-role diagnostic profiles | #### `KarakaPair` fields | Field | Type | Description | |---|---|---| | `role_a` | `str` | First requested role | | `role_b` | `str` | Second requested role | | `planet_a` | `str` | Planet assigned to the first role | | `planet_b` | `str` | Planet assigned to the second role | | `type_a` | `str` | Type classification for the first role holder | | `type_b` | `str` | Type classification for the second role holder | | `involves_node` | `bool` | True when either role holder is a node | | `both_are_nodes` | `bool` | True when both role holders are nodes | #### `KarakaAssignment` fields | Field | Type | Description | |---|---|---| | `karaka_name` | `str` | Assigned karaka role | | `karaka_rank` | `int` | Role rank in the ordered assignment | | `planet` | `str` | Assigned planet | | `degree_in_sign` | `float` | Degrees traversed within the sign | | `sidereal_longitude` | `float` | Full sidereal longitude | | `is_rahu_inverted` | `bool` | True when Rahu inversion governs the assignment | #### Jaimini policy & enums | Public symbol | Kind | Description | |---|---|---| | `JaiminiPolicy` | dataclass | `scheme`, `ayanamsa_system` | | `KarakaRole` | enum-like class | Atmakaraka through Darakaraka role constants | | `KarakaPlanetType` | enum-like class | `LUMINARY`, `INNER`, `OUTER`, `NODE` | | `KARAKA_NAMES_7` | `tuple[str, ...]` | Canonical 7-karaka role sequence | | `KARAKA_NAMES_8` | `tuple[str, ...]` | Canonical 8-karaka role sequence | ### Vedic Dignities ```python from moira import ( vedic_dignity, planetary_relationships, dignity_condition_profile, chart_dignity_profile, VedicDignityResult, PlanetaryRelationship, DignityConditionProfile, ChartDignityProfile, VedicDignityPolicy, validate_dignity_output, ) ``` | Function | Returns | Description | |---|---|---| | `vedic_dignity(planet, sidereal_longitude)` | `VedicDignityResult` | Compute exaltation, debilitation, own-sign, and Mulatrikona condition | | `planetary_relationships(sidereal_longitudes)` | `list[PlanetaryRelationship]` | Natural/compound relationship diagnostics across a chart | | `dignity_condition_profile(result)` | `DignityConditionProfile` | Inspect one Vedic dignity result | | `chart_dignity_profile(dignity_results)` | `ChartDignityProfile` | Aggregate chart-level dignity profile | | `validate_dignity_output(result)` | `None` | Validate Vedic dignity output invariants | #### `VedicDignityResult` fields | Field | Type | Description | |---|---|---| | `planet` | `str` | Planet name | | `sidereal_longitude` | `float` | Sidereal longitude used for evaluation | | `sign_index` | `int` | Zero-based sidereal sign index | | `sign` | `str` | Sidereal sign name | | `dignity_rank` | `str` | Resulting dignity rank | | `is_exalted` | `bool` | Exaltation flag | | `is_debilitated` | `bool` | Debilitation flag | | `is_mulatrikona` | `bool` | Mulatrikona flag | | `is_own_sign` | `bool` | Own-sign flag | | `exaltation_score` | `float` | Continuous exaltation-strength score | #### `PlanetaryRelationship` fields | Field | Type | Description | |---|---|---| | `from_planet` | `str` | Source planet | | `to_planet` | `str` | Target planet | | `natural` | `str` | Natural relationship | | `temporary` | `str` | Temporary relationship | | `compound` | `str` | Combined relationship result | #### `DignityConditionProfile` fields | Field | Type | Description | |---|---|---| | `planet` | `str` | Planet name | | `dignity_rank` | `str` | Dignity rank | | `tier` | `str` | Interpreted dignity tier | | `exaltation_score` | `float` | Exaltation-strength score | | `sign_index` | `int` | Sign index | | `sign` | `str` | Sign name | #### `ChartDignityProfile` fields | Field | Type | Description | |---|---|---| | `strong_count` | `int` | Count of strong dignities | | `neutral_count` | `int` | Count of neutral dignities | | `weak_count` | `int` | Count of weak dignities | | `strongest_planet` | `str` | Strongest planet by dignity tier | | `weakest_planet` | `str` | Weakest planet by dignity tier | | `planet_tiers` | `dict[str, str]` | Planet-to-tier mapping | | `exaltation_scores` | `dict[str, float]` | Planet-to-exaltation-score mapping | #### Vedic dignity policy, enums, and constants | Public symbol | Kind | Description | |---|---|---| | `VedicDignityPolicy` | dataclass | `ayanamsa_system` | | `VedicDignityRank` | enum-like class | `EXALTATION`, `MULATRIKONA`, `OWN_SIGN`, `FRIEND_SIGN`, `NEUTRAL_SIGN`, `ENEMY_SIGN`, `DEBILITATION` | | `CompoundRelationship` | enum-like class | `GREAT_FRIEND`, `FRIEND`, `NEUTRAL`, `ENEMY`, `GREAT_ENEMY` | | `DignityTier` | enum-like class | `STRONG`, `NEUTRAL`, `WEAK` | | `EXALTATION_SIGN` | `dict[str, int]` | Planet → exaltation sign index | | `EXALTATION_DEGREE` | `dict[str, float]` | Planet → deepest exaltation degree | | `DEBILITATION_SIGN` | `dict[str, int]` | Planet → debilitation sign index | | `MULATRIKONA_SIGN` | `dict[str, int]` | Planet → Mulatrikona sign index | | `MULATRIKONA_START` | `dict[str, float]` | Planet → Mulatrikona start degree | | `MULATRIKONA_END` | `dict[str, float]` | Planet → Mulatrikona end degree | | `OWN_SIGNS` | `dict[str, tuple[int, ...]]` | Planet → own-sign indices | | `NATURAL_FRIENDS` | `dict[str, tuple[str, ...]]` | Planet → natural friends | | `NATURAL_NEUTRALS` | `dict[str, tuple[str, ...]]` | Planet → natural neutrals | | `NATURAL_ENEMIES` | `dict[str, tuple[str, ...]]` | Planet → natural enemies | ### Ashtakavarga ```python from moira import ( bhinnashtakavarga, ashtakavarga, sign_strength_profile, transit_strength, ashtakavarga_chart_profile, BhinnashtakavargaResult, AshtakavargaResult, SignStrengthProfile, AshtakavargaChartProfile, AshtakavargaPolicy, validate_ashtakavarga_output, ) ``` | Function | Returns | Description | |---|---|---| | `bhinnashtakavarga(planet, sign_indices)` | `BhinnashtakavargaResult` | Per-planet rekha distribution across the 12 signs | | `ashtakavarga(sidereal_longitudes, ayanamsa_system=None, policy=None)` | `AshtakavargaResult` | Full Sarvashtakavarga and Bhinnashtakavarga result | | `sign_strength_profile(bhinna, sign_idx, policy=None)` | `SignStrengthProfile` | Interpret one sign within a Bhinnashtakavarga result | | `transit_strength(planet, transit_sign_index, bhinna)` | `int` | Rekha strength of a transit through a sign | | `ashtakavarga_chart_profile(result, policy=None)` | `AshtakavargaChartProfile` | Aggregate Ashtakavarga chart profile | | `validate_ashtakavarga_output(result)` | `None` | Validate Ashtakavarga output invariants | #### `BhinnashtakavargaResult` fields | Field | Type | Description | |---|---|---| | `planet` | `str` | Planet name | | `rekhas` | `tuple[int, ...]` | 12-sign rekha vector | | `total_rekhas` | `int` | Sum of rekhas across all signs | #### `AshtakavargaResult` fields | Field | Type | Description | |---|---|---| | `ayanamsa_system` | `str` | Governing ayanamsa system | | `bhinnashtakavarga` | `dict[str, BhinnashtakavargaResult]` | Per-planet bhinna results | | `sarvashtakavarga` | `tuple[int, ...]` | Aggregate 12-sign Sarvashtakavarga vector | | `shodhana_bhinnashtakavarga` | `dict[str, BhinnashtakavargaResult] \| None` | Shodhana-adjusted bhinna results when enabled | | `shodhana_sarvashtakavarga` | `tuple[int, ...] \| None` | Shodhana-adjusted Sarvashtakavarga when enabled | #### `SignStrengthProfile` fields | Field | Type | Description | |---|---|---| | `planet` | `str` | Planet name | | `sign_idx` | `int` | Zero-based sign index | | `rekha_count` | `int` | Rekha count in the sign | | `tier` | `str` | Strength tier under the active policy | #### `AshtakavargaChartProfile` fields | Field | Type | Description | |---|---|---| | `sarva_total` | `int` | Total Sarvashtakavarga points | | `sarva_max` | `int` | Maximum sign score | | `sarva_max_sign_idx` | `int` | Sign index of the maximum score | | `sarva_min` | `int` | Minimum sign score | | `sarva_min_sign_idx` | `int` | Sign index of the minimum score | | `strong_planet_sign_counts` | `dict[str, int]` | Count of strong signs by planet | | `ayanamsa_system` | `str` | Governing ayanamsa system | #### Ashtakavarga policy & constants | Public symbol | Kind | Description | |---|---|---| | `AshtakavargaPolicy` | dataclass | `ayanamsa_system`, `strong_threshold`, `apply_trikona_shodhana`, `apply_ekadhipatya_shodhana` | | `RekhaTier` | enum-like class | Strength-tier constants for rekha interpretation | | `REKHA_TABLES` | `dict[str, dict[str, tuple[int, ...]]]` | Classical rekha tables used to build Bhinnashtakavarga | ### Shadbala ```python 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, ) ``` | Function | Returns | Description | |---|---|---| | `sthana_bala(planet, sidereal_lon, houses, jd, ayanamsa_system=Ayanamsa.LAHIRI)` | `SthanaBala` | Positional strength components for one planet | | `dig_bala(planet, sidereal_lon, houses, jd, ayanamsa_system=Ayanamsa.LAHIRI)` | `float` | Directional 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)` | `KalaBala` | Temporal strength components for one planet | | `chesta_bala(planet, speed, planet_sidereal_lon=None, mandoccha_sidereal_lon=None)` | `float` | Motional strength for one planet | | `drig_bala(planet, sidereal_longitudes)` | `float` | Aspect-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)` | `ShadbalaResult` | Full Shadbala computation for the seven classical planets | | `hora_lord_at(birth_jd, sunrise_jd)` | `str` | Planetary hora lord at birth | | `shadbala_condition_profile(planet_result)` | `ShadbalaConditionProfile` | Inspect one planet’s Shadbala result | | `shadbala_chart_profile(result)` | `ShadbalaChartProfile` | Aggregate chart-level Shadbala profile | | `validate_shadbala_output(result)` | `None` | Validate Shadbala output invariants | #### `PlanetShadbala` fields | Field | Type | Description | |---|---|---| | `planet` | `str` | Planet name | | `sthana_bala` | `SthanaBala` | Positional strength components | | `dig_bala` | `float` | Directional strength | | `kala_bala` | `KalaBala` | Temporal strength components | | `chesta_bala` | `float` | Motional strength | | `naisargika_bala` | `float` | Natural strength constant | | `drig_bala` | `float` | Aspect-based strength | | `total_shashtiamsas` | `float` | Total strength in shashtiamsas | | `total_rupas` | `float` | Total strength in rupas | | `required_rupas` | `float` | Required threshold for sufficiency | | `is_sufficient` | `bool` | Sufficiency flag | #### `ShadbalaResult` fields | Field | Type | Description | |---|---|---| | `jd` | `float` | Julian day of the computation moment | | `ayanamsa_system` | `str` | Governing ayanamsa system | | `planets` | `dict[str, PlanetShadbala]` | Per-planet Shadbala results | #### `ShadbalaConditionProfile` fields | Field | Type | Description | |---|---|---| | `planet` | `str` | Planet name | | `tier` | `str` | Interpreted strength tier | | `total_rupas` | `float` | Total strength in rupas | | `required_rupas` | `float` | Required threshold | | `strength_ratio` | `float` | Ratio of actual to required strength | | `is_sufficient` | `bool` | Sufficiency flag | #### `ShadbalaChartProfile` fields | Field | Type | Description | |---|---|---| | `sufficient_count` | `int` | Count of planets meeting the threshold | | `insufficient_count` | `int` | Count of planets below threshold | | `strongest_planet` | `str` | Strongest planet by ratio | | `weakest_planet` | `str` | Weakest planet by ratio | | `planet_tiers` | `dict[str, str]` | Planet-to-tier mapping | | `strength_ratios` | `dict[str, float]` | Planet-to-ratio mapping | | `ayanamsa_system` | `str` | Governing ayanamsa system | #### `SthanaBala` fields | Field | Type | Description | |---|---|---| | `uchcha` | `float` | Exaltation-proximity component | | `saptavargaja` | `float` | Seven-varga dignity component | | `ojayugma` | `float` | Odd/even sign-parity component | | `kendradi` | `float` | Angularity component | | `drekkana` | `float` | Decan-gender component | | `total` | `float` | Total positional strength | #### `KalaBala` fields | Field | Type | Description | |---|---|---| | `nathonnatha` | `float` | Day/night strength component | | `paksha` | `float` | Lunar-phase strength component | | `tribhaga` | `float` | Third-of-day/night component | | `abda_masa_vara_hora` | `float` | Year/month/weekday/hour-lord component | | `ayana` | `float` | Solstitial component | | `yuddha` | `float` | Planetary-war bonus component | | `total` | `float` | Total temporal strength | #### Shadbala policy, tiers, and constants | Public symbol | Kind | Description | |---|---|---| | `ShadbalaPolicy` | dataclass | `ayanamsa_system` | | `ShadbalaTier` | enum-like class | `SUFFICIENT`, `INSUFFICIENT` | | `NAISARGIKA_BALA` | `dict[str, float]` | Natural fixed-strength constants in shashtiamsas | | `REQUIRED_RUPAS` | `dict[str, float]` | Required sufficiency thresholds in rupas | | `MEAN_DAILY_MOTION` | `dict[str, float]` | Mean daily motion constants used in Chesta Bala | --- ## 10. Planetary Cycles Engine ```python 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. | Function | Returns | Description | |---|---|---| | `return_series(body, natal_lon, jd_start, jd_end)` | `ReturnSeries` | All direct returns of a body to its natal longitude | | `half_return_series(body, natal_lon, jd_start, jd_end)` | `ReturnSeries` | Returns and half-returns (oppositions) interleaved | | `lifetime_returns(body, natal_lon, jd_natal, age_years=90.0)` | `ReturnSeries` | Full-lifetime return sequence from birth | #### `ReturnEvent` fields | Field | Type | Description | |---|---|---| | `body` | `str` | The returning body | | `return_number` | `int` | Ordinal (1 = first return) | | `jd_ut` | `float` | JD UT of exact return | | `longitude` | `float` | Natal longitude returned to (°) | | `is_half` | `bool` | True for a half-return (opposition to natal) | #### `ReturnSeries` fields | Field | Type | Description | |---|---|---| | `body` | `str` | Body name | | `natal_longitude` | `float` | Natal longitude (°) | | `jd_start` | `float` | Start of search window | | `jd_end` | `float` | End of search window | | `returns` | `tuple[ReturnEvent, ...]` | All returns, chronological | | `count` | `int` | Number of returns found | ### Synodic Cycles ```python pos = synodic_cycle_position(body1, body2, jd_ut) # SynodicCyclePosition ``` #### `SynodicCyclePosition` fields | Field | Type | Description | |---|---|---| | `body1` | `str` | First body | | `body2` | `str` | Second body | | `jd_ut` | `float` | Moment of evaluation | | `phase_angle` | `float` | Phase angle from body1 to body2 (°), [0°, 360°) — 0° = conjunction | | `phase` | `SynodicPhase` | Eight-fold phase classification | | `is_waxing` | `bool` | True if the phase angle is increasing (0°–180°) | | `lon1` | `float` | Ecliptic longitude of body1 at evaluation (°) | | `lon2` | `float` | Ecliptic 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). | Function | Returns | Description | |---|---|---| | `great_conjunctions(jd_start, jd_end)` | `GreatConjunctionSeries` | All Jupiter–Saturn conjunctions in a range | | `mutation_period_at(longitude)` | `GreatMutationElement` | Elemental trigon (FIRE / EARTH / AIR / WATER) for a given ecliptic longitude | #### `GreatConjunction` fields | Field | Type | Description | |---|---|---| | `jd_ut` | `float` | JD UT of exact conjunction | | `longitude` | `float` | Conjunction longitude (°) | | `sign` | `str` | Zodiac sign name | | `sign_symbol` | `str` | Zodiac sign glyph | | `degree_in_sign` | `float` | Degree within the sign | | `element` | `GreatMutationElement` | Elemental trigon: FIRE / EARTH / AIR / WATER | `GreatMutationElement` values: `FIRE EARTH AIR WATER` #### `GreatConjunctionSeries` fields | Field | Type | Description | |---|---|---| | `jd_start` | `float` | Start of search window | | `jd_end` | `float` | End of search window | | `conjunctions` | `tuple[GreatConjunction, ...]` | All conjunctions found, chronological | | `count` | `int` | Number of conjunctions | | `elements_represented` | `tuple[GreatMutationElement, ...]` | Distinct elements present, in order of first occurrence | #### `MutationPeriod` fields | Field | Type | Description | |---|---|---| | `element` | `GreatMutationElement` | Dominant element for this ~200-year period | | `start_conjunction` | `GreatConjunction` | First conjunction that inaugurated this element period | | `end_conjunction` | `GreatConjunction \| None` | Final conjunction in this element before mutation (None if period extends beyond the search window) | | `conjunction_count` | `int` | Number 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. | Function | Returns | Description | |---|---|---| | `planetary_age_at(age_years)` | `PlanetaryAgePeriod` | Which planet governs a given age | | `planetary_age_profile(age_years=None)` | `PlanetaryAgeProfile` | Full seven-period model; pass `age_years` to identify the current period | #### `PlanetaryAgePeriod` fields | Field | Type | Description | |---|---|---| | `ruler` | `PlanetaryAgeName` | Governing planet | | `start_age` | `float` | Age when this period begins (years) | | `end_age` | `float \| None` | Age when this period ends (None for Saturn, which is open-ended) | | `label` | `str` | Human-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 | Field | Type | Description | |---|---|---| | `periods` | `tuple[PlanetaryAgePeriod, ...]` | All seven age periods in order | | `current` | `PlanetaryAgePeriod \| None` | Active period for the queried age, or None if not queried | | `queried_age` | `float \| None` | The 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 | Function | Returns | Description | |---|---|---| | `firdar_series(jd_natal, is_day)` | `FirdarSeries` | Complete 75-year Firdar sequence from birth | | `firdar_at(jd_natal, jd_now, is_day)` | `FirdarPeriod` | Active Firdar period (major + sub) at jd_now | #### `FirdarSeries` fields | Field | Type | Description | |---|---|---| | `birth_jd` | `float` | Birth Julian Day | | `is_day_birth` | `bool` | True for diurnal nativity | | `periods` | `tuple[FirdarPeriod, ...]` | All 9 firdar major periods in sequence | | `total_years` | `float` | Sum of all periods (~75 Julian years) | #### `FirdarPeriod` fields (cycles.py vessel) | Field | Type | Description | |---|---|---| | `ruler` | `str` | The planet (or node) governing this firdar | | `start_jd` | `float` | Start JD UT of the major period | | `end_jd` | `float` | End JD UT | | `duration_years` | `float` | Duration in Julian years | | `ordinal` | `int` | Position in the sequence (1–9) | | `sub_periods` | `tuple[FirdarSubPeriod, ...] \| None` | 7 planetary sub-periods (None for nodal firdars) | #### `FirdarSubPeriod` fields | Field | Type | Description | |---|---|---| | `sub_ruler` | `str` | Governing planet for the sub-period | | `start_jd` | `float` | Start JD UT | | `end_jd` | `float` | End JD UT | | `duration_years` | `float` | Duration in Julian years | ### Planetary Days and Hours | Function | Returns | Description | |---|---|---| | `planetary_day_ruler(jd_ut)` | `PlanetaryDayInfo` | Chaldean day ruler for the given JD | | `planetary_hours_for_day(jd_ut, latitude, longitude)` | `PlanetaryHoursProfile` | Full day and night planetary hour schedule | #### `PlanetaryDayInfo` fields | Field | Type | Description | |---|---|---| | `ruler` | `str` | Planet ruling this day (Chaldean order) | | `weekday_name` | `str` | Name of the weekday | | `weekday_number` | `int` | ISO weekday (1 = Monday, 7 = Sunday) | #### `PlanetaryHour` fields | Field | Type | Description | |---|---|---| | `hour_number` | `int` | 1–24 (1–12 = day hours, 13–24 = night hours) | | `ruler` | `str` | Planet governing this hour | | `start_jd` | `float` | Start of this hour (JD UT) | | `end_jd` | `float` | End of this hour (JD UT) | | `is_day_hour` | `bool` | True for a daytime (diurnal) hour | #### `PlanetaryHoursProfile` fields | Field | Type | Description | |---|---|---| | `day_info` | `PlanetaryDayInfo` | The day's ruler and weekday metadata | | `sunrise_jd` | `float` | Sunrise JD used for the day's hours | | `sunset_jd` | `float` | Sunset JD used | | `next_sunrise_jd` | `float` | Next sunrise JD (used for nighttime hour duration) | | `hours` | `tuple[PlanetaryHour, ...]` | All 24 hours in order (1–24) | | `day_hour_length` | `float` | Duration of one daytime hour (days) | | `night_hour_length` | `float` | Duration of one nighttime hour (days) | --- ## 11. Huber Method ```python 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 | Constant | Value | Description | |---|---|---| | `PHI` | 0.6180... | Golden ratio fractional part | | `PHI_COMPLEMENT` | 0.3819... | Complement of phi (1 − phi) | | `CYCLE_YEARS` | 72.0 | Full Age Point cycle in years | | `YEARS_PER_HOUSE` | 6.0 | Years the Age Point spends per house | ### `HouseZone` — golden-section zones Each house is divided by the golden ratio into three developmental zones: | Zone | Fraction | Quality | |---|---|---| | `CARDINAL` | 0.000 – 0.382 | Outward initiative, environmental engagement | | `FIXED` | 0.382 – 0.618 | Consolidation, stable expression | | `MUTABLE` | 0.618 – 1.000 | Transition, preparation for the next house | ### House Zone Analysis ```python zones = house_zones(houses) # list[HouseZoneProfile] — one per house ``` #### `HouseZoneProfile` fields | Field | Type | Description | |---|---|---| | `house` | `int` | House number (1–12) | | `cusp_longitude` | `float` | Opening cusp longitude (°) | | `next_cusp_longitude` | `float` | Next cusp longitude (°) | | `house_size` | `float` | Angular size of the house (°) | | `balance_point_longitude` | `float` | Balance Point longitude (cusp + 0.382 × size) | | `low_point_longitude` | `float` | Low Point longitude (cusp + 0.618 × size) | | `balance_point_fraction` | `float` | Always PHI_COMPLEMENT (~0.382) | | `low_point_fraction` | `float` | Always PHI (~0.618) | ### Age Point ```python 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 | Field | Type | Description | |---|---|---| | `age_years` | `float` | Age in years from birth | | `cycle` | `int` | Which 72-year cycle (1 = first life, 2 = second…) | | `house` | `int` | House number currently occupied (1–12) | | `fraction_through_house` | `float` | 0.0 at cusp, 1.0 at next cusp | | `longitude` | `float` | Ecliptic longitude of the Age Point (°) | | `zone` | `HouseZone` | CARDINAL / FIXED / MUTABLE zone | | `years_into_house` | `float` | Years elapsed since entering this house | | `intensity` | `float` | Dynamic Intensity Curve value (0.0–1.0) | ```python 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 ```python 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 ```python 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 | Field | Type | Description | |---|---|---| | `scores` | `list[PlanetIntensityScore]` | Per-planet scores, highest first | | `mean_intensity` | `float` | Mean intensity across all scored planets | | `dominant_planet` | `str` | Planet with the highest intensity score | | `dominant_zone` | `HouseZone` | Zone of the dominant planet | `PlanetIntensityScore`: `planet`, `longitude`, `house`, `zone`, `fraction_through_house`, `intensity`. ```python 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 ```python 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, ) ``` | Function | Returns | Description | |---|---|---| | `synastry_aspects(chart_a, chart_b, tier=0, orbs=None, orb_factor=1.0, include_nodes=True)` | `list[AspectData]` | Inter-chart aspects (default tier 0: major aspects) | | `synastry_contacts(chart_a, chart_b, ...)` | `list[SynastryAspectContact]` | Contacts with classification | | `house_overlay(chart_source, target_houses, ...)` | `SynastryHouseOverlay` | chart_source planets in target_houses | | `mutual_house_overlays(chart_a, houses_a, chart_b, houses_b, ...)` | `MutualHouseOverlay` | Both overlay directions | ### Composite Charts ```python 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 ```python 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: | Variant | Function | Midpoint time | Midpoint location | |---|---|---|---| | Standard | `davison_chart` | JD arithmetic mean | Spherical midpoint | | Uncorrected | `davison_chart_uncorrected` | Arithmetic mean | Arithmetic mean | | Reference Place | `davison_chart_reference_place` | Arithmetic mean | Supplied explicitly | | Spherical | `davison_chart_spherical_midpoint` | Arithmetic mean | Great-circle midpoint | | Corrected | `davison_chart_corrected` | Corrected for JD midpoint | Spherical midpoint | `DavisonChart`: `chart` (Chart), `info` (DavisonInfo — midpoint JD, lat, lon). ### Exact relationship-chart transits Track A admits exact aspect perfections from moving bodies to an immutable composite or Davison target set. The implementation delegates every event search to the canonical transit solver; it does not introduce a second aspect or ephemeris authority. | Function | Returns | Description | |---|---|---| | `relationship_chart_targets(chart, *, include_nodes=True, include_angles=False, include_cusps=False, target_names=None)` | `RelationshipChartTargetSet` | Build the typed, identity-bound static target set for a composite or Davison chart | | `find_relationship_transits(chart, moving_bodies, jd_start, jd_end, *, tier=0, aspect_names=None, include_nodes=True, include_angles=False, include_cusps=False, target_names=None, direction="either", step_days=None, reader=None, policy=None, search_motion="forward")` | `RelationshipTransitSearchResult` | Search exact canonical aspect perfections against the selected static targets | | `find_composite_transits(...)` | `RelationshipTransitSearchResult` | Composite-chart type-restricted wrapper | | `find_davison_transits(...)` | `RelationshipTransitSearchResult` | Davison-chart type-restricted wrapper | Public vessels and enums are `RelationshipChartKind`, `RelationshipTargetKind`, `RelationshipChartIdentity`, `RelationshipTransitTarget`, `RelationshipChartTargetSet`, `RelationshipTransitEvent`, `RelationshipTransitSearchTruth`, and `RelationshipTransitSearchResult`. The result preserves chart identity, construction truth, target and aspect selection, transit policy, search count, and exact canonical `TransitEvent` receipts. The `Moira` facade exposes the same effective policies: | `Moira` method | Delegation | |---|---| | `relationship_chart_targets(...)` | Kernel-free target and identity composition | | `relationship_transits(...)` | Reader-bound generic relationship transit search | | `composite_transits(...)` | Reader-bound composite transit search | | `davison_transits(...)` | Reader-bound Davison transit search | Orb-entry/exit windows, progressed or directed relationship charts, cross-chart multi-body patterns, scoring, and interpretation are not part of this contract. --- ## 13. Geography ### Astro*Carto*Graphy ```python from moira.facade import acg_lines, acg_from_chart, ACGLine ``` | Function | Returns | Description | |---|---|---| | `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 | Field | Type | Description | |---|---|---| | `planet` | `str` | Body name | | `line_type` | `str` | `"MC"` / `"IC"` / `"ASC"` / `"DSC"` | | `longitude` | `float \| None` | Geographic longitude for MC/IC meridians | | `points` | `list[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`. ### Fixed-star and explicit-epoch dynamic Astrocartography | Function | Returns | Description | |---|---|---| | `fixed_star_equatorial_subject(name, jd_tt, obliquity_deg)` | `FixedStarAstrocartographySubject` | Resolve one canonical fixed-star identity and its true-of-date equatorial geometry | | `fixed_star_astrocartography(star_names, jd_ut, jd_tt, *, lat_step=2.0, refraction=False)` | `FixedStarAstrocartographyResult` | Compute MC/IC/ASC/DSC lines and zenith/nadir points for explicit stars | | `fixed_star_astrocartography_from_chart(chart, star_names, *, lat_step=2.0, refraction=False)` | `FixedStarAstrocartographyResult` | Use a chart's admitted time truth while preserving the same star receipts | | `transiting_astrocartography(epochs_jd_ut, bodies, *, observer_latitude, observer_longitude, observer_elevation_m=0.0, lat_step=2.0, refraction=False, reader=None)` | `DynamicAstrocartographySeries` | Compute one to 128 strictly increasing caller-supplied transit snapshots and exact adjacent line shifts | Fixed-star vessels are `FixedStarAstrocartographySubject`, `FixedStarAstrocartographyTruth`, and `FixedStarAstrocartographyResult`. Dynamic vessels are `DynamicAstrocartographyMode`, `DynamicAstrocartographyPosition`, `DynamicAstrocartographySnapshotTruth`, `DynamicAstrocartographySnapshot`, `AstrocartographyCurvePointShift`, `DynamicAstrocartographyLineTransition`, `DynamicAstrocartographySeriesTruth`, and `DynamicAstrocartographySeries`. The facade supplies `Moira.fixed_star_astrocartography(...)` as the same kernel-free star/geometry convenience and `Moira.transiting_astrocartography(...)` as the reader-bound dynamic call. Star/place ranking, catalog sweeps, interpolation, progressed or directed cyclocartography, destination scores, travel advice, and interpretation are excluded. ### Relocated returns | Function | Returns | Description | |---|---|---| | `relocated_solar_return(natal_sun_longitude, year, source_latitude, source_longitude, relocated_latitude, relocated_longitude, **policy)` | `RelocatedReturnChart` | Solve one canonical solar return and cast its unchanged sky in source and relocated house frames | | `relocated_lunar_return(natal_moon_longitude, jd_start, source_latitude, source_longitude, relocated_latitude, relocated_longitude, **policy)` | `RelocatedReturnChart` | Canonical lunar-return composition at two locations | | `relocated_planetary_return(body, natal_longitude, jd_start, source_latitude, source_longitude, relocated_latitude, relocated_longitude, **policy)` | `RelocatedReturnChart` | Canonical admitted planetary-return composition at two locations | `RelocatedReturnChart` contains source and relocated `ChartContext` values plus typed `ReturnMomentTruth` and `ReturnRelocationTruth`. Supporting public enums and truth vessels are `ReturnKind`, `ReturnSearchPolicyTruth`, `ReturnMomentTruth`, and `ReturnRelocationTruth`. Only the local house frame changes; the exact return epoch and celestial positions remain identical. The reader-bound facade exposes the three matching `relocated_*_return` methods. This contract does not add another return solver, relocation rankings, or interpretive claims. | `Moira` method | Delegation | |---|---| | `relocated_solar_return(...)` | Reader-bound canonical solar return composition | | `relocated_lunar_return(...)` | Reader-bound canonical lunar return composition | | `relocated_planetary_return(...)` | Reader-bound canonical planetary return composition | ```python 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 ```python from moira.facade import local_space_positions, local_space_from_chart, LocalSpacePosition ``` | Function | Returns | Description | |---|---|---| | `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 | Field | Type | Description | |---|---|---| | `body` | `str` | Body name | | `azimuth` | `float` | Compass bearing 0-360 degrees (North = 0, East = 90) | | `altitude` | `float` | Elevation above (+) or below (-) horizon | | `is_above` | `bool` | True when `altitude >= 0` | **Method:** `compass_direction() -> str` - returns an 8-point compass label (N/NE/E/SE/S/SW/W/NW). ```python 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. ```python 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, ) ``` | Function | Returns | Description | |---|---|---| | `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(...)` | `ParanSearchResult` | Paran 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(...)` | `ParanSearchResult` | Birth-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, ...)` | `ParanSiteResult` | Recompute 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)` | `ParanFieldAnalysis` | Identify active regions, peaks, and threshold crossings | | `evaluate_paran_stability(paran, jd_day, lat, lon, ...)` | `ParanStability` | Recompute one paran under time-anchor perturbations | | `extract_paran_field_contours(samples, metric, threshold)` | `ParanContourExtraction` | Extract contour segments from a rectangular sampled field | | `consolidate_paran_contours(extraction)` | `ParanContourPathSet` | Stitch contour segments into paths and report orphans | | `analyze_paran_field_structure(analysis, path_set)` | `ParanFieldStructure` | Derive 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 | Field | Type | Description | |---|---|---| | `body1` | `str` | First body | | `body2` | `str` | Second body | | `circle1` | `str` | Circle type for the first body | | `circle2` | `str` | Circle type for the second body | | `jd1` | `float` | Event JD for the first body crossing | | `jd2` | `float` | Event JD for the second body crossing | | `orb_min` | `float` | Difference between the crossings in minutes of time | | `crossing1` | `ParanCrossing` | Crossing details for the first body | | `crossing2` | `ParanCrossing` | Crossing details for the second body | | `signature` | `ParanSignature` | Combined paran signature metadata | --- ## 14. Fixed Stars ### Unified fixed-star surface (`star_registry.csv` + metadata sidecars) ```python 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, ) ``` | Function | Returns | Description | |---|---|---| | `star_at(name, jd_tt, policy=None)` | `FixedStar` | Public 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)` | `float` | Visual magnitude | | `load_catalog()` | `None` | Reload the sovereign fixed-star registry and indexes | | `heliacal_rising(name, jd_ut, latitude, longitude)` | `float \| None` | JD of heliacal rising | | `heliacal_setting(name, jd_ut, latitude, longitude)` | `float \| None` | JD of heliacal setting | | `heliacal_rising_event(name, jd_ut, lat, lon)` | `HeliacalEvent` | Heliacal rising with classification | | `heliacal_setting_event(name, jd_ut, lat, lon)` | `HeliacalEvent` | Heliacal setting with classification | | `heliacal_catalog_batch(event_kind, jd_start, latitude, longitude, *, max_magnitude=6.5, names=None, search_days=400, policy=None)` | `HeliacalBatchResult` | Batch 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: ```python 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 | Function | Returns | Description | |---|---|---| | `star_at(name, jd_tt)` | `FixedStar` | Named 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 | Field | Type | Description | |---|---|---| | `name` | `str` | Traditional name | | `nomenclature` | `str \| None` | Catalog designation or alternate nomenclature | | `longitude` | `float` | Ecliptic longitude (°) | | `latitude` | `float` | Ecliptic latitude (°) | | `magnitude` | `float` | Visual magnitude | | `bp_rp` | `float \| None` | Gaia BP−RP colour index | | `teff_k` | `float \| None` | Effective temperature (K) from Gaia | | `parallax_mas` | `float \| None` | Gaia parallax (mas) | | `distance_ly` | `float \| None` | Distance in light-years | | `quality` | `StellarQuality \| None` | Stellar classification from Gaia colours | | `source` | `str` | Data source used for the merged record | | `is_topocentric` | `bool` | Whether topocentric correction was applied | | `computation_truth` | `FixedStarTruth` | Computation truth data | | `classification` | `FixedStarClassification` | Classification metadata | | `relation` | `UnifiedStarRelation` | Relation metadata | | `condition_profile` | `StarConditionProfile` | Consolidated 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. ```python from moira.facade import ( HeliacalEventKind, VisibilityTargetKind, LightPollutionClass, LightPollutionDerivationMode, ObserverAid, ObserverVisibilityEnvironment, VisibilityCriterionFamily, VisibilityExtinctionModel, VisibilityTwilightModel, ExtinctionCoefficient, MoonlightPolicy, AtmosphericExtinctionAssessment, TwilightSkyBrightnessAssessment, PointSourceVisibilityThreshold, VisibilityPolicy, VisibilitySearchPolicy, LunarCrescentVisibilityClass, LunarCrescentDetails, VisibilityAssessment, GeneralVisibilityEvent, relative_optical_airmass, atmospheric_extinction, directional_twilight_sky_brightness, point_source_visibility_threshold, visibility_assessment, visual_limiting_magnitude, visibility_event, ) ``` | Function | Returns | Description | |---|---|---| | `relative_optical_airmass(apparent_altitude_deg)` | `float` | Kasten–Young (1989) relative optical air mass for an apparent above-horizon direction | | `atmospheric_extinction(apparent_altitude_deg, *, model, ...)` | `AtmosphericExtinctionAssessment` | Auditable direct extinction from a measured broadband coefficient or Schaefer (1993) clear-air components, with target and sky-model coefficients kept distinct | | `directional_twilight_sky_brightness(target_altitude_deg, sun_altitude_deg, sun_target_separation_deg, *, extinction_coefficient_k)` | `TwilightSkyBrightnessAssessment` | Schaefer (1993) directional cloudless twilight contribution with an explicit validity verdict | | `point_source_visibility_threshold(background_nanolamberts, *, field_factor=2.0)` | `PointSourceVisibilityThreshold` | Crumey (2014) scotopic naked-eye point-source limit without out-of-range extrapolation | | `visibility_assessment(body, jd_ut, lat, lon, *, policy=None)` | `VisibilityAssessment` | Criterion-based visibility judgment at one observing moment | | `visual_limiting_magnitude(jd_ut, lat, lon, *, policy=None)` | `float` | Estimated 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 \| None` | Search for the next generalized visibility event matching the requested kind | Key policy and vessel types: `VisibilityPolicy`, `VisibilitySearchPolicy`, `VisibilityAssessment`, `GeneralVisibilityEvent`, `ObserverVisibilityEnvironment`, `AtmosphericExtinctionAssessment`, `TwilightSkyBrightnessAssessment`, `PointSourceVisibilityThreshold`, `LunarCrescentDetails`. The default policy remains the legacy limiting-magnitude/arcus-visionis path. The physical point-source path is opt-in and requires the coherent combination `CRUMEY_2014_POINT_SOURCE`, either physical extinction model, and `SCHAEFER_1993_DIRECTIONAL`. It is admitted only for naked-eye unresolved planets and stars. It reports, rather than extrapolates through, daylight, below-horizon targets, and background luminance outside Crumey's published scotopic range. The physical criterion is currently a single-epoch assessment surface; `visibility_event()` rejects it rather than silently using the legacy event-search threshold. `visual_limiting_magnitude()` remains a scalar zenith-reference product when its optional moonlight model is enabled. Crumey's published field factor includes the observing medium, so the default does not apply direct target extinction a second time. A caller with a field factor calibrated without atmospheric loss may set `crumey_field_factor_includes_atmosphere=False`; the assessment then exposes that separate extinction in both its criterion magnitude and response flag. When no measured sky surface brightness is supplied, the physical policy requires the explicit `BORTLE_TABLE` derivation mode; it never repurposes the legacy Bortle-to-limiting-magnitude interpolation as a luminance model. For Schaefer's component path, `total_zenith_extinction_coefficient` is the scotopic direct-target value, while `sky_brightness_extinction_coefficient` preserves the visual-band value consumed by the twilight, dark-sky, and K&S moonlight equations. ```python physical = VisibilityPolicy( criterion_family=VisibilityCriterionFamily.CRUMEY_2014_POINT_SOURCE, light_pollution_derivation_mode=LightPollutionDerivationMode.BORTLE_TABLE, extinction_model=VisibilityExtinctionModel.KASTEN_YOUNG_1989_BROADBAND, twilight_model=VisibilityTwilightModel.SCHAEFER_1993_DIRECTIONAL, environment=ObserverVisibilityEnvironment( sky_surface_brightness_mag_arcsec2=21.25, ), ) result = visibility_assessment("Venus", jd_ut, lat, lon, policy=physical) ``` ### Variable Stars ```python 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 | Constant | Meaning | |---|---| | `VarType.ECLIPSING_ALGOL` | Algol-type (EA) — sharp minima | | `VarType.ECLIPSING_BETA` | Beta Lyrae-type (EB) — continuous variation | | `VarType.ECLIPSING_W_UMA` | W Ursae Maj.-type (EW) — contact binaries | | `VarType.CEPHEID` | Delta Cephei-type pulsating | | `VarType.RR_LYRAE` | RR Lyrae pulsating | | `VarType.MIRA` | Mira-type long-period | | `VarType.SEMI_REG_SG` | Semi-regular supergiant | | `VarType.SEMI_REG` | Semi-regular | #### `VariableStar` fields | Field | Type | Description | |---|---|---| | `name` | `str` | Star name | | `designation` | `str \| None` | Catalog designation | | `var_type` | `VarType` | Variability classification | | `period_days` | `float` | Period in days (0 if irregular) | | `epoch_jd` | `float` | Reference epoch JD | | `epoch_is_minimum` | `bool` | True when the epoch is a minimum, False when it is a maximum | | `mag_max` | `float` | Magnitude at maximum brightness | | `mag_min` | `float` | Magnitude at minimum brightness | | `mag_min2` | `float \| None` | Secondary minimum magnitude when applicable | | `eclipse_width` | `float` | Eclipse duration as fraction of period (EA only) | | `classical_quality` | `str` | "malefic" / "benefic" / "neutral" / "mixed" | | `note` | `str` | Short catalog note | **Derived properties:** `amplitude`, `is_eclipsing`, `is_pulsating`, `is_long_period`, `is_irregular`, `is_malefic`, `is_benefic`, `type_class`. #### Core functions | Function | Returns | Description | |---|---|---| | `variable_star(name)` | `VariableStar` | Look 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)` | `float` | Phase in [0, 1) at a given JD | | `magnitude_at(star, jd)` | `float` | Interpolated visual magnitude | | `malefic_intensity(star, jd, policy=None)` | `float` | Malefic score [0, 1] | | `benefic_strength(star, jd, policy=None)` | `float` | Benefic score [0, 1] | | `is_in_eclipse(star, jd, policy=None)` | `bool` | True when near minimum for eclipsing type | | `next_minimum(star, jd)` | `float` | JD of next minimum | | `next_maximum(star, jd)` | `float` | JD 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 | Function | Returns | Description | |---|---|---| | `algol_phase(jd)` | `float` | Algol phase at JD | | `algol_magnitude(jd)` | `float` | Algol magnitude at JD | | `algol_next_minimum(jd)` | `float` | JD of next Algol minimum | | `algol_is_eclipsed(jd, policy=None)` | `bool` | True when Algol is near minimum | #### Condition profile API ```python 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 ```python 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, ) ``` | Function | Returns | Description | |---|---|---| | `multiple_star(name)` | `MultipleStarSystem` | Retrieve 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)` | `float` | Current angular separation (arcsec) | | `position_angle_at(system, jd_tt)` | `float` | Current position angle (°) | | `is_resolvable(system, jd_tt, aperture_mm)` | `bool` | True if resolvable with aperture | | `dominant_component(system)` | `StarComponent` | Brighter/primary component | | `combined_magnitude(system)` | `float` | Combined visual magnitude | | `components_at(system, jd_tt)` | `dict` | Full component/separation snapshot for the system | | `sirius_ab_separation_at(jd_tt)` | `float` | Sirius A–B separation (arcsec) | | `sirius_b_resolvable(jd_tt, aperture_mm=200)` | `bool` | True if Sirius B is resolvable | | `castor_separation_at(jd_tt)` | `float` | Castor 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 ```python 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 | Field | Type | Description | |---|---|---| | `sun_longitude` | `float` | Sun longitude at the evaluated moment | | `moon_longitude` | `float` | Moon longitude at the evaluated moment | | `node_longitude` | `float` | Node longitude at the evaluated moment | | `moon_latitude` | `float` | Moon latitude relative to the ecliptic | | `eclipse_type` | `EclipseType` | TOTAL_SOLAR / ANNULAR / PARTIAL_SOLAR / PENUMBRAL / PARTIAL_LUNAR / TOTAL_LUNAR | | `is_eclipse_season` | `bool` | Whether the Sun is close enough to the nodes for eclipse season | | `is_solar_eclipse` | `bool` | Solar eclipse flag | | `is_lunar_eclipse` | `bool` | Lunar eclipse flag | | `eclipse_magnitude` | `float` | Computed eclipse magnitude | | `saros_series` | `int \| None` | Conventional van den Bergh/NASA Saros-series number | | `saros_index` | `int \| None` | Compatibility name for `saros_series` | | `saros_lunation_number` | `int \| None` | Signed NASA/GSFC `Luna Num` used for the series assignment | | `saros_cycle_position` | `float` | Legacy mean-month phase within a 223-month cycle; not a series identity | | `metonic_year` | `float` | Metonic cycle position | | `moon_distance_km` | `float` | Geocentric Moon distance in kilometers | | `galactic_center_longitude` | `float` | Galactic center longitude reference | | `sun_apparent_radius` | `float` | Apparent solar radius | | `moon_apparent_radius` | `float` | Apparent lunar radius | | `earth_shadow_apparent_radius` | `float` | Apparent umbral radius | | `earth_penumbra_apparent_radius` | `float` | Apparent penumbral radius | | `sun_stone` | `int` | Aubrey-stone style solar index | | `moon_stone` | `int` | Aubrey-stone style lunar index | | `node_stone` | `int` | Aubrey-stone style node index | | `south_node_stone` | `int` | Aubrey-stone style south-node index | | `angular_separation_3d` | `float` | 3D Sun/Moon angular separation | | `solar_topocentric_separation` | `float` | Topocentric Sun/Moon separation | | `sun_node_distance` | `float` | Distance from Sun to node | | `metonic_is_reset` | `bool` | Whether the Metonic cycle resets here | | `moon_parallax` | `float` | Lunar parallax | | `sun_side` | `int` | Stonehenge side index for the Sun | | `sun_pos_in_side` | `int` | Position of the Sun within the side index | #### NASA-compatible lunar eclipse API ```python 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 ```python from moira.facade import ( greatest_elongation, perihelion, aphelion, next_moon_phase, moon_phases_in_range, PhenomenonEvent, ) ``` | Function | Returns | Description | |---|---|---| | `greatest_elongation(body, jd_start, direction="east", reader=None, max_days=600.0)` | `PhenomenonEvent \| None` | Next greatest elongation of Mercury or Venus in the requested direction | | `perihelion(body, jd_start, reader=None, max_days=None)` | `PhenomenonEvent \| None` | Next perihelion passage | | `aphelion(body, jd_start, reader=None, max_days=None)` | `PhenomenonEvent \| None` | Next aphelion passage | | `next_moon_phase(phase_name, jd_start, reader=None)` | `PhenomenonEvent` | Next 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`. `perihelion` and `aphelion` adapt the versioned orbital-core passage search. They admit every loaded orbital body, use Earth for the Moon and Sun for every other body, and preserve verified UT1 in `jd_ut`; `value` is the event distance in AU. A legitimate non-found outcome returns `None`. Numerical search failure still raises and is never converted into physical absence. ### Occultations ```python 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, ) ``` | Function | Returns | Description | |---|---|---| | `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)` | `OccultationPathTopology` | Detailed 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)` | `OccultationPathTopology` | Detailed 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) ```python from moira.facade import ( sothic_rising_series, sothic_rising, sothic_epochs, sothic_drift_rate, egyptian_civil_date, days_from_1_thoth, predict_sothic_epoch, predicted_sothic_epoch_year, sothic_chart_condition_profile, sothic_condition_network_profile, SothicAnchor, SothicEpochPrediction, SothicYearOutcome, SothicRisingSeries, EgyptianDate, SothicEntry, SothicEpoch, EGYPTIAN_MONTHS, EGYPTIAN_SEASONS, EPAGOMENAL_BIRTHS, CENSORINUS_139_ANCHOR, SOTHIC_EPOCH_REFERENCES, HISTORICAL_SOTHIC_EPOCHS, SothicComputationPolicy, ) ``` | Function | Returns | Description | |---|---|---| | `sothic_rising_series(latitude, longitude, year_start, year_end, epoch_jd=1772027.5, arcus_visionis=10.0, policy=None)` | `SothicRisingSeries` | Exhaustive annual outcomes; delegated failures propagate | | `sothic_rising(latitude, longitude, year_start, year_end, epoch_jd=1772027.5, arcus_visionis=10.0, policy=None)` | `list[SothicEntry]` | Compatibility projection containing only found entries | | `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)` | `float` | Drift rate derived from a `list[SothicEntry]` | | `egyptian_civil_date(jd, epoch_jd=1772027.5, policy=None)` | `EgyptianDate` | Wandering civil calendar date | | `days_from_1_thoth(jd, epoch_jd=1772027.5)` | `float` | Days elapsed since the last 1 Thoth | | `predict_sothic_epoch(known_epoch_year, n_cycles, cycle_length_years=1460.0, policy=None)` | `SothicEpochPrediction` | Typed fixed-interval projection labelled as schematic | | `predicted_sothic_epoch_year(known_epoch_year, n_cycles, cycle_length_years=1460.0, policy=None)` | `float` | Compatibility scalar for the typed schematic projection | `CENSORINUS_139_ANCHOR` records Julian `0139-07-20`, proleptic Gregorian `0139-07-19`, and JD `1772027.5` as a primary-text calendar anchor, not a precise observed event timestamp. `SOTHIC_EPOCH_REFERENCES` distinguishes that anchor from fixed 1460-year schematic projections. The compatibility `HISTORICAL_SOTHIC_EPOCHS` dictionary list now carries the same evidence labels and astronomical-year numbering. `EPAGOMENAL_BIRTHS`: five epagomenal days and their mythological births. --- ## 16. Harmograms — Spectral Harmonic Analysis ```python 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 ```python domain = HarmonicDomain(harmonic_start=1, harmonic_stop=12) # .harmonics → tuple of all integers in [start, stop] ``` | Field | Type | Default | Description | |---|---|---|---| | `harmonic_start` | `int` | 1 | First harmonic (must be ≥ 1) | | `harmonic_stop` | `int` | 12 | Last harmonic (must be ≥ start) | **Property:** `harmonics` → `tuple[int, ...]` of all harmonics in the range. --- ### Core Data Vessels #### `HarmonicVectorComponent` — single harmonic result | Field | Type | Description | |---|---|---| | `harmonic` | `int` | Harmonic number (≥ 1) | | `amplitude` | `float` | Resultant amplitude (≥ 0); 1.0 = perfect clustering at this harmonic | | `phase_deg` | `float` | Phase of the resultant vector (°), [0°, 360°) | **Property:** `amplitude_squared`. #### `PointSetHarmonicVector` — harmonic vector for a named point set | Field | Type | Description | |---|---|---| | `policy` | `PointSetHarmonicVectorPolicy` | Normalization and domain policy used | | `body_names` | `tuple[str, ...]` | Names of the contributing points | | `point_count` | `int` | Number of points | | `harmonic_zero_amplitude` | `float` | H=0 amplitude (reflects normalization) | | `components` | `tuple[HarmonicVectorComponent, ...]` | One component per harmonic in the domain | **Method:** `get_component(harmonic)` → `HarmonicVectorComponent`. #### `ZeroAriesPart` — one pairwise angular difference | Field | Type | Description | |---|---|---| | `source_name` | `str` | Source body | | `target_name` | `str` | Target body | | `longitude_deg` | `float` | Angular difference projected to [0°, 360°) | #### `ZeroAriesPartsSet` — collection of pairwise parts | Field | Type | Description | |---|---|---| | `policy` | `ZeroAriesPartsPolicy` | Construction policy | | `source_body_names` | `tuple[str, ...]` | Source body names | | `target_body_names` | `tuple[str, ...]` | Target body names | | `parts` | `tuple[ZeroAriesPart, ...]` | All constructed parts | **Properties:** `source_point_count`, `target_point_count`, `parts_count`. #### `ZeroAriesPartsHarmonicVector` — harmonic vector for a parts set | Field | Type | Description | |---|---|---| | `vector_policy` | `PointSetHarmonicVectorPolicy` | Normalization policy | | `parts_policy` | `ZeroAriesPartsPolicy` | Parts construction policy | | `source_body_names` | `tuple[str, ...]` | Source bodies | | `target_body_names` | `tuple[str, ...]` | Target bodies | | `parts_count` | `int` | Number of parts contributing | | `harmonic_zero_amplitude` | `float` | H=0 amplitude | | `components` | `tuple[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. ```python spectrum = intensity_function_spectrum(longitudes, harmonic, policy=...) # IntensityFunctionSpectrum ``` #### `IntensitySpectrumComponent` fields | Field | Type | Description | |---|---|---| | `harmonic` | `int` | Harmonic number | | `amplitude` | `float` | Intensity amplitude at this harmonic (≥ 0) | | `phase_deg` | `float` | Phase (°), [0°, 360°) | #### `IntensityFunctionSpectrum` fields | Field | Type | Description | |---|---|---| | `policy` | `HarmogramIntensityPolicy` | Intensity policy used | | `harmonic_number` | `int` | The primary harmonic being analyzed | | `realization_mode` | `IntensitySpectrumRealizationMode` | Computation method | | `harmonic_zero_amplitude` | `float` | H=0 reference amplitude | | `components` | `tuple[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. ```python proj = project_harmogram_strength(source_vector, intensity_spectrum, policy=...) # HarmogramProjection ``` #### `HarmogramProjectionTerm` fields (one per harmonic) | Field | Type | Description | |---|---|---| | `harmonic` | `int` | Harmonic number | | `source_amplitude` | `float` | Source vector amplitude at this harmonic | | `source_phase_deg` | `float` | Source vector phase at this harmonic | | `intensity_amplitude` | `float` | Intensity spectrum amplitude at this harmonic | | `intensity_phase_deg` | `float` | Intensity spectrum phase at this harmonic | | `signed_contribution` | `float` | Signed contribution to total strength (positive = reinforcing) | #### `HarmogramProjection` fields | Field | Type | Description | |---|---|---| | `source_vector` | `PointSetHarmonicVector \| ZeroAriesPartsHarmonicVector` | Source point set | | `intensity_spectrum` | `IntensityFunctionSpectrum` | Intensity spectrum used | | `normalization_mode` | `HarmonicVectorNormalizationMode` | Normalization applied | | `realization_mode` | `HarmogramProjectionRealizationMode` | Computation method | | `harmonic_zero_contribution` | `float` | H=0 contribution | | `total_strength` | `float` | Total projected strength (sum of all term contributions) | | `terms` | `tuple[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. ```python 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) | Field | Type | Description | |---|---|---| | `sample_index` | `int` | Index into the epoch sequence (≥ 0) | | `sample_time` | `float` | JD of this sample | | `source_vector` | `ZeroAriesPartsHarmonicVector` | Parts vector for this epoch | | `projection` | `HarmogramProjection` | Full projection at this epoch | | `total_strength` | `float` | Total projected strength at this epoch | #### `HarmogramTraceSeries` fields | Field | Type | Description | |---|---|---| | `harmonic_number` | `int` | The harmonic being traced | | `intensity_spectrum` | `IntensityFunctionSpectrum` | Shared intensity spectrum | | `samples` | `tuple[HarmogramTraceSample, ...]` | All samples in epoch order | **Property:** `strengths` → `tuple[float, ...]` of `total_strength` per sample. #### `HarmogramTrace` fields | Field | Type | Description | |---|---|---| | `policy` | `HarmogramPolicy` | Governing policy | | `interval_start` | `float` | JD start of the trace interval | | `interval_stop` | `float` | JD end of the trace interval | | `sample_times` | `tuple[float, ...]` | All epoch JDs in order | | `series` | `tuple[HarmogramTraceSeries, ...]` | One series per harmonic in the output | --- ### Research Tools ```python dominant = dominant_harmonic_contributors(projection, top_n=5) # list[HarmogramDominantTerm] — harmonics ranked by |signed_contribution| ``` #### `HarmogramDominantTerm` fields | Field | Type | Description | |---|---|---| | `harmonic` | `int` | Harmonic number | | `absolute_contribution` | `float` | Absolute value of the contribution (≥ 0) | | `signed_contribution` | `float` | Signed contribution (positive = reinforcing) | ```python cmp = compare_intensity_spectra(spectrum_a, spectrum_b) # IntensitySpectrumComparison ``` #### `IntensitySpectrumComparison` fields | Field | Type | Description | |---|---|---| | `left` | `IntensityFunctionSpectrum` | First spectrum | | `right` | `IntensityFunctionSpectrum` | Second spectrum | | `max_absolute_delta` | `float` | Maximum per-harmonic amplitude difference | | `terms` | `tuple[IntensitySpectrumComparisonTerm, ...]` | Per-harmonic delta terms | `IntensitySpectrumComparisonTerm`: `harmonic`, `left_amplitude`, `right_amplitude`, `amplitude_delta`. ```python 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 | Field | Type | Default | Description | |---|---|---|---| | `point_set_policy` | `PointSetHarmonicVectorPolicy` | default | Normalization and domain for point sets | | `parts_policy` | `ZeroAriesPartsPolicy` | default | Zero-Aries parts construction | | `intensity_policy` | `HarmogramIntensityPolicy` | default | Intensity function shape | | `sampling_policy` | `HarmogramSamplingPolicy` | default | Trace sampling | | `output_mode` | `HarmogramOutputMode` | `MULTI_HARMONIC_FAMILY` | Single vs multi-harmonic output | | `chart_domain` | `HarmogramChartDomain` | `DYNAMIC_SKY_ONLY_TRACE` | What the trace represents | | `trace_family` | `HarmogramTraceFamily` | `DYNAMIC_ZERO_ARIES_PARTS` | Parts construction family | `chart_domain` and `trace_family` must be consistent (enforced by `__post_init__`). #### `HarmogramIntensityPolicy` — intensity function shape | Field | Type | Default | Description | |---|---|---|---| | `family` | `HarmogramIntensityFamily` | `COSINE_BELL_HARMONIC_ASPECTS` | Aspect weighting shape | | `include_conjunction` | `bool` | `True` | Whether to include H-fold conjunctions | | `orb_mode` | `HarmogramOrbMode` | `COSINE_BELL` | Orb weighting function (must match family) | | `orb_scaling_mode` | `HarmogramOrbScalingMode` | `EQUATED_TO_HARMONIC_ONE` | How orb scales with harmonic | | `symmetry_mode` | `HarmogramSymmetryMode` | `STAR_SYMMETRIC` | Star-symmetric or conjunction-excluded | | `normalization_mode` | `IntensityNormalizationMode` | `PEAK_ONE` | Spectrum normalization | | `harmonic_domain` | `HarmonicDomain` | H1–H12 | Harmonics evaluated | | `orb_width_deg` | `float` | 24.0 | Orb width at H=1 (°) | | `gaussian_width_parameter_mode` | `GaussianWidthParameterMode` | `FWHM` | Gaussian width interpretation | | `gaussian_width_deg` | `float \| None` | `None` | Gaussian width (required for Gaussian family) | | `sample_count` | `int` | 4096 | Quadrature 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` | Field | Type | Default | Description | |---|---|---|---| | `normalization_mode` | `HarmonicVectorNormalizationMode` | `MEAN_RESULTANT` | `RAW_SUM` or `MEAN_RESULTANT` | | `harmonic_domain` | `HarmonicDomain` | H1–H12 | Harmonics computed | #### `ZeroAriesPartsPolicy` | Field | Type | Default | Description | |---|---|---|---| | `pair_construction_mode` | `ZeroAriesPairConstructionMode` | `ORDERED` | `ORDERED` (A→B ≠ B→A) or `UNORDERED` | | `self_pair_mode` | `SelfPairMode` | `INCLUDE` | Whether to include self-pairs (A→A = 0°) | --- ### `HarmogramChartDomain` and `HarmogramTraceFamily` constraints | `chart_domain` | Compatible `trace_family` | |---|---| | `DYNAMIC_SKY_ONLY_TRACE` | `DYNAMIC_ZERO_ARIES_PARTS` | | `TRANSIT_TO_NATAL_TRACE` | `TRANSIT_TO_NATAL_ZERO_ARIES_PARTS` | | `DIRECTED_OR_PROGRESSED_TRACE` | `DIRECTED_TO_NATAL_ZERO_ARIES_PARTS` or `PROGRESSED_TO_NATAL_ZERO_ARIES_PARTS` | --- ### Example: natal chart H4 spectral analysis ```python 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 ```python # 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_`, where `` 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: ```python 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 ```python 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 ```python 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, ) ``` | Function | Signature | Description | |---|---|---| | `jd_from_datetime` | `(dt: datetime) → float` | timezone-aware datetime → UTC-coded civil JD; naïve datetimes raise `ValueError` | | `datetime_from_jd` | `(jd: float) → datetime` | UTC-coded civil JD → UTC datetime | | `julian_day` | `(year, month, day, hour=0.0) → float` | Calendar date → JD | | `calendar_from_jd` | `(jd: float) → CalendarDateTime` | JD → BCE-safe calendar breakdown | | `calendar_datetime_from_jd` | `(jd: float) → CalendarDateTime` | Alias for `calendar_from_jd` | | `format_jd_utc` | `(jd: float) → str` | Human-readable UTC string | | `safe_datetime_from_jd` | `(jd: float) → datetime \| None` | Returns None for out-of-range JDs | | `greenwich_mean_sidereal_time` | `(jd_ut: float) → float` | GMST in degrees | | `local_sidereal_time` | `(jd_ut, longitude, dpsi=None, obliq=None) → float` | LAST in degrees | | `delta_t` | `(year: float) → float` | ΔT in seconds for a decimal year | ### `CalendarDateTime` fields | Field | Type | Description | |---|---|---| | `year` | `int` | Proleptic Gregorian year (negative for BCE) | | `month` | `int` | Month (1–12) | | `day` | `int` | Day (1–31) | | `hour` | `float` | Decimal UT hour | | `is_bce` | `bool` | True when year < 1 (BCE convention) | ```python 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 ```python 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 ```python 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 ```python # 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 class | Module | Key parameters | |---|---|---| | `AspectPolicy` | aspects | `orb_table`, `min_tier`, `include_minor`, `motion_threshold` | | `HousePolicy` | houses | `polar_fallback`, `unknown_system` | | `DignityComputationPolicy` | dignities | essential 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 | | `VedicDignityPolicy` | vedic dignities | planetary friendship and compound-relationship policy | | `AstrodynePolicy` | Church of Light Astrodynes | fixed one-degree dignity band, magnitude parallels, Mercury two-stage orb, mutual-reception bonus | | `LotsComputationPolicy` | lots | `reversal_kind`, `derived_reference`, `external_reference` | | `ProgressionComputationPolicy` | progressions | `time_key`, `direction`, `house_frame` | | `TransitComputationPolicy` | transits | `search`, `return_search`, `syzygy_search` | | `TransitSearchPolicy` | transits | `step_days`, `max_iterations`, `exact_threshold` | | `SynastryComputationPolicy` | synastry | `aspect_policy`, `overlay_policy`, `composite_policy`, `davison_policy` | | `JaiminiPolicy` | jaimini | karaka assignment and tie-break policy | | `PanchangaPolicy` | panchanga | panchanga computation and classification policy | | `AshtottariPolicy` | alternate dasha | Ashtottari year-basis and sequence policy | | `YoginiPolicy` | alternate dasha | Yogini year-basis and sequence policy | | `AshtakavargaPolicy` | ashtakavarga | shodhana and sign-strength interpretation policy | | `ShadbalaPolicy` | shadbala | sufficiency thresholds and component interpretation policy | | `PatternComputationPolicy` | patterns | `selection`, `stellium`, `orb_factor`, `dominant_only` | | `TimelordComputationPolicy` | timelords | `firdaria_year_policy`, `zr_year_policy` | | `VimshottariComputationPolicy` | dasha | `year_policy`, `ayanamsa_policy` | | `FixedStarComputationPolicy` | fixed_stars | `lookup_policy`, `heliacal_search_policy` | | `VarStarPolicy` | variable_stars | `eclipse_threshold` | | `UnifiedStarComputationPolicy` | stars | `merge_policy` | | `SothicComputationPolicy` | sothic | `calendar_policy`, `heliacal_policy`, `epoch_policy` | ### Doctrine constants ```python 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. ```python 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. The former package-level `[stub]` markers have been retired: the listed submodules are implemented surfaces. Individual operations may still fail closed when a required resource or explicitly unsupported product is requested. --- ### `moira.sky.time` — Time Systems and ΔT ```python 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** | Function | Signature | Description | |---|---|---| | `julian_day` | `(year, month, day, hour=0.0) → float` | Calendar date → JD | | `calendar_from_jd` | `(jd: float) → CalendarDateTime` | JD → BCE-safe calendar breakdown | | `calendar_datetime_from_jd` | `(jd: float) → CalendarDateTime` | Alias for `calendar_from_jd` | | `jd_from_datetime` | `(dt: datetime) → float` | timezone-aware datetime → UTC-coded civil JD; naïve datetimes raise `ValueError` | | `decimal_year` | `(year, month=1) → float` | NASA-compatible month-midpoint decimal year | | `decimal_year_from_jd` | `(jd: float) → float` | JD → NASA-compatible month-midpoint decimal year | | `centuries_from_j2000` | `(jd_tt: float) → float` | Julian centuries from J2000.0 | **Time scale conversions** | Function | Signature | Description | |---|---|---| | `utc_to_tt` | `(jd_utc: float) → float` | UTC → TT through TAI and the leap-second table | | `utc_to_ut1` | `(jd_utc: float) → float` | UTC → UT1 through admitted DUT1 data, with explicit historical/fallback policy | | `ut_to_tt` | `(jd_ut: float) → float` | UT1 → Terrestrial Time (adds ΔT) | | `tt_to_ut` | `(jd_tt: float) → float` | TT → UT1 | | `tt_to_tdb` | `(jd_tt: float) → float` | TT → 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** | Function | Signature | Description | |---|---|---| | `earth_rotation_angle` | `(jd_ut: float) → float` | ERA in degrees | | `greenwich_mean_sidereal_time` | `(jd_ut: float) → float` | GMST in degrees | | `apparent_sidereal_time` | `(jd_ut: float) → float` | GAST in degrees | | `apparent_sidereal_time_at` | `(jd_ut, dpsi, obliq) → float` | GAST with supplied nutation terms | | `local_sidereal_time` | `(jd_ut, longitude, dpsi=None, obliq=None) → float` | LAST in degrees | **ΔT — standard** | Function | Signature | Description | |---|---|---| | `delta_t` | `(year: float) → float` | Source-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** ```python bd = delta_t_breakdown(year) dist = delta_t_distribution(year) ``` | Function | Signature | Description | |---|---|---| | `delta_t_breakdown` | `(year: float) → DeltaTBreakdown` | Source/scenario total in the stable additive accounting vessel | | `delta_t_distribution` | `(year: float) → DeltaTDistribution` | Mean plus source-error or uncalibrated policy scale in a normal-shaped convenience vessel | | `secular_trend` | `(year: float) → float` | Compatibility helper for the declared future curvature scenario | | `core_delta_t` | `(year: float) → float` | Compatibility surface; zero while the C04 proxy is quarantined | | `cryo_delta_t` | `(year: float) → float` | Compatibility surface; zero while the GRACE derivative is quarantined | | `fluid_lowfreq` | `(year: float) → float` | Compatibility 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 a table diagnostic, not a scenario input. 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 | Field | Type | Description | |---|---|---| | `year` | `float` | Decimal year requested | | `total` | `float` | Total ΔT in seconds | | `secular` | `float` | Declared curvature baseline; not a measured causal decomposition in source-backed eras | | `core` | `float` | Reserved compatibility component; currently zero | | `cryo` | `float` | Reserved compatibility component; currently zero | | `fluid` | `float` | Reserved compatibility component; currently zero | | `bridge` | `float` | Explicit arithmetic reconciliation from the curvature baseline to the admitted total or boundary-conditioned scenario | | `residual` | `float` | Reserved compatibility component; currently zero | | `era` | `str` | Compatibility 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 | Member | Type | Description | |---|---|---| | `year` | `float` | Decimal year requested | | `mean` | `float` | Same mean returned by the physical-policy total | | `sigma` | `float` | HPIERS source-error scale where aligned with that source; otherwise a modern floor or uncalibrated future policy scale in seconds | | `variance` | `float` | `sigma²` | | `pdf(delta_t_seconds)` | `float` | Normal-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 core-anomaly persistence. --- ### `moira.sky.position` — Astrometric Correction Pipeline ```python 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: | Stage | Function | Description | |---|---|---| | 1 | `apply_light_time(body, jd_tt, reader, earth_ssb, barycentric_fn)` | Iterative geometric delay; returns `(corrected_xyz: Vec3, light_time_days: float)` | | 2 | `apply_aberration(xyz, observer_vel)` | Relativistic stellar aberration due to observer velocity | | 3 | `apply_deflection(xyz, jd_tt, reader)` | Gravitational light deflection by Sun, Jupiter, and Saturn | | 4 | `apply_frame_bias(xyz)` | IAU 2006 FK5/ICRS frame alignment (~17 mas offset) | | 5 | `topocentric_correction(xyz_gcrs, jd_tt, lat, lon, elev_m=0.0)` | WGS-84 observer parallax shift | Atmospheric refraction (applied after stage 5): | Function | Signature | Description | |---|---|---| | `apply_refraction(alt_deg, policy)` | `→ float` | Apply the policy-selected refraction model to an altitude in degrees | | `atmospheric_refraction(altitude_deg)` | `→ float` | Bennett (1982) refraction in arcminutes — fast, altitude only | | `atmospheric_refraction_extended(altitude_deg, temperature_C, pressure_mbar, humidity, wavelength_um, elevation_m)` | `→ float` | Full physical refraction model | --- ### `moira.sky.frames` — Coordinate Frame Transforms ```python 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** | Function | Signature | Description | |---|---|---| | `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) → float` | Ecliptic 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** | Function | Signature | Description | |---|---|---| | `precession_matrix_equatorial` | `(jd_tt: float) → Mat3` | IAU 2006 precession rotation matrix | | `nutation_matrix_equatorial` | `(jd_tt: float) → Mat3` | IAU 2000A nutation rotation matrix | | `rot_x` / `rot_y` / `rot_z` | `(angle_rad: float) → Mat3` | Single-axis rotation matrices (astronomical sign convention) | | `cotrans_sp` | `(xyz: Vec3, matrix: Mat3) → Vec3` | Apply a rotation matrix to a Cartesian triple | | `aberration_correction` | `(xyz: Vec3, observer_vel: Vec3) → Vec3` | Annual aberration (geometric model) | **Utility** | Function | Signature | Description | |---|---|---| | `normalize_degrees` | `(deg: float) → float` | Fold angle into [0, 360) | | `angular_distance` | `(a, b: float) → float` | Unsigned shortest arc between two angles (degrees) | | `signed_angular_distance` | `(a, b: float) → float` | Signed angular difference (degrees) | | `equation_of_time` | `(jd_tt: float) → float` | Solar time correction in minutes | **Horizon frame — batch** ```python 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 ```python 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, ) ``` | Function | Returns | Description | |---|---|---| | `planet_at(body, jd_ut, reader=None, ...)` | `PlanetData` | Geocentric ecliptic position | | `sky_position_at(body, jd_ut, observer_lat, observer_lon, observer_elev_m=0.0, reader=None)` | `SkyPosition` | Topocentric 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)` | `float` | Solar ecliptic longitude (geocentric, true-of-date) | | `planet_relative_to(body, jd_ut, reference)` | `PlanetData` | Body position measured from reference body | | `next_heliocentric_transit(body, jd_start, longitude)` | `float` | Next JD when body's heliocentric longitude equals target | | `heliocentric_planet_at(body, jd_ut, reader=None)` | `HeliocentricData` | Heliocentric 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)` | `SSBPosition` | SSB-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)` | `PlanetocentricData` | Geometric 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)` | `NodeData` | IERS 2003 mean lunar ascending node; true equinox of date by default | | `true_node(jd_ut, reader=None)` | `NodeData` | True (osculating) ascending node; speed is the differentiated true-of-date longitude, not the mean-node rate | | `mean_lilith(jd_ut, *, nutation=True)` | `NodeData` | IERS 2003 mean lunar apogee (F+Ω−l+180°); true equinox of date by default, raw mean equinox with `nutation=False` | | `true_lilith(jd_ut)` | `NodeData` | True Lilith (osculating apogee); speed is the differentiated true-of-date longitude, not the mean-apogee rate | | `next_moon_node_crossing(jd_start, reader=None, ascending=True)` | `float` | Next Moon ecliptic crossing (JD UT) | | `nodes_and_apsides_at(jd_ut)` | `NodesAndApsides` | Combined 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 ```python 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, LunarObserver, LunarOrientation, LunarOrientationSource, lunar_orientation_at, ) ``` **Phase quantities** | Function | Signature | Description | |---|---|---| | `phase_angle(body, jd_ut)` | `→ float` | Sun–Planet–Earth angle in degrees | | `illuminated_fraction(phase_angle)` | `→ float` | Illuminated disk fraction (0.0–1.0) | | `elongation(body, jd_ut)` | `→ float` | Angular separation from the Sun (0°–180°) | | `synodic_phase_angle(body1, body2, jd_ut)` | `→ float` | Ecliptic phase angle between any two bodies (0°–360°) | | `synodic_phase_state(angle_deg)` | `→ str` | Coarse phase label (e.g. "waxing", "full", "waning") | | `angular_diameter(body, jd_ut)` | `→ float` | Apparent angular diameter in arcseconds | | `apparent_magnitude(body, jd_ut)` | `→ float` | Apparent visual magnitude (V band) | | `lunar_orientation_at(jd_ut, *, observer=None, reader=None)` | `→ LunarOrientation` | Geocentric or WGS-84 topocentric total apparent lunar libration and disc orientation | **Lunar orientation** `LunarOrientation` exposes east-positive `sub_observer_longitude_east_deg` and `sub_solar_longitude_east_deg` in `[-180, 180)`, north-positive surface latitudes, the solar colongitude, and axis/bright-limb position angles in `[0, 360)`. The `libration_longitude_deg` and `libration_latitude_deg` properties are exact aliases of the sub-observer coordinates. Position angles increase eastward/counter-clockwise from true-of-date celestial north in a north-up view. The bright-limb angle is `None` only when its sky-plane projection is singular. Pass `LunarObserver(latitude_deg, longitude_deg, elevation_m=0.0)` for topocentric diurnal libration; omit it for the geocentre. Direct calls require an explicit DE441/LE441 reader or an active reader context. The result's `LunarOrientationSource` records translation/orientation identities, the resolved `MOON_ME_DE440_ME421` frame, exact PCK/FK digests, descriptor-derived coverage, and light-time/time-scale policy. Missing, mismatched, or out-of-coverage orientation resources raise the corresponding named `LunarOrientationResource*` error rather than selecting a fallback. **Phenomena search** | Function | Signature | Description | |---|---|---| | `greatest_elongation(body, jd_start)` | `→ PhenomenonEvent` | Next greatest elongation of Mercury or Venus | | `perihelion(body, jd_start)` | `→ PhenomenonEvent` | Next perihelion passage | | `aphelion(body, jd_start)` | `→ PhenomenonEvent` | Next aphelion passage | | `next_moon_phase(phase_name, jd_start)` | `→ float` | Next 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)` | `→ PhenomenonEvent` | Next geocentric conjunction | | `conjunctions_in_range(body1, body2, jd_start, jd_end)` | `→ list[PhenomenonEvent]` | All conjunctions in a date range | | `resonance(body1, body2)` | `→ OrbitalResonance` | Best harmonic ratio P/Q between sidereal periods | | `planet_phenomena_at(body, jd_ut)` | `→ PlanetPhenomena` | Full 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 ```python 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. | Function | Signature | Description | |---|---|---| | `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)]` | Legacy ecliptic coordinates of GC, NGP, GAC, SGP, and the historical M87-anchored SGC convention | | `galactic_position_of(body, jd_ut, ...)` | `→ GalacticPosition` | `GalacticPosition` for one body | | `all_galactic_positions(chart_lons, jd_ut, ...)` | `→ list[GalacticPosition]` | `GalacticPosition` for all bodies in a chart | Named compatibility points: **GC** (the galactic coordinate-frame origin, close to but distinct from Sagittarius A*), **NGP** (North Galactic Pole), **GAC** (Galactic Anti-Center), **SGP** (South Galactic Pole), and **SGC** (the legacy astrological convention anchored on M87, not the formal supergalactic longitude origin). Use `moira.cosmic_references` for typed identities and source/semantic receipts. --- ### `moira.sky.events` — Rise, Set, Transit, Twilight, and Stations ```python 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** | Function | Signature | Description | |---|---|---| | `find_phenomena(body, jd_day, lat, lon, policy)` | `→ dict` | Rise, 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)` | `→ float` | JD_UT of upper (or lower) meridian transit | | `twilight_times(jd_day, lat, lon)` | `→ TwilightTimes` | Civil / nautical / astronomical twilight table for a day and location | #### `TwilightTimes` fields | Field | Description | |---|---| | `civil_dusk`, `civil_dawn` | Sun at −6° | | `nautical_dusk`, `nautical_dawn` | Sun at −12° | | `astro_dusk`, `astro_dawn` | Sun at −18° | | `sunset`, `sunrise` | Limb at geometric horizon | | `solar_noon` | Upper meridian transit | **Stations** | Function | Signature | Description | |---|---|---| | `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)` | `→ StationEvent` | First station (SR or SD) after jd_start | | `is_retrograde(body, jd_ut)` | `→ bool` | True 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 ```python 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** ```python 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** | Surface | Exact signature | Returns | |---|---|---| | `EclipseCalculator` | `lunar_eclipse_visibility_map(jd_start, *, kind="any", backward=False, mode="native", sample_count=181)` | `LunarEclipseVisibilityMap` | | `Moira` facade | `lunar_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** | Surface | Exact signature | Returns | |---|---|---| | `EclipseCalculator` / `Moira` | `solar_global_circumstances(jd_start, *, kind="any", backward=False)` | `SolarEclipseGlobalCircumstances` | | `EclipseCalculator` / `Moira` | `solar_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` / `Moira` | `lunar_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** | Surface | Exact signature | Returns | |---|---|---| | `EclipseCalculator` | `solar_eclipse_footprint(jd_start, *, kind="any", backward=False, sample_count=181)` | `SolarEclipseVisibilityFootprint` | | `Moira` facade | `solar_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** | Vessel | Fields | |---|---| | `SolarEclipseFootprintPoint` | `jd_ut`, `latitude_deg`, `longitude_deg`; computed `datetime_utc` and BCE-safe `calendar_utc` | | `SolarEclipsePenumbralContact` | `kind`, `point` | | `SolarEclipseFootprintContacts` | required `p1`/`p4`, paired optional `p2`/`p3` | | `SolarEclipseLimitTrack` | `kind`, `component_id`, `segment_id`, immutable time-ordered `points` | | `SolarEclipseVisibilityFootprint` | `event`, `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** ```python 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_series`, `saros_index`, `saros_lunation_number`, `saros_cycle_position`, `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 ```python 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, ) ``` | Function | Signature | Description | |---|---|---| | `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)` | `→ OccultationPathGeometry` | Compatibility 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)` | `→ OccultationPathTopology` | Polar-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)` | `→ OccultationPathGeometry` | Compatibility 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)` | `→ OccultationPathTopology` | Polar-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)` | `→ GrazeProductTrack` | Full graze track across a latitude band | --- ### `moira.sky.visibility` — Observational Visibility Doctrine ```python from moira.sky.visibility import ( HeliacalEventKind, VisibilityTargetKind, LightPollutionClass, LightPollutionDerivationMode, ObserverAid, VisibilityCriterionFamily, LunarCrescentVisibilityClass, VisibilityExtinctionModel, VisibilityTwilightModel, MoonlightPolicy, ExtinctionCoefficient, ObserverVisibilityEnvironment, AtmosphericExtinctionAssessment, TwilightSkyBrightnessAssessment, PointSourceVisibilityThreshold, VisibilityPolicy, VisibilitySearchPolicy, VisibilityModel, HeliacalPolicy, LunarCrescentDetails, VisibilityAssessment, GeneralVisibilityEvent, PlanetHeliacalEvent, relative_optical_airmass, atmospheric_extinction, directional_twilight_sky_brightness, point_source_visibility_threshold, visibility_assessment, visual_limiting_magnitude, visibility_event, planet_heliacal_rising, planet_heliacal_setting, planet_acronychal_rising, planet_acronychal_setting, ) ``` | Function | Signature | Description | |---|---|---| | `relative_optical_airmass(apparent_altitude_deg)` | `→ float` | Kasten–Young (1989) relative optical air mass | | `atmospheric_extinction(apparent_altitude_deg, *, model, ...)` | `→ AtmosphericExtinctionAssessment` | Direct-beam extinction and transmission with explicit target/sky spectral coefficients | | `directional_twilight_sky_brightness(target_altitude_deg, sun_altitude_deg, separation_deg, *, extinction_coefficient_k)` | `→ TwilightSkyBrightnessAssessment` | Directional cloudless twilight contribution and validity state | | `point_source_visibility_threshold(background_nanolamberts, *, field_factor=2.0)` | `→ PointSourceVisibilityThreshold` | Crumey (2014) scotopic naked-eye threshold | | `visibility_assessment(body, jd_ut, lat, lon, *, policy=None)` | `→ VisibilityAssessment` | Single-epoch observability check | | `visual_limiting_magnitude(jd_ut, lat, lon, *, policy=None)` | `→ float` | Effective limiting magnitude for the observer/epoch | | `visibility_event(body, event_kind, jd_start, lat, lon, *, heliacal_policy=None, ...)` | `→ GeneralVisibilityEvent \| None` | Search for next heliacal event | | `planet_heliacal_rising(body, jd_start, lat, lon, ...)` | `→ PlanetHeliacalEvent \| None` | Morning first visibility | | `planet_heliacal_setting(body, jd_start, lat, lon, ...)` | `→ PlanetHeliacalEvent \| None` | Evening last visibility | | `planet_acronychal_rising(body, jd_start, lat, lon, ...)` | `→ PlanetHeliacalEvent \| None` | Evening first visibility | | `planet_acronychal_setting(body, jd_start, lat, lon, ...)` | `→ PlanetHeliacalEvent \| None` | Morning 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`. The physical point-source criterion exposes the full derivation on `VisibilityAssessment`: target extinction, directional dark/twilight/moon backgrounds, total sky brightness, point-source threshold, extinction-adjusted target magnitude, visibility margin, and an explicit criterion validity/reason. It does not turn these equations into a cloud, daylight, telescope, extended object, Sun/Moon, or physical event-search model. --- ## 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`. ```python 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 ```python from moira.vedic import ( UserDefinedAyanamsa, NakshatraPosition, nakshatra_of, all_nakshatras_at, # ayanamsa, tropical_to_sidereal, sidereal_to_tropical already in essentials ) ``` | Function | Signature | Description | |---|---|---| | `nakshatra_of(tropical_longitude, jd, ayanamsa_system=Ayanamsa.LAHIRI)` | `→ NakshatraPosition` | Nakshatra 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 | Field | Type | Description | |---|---|---| | `nakshatra` | `str` | Nakshatra name | | `nakshatra_index` | `int` | 0–26 | | `nakshatra_lord` | `str` | Dasha lord of this nakshatra | | `pada` | `int` | 1–4 | | `degrees_in` | `float` | Degrees into this nakshatra (0–13.333°) | | `sidereal_lon` | `float` | Sidereal 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) ```python 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, ) ``` | Function | Return | Description | |---|---|---| | `available_pancha_pakshi_profiles()` | `tuple[PanchaPakshiProfileDescriptor, ...]` | List public named profiles without selecting a default | | `pancha_pakshi_uromarisi_constitution_status()` | `PanchaPakshiUromarisiConstitutionStatus` | Return immutable SCP closure and admission metadata without exposing private historical/network research | | `pancha_pakshi_profile_info(profile_id)` | `PanchaPakshiProfileInfo` | Inspect admission, capabilities, source, locators, conflicts, and omissions | | `pancha_pakshi_identity_from_initial_vowel(profile_id, initial_vowel)` | `PanchaPakshiInitialVowelIdentity` | Resolve one explicitly listed aksara/query-or-name initial; not natal Moon identity | | `pancha_pakshi_schedule(profile_id, *, paksha, half, weekday)` | `PanchaPakshiSchedule` | Materialize one exact nominal fixed-clock schedule from explicit source labels | | `pancha_pakshi_first_eat_bird_mapping(profile_id, *, profile_paksha, half, weekday)` | `PanchaPakshiFirstEatBirdMapping` | Return 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)` | `PanchaPakshiAstronomicalPakshaInference` | Classify 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)` | `PanchaPakshiNakshatraBirdMapping` | Return 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)` | `PanchaPakshiNatalMoonIdentity` | Apply 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)` | `PanchaPakshiPaduBirdMapping` | Return 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)` | `PanchaPakshiSookshmaSelection` | Select 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)` | `PanchaPakshiScheduleSookshmaSelection` | Compose 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)` | `PanchaPakshiCivilTimeSookshmaSelection` | Route 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)` | `PanchaPakshiLocalSolarContext` | Resolve 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)` | `PanchaPakshiFixedClockMaterialization` | Anchor 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)` | `PanchaPakshiFixedClockCurrentCellSelection` | Resolve 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)` | `PanchaPakshiSolarProportionalMaterialization` | Map 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)` | `PanchaPakshiSolarProportionalCurrentCellSelection` | Resolve 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)` | `PanchaPakshiDirectedRelationship` | Return 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 vessel | Public contract | |---|---| | `PanchaPakshiAstronomicalPaksha` | Finite astronomical phase-half enum: `shukla` or `krishna`; it remains distinct from the profile-owned `purva`/`amara` enum | | `PanchaPakshiAstronomicalPakshaInferencePolicy` | Origin, 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 | | `PanchaPakshiAstronomicalPakshaInference` | Profile 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 vessel | Public contract | |---|---| | `PanchaPakshiNakshatraBirdMapping` | Pure source-table Paksha, nakshatra index/name, bird, direct-attestation status, declared verse precedence, one mapping locator, and profile provenance; no epoch or natal claim | | `PanchaPakshiNatalMoonIdentityPolicy` | Fixed 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 | | `PanchaPakshiNatalMoonIdentity` | All 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 vessel | Public contract | |---|---| | `PanchaPakshiPaduBirdMapping` | Explicit 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 vessel | Public contract | |---|---| | `PanchaPakshiFirstEatBirdMapping` | Explicit 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 vessel | Public contract | |---|---| | `PanchaPakshiSookshmaSelectorPolicy` | Mandatory policy ID, exact container and duration doctrine, half-open ownership, explicit no-default/no-Uromarisi-composition statuses, and two source locators | | `PanchaPakshiSookshmaInterval` | Ordinal, optional source-attested activity, and exact rational start, end, and duration | | `PanchaPakshiSookshmaSelection` | Explicit 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 vessel | Public contract | |---|---| | `PanchaPakshiSolarProportionalMaterialization` | Stage 2A `context`, immutable `policy`, TT/UT1 anchor and governing-half-end fields, `solar_half_duration_seconds_tt`, 25 `cells`, and profile-owned `provenance` | | `PanchaPakshiSolarProportionalMaterializationPolicy` | Explicit 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 | | `PanchaPakshiSolarProportionalCell` | Ordered 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 vessel | Public contract | |---|---| | `PanchaPakshiSolarProportionalCurrentCellSelection` | Complete governing Stage 2D `materialization`, immutable selection `policy`, requested TT witness, selected-only status, one non-null materialization member, and route-specific provenance | | `PanchaPakshiSolarProportionalCurrentCellSelectionPolicy` | Stage 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](PANCHA_PAKSHI_RESEARCH_STANDARD.md) 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 ```python 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, ) ``` | Function | Signature | Description | |---|---|---| | `panchanga_at(sun_tropical_lon, moon_tropical_lon, jd, ayanamsa_system='Lahiri', policy=None)` | `→ PanchangaResult` | Five Panchanga elements at a given JD | | `daily_panchanga(local_date, latitude, longitude, *, timezone, policy=None, reader=None)` | `→ DailyPanchangaResult` | Local sunrise-to-next-sunrise day, exact computed limb endings, selected policy and unavailable states | | `lunar_month_at(jd_ut1, *, policy=None, reader=None)` | `→ LunarMonthResult` | Solved surrounding lunations, Amanta/ordinary Purnimanta labels, intercalation evidence and explicit unavailable mappings | | `sankranti_at(jd_start, jd_end, reader=None)` | `→ SankrantiResult` | Solar ingress into each rashi in a date range | | `tithi_condition_profile(result)` | `→ TithiConditionProfile` | Tithi quality assessment | | `panchanga_profile(result)` | `→ PanchangaProfile` | Aggregate 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. `Moira.daily_panchanga(local_date, latitude, longitude, *, timezone, policy=None)` binds the facade's reader. `DailyPanchangaPolicy` selects a named ayanamsa, sunrise convention and bounded solver tolerance. Numerical event moments are explicit UT1 JDs with converted UTC/local timestamps. Daily coverage and an actual limb ending after next sunrise remain separate fields. See the [daily standard and worked request](DAILY_PANCHANGA_STANDARD.md) for all nine curated exports, source choices, strict REST contract and verification. #### `PanchangaResult` fields | Field | Type | Description | |---|---|---| | `tithi` | `int` | Lunar day (1–30) | | `tithi_name` | `str` | Name from `TITHI_NAMES` | | `paksha` | `TithiPaksha` | `Shukla` (waxing) or `Krishna` (waning) | | `yoga` | `int` | Yoga index (0–26) | | `yoga_name` | `str` | Name from `YOGA_NAMES` | | `yoga_class` | `YogaClass` | Auspicious / Inauspicious / Neutral | | `karana` | `int` | Karana index | | `karana_name` | `str` | Name from `KARANA_NAMES` | | `karana_type` | `KaranaType` | Fixed or Movable | | `vara` | `int` | Weekday (0 = Sunday) | | `vara_lord` | `VaraLordType` | Planetary ruler of the day | --- ### Vedic Dignities ```python from moira.vedic import ( vedic_dignity, planetary_relationships, dignity_condition_profile, chart_dignity_profile, VedicDignityResult, PlanetaryRelationship, VedicDignityPolicy, DignityConditionProfile, ChartDignityProfile, VedicDignityRank, CompoundRelationship, DignityTier, ) ``` | Function | Signature | Description | |---|---|---| | `vedic_dignity(planet, longitude, policy=None)` | `→ VedicDignityResult` | Essential Vedic dignity for a planet at a longitude | | `planetary_relationships(planet, jd_ut, policy=None)` | `→ PlanetaryRelationship` | Natural + temporary + compound friendship | | `dignity_condition_profile(planet, chart)` | `→ DignityConditionProfile` | Dignity assessment with tier classification | | `chart_dignity_profile(chart)` | `→ ChartDignityProfile` | Full 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 ```python 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`: | Function | Division | Traditional name | |---|---|---| | `hora` | D-2 | Hora chart | | `saptamsa` | D-7 | Children / fertility | | `navamsa` | D-9 | Marriage / dharma | | `dashamansa` | D-10 | Career / profession | | `dwadashamsa` | D-12 | Ancestors / parents | | `chaturthamsha` | D-4 | Property | | `shashthamsha` | D-6 | Enemies / health | | `ashtamsha` | D-8 | Longevity | | `shodashamsha` | D-16 | Vehicles, comforts | | `vimshamsha` | D-20 | Spirituality | | `chaturvimshamsha` | D-24 | Education, learning | | `saptavimshamsha` | D-27 | Strength | | `trimshamsa` | D-30 | Evils, illness | | `khavedamsha` | D-40 | Auspicious/inauspicious effects | | `akshavedamsha` | D-45 | All matters | | `shashtiamsha` | D-60 | Past life karma | --- ### Vimshottari Dasha ```python 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, ) ``` | Function | Signature | Description | |---|---|---| | `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)` | `→ DashaActiveLine` | Named active chain from `current_dasha(...)` output | | `dasha_condition_profile(period)` | `→ DashaConditionProfile` | Condition assessment for one `DashaPeriod` | | `dasha_sequence_profile(periods)` | `→ DashaSequenceProfile` | Aggregate profile for `vimshottari(...)` output | | `dasha_lord_pair(line)` | `→ DashaLordPair` | Mahadasha 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 ```python 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, ) ``` | Function | Signature | Description | |---|---|---| | `ashtottari(moon_lon, birth_jd, policy=None)` | `→ list[AlternateDashaPeriod]` | BPHS 108-year Ardra-first, 28-place Ashtottari sequence | | `yogini_dasha(moon_lon, birth_jd, policy=None)` | `→ list[AlternateDashaPeriod]` | BPHS 36-year add-three/remainder-eight Yogini sequence | `AshtottariPolicy` and `YoginiPolicy` control year-basis and ayanamsa policy. Ashtottari's exact Abhijit arc is the separately named traditional convention documented in `ALTERNATE_DASHAS_BACKEND_STANDARD.md`. --- ### Jaimini Karakas ```python 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, ) ``` | Function | Signature | Description | |---|---|---| | `jaimini_karakas(chart_longitudes, policy=None)` | `→ JaiminiKarakaResult` | Full 7- or 8-karaka assignment by degree | | `atmakaraka(chart_longitudes, policy=None)` | `→ KarakaAssignment` | Highest-degree planet (soul significator) | | `karaka_condition_profile(chart_longitudes, policy=None)` | `→ KarakaConditionProfile` | Dignity condition for each karaka | | `jaimini_chart_profile(chart_longitudes, policy=None)` | `→ JaiminiChartProfile` | Full chart karaka profile | `JaiminiPolicy` controls the 7-karaka vs. 8-karaka scheme and Rahu tie-break behaviour. --- ### Ashtakavarga ```python from moira.vedic import ( bhinnashtakavarga, ashtakavarga, transit_strength, sign_strength_profile, ashtakavarga_chart_profile, BhinnashtakavargaResult, AshtakavargaResult, SignStrengthProfile, AshtakavargaChartProfile, AshtakavargaPolicy, RekhaTier, REKHA_TABLES, ) ``` | Function | Signature | Description | |---|---|---| | `bhinnashtakavarga(planet, chart_longitudes, policy=None)` | `→ BhinnashtakavargaResult` | Individual planet's bindus per sign (8-source table) | | `ashtakavarga(chart_longitudes, policy=None)` | `→ AshtakavargaResult` | Sarvashtakavarga — combined bindus per sign | | `transit_strength(planet, transit_lon, natal_chart_longitudes)` | `→ float` | Ashtakavarga bindus for a transit position | | `sign_strength_profile(chart_longitudes, policy=None)` | `→ SignStrengthProfile` | Per-sign bindu strengths with `RekhaTier` labels | | `ashtakavarga_chart_profile(chart_longitudes, policy=None)` | `→ AshtakavargaChartProfile` | Full chart Ashtakavarga profile | --- ### Shadbala ```python 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, ) ``` | Function | Signature | Description | |---|---|---| | `shadbala(sidereal_longitudes, planet_speeds, houses, jd, tithi_number, vara_lord, is_day, ayanamsa_system='Lahiri', hora_lord=None, planet_latitudes=None)` | `→ ShadbalaResult` | Full six-fold strength for the seven classical planets | | `sthana_bala(planet, sidereal_lon, houses, jd, ayanamsa_system='Lahiri')` | `→ SthanaBala` | Positional strength components | | `dig_bala(planet, sidereal_lon, houses, jd, ayanamsa_system='Lahiri')` | `→ float` | Directional strength | | `kala_bala(planet, sidereal_lon, sun_sidereal_lon, jd, tithi_number, is_day, vara_lord, planet_speeds, ...)` | `→ KalaBala` | Temporal strength components | | `chesta_bala(planet, speed, planet_sidereal_lon=None, mandoccha_sidereal_lon=None)` | `→ float` | Motional strength | | `drig_bala(planet, sidereal_longitudes)` | `→ float` | Aspectual strength | | `hora_lord_at(birth_jd, sunrise_jd)` | `→ str` | Planetary hour ruler from a caller-supplied sunrise | | `shadbala_condition_profile(planet_result)` | `→ ShadbalaConditionProfile` | Condition assessment for one `PlanetShadbala` | | `shadbala_chart_profile(result)` | `→ ShadbalaChartProfile` | Aggregate 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 | Tier | Meaning | Examples | |---|---|---| | **Frozen** | Signature and semantics will not change without a major version bump | `Moira`, `Chart`, `Body`, `HouseSystem`, `AspectData`, `HouseCusps`, all `__all__` members | | **Provisional** | None currently designated | — | --- ## Appendix B — Kernel & Reader ```python 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 | Property | Value | |---|---| | Ephemeris | JPL DE441 | | Date coverage | 13 200 BC → 17 191 AD | | Coordinate frame | Geocentric ecliptic, tropical (J2000.0 mean equinox) | | Sidereal option | Any ayanamsa via `ayanamsa()` / `tropical_to_sidereal()` | | ΔT model | Hybrid IERS/Morrison-Stephenson/extrapolation | | Nutation | IAU 2000A (1365 terms) | | Obliquity | Laskar 1986 / IAU 2006 combined | | Topocentric Moon | Parallax-corrected RA/Dec and altitude | | Fixed stars | Sovereign registry (`star_registry.csv` + JSON sidecars) | | Variable stars | 20-star classical catalog with period/epoch/magnitude data |