API Reference

Complete auto-generated API documentation from docstrings.


Main Package

The main stellium package exports the most commonly used classes and functions.

Builders

class stellium.ChartBuilder(datetime, location, native=None)[source]

Bases: object

Fluent builder for creating astrological charts.

Example:

chart = (
    ChartBuilder.from_native(native)
    .with_ephemeris(SwissEphemeris())
    .with_house_systems([PlacidusHouses(), WholeSignHouses()])
    .with_aspects(ModernAspectEngine())
    .with_orbs(SimpleOrbEngine())
    .calculate()
)
add_analyzer(analyzer)[source]

Adds a data analyzer to the calculation pipeline. (e.g., PatternDetector)

Return type:

ChartBuilder

add_component(component)[source]

Add an additional calculation component (e.g. ArabicParts).

Return type:

ChartBuilder

add_house_system(engine)[source]

Adds an additional house engine to the calculation list. (e.g., to calculate Placidus and Whole Sign)

Return type:

ChartBuilder

bazi()[source]

Calculate the BaZi (Four Pillars / 八字) chart directly from the builder.

Skips Western chart calculation entirely — uses the already-resolved datetime and location to compute Chinese Four Pillars.

Returns:

A BaZiChart with all four pillars, ready for analysis.

Example:

bazi = ChartBuilder.from_details("1994-01-06 11:47", "Palo Alto, CA").bazi()
print(bazi.hanzi)           # Eight characters
print(bazi.strength())      # Strength analysis
calculate()[source]

Execute all calculations and return the final chart.

Return type:

CalculatedChart | UnknownTimeChart

Returns:

CalculatedChart with all calculated data, or UnknownTimeChart if time_unknown flag is set

classmethod from_details(datetime_input, location_input, *, name=None, time_unknown=False)[source]

Create a ChartBuilder from datetime and location (convenience method).

This method accepts flexible datetime and location inputs, creates a Native object internally, and returns a ready-to-configure ChartBuilder.

Parameters:
  • datetime_input (str | datetime | dict) – Datetime as string, datetime object, or dict - String: “2024-11-24 14:30”, “11/24/2024 2:30 PM”, etc. - datetime: Any datetime object (naive will be localized to location) - dict: {“year”: 2024, “month”: 11, “day”: 24, “hour”: 14, “minute”: 30}

  • location_input (str | tuple[float, float] | dict) – Location as string, (lat, lon) tuple, or dict - String: “Palo Alto, CA” (will be geocoded) - Tuple: (37.4419, -122.1430) - dict: {“latitude”: 37.4419, “longitude”: -122.1430, “name”: “Palo Alto”}

  • name (str | None) – Optional name of the person or event (for display purposes)

  • time_unknown (bool) – If True, creates an UnknownTimeChart (no houses/angles, Moon shown as range, time normalized to noon)

Return type:

ChartBuilder

Returns:

ChartBuilder instance ready to configure

Examples

>>> # Simple string inputs
>>> chart = ChartBuilder.from_details(
...     "1994-01-06 11:47",
...     "Palo Alto, CA"
... ).calculate()
>>>
>>> # With a name
>>> chart = ChartBuilder.from_details(
...     "1994-01-06 11:47",
...     "Palo Alto, CA",
...     name="Kate Louie"
... ).calculate()
>>>
>>> # Unknown birth time
>>> chart = ChartBuilder.from_details(
...     "1994-01-06",
...     "Palo Alto, CA",
...     name="Someone",
...     time_unknown=True
... ).calculate()
classmethod from_native(native)[source]

Create a new ChartBuilder from a Native object.

This is the primary factory method.

Return type:

ChartBuilder

classmethod from_notable(name)[source]

Create a ChartBuilder from the notable registry by name.

This is a convenience method that looks up a famous birth or event from the curated registry and creates a chart for it.

The notable’s name is automatically set on the chart for display purposes.

Parameters:

name (str) – Name of person or event (case-insensitive)

Return type:

ChartBuilder

Returns:

ChartBuilder instance ready to build, with name pre-set

Raises:

ValueError – If name not found in registry

Example

>>> chart = ChartBuilder.from_notable("Albert Einstein").calculate()
>>> chart = ChartBuilder.from_notable("marie curie").calculate()
with_aspects(engine=None)[source]

Set the aspect calculation engine.

Return type:

ChartBuilder

with_cache(cache=None, enabled=True, cache_dir='.cache', max_age_seconds=86400)[source]

Configure caching for this chart calculation.

Parameters:
  • cache (Cache | None) – Custom cache instance (creates new one if None)

  • enabled (bool) – Whether to enable caching

  • cache_dir (str) – Cache directory

  • max_age_seconds (int) – Maximum cache age

Return type:

ChartBuilder

Returns:

Self for chaining

Examples

# Disable caching for this chart chart = ChartBuilder.from_native(native).with_cache(enabled=False).calculate()

# Use custom cache directory chart = ChartBuilder.from_native(native).with_cache(cache_dir=”/tmp/my_cache”).calculate()

# Use shared cache instance my_cache = Cache(cache_dir=”/shared/cache”) chart1 = ChartBuilder.from_native(n1).with_cache(cache=my_cache).calculate() chart2 = ChartBuilder.from_native(n2).with_cache(cache=my_cache).calculate()

with_config(config)[source]

Set the calculation configuration (which objects to find).

Return type:

ChartBuilder

with_declination_aspects(orb=1.0, include_types=None)[source]

Enable declination aspect calculation (Parallel/Contraparallel).

Declination aspects are based on equatorial coordinates rather than ecliptic longitude. They use a tighter orb (default 1.0°) than longitude-based aspects.

  • Parallel: Two bodies at the same declination (same hemisphere). Interpreted like a conjunction.

  • Contraparallel: Two bodies at equal declination but opposite hemispheres. Interpreted like an opposition.

Parameters:
  • orb (float) – Maximum orb in degrees (default 1.0°, range 1.0-1.5° typical)

  • include_types (set | None) – Which ObjectTypes to include. Default: PLANET, NODE. Can also include ANGLE, ASTEROID, POINT.

Return type:

ChartBuilder

Returns:

Self for chaining

Example

>>> chart = (ChartBuilder.from_native(native)
...     .with_aspects()
...     .with_declination_aspects(orb=1.0)
...     .calculate())
>>> for asp in chart.declination_aspects:
...     print(asp.description)
>>> parallels = chart.get_parallels()
>>> contraparallels = chart.get_contraparallels()
with_ephemeris(engine)[source]

Set the ephemeris engine.

Return type:

ChartBuilder

with_heliocentric()[source]

Use heliocentric (Sun-centered) coordinates.

In a heliocentric chart, positions are calculated as seen from the Sun rather than Earth. This changes the chart significantly:

  • Earth appears as a planet (replacing the Sun)

  • Sun is removed (it’s the center point)

  • Lunar nodes and apogees are removed (Earth-relative concepts)

  • Moon is kept (still orbits Earth, has heliocentric position)

  • Houses and angles are not calculated (Earth-horizon concepts)

Heliocentric charts are used in: - Financial astrology (market timing) - Some modern experimental techniques - Scientific/astronomical contexts

Return type:

ChartBuilder

Returns:

Self for method chaining

Example

>>> chart = (ChartBuilder.from_native(native)
...     .with_heliocentric()
...     .calculate())
>>> earth = chart.get_object("Earth")
>>> print(earth.sign_position)  # Where Earth is from the Sun's view
with_house_systems(engines)[source]

Replaces the entire list of house engines (eg - to calculate only Whole Sign)

Return type:

ChartBuilder

with_name(name)[source]

Set the chart name (for display purposes).

Parameters:

name (str) – Name to display on the chart (e.g., person’s name, event name)

Return type:

ChartBuilder

Returns:

Self for method chaining

Example

>>> chart = (ChartBuilder.from_native(native)
...     .with_name("John Doe")
...     .calculate())
with_orbs(engine=None)[source]

Set the orb calculation engine.

Return type:

ChartBuilder

with_sidereal(ayanamsa='lahiri')[source]

Use sidereal zodiac for calculations.

The sidereal zodiac is based on fixed star positions, unlike the tropical zodiac which is based on the seasons. Different ayanamsa systems represent different methods of calculating the offset between tropical and sidereal.

Parameters:

ayanamsa (str) – The ayanamsa system to use. Common options: - “lahiri” (default) - Indian government standard, most common for Vedic - “fagan_bradley” - Primary Western sidereal - “raman” - B.V. Raman’s system, popular in South India - “krishnamurti” - Used in KP system - “yukteshwar” - Sri Yukteshwar’s system See stellium.core.ayanamsa.list_ayanamsas() for all options

Return type:

ChartBuilder

Returns:

Self for method chaining

Example

>>> # Vedic-style chart with Lahiri ayanamsa
>>> chart = (ChartBuilder.from_native(native)
...     .with_sidereal("lahiri")
...     .calculate())
>>>
>>> # Western sidereal with Fagan-Bradley
>>> chart = (ChartBuilder.from_native(native)
...     .with_sidereal("fagan_bradley")
...     .calculate())
with_tnos()[source]

Include Trans-Neptunian Objects in the calculation.

Adds the major TNOs: - Eris (dwarf planet, discord) - Sedna (isolation, deep healing) - Makemake (resourcefulness, manifestation) - Haumea (rebirth, fertility) - Orcus (oaths, consequences) - Quaoar (creation, harmony)

Note: TNOs require additional Swiss Ephemeris asteroid files (se1 files) to be present in your ephemeris data directory. Download them from: https://www.astro.com/ftp/swisseph/ephe/

Example:

chart = ChartBuilder.from_native(native).with_tnos().calculate()
Return type:

ChartBuilder

with_tropical()[source]

Use tropical zodiac for calculations (default).

The tropical zodiac is based on the seasons, with 0° Aries aligned to the March equinox. This is the standard system used in Western astrology.

This method is included for explicitness - tropical is already the default, so you only need to call this if you want to override a previous .with_sidereal() call.

Return type:

ChartBuilder

Returns:

Self for method chaining

Example

>>> # Explicit tropical (same as default)
>>> chart = (ChartBuilder.from_native(native)
...     .with_tropical()
...     .calculate())
>>>
>>> # Override previous sidereal setting
>>> chart = (ChartBuilder.from_native(native)
...     .with_sidereal("lahiri")
...     .with_tropical()  # Back to tropical
...     .calculate())
with_unknown_time()[source]

Mark this chart as having unknown birth time.

When birth time is unknown: - Time is normalized to noon for planet calculations - Houses and angles will NOT be calculated - Moon will include a range showing possible positions throughout the day - The resulting chart is an UnknownTimeChart (subclass of CalculatedChart)

Return type:

ChartBuilder

Returns:

Self for method chaining

Example

>>> chart = (ChartBuilder
...     .from_details("1994-01-06", "Palo Alto, CA")
...     .with_unknown_time()
...     .calculate())
>>> isinstance(chart, UnknownTimeChart)
True
>>> chart.moon_range.arc_size
13.5  # Moon travels ~13.5° that day
with_uranian()[source]

Include Hamburg/Uranian hypothetical planets and points in the calculation.

Adds the 8 transneptunian points (TNPs) used in Uranian astrology: - Cupido (family, groups, art, community) - Hades (decay, the past, what’s hidden) - Zeus (leadership, fire, directed energy) - Kronos (authority, expertise, high position) - Apollon (expansion, science, commerce, success) - Admetos (depth, stagnation, raw materials) - Vulkanus (immense power, force, intensity) - Poseidon (spirituality, enlightenment, clarity)

Also adds the Aries Point (0° Aries), a fundamental reference point in Uranian astrology representing worldly manifestation and the intersection of personal and collective.

These are hypothetical planets developed by Alfred Witte and the Hamburg School of Astrology.

Example:

# Just Uranian planets
chart = ChartBuilder.from_native(native).with_uranian().calculate()

# Full Uranian setup (TNOs + TNPs)
chart = ChartBuilder.from_native(native).with_tnos().with_uranian().calculate()
Return type:

ChartBuilder

class stellium.ComparisonBuilder(chart1, comparison_type, chart1_label='Native')[source]

Bases: object

Fluent builder for creating Comparison objects.

Provides convenient construction methods for both synastry and transits:

For synastry:
comp = ComparisonBuilder.from_native(chart1)

.with_partner(chart2) .calculate()

For transits:
comp = ComparisonBuilder.from_native(natal_chart)

.with_transit(transit_datetime, transit_location) .calculate()

classmethod arc_direction(natal_data, *, target_date=None, age=None, arc_type='solar_arc', rulership_system='traditional', natal_label='Natal', directed_label='Directed')[source]

Create an arc direction comparison (natal vs directed chart).

Arc directions move ALL points by the same angular distance, preserving natal relationships. This differs from progressions where each planet moves at its own rate.

Arc types supported:
  • “solar_arc”: Arc = progressed Sun - natal Sun (~1°/year actual)

  • “naibod”: Arc = 0.9856° × years (mean solar motion)

  • “lunar”: Arc = progressed Moon - natal Moon (~12-13°/year)

  • “chart_ruler”: Arc based on planet ruling the Ascendant sign

  • “sect”: Day charts use solar arc, night charts use lunar arc

  • Any planet name (e.g., “Mars”, “Venus”): Uses that planet’s arc

Parameters:
  • natal_data (CalculatedChart | Native | tuple[str | datetime | dict, str | tuple[float, float] | dict]) – Natal chart data (Native, CalculatedChart, or tuple)

  • target_date (str | datetime | None) – Target date for directions (either this or age required)

  • age (float | None) – Age in years (alternative to target_date)

  • arc_type (str) – Type of arc to use (see above)

  • rulership_system (Literal['traditional', 'modern']) – “traditional” or “modern” (for chart_ruler arc)

  • natal_label (str) – Label for natal chart (default: “Natal”)

  • directed_label (str) – Label for directed chart (default: “Directed”)

Return type:

ComparisonBuilder

Returns:

ComparisonBuilder instance ready to configure and calculate

Examples

>>> # Solar arc directions at age 30
>>> directed = ComparisonBuilder.arc_direction(
...     natal_chart, age=30, arc_type="solar_arc"
... ).calculate()
>>> # Naibod arc directions to a specific date
>>> directed = ComparisonBuilder.arc_direction(
...     natal_chart, target_date="2025-06-15", arc_type="naibod"
... ).calculate()
>>> # Chart ruler arc (uses planet ruling ASC sign)
>>> directed = ComparisonBuilder.arc_direction(
...     natal_chart, age=30, arc_type="chart_ruler"
... ).calculate()
>>> # Sect-based arc (solar for day charts, lunar for night)
>>> directed = ComparisonBuilder.arc_direction(
...     natal_chart, age=30, arc_type="sect"
... ).calculate()
>>> # Mars arc directions
>>> directed = ComparisonBuilder.arc_direction(
...     natal_chart, age=30, arc_type="Mars"
... ).calculate()
calculate()[source]

Execute all calculations and return the final Comparison.

This method ensures that both charts have their internal aspects calculated before computing cross-chart aspects. If a chart doesn’t already have aspects, they will be calculated using the internal aspect engine configuration.

Return type:

Comparison

Returns:

Comparison object with all calculated data

classmethod compare(data1, data2, comparison_type, chart1_label='Chart 1', chart2_label='Chart 2')[source]

General method for creating any type of comparison.

This is the flexible method that accepts any combination of inputs and any comparison type. Convenience methods (.synastry(), .transit(), .progression()) are thin wrappers that call this method with appropriate defaults.

Parameters:
Return type:

ComparisonBuilder

Returns:

ComparisonBuilder instance ready to configure and calculate

Examples

>>> # With Native objects
>>> native1 = Native("1994-01-06 11:47", "Palo Alto, CA")
>>> native2 = Native("2000-01-01 17:00", "Seattle, WA")
>>> comparison = ComparisonBuilder.compare(native1, native2, "synastry").calculate()
>>>
>>> # With (datetime, location) tuples
>>> comparison = ComparisonBuilder.compare(
...     ("1994-01-06 11:47", "Palo Alto, CA"),
...     ("2000-01-01 17:00", "Seattle, WA"),
...     "synastry"
... ).calculate()
>>>
>>> # Mixed inputs
>>> comparison = ComparisonBuilder.compare(
...     native1,
...     ("2024-11-24 14:30", None),  # Uses chart1's location for transits
...     "transit"
... ).calculate()
classmethod from_native(native_chart, native_label='Native')[source]

Start building a comparison from a native chart.

Use this when you have a CalculatedChart already. Chain with .with_partner() or .with_transit()

Parameters:
  • native_chart (CalculatedChart) – The native/primary chart

  • native_label (str) – Label for the native chart

Return type:

ComparisonBuilder

Returns:

ComparisonBuilder instance

classmethod progression(natal_data, progressed_data=None, *, target_date=None, age=None, angle_method='quotidian', natal_label='Natal', progressed_label='Progressed')[source]

Create a progression comparison with auto-calculation support.

Secondary progressions use the symbolic equation “one day = one year.” To find progressed positions at age 30, look at where planets were 30 days after birth.

Can be called three ways: 1. Auto-calculate by target date: progression(natal, target_date=”2025-06-15”) 2. Auto-calculate by age: progression(natal, age=30) 3. Manual (legacy): progression(natal, progressed_chart)

Parameters:
  • natal_data (CalculatedChart | Native | tuple[str | datetime | dict, str | tuple[float, float] | dict]) – Natal chart data (Native, CalculatedChart, or (datetime, location) tuple)

  • progressed_data (CalculatedChart | Native | tuple[str | datetime | dict, str | tuple[float, float] | dict] | None) – Optional pre-calculated progressed chart (for backwards compatibility)

  • target_date (str | datetime | None) – Target date for progression (triggers auto-calculation)

  • age (float | None) – Age in years for progression (alternative to target_date)

  • angle_method (Literal['quotidian', 'solar_arc', 'naibod']) – How to progress angles: - “quotidian” (default): Actual daily motion from Swiss Ephemeris - “solar_arc”: Angles progress at rate of progressed Sun - “naibod”: Angles progress at mean Sun rate (59’08”/year)

  • natal_label (str) – Label for natal chart (default: “Natal”)

  • progressed_label (str) – Label for progressed chart (default: “Progressed”)

Return type:

ComparisonBuilder

Returns:

ComparisonBuilder instance ready to configure and calculate

Examples

>>> # Auto-calculate by age (most convenient)
>>> prog = ComparisonBuilder.progression(natal, age=30).calculate()
>>>
>>> # Auto-calculate by target date
>>> prog = ComparisonBuilder.progression(
...     natal, target_date="2025-06-15"
... ).calculate()
>>>
>>> # With solar arc angles
>>> prog = ComparisonBuilder.progression(
...     natal, age=30, angle_method="solar_arc"
... ).calculate()
>>>
>>> # Legacy: explicit progressed chart (backwards compatible)
>>> progressed_chart = ChartBuilder.from_details(
...     "1994-02-05 11:47", "Palo Alto, CA"
... ).calculate()
>>> prog = ComparisonBuilder.progression(natal, progressed_chart).calculate()
classmethod synastry(data1, data2, chart1_label='Person 1', chart2_label='Person 2')[source]

Create a synastry comparison between two natal charts.

Synastry analyzes the relationship between two people by comparing their birth charts. This is a convenience method that calls .compare() with comparison_type=”synastry”.

Parameters:
Return type:

ComparisonBuilder

Returns:

ComparisonBuilder instance ready to configure and calculate

Examples

>>> # Simple string inputs
>>> comparison = ComparisonBuilder.synastry(
...     ("1994-01-06 11:47", "Palo Alto, CA"),
...     ("2000-01-01 17:00", "Seattle, WA")
... ).calculate()
>>>
>>> # With Native objects
>>> native1 = Native("1994-01-06 11:47", "Palo Alto, CA")
>>> native2 = Native("2000-01-01 17:00", "Seattle, WA")
>>> comparison = ComparisonBuilder.synastry(native1, native2).calculate()
>>>
>>> # With custom labels
>>> comparison = ComparisonBuilder.synastry(
...     ("1994-01-06 11:47", "Palo Alto, CA"),
...     ("2000-01-01 17:00", "Seattle, WA"),
...     chart1_label="Kate",
...     chart2_label="Partner"
... ).calculate()
classmethod transit(natal_data, transit_data, natal_label='Natal', transit_label='Transit')[source]

Create a transit comparison (natal chart vs current sky positions).

Transits analyze how current planetary positions interact with a natal chart for timing and prediction. This is a convenience method that calls .compare() with comparison_type=”transit”.

Parameters:
Return type:

ComparisonBuilder

Returns:

ComparisonBuilder instance ready to configure and calculate

Examples

>>> # Transit using natal location
>>> comparison = ComparisonBuilder.transit(
...     ("1994-01-06 11:47", "Palo Alto, CA"),
...     ("2024-11-24 14:30", None)  # Uses Palo Alto
... ).calculate()
>>>
>>> # Transit with different location
>>> comparison = ComparisonBuilder.transit(
...     ("1994-01-06 11:47", "Palo Alto, CA"),
...     ("2024-11-24 14:30", "New York, NY")
... ).calculate()
>>>
>>> # With Native object
>>> natal = Native("1994-01-06 11:47", "Palo Alto, CA")
>>> comparison = ComparisonBuilder.transit(
...     natal,
...     ("2024-11-24 14:30", None)
... ).calculate()
with_aspect_config(aspect_config)[source]

Set aspect configuration (orbs, which aspects, etc.).

Parameters:

aspect_config (AspectConfig) – AspectConfig instance

Return type:

ComparisonBuilder

Returns:

Self for chaining

with_aspect_engine(engine)[source]

Set the aspect engine for cross-chart aspects.

Parameters:

engine – AspectEngine instance

Return type:

ComparisonBuilder

Returns:

Self for chaining

with_internal_aspect_engine(engine)[source]

Set aspect engine for calculating internal (natal) aspects.

This engine will be used to calculate aspects within chart1 and chart2 if they don’t already have aspects calculated. If not set, defaults to ModernAspectEngine().

Parameters:

engine – AspectEngine instance for internal aspects

Return type:

ComparisonBuilder

Returns:

Self for chaining

with_internal_orb_engine(engine)[source]

Set orb engine for calculating internal (natal) aspects.

This engine will be used for orb allowances when calculating internal aspects in chart1/chart2. If not set, defaults to SimpleOrbEngine with registry defaults.

Parameters:

engine (OrbEngine) – OrbEngine instance for internal aspect orbs

Return type:

ComparisonBuilder

Returns:

Self for chaining

with_orb_engine(engine)[source]

Set the orb calculation engine for dynamic orb calculation.

OrbEngine will be used to calculate orbs for each planet pair dynamically (e.g., wider orbs for Sun/Moon, tighter for fast planets).

If provided, OrbEngine takes precedence over AspectConfig.orbs.

Examples

from stellium.engines.orbs import SimpleOrbEngine, LuminariesOrbEngine

# Simple engine with fixed orbs per aspect simple = SimpleOrbEngine({‘Conjunction’: 8.0, ‘Trine’: 8.0}) builder.with_orb_engine(simple)

# Luminaries engine (wider orbs for Sun/Moon) lum = LuminariesOrbEngine() builder.with_orb_engine(lum)

Parameters:

engine – OrbEngine instance implementing get_orb_allowance()

Return type:

ComparisonBuilder

Returns:

Self for chaining

with_other(other_input, location=None, other_label='Other', comparison_type=None)[source]

Generic method to add second chart.

This is a flexible alternative to with_partner() and with_transit().

Parameters:
Return type:

ComparisonBuilder

Returns:

Self for chaining

with_partner(partner_chart_or_datetime_or_native, location=None, partner_label='Partner')[source]

Add partner chart for synastry comparison.

Parameters:
  • partner_chart_or_datetime – Either a CalculatedChart or datetime

  • location (ChartLocation | None) – Required if providing datetime

  • partner_label (str) – Label for the partner chart

Return type:

ComparisonBuilder

Returns:

Self for chaining

with_transit(transit_datetime, location=None)[source]

Add transit chart for transit comparison.

Convenience method that calls with_other() with appropriate settings.

Parameters:
  • transit_datetime (datetime) – Transit datetime

  • location (ChartLocation | None) – Optional location (defaults to native’s location)

Return type:

ComparisonBuilder

Returns:

Self for chaining

without_house_overlays()[source]

Disable house overlay calculation.

Return type:

ComparisonBuilder

Returns:

Self for chaining

class stellium.ReturnBuilder(natal, planet, *, year=None, near_date=None, occurrence=None, location=None)[source]

Bases: object

Fluent builder for planetary return charts.

Uses composition: wraps ChartBuilder rather than inheriting from it. This allows us to: - Lazily calculate the return moment before building the inner chart - Inject return-specific metadata into the final chart - Delegate all chainable methods without tight coupling

Usage:
>>> # Solar Return for 2025
>>> sr = ReturnBuilder.solar(natal_chart, 2025).calculate()
>>>
>>> # Lunar Return near a date
>>> lr = ReturnBuilder.lunar(natal_chart, near_date="2025-03-15").calculate()
>>>
>>> # First Saturn Return
>>> saturn = ReturnBuilder.planetary(natal_chart, "Saturn", occurrence=1).calculate()
>>>
>>> # Relocated Solar Return
>>> sr_relocated = (
...     ReturnBuilder.solar(natal_chart, 2025, location="Tokyo, Japan")
...     .calculate()
... )
add_analyzer(analyzer)[source]

Add a chart analyzer.

Return type:

ReturnBuilder

add_component(component)[source]

Add a calculation component.

Return type:

ReturnBuilder

add_house_system(engine)[source]

Add an additional house system.

Return type:

ReturnBuilder

calculate()[source]

Calculate the return chart.

This: 1. Finds the exact moment of the planetary return 2. Creates a ChartBuilder for that moment 3. Applies any deferred configuration 4. Injects return metadata 5. Returns the calculated chart

Return type:

CalculatedChart

Returns:

CalculatedChart with return metadata in chart.metadata

classmethod lunar(natal, *, near_date=None, occurrence=None, location=None)[source]

Create a Lunar Return builder.

A Lunar Return is the chart cast for when the Moon returns to its exact natal position. This happens approximately every 27.3 days.

Parameters:
Return type:

ReturnBuilder

Returns:

ReturnBuilder configured for Lunar Return

Example

>>> # Lunar Return nearest to March 15, 2025
>>> lr = ReturnBuilder.lunar(natal, near_date="2025-03-15").calculate()
>>>
>>> # The 100th Lunar Return
>>> lr_100 = ReturnBuilder.lunar(natal, occurrence=100).calculate()
classmethod planetary(natal, planet, *, near_date=None, occurrence=None, location=None)[source]

Create a planetary return builder for any planet.

Parameters:
Return type:

ReturnBuilder

Returns:

ReturnBuilder configured for the specified planetary return

Example

>>> # First Saturn Return (~age 29)
>>> sr1 = ReturnBuilder.planetary(natal, "Saturn", occurrence=1).calculate()
>>>
>>> # Jupiter Return nearest to 2025
>>> jr = ReturnBuilder.planetary(
...     natal, "Jupiter", near_date="2025-06-01"
... ).calculate()
classmethod solar(natal, year, *, location=None)[source]

Create a Solar Return builder.

A Solar Return is the chart cast for when the Sun returns to its exact natal position. This happens approximately on your birthday each year (but the exact time varies).

Parameters:
Return type:

ReturnBuilder

Returns:

ReturnBuilder configured for Solar Return

Example

>>> sr_2025 = ReturnBuilder.solar(natal, 2025).calculate()
with_aspects(engine=None)[source]

Set the aspect engine.

Return type:

ReturnBuilder

with_config(config)[source]

Set the calculation configuration.

Return type:

ReturnBuilder

with_ephemeris(engine)[source]

Set the ephemeris engine.

Return type:

ReturnBuilder

with_house_systems(engines)[source]

Set the house system engines.

Return type:

ReturnBuilder

with_orbs(engine=None)[source]

Set the orb engine.

Return type:

ReturnBuilder

class stellium.SynthesisBuilder(chart1, chart2, method)[source]

Bases: object

Builder for synthesizing two charts into one (composite or davison).

Example:

# Simple davison
davison = SynthesisBuilder.davison(chart1, chart2).calculate()

# Configured composite
composite = (SynthesisBuilder.composite(chart1, chart2)
    .with_midpoint_method("short_arc")
    .with_labels("Alice", "Bob")
    .calculate())
calculate()[source]

Calculate the synthesis chart.

Return type:

SynthesisChart

Returns:

SynthesisChart (subclass of CalculatedChart)

classmethod composite(chart1, chart2)[source]

Create composite chart (midpoint of all positions).

Parameters:
Return type:

SynthesisBuilder

Returns:

SynthesisBuilder configured for composite calculation

classmethod davison(chart1, chart2)[source]

Create davison chart (midpoint in time and space).

Parameters:
Return type:

SynthesisBuilder

Returns:

SynthesisBuilder configured for davison calculation

with_houses(houses)[source]

Set house calculation method for composite charts.

Parameters:

houses (bool | str) – True (default) - Derived ASC method (midpoint Ascendants, derive cusps) False - No houses (positions only) “place” - Reference place method (geographic midpoint + derived time)

Return type:

SynthesisBuilder

Returns:

Self for chaining

Example

# No houses composite = SynthesisBuilder.composite(c1, c2).with_houses(False).calculate()

# Reference place method composite = SynthesisBuilder.composite(c1, c2).with_houses(“place”).calculate()

with_labels(label1, label2)[source]

Set descriptive labels for source charts.

Parameters:
  • label1 (str) – Label for first chart (e.g., “Alice”, “Natal”)

  • label2 (str) – Label for second chart (e.g., “Bob”, “Transit”)

Return type:

SynthesisBuilder

Returns:

Self for chaining

with_location_method(method)[source]

Set location midpoint method for davison charts.

Parameters:

method (str) – “great_circle” (default) - Geodesic midpoint following Earth’s curvature “simple” - Arithmetic mean of lat/lon (faster but less accurate)

Return type:

SynthesisBuilder

Returns:

Self for chaining

with_midpoint_method(method)[source]

Set midpoint calculation method for composite charts.

Parameters:

method (str) – “short_arc” (default) or “long_arc” - short_arc: Always takes shorter path around zodiac - long_arc: Always takes longer path

Return type:

SynthesisBuilder

Returns:

Self for chaining

class stellium.MultiChartBuilder(charts=None)[source]

Bases: object

Fluent builder for creating MultiChart objects.

Supports all multi-chart scenarios:

For synastry:

mc = MultiChartBuilder.synastry(chart1, chart2).calculate()

For transits:

mc = MultiChartBuilder.transit(natal, “2025-06-15”).calculate()

For progressions:

mc = MultiChartBuilder.progression(natal, age=30).calculate()

For 3-4 chart configurations:
mc = (MultiChartBuilder.from_chart(natal, “Natal”)

.add_progression(age=30, label=”Progressed”) .add_transit(“2025-06-15”, label=”Transit”) .calculate())

add_arc_direction(*, target_date=None, age=None, arc_type='solar_arc', rulership_system='traditional', label='Directed')[source]

Add a directed chart.

Parameters:
  • target_date (str | datetime | None) – Target date for directions

  • age (float | None) – Age in years

  • arc_type (str) – Type of arc to use

  • rulership_system (Literal['traditional', 'modern']) – Rulership system

  • label (str) – Label for directed chart

Return type:

MultiChartBuilder

Returns:

Self for chaining

add_chart(chart, label, relationship_to=0, relationship_type=None)[source]

Add a chart to the builder.

Parameters:
  • chart (CalculatedChart) – Chart to add

  • label (str) – Label for this chart

  • relationship_to (int) – Which existing chart this relates to (default: 0)

  • relationship_type (ComparisonType | None) – Type of relationship (optional)

Return type:

MultiChartBuilder

Returns:

Self for chaining

add_progression(*, target_date=None, age=None, progression_type='secondary', angle_method='quotidian', label='Progressed')[source]

Add a progressed chart.

Parameters:
  • target_date (str | datetime | None) – Target date for progression

  • age (float | None) – Age in years

  • progression_type (Literal['secondary', 'tertiary', 'minor']) – “secondary” (default), “tertiary”, or “minor”

  • angle_method (Literal['quotidian', 'solar_arc', 'naibod']) – How to progress angles

  • label (str) – Label for progressed chart

Return type:

MultiChartBuilder

Returns:

Self for chaining

add_transit(transit_data, location=None, label='Transit')[source]

Add a transit chart.

Parameters:
  • transit_data (str | datetime | CalculatedChart) – Transit datetime or chart

  • location (Any) – Location (uses chart[0] location if None)

  • label (str) – Label for transit chart

Return type:

MultiChartBuilder

Returns:

Self for chaining

classmethod arc_direction(natal_data, *, target_date=None, age=None, arc_type='solar_arc', rulership_system='traditional', natal_label='Natal', directed_label='Directed')[source]

Create an arc direction comparison (natal vs directed chart).

Arc directions move ALL points by the same angular distance.

Parameters:
Return type:

MultiChartBuilder

Returns:

MultiChartBuilder configured for arc directions

calculate()[source]

Execute all calculations and return the MultiChart.

Return type:

MultiChart

Returns:

MultiChart object with all calculated data

classmethod from_chart(chart, label='Chart 1')[source]

Start building from a single chart.

Use .add_chart(), .add_transit(), etc. to add more charts.

Parameters:
Return type:

MultiChartBuilder

Returns:

MultiChartBuilder ready for adding more charts

classmethod from_charts(charts, labels=None)[source]

Create a MultiChartBuilder from a list of calculated charts.

Parameters:
Return type:

MultiChartBuilder

Returns:

MultiChartBuilder ready for configuration

classmethod progression(natal_data, progressed_data=None, *, target_date=None, age=None, progression_type='secondary', angle_method='quotidian', natal_label='Natal', progressed_label='Progressed')[source]

Create a progression comparison with auto-calculation support.

Supports three progression types: - secondary (default): 1 day = 1 year. The standard progression. - tertiary: 1 day = 1 lunar month (~27.3 days). Faster-moving. - minor: 1 lunar month = 1 year. Intermediate rate.

Parameters:
Return type:

MultiChartBuilder

Returns:

MultiChartBuilder configured for progressions

Examples:

# Secondary (standard, 1 day = 1 year)
prog = MultiChartBuilder.progression(natal, age=30).calculate()

# Tertiary (1 day = 1 lunar month)
prog = MultiChartBuilder.progression(
    natal, age=30, progression_type="tertiary"
).calculate()

# Minor (1 lunar month = 1 year)
prog = MultiChartBuilder.progression(
    natal, age=30, progression_type="minor"
).calculate()
classmethod synastry(data1, data2, label1='Person 1', label2='Person 2')[source]

Create a synastry comparison between two natal charts.

Parameters:
Return type:

MultiChartBuilder

Returns:

MultiChartBuilder configured for synastry

classmethod transit(natal_data, transit_data, natal_label='Natal', transit_label='Transit')[source]

Create a transit comparison (natal chart vs current sky).

Parameters:
Return type:

MultiChartBuilder

Returns:

MultiChartBuilder configured for transits

Example

# Using a raw datetime (uses natal location) mc = MultiChartBuilder.transit(natal, datetime(2025, 1, 1, 12, 0))

# Using a tuple with explicit location mc = MultiChartBuilder.transit(natal, (datetime(2025, 1, 1), “New York”))

with_aspect_engine(engine)[source]

Set the aspect engine for cross-chart aspects.

Parameters:

engine – AspectEngine instance

Return type:

MultiChartBuilder

Returns:

Self for chaining

with_cross_aspects(pairs='to_primary')[source]

Configure which chart pairs to calculate cross-aspects for.

Parameters:

pairs (Union[list[tuple[int, int]], Literal['all', 'to_primary', 'adjacent']]) – Either: - “to_primary”: Only aspects to chart[0] (default) - “adjacent”: Adjacent pairs (0-1, 1-2, 2-3) - “all”: All possible pairs - List of (i, j) tuples for explicit pairs

Return type:

MultiChartBuilder

Returns:

Self for chaining

with_house_overlays(enabled=True)[source]

Enable or disable house overlay calculation.

Parameters:

enabled (bool) – Whether to calculate house overlays

Return type:

MultiChartBuilder

Returns:

Self for chaining

with_internal_aspect_engine(engine)[source]

Set aspect engine for calculating internal (natal) aspects.

Parameters:

engine – AspectEngine instance

Return type:

MultiChartBuilder

Returns:

Self for chaining

with_internal_orb_engine(engine)[source]

Set orb engine for calculating internal (natal) aspects.

Parameters:

engine (OrbEngine) – OrbEngine instance

Return type:

MultiChartBuilder

Returns:

Self for chaining

with_labels(labels)[source]

Set labels for each chart.

Parameters:

labels (list[str]) – List of labels

Return type:

MultiChartBuilder

Returns:

Self for chaining

with_orb_engine(engine)[source]

Set the orb engine for cross-chart aspects.

Parameters:

engine (OrbEngine) – OrbEngine instance

Return type:

MultiChartBuilder

Returns:

Self for chaining

without_cross_aspects()[source]

Disable cross-aspect calculation.

Return type:

MultiChartBuilder

Returns:

Self for chaining

without_house_overlays()[source]

Disable house overlay calculation.

Return type:

MultiChartBuilder

Returns:

Self for chaining

class stellium.ReportBuilder[source]

Bases: object

Builder for chart reports.

Example:

report = (
    ReportBuilder()
    .from_chart(chart)
    .with_chart_overview()
    .with_planet_positions()
    .render(format="rich_table")
)
from_chart(chart)[source]

Set the chart to generate reports from.

Parameters:

chart (CalculatedChart | Comparison | MultiChart) – A CalculatedChart, Comparison, or MultiChart

Return type:

ReportBuilder

Returns:

Self for chaining

preset_aspects_only()[source]

Aspects-only preset: Focus on planetary relationships.

Includes: - Chart overview - All aspects (with orbs) - Aspect patterns (Grand Trines, T-Squares, etc.)

Return type:

ReportBuilder

Returns:

Self for chaining

Note: Aspect patterns require AspectPatternAnalyzer component.

Example

>>> report = ReportBuilder().from_chart(chart).preset_aspects_only().render()
preset_detailed()[source]

Detailed preset: Comprehensive report with all major sections.

Includes: - Chart overview - Moon phase - Planet positions (with speed and all house systems) - Declinations - All aspects (sorted by orb) - House cusps - Essential dignities

Return type:

ReportBuilder

Returns:

Self for chaining

Example

>>> report = ReportBuilder().from_chart(chart).preset_detailed().render()
preset_full()[source]

Full preset: Everything available.

Includes all sections for maximum detail: - Chart overview - Moon phase - Planet positions (with speed and all house systems) - Declinations - All aspects - Aspect patterns (Grand Trines, T-Squares, etc.) - House cusps - Essential dignities - Midpoints and midpoint aspects - Fixed stars - Zodiacal Releasing (Part of Fortune and Part of Spirit)

Note: Some sections require specific components to be added during chart calculation (e.g., DignityComponent, AspectPatternAnalyzer, MidpointCalculator, FixedStarsComponent, ZodiacalReleasingAnalyzer). Missing components show helpful messages rather than errors.

Return type:

ReportBuilder

Returns:

Self for chaining

Example

>>> chart = (ChartBuilder.from_native(native)
...     .with_aspects()
...     .add_component(DignityComponent())
...     .add_analyzer(AspectPatternAnalyzer())
...     .add_component(MidpointCalculator())
...     .add_component(FixedStarsComponent())
...     .add_analyzer(ZodiacalReleasingAnalyzer(["Part of Fortune", "Part of Spirit"]))
...     .calculate())
>>> report = ReportBuilder().from_chart(chart).preset_full().render()
preset_minimal()[source]

Minimal preset: Just the basics.

Includes: - Chart overview (name, date, location) - Planet positions

Return type:

ReportBuilder

Returns:

Self for chaining

Example

>>> report = ReportBuilder().from_chart(chart).preset_minimal().render()
preset_positions_only()[source]

Positions-only preset: Focus on planetary placements.

Includes: - Chart overview - Planet positions (with speed and house placements) - Declinations - House cusps

No aspects or interpretive sections.

Return type:

ReportBuilder

Returns:

Self for chaining

Example

>>> report = ReportBuilder().from_chart(chart).preset_positions_only().render()
preset_standard()[source]

Standard preset: Common report sections for everyday use.

Includes: - Chart overview - Planet positions (with house placements) - Major aspects (sorted by orb) - House cusps

Return type:

ReportBuilder

Returns:

Self for chaining

Example

>>> report = ReportBuilder().from_chart(chart).preset_standard().render()
preset_synastry()[source]

Synastry preset: Optimized for relationship comparison charts.

Designed for Comparison objects, this preset shows: - Chart overview (displays both charts’ info) - Planet positions (side-by-side tables for each chart) - Cross-chart aspects (with chart labels) - House cusps (side-by-side tables for each chart)

Return type:

ReportBuilder

Returns:

Self for chaining

Example

>>> comparison = ComparisonBuilder.synastry(chart1, chart2).calculate()
>>> report = ReportBuilder().from_chart(comparison).preset_synastry().render()
preset_transit()[source]

Transit preset: Optimized for transit comparison charts.

Shows natal chart positions alongside transit positions, with cross-chart aspects showing transiting planets’ aspects to natal positions.

Includes: - Chart overview - Planet positions (side-by-side: natal vs transit) - Cross-chart aspects (all aspects, tight orbs) - House cusps (side-by-side)

Return type:

ReportBuilder

Returns:

Self for chaining

Example

>>> transit = ComparisonBuilder.transit(natal, transit_time).calculate()
>>> report = ReportBuilder().from_chart(transit).preset_transit().render()
preset_transit_calendar(end, start=None, include_minor_planets=False)[source]

Transit calendar preset: Sky events over a date range.

Bundles all three transit calendar sections showing what’s happening in the sky between two dates. Useful for planning around retrogrades, sign changes, and eclipses.

Includes: - Planetary stations (retrograde/direct) - Sign ingresses (planets changing signs) - Eclipses (solar and lunar)

Parameters:
  • end (datetime) – End date for the calendar (required)

  • start (datetime | None) – Start date (optional, defaults to chart date)

  • include_minor_planets (bool) – Include Chiron in stations/ingresses (default: False)

Return type:

ReportBuilder

Returns:

Self for chaining

Example

>>> # Transit calendar for the next year from chart date
>>> from datetime import timedelta
>>> chart_date = chart.datetime.utc_datetime
>>> report = (ReportBuilder()
...     .from_chart(chart)
...     .preset_transit_calendar(end=chart_date + timedelta(days=365))
...     .render())
>>>
>>> # Specific date range
>>> from datetime import datetime
>>> report = (ReportBuilder()
...     .from_chart(chart)
...     .preset_transit_calendar(
...         start=datetime(2025, 1, 1),
...         end=datetime(2025, 12, 31)
...     )
...     .render())

Note

This preset does NOT include natal chart information - it’s purely about sky events. For transits TO your natal chart, use ComparisonBuilder.transit() with preset_transit() instead.

render(format='rich_table', file=None, show=None)[source]

Render the report with flexible output options.

Parameters:
  • format (str) – Output format (“rich_table”, “plain_table”, “text”, “prose”, “pdf”, “html”)

  • file (str | None) – Optional filename to save to

  • show (bool | None) – Whether to display in terminal. Defaults to True for terminal formats (rich_table, plain_table, text, prose) and False for file formats (pdf, html).

Return type:

str | None

Returns:

Filename if saved to file, None otherwise

Raises:

Examples

# Show in terminal with Rich formatting report.render()

# Save to file (with terminal preview) report.render(format=”plain_table”, file=”chart.txt”)

# Save quietly (no terminal output) report.render(format=”plain_table”, file=”chart.txt”, show=False)

# Generate PDF with chart image and title (configured via builder) report.with_chart_image().with_title(“My Report”).render(

format=”pdf”, file=”report.pdf”

)

with_arabic_parts(mode='all', show_formula=True, show_description=False)[source]

Add Arabic Parts (Lots) table.

Parameters:
  • mode (str) – Which parts to display (DEFAULT: “all”) - “all”: All calculated parts - “core”: 7 Hermetic Lots (Fortune, Spirit, Eros, etc.) - “family”: Family & Relationship Lots - “life”: Life Topic Lots - “planetary”: Planetary Exaltation Lots

  • show_formula (bool) – Include the formula column (DEFAULT: True) Formula shows as “ASC + Point2 - Point3” with * for sect-aware parts

  • show_description (bool) – Include part descriptions (DEFAULT: False)

Return type:

ReportBuilder

Returns:

Self for chaining

Example

>>> # Show all Arabic Parts with formulas
>>> report = (ReportBuilder()
...     .from_chart(chart)
...     .with_arabic_parts()
...     .render())
>>>
>>> # Show only core Hermetic Lots with descriptions
>>> report = (ReportBuilder()
...     .from_chart(chart)
...     .with_arabic_parts(
...         mode="core",
...         show_description=True
...     )
...     .render())

Note

Requires ArabicPartsCalculator to be added to chart builder:

from stellium.components.arabic_parts import ArabicPartsCalculator

chart = (ChartBuilder.from_native(native)

.add_component(ArabicPartsCalculator()) .calculate())

with_aspect_patterns(pattern_types='all', sort_by='type')[source]

Add aspect patterns table (Grand Trines, T-Squares, Yods, etc.).

Parameters:
  • pattern_types (str | list[str]) – Which pattern types to show (DEFAULT: “all”) - “all”: Show all detected patterns - list[str]: Show specific pattern types

  • sort_by (str) – How to sort patterns (DEFAULT: “type”) - “type”: Group by pattern type - “element”: Group by element - “count”: Sort by number of planets

Return type:

ReportBuilder

Returns:

Self for chaining

Note

Requires AspectPatternAnalyzer to be added to chart builder. If missing, displays helpful message instead of erroring.

with_aspects(mode='all', orbs=True, sort_by='orb', include_aspectarian=True, aspectarian_detailed=False, aspectarian_cell_size=None, aspectarian_theme=None)[source]

Add aspects table with optional aspectarian grid.

Parameters:
  • mode (str) – “all”, “major”, “minor”, or “harmonic”

  • orbs (bool) – Show orb column

  • sort_by (str) – How to sort aspects (“orb”, “planet”, or “aspect_type”)

  • include_aspectarian (bool) – Include aspectarian grid SVG (default: True)

  • aspectarian_detailed (bool) – Show orb and A/S in aspectarian cells (default: False)

  • aspectarian_cell_size (int | None) – Override cell size for aspectarian (default: config default)

  • aspectarian_theme (str | None) – Theme for aspectarian rendering (default: None)

Return type:

ReportBuilder

Returns:

Self for chaining

Note

The aspectarian SVG is displayed in HTML/PDF output. Terminal output shows a placeholder with dimensions.

with_chart_image(path=None)[source]

Include a chart wheel image in the report.

When called without arguments, automatically generates a chart SVG using the chart’s default draw settings.

Parameters:

path (str | None) – Optional path to an existing SVG file. If not provided, a chart image will be auto-generated when rendering.

Return type:

ReportBuilder

Returns:

Self for chaining

Examples

# Auto-generate chart image report.with_chart_image()

# Use existing SVG file report.with_chart_image(“my_chart.svg”)

with_chart_overview()[source]

Add chart overview section (birth data, chart type, etc.).

Return type:

ReportBuilder

Returns:

Self for chaining

with_cross_aspects(mode='all', orbs=True, sort_by='orb')[source]

Add cross-chart aspects table (for Comparison charts).

Shows aspects between chart1 planets and chart2 planets with appropriate labels for each chart.

Parameters:
  • mode (str) – “all”, “major”, “minor”, or “harmonic”

  • orbs (bool) – Show orb column

  • sort_by (str) – How to sort aspects (“orb”, “planet”, “aspect_type”)

Return type:

ReportBuilder

Returns:

Self for chaining

Note

This section requires a Comparison object (from ComparisonBuilder). If used with a single CalculatedChart, displays a helpful message.

Example

>>> comparison = ComparisonBuilder.synastry(chart1, chart2).calculate()
>>> report = (ReportBuilder()
...     .from_chart(comparison)
...     .with_cross_aspects(mode="major")
...     .render())
with_declination_aspects(mode='all', show_orbs=True, show_oob_status=True, sort_by='orb')[source]

Add declination aspects table (Parallel and Contraparallel).

Declination aspects are based on equatorial coordinates rather than ecliptic longitude. They represent a different type of planetary relationship.

  • Parallel: Two planets at the same declination (same hemisphere). Interpreted like a conjunction.

  • Contraparallel: Two planets at equal declination but opposite hemispheres. Interpreted like an opposition.

Parameters:
  • mode (str) – Which aspects to show (DEFAULT: “all”) - “all”: Both parallel and contraparallel - “parallel”: Only parallel aspects - “contraparallel”: Only contraparallel aspects

  • show_orbs (bool) – Show orb column (DEFAULT: True)

  • show_oob_status (bool) – Show out-of-bounds status (DEFAULT: True)

  • sort_by (str) – How to sort aspects (DEFAULT: “orb”) - “orb”: Tightest aspects first - “planet”: Group by planet - “aspect_type”: Group by Parallel/Contraparallel

Return type:

ReportBuilder

Returns:

Self for chaining

Note

Requires .with_declination_aspects() on ChartBuilder:
chart = (ChartBuilder.from_native(native)

.with_aspects() .with_declination_aspects(orb=1.0) .calculate())

Example

>>> report = (ReportBuilder()
...     .from_chart(chart)
...     .with_chart_overview()
...     .with_declination_aspects(mode="all")
...     .render())
with_declinations()[source]

Add declinations table.

Shows planetary declinations (distance from celestial equator), direction (north/south), and out-of-bounds status.

Out-of-bounds planets have declination beyond the Sun’s maximum (~23°27’) and are considered to have extra intensity or unconventional expression.

Return type:

ReportBuilder

Returns:

Self for chaining

Example

>>> report = (ReportBuilder()
...     .from_chart(chart)
...     .with_chart_overview()
...     .with_declinations()
...     .render())
with_dignities(essential='both', show_details=False)[source]

Add essential dignities table.

Parameters:
  • essential (str) – Which essential dignity system(s) to show (DEFAULT: “both”) - “traditional”: Traditional dignities only - “modern”: Modern dignities only - “both”: Both systems - “none”: Skip essential dignities

  • show_details (bool) – Show dignity names instead of just scores

Return type:

ReportBuilder

Returns:

Self for chaining

Note

Requires DignityComponent to be added to chart builder. If missing, displays helpful message instead of erroring.

with_dispositors(mode='both', rulership='traditional', house_system=None, show_chains=True)[source]

Add dispositor analysis section.

Shows planetary and/or house-based dispositor chains, final dispositor(s), and mutual receptions.

Parameters:
  • mode (str) – Which dispositor analysis to show (DEFAULT: “both”) - “planetary”: Traditional planet-disposes-planet - “house”: Kate’s house-based innovation (life area flow) - “both”: Show both analyses

  • rulership (str) – “traditional” or “modern” rulership system (DEFAULT: “traditional”)

  • house_system (str | None) – House system for house-based mode (defaults to chart’s default)

  • show_chains (bool) – Whether to show full disposition chain details (DEFAULT: True)

Return type:

ReportBuilder

Returns:

Self for chaining

Example

>>> report = (ReportBuilder()
...     .from_chart(chart)
...     .with_chart_overview()
...     .with_dispositors(mode="both")
...     .render())

Note

For graphical output (SVG), use the DispositorEngine directly:

from stellium.engines.dispositors import DispositorEngine, render_both_dispositors engine = DispositorEngine(chart) graph = render_both_dispositors(engine.planetary(), engine.house_based()) graph.render(“dispositors”, format=”svg”)

with_eclipses(end, start=None, eclipse_types='both')[source]

Add eclipses table.

Shows solar and lunar eclipses within a date range. Useful for eclipse calendars and transit planning.

Parameters:
  • end (datetime) – End date for eclipse search (required)

  • start (datetime | None) – Start date for eclipse search (optional, defaults to chart date)

  • eclipse_types (str) – Which types to include (“both”, “solar”, “lunar”)

Return type:

ReportBuilder

Returns:

Self for chaining

Example

>>> # Eclipses for the next 2 years from chart date
>>> from datetime import datetime, timedelta
>>> chart_date = chart.datetime.utc_datetime
>>> report = (ReportBuilder()
...     .from_chart(chart)
...     .with_eclipses(end=chart_date + timedelta(days=730))
...     .render())
>>>
>>> # Only solar eclipses in a specific range
>>> report = (ReportBuilder()
...     .from_chart(chart)
...     .with_eclipses(
...         start=datetime(2025, 1, 1),
...         end=datetime(2025, 12, 31),
...         eclipse_types="solar"
...     )
...     .render())
with_fixed_stars(tier=None, include_keywords=True, sort_by='longitude')[source]

Add fixed stars table.

Shows positions and metadata for fixed stars in the chart. Requires FixedStarsComponent to be added to chart builder.

Parameters:
  • tier (int | None) – Filter to specific tier (DEFAULT: None = all tiers) - 1: Royal Stars only (Aldebaran, Regulus, Antares, Fomalhaut) - 2: Major Stars only - 3: Extended Stars only - None: All tiers

  • include_keywords (bool) – Include interpretive keywords column (DEFAULT: True)

  • sort_by (str) – Sort order (DEFAULT: “longitude”) - “longitude”: Zodiacal order - “magnitude”: Brightest first - “tier”: Royal first, then Major, then Extended

Return type:

ReportBuilder

Returns:

Self for chaining

Example

>>> # Royal stars only
>>> report = (ReportBuilder()
...     .from_chart(chart)
...     .with_fixed_stars(tier=1)
...     .render())
>>>
>>> # All stars sorted by brightness
>>> report = (ReportBuilder()
...     .from_chart(chart)
...     .with_fixed_stars(sort_by="magnitude")
...     .render())

Note

Requires FixedStarsComponent to be added to chart builder:
chart = (ChartBuilder.from_native(native)

.add_component(FixedStarsComponent()) .calculate())

with_house_cusps(systems='all')[source]

Add house cusps table.

Parameters:

systems (str | list[str]) – Which house systems to display (DEFAULT: “all”) - “all”: Show all calculated systems - list[str]: Show specific systems

Return type:

ReportBuilder

Returns:

Self for chaining

with_ingresses(end, start=None, planets=None, include_moon=False, include_minor=False)[source]

Add sign ingresses table.

Shows when planets enter new zodiac signs within a date range. Useful for tracking sign changes and transit planning.

Parameters:
  • end (datetime) – End date for ingress search (required)

  • start (datetime | None) – Start date for ingress search (optional, defaults to chart date)

  • planets (list[str] | None) – Which planets to include (default: Sun through Pluto)

  • include_moon (bool) – Include Moon ingresses (default: False, very frequent)

  • include_minor (bool) – Include Chiron (default: False)

Return type:

ReportBuilder

Returns:

Self for chaining

Example

>>> # Ingresses for the next year from chart date
>>> from datetime import datetime, timedelta
>>> chart_date = chart.datetime.utc_datetime
>>> report = (ReportBuilder()
...     .from_chart(chart)
...     .with_ingresses(end=chart_date + timedelta(days=365))
...     .render())
>>>
>>> # Specific date range with Moon included
>>> report = (ReportBuilder()
...     .from_chart(chart)
...     .with_ingresses(
...         start=datetime(2025, 1, 1),
...         end=datetime(2025, 12, 31),
...         include_moon=True
...     )
...     .render())
with_midpoint_aspects(mode='conjunction', orb=1.5, midpoint_filter='all', sort_by='orb')[source]

Add planets aspecting midpoints table.

This shows which planets activate which midpoints - the most useful way to interpret midpoints. Typically only conjunctions matter (1-2° orb).

Parameters:
  • mode (str) – Which aspects to check (DEFAULT: “conjunction”) - “conjunction”: Only conjunctions (most common, recommended) - “hard”: Conjunction, square, opposition - “all”: All major aspects

  • orb (float) – Maximum orb in degrees (DEFAULT: 1.5°) Midpoints use tighter orbs than regular aspects.

  • midpoint_filter (str) – Which midpoints to check (DEFAULT: “all”) - “all”: All calculated midpoints - “core”: Only Sun/Moon/ASC/MC midpoints

  • sort_by (str) – Sort order (DEFAULT: “orb”) - “orb”: Tightest aspects first - “planet”: Group by aspecting planet - “midpoint”: Group by midpoint

Return type:

ReportBuilder

Returns:

Self for chaining

Example

>>> # Show planets conjunct any midpoint within 1.5°
>>> report = (ReportBuilder()
...     .from_chart(chart)
...     .with_midpoint_aspects()
...     .render())
>>>
>>> # Show hard aspects to core midpoints only
>>> report = (ReportBuilder()
...     .from_chart(chart)
...     .with_midpoint_aspects(
...         mode="hard",
...         midpoint_filter="core",
...         orb=2.0
...     )
...     .render())

Note

Requires MidpointCalculator to be added to chart builder:
chart = (ChartBuilder.from_native(native)

.add_component(MidpointCalculator()) .calculate())

with_midpoint_trees(tree_bases=None, branch_objects=None, orb=1.5, aspect_mode='conjunction', output='both')[source]

Add midpoint tree visualization section.

Generates tree diagrams showing which midpoints aspect focal points. This is a standard Uranian/Hamburg astrology technique for interpreting planetary pictures.

Parameters:
  • tree_bases (list[str] | None) – Focal points to build trees for (DEFAULT: Sun, Moon, MC, ASC)

  • branch_objects (list[str] | None) – Objects to include in midpoint pairs. Default: 10 planets + ASC + MC + True Node

  • orb (float) – Maximum orb in degrees (DEFAULT: 1.5°)

  • aspect_mode (str) – Which aspects to check (DEFAULT: “conjunction”) - “conjunction”: Only conjunctions (0°) - “hard”: Conjunction + 45° series (0°, 45°, 90°, 135°, 180°) - “all”: All major aspects

  • output (str) – What to generate (DEFAULT: “both”) - “svg”: Just SVG visualization - “text”: Just text output - “both”: Both SVG and text

Return type:

ReportBuilder

Returns:

Self for chaining

Example

>>> # Show midpoint trees with hard aspects
>>> report = (ReportBuilder()
...     .from_chart(chart)
...     .with_midpoint_trees(aspect_mode="hard")
...     .render())
>>>
>>> # Custom focal points with conjunction only
>>> report = (ReportBuilder()
...     .from_chart(chart)
...     .with_midpoint_trees(
...         tree_bases=["Sun", "Moon"],
...         orb=2.0,
...         aspect_mode="conjunction"
...     )
...     .render())

Note

Requires MidpointCalculator to be added to chart builder:
chart = (ChartBuilder.from_native(native)

.add_component(MidpointCalculator()) .calculate())

with_midpoints(mode='all', threshold=None)[source]

Add midpoints table.

Parameters:
  • mode (str) – “all” or “core” (Sun/Moon/ASC/MC midpoints)

  • threshold (int | None) – Only show top N midpoints by importance

Return type:

ReportBuilder

Returns:

Self for chaining

with_moon_phase()[source]

Add moon phase section.

Return type:

ReportBuilder

with_planet_positions(include_speed=False, include_house=True, house_systems='all')[source]

Add planet positions table.

Parameters:
  • include_speed (bool) – Show speed in longitude (for retrogrades)

  • include_house (bool) – Show house placement

  • house_systems (str | list[str]) – Which house systems to display (DEFAULT: “all”) - “all”: Show all calculated systems - list[str]: Show specific systems - None: Show default system only

Return type:

ReportBuilder

Returns:

Self for chaining

with_profections(age=None, date=None, include_monthly=True, include_multi_point=True, include_timeline=False, timeline_range=None, points=None, house_system=None, rulership='traditional')[source]

Add profection timing analysis section.

Profections are a Hellenistic technique where the ASC advances one sign per year. The planet ruling that sign becomes the “Lord of the Year.”

Parameters:
  • age (int | None) – Age for profection (either age OR date required)

  • date (str | None) – Target date as ISO string (e.g., “2025-06-15”)

  • include_monthly (bool) – Show monthly profection when date is provided

  • include_multi_point (bool) – Show lords for ASC, Sun, Moon, MC

  • include_timeline (bool) – Show timeline table of Lords

  • timeline_range (tuple[int, int] | None) – Custom range for timeline (e.g., (25, 40))

  • points (list[str] | None) – Custom points for multi-point analysis

  • house_system (str | None) – House system to use (default: prefers Whole Sign)

  • rulership (str) – “traditional” or “modern”

Return type:

ReportBuilder

Returns:

Self for chaining

Example:

# By age
report = (
    ReportBuilder()
    .from_chart(chart)
    .with_profections(age=30)
    .render()
)

# By date with timeline
report = (
    ReportBuilder()
    .from_chart(chart)
    .with_profections(date="2025-06-15", include_timeline=True)
    .render()
)
with_profections_wheel(age=None, date=None, compare_ages=None, show_wheel=True, show_table=True, house_system=None, rulership='traditional')[source]

Add profection wheel visualization section.

Generates a visual wheel diagram showing annual profections: - Circular wheel with ages 0-95 spiraling through 12 houses - Zodiac signs and house labels around the perimeter - Natal planet positions marked on the wheel - Current age highlighted - Summary table with profection details

Parameters:
  • age (int | None) – Current age to highlight (either age OR date required)

  • date (str | None) – Target date as ISO string (e.g., “2025-06-15”)

  • compare_ages (list[int] | None) – List of ages to compare in table (default: current and next)

  • show_wheel (bool) – Whether to show the wheel visualization (default: True)

  • show_table (bool) – Whether to show the summary table (default: True)

  • house_system (str | None) – House system to use (default: prefers Whole Sign)

  • rulership (str) – “traditional” or “modern”

Return type:

ReportBuilder

Returns:

Self for chaining

Example:

# By age with both wheel and table
report = (
    ReportBuilder()
    .from_chart(chart)
    .with_profections_wheel(age=30)
    .render(format="pdf", file="profections.pdf")
)

# Compare specific ages
report = (
    ReportBuilder()
    .from_chart(chart)
    .with_profections_wheel(
        age=30,
        compare_ages=[30, 31, 32]
    )
    .render()
)
with_section(section)[source]

Add a custom section.

This allows users to extend the report system with their own sections.

Parameters:

section (ReportSection) – Any object implementing the ReportSection protocol

Return type:

ReportBuilder

Returns:

Self for chaining

Example:

class MyCustomSection:
    @property
    def section_name(self) -> str:
        return "My Analysis"

    def generate_data(self, chart: CalculatedChart) -> dict:
        return {"type": "text", "text": "Custom analysis..."}

report = (
    ReportBuilder()
    .from_chart(chart)
    .with_section(MyCustomSection())
    .render()
)
with_stations(end, start=None, planets=None, include_minor=False)[source]

Add planetary stations (retrograde/direct) table.

Shows when planets station retrograde or direct within a date range. Useful for retrograde calendars and transit planning.

Parameters:
  • end (datetime) – End date for station search (required)

  • start (datetime | None) – Start date for station search (optional, defaults to chart date)

  • planets (list[str] | None) – Which planets to include (default: Mercury through Pluto)

  • include_minor (bool) – Include Chiron (default: False)

Return type:

ReportBuilder

Returns:

Self for chaining

Example

>>> # Stations for the next year from chart date
>>> from datetime import datetime, timedelta
>>> chart_date = chart.datetime.utc_datetime
>>> report = (ReportBuilder()
...     .from_chart(chart)
...     .with_stations(end=chart_date + timedelta(days=365))
...     .render())
>>>
>>> # Specific date range
>>> report = (ReportBuilder()
...     .from_chart(chart)
...     .with_stations(
...         start=datetime(2025, 1, 1),
...         end=datetime(2025, 12, 31)
...     )
...     .render())
with_title(title)[source]

Set a custom title for the report.

The title appears on the cover page of PDF reports. If not set, a default title is generated from the chart’s name.

Parameters:

title (str) – Custom title string

Return type:

ReportBuilder

Returns:

Self for chaining

Examples

report.with_title(“Birth Chart Analysis”) report.with_title(“Albert Einstein - Complete Natal Analysis”)

with_zodiacal_releasing(lots=None, mode='both', query_date=None, query_age=None, context_periods=2)[source]

Add Zodiacal Releasing timing analysis section.

Zodiacal Releasing is a Hellenistic predictive technique that divides life into major periods ruled by signs, showing when different life themes are activated.

Parameters:
  • lots (str | list[str] | None) – Which lot(s) to display: - str: Single lot name (e.g., “Part of Fortune”) - list[str]: Multiple lots (e.g., [“Part of Fortune”, “Part of Spirit”]) - None: All lots calculated in the chart (DEFAULT)

  • mode (str) – Display mode: - “snapshot”: Current periods only - “timeline”: L1 timeline only - “both”: Both snapshot and timeline (DEFAULT)

  • query_date (str | None) – Date for snapshot as ISO string (defaults to now)

  • query_age (float | None) – Age for snapshot (alternative to query_date)

  • context_periods (int) – Number of L3/L4 periods to show before/after current (default: 2)

Return type:

ReportBuilder

Returns:

Self for chaining

Note

Requires ZodiacalReleasingAnalyzer to be added during chart calculation:

from stellium.engines.releasing import ZodiacalReleasingAnalyzer

chart = (

ChartBuilder.from_native(native) .add_analyzer(ZodiacalReleasingAnalyzer([“Part of Fortune”, “Part of Spirit”])) .calculate()

)

Example:

# Show current ZR state for all calculated lots
report = (
    ReportBuilder()
    .from_chart(chart)
    .with_zodiacal_releasing()
    .render()
)

# Show ZR for specific lot at specific age
report = (
    ReportBuilder()
    .from_chart(chart)
    .with_zodiacal_releasing(
        lots="Part of Fortune",
        mode="snapshot",
        query_age=30
    )
    .render()
)

# Show only L1 timeline for Fortune and Spirit
report = (
    ReportBuilder()
    .from_chart(chart)
    .with_zodiacal_releasing(
        lots=["Part of Fortune", "Part of Spirit"],
        mode="timeline"
    )
    .render()
)
with_zr_visualization(lot='Part of Fortune', year=None, levels=(1, 2, 3), output='both')[source]

Add Zodiacal Releasing visualization (SVG timeline diagram).

Generates visual timeline diagrams in Honeycomb Collective style: - Overview page: natal angles chart + period length reference - Timeline page: stacked L1/L2/L3 timelines with peak shapes

Parameters:
  • lot (str) – Which lot to visualize (default: “Part of Fortune”)

  • year (int | None) – Year to visualize (defaults to current year)

  • levels (tuple[int, ...]) – Which levels to show in timeline (default: 1, 2, 3)

  • output (str) – What to generate: - “overview”: Just the overview page - “timeline”: Just the timeline visualization - “both”: Both pages (DEFAULT)

Return type:

ReportBuilder

Returns:

Self for chaining

Note

Requires ZodiacalReleasingAnalyzer to be added during chart calculation:

from stellium.engines.releasing import ZodiacalReleasingAnalyzer

chart = (

ChartBuilder.from_native(native) .add_analyzer(ZodiacalReleasingAnalyzer([“Part of Fortune”])) .calculate()

)

Example:

# Add ZR visualization to PDF report
report = (
    ReportBuilder()
    .from_chart(chart)
    .with_chart_overview()
    .with_zr_visualization(lot="Part of Fortune", year=2025)
    .render(format="pdf", file="report.pdf")
)
class stellium.PlannerBuilder(native)[source]

Bases: object

Fluent builder for creating personalized astrological planners.

Example

>>> from stellium import Native
>>> from stellium.planner import PlannerBuilder
>>>
>>> native = Native("1990-05-15 14:30", "San Francisco, CA")
>>> planner = (PlannerBuilder.for_native(native)
...     .year(2025)
...     .timezone("America/Los_Angeles")
...     .with_natal_chart()
...     .with_solar_return()
...     .include_natal_transits()
...     .generate("my_planner.pdf"))
binding_margin(inches)[source]

Add extra margin for binding.

Parameters:

inches (float) – Extra margin in inches (added to inner edge)

Return type:

PlannerBuilder

Returns:

Self for chaining

date_range(start, end)[source]

Set a custom date range for the planner.

Parameters:
  • start (date) – Start date

  • end (date) – End date

Return type:

PlannerBuilder

Returns:

Self for chaining

exclude_voc()[source]

Exclude Void of Course Moon periods.

Return type:

PlannerBuilder

classmethod for_native(native)[source]

Start building a planner for a native.

Parameters:

native (Native) – The Native whose planner to create

Return type:

PlannerBuilder

Returns:

PlannerBuilder instance for chaining

generate(output_path=None)[source]

Generate the PDF planner.

Parameters:

output_path (str | None) – Optional file path to save the PDF. If None, only returns bytes.

Return type:

bytes

Returns:

PDF as bytes

include_ingresses(planets=None)[source]

Include planet sign ingresses.

Parameters:

planets (list[str] | None) – Which planets to track. Default (None) includes all.

Return type:

PlannerBuilder

Returns:

Self for chaining

include_moon_phases(enabled=True)[source]

Include Moon phases (new, full, quarters).

Return type:

PlannerBuilder

include_mundane_transits(enabled=True)[source]

Include mundane transits (planet-to-planet in sky).

Return type:

PlannerBuilder

include_natal_transits(planets=None)[source]

Include transits to natal planets.

Parameters:

planets (list[str] | None) – Which transiting planets to include. Default (None) uses outer planets: Jupiter, Saturn, Uranus, Neptune, Pluto

Return type:

PlannerBuilder

Returns:

Self for chaining

include_stations(planets=None)[source]

Include retrograde/direct stations.

Parameters:

planets (list[str] | None) – Which planets to track. Default (None) uses Mercury-Pluto.

Return type:

PlannerBuilder

Returns:

Self for chaining

include_voc(mode='traditional')[source]

Include Void of Course Moon periods.

Parameters:

mode (Literal['traditional', 'modern']) – “traditional” (Sun-Saturn) or “modern” (includes outer planets)

Return type:

PlannerBuilder

Returns:

Self for chaining

location(location)[source]

Set location for angle calculations and planetary hours.

Defaults to the native’s birth location if not specified.

Parameters:

location (str | tuple[float, float]) – City name or (latitude, longitude) tuple

Return type:

PlannerBuilder

Returns:

Self for chaining

page_size(size)[source]

Set page size.

Parameters:

size (Literal['a4', 'a5', 'letter', 'half-letter']) – “a4” (default), “a5”, “letter”, or “half-letter” (alias for a5)

Return type:

PlannerBuilder

Returns:

Self for chaining

timezone(tz)[source]

Set the timezone for transit times.

This is required - transit times will be displayed in this timezone.

Parameters:

tz (str) – Timezone string (e.g., “America/Los_Angeles”, “Europe/London”)

Return type:

PlannerBuilder

Returns:

Self for chaining

week_starts_on(day)[source]

Set the first day of the week for calendar grids.

Parameters:

day (Literal['sunday', 'monday']) – “sunday” (default) or “monday”

Return type:

PlannerBuilder

Returns:

Self for chaining

with_graphic_ephemeris(harmonic=360, enabled=True)[source]

Include graphic ephemeris for the planner period.

Parameters:
  • harmonic (int) – Harmonic compression (360=full zodiac, 90=cardinal, 45=semi-square)

  • enabled (bool) – Whether to include graphic ephemeris

Return type:

PlannerBuilder

Returns:

Self for chaining

with_natal_chart(enabled=True)[source]

Include natal chart wheel in front matter.

Return type:

PlannerBuilder

with_profections(enabled=True)[source]

Include annual profection info (Lord of the Year).

Return type:

PlannerBuilder

with_progressed_chart(enabled=True)[source]

Include secondary progressed chart in front matter.

Return type:

PlannerBuilder

with_solar_return(enabled=True)[source]

Include solar return chart for the planner year.

Return type:

PlannerBuilder

with_zr_timeline(lot='Part of Fortune', enabled=True)[source]

Include Zodiacal Releasing timeline visualization.

Parameters:
  • lot (str) – Which lot to release from (default: “Part of Fortune”)

  • enabled (bool) – Whether to include ZR timeline

Return type:

PlannerBuilder

Returns:

Self for chaining

year(year)[source]

Set the calendar year for the planner.

Parameters:

year (int) – Calendar year (e.g., 2025)

Return type:

PlannerBuilder

Returns:

Self for chaining

class stellium.ElectionalSearch(start, end, location)[source]

Bases: object

Find auspicious times matching astrological conditions.

The search engine accepts conditions (callable filters) and finds times within a date range where all conditions are met.

Conditions can be: - Lambda functions: lambda c: c.get_object(“Moon”).phase.is_waxing - Helper predicates: is_waxing(), not_voc(), on_angle(“Jupiter”) - Composed conditions: all_of(cond1, cond2), any_of(cond1, cond2), not_(cond)

Example

>>> search = ElectionalSearch("2025-01-01", "2025-06-30", "San Francisco, CA")
>>> results = (search
...     .where(lambda c: c.get_object("Moon").phase.is_waxing)
...     .where(lambda c: c.get_object("Moon").sign not in ["Scorpio", "Capricorn"])
...     .find_windows())
start

Search range start

end

Search range end

location

Location for chart calculations

count(step='hour', optimize=True)[source]

Count how many moments match conditions (without storing them).

Uses interval algebra for O(windows) performance when optimize=True and all conditions have window generators. Falls back to iteration otherwise.

Parameters:
  • step (Literal['minute', '5min', '15min', '30min', 'hour', '2hour', '4hour', 'day']) – Time step granularity

  • optimize (bool) – If True, use interval-based optimization (default True)

Return type:

int

Returns:

Number of matching moments

find_first(step='hour')[source]

Find the first moment meeting all conditions.

Parameters:

step (Literal['minute', '5min', '15min', '30min', 'hour', '2hour', '4hour', 'day']) – Time step granularity (see find_moments)

Return type:

ElectionMoment | None

Returns:

First matching ElectionMoment, or None if no matches

find_moments(max_results=100, step='hour', optimize=True)[source]

Find specific moments meeting all conditions.

Uses hierarchical filtering for performance: day-level conditions are checked first to skip entire days that can’t have valid moments.

Parameters:
  • max_results (int) – Maximum number of results to return

  • step (Literal['minute', '5min', '15min', '30min', 'hour', '2hour', '4hour', 'day']) – Time step granularity (default: “hour”) - “minute”: Every minute (slow, use for short ranges) - “5min”, “15min”, “30min”: 5/15/30 minute steps - “hour”: Every hour (good default) - “2hour”, “4hour”: Coarser steps for long ranges - “day”: Daily (for very long ranges, may miss windows)

  • optimize (bool) – If True, use hierarchical day-level filtering (default True)

Return type:

list[ElectionMoment]

Returns:

List of ElectionMoment objects, sorted by datetime

find_windows(step='hour', min_duration_minutes=0, optimize=True)[source]

Find time windows where all conditions are met.

Adjacent passing moments are coalesced into windows. This is useful for seeing “good periods” rather than individual moments.

Uses hierarchical filtering for performance: day-level conditions are checked first to skip entire days that can’t have valid moments.

Parameters:
  • step (Literal['minute', '5min', '15min', '30min', 'hour', '2hour', '4hour', 'day']) – Time step granularity (see find_moments)

  • min_duration_minutes (int) – Minimum window duration to include (default 0)

  • optimize (bool) – If True, use hierarchical day-level filtering (default True)

Return type:

list[ElectionWindow]

Returns:

List of ElectionWindow objects, sorted by start time

iter_moments(step='hour', optimize=True)[source]

Iterate over moments meeting conditions (memory efficient).

Unlike find_moments(), this yields results one at a time without storing them all in memory. Useful for very long searches.

Uses interval algebra for performance when optimize=True and predicates have window generators attached. Falls back to day-level filtering for predicates without window generators.

Parameters:
  • step (Literal['minute', '5min', '15min', '30min', 'hour', '2hour', '4hour', 'day']) – Time step granularity

  • optimize (bool) – If True, use interval-based optimization (default True)

Yields:

ElectionMoment objects as they are found

Return type:

Generator[ElectionMoment, None, None]

where(condition)[source]

Add a condition that must be met.

Conditions are combined with AND logic. All conditions must be true for a time to be considered valid.

Parameters:

condition (Callable[[CalculatedChart], bool]) – A callable taking CalculatedChart, returning bool

Return type:

ElectionalSearch

Returns:

Self for method chaining

with_progress(callback)[source]

Set a progress callback for long searches.

Parameters:

callback (Callable[[int, int], None]) – Function called with (current_step, total_steps)

Return type:

ElectionalSearch

Returns:

Self for method chaining

Input Data

class stellium.Native(datetime_input, location_input, *, name=None, time_unknown=False)[source]

Bases: object

Represents the “native” data (time and place) for a chart. This class handles all the input parsing and data cleaning.

datetime: ChartDateTime
location: ChartLocation
name: str | None
time_unknown: bool
class stellium.Notable(name, event_type, year, month, day, hour, minute, location_input, category, subcategories=None, notable_for='', astrological_notes='', data_quality='C', sources=None, verified=False)[source]

Bases: Native

A Native with curated metadata from the registry.

Represents famous births and notable events. The base Native class handles all datetime/location parsing - Notable just adds metadata.

Example

>>> notable = Notable(
...     name="Albert Einstein",
...     event_type="birth",
...     year=1879, month=3, day=14, hour=11, minute=30,
...     location_input="Ulm, Germany",
...     category="scientist"
... )
>>> chart = ChartBuilder.from_native(notable).calculate()
astrological_notes: str
category: str
data_quality: str
event_type: str
property is_birth: bool

Check if this is a birth record.

property is_event: bool

Check if this is an event record.

name: str
notable_for: str
sources: list[str] | None
subcategories: list[str] | None
verified: bool

Data Models

class stellium.CalculatedChart(datetime, location, positions, house_systems=<factory>, house_placements=<factory>, aspects=(), declination_aspects=(), metadata=<factory>, zodiac_type=None, ayanamsa=None, ayanamsa_value=None, calculation_timestamp=<factory>, chart_tags=())[source]

Bases: object

Complete calculated chart - the final output.

This is what a ChartBuilder returns. It’s immutable and contains everything you need to analyze or visualize the chart.

aspects: tuple[Aspect, ...] = ()
available_components()[source]

List all components and analyzers whose results are available.

Return type:

list[str]

Returns:

Sorted list of component/analyzer names that can be passed to get_component_result().

Examples:

chart.available_components()
# ['Arabic Parts', 'Aspect Patterns', 'Essential Dignities']
ayanamsa: str | None = None
ayanamsa_value: float | None = None
bazi()[source]

Calculate the BaZi (Four Pillars / 八字) chart for this birth data.

Uses the chart’s datetime and location to compute the Chinese Four Pillars, reusing the already-resolved timezone information.

Returns:

A BaZiChart with all four pillars, ready for analysis.

Example:

chart = ChartBuilder.from_details("1994-01-06 11:47", "Palo Alto, CA").calculate()
bazi = chart.bazi()
print(bazi.hanzi)           # Eight characters
print(bazi.day_master)      # Day Master stem
print(bazi.strength())      # Strength analysis
calculation_timestamp: datetime
chart_tags: tuple[str, ...] = ()
datetime: ChartDateTime
declination_aspects: tuple[Aspect, ...] = ()
property default_house_system: str
draconic()[source]

Return a draconic version of this chart.

The draconic chart rotates all positions so that the North Node is at 0° Aries. This is sometimes called the “soul chart” and represents the soul’s orientation before incarnation.

The transformation subtracts the North Node’s longitude from all positions and normalizes to 0-360°.

Return type:

CalculatedChart

Returns:

A new CalculatedChart with all longitudes rotated and “draconic” added to chart_tags.

Example:

draconic = chart.draconic()
print(draconic.get_object("Sun").sign_position)
draconic.draw("draconic.svg").save()
draw(filename='chart.svg')[source]

Start building a chart visualization with fluent API.

This is a convenience method that creates a ChartDrawBuilder for easy, discoverable chart visualization. It provides presets and a fluent interface for customization.

Parameters:

filename (str) – Output filename for the SVG

Return type:

ChartDrawBuilder

Returns:

ChartDrawBuilder instance for chaining

Example:

# Simple preset
chart.draw("my_chart.svg").preset_standard().save()

# Custom configuration
chart.draw("custom.svg").with_theme("dark").with_moon_phase(
    position="top-left", show_label=True
).with_chart_info(position="top-right").save()
draw_dial(filename='dial.svg', degrees=90)[source]

Draw a Uranian/Hamburg school dial chart.

Creates a dial visualization that compresses the zodiac to reveal hard aspects. On a 90° dial, conjunctions, squares, and oppositions all appear as conjunctions.

Parameters:
  • filename (str) – Output filename for the SVG

  • degrees (int) – Dial size - 90 (default), 45, or 360

Return type:

DialDrawBuilder

Returns:

DialDrawBuilder instance for chaining

Example:

# Basic 90° dial
chart.draw_dial("dial.svg").save()

# With theme
chart.draw_dial("dial.svg").with_theme("midnight").save()

# With transits on outer ring
chart.draw_dial("dial.svg")
    .with_outer_ring(transit_chart.get_planets(), label="Transits")
    .save()

# 360° dial with pointer to Sun
chart.draw_dial("dial.svg", degrees=360)
    .with_pointer(pointing_to="Sun")
    .save()
draw_vedic(filename='vedic_chart.svg', style='north_indian', theme='classic', label_style='abbreviation', show_degrees=True, size=500)[source]

Draw a Vedic (Jyotish) chart in North Indian or South Indian style.

Parameters:
  • filename (str) – Output filename for the SVG

  • style (str) – “north_indian” (diamond) or “south_indian” (grid)

  • theme (str) – “classic”, “dark”, or “traditional”

  • label_style (str) – “abbreviation” (Ari, Su), “number” (1, 2), “glyph” (unicode symbols), or “full” (Aries, Sun)

  • show_degrees (bool) – Show degree + minutes for each planet

  • size (int) – SVG width/height in pixels

Return type:

CalculatedChart

Returns:

self (for chaining)

Example:

chart.draw_vedic("north.svg", style="north_indian")
chart.draw_vedic("south.svg", style="south_indian", theme="traditional")
get_accidental_dignities(system=None)[source]

Get accidental dignity calculations.

Parameters:

system (str | None) – Specific house system (“Placidus”). If None returns all systems.

Return type:

dict[str, Any]

Returns:

Dictionary of planetary accidental dignities

get_all_accidental_dignities()[source]

Get all accidental dignities (entire object).

Return type:

dict[str, Any]

get_angles()[source]

Get all chart angles.

Return type:

list[CelestialPosition]

get_component_result(name)[source]

Get the result of a component or analyzer by name.

Works with any component added via .add_component() or analyzer added via .add_analyzer() on ChartBuilder.

Parameters:

name (str) – The component or analyzer name (e.g., “Arabic Parts”, “Essential Dignities”, “Aspect Patterns”). Also accepts the metadata key as an alias (e.g., “dignities”).

Returns:

list of CelestialPosition objects - For metadata-based components/analyzers: the stored dict or list - For dual-storage components: dict with “positions” and “metadata” keys

Return type:

  • For position-based components

Raises:

KeyError – If no component with that name was used. The error message lists all available component names.

Examples:

parts = chart.get_component_result("Arabic Parts")
dignities = chart.get_component_result("Essential Dignities")
patterns = chart.get_component_result("Aspect Patterns")
chart.available_components()
get_contraparallels()[source]

Get all contraparallel aspects (same declination, opposite hemispheres).

Return type:

list[Aspect]

get_declination_aspects(aspect_type=None)[source]

Get declination aspects (Parallel and Contraparallel).

Parameters:

aspect_type (str | None) – Filter to “Parallel” or “Contraparallel”. None returns all declination aspects.

Return type:

list[Aspect]

Returns:

List of declination aspects

get_dignities(system='traditional')[source]

Get essential dignity calculations.

Parameters:

system (str) – “traditional” or “modern”

Return type:

dict[str, Any]

Returns:

Dictionary of planet dignities, or empty dict if not calculated

get_house(object_name, system_name=None)[source]

Helper method to get the house number for a specific object in a specific system.

Return type:

int | None

get_houses(system_name=None)[source]

Get all cusps for a specific system (or default system).

Return type:

HouseCusps

get_mutual_receptions(system='traditional')[source]

Get all mutual receptions in the chart.

Parameters:

system (str) – “traditional” or “modern”

Return type:

list[dict[str, Any]]

Returns:

List of mutual reception dictionaries

get_nodes()[source]

Get all nodes (True Node, South Node, etc.).

Return type:

list[CelestialPosition]

get_object(name)[source]

Get a celestial object by name.

Return type:

CelestialPosition | None

get_parallels()[source]

Get all parallel aspects (same declination, same hemisphere).

Return type:

list[Aspect]

get_planet_accidental(planet_name, system=None)[source]

Get accidental dignity for a specific planet.

Parameters:
  • planet_name (str) – Name of the planet

  • system (str | None) – House system (defaults to default house system if None)

Return type:

dict[str, Any] | None

Returns:

Accidental dignity data, or None if not found

get_planet_dignity(planet_name, system='traditional')[source]

Get dignity calculation for a specific planet.

Parameters:
  • planet_name (str) – Name of the planet (e.g., “Sun”, “Moon”)

  • system (str) – “traditional” or “modern”

Return type:

dict[str, Any] | None

Returns:

Dignity data for the planet, or None if not found

get_planet_total_score(planet_name, essential_system='traditional', accidental_system=None)[source]

Get combined essential + accidental dignity score.

Parameters:
  • planet_name (str) – Name of the planet

  • essential_system (str) – “traditional” or “modern”

  • accidental_system (str | None) – House system name (defaults to default system)

Return type:

dict[str, Any]

Returns:

Dict with essential, accidental, and total scores

get_planets()[source]

Get all planetary objects.

Return type:

list[CelestialPosition]

get_points()[source]

Get all calculated points (Vertex, Lilith, etc.).

Return type:

list[CelestialPosition]

get_strongest_planet(system='traditional')[source]

Find the planet with the highest dignity score (Almuten).

Parameters:

system (str) – “traditional” or “modern”

Return type:

tuple[str, int] | None

Returns:

Tuple of (planet_name, score) or None if no dignities calculated

house_placements: dict[str, dict[str, int]]
house_systems: dict[str, HouseCusps]
location: ChartLocation
lord_of_year(age, point='ASC', house_system=None, rulership='traditional')[source]

Quick access to Lord of the Year.

The Lord of the Year is the planet ruling the profected sign for a given age. It’s one of the most important predictive indicators in Hellenistic astrology.

Parameters:
  • age (int) – Age in completed years

  • point (str) – Point to profect (default “ASC”)

  • house_system (str | None) – House system to use

  • rulership (str) – “traditional” or “modern” rulers

Return type:

str

Returns:

Name of the ruling planet

Example:

print(chart.lord_of_year(30))  # "Saturn"
metadata: dict[str, Any]
positions: tuple[CelestialPosition, ...]
profection(age=None, date=None, point='ASC', include_monthly=True, house_system=None, rulership='traditional')[source]

Calculate profections for this chart.

Profections move a point forward one sign per year. The planet ruling the profected sign becomes the “Lord of the Year.”

Parameters:
  • age (int | None) – Age in completed years (either age OR date required)

  • date (datetime | str | None) – Specific date (datetime or ISO string)

  • point (str) – Point to profect (default “ASC”)

  • include_monthly (bool) – Whether to include monthly profection (date only)

  • house_system (str | None) – House system to use (default: prefers Whole Sign)

  • rulership (str) – “traditional” or “modern” rulers

Returns:

ProfectionResult, or tuple(annual, monthly) if include_monthly

Example:

# By age
result = chart.profection(age=30)
print(f"Lord of Year 30: {result.ruler}")

# By date (gets both annual and monthly)
annual, monthly = chart.profection(date="2025-06-15")
print(f"Annual: {annual.ruler}, Monthly: {monthly.ruler}")
profection_timeline(start_age, end_age, point='ASC', house_system=None, rulership='traditional')[source]

Generate profections for a range of ages.

Parameters:
  • start_age (int) – First age (inclusive)

  • end_age (int) – Last age (inclusive)

  • point (str) – Point to profect (default “ASC”)

  • house_system (str | None) – House system to use

  • rulership (str) – “traditional” or “modern” rulers

Returns:

ProfectionTimeline with all entries

Example:

timeline = chart.profection_timeline(25, 35)
for entry in timeline.entries:
    print(f"Age {entry.units}: {entry.ruler}")
profections(age=None, date=None, points=None, house_system=None, rulership='traditional')[source]

Profect multiple points at once.

Parameters:
  • age (int | None) – Age in completed years (either age OR date required)

  • date (datetime | str | None) – Specific date (datetime or ISO string)

  • points (list[str] | None) – Points to profect (default: ASC, Sun, Moon, MC)

  • house_system (str | None) – House system to use

  • rulership (str) – “traditional” or “modern” rulers

Returns:

MultiProfectionResult with all profections

Example:

result = chart.profections(age=30)
print(result.lords)  # {"ASC": "Saturn", "Sun": "Mars", ...}
sect()[source]

Check which sect this chart is (day or night) (Sun above the horizon).

Return type:

str | None

Returns:

“day” or “night”

to_dict()[source]

Serialize to dictionary for JSON export.

This enables web API integration, storage, etc.

Return type:

dict[str, Any]

to_prompt_text(sections=None, include_extras=True)[source]

Export chart data as clean, human-readable text suitable for LLM prompts.

Produces a structured markdown-style summary of the chart including planetary positions, house cusps, aspects, declination aspects, and any component results (Arabic Parts, midpoints, fixed stars, dignities, antiscia, aspect patterns, etc.).

Parameters:
  • sections (set[str] | None) – Set of section names to include. When None (the default), every section that has data is included. Valid names: “info”, “positions”, “angles”, “houses”, “aspects”, “declination_aspects”, “arabic_parts”, “midpoints”, “fixed_stars”, “dignities”, “antiscia”, “patterns”, “nodes”, “points”, “extras”.

  • include_extras (bool) – When True (default), automatically picks up data from unknown/future components that aren’t handled by a dedicated section. Set to False to only show data from known, explicitly-supported sections.

Return type:

str

Returns:

A multi-line string ready to paste into an LLM prompt.

Example:

text = chart.to_prompt_text()
prompt = f"Interpret this birth chart:\n\n{text}"

# Only specific sections
text = chart.to_prompt_text(sections={"info", "positions", "aspects"})
voc_moon(aspects='traditional')[source]

Check if the Moon is void of course.

The Moon is void of course (VOC) when it will not complete any major Ptolemaic aspect (conjunction, sextile, square, trine, opposition) before leaving its current sign. This is traditionally considered an inauspicious time for beginning new ventures.

Parameters:

aspects (Literal['traditional', 'modern']) – Planet set to consider: - “traditional”: Sun through Saturn (visible planets) - “modern”: Includes Uranus, Neptune, Pluto

Return type:

VOCMoonResult

Returns:

VOCMoonResult with void status and timing details

Example:

voc = chart.voc_moon()
if voc.is_void:
    print(f"Moon is VOC until {voc.void_until}")
    print(f"Will enter {voc.next_sign}")
else:
    print(f"Moon will {voc.next_aspect}")
    print(f"Aspect perfects at {voc.void_until}")
zodiac_type: Any = None
zodiacal_releasing(lot='Part of Fortune')[source]

Get the zodiacal releasing full life timeline for a given lot.

Parameters:

lot (str) – Name of timeline’s lot (defaults to ‘Part of Fortune’)

Return type:

ZRTimeline

Returns:

Zodiacal releasing full timeline object

zr_at_age(age, lot='Part of Fortune')[source]

Get the zodiacal releasing periods for a given age.

Parameters:
  • age (float) – age in years (float) to fetch for

  • lot (str) – Name of lot (defaults to ‘Part of Fortune’)

Return type:

ZRSnapshot

Returns:

Snapshot of ZR for that age’s datetime

zr_at_date(date, lot='Part of Fortune')[source]

Get the zodiacal releasing periods for a given date.

Parameters:
  • date (datetime) – datetime to fetch for

  • lot (str) – Name of lot (defaults to ‘Part of Fortune’)

Return type:

ZRSnapshot

Returns:

Snapshot of ZR for that datetime

class stellium.core.models.UnknownTimeChart(datetime, location, positions, house_systems=<factory>, house_placements=<factory>, aspects=(), declination_aspects=(), metadata=<factory>, zodiac_type=None, ayanamsa=None, ayanamsa_value=None, calculation_timestamp=<factory>, chart_tags=(), moon_range=None)[source]

Bases: CalculatedChart

A natal chart calculated without known birth time.

Inherits from CalculatedChart for compatibility with all existing code, but has some key differences: - Houses are empty (no house cusps without birth time) - Angles are not calculated (no Asc/MC/Dsc/IC) - Moon is shown as a range rather than single position - Planetary positions are calculated for noon

The chart can still be: - Visualized (without houses, with moon arc) - Exported to JSON - Used for aspect calculations (using noon Moon) - Used for dignity calculations (planets only)

moon_range

MoonRange showing the Moon’s possible positions throughout the day

get_angles()[source]

Angles are not available for unknown time charts.

Return type:

list[CelestialPosition]

get_house(object_name, system_name=None)[source]

Houses are not available for unknown time charts.

Return type:

None

get_houses(system_name=None)[source]

Houses are not available for unknown time charts.

Return type:

None

property is_time_unknown: bool

Always True for this chart type.

moon_range: MoonRange = None
to_dict()[source]

Serialize to dictionary, including moon_range.

Return type:

dict[str, Any]

to_prompt_text(sections=None, include_extras=True)[source]

Export unknown-time chart as prompt text.

Automatically excludes houses and angles sections since they are not available without a birth time.

Return type:

str

class stellium.CelestialPosition(name, object_type, longitude, latitude=0.0, distance=0.0, speed_longitude=0.0, speed_latitude=0.0, speed_distance=0.0, declination=None, right_ascension=None, phase=None)[source]

Bases: object

Immutable representation of a celestial object’s position.

This is the OUTPUT of ephemeris calculations.

declination: float | None = None
property declination_direction: str

‘north’, ‘south’, or ‘none’.

Type:

Direction of declination

distance: float = 0.0
property is_out_of_bounds: bool

Planet is beyond the Sun’s maximum declination (~23°27’).

Out-of-bounds planets are considered to have extra intensity, unpredictability, or unconventional expression in their significations.

The Sun’s declination varies between approximately +23.4367° and -23.4367° (the Tropic of Cancer and Tropic of Capricorn). When a planet exceeds these bounds, it’s “out of bounds.”

Moon, Mercury, Mars, and Venus can go out of bounds. Jupiter, Saturn, and outer planets rarely or never do.

is_retrograde: bool
latitude: float = 0.0
longitude: float
name: str
object_type: ObjectType
phase: PhaseData | None = None
right_ascension: float | None = None
sign: str
sign_degree: float
property sign_position: str

Human-readable sign position (e.g. 15°23’ Aries)

speed_distance: float = 0.0
speed_latitude: float = 0.0
speed_longitude: float = 0.0
class stellium.core.models.MidpointPosition(name, object_type, longitude, latitude=0.0, distance=0.0, speed_longitude=0.0, speed_latitude=0.0, speed_distance=0.0, declination=None, right_ascension=None, phase=None, object1=None, object2=None, is_indirect=False)[source]

Bases: CelestialPosition

Specialized position type for midpoints between two celestial objects.

A midpoint represents the halfway point between two celestial objects, either along the shorter arc (direct) or the longer arc (indirect).

object1

First component object

object2

Second component object

is_indirect

True if this is the indirect (opposite) midpoint

Example

# Sun at 10° Aries, Moon at 20° Aries # Direct midpoint: 15° Aries # Indirect midpoint: 15° Libra (opposite)

midpoint = MidpointPosition(

name=”Midpoint:Sun/Moon”, object_type=ObjectType.MIDPOINT, longitude=15.0, # 15° Aries object1=sun_position, object2=moon_position, is_indirect=False,

)

is_indirect: bool = False
object1: CelestialPosition = None
object2: CelestialPosition = None
class stellium.FixedStarPosition(name, object_type, longitude, latitude=0.0, distance=0.0, speed_longitude=0.0, speed_latitude=0.0, speed_distance=0.0, declination=None, right_ascension=None, phase=None, swe_name='', constellation='', bayer='', tier=2, is_royal=False, magnitude=0.0, nature='', keywords=<factory>)[source]

Bases: CelestialPosition

Position of a fixed star at a specific time.

Extends CelestialPosition with fixed star-specific metadata from the registry, including traditional astrological properties like planetary nature and keywords.

Fixed stars move very slowly due to precession (~1 degree per 72 years), so their positions change slightly between charts. Swiss Ephemeris handles precession automatically based on the Julian Day.

swe_name

Swiss Ephemeris lookup name

constellation

Traditional constellation (e.g., “Leo”)

bayer

Bayer designation (e.g., “Alpha Leonis”)

tier

Star tier (1=Royal, 2=Major, 3=Extended)

is_royal

Whether this is one of the four Royal Stars of Persia

magnitude

Apparent visual magnitude (lower = brighter)

nature

Traditional planetary nature (e.g., “Mars/Jupiter”)

keywords

Interpretive keywords for the star

Example:

regulus = FixedStarPosition(
    name="Regulus",
    object_type=ObjectType.FIXED_STAR,
    longitude=150.12,  # ~0 Virgo (moves slowly through precession)
    swe_name="Regulus",
    constellation="Leo",
    tier=1,
    is_royal=True,
    magnitude=1.35,
    nature="Mars/Jupiter",
    keywords=("royalty", "success", "fame"),
)
bayer: str = ''
constellation: str = ''
is_royal: bool = False
keywords: tuple[str, ...]
magnitude: float = 0.0
nature: str = ''
swe_name: str = ''
tier: int = 2
class stellium.Aspect(object1, object2, aspect_name, aspect_degree, orb, is_applying=None)[source]

Bases: object

Immutable aspect between two objects.

aspect_degree: int
aspect_name: str
property description: str

Human-readable aspect description.

is_applying: bool | None = None
object1: CelestialPosition
object2: CelestialPosition
orb: float
class stellium.core.models.AspectPattern(name, planets, aspects, element=None, quality=None)[source]

Bases: object

Represents a detected aspect pattern in a chart. (e.g., Grand Trine, T-Square, Yod, etc.)

aspects: list[Aspect]
element: str | None = None
property focal_planet: CelestialPosition | None

Get the focal/apex planet for patterns that have one.

name: str
planets: list[CelestialPosition]
quality: str | None = None
to_dict()[source]

Serialize to dictionary for storage/JSON.

Return type:

dict[str, Any]

class stellium.HouseCusps(system, cusps)[source]

Bases: object

Immutable house cusp data.

cusps: tuple[float, ...]
get_cusp(house_number)[source]

Get cusp for a specific house (1-12)

Return type:

float

get_description(house_number)[source]

Get human-readable cusp description for a specific house.

Return type:

str

get_sign(house_number)[source]

Get sign name for a specific house (1-12)

Return type:

str

get_sign_degree(house_number)[source]

Get sign name for a specific house (1-12)

Return type:

str

system: str
class stellium.ChartDateTime(utc_datetime, julian_day, local_datetime=None)[source]

Bases: object

Immutable datetime data for chart calculation.

julian_day: float
local_datetime: datetime | None = None
utc_datetime: datetime
class stellium.ChartLocation(latitude, longitude, name='', timezone='')[source]

Bases: object

Immutable location data for chart calculation.

latitude: float
longitude: float
name: str = ''
timezone: str = ''
class stellium.PhaseData(phase_angle, illuminated_fraction, elongation, apparent_diameter, apparent_magnitude, geocentric_parallax=0.0, sun_longitude=None, moon_longitude=None)[source]

Bases: object

Planetary phase information.

Contains data about a celestial object’s appearance and illumination as seen from Earth. Available for Moon, planets, and some asteroids.

phase_angle

Angular separation from Sun (0-360°) - 0° = conjunction (new moon) - 90° = quadrature (quarter moon) - 180° = opposition (full moon)

illuminated_fraction

Fraction of disk that is illuminated (0.0-1.0) - 0.0 = completely dark (new) - 0.5 = half illuminated (quarter) - 1.0 = fully illuminated (full)

elongation

Elongation of the planet

apparent_diameter

Angular diameter as seen from Earth (arc seconds)

apparent_magnitude

Visual brightness magnitude (lower = brighter)

geocentric_parallax

Parallax angle (radians) - primarily for Moon

apparent_diameter: float
apparent_magnitude: float
elongation: float
geocentric_parallax: float = 0.0
illuminated_fraction: float
property is_waxing: bool

Whether object is waxing (growing in illumination).

For the Moon: waxing when Moon is 0-180° ahead of Sun. Uses Sun/Moon longitudes when available (accurate). Falls back to phase_angle for backward compatibility.

moon_longitude: float | None = None
phase_angle: float
property phase_name: str

Human-readable phase name (primarily for Moon).

Returns:

Phase name like “New”, “Waxing Crescent”, etc.

sun_longitude: float | None = None
class stellium.MultiChart(charts, labels=(), relationships=<factory>, cross_aspects=<factory>, house_overlays=<factory>, calculation_timestamp=<factory>, metadata=<factory>)[source]

Bases: object

Unified multi-chart container supporting 2-4 charts with analysis and visualization.

Supports all chart relationship types: - Synastry (two natal charts) - Transits (natal + transit sky) - Progressions (natal + progressed) - Arc Directions (natal + directed) - Triwheels/Quadwheels (3-4 charts)

Access charts via: - Indexed: mc[0], mc[1], mc.charts[2] - Named: mc.chart1, mc.chart2, mc.chart3, mc.chart4 - Semantic: mc.inner, mc.outer, mc.natal

charts

Tuple of 2-4 CalculatedChart objects

labels

Display labels for each chart

relationships

Per-pair relationship types {(0,1): ComparisonType.SYNASTRY}

cross_aspects

Cross-chart aspects indexed by pair {(0,1): (aspects…)}

house_overlays

House overlays indexed by (planet_chart, house_chart)

calculation_timestamp

When this MultiChart was created

metadata

Additional metadata

calculate_compatibility_score(pair=(0, 1), weights=None)[source]

Calculate a simple compatibility score based on aspects.

This is a basic implementation - users can implement their own weighting schemes.

Parameters:
  • pair (tuple[int, int]) – Which chart pair to score (default: (0, 1))

  • weights (dict[str, float] | None) – Optional custom weights for aspect types

Return type:

float

Returns:

Compatibility score (0-100)

calculation_timestamp: datetime
property chart1: CalculatedChart

Primary chart (innermost ring).

property chart2: CalculatedChart

Second chart.

property chart3: CalculatedChart | None

Third chart (if present).

property chart4: CalculatedChart | None

Fourth chart (if present).

property chart_count: int

Number of charts in this MultiChart.

charts: tuple[CalculatedChart, ...]
cross_aspects: dict[tuple[int, int], tuple[Aspect, ...]]
property datetime

Delegate to chart1’s datetime for visualization compatibility.

draw(filename='multichart.svg')[source]

Start building a multi-chart visualization.

Parameters:

filename (str) – Output filename for the SVG

Return type:

ChartDrawBuilder

Returns:

ChartDrawBuilder configured for this MultiChart

get_all_cross_aspects()[source]

Get all cross-chart aspects flattened into a single list.

Return type:

list[Aspect]

Returns:

List of all Aspect objects from all chart pairs

get_all_house_overlays()[source]

Get all house overlays flattened into a single list.

Return type:

list[HouseOverlay]

Returns:

List of all HouseOverlay objects

get_angles(chart=0)[source]

Get all chart angles from specified chart.

Return type:

list[CelestialPosition]

get_cross_aspects(chart1_idx=0, chart2_idx=1)[source]

Get cross-chart aspects between two specific charts.

Parameters:
  • chart1_idx (int) – First chart index (default: 0)

  • chart2_idx (int) – Second chart index (default: 1)

Return type:

tuple[Aspect, ...]

Returns:

Tuple of Aspect objects

get_house_overlays(planet_chart, house_chart)[source]

Get house overlays for a specific chart pair.

Parameters:
  • planet_chart (int) – Index of chart whose planets to check

  • house_chart (int) – Index of chart whose houses to use

Return type:

tuple[HouseOverlay, ...]

Returns:

Tuple of HouseOverlay objects

get_object(name, chart=0)[source]

Get a celestial object by name from a specific chart.

Parameters:
  • name (str) – Object name (e.g., “Sun”, “Moon”)

  • chart (int) – Chart index (0-based) or 1-based if > 0

Return type:

CelestialPosition | None

Returns:

CelestialPosition or None

get_object_aspects(object_name, chart=0)[source]

Get all cross-chart aspects involving a specific object from a specific chart.

Parameters:
  • object_name (str) – Name of the celestial object (e.g., “Venus”, “Moon”)

  • chart (int) – Chart index (0 = inner/natal, 1 = outer/transit, etc.)

Return type:

list[Aspect]

Returns:

List of Aspect objects involving the specified object

Example

>>> venus_aspects = multichart.get_object_aspects("Venus", chart=0)
>>> for asp in venus_aspects:
...     print(f"Venus {asp.aspect_name} {asp.object2.name}")
get_planets(chart=0)[source]

Get all planetary objects from specified chart.

Return type:

list[CelestialPosition]

get_relationship(idx1, idx2)[source]

Get the relationship type between two charts.

Parameters:
  • idx1 (int) – First chart index

  • idx2 (int) – Second chart index

Return type:

ComparisonType | None

Returns:

ComparisonType or None if not defined

house_overlays: dict[tuple[int, int], tuple[HouseOverlay, ...]]
property inner: CalculatedChart

Semantic alias for innermost chart (chart1).

labels: tuple[str, ...] = ()
property location

Delegate to chart1’s location for visualization compatibility.

metadata: dict[str, Any]
property natal: CalculatedChart

Semantic alias for the natal/base chart (chart1).

property outer: CalculatedChart

Semantic alias for outermost chart.

relationships: dict[tuple[int, int], ComparisonType]
to_dict()[source]

Serialize to dictionary for JSON export.

Return type:

dict[str, Any]

Returns:

Dictionary with full MultiChart data

to_prompt_text(sections=None, include_extras=True)[source]

Export multi-chart data as clean, human-readable text for LLM prompts.

Shows each chart clearly labeled, followed by cross-chart aspects and house overlays.

Parameters:
  • sections (set[str] | None) – Passed through to each individual chart’s to_prompt_text call. None means all sections.

  • include_extras (bool) – Passed through to each chart. When True, picks up data from unknown/future components.

Return type:

str

Returns:

A multi-line string ready to paste into an LLM prompt.

Enums

class stellium.core.models.ObjectType(value)[source]

Bases: Enum

Type of astrological object.

ANGLE = 'angle'
ANTISCION = 'antiscion'
ARABIC_PART = 'arabic_part'
ASTEROID = 'asteroid'
CONTRA_ANTISCION = 'contra_antiscion'
FIXED_STAR = 'fixed_star'
MIDPOINT = 'midpoint'
NODE = 'node'
PLANET = 'planet'
POINT = 'point'
TECHNICAL = 'technical'
class stellium.core.models.ComparisonType(value)[source]

Bases: Enum

Type of chart comparison.

ARC_DIRECTION = 'arc_direction'
PROGRESSION = 'progression'
SYNASTRY = 'synastry'
TRANSIT = 'transit'

Comparison Types

class stellium.Comparison(comparison_type, chart1, chart2, cross_aspects=(), house_overlays=(), chart1_label='Native', chart2_label='Other', calculation_timestamp=<factory>)[source]

Bases: object

Comparison between two charts (synastry or transits).

This class mimics CalculatedChart’s interface while providing cross-chart analysis. It holds two complete charts and calculates their interactions.

property aspects: tuple[Aspect, ...]

Primary chart’s natal aspects (chart1 internal).

calculate_compatibility_score(weights=None)[source]

Calculate a simple compatibility score based on aspects.

This is a basic implementation - users can implement their own weighting schemes.

Parameters:

weights (dict[str, float] | None) – Optional custom weights for aspect types

Return type:

float

Returns:

Compatibility score (0-100)

calculation_timestamp: datetime
chart1: CalculatedChart
chart1_label: str = 'Native'
chart2: CalculatedChart
property chart2_aspects: tuple[Aspect, ...]

Secondary chart’s internal aspects.

property chart2_datetime: ChartDateTime

Secondary chart datetime.

property chart2_houses: HouseCusps

Secondary chart houses.

chart2_label: str = 'Other'
property chart2_location: ChartLocation

Secondary chart location.

property chart2_positions: tuple[CelestialPosition, ...]

Secondary chart positions.

comparison_type: ComparisonType
cross_aspects: tuple[ComparisonAspect, ...] = ()
property datetime: ChartDateTime

Primary chart datetime (chart1/native).

draw(filename='synastry.svg')[source]

Start building a comparison chart visualization with fluent API.

This is a convenience method that creates a ChartDrawBuilder for easy, discoverable comparison chart visualization. It provides synastry-specific presets and a fluent interface for customization.

Parameters:

filename (str) – Output filename for the SVG

Return type:

ChartDrawBuilder

Returns:

ChartDrawBuilder instance for chaining

Example:

# Simple synastry preset
comparison.draw("synastry.svg").preset_synastry().save()

# Custom configuration
comparison.draw("custom.svg").with_theme("celestial").with_moon_phase(
    position="top-left"
).with_chart_info(position="top-right").save()
get_angles(chart=1)[source]

Get all chart angles from specified chart.

Return type:

list[CelestialPosition]

get_object(name, chart=1)[source]

Get a celestial object by name from either chart.

Parameters:
  • name (str) – Object name (e.g., “Sun”, “Moon”)

  • from_chart – Which chart to get from

Return type:

CelestialPosition | None

Returns:

CelestialPosition or None

get_object_aspects(object_name, chart=1)[source]

Get all cross-chart aspects involving a specific object.

Parameters:
  • object_name (str) – Name of the object

  • chart (Literal[1, 2]) – Which chart the object belongs to

Return type:

list[ComparisonAspect]

Returns:

List of ComparisonAspect objects

get_object_houses(object_name, chart=1)[source]

Get house overlays for a specific planet.

Parameters:
  • planet_name – Planet name

  • planet_owner – Which chart owns the planet

Return type:

list[HouseOverlay]

Returns:

List of HouseOverlay objects

get_objects_in_house(house_number, house_owner, planet_owner='both')[source]

Get all planets falling in a specific house.

Parameters:
  • house_number (int) – House number (1-12)

  • house_owner (Literal[1, 2]) – Whose house system to use

  • planet_owner (Literal[1, 2, 'both']) – Whose planets to check (or “both”)

Return type:

list[HouseOverlay]

Returns:

List of HouseOverlay objects

get_planets(chart=1)[source]

Get all planetary objects from specified chart.

Return type:

list[CelestialPosition]

house_overlays: tuple[HouseOverlay, ...] = ()
property houses: HouseCusps

Primary chart houses (chart1/native).

property location: ChartLocation

Primary chart location (chart1/native).

property positions: tuple[CelestialPosition, ...]

Primary chart positions (chart1/native).

to_dict()[source]

Serialize to dictionary for JSON export.

Return type:

dict[str, Any]

Returns:

Dictionary with full comparison data

class stellium.ComparisonAspect(object1, object2, aspect_name, aspect_degree, orb, is_applying=None, chart1_to_chart2=True, in_chart1_house=None, in_chart2_house=None)[source]

Bases: Aspect

Aspect between objects from two different charts.

This extends the base Aspect model with comparison-specific metadata.

aspect_degree: int
aspect_name: str
chart1_to_chart2: bool = True
property description: str

Human-readable aspect description.

in_chart1_house: int | None = None
in_chart2_house: int | None = None
is_applying: bool | None = None
object1: CelestialPosition
object2: CelestialPosition
orb: float
class stellium.HouseOverlay(planet_name, planet_owner, falls_in_house, house_owner, planet_position)[source]

Bases: object

Represents one chart’s planets falling in another chart’s houses.

property description: str
falls_in_house: int
house_owner: Literal['chart1', 'chart2']
planet_name: str
planet_owner: Literal['chart1', 'chart2']
planet_position: CelestialPosition
class stellium.SynthesisChart(datetime, location, positions, house_systems=<factory>, house_placements=<factory>, aspects=(), declination_aspects=(), metadata=<factory>, zodiac_type=None, ayanamsa=None, ayanamsa_value=None, calculation_timestamp=<factory>, chart_tags=(), synthesis_method='', source_chart1=None, source_chart2=None, chart1_label='Chart 1', chart2_label='Chart 2', midpoint_method=None, houses_config=None, location_method=None)[source]

Bases: CalculatedChart

A chart synthesized from two source charts (composite or davison).

Inherits all fields from CalculatedChart: - positions: tuple[CelestialPosition, …] - aspects: tuple[Aspect, …] - house_systems: dict[str, HouseCusps] - house_placements: dict[str, dict] - datetime: ChartDateTime - location: ChartLocation - metadata: dict

And adds synthesis-specific fields.

chart1_label: str = 'Chart 1'

Descriptive label for first chart (e.g., “Alice”, “Natal”)

chart2_label: str = 'Chart 2'

Descriptive label for second chart (e.g., “Bob”, “Transit”)

houses_config: bool | str | None = None

True (derived), False (none), or “place”

Type:

Composite only

location_method: str | None = None

“simple” or “great_circle”

Type:

Davison only

midpoint_method: str | None = None

“short_arc” or “long_arc”

Type:

Composite only

source_chart1: CalculatedChart | None = None

The first source chart (full chart object for reference)

source_chart2: CalculatedChart | None = None

The second source chart (full chart object for reference)

synthesis_method: str = ''

“composite” or “davison”

Type:

The synthesis method used

to_dict()[source]

Extend parent’s to_dict with synthesis-specific fields.

Return type:

dict[str, Any]

to_prompt_text(sections=None, include_extras=True, include_source_charts=False)[source]

Export synthesis chart as prompt text.

Parameters:
  • sections (set[str] | None) – Section names to include (None = all available).

  • include_extras (bool) – Pick up unknown/future component data (default True).

  • include_source_charts (bool) – If True, also include the full prompt text of both source charts for reference.

Return type:

str

Returns:

A multi-line string ready to paste into an LLM prompt.

Profections & Time Lord Techniques

class stellium.ProfectionEngine(chart, house_system=None, rulership='traditional')[source]

Bases: object

General-purpose profection calculator.

Profections move a point forward one sign per unit of time (year, month, day). This engine handles all the complexity of looking up houses, rulers, and finding what planets are activated.

Parameters:
  • chart (CalculatedChart) – The natal chart to profect from

  • house_system (str | None) – House system to use (default “Whole Sign” - traditional)

  • rulership (Literal['traditional', 'modern']) – Rulership system (“traditional” or “modern”)

Example

>>> engine = ProfectionEngine(chart)
>>> result = engine.annual(30)  # Age 30 profection
>>> print(result.ruler)  # Lord of the Year
DEFAULT_POINTS = ['ASC', 'Sun', 'Moon', 'MC']
annual(age, point='ASC')[source]

Annual profection for a given age.

This is the most common use case: what house and lord are activated for a specific year of life?

Parameters:
  • age (int) – Age in completed years (0 = first year of life)

  • point (str) – Point to profect (default “ASC”)

Return type:

ProfectionResult

Returns:

ProfectionResult for that age

Example

>>> result = engine.annual(30)
>>> print(f"At age 30: {result.profected_sign} year")
>>> print(f"Lord of the Year: {result.ruler}")
for_date(date, point='ASC', include_monthly=True)[source]

Calculate profections for a specific date.

If include_monthly is True, returns both annual and monthly profection.

Parameters:
  • date (datetime | str) – Target date (datetime or ISO string)

  • point (str) – Point to profect (default “ASC”)

  • include_monthly (bool) – Whether to calculate monthly profection too

Return type:

ProfectionResult | tuple[ProfectionResult, ProfectionResult]

Returns:

ProfectionResult, or tuple of (annual, monthly) if include_monthly

Example

>>> annual, monthly = engine.for_date("2025-06-15")
>>> print(f"Year: {annual.ruler}, Month: {monthly.ruler}")
lord_of_month(age, month, point='ASC')[source]

Convenience: just get the Lord of the Month.

Parameters:
  • age (int) – Age in completed years

  • month (int) – Month within profection year (0-11)

  • point (str) – Point to profect (default “ASC”)

Return type:

str

Returns:

Name of the ruling planet

lord_of_year(age, point='ASC')[source]

Convenience: just get the Lord of the Year.

Parameters:
  • age (int) – Age in completed years

  • point (str) – Point to profect (default “ASC”)

Return type:

str

Returns:

Name of the ruling planet

Example

>>> print(engine.lord_of_year(30))  # "Saturn"
monthly(age, month, point='ASC')[source]

Monthly profection within a given year.

Profects forward by (age + month) signs total.

Parameters:
  • age (int) – Age in completed years

  • month (int) – Month within the profection year (0-11)

  • point (str) – Point to profect (default “ASC”)

Return type:

ProfectionResult

Returns:

ProfectionResult for that month

Example

>>> # 4th month of age 30 year
>>> result = engine.monthly(age=30, month=4)
>>> print(f"Month 4: {result.profected_sign}")
multi(age, points=None)[source]

Profect multiple points at once.

Useful for seeing all the lords for a given age - who rules the profected ASC, Sun, Moon, MC?

Parameters:
  • age (int) – Age in completed years

  • points (list[str] | None) – Points to profect (default: ASC, Sun, Moon, MC)

Return type:

MultiProfectionResult

Returns:

MultiProfectionResult with all profections

Example

>>> results = engine.multi(30)
>>> print(results.lords)  # {"ASC": "Saturn", "Sun": "Mars", ...}
multi_for_date(date, points=None)[source]

Profect multiple points for a specific date.

Parameters:
  • date (datetime | str) – Target date

  • points (list[str] | None) – Points to profect (default: ASC, Sun, Moon, MC)

Return type:

MultiProfectionResult

Returns:

MultiProfectionResult with date attached

profect(point, units, unit_type='year')[source]

Core profection operation.

Profects any point forward by N signs and returns everything you’d want to know about the result.

Parameters:
  • point (str) – Point to profect (“ASC”, “Sun”, “Moon”, “MC”, etc.)

  • units (int) – Number of signs to move forward (typically age for years)

  • unit_type (str) – Type of profection (“year”, “month”, “day”)

Return type:

ProfectionResult

Returns:

ProfectionResult with full details

Example

>>> result = engine.profect("ASC", units=30, unit_type="year")
>>> print(f"House {result.profected_house}: {result.profected_sign}")
timeline(start_age, end_age, point='ASC')[source]

Generate profections for a range of ages.

Useful for seeing the sequence of lords through life, or for timeline visualizations.

Parameters:
  • start_age (int) – First age (inclusive)

  • end_age (int) – Last age (inclusive)

  • point (str) – Point to profect (default “ASC”)

Return type:

ProfectionTimeline

Returns:

ProfectionTimeline with all entries

Example

>>> timeline = engine.timeline(25, 35)
>>> for entry in timeline.entries:
...     print(f"Age {entry.units}: {entry.ruler}")
class stellium.ProfectionResult(source_point, source_sign, source_house, units, unit_type, profected_house, profected_sign, ruler, ruler_position, ruler_house, ruler_modern, planets_in_house=<factory>)[source]

Bases: object

Result of profecting a single point.

Contains everything you’d want to know about a profection: what was profected, where it landed, who rules it, and what’s there.

source_point

Name of the profected point (“ASC”, “Sun”, etc.)

source_sign

The sign the point is in natally

source_house

The house the point is in natally (1-12)

units

How many signs forward the point moved

unit_type

Type of profection (“year”, “month”, “day”)

profected_house

The house that is activated (1-12)

profected_sign

The sign on that house cusp

ruler

Traditional ruler of the profected sign (Lord of Year/Month)

ruler_position

Natal position of the ruling planet

ruler_house

Which house the ruler is in natally

ruler_modern

Modern ruler if different from traditional

planets_in_house

List of natal planets in the profected house

planets_in_house: tuple[CelestialPosition, ...]
profected_house: int
profected_sign: str
ruler: str
ruler_house: int | None
ruler_modern: str | None
ruler_position: CelestialPosition | None
source_house: int
source_point: str
source_sign: str
unit_type: str
units: int
class stellium.MultiProfectionResult(age, date, results)[source]

Bases: object

Profections from multiple points for the same time period.

Useful for seeing all the lords at once - e.g., who rules the profected ASC, Sun, Moon, MC, and Fortune for age 30.

age

The age for these profections

date

Optional specific date (for monthly profections)

results

Dictionary of ProfectionResult keyed by point name

property activated_houses: dict[str, int]

Get all activated houses by point name.

age: int
date: datetime | None
property lords: dict[str, str]

Get all lords by point name.

results: dict[str, ProfectionResult]
class stellium.ProfectionTimeline(point, start_age, end_age, entries)[source]

Bases: object

A range of profections over time.

Useful for seeing the sequence of lords through a span of life, or for displaying in a timeline visualization.

point

The point being profected (e.g., “ASC”)

start_age

First age in the timeline

end_age

Last age in the timeline

entries

List of ProfectionResult for each age

end_age: int
entries: tuple[ProfectionResult, ...]
find_by_lord(lord)[source]

Find all years ruled by a specific planet.

Return type:

list[ProfectionResult]

lords_sequence()[source]

Get the sequence of lords.

Return type:

list[str]

point: str
start_age: int

I/O Functions

stellium.parse_aaf(path)[source]

Parse an AAF (Astrodienst Astrological Format) file into Native objects.

AAF is the export format from astro.com. Each chart record consists of two lines: #A93 (human-readable data) and #B93 (computed values).

Parameters:

path (str | Path) – Path to the AAF file

Return type:

list[Native]

Returns:

List of Native objects, one per chart in the file

Raises:

Example

>>> natives = parse_aaf("my_charts.aaf")
>>> len(natives)
20
>>> natives[0].name
'Kate Louie'
>>> chart = ChartBuilder.from_native(natives[0]).calculate()
stellium.parse_csv(path, mapping=None, *, delimiter=',', encoding='utf-8', skip_errors=True)[source]

Parse a CSV file containing birth data into Native objects.

This function supports flexible CSV formats through column mapping. If no mapping is provided, it will auto-detect columns based on common naming conventions.

Parameters:
  • path (str | Path) – Path to the CSV file

  • mapping (CSVColumnMapping | None) – Optional column mapping configuration. If None, auto-detects columns from headers.

  • delimiter (str) – CSV delimiter character (default: comma)

  • encoding (str) – File encoding (default: utf-8)

  • skip_errors (bool) – If True, skip rows that fail to parse and continue. If False, raise an exception on the first error.

Return type:

list[Native]

Returns:

List of Native objects, one per valid row in the CSV

Raises:
  • FileNotFoundError – If the file doesn’t exist

  • ValueError – If required columns are missing or skip_errors=False and a row fails to parse

Example

# Auto-detect columns >>> natives = parse_csv(“birth_data.csv”)

# Custom column mapping >>> mapping = CSVColumnMapping( … name=”Full Name”, … date=”DOB”, … time=”Birth Time”, … location=”Birth Place”, … ) >>> natives = parse_csv(“birth_data.csv”, mapping=mapping)

# With date format hint for ambiguous dates >>> mapping = CSVColumnMapping( … date=”date”, … date_format=”%d/%m/%Y”, # European format … ) >>> natives = parse_csv(“european_data.csv”, mapping=mapping)

stellium.read_csv(path, *, name=None, datetime=None, date=None, time=None, location=None, latitude=None, longitude=None, date_format=None, time_format=None)[source]

Simple interface for reading CSV files with common column configurations.

This is a convenience wrapper around parse_csv() that allows specifying column names as keyword arguments.

Parameters:
  • path (str | Path) – Path to the CSV file

  • name (str | tuple[str, str] | None) – Column name for person/event name, or tuple of (first, last)

  • datetime (str | None) – Column name for combined datetime

  • date (str | None) – Column name for date

  • time (str | None) – Column name for time

  • location (str | None) – Column name for location string

  • latitude (str | None) – Column name for latitude

  • longitude (str | None) – Column name for longitude

  • date_format (str | None) – strptime format for dates (e.g., “%d/%m/%Y”)

  • time_format (str | None) – strptime format for times (e.g., “%I:%M %p”)

Return type:

list[Native]

Returns:

List of Native objects

Example

# Simple auto-detection >>> natives = read_csv(“data.csv”)

# Specify key columns >>> natives = read_csv( … “data.csv”, … name=”Full Name”, … date=”DOB”, … time=”Birth Time”, … location=”City”, … )

# Combined first/last name >>> natives = read_csv( … “data.csv”, … name=(“First Name”, “Last Name”), … datetime=”Birth DateTime”, … latitude=”Lat”, … longitude=”Long”, … )

stellium.parse_dataframe(df, mapping=None, *, skip_errors=True)[source]

Parse a pandas DataFrame containing birth data into Native objects.

This function supports flexible DataFrame formats through column mapping. If no mapping is provided, it will auto-detect columns based on common naming conventions.

Parameters:
  • df (pd.DataFrame) – pandas DataFrame with birth data

  • mapping (CSVColumnMapping | None) – Optional column mapping configuration. If None, auto-detects columns from DataFrame column names.

  • skip_errors (bool) – If True, skip rows that fail to parse and continue. If False, raise an exception on the first error.

Return type:

list[Native]

Returns:

List of Native objects, one per valid row in the DataFrame

Raises:
  • ImportError – If pandas is not installed

  • ValueError – If required columns are missing or skip_errors=False and a row fails to parse

Example

>>> import pandas as pd
>>> from stellium.io import parse_dataframe
>>>
>>> df = pd.DataFrame({
...     "name": ["Kate Louie", "Albert Einstein"],
...     "date": ["1994-01-06", "1879-03-14"],
...     "time": ["11:47", "11:30"],
...     "latitude": [37.3861, 48.4011],
...     "longitude": [-122.0839, 9.9876],
... })
>>> natives = parse_dataframe(df)
>>> len(natives)
2
>>> # With custom column mapping
>>> mapping = CSVColumnMapping(
...     name="Full Name",
...     date="DOB",
...     latitude="Lat",
...     longitude="Lon",
... )
>>> natives = parse_dataframe(df, mapping=mapping)
stellium.read_dataframe(df, *, name=None, datetime=None, date=None, time=None, location=None, latitude=None, longitude=None, date_format=None, time_format=None)[source]

Simple interface for reading pandas DataFrames with common column configurations.

This is a convenience wrapper around parse_dataframe() that allows specifying column names as keyword arguments.

Parameters:
  • df (pd.DataFrame) – pandas DataFrame with birth data

  • name (str | tuple[str, str] | None) – Column name for person/event name, or tuple of (first, last)

  • datetime (str | None) – Column name for combined datetime

  • date (str | None) – Column name for date

  • time (str | None) – Column name for time

  • location (str | None) – Column name for location string

  • latitude (str | None) – Column name for latitude

  • longitude (str | None) – Column name for longitude

  • date_format (str | None) – strptime format for dates (e.g., “%d/%m/%Y”)

  • time_format (str | None) – strptime format for times (e.g., “%I:%M %p”)

Return type:

list[Native]

Returns:

List of Native objects

Example

>>> import pandas as pd
>>> from stellium.io import read_dataframe
>>>
>>> df = pd.DataFrame({
...     "Person": ["Kate Louie"],
...     "Birthday": ["1994-01-06"],
...     "Birth Time": ["11:47"],
...     "Lat": [37.3861],
...     "Long": [-122.0839],
... })
>>>
>>> natives = read_dataframe(
...     df,
...     name="Person",
...     date="Birthday",
...     time="Birth Time",
...     latitude="Lat",
...     longitude="Long",
... )
stellium.dataframe_from_natives(natives, *, include_coords=True, include_timezone=False)[source]

Convert a list of Native objects back to a pandas DataFrame.

This is useful for exporting processed data or for round-trip operations.

Parameters:
  • natives (list[Native]) – List of Native objects to convert

  • include_coords (bool) – Include latitude/longitude columns (default: True)

  • include_timezone (bool) – Include timezone column (default: False)

Return type:

pd.DataFrame

Returns:

pandas DataFrame with birth data

Example

>>> from stellium.io import parse_csv, dataframe_from_natives
>>>
>>> natives = parse_csv("birth_data.csv")
>>> df = dataframe_from_natives(natives)
>>> df.to_excel("birth_data.xlsx")  # Export to Excel

Registry Functions

stellium.get_object_info(name)[source]

Get celestial object info by name.

Parameters:

name (str) – The technical name of the object (e.g., “Mean Apogee”, “Sun”)

Return type:

CelestialObjectInfo | None

Returns:

CelestialObjectInfo if found, None otherwise

stellium.get_aspect_info(name)[source]

Get aspect information by name.

Parameters:

name (str) – The aspect name to look up (e.g., “Conjunction”, “Trine”)

Return type:

AspectInfo | None

Returns:

AspectInfo object if found, None otherwise

stellium.get_fixed_star_info(name)[source]

Get fixed star info by name.

Parameters:

name (str) – The name of the star (e.g., “Regulus”, “Algol”)

Return type:

FixedStarInfo | None

Returns:

FixedStarInfo if found, None otherwise

stellium.get_royal_stars()[source]

Get the four Royal Stars of Persia.

Return type:

list[FixedStarInfo]

Returns:

List of the four royal stars (Aldebaran, Regulus, Antares, Fomalhaut)

stellium.get_stars_by_tier(tier)[source]

Get all fixed stars of a specific tier.

Parameters:

tier (int) – The tier level (1=Royal, 2=Major, 3=Extended)

Return type:

list[FixedStarInfo]

Returns:

List of FixedStarInfo matching the tier

stellium.get_notable_registry()[source]

Get the global notable registry instance (lazy import).


Core (stellium.core)

Core abstractions, data models, protocols, and configuration.

Models (stellium.core.models)

Immutable data models for astrological calculations.

These are pure data containers - no business logic, no calculations.

They represent the OUTPUT of calculations, not the process.

class stellium.core.models.Aspect(object1, object2, aspect_name, aspect_degree, orb, is_applying=None)[source]

Bases: object

Immutable aspect between two objects.

aspect_degree: int
aspect_name: str
property description: str

Human-readable aspect description.

is_applying: bool | None = None
object1: CelestialPosition
object2: CelestialPosition
orb: float
class stellium.core.models.AspectPattern(name, planets, aspects, element=None, quality=None)[source]

Bases: object

Represents a detected aspect pattern in a chart. (e.g., Grand Trine, T-Square, Yod, etc.)

aspects: list[Aspect]
element: str | None = None
property focal_planet: CelestialPosition | None

Get the focal/apex planet for patterns that have one.

name: str
planets: list[CelestialPosition]
quality: str | None = None
to_dict()[source]

Serialize to dictionary for storage/JSON.

Return type:

dict[str, Any]

class stellium.core.models.CalculatedChart(datetime, location, positions, house_systems=<factory>, house_placements=<factory>, aspects=(), declination_aspects=(), metadata=<factory>, zodiac_type=None, ayanamsa=None, ayanamsa_value=None, calculation_timestamp=<factory>, chart_tags=())[source]

Bases: object

Complete calculated chart - the final output.

This is what a ChartBuilder returns. It’s immutable and contains everything you need to analyze or visualize the chart.

aspects: tuple[Aspect, ...] = ()
available_components()[source]

List all components and analyzers whose results are available.

Return type:

list[str]

Returns:

Sorted list of component/analyzer names that can be passed to get_component_result().

Examples:

chart.available_components()
# ['Arabic Parts', 'Aspect Patterns', 'Essential Dignities']
ayanamsa: str | None = None
ayanamsa_value: float | None = None
bazi()[source]

Calculate the BaZi (Four Pillars / 八字) chart for this birth data.

Uses the chart’s datetime and location to compute the Chinese Four Pillars, reusing the already-resolved timezone information.

Returns:

A BaZiChart with all four pillars, ready for analysis.

Example:

chart = ChartBuilder.from_details("1994-01-06 11:47", "Palo Alto, CA").calculate()
bazi = chart.bazi()
print(bazi.hanzi)           # Eight characters
print(bazi.day_master)      # Day Master stem
print(bazi.strength())      # Strength analysis
calculation_timestamp: datetime
chart_tags: tuple[str, ...] = ()
datetime: ChartDateTime
declination_aspects: tuple[Aspect, ...] = ()
property default_house_system: str
draconic()[source]

Return a draconic version of this chart.

The draconic chart rotates all positions so that the North Node is at 0° Aries. This is sometimes called the “soul chart” and represents the soul’s orientation before incarnation.

The transformation subtracts the North Node’s longitude from all positions and normalizes to 0-360°.

Return type:

CalculatedChart

Returns:

A new CalculatedChart with all longitudes rotated and “draconic” added to chart_tags.

Example:

draconic = chart.draconic()
print(draconic.get_object("Sun").sign_position)
draconic.draw("draconic.svg").save()
draw(filename='chart.svg')[source]

Start building a chart visualization with fluent API.

This is a convenience method that creates a ChartDrawBuilder for easy, discoverable chart visualization. It provides presets and a fluent interface for customization.

Parameters:

filename (str) – Output filename for the SVG

Return type:

ChartDrawBuilder

Returns:

ChartDrawBuilder instance for chaining

Example:

# Simple preset
chart.draw("my_chart.svg").preset_standard().save()

# Custom configuration
chart.draw("custom.svg").with_theme("dark").with_moon_phase(
    position="top-left", show_label=True
).with_chart_info(position="top-right").save()
draw_dial(filename='dial.svg', degrees=90)[source]

Draw a Uranian/Hamburg school dial chart.

Creates a dial visualization that compresses the zodiac to reveal hard aspects. On a 90° dial, conjunctions, squares, and oppositions all appear as conjunctions.

Parameters:
  • filename (str) – Output filename for the SVG

  • degrees (int) – Dial size - 90 (default), 45, or 360

Return type:

DialDrawBuilder

Returns:

DialDrawBuilder instance for chaining

Example:

# Basic 90° dial
chart.draw_dial("dial.svg").save()

# With theme
chart.draw_dial("dial.svg").with_theme("midnight").save()

# With transits on outer ring
chart.draw_dial("dial.svg")
    .with_outer_ring(transit_chart.get_planets(), label="Transits")
    .save()

# 360° dial with pointer to Sun
chart.draw_dial("dial.svg", degrees=360)
    .with_pointer(pointing_to="Sun")
    .save()
draw_vedic(filename='vedic_chart.svg', style='north_indian', theme='classic', label_style='abbreviation', show_degrees=True, size=500)[source]

Draw a Vedic (Jyotish) chart in North Indian or South Indian style.

Parameters:
  • filename (str) – Output filename for the SVG

  • style (str) – “north_indian” (diamond) or “south_indian” (grid)

  • theme (str) – “classic”, “dark”, or “traditional”

  • label_style (str) – “abbreviation” (Ari, Su), “number” (1, 2), “glyph” (unicode symbols), or “full” (Aries, Sun)

  • show_degrees (bool) – Show degree + minutes for each planet

  • size (int) – SVG width/height in pixels

Return type:

CalculatedChart

Returns:

self (for chaining)

Example:

chart.draw_vedic("north.svg", style="north_indian")
chart.draw_vedic("south.svg", style="south_indian", theme="traditional")
get_accidental_dignities(system=None)[source]

Get accidental dignity calculations.

Parameters:

system (str | None) – Specific house system (“Placidus”). If None returns all systems.

Return type:

dict[str, Any]

Returns:

Dictionary of planetary accidental dignities

get_all_accidental_dignities()[source]

Get all accidental dignities (entire object).

Return type:

dict[str, Any]

get_angles()[source]

Get all chart angles.

Return type:

list[CelestialPosition]

get_component_result(name)[source]

Get the result of a component or analyzer by name.

Works with any component added via .add_component() or analyzer added via .add_analyzer() on ChartBuilder.

Parameters:

name (str) – The component or analyzer name (e.g., “Arabic Parts”, “Essential Dignities”, “Aspect Patterns”). Also accepts the metadata key as an alias (e.g., “dignities”).

Returns:

list of CelestialPosition objects - For metadata-based components/analyzers: the stored dict or list - For dual-storage components: dict with “positions” and “metadata” keys

Return type:

  • For position-based components

Raises:

KeyError – If no component with that name was used. The error message lists all available component names.

Examples:

parts = chart.get_component_result("Arabic Parts")
dignities = chart.get_component_result("Essential Dignities")
patterns = chart.get_component_result("Aspect Patterns")
chart.available_components()
get_contraparallels()[source]

Get all contraparallel aspects (same declination, opposite hemispheres).

Return type:

list[Aspect]

get_declination_aspects(aspect_type=None)[source]

Get declination aspects (Parallel and Contraparallel).

Parameters:

aspect_type (str | None) – Filter to “Parallel” or “Contraparallel”. None returns all declination aspects.

Return type:

list[Aspect]

Returns:

List of declination aspects

get_dignities(system='traditional')[source]

Get essential dignity calculations.

Parameters:

system (str) – “traditional” or “modern”

Return type:

dict[str, Any]

Returns:

Dictionary of planet dignities, or empty dict if not calculated

get_house(object_name, system_name=None)[source]

Helper method to get the house number for a specific object in a specific system.

Return type:

int | None

get_houses(system_name=None)[source]

Get all cusps for a specific system (or default system).

Return type:

HouseCusps

get_mutual_receptions(system='traditional')[source]

Get all mutual receptions in the chart.

Parameters:

system (str) – “traditional” or “modern”

Return type:

list[dict[str, Any]]

Returns:

List of mutual reception dictionaries

get_nodes()[source]

Get all nodes (True Node, South Node, etc.).

Return type:

list[CelestialPosition]

get_object(name)[source]

Get a celestial object by name.

Return type:

CelestialPosition | None

get_parallels()[source]

Get all parallel aspects (same declination, same hemisphere).

Return type:

list[Aspect]

get_planet_accidental(planet_name, system=None)[source]

Get accidental dignity for a specific planet.

Parameters:
  • planet_name (str) – Name of the planet

  • system (str | None) – House system (defaults to default house system if None)

Return type:

dict[str, Any] | None

Returns:

Accidental dignity data, or None if not found

get_planet_dignity(planet_name, system='traditional')[source]

Get dignity calculation for a specific planet.

Parameters:
  • planet_name (str) – Name of the planet (e.g., “Sun”, “Moon”)

  • system (str) – “traditional” or “modern”

Return type:

dict[str, Any] | None

Returns:

Dignity data for the planet, or None if not found

get_planet_total_score(planet_name, essential_system='traditional', accidental_system=None)[source]

Get combined essential + accidental dignity score.

Parameters:
  • planet_name (str) – Name of the planet

  • essential_system (str) – “traditional” or “modern”

  • accidental_system (str | None) – House system name (defaults to default system)

Return type:

dict[str, Any]

Returns:

Dict with essential, accidental, and total scores

get_planets()[source]

Get all planetary objects.

Return type:

list[CelestialPosition]

get_points()[source]

Get all calculated points (Vertex, Lilith, etc.).

Return type:

list[CelestialPosition]

get_strongest_planet(system='traditional')[source]

Find the planet with the highest dignity score (Almuten).

Parameters:

system (str) – “traditional” or “modern”

Return type:

tuple[str, int] | None

Returns:

Tuple of (planet_name, score) or None if no dignities calculated

house_placements: dict[str, dict[str, int]]
house_systems: dict[str, HouseCusps]
location: ChartLocation
lord_of_year(age, point='ASC', house_system=None, rulership='traditional')[source]

Quick access to Lord of the Year.

The Lord of the Year is the planet ruling the profected sign for a given age. It’s one of the most important predictive indicators in Hellenistic astrology.

Parameters:
  • age (int) – Age in completed years

  • point (str) – Point to profect (default “ASC”)

  • house_system (str | None) – House system to use

  • rulership (str) – “traditional” or “modern” rulers

Return type:

str

Returns:

Name of the ruling planet

Example:

print(chart.lord_of_year(30))  # "Saturn"
metadata: dict[str, Any]
positions: tuple[CelestialPosition, ...]
profection(age=None, date=None, point='ASC', include_monthly=True, house_system=None, rulership='traditional')[source]

Calculate profections for this chart.

Profections move a point forward one sign per year. The planet ruling the profected sign becomes the “Lord of the Year.”

Parameters:
  • age (int | None) – Age in completed years (either age OR date required)

  • date (datetime | str | None) – Specific date (datetime or ISO string)

  • point (str) – Point to profect (default “ASC”)

  • include_monthly (bool) – Whether to include monthly profection (date only)

  • house_system (str | None) – House system to use (default: prefers Whole Sign)

  • rulership (str) – “traditional” or “modern” rulers

Returns:

ProfectionResult, or tuple(annual, monthly) if include_monthly

Example:

# By age
result = chart.profection(age=30)
print(f"Lord of Year 30: {result.ruler}")

# By date (gets both annual and monthly)
annual, monthly = chart.profection(date="2025-06-15")
print(f"Annual: {annual.ruler}, Monthly: {monthly.ruler}")
profection_timeline(start_age, end_age, point='ASC', house_system=None, rulership='traditional')[source]

Generate profections for a range of ages.

Parameters:
  • start_age (int) – First age (inclusive)

  • end_age (int) – Last age (inclusive)

  • point (str) – Point to profect (default “ASC”)

  • house_system (str | None) – House system to use

  • rulership (str) – “traditional” or “modern” rulers

Returns:

ProfectionTimeline with all entries

Example:

timeline = chart.profection_timeline(25, 35)
for entry in timeline.entries:
    print(f"Age {entry.units}: {entry.ruler}")
profections(age=None, date=None, points=None, house_system=None, rulership='traditional')[source]

Profect multiple points at once.

Parameters:
  • age (int | None) – Age in completed years (either age OR date required)

  • date (datetime | str | None) – Specific date (datetime or ISO string)

  • points (list[str] | None) – Points to profect (default: ASC, Sun, Moon, MC)

  • house_system (str | None) – House system to use

  • rulership (str) – “traditional” or “modern” rulers

Returns:

MultiProfectionResult with all profections

Example:

result = chart.profections(age=30)
print(result.lords)  # {"ASC": "Saturn", "Sun": "Mars", ...}
sect()[source]

Check which sect this chart is (day or night) (Sun above the horizon).

Return type:

str | None

Returns:

“day” or “night”

to_dict()[source]

Serialize to dictionary for JSON export.

This enables web API integration, storage, etc.

Return type:

dict[str, Any]

to_prompt_text(sections=None, include_extras=True)[source]

Export chart data as clean, human-readable text suitable for LLM prompts.

Produces a structured markdown-style summary of the chart including planetary positions, house cusps, aspects, declination aspects, and any component results (Arabic Parts, midpoints, fixed stars, dignities, antiscia, aspect patterns, etc.).

Parameters:
  • sections (set[str] | None) – Set of section names to include. When None (the default), every section that has data is included. Valid names: “info”, “positions”, “angles”, “houses”, “aspects”, “declination_aspects”, “arabic_parts”, “midpoints”, “fixed_stars”, “dignities”, “antiscia”, “patterns”, “nodes”, “points”, “extras”.

  • include_extras (bool) – When True (default), automatically picks up data from unknown/future components that aren’t handled by a dedicated section. Set to False to only show data from known, explicitly-supported sections.

Return type:

str

Returns:

A multi-line string ready to paste into an LLM prompt.

Example:

text = chart.to_prompt_text()
prompt = f"Interpret this birth chart:\n\n{text}"

# Only specific sections
text = chart.to_prompt_text(sections={"info", "positions", "aspects"})
voc_moon(aspects='traditional')[source]

Check if the Moon is void of course.

The Moon is void of course (VOC) when it will not complete any major Ptolemaic aspect (conjunction, sextile, square, trine, opposition) before leaving its current sign. This is traditionally considered an inauspicious time for beginning new ventures.

Parameters:

aspects (Literal['traditional', 'modern']) – Planet set to consider: - “traditional”: Sun through Saturn (visible planets) - “modern”: Includes Uranus, Neptune, Pluto

Return type:

VOCMoonResult

Returns:

VOCMoonResult with void status and timing details

Example:

voc = chart.voc_moon()
if voc.is_void:
    print(f"Moon is VOC until {voc.void_until}")
    print(f"Will enter {voc.next_sign}")
else:
    print(f"Moon will {voc.next_aspect}")
    print(f"Aspect perfects at {voc.void_until}")
zodiac_type: Any = None
zodiacal_releasing(lot='Part of Fortune')[source]

Get the zodiacal releasing full life timeline for a given lot.

Parameters:

lot (str) – Name of timeline’s lot (defaults to ‘Part of Fortune’)

Return type:

ZRTimeline

Returns:

Zodiacal releasing full timeline object

zr_at_age(age, lot='Part of Fortune')[source]

Get the zodiacal releasing periods for a given age.

Parameters:
  • age (float) – age in years (float) to fetch for

  • lot (str) – Name of lot (defaults to ‘Part of Fortune’)

Return type:

ZRSnapshot

Returns:

Snapshot of ZR for that age’s datetime

zr_at_date(date, lot='Part of Fortune')[source]

Get the zodiacal releasing periods for a given date.

Parameters:
  • date (datetime) – datetime to fetch for

  • lot (str) – Name of lot (defaults to ‘Part of Fortune’)

Return type:

ZRSnapshot

Returns:

Snapshot of ZR for that datetime

class stellium.core.models.CelestialPosition(name, object_type, longitude, latitude=0.0, distance=0.0, speed_longitude=0.0, speed_latitude=0.0, speed_distance=0.0, declination=None, right_ascension=None, phase=None)[source]

Bases: object

Immutable representation of a celestial object’s position.

This is the OUTPUT of ephemeris calculations.

declination: float | None = None
property declination_direction: str

‘north’, ‘south’, or ‘none’.

Type:

Direction of declination

distance: float = 0.0
property is_out_of_bounds: bool

Planet is beyond the Sun’s maximum declination (~23°27’).

Out-of-bounds planets are considered to have extra intensity, unpredictability, or unconventional expression in their significations.

The Sun’s declination varies between approximately +23.4367° and -23.4367° (the Tropic of Cancer and Tropic of Capricorn). When a planet exceeds these bounds, it’s “out of bounds.”

Moon, Mercury, Mars, and Venus can go out of bounds. Jupiter, Saturn, and outer planets rarely or never do.

is_retrograde: bool
latitude: float = 0.0
longitude: float
name: str
object_type: ObjectType
phase: PhaseData | None = None
right_ascension: float | None = None
sign: str
sign_degree: float
property sign_position: str

Human-readable sign position (e.g. 15°23’ Aries)

speed_distance: float = 0.0
speed_latitude: float = 0.0
speed_longitude: float = 0.0
class stellium.core.models.ChartDateTime(utc_datetime, julian_day, local_datetime=None)[source]

Bases: object

Immutable datetime data for chart calculation.

julian_day: float
local_datetime: datetime | None = None
utc_datetime: datetime
class stellium.core.models.ChartLocation(latitude, longitude, name='', timezone='')[source]

Bases: object

Immutable location data for chart calculation.

latitude: float
longitude: float
name: str = ''
timezone: str = ''
class stellium.core.models.ComparisonAspect(object1, object2, aspect_name, aspect_degree, orb, is_applying=None, chart1_to_chart2=True, in_chart1_house=None, in_chart2_house=None)[source]

Bases: Aspect

Aspect between objects from two different charts.

This extends the base Aspect model with comparison-specific metadata.

aspect_degree: int
aspect_name: str
chart1_to_chart2: bool = True
property description: str

Human-readable aspect description.

in_chart1_house: int | None = None
in_chart2_house: int | None = None
is_applying: bool | None = None
object1: CelestialPosition
object2: CelestialPosition
orb: float
class stellium.core.models.ComparisonType(value)[source]

Bases: Enum

Type of chart comparison.

ARC_DIRECTION = 'arc_direction'
PROGRESSION = 'progression'
SYNASTRY = 'synastry'
TRANSIT = 'transit'
class stellium.core.models.FixedStarPosition(name, object_type, longitude, latitude=0.0, distance=0.0, speed_longitude=0.0, speed_latitude=0.0, speed_distance=0.0, declination=None, right_ascension=None, phase=None, swe_name='', constellation='', bayer='', tier=2, is_royal=False, magnitude=0.0, nature='', keywords=<factory>)[source]

Bases: CelestialPosition

Position of a fixed star at a specific time.

Extends CelestialPosition with fixed star-specific metadata from the registry, including traditional astrological properties like planetary nature and keywords.

Fixed stars move very slowly due to precession (~1 degree per 72 years), so their positions change slightly between charts. Swiss Ephemeris handles precession automatically based on the Julian Day.

swe_name

Swiss Ephemeris lookup name

constellation

Traditional constellation (e.g., “Leo”)

bayer

Bayer designation (e.g., “Alpha Leonis”)

tier

Star tier (1=Royal, 2=Major, 3=Extended)

is_royal

Whether this is one of the four Royal Stars of Persia

magnitude

Apparent visual magnitude (lower = brighter)

nature

Traditional planetary nature (e.g., “Mars/Jupiter”)

keywords

Interpretive keywords for the star

Example:

regulus = FixedStarPosition(
    name="Regulus",
    object_type=ObjectType.FIXED_STAR,
    longitude=150.12,  # ~0 Virgo (moves slowly through precession)
    swe_name="Regulus",
    constellation="Leo",
    tier=1,
    is_royal=True,
    magnitude=1.35,
    nature="Mars/Jupiter",
    keywords=("royalty", "success", "fame"),
)
bayer: str = ''
constellation: str = ''
is_royal: bool = False
keywords: tuple[str, ...]
magnitude: float = 0.0
nature: str = ''
swe_name: str = ''
tier: int = 2
class stellium.core.models.HouseCusps(system, cusps)[source]

Bases: object

Immutable house cusp data.

cusps: tuple[float, ...]
get_cusp(house_number)[source]

Get cusp for a specific house (1-12)

Return type:

float

get_description(house_number)[source]

Get human-readable cusp description for a specific house.

Return type:

str

get_sign(house_number)[source]

Get sign name for a specific house (1-12)

Return type:

str

get_sign_degree(house_number)[source]

Get sign name for a specific house (1-12)

Return type:

str

system: str
class stellium.core.models.HouseOverlay(planet_name, planet_owner, falls_in_house, house_owner, planet_position)[source]

Bases: object

Represents one chart’s planets falling in another chart’s houses.

property description: str
falls_in_house: int
house_owner: Literal['chart1', 'chart2']
planet_name: str
planet_owner: Literal['chart1', 'chart2']
planet_position: CelestialPosition
class stellium.core.models.MidpointPosition(name, object_type, longitude, latitude=0.0, distance=0.0, speed_longitude=0.0, speed_latitude=0.0, speed_distance=0.0, declination=None, right_ascension=None, phase=None, object1=None, object2=None, is_indirect=False)[source]

Bases: CelestialPosition

Specialized position type for midpoints between two celestial objects.

A midpoint represents the halfway point between two celestial objects, either along the shorter arc (direct) or the longer arc (indirect).

object1

First component object

object2

Second component object

is_indirect

True if this is the indirect (opposite) midpoint

Example

# Sun at 10° Aries, Moon at 20° Aries # Direct midpoint: 15° Aries # Indirect midpoint: 15° Libra (opposite)

midpoint = MidpointPosition(

name=”Midpoint:Sun/Moon”, object_type=ObjectType.MIDPOINT, longitude=15.0, # 15° Aries object1=sun_position, object2=moon_position, is_indirect=False,

)

is_indirect: bool = False
object1: CelestialPosition = None
object2: CelestialPosition = None
class stellium.core.models.MoonRange(start_longitude, end_longitude, noon_longitude, start_sign, end_sign, crosses_sign_boundary)[source]

Bases: object

Moon position range for unknown birth time charts.

When birth time is unknown, the Moon’s position could be anywhere within a ~12-14° range (Moon moves about 12-14° per day). This dataclass captures the full range to display on the chart.

start_longitude

Moon position at 00:00:00 local time

end_longitude

Moon position at 23:59:59 local time

noon_longitude

Moon position at 12:00:00 (displayed position)

start_sign

Zodiac sign at start of day

end_sign

Zodiac sign at end of day

crosses_sign_boundary

True if Moon changes sign during the day

property arc_size: float

Size of the Moon’s arc in degrees.

Handles wrap-around at 0°/360° Aries point.

crosses_sign_boundary: bool
end_longitude: float
end_sign: str
noon_longitude: float
property sign_display: str

Human-readable sign display for the Moon range.

start_longitude: float
start_sign: str
class stellium.core.models.ObjectType(value)[source]

Bases: Enum

Type of astrological object.

ANGLE = 'angle'
ANTISCION = 'antiscion'
ARABIC_PART = 'arabic_part'
ASTEROID = 'asteroid'
CONTRA_ANTISCION = 'contra_antiscion'
FIXED_STAR = 'fixed_star'
MIDPOINT = 'midpoint'
NODE = 'node'
PLANET = 'planet'
POINT = 'point'
TECHNICAL = 'technical'
class stellium.core.models.PhaseData(phase_angle, illuminated_fraction, elongation, apparent_diameter, apparent_magnitude, geocentric_parallax=0.0, sun_longitude=None, moon_longitude=None)[source]

Bases: object

Planetary phase information.

Contains data about a celestial object’s appearance and illumination as seen from Earth. Available for Moon, planets, and some asteroids.

phase_angle

Angular separation from Sun (0-360°) - 0° = conjunction (new moon) - 90° = quadrature (quarter moon) - 180° = opposition (full moon)

illuminated_fraction

Fraction of disk that is illuminated (0.0-1.0) - 0.0 = completely dark (new) - 0.5 = half illuminated (quarter) - 1.0 = fully illuminated (full)

elongation

Elongation of the planet

apparent_diameter

Angular diameter as seen from Earth (arc seconds)

apparent_magnitude

Visual brightness magnitude (lower = brighter)

geocentric_parallax

Parallax angle (radians) - primarily for Moon

apparent_diameter: float
apparent_magnitude: float
elongation: float
geocentric_parallax: float = 0.0
illuminated_fraction: float
property is_waxing: bool

Whether object is waxing (growing in illumination).

For the Moon: waxing when Moon is 0-180° ahead of Sun. Uses Sun/Moon longitudes when available (accurate). Falls back to phase_angle for backward compatibility.

moon_longitude: float | None = None
phase_angle: float
property phase_name: str

Human-readable phase name (primarily for Moon).

Returns:

Phase name like “New”, “Waxing Crescent”, etc.

sun_longitude: float | None = None
class stellium.core.models.UnknownTimeChart(datetime, location, positions, house_systems=<factory>, house_placements=<factory>, aspects=(), declination_aspects=(), metadata=<factory>, zodiac_type=None, ayanamsa=None, ayanamsa_value=None, calculation_timestamp=<factory>, chart_tags=(), moon_range=None)[source]

Bases: CalculatedChart

A natal chart calculated without known birth time.

Inherits from CalculatedChart for compatibility with all existing code, but has some key differences: - Houses are empty (no house cusps without birth time) - Angles are not calculated (no Asc/MC/Dsc/IC) - Moon is shown as a range rather than single position - Planetary positions are calculated for noon

The chart can still be: - Visualized (without houses, with moon arc) - Exported to JSON - Used for aspect calculations (using noon Moon) - Used for dignity calculations (planets only)

moon_range

MoonRange showing the Moon’s possible positions throughout the day

get_angles()[source]

Angles are not available for unknown time charts.

Return type:

list[CelestialPosition]

get_house(object_name, system_name=None)[source]

Houses are not available for unknown time charts.

Return type:

None

get_houses(system_name=None)[source]

Houses are not available for unknown time charts.

Return type:

None

property is_time_unknown: bool

Always True for this chart type.

moon_range: MoonRange = None
to_dict()[source]

Serialize to dictionary, including moon_range.

Return type:

dict[str, Any]

to_prompt_text(sections=None, include_extras=True)[source]

Export unknown-time chart as prompt text.

Automatically excludes houses and angles sections since they are not available without a birth time.

Return type:

str

class stellium.core.models.ZRPeriod(level, sign, ruler, start, end, length_days, is_angular, angle_from_lot, is_loosing_bond, is_peak, ruler_role=None, tenant_roles=<factory>, score=0)[source]

Bases: object

A single Zodiacal Releasing period at any level.

Represents a time period during which a particular sign is activated in the Zodiacal Releasing technique.

level

The period level (1=major, 2=sub, 3=sub-sub, 4=sub-sub-sub)

sign

The zodiac sign activated during this period

ruler

The traditional ruler of the sign

start

When this period begins

end

When this period ends

length_days

Duration in days

is_angular

Whether this sign is angular to the Lot (1st, 4th, 7th, 10th)

angle_from_lot

The angular position (1, 4, 7, or 10) or None

is_loosing_bond

Whether this period triggers Loosing of the Bond (L2+)

is_peak

Whether this is a peak period (10th from Lot)

angle_from_lot: int | None
end: datetime
is_angular: bool
is_loosing_bond: bool
is_peak: bool
length_days: float
level: int
ruler: str
ruler_role: str | None = None
score: int = 0
property sentiment: str
sign: str
start: datetime
tenant_roles: list[str]
class stellium.core.models.ZRSnapshot(lot, lot_sign, date, age, l1, l2, l3, l4)[source]

Bases: object

Complete Zodiacal Releasing state at a moment in time.

Captures which periods are active at all levels for a specific date.

lot

Name of the lot (e.g., “Part of Fortune”)

lot_sign

The sign the lot is placed in

date

The queried date

age

Age in years at this date

l1

The active Level 1 (major) period

l2

The active Level 2 (sub) period

l3

The active Level 3 period (if calculated)

l4

The active Level 4 period (if calculated)

age: float
date: datetime
property is_lb: bool

Is Loosing of the Bond active at any level?

property is_peak: bool

Are we in a 10th-from-Lot period at any level?

l1: ZRPeriod
l2: ZRPeriod
l3: ZRPeriod | None
l4: ZRPeriod | None
lot: str
lot_sign: str
property rulers: list[str]

All currently active rulers.

class stellium.core.models.ZRTimeline(lot, lot_sign, birth_date, periods, max_level)[source]

Bases: object

Complete Zodiacal Releasing timeline for a life.

Contains all calculated periods at all levels and provides methods to query the timeline at specific dates or ages.

lot

Name of the lot (e.g., “Part of Fortune”)

lot_sign

The sign the lot is placed in

birth_date

The native’s birth date

periods

Dict mapping level (1-4) to list of periods

max_level

Maximum level calculated (1-4)

at_age(age)[source]

Get complete ZR state at a specific age.

Return type:

ZRSnapshot

at_date(date)[source]

Get complete ZR state at a specific date.

Return type:

ZRSnapshot

birth_date: datetime
find_loosing_bonds(level=2)[source]

Find all Loosing of the Bond periods at a given level.

Return type:

list[ZRPeriod]

find_peaks(level=1)[source]

Find all peak periods (10th from Lot) at a given level.

Return type:

list[ZRPeriod]

l1_periods()[source]

Get all L1 periods (the major life chapters).

Return type:

list[ZRPeriod]

lot: str
lot_sign: str
max_level: int
periods: dict[int, list[ZRPeriod]]
stellium.core.models.longitude_to_sign_and_degree(longitude)[source]

Convert position longitude to a sign and sign degree.

Parameters:

longitude (float) – Position longitude (0-360)

Return type:

tuple[str, float]

Returns:

tuple of (sign_name, sign_degree)

Native & Notable (stellium.core.native)

The Native class represents the core data for a single person or event.

Its job is to handle messy inputs (strings, dicts, naive datetimes, etc.) and process them into the clean, immutable ChartDateTime and ChartLocation objects that the rest of the system requires.

class stellium.core.native.Native(datetime_input, location_input, *, name=None, time_unknown=False)[source]

Bases: object

Represents the “native” data (time and place) for a chart. This class handles all the input parsing and data cleaning.

datetime: ChartDateTime
location: ChartLocation
name: str | None
time_unknown: bool
class stellium.core.native.Notable(name, event_type, year, month, day, hour, minute, location_input, category, subcategories=None, notable_for='', astrological_notes='', data_quality='C', sources=None, verified=False)[source]

Bases: Native

A Native with curated metadata from the registry.

Represents famous births and notable events. The base Native class handles all datetime/location parsing - Notable just adds metadata.

Example

>>> notable = Notable(
...     name="Albert Einstein",
...     event_type="birth",
...     year=1879, month=3, day=14, hour=11, minute=30,
...     location_input="Ulm, Germany",
...     category="scientist"
... )
>>> chart = ChartBuilder.from_native(notable).calculate()
astrological_notes: str
category: str
data_quality: str
event_type: str
property is_birth: bool

Check if this is a birth record.

property is_event: bool

Check if this is an event record.

name: str
notable_for: str
sources: list[str] | None
subcategories: list[str] | None
verified: bool

Registry (stellium.core.registry)

Celestial Objects Registry

A comprehensive catalog of all celestial objects used in astrological calculations. This centralizes metadata like names, glyphs, types, and descriptions for planets, asteroids, nodes, points, fixed stars, and other celestial bodies.

class stellium.core.registry.AspectInfo(name, angle, category, family=None, glyph='', color='#CCCCCC', default_orb=2.0, aliases=<factory>, description='', metadata=<factory>)[source]

Bases: object

Complete metadata for an astrological aspect.

This represents everything we know about an aspect - from its technical name and exact angle to its glyph, color, and visualization metadata.

aliases: list[str]
angle: float
category: str
color: str = '#CCCCCC'
default_orb: float = 2.0
description: str = ''
family: str | None = None
glyph: str = ''
metadata: dict[str, Any]
name: str
class stellium.core.registry.CelestialObjectInfo(name, display_name, object_type, glyph, glyph_svg_path=None, swiss_ephemeris_id=None, avg_daily_motion=None, category=None, aliases=<factory>, description='', metadata=<factory>)[source]

Bases: object

Complete metadata for a celestial object.

This represents everything we know about an object - from its technical name and ephemeris ID to its glyph and human-readable description.

aliases: list[str]
avg_daily_motion: float | None = None
category: str | None = None
description: str = ''
display_name: str
glyph: str
glyph_svg_path: str | None = None
metadata: dict[str, Any]
name: str
object_type: ObjectType
swiss_ephemeris_id: int | None = None
class stellium.core.registry.FixedStarInfo(name, swe_name, glyph='★', glyph_svg_path=None, constellation='', bayer='', tier=2, is_royal=False, magnitude=0.0, nature='', keywords=<factory>, description='')[source]

Bases: object

Complete metadata for a fixed star.

This contains everything needed to look up and interpret a fixed star, from its Swiss Ephemeris name to its traditional astrological meanings.

bayer: str = ''
constellation: str = ''
description: str = ''
glyph: str = '★'
glyph_svg_path: str | None = None
is_royal: bool = False
keywords: tuple[str, ...]
magnitude: float = 0.0
name: str
nature: str = ''
swe_name: str
tier: int = 2
stellium.core.registry.get_all_by_category(category)[source]

Get all celestial objects in a specific category.

Parameters:

category (str) – The category to filter by (e.g., “Centaur”, “Fixed Star”)

Return type:

list[CelestialObjectInfo]

Returns:

List of CelestialObjectInfo matching the category

stellium.core.registry.get_all_by_type(object_type)[source]

Get all celestial objects of a specific type.

Parameters:

object_type (ObjectType) – The ObjectType to filter by

Return type:

list[CelestialObjectInfo]

Returns:

List of CelestialObjectInfo matching the type

stellium.core.registry.get_all_fixed_stars()[source]

Get all fixed stars in the registry.

Return type:

list[FixedStarInfo]

Returns:

List of all FixedStarInfo objects

stellium.core.registry.get_aspect_by_alias(alias)[source]

Get aspect information by alias.

Parameters:

alias (str) – An alternative name for the aspect (e.g., “Conjunct”, “Inconjunct”)

Return type:

AspectInfo | None

Returns:

AspectInfo object if found, None otherwise

stellium.core.registry.get_aspect_info(name)[source]

Get aspect information by name.

Parameters:

name (str) – The aspect name to look up (e.g., “Conjunction”, “Trine”)

Return type:

AspectInfo | None

Returns:

AspectInfo object if found, None otherwise

stellium.core.registry.get_aspects_by_category(category)[source]

Get all aspects in a specific category.

Parameters:

category (str) – The category to filter by (“Major”, “Minor”, “Harmonic”)

Return type:

list[AspectInfo]

Returns:

List of AspectInfo matching the category

stellium.core.registry.get_aspects_by_family(family)[source]

Get all aspects in a specific family.

Parameters:

family (str) – The family to filter by (e.g., “Ptolemaic”, “Quintile Series”, “Septile Series”)

Return type:

list[AspectInfo]

Returns:

List of AspectInfo matching the family

stellium.core.registry.get_by_alias(alias)[source]

Get celestial object info by any of its aliases.

Parameters:

alias (str) – An alias for the object (e.g., “Lilith”, “BML”, “North Node”)

Return type:

CelestialObjectInfo | None

Returns:

CelestialObjectInfo if found, None otherwise

stellium.core.registry.get_fixed_star_info(name)[source]

Get fixed star info by name.

Parameters:

name (str) – The name of the star (e.g., “Regulus”, “Algol”)

Return type:

FixedStarInfo | None

Returns:

FixedStarInfo if found, None otherwise

stellium.core.registry.get_object_info(name)[source]

Get celestial object info by name.

Parameters:

name (str) – The technical name of the object (e.g., “Mean Apogee”, “Sun”)

Return type:

CelestialObjectInfo | None

Returns:

CelestialObjectInfo if found, None otherwise

stellium.core.registry.get_royal_stars()[source]

Get the four Royal Stars of Persia.

Return type:

list[FixedStarInfo]

Returns:

List of the four royal stars (Aldebaran, Regulus, Antares, Fomalhaut)

stellium.core.registry.get_stars_by_tier(tier)[source]

Get all fixed stars of a specific tier.

Parameters:

tier (int) – The tier level (1=Royal, 2=Major, 3=Extended)

Return type:

list[FixedStarInfo]

Returns:

List of FixedStarInfo matching the tier

stellium.core.registry.search_aspects(query)[source]

Search for aspects by name, alias, or description.

Parameters:

query (str) – Search string (case-insensitive)

Return type:

list[AspectInfo]

Returns:

List of matching AspectInfo objects

stellium.core.registry.search_fixed_stars(query)[source]

Search for fixed stars by name, constellation, or keywords.

Parameters:

query (str) – Search string (case-insensitive)

Return type:

list[FixedStarInfo]

Returns:

List of matching FixedStarInfo objects

stellium.core.registry.search_objects(query)[source]

Search for objects by name, display name, alias, or description.

Parameters:

query (str) – Search string (case-insensitive)

Return type:

list[CelestialObjectInfo]

Returns:

List of matching CelestialObjectInfo objects

Comparison (stellium.core.comparison)

Comparison chart implementation for synastry, transits, and progressions.

This module provides a unified interface for comparing two charts: - Synastry: Two natal charts (relationship analysis) - Transits: Natal chart + current sky positions (timing analysis) - Progressions: Progressed chart + natal chart (symbolic timing)

The Comparison class mimics CalculatedChart’s interface while providing cross-chart analysis capabilities.

Configuration:

Uses AspectEngine + OrbEngine for aspect calculations:

AspectEngine: - Determines which aspects to calculate (via AspectConfig) - CrossChartAspectEngine for cross-chart aspects (chart1 × chart2) - ModernAspectEngine for internal aspects (if charts lack them)

OrbEngine: - Determines orb allowances for each aspect - Defaults are comparison-type specific:

  • Synastry: 6°/4° (moderate - connections matter)

  • Transits: 3°/2° (tight - timing precision)

  • Progressions: 1° (very tight - symbolic timing)

Builder Methods:

Cross-chart aspects: - .with_aspect_engine(engine) - Custom CrossChartAspectEngine - .with_orb_engine(engine) - Custom orb allowances

Internal (natal) aspects: - .with_internal_aspect_engine(engine) - Engine for chart1/chart2 internal aspects - .with_internal_orb_engine(engine) - Orbs for internal aspects

House overlays: - .without_house_overlays() - Disable house overlay calculation

class stellium.core.comparison.Comparison(comparison_type, chart1, chart2, cross_aspects=(), house_overlays=(), chart1_label='Native', chart2_label='Other', calculation_timestamp=<factory>)[source]

Bases: object

Comparison between two charts (synastry or transits).

This class mimics CalculatedChart’s interface while providing cross-chart analysis. It holds two complete charts and calculates their interactions.

property aspects: tuple[Aspect, ...]

Primary chart’s natal aspects (chart1 internal).

calculate_compatibility_score(weights=None)[source]

Calculate a simple compatibility score based on aspects.

This is a basic implementation - users can implement their own weighting schemes.

Parameters:

weights (dict[str, float] | None) – Optional custom weights for aspect types

Return type:

float

Returns:

Compatibility score (0-100)

calculation_timestamp: datetime
chart1: CalculatedChart
chart1_label: str = 'Native'
chart2: CalculatedChart
property chart2_aspects: tuple[Aspect, ...]

Secondary chart’s internal aspects.

property chart2_datetime: ChartDateTime

Secondary chart datetime.

property chart2_houses: HouseCusps

Secondary chart houses.

chart2_label: str = 'Other'
property chart2_location: ChartLocation

Secondary chart location.

property chart2_positions: tuple[CelestialPosition, ...]

Secondary chart positions.

comparison_type: ComparisonType
cross_aspects: tuple[ComparisonAspect, ...] = ()
property datetime: ChartDateTime

Primary chart datetime (chart1/native).

draw(filename='synastry.svg')[source]

Start building a comparison chart visualization with fluent API.

This is a convenience method that creates a ChartDrawBuilder for easy, discoverable comparison chart visualization. It provides synastry-specific presets and a fluent interface for customization.

Parameters:

filename (str) – Output filename for the SVG

Return type:

ChartDrawBuilder

Returns:

ChartDrawBuilder instance for chaining

Example:

# Simple synastry preset
comparison.draw("synastry.svg").preset_synastry().save()

# Custom configuration
comparison.draw("custom.svg").with_theme("celestial").with_moon_phase(
    position="top-left"
).with_chart_info(position="top-right").save()
get_angles(chart=1)[source]

Get all chart angles from specified chart.

Return type:

list[CelestialPosition]

get_object(name, chart=1)[source]

Get a celestial object by name from either chart.

Parameters:
  • name (str) – Object name (e.g., “Sun”, “Moon”)

  • from_chart – Which chart to get from

Return type:

CelestialPosition | None

Returns:

CelestialPosition or None

get_object_aspects(object_name, chart=1)[source]

Get all cross-chart aspects involving a specific object.

Parameters:
  • object_name (str) – Name of the object

  • chart (Literal[1, 2]) – Which chart the object belongs to

Return type:

list[ComparisonAspect]

Returns:

List of ComparisonAspect objects

get_object_houses(object_name, chart=1)[source]

Get house overlays for a specific planet.

Parameters:
  • planet_name – Planet name

  • planet_owner – Which chart owns the planet

Return type:

list[HouseOverlay]

Returns:

List of HouseOverlay objects

get_objects_in_house(house_number, house_owner, planet_owner='both')[source]

Get all planets falling in a specific house.

Parameters:
  • house_number (int) – House number (1-12)

  • house_owner (Literal[1, 2]) – Whose house system to use

  • planet_owner (Literal[1, 2, 'both']) – Whose planets to check (or “both”)

Return type:

list[HouseOverlay]

Returns:

List of HouseOverlay objects

get_planets(chart=1)[source]

Get all planetary objects from specified chart.

Return type:

list[CelestialPosition]

house_overlays: tuple[HouseOverlay, ...] = ()
property houses: HouseCusps

Primary chart houses (chart1/native).

property location: ChartLocation

Primary chart location (chart1/native).

property positions: tuple[CelestialPosition, ...]

Primary chart positions (chart1/native).

to_dict()[source]

Serialize to dictionary for JSON export.

Return type:

dict[str, Any]

Returns:

Dictionary with full comparison data

class stellium.core.comparison.ComparisonBuilder(chart1, comparison_type, chart1_label='Native')[source]

Bases: object

Fluent builder for creating Comparison objects.

Provides convenient construction methods for both synastry and transits:

For synastry:
comp = ComparisonBuilder.from_native(chart1)

.with_partner(chart2) .calculate()

For transits:
comp = ComparisonBuilder.from_native(natal_chart)

.with_transit(transit_datetime, transit_location) .calculate()

classmethod arc_direction(natal_data, *, target_date=None, age=None, arc_type='solar_arc', rulership_system='traditional', natal_label='Natal', directed_label='Directed')[source]

Create an arc direction comparison (natal vs directed chart).

Arc directions move ALL points by the same angular distance, preserving natal relationships. This differs from progressions where each planet moves at its own rate.

Arc types supported:
  • “solar_arc”: Arc = progressed Sun - natal Sun (~1°/year actual)

  • “naibod”: Arc = 0.9856° × years (mean solar motion)

  • “lunar”: Arc = progressed Moon - natal Moon (~12-13°/year)

  • “chart_ruler”: Arc based on planet ruling the Ascendant sign

  • “sect”: Day charts use solar arc, night charts use lunar arc

  • Any planet name (e.g., “Mars”, “Venus”): Uses that planet’s arc

Parameters:
  • natal_data (CalculatedChart | Native | tuple[str | datetime | dict, str | tuple[float, float] | dict]) – Natal chart data (Native, CalculatedChart, or tuple)

  • target_date (str | datetime | None) – Target date for directions (either this or age required)

  • age (float | None) – Age in years (alternative to target_date)

  • arc_type (str) – Type of arc to use (see above)

  • rulership_system (Literal['traditional', 'modern']) – “traditional” or “modern” (for chart_ruler arc)

  • natal_label (str) – Label for natal chart (default: “Natal”)

  • directed_label (str) – Label for directed chart (default: “Directed”)

Return type:

ComparisonBuilder

Returns:

ComparisonBuilder instance ready to configure and calculate

Examples

>>> # Solar arc directions at age 30
>>> directed = ComparisonBuilder.arc_direction(
...     natal_chart, age=30, arc_type="solar_arc"
... ).calculate()
>>> # Naibod arc directions to a specific date
>>> directed = ComparisonBuilder.arc_direction(
...     natal_chart, target_date="2025-06-15", arc_type="naibod"
... ).calculate()
>>> # Chart ruler arc (uses planet ruling ASC sign)
>>> directed = ComparisonBuilder.arc_direction(
...     natal_chart, age=30, arc_type="chart_ruler"
... ).calculate()
>>> # Sect-based arc (solar for day charts, lunar for night)
>>> directed = ComparisonBuilder.arc_direction(
...     natal_chart, age=30, arc_type="sect"
... ).calculate()
>>> # Mars arc directions
>>> directed = ComparisonBuilder.arc_direction(
...     natal_chart, age=30, arc_type="Mars"
... ).calculate()
calculate()[source]

Execute all calculations and return the final Comparison.

This method ensures that both charts have their internal aspects calculated before computing cross-chart aspects. If a chart doesn’t already have aspects, they will be calculated using the internal aspect engine configuration.

Return type:

Comparison

Returns:

Comparison object with all calculated data

classmethod compare(data1, data2, comparison_type, chart1_label='Chart 1', chart2_label='Chart 2')[source]

General method for creating any type of comparison.

This is the flexible method that accepts any combination of inputs and any comparison type. Convenience methods (.synastry(), .transit(), .progression()) are thin wrappers that call this method with appropriate defaults.

Parameters:
Return type:

ComparisonBuilder

Returns:

ComparisonBuilder instance ready to configure and calculate

Examples

>>> # With Native objects
>>> native1 = Native("1994-01-06 11:47", "Palo Alto, CA")
>>> native2 = Native("2000-01-01 17:00", "Seattle, WA")
>>> comparison = ComparisonBuilder.compare(native1, native2, "synastry").calculate()
>>>
>>> # With (datetime, location) tuples
>>> comparison = ComparisonBuilder.compare(
...     ("1994-01-06 11:47", "Palo Alto, CA"),
...     ("2000-01-01 17:00", "Seattle, WA"),
...     "synastry"
... ).calculate()
>>>
>>> # Mixed inputs
>>> comparison = ComparisonBuilder.compare(
...     native1,
...     ("2024-11-24 14:30", None),  # Uses chart1's location for transits
...     "transit"
... ).calculate()
classmethod from_native(native_chart, native_label='Native')[source]

Start building a comparison from a native chart.

Use this when you have a CalculatedChart already. Chain with .with_partner() or .with_transit()

Parameters:
  • native_chart (CalculatedChart) – The native/primary chart

  • native_label (str) – Label for the native chart

Return type:

ComparisonBuilder

Returns:

ComparisonBuilder instance

classmethod progression(natal_data, progressed_data=None, *, target_date=None, age=None, angle_method='quotidian', natal_label='Natal', progressed_label='Progressed')[source]

Create a progression comparison with auto-calculation support.

Secondary progressions use the symbolic equation “one day = one year.” To find progressed positions at age 30, look at where planets were 30 days after birth.

Can be called three ways: 1. Auto-calculate by target date: progression(natal, target_date=”2025-06-15”) 2. Auto-calculate by age: progression(natal, age=30) 3. Manual (legacy): progression(natal, progressed_chart)

Parameters:
  • natal_data (CalculatedChart | Native | tuple[str | datetime | dict, str | tuple[float, float] | dict]) – Natal chart data (Native, CalculatedChart, or (datetime, location) tuple)

  • progressed_data (CalculatedChart | Native | tuple[str | datetime | dict, str | tuple[float, float] | dict] | None) – Optional pre-calculated progressed chart (for backwards compatibility)

  • target_date (str | datetime | None) – Target date for progression (triggers auto-calculation)

  • age (float | None) – Age in years for progression (alternative to target_date)

  • angle_method (Literal['quotidian', 'solar_arc', 'naibod']) – How to progress angles: - “quotidian” (default): Actual daily motion from Swiss Ephemeris - “solar_arc”: Angles progress at rate of progressed Sun - “naibod”: Angles progress at mean Sun rate (59’08”/year)

  • natal_label (str) – Label for natal chart (default: “Natal”)

  • progressed_label (str) – Label for progressed chart (default: “Progressed”)

Return type:

ComparisonBuilder

Returns:

ComparisonBuilder instance ready to configure and calculate

Examples

>>> # Auto-calculate by age (most convenient)
>>> prog = ComparisonBuilder.progression(natal, age=30).calculate()
>>>
>>> # Auto-calculate by target date
>>> prog = ComparisonBuilder.progression(
...     natal, target_date="2025-06-15"
... ).calculate()
>>>
>>> # With solar arc angles
>>> prog = ComparisonBuilder.progression(
...     natal, age=30, angle_method="solar_arc"
... ).calculate()
>>>
>>> # Legacy: explicit progressed chart (backwards compatible)
>>> progressed_chart = ChartBuilder.from_details(
...     "1994-02-05 11:47", "Palo Alto, CA"
... ).calculate()
>>> prog = ComparisonBuilder.progression(natal, progressed_chart).calculate()
classmethod synastry(data1, data2, chart1_label='Person 1', chart2_label='Person 2')[source]

Create a synastry comparison between two natal charts.

Synastry analyzes the relationship between two people by comparing their birth charts. This is a convenience method that calls .compare() with comparison_type=”synastry”.

Parameters:
Return type:

ComparisonBuilder

Returns:

ComparisonBuilder instance ready to configure and calculate

Examples

>>> # Simple string inputs
>>> comparison = ComparisonBuilder.synastry(
...     ("1994-01-06 11:47", "Palo Alto, CA"),
...     ("2000-01-01 17:00", "Seattle, WA")
... ).calculate()
>>>
>>> # With Native objects
>>> native1 = Native("1994-01-06 11:47", "Palo Alto, CA")
>>> native2 = Native("2000-01-01 17:00", "Seattle, WA")
>>> comparison = ComparisonBuilder.synastry(native1, native2).calculate()
>>>
>>> # With custom labels
>>> comparison = ComparisonBuilder.synastry(
...     ("1994-01-06 11:47", "Palo Alto, CA"),
...     ("2000-01-01 17:00", "Seattle, WA"),
...     chart1_label="Kate",
...     chart2_label="Partner"
... ).calculate()
classmethod transit(natal_data, transit_data, natal_label='Natal', transit_label='Transit')[source]

Create a transit comparison (natal chart vs current sky positions).

Transits analyze how current planetary positions interact with a natal chart for timing and prediction. This is a convenience method that calls .compare() with comparison_type=”transit”.

Parameters:
Return type:

ComparisonBuilder

Returns:

ComparisonBuilder instance ready to configure and calculate

Examples

>>> # Transit using natal location
>>> comparison = ComparisonBuilder.transit(
...     ("1994-01-06 11:47", "Palo Alto, CA"),
...     ("2024-11-24 14:30", None)  # Uses Palo Alto
... ).calculate()
>>>
>>> # Transit with different location
>>> comparison = ComparisonBuilder.transit(
...     ("1994-01-06 11:47", "Palo Alto, CA"),
...     ("2024-11-24 14:30", "New York, NY")
... ).calculate()
>>>
>>> # With Native object
>>> natal = Native("1994-01-06 11:47", "Palo Alto, CA")
>>> comparison = ComparisonBuilder.transit(
...     natal,
...     ("2024-11-24 14:30", None)
... ).calculate()
with_aspect_config(aspect_config)[source]

Set aspect configuration (orbs, which aspects, etc.).

Parameters:

aspect_config (AspectConfig) – AspectConfig instance

Return type:

ComparisonBuilder

Returns:

Self for chaining

with_aspect_engine(engine)[source]

Set the aspect engine for cross-chart aspects.

Parameters:

engine – AspectEngine instance

Return type:

ComparisonBuilder

Returns:

Self for chaining

with_internal_aspect_engine(engine)[source]

Set aspect engine for calculating internal (natal) aspects.

This engine will be used to calculate aspects within chart1 and chart2 if they don’t already have aspects calculated. If not set, defaults to ModernAspectEngine().

Parameters:

engine – AspectEngine instance for internal aspects

Return type:

ComparisonBuilder

Returns:

Self for chaining

with_internal_orb_engine(engine)[source]

Set orb engine for calculating internal (natal) aspects.

This engine will be used for orb allowances when calculating internal aspects in chart1/chart2. If not set, defaults to SimpleOrbEngine with registry defaults.

Parameters:

engine (OrbEngine) – OrbEngine instance for internal aspect orbs

Return type:

ComparisonBuilder

Returns:

Self for chaining

with_orb_engine(engine)[source]

Set the orb calculation engine for dynamic orb calculation.

OrbEngine will be used to calculate orbs for each planet pair dynamically (e.g., wider orbs for Sun/Moon, tighter for fast planets).

If provided, OrbEngine takes precedence over AspectConfig.orbs.

Examples

from stellium.engines.orbs import SimpleOrbEngine, LuminariesOrbEngine

# Simple engine with fixed orbs per aspect simple = SimpleOrbEngine({‘Conjunction’: 8.0, ‘Trine’: 8.0}) builder.with_orb_engine(simple)

# Luminaries engine (wider orbs for Sun/Moon) lum = LuminariesOrbEngine() builder.with_orb_engine(lum)

Parameters:

engine – OrbEngine instance implementing get_orb_allowance()

Return type:

ComparisonBuilder

Returns:

Self for chaining

with_other(other_input, location=None, other_label='Other', comparison_type=None)[source]

Generic method to add second chart.

This is a flexible alternative to with_partner() and with_transit().

Parameters:
Return type:

ComparisonBuilder

Returns:

Self for chaining

with_partner(partner_chart_or_datetime_or_native, location=None, partner_label='Partner')[source]

Add partner chart for synastry comparison.

Parameters:
  • partner_chart_or_datetime – Either a CalculatedChart or datetime

  • location (ChartLocation | None) – Required if providing datetime

  • partner_label (str) – Label for the partner chart

Return type:

ComparisonBuilder

Returns:

Self for chaining

with_transit(transit_datetime, location=None)[source]

Add transit chart for transit comparison.

Convenience method that calls with_other() with appropriate settings.

Parameters:
  • transit_datetime (datetime) – Transit datetime

  • location (ChartLocation | None) – Optional location (defaults to native’s location)

Return type:

ComparisonBuilder

Returns:

Self for chaining

without_house_overlays()[source]

Disable house overlay calculation.

Return type:

ComparisonBuilder

Returns:

Self for chaining

MultiChart (stellium.core.multichart)

Unified MultiChart implementation for 2-4 chart comparisons.

This module provides a single interface for all multi-chart scenarios: - Synastry (two natal charts) - Transits (natal + current sky) - Progressions (natal + progressed) - Arc Directions (natal + directed) - Triwheels (3 charts) - Quadwheels (4 charts)

The MultiChart class combines the features of the former Comparison and MultiWheel classes into a unified architecture.

Ring order (center -> out): - Tiny aspect center - Chart 1 ring (innermost) - primary/natal chart - Chart 2 ring - Chart 3 ring (if present) - Chart 4 ring (if present) - Zodiac ring (outermost)

class stellium.core.multichart.MultiChart(charts, labels=(), relationships=<factory>, cross_aspects=<factory>, house_overlays=<factory>, calculation_timestamp=<factory>, metadata=<factory>)[source]

Bases: object

Unified multi-chart container supporting 2-4 charts with analysis and visualization.

Supports all chart relationship types: - Synastry (two natal charts) - Transits (natal + transit sky) - Progressions (natal + progressed) - Arc Directions (natal + directed) - Triwheels/Quadwheels (3-4 charts)

Access charts via: - Indexed: mc[0], mc[1], mc.charts[2] - Named: mc.chart1, mc.chart2, mc.chart3, mc.chart4 - Semantic: mc.inner, mc.outer, mc.natal

charts

Tuple of 2-4 CalculatedChart objects

labels

Display labels for each chart

relationships

Per-pair relationship types {(0,1): ComparisonType.SYNASTRY}

cross_aspects

Cross-chart aspects indexed by pair {(0,1): (aspects…)}

house_overlays

House overlays indexed by (planet_chart, house_chart)

calculation_timestamp

When this MultiChart was created

metadata

Additional metadata

calculate_compatibility_score(pair=(0, 1), weights=None)[source]

Calculate a simple compatibility score based on aspects.

This is a basic implementation - users can implement their own weighting schemes.

Parameters:
  • pair (tuple[int, int]) – Which chart pair to score (default: (0, 1))

  • weights (dict[str, float] | None) – Optional custom weights for aspect types

Return type:

float

Returns:

Compatibility score (0-100)

calculation_timestamp: datetime
property chart1: CalculatedChart

Primary chart (innermost ring).

property chart2: CalculatedChart

Second chart.

property chart3: CalculatedChart | None

Third chart (if present).

property chart4: CalculatedChart | None

Fourth chart (if present).

property chart_count: int

Number of charts in this MultiChart.

charts: tuple[CalculatedChart, ...]
cross_aspects: dict[tuple[int, int], tuple[Aspect, ...]]
property datetime

Delegate to chart1’s datetime for visualization compatibility.

draw(filename='multichart.svg')[source]

Start building a multi-chart visualization.

Parameters:

filename (str) – Output filename for the SVG

Return type:

ChartDrawBuilder

Returns:

ChartDrawBuilder configured for this MultiChart

get_all_cross_aspects()[source]

Get all cross-chart aspects flattened into a single list.

Return type:

list[Aspect]

Returns:

List of all Aspect objects from all chart pairs

get_all_house_overlays()[source]

Get all house overlays flattened into a single list.

Return type:

list[HouseOverlay]

Returns:

List of all HouseOverlay objects

get_angles(chart=0)[source]

Get all chart angles from specified chart.

Return type:

list[CelestialPosition]

get_cross_aspects(chart1_idx=0, chart2_idx=1)[source]

Get cross-chart aspects between two specific charts.

Parameters:
  • chart1_idx (int) – First chart index (default: 0)

  • chart2_idx (int) – Second chart index (default: 1)

Return type:

tuple[Aspect, ...]

Returns:

Tuple of Aspect objects

get_house_overlays(planet_chart, house_chart)[source]

Get house overlays for a specific chart pair.

Parameters:
  • planet_chart (int) – Index of chart whose planets to check

  • house_chart (int) – Index of chart whose houses to use

Return type:

tuple[HouseOverlay, ...]

Returns:

Tuple of HouseOverlay objects

get_object(name, chart=0)[source]

Get a celestial object by name from a specific chart.

Parameters:
  • name (str) – Object name (e.g., “Sun”, “Moon”)

  • chart (int) – Chart index (0-based) or 1-based if > 0

Return type:

CelestialPosition | None

Returns:

CelestialPosition or None

get_object_aspects(object_name, chart=0)[source]

Get all cross-chart aspects involving a specific object from a specific chart.

Parameters:
  • object_name (str) – Name of the celestial object (e.g., “Venus”, “Moon”)

  • chart (int) – Chart index (0 = inner/natal, 1 = outer/transit, etc.)

Return type:

list[Aspect]

Returns:

List of Aspect objects involving the specified object

Example

>>> venus_aspects = multichart.get_object_aspects("Venus", chart=0)
>>> for asp in venus_aspects:
...     print(f"Venus {asp.aspect_name} {asp.object2.name}")
get_planets(chart=0)[source]

Get all planetary objects from specified chart.

Return type:

list[CelestialPosition]

get_relationship(idx1, idx2)[source]

Get the relationship type between two charts.

Parameters:
  • idx1 (int) – First chart index

  • idx2 (int) – Second chart index

Return type:

ComparisonType | None

Returns:

ComparisonType or None if not defined

house_overlays: dict[tuple[int, int], tuple[HouseOverlay, ...]]
property inner: CalculatedChart

Semantic alias for innermost chart (chart1).

labels: tuple[str, ...] = ()
property location

Delegate to chart1’s location for visualization compatibility.

metadata: dict[str, Any]
property natal: CalculatedChart

Semantic alias for the natal/base chart (chart1).

property outer: CalculatedChart

Semantic alias for outermost chart.

relationships: dict[tuple[int, int], ComparisonType]
to_dict()[source]

Serialize to dictionary for JSON export.

Return type:

dict[str, Any]

Returns:

Dictionary with full MultiChart data

to_prompt_text(sections=None, include_extras=True)[source]

Export multi-chart data as clean, human-readable text for LLM prompts.

Shows each chart clearly labeled, followed by cross-chart aspects and house overlays.

Parameters:
  • sections (set[str] | None) – Passed through to each individual chart’s to_prompt_text call. None means all sections.

  • include_extras (bool) – Passed through to each chart. When True, picks up data from unknown/future components.

Return type:

str

Returns:

A multi-line string ready to paste into an LLM prompt.

class stellium.core.multichart.MultiChartBuilder(charts=None)[source]

Bases: object

Fluent builder for creating MultiChart objects.

Supports all multi-chart scenarios:

For synastry:

mc = MultiChartBuilder.synastry(chart1, chart2).calculate()

For transits:

mc = MultiChartBuilder.transit(natal, “2025-06-15”).calculate()

For progressions:

mc = MultiChartBuilder.progression(natal, age=30).calculate()

For 3-4 chart configurations:
mc = (MultiChartBuilder.from_chart(natal, “Natal”)

.add_progression(age=30, label=”Progressed”) .add_transit(“2025-06-15”, label=”Transit”) .calculate())

add_arc_direction(*, target_date=None, age=None, arc_type='solar_arc', rulership_system='traditional', label='Directed')[source]

Add a directed chart.

Parameters:
  • target_date (str | datetime | None) – Target date for directions

  • age (float | None) – Age in years

  • arc_type (str) – Type of arc to use

  • rulership_system (Literal['traditional', 'modern']) – Rulership system

  • label (str) – Label for directed chart

Return type:

MultiChartBuilder

Returns:

Self for chaining

add_chart(chart, label, relationship_to=0, relationship_type=None)[source]

Add a chart to the builder.

Parameters:
  • chart (CalculatedChart) – Chart to add

  • label (str) – Label for this chart

  • relationship_to (int) – Which existing chart this relates to (default: 0)

  • relationship_type (ComparisonType | None) – Type of relationship (optional)

Return type:

MultiChartBuilder

Returns:

Self for chaining

add_progression(*, target_date=None, age=None, progression_type='secondary', angle_method='quotidian', label='Progressed')[source]

Add a progressed chart.

Parameters:
  • target_date (str | datetime | None) – Target date for progression

  • age (float | None) – Age in years

  • progression_type (Literal['secondary', 'tertiary', 'minor']) – “secondary” (default), “tertiary”, or “minor”

  • angle_method (Literal['quotidian', 'solar_arc', 'naibod']) – How to progress angles

  • label (str) – Label for progressed chart

Return type:

MultiChartBuilder

Returns:

Self for chaining

add_transit(transit_data, location=None, label='Transit')[source]

Add a transit chart.

Parameters:
  • transit_data (str | datetime | CalculatedChart) – Transit datetime or chart

  • location (Any) – Location (uses chart[0] location if None)

  • label (str) – Label for transit chart

Return type:

MultiChartBuilder

Returns:

Self for chaining

classmethod arc_direction(natal_data, *, target_date=None, age=None, arc_type='solar_arc', rulership_system='traditional', natal_label='Natal', directed_label='Directed')[source]

Create an arc direction comparison (natal vs directed chart).

Arc directions move ALL points by the same angular distance.

Parameters:
Return type:

MultiChartBuilder

Returns:

MultiChartBuilder configured for arc directions

calculate()[source]

Execute all calculations and return the MultiChart.

Return type:

MultiChart

Returns:

MultiChart object with all calculated data

classmethod from_chart(chart, label='Chart 1')[source]

Start building from a single chart.

Use .add_chart(), .add_transit(), etc. to add more charts.

Parameters:
Return type:

MultiChartBuilder

Returns:

MultiChartBuilder ready for adding more charts

classmethod from_charts(charts, labels=None)[source]

Create a MultiChartBuilder from a list of calculated charts.

Parameters:
Return type:

MultiChartBuilder

Returns:

MultiChartBuilder ready for configuration

classmethod progression(natal_data, progressed_data=None, *, target_date=None, age=None, progression_type='secondary', angle_method='quotidian', natal_label='Natal', progressed_label='Progressed')[source]

Create a progression comparison with auto-calculation support.

Supports three progression types: - secondary (default): 1 day = 1 year. The standard progression. - tertiary: 1 day = 1 lunar month (~27.3 days). Faster-moving. - minor: 1 lunar month = 1 year. Intermediate rate.

Parameters:
Return type:

MultiChartBuilder

Returns:

MultiChartBuilder configured for progressions

Examples:

# Secondary (standard, 1 day = 1 year)
prog = MultiChartBuilder.progression(natal, age=30).calculate()

# Tertiary (1 day = 1 lunar month)
prog = MultiChartBuilder.progression(
    natal, age=30, progression_type="tertiary"
).calculate()

# Minor (1 lunar month = 1 year)
prog = MultiChartBuilder.progression(
    natal, age=30, progression_type="minor"
).calculate()
classmethod synastry(data1, data2, label1='Person 1', label2='Person 2')[source]

Create a synastry comparison between two natal charts.

Parameters:
Return type:

MultiChartBuilder

Returns:

MultiChartBuilder configured for synastry

classmethod transit(natal_data, transit_data, natal_label='Natal', transit_label='Transit')[source]

Create a transit comparison (natal chart vs current sky).

Parameters:
Return type:

MultiChartBuilder

Returns:

MultiChartBuilder configured for transits

Example

# Using a raw datetime (uses natal location) mc = MultiChartBuilder.transit(natal, datetime(2025, 1, 1, 12, 0))

# Using a tuple with explicit location mc = MultiChartBuilder.transit(natal, (datetime(2025, 1, 1), “New York”))

with_aspect_engine(engine)[source]

Set the aspect engine for cross-chart aspects.

Parameters:

engine – AspectEngine instance

Return type:

MultiChartBuilder

Returns:

Self for chaining

with_cross_aspects(pairs='to_primary')[source]

Configure which chart pairs to calculate cross-aspects for.

Parameters:

pairs (Union[list[tuple[int, int]], Literal['all', 'to_primary', 'adjacent']]) – Either: - “to_primary”: Only aspects to chart[0] (default) - “adjacent”: Adjacent pairs (0-1, 1-2, 2-3) - “all”: All possible pairs - List of (i, j) tuples for explicit pairs

Return type:

MultiChartBuilder

Returns:

Self for chaining

with_house_overlays(enabled=True)[source]

Enable or disable house overlay calculation.

Parameters:

enabled (bool) – Whether to calculate house overlays

Return type:

MultiChartBuilder

Returns:

Self for chaining

with_internal_aspect_engine(engine)[source]

Set aspect engine for calculating internal (natal) aspects.

Parameters:

engine – AspectEngine instance

Return type:

MultiChartBuilder

Returns:

Self for chaining

with_internal_orb_engine(engine)[source]

Set orb engine for calculating internal (natal) aspects.

Parameters:

engine (OrbEngine) – OrbEngine instance

Return type:

MultiChartBuilder

Returns:

Self for chaining

with_labels(labels)[source]

Set labels for each chart.

Parameters:

labels (list[str]) – List of labels

Return type:

MultiChartBuilder

Returns:

Self for chaining

with_orb_engine(engine)[source]

Set the orb engine for cross-chart aspects.

Parameters:

engine (OrbEngine) – OrbEngine instance

Return type:

MultiChartBuilder

Returns:

Self for chaining

without_cross_aspects()[source]

Disable cross-aspect calculation.

Return type:

MultiChartBuilder

Returns:

Self for chaining

without_house_overlays()[source]

Disable house overlay calculation.

Return type:

MultiChartBuilder

Returns:

Self for chaining

Synthesis (stellium.core.synthesis)

Synthesis charts: Composite and Davison chart calculations.

These create a single “synthesized” chart from two source charts, representing a relationship or combined energy.

Composite: Midpoint of each planet/point between charts Davison: Midpoint in time and space, then regular chart calculation

class stellium.core.synthesis.SynthesisBuilder(chart1, chart2, method)[source]

Bases: object

Builder for synthesizing two charts into one (composite or davison).

Example:

# Simple davison
davison = SynthesisBuilder.davison(chart1, chart2).calculate()

# Configured composite
composite = (SynthesisBuilder.composite(chart1, chart2)
    .with_midpoint_method("short_arc")
    .with_labels("Alice", "Bob")
    .calculate())
calculate()[source]

Calculate the synthesis chart.

Return type:

SynthesisChart

Returns:

SynthesisChart (subclass of CalculatedChart)

classmethod composite(chart1, chart2)[source]

Create composite chart (midpoint of all positions).

Parameters:
Return type:

SynthesisBuilder

Returns:

SynthesisBuilder configured for composite calculation

classmethod davison(chart1, chart2)[source]

Create davison chart (midpoint in time and space).

Parameters:
Return type:

SynthesisBuilder

Returns:

SynthesisBuilder configured for davison calculation

with_houses(houses)[source]

Set house calculation method for composite charts.

Parameters:

houses (bool | str) – True (default) - Derived ASC method (midpoint Ascendants, derive cusps) False - No houses (positions only) “place” - Reference place method (geographic midpoint + derived time)

Return type:

SynthesisBuilder

Returns:

Self for chaining

Example

# No houses composite = SynthesisBuilder.composite(c1, c2).with_houses(False).calculate()

# Reference place method composite = SynthesisBuilder.composite(c1, c2).with_houses(“place”).calculate()

with_labels(label1, label2)[source]

Set descriptive labels for source charts.

Parameters:
  • label1 (str) – Label for first chart (e.g., “Alice”, “Natal”)

  • label2 (str) – Label for second chart (e.g., “Bob”, “Transit”)

Return type:

SynthesisBuilder

Returns:

Self for chaining

with_location_method(method)[source]

Set location midpoint method for davison charts.

Parameters:

method (str) – “great_circle” (default) - Geodesic midpoint following Earth’s curvature “simple” - Arithmetic mean of lat/lon (faster but less accurate)

Return type:

SynthesisBuilder

Returns:

Self for chaining

with_midpoint_method(method)[source]

Set midpoint calculation method for composite charts.

Parameters:

method (str) – “short_arc” (default) or “long_arc” - short_arc: Always takes shorter path around zodiac - long_arc: Always takes longer path

Return type:

SynthesisBuilder

Returns:

Self for chaining

class stellium.core.synthesis.SynthesisChart(datetime, location, positions, house_systems=<factory>, house_placements=<factory>, aspects=(), declination_aspects=(), metadata=<factory>, zodiac_type=None, ayanamsa=None, ayanamsa_value=None, calculation_timestamp=<factory>, chart_tags=(), synthesis_method='', source_chart1=None, source_chart2=None, chart1_label='Chart 1', chart2_label='Chart 2', midpoint_method=None, houses_config=None, location_method=None)[source]

Bases: CalculatedChart

A chart synthesized from two source charts (composite or davison).

Inherits all fields from CalculatedChart: - positions: tuple[CelestialPosition, …] - aspects: tuple[Aspect, …] - house_systems: dict[str, HouseCusps] - house_placements: dict[str, dict] - datetime: ChartDateTime - location: ChartLocation - metadata: dict

And adds synthesis-specific fields.

chart1_label: str = 'Chart 1'

Descriptive label for first chart (e.g., “Alice”, “Natal”)

chart2_label: str = 'Chart 2'

Descriptive label for second chart (e.g., “Bob”, “Transit”)

houses_config: bool | str | None = None

True (derived), False (none), or “place”

Type:

Composite only

location_method: str | None = None

“simple” or “great_circle”

Type:

Davison only

midpoint_method: str | None = None

“short_arc” or “long_arc”

Type:

Composite only

source_chart1: CalculatedChart | None = None

The first source chart (full chart object for reference)

source_chart2: CalculatedChart | None = None

The second source chart (full chart object for reference)

synthesis_method: str = ''

“composite” or “davison”

Type:

The synthesis method used

to_dict()[source]

Extend parent’s to_dict with synthesis-specific fields.

Return type:

dict[str, Any]

to_prompt_text(sections=None, include_extras=True, include_source_charts=False)[source]

Export synthesis chart as prompt text.

Parameters:
  • sections (set[str] | None) – Section names to include (None = all available).

  • include_extras (bool) – Pick up unknown/future component data (default True).

  • include_source_charts (bool) – If True, also include the full prompt text of both source charts for reference.

Return type:

str

Returns:

A multi-line string ready to paste into an LLM prompt.

stellium.core.synthesis.calculate_datetime_midpoint(dt1, dt2)[source]

Calculate midpoint between two datetimes using Julian day.

Parameters:
Return type:

tuple[datetime, float]

Returns:

Tuple of (midpoint_datetime, midpoint_julian_day)

stellium.core.synthesis.calculate_location_midpoint(loc1, loc2, method='great_circle')[source]

Calculate geographic midpoint between two locations.

Parameters:
  • loc1 (ChartLocation) – First location

  • loc2 (ChartLocation) – Second location

  • method (str) – “great_circle” (default, geodesic) or “simple” (arithmetic mean)

Return type:

ChartLocation

Returns:

Midpoint location

Note

Great circle (geodesic) midpoint follows the Earth’s curvature and is more accurate for locations far apart. Simple arithmetic mean can give incorrect results, especially across the date line or for distant points.

stellium.core.synthesis.calculate_midpoint_longitude(lon1, lon2, method='short_arc')[source]

Calculate midpoint between two zodiac longitudes.

Parameters:
  • lon1 (float) – First longitude (0-360)

  • lon2 (float) – Second longitude (0-360)

  • method (str) – “short_arc” (default) or “long_arc”

Return type:

float

Returns:

Midpoint longitude (0-360)

Examples

>>> calculate_midpoint_longitude(10, 20)  # Both in Aries
15.0
>>> calculate_midpoint_longitude(10, 190)  # Aries and Libra
100.0  # Cancer (short arc)
>>> calculate_midpoint_longitude(10, 190, "long_arc")
280.0  # Capricorn (long arc)
stellium.core.synthesis.julian_day_to_datetime(jd, timezone='UTC')[source]

Convert Julian day to Python datetime.

Parameters:
  • jd (float) – Julian day number

  • timezone (str) – Timezone string (default UTC)

Return type:

datetime

Returns:

Timezone-aware datetime object

Configuration (stellium.core.config)

Configuration models for chart calculation.

class stellium.core.config.AspectConfig(aspects=<factory>, include_angles=True, include_nodes=True, include_asteroids=True)[source]

Bases: object

Configuration for aspect calculations.

Will be passed directly into the AspectEngine.

aspects: list[str]
include_angles: bool = True
include_asteroids: bool = True
include_nodes: bool = True
class stellium.core.config.CalculationConfig(include_planets=<factory>, include_nodes=True, include_chiron=True, include_points=<factory>, include_asteroids=<factory>, zodiac_type=ZodiacType.TROPICAL, ayanamsa=None, heliocentric=False)[source]

Bases: object

Overall configuration for chart calculations.

Passed to the ChartBuilder.

ayanamsa: str | None = None
classmethod comprehensive()[source]

Comprehensive calculation – a well-rounded set.

Return type:

CalculationConfig

heliocentric: bool = False
include_asteroids: list[str]
include_chiron: bool = True
include_nodes: bool = True
include_planets: list[str]
include_points: list[str]
classmethod minimal()[source]

Minimal calculation - planets only.

Return type:

CalculationConfig

zodiac_type: ZodiacType = 'tropical'

Ayanamsa (stellium.core.ayanamsa)

Ayanamsa (sidereal offset) definitions and registry for Stellium.

This module provides the ZodiacType enum and a registry of ayanamsa systems used in sidereal astrology. Each ayanamsa represents a different calculation method for determining the offset between the tropical and sidereal zodiacs.

class stellium.core.ayanamsa.AyanamsaInfo(name, swe_constant, description, tradition)[source]

Bases: object

Information about a specific ayanamsa system.

name

Human-readable name of the ayanamsa

swe_constant

Swiss Ephemeris constant for this ayanamsa

description

Brief description of the system

tradition

Tradition this ayanamsa belongs to (vedic, western_sidereal, etc.)

description: str
name: str
swe_constant: int
tradition: str
class stellium.core.ayanamsa.ZodiacType(value)[source]

Bases: Enum

Type of zodiac system used for calculations.

TROPICAL: Based on the seasons (0° Aries = March equinox) SIDEREAL: Based on fixed star positions (varies by ayanamsa)

SIDEREAL = 'sidereal'
TROPICAL = 'tropical'
stellium.core.ayanamsa.get_ayanamsa(name)[source]

Get ayanamsa information by name.

Parameters:

name (str) – Name of the ayanamsa (case-insensitive, accepts spaces/hyphens)

Return type:

AyanamsaInfo

Returns:

AyanamsaInfo for the requested ayanamsa

Raises:

ValueError – If ayanamsa name is not recognized

Examples

>>> info = get_ayanamsa("lahiri")
>>> info.name
'Lahiri'
>>> info = get_ayanamsa("Fagan-Bradley")  # Case insensitive, hyphen ok
>>> info.tradition
'western_sidereal'
stellium.core.ayanamsa.get_ayanamsa_value(julian_day, ayanamsa)[source]

Calculate the ayanamsa offset value for a specific date.

The ayanamsa value represents the difference in degrees between the tropical and sidereal zodiacs at a given point in time.

Parameters:
  • julian_day (float) – Julian day number for the calculation

  • ayanamsa (str) – Name of the ayanamsa system to use

Return type:

float

Returns:

Ayanamsa offset in degrees

Example

>>> from stellium.utils.time import datetime_to_julian_day
>>> from datetime import datetime
>>> jd = datetime_to_julian_day(datetime(2000, 1, 1, 12, 0))
>>> offset = get_ayanamsa_value(jd, "lahiri")
>>> print(f"Lahiri ayanamsa in 2000: {offset:.2f}°")
Lahiri ayanamsa in 2000: 23.85°
stellium.core.ayanamsa.list_ayanamsas()[source]

Get a list of all available ayanamsa names.

Return type:

list[str]

Returns:

Sorted list of ayanamsa names (registry keys)

Example

>>> available = list_ayanamsas()
>>> "lahiri" in available
True
>>> "fagan_bradley" in available
True

Chart Utilities (stellium.core.chart_utils)

Utility functions for chart type handling.

This module provides centralized utilities for working with different chart types (CalculatedChart, MultiChart, Comparison) without requiring direct imports that could cause circular dependencies.

The functions use duck-typing (hasattr checks) where necessary to avoid importing MultiChart or Comparison directly, which helps prevent circular import issues.

Example usage:

from stellium.core.chart_utils import get_all_charts, get_chart_labels

def process_chart(chart):

charts = get_all_charts(chart) labels = get_chart_labels(chart) for c, label in zip(charts, labels):

print(f”{label}: {c.datetime}”)

stellium.core.chart_utils.chart_count(chart)[source]

Get the number of charts.

Parameters:

chart (Any) – Any chart-like object

Return type:

int

Returns:

Number of charts (1 for single chart, 2+ for multi-chart types)

stellium.core.chart_utils.get_all_charts(chart)[source]

Get all charts as a list.

For single charts, returns [chart]. For MultiChart/MultiWheel, returns list(charts). For Comparison, returns [chart1, chart2].

Parameters:

chart (Any) – Any chart-like object

Return type:

list[CalculatedChart]

Returns:

List of CalculatedChart objects

stellium.core.chart_utils.get_chart_at_index(chart, index)[source]

Get a specific chart by index.

Parameters:
  • chart (Any) – Any chart-like object

  • index (int) – 0-based index of the chart to retrieve

Return type:

CalculatedChart

Returns:

The CalculatedChart at the specified index

Raises:

IndexError – If index is out of range

stellium.core.chart_utils.get_chart_label_at_index(chart, index)[source]

Get the label for a specific chart by index.

Parameters:
  • chart (Any) – Any chart-like object

  • index (int) – 0-based index of the chart

Return type:

str

Returns:

The label string for the chart at the specified index

Raises:

IndexError – If index is out of range

stellium.core.chart_utils.get_chart_labels(chart)[source]

Get labels for all charts.

Generates default labels (“Chart 1”, “Chart 2”, etc.) if none are set.

Parameters:

chart (Any) – Any chart-like object

Return type:

list[str]

Returns:

List of label strings, one per chart

stellium.core.chart_utils.get_primary_chart(chart)[source]

Extract the primary (first) chart from any chart type.

For single charts, returns the chart itself. For MultiChart/MultiWheel, returns charts[0]. For Comparison, returns chart1.

Parameters:

chart (Any) – Any chart-like object

Return type:

CalculatedChart

Returns:

The primary CalculatedChart

stellium.core.chart_utils.is_comparison(chart)[source]

Check if chart is a Comparison (deprecated type).

Uses duck-typing to avoid circular imports.

Parameters:

chart (Any) – Any chart-like object

Return type:

bool

Returns:

True if chart has Comparison attributes (comparison_type, chart1, chart2)

stellium.core.chart_utils.is_multichart(chart)[source]

Check if chart is a MultiChart.

Uses duck-typing to avoid circular imports.

Parameters:

chart (Any) – Any chart-like object

Return type:

bool

Returns:

True if chart has MultiChart attributes (charts, chart_count, get_cross_aspects)

stellium.core.chart_utils.is_multiwheel(chart)[source]

Check if chart is a MultiWheel (deprecated type).

Uses duck-typing to avoid circular imports.

Parameters:

chart (Any) – Any chart-like object

Return type:

bool

Returns:

True if chart has MultiWheel attributes but not MultiChart attributes

stellium.core.chart_utils.is_single_chart(chart)[source]

Check if chart is a single CalculatedChart (not UnknownTimeChart or multi-chart).

Parameters:

chart (Any) – Any chart-like object

Return type:

bool

Returns:

True if chart is a single CalculatedChart (not UnknownTimeChart)

stellium.core.chart_utils.is_unknown_time_chart(chart)[source]

Check if chart is an UnknownTimeChart.

Parameters:

chart (Any) – Any chart-like object

Return type:

bool

Returns:

True if chart is an UnknownTimeChart

Multiwheel (stellium.core.multiwheel)

MultiWheel chart implementation for 2-4 chart comparisons.

This module provides a unified interface for rendering multiple charts concentrically inside a single zodiac wheel: - Biwheel (2 charts): Natal + transits, synastry, etc. - Triwheel (3 charts): Natal + progressed + transits - Quadwheel (4 charts): Maximum supported

Ring order (center → out): - Tiny aspect center (no aspect lines drawn) - Chart 1 ring (innermost) - houses + objects - Chart 2 ring - Chart 3 ring (if present) - Chart 4 ring (if present) - Zodiac ring (outermost)

Each chart ring includes: - Alternating house fills (theme-colored per chart) - House divider lines (full ring width) - Planet glyphs with compact info (degree only) - Position ticks on ring’s inner rim

class stellium.core.multiwheel.MultiWheel(charts, labels=(), cross_aspects=<factory>, calculation_timestamp=<factory>)[source]

Bases: object

Multi-chart comparison supporting 2-4 charts rendered concentrically.

All charts are rendered inside the zodiac ring, with Chart 1 as the innermost ring and subsequent charts expanding outward.

charts

Tuple of 2-4 CalculatedChart objects

labels

Optional labels for each chart (auto-generated if empty)

cross_aspects

Dict mapping chart index pairs to their cross-aspects

calculation_timestamp

When this MultiWheel was created

calculation_timestamp: datetime
property chart1: CalculatedChart

Primary chart (innermost ring).

property chart2: CalculatedChart

Second chart.

property chart3: CalculatedChart | None

Third chart (if present).

property chart4: CalculatedChart | None

Fourth chart (if present).

property chart_count: int

Number of charts in this MultiWheel.

charts: tuple[CalculatedChart, ...]
cross_aspects: dict[tuple[int, int], tuple[ComparisonAspect, ...]]
draw(filename='multiwheel.svg')[source]

Start building a multiwheel visualization.

Parameters:

filename (str) – Output filename for the SVG

Return type:

ChartDrawBuilder

Returns:

ChartDrawBuilder configured for this MultiWheel

labels: tuple[str, ...] = ()
class stellium.core.multiwheel.MultiWheelBuilder(charts)[source]

Bases: object

Fluent builder for creating MultiWheel objects.

Usage:
multiwheel = (MultiWheelBuilder

.from_charts([natal, transit, progressed]) .with_labels([“Natal”, “Transit”, “Progressed”]) .calculate())

# Or simply: multiwheel = MultiWheelBuilder.from_charts([chart1, chart2]).calculate()

calculate()[source]

Build the MultiWheel object.

Return type:

MultiWheel

Returns:

Configured MultiWheel ready for visualization

classmethod from_charts(charts)[source]

Create a MultiWheelBuilder from a list of calculated charts.

Parameters:

charts (list[CalculatedChart]) – List of 2-4 CalculatedChart objects

Return type:

MultiWheelBuilder

Returns:

MultiWheelBuilder ready for configuration

with_cross_aspects()[source]

Enable cross-chart aspect calculation.

Note: This can be expensive for 3-4 charts as it calculates aspects between all chart pairs.

Return type:

MultiWheelBuilder

Returns:

self for chaining

with_labels(labels)[source]

Set labels for each chart.

Parameters:

labels (list[str]) – List of labels (should match chart count)

Return type:

MultiWheelBuilder

Returns:

self for chaining

Protocols (stellium.core.protocols)

Interface definitions for extending Stellium.

Protocol definitions for Stellium components.

Protocols define INTERFACES - what methods a component must implement. They don’t provide implementation - that’s in the engine classes.

Think of these as contracts: “If you want to be an EphemerisEngine, you must implement these methods with these signatures.”

class stellium.core.protocols.AspectEngine(*args, **kwargs)[source]

Bases: Protocol

Protocol for aspect calculation engines.

Different implementations might use: - Traditional aspects (Ptolemaic) - Modern aspects (including minor aspects) - Harmonic aspects - Vedic aspects (completely different system)

calculate_aspects(positions, orb_engine)[source]

Calculate aspects between celestial objects.

Parameters:
  • positions (list[CelestialPosition]) – Objects to find aspects between

  • orb_engine (OrbEngine) – Optional custom orb settings

Return type:

list[Aspect]

Returns:

List of Aspect objects

class stellium.core.protocols.ChartAnalyzer(*args, **kwargs)[source]

Bases: Protocol

Protocol for chart analysis components.

Analyzers examine a calculated chart and return findings.

analyze(chart)[source]

Analyze the chart.

Parameters:

chart (CalculatedChart) – Chart to analyze

Return type:

list | dict

Returns:

Dict of findings (type depends on analyzer)

property analyzer_name: str

Name of this analyzer.

property metadata_name: str

Name that the metadata should be store under

class stellium.core.protocols.ChartComponent(*args, **kwargs)[source]

Bases: Protocol

Base protocol for chart calculation components.

Components can be: - Arabic part calculators - Midpoint finders - Pattern detectors (grand trine, T-square, etc.) - Fixed star calculators - Harmonic charts

calculate(datetime, location, positions, house_systems_map, house_placements_map)[source]

Calculate additional chart objects.

Parameters:
Return type:

list[CelestialPosition]

Returns:

List of additional CelestialPosition objects

property component_name: str

Name of this component.

metadata_name = ''
class stellium.core.protocols.ChartLike(*args, **kwargs)[source]

Bases: Protocol

Protocol for chart-like objects (single or multi-chart).

This protocol defines the common interface that all chart types should support, enabling code to work with CalculatedChart, MultiChart, or Comparison objects interchangeably where appropriate.

Note: The chart parameter in methods like get_object() defaults to 0, which works for single charts (ignored) and multi-charts (returns from first chart).

property datetime: ChartDateTime

The primary datetime of the chart (or first chart for multi-charts).

draw(filename='chart.svg')[source]

Create a visualization builder for this chart.

Parameters:

filename (str) – Default filename for saving

Return type:

ChartDrawBuilder

Returns:

ChartDrawBuilder configured for this chart

get_object(name, chart=0)[source]

Get a celestial object by name.

Parameters:
  • name (str) – Name of the object (e.g., “Sun”, “Moon”)

  • chart (int) – For multi-charts, which chart to query (0-indexed). Ignored for single charts.

Return type:

CelestialPosition | None

Returns:

The CelestialPosition if found, None otherwise

get_planets(chart=0)[source]

Get all planetary positions.

Parameters:

chart (int) – For multi-charts, which chart to query (0-indexed). Ignored for single charts.

Return type:

list[CelestialPosition]

Returns:

List of planet CelestialPosition objects

property location: ChartLocation

The primary location of the chart (or first chart for multi-charts).

property metadata: dict[str, Any]

Chart metadata dictionary.

class stellium.core.protocols.CrossChartAspectEngine(*args, **kwargs)[source]

Bases: Protocol

Protocol for calculating aspects between two charts.

This is separate from AspectEngine to allow different orb configurations and aspect sets for cross-chart work.

Use cases: - Synastry: Person A’s planets aspecting Person B’s planets - Transits: Current sky aspecting natal chart - Progressions: Progressed chart aspecting natal chart

calculate_cross_aspects(chart1_positions, chart2_positions, orb_engine)[source]

Calculate aspects between two sets of positions.

Only calculates aspects where one object is from chart1 and the other is from chart2. Internal aspects within each chart are not calculated.

Parameters:
  • chart1_positions (list[CelestialPosition]) – Positions from first chart (e.g., natal/inner)

  • chart2_positions (list[CelestialPosition]) – Positions from second chart (e.g., transit/outer)

  • orb_engine (OrbEngine) – The OrbEngine that will provide orb allowances

Return type:

list[Aspect]

Returns:

List of Aspect objects representing cross-chart aspects

class stellium.core.protocols.DignityCalculator(*args, **kwargs)[source]

Bases: Protocol

Protocol for dignity/debility calculation.

Different implementations: - Traditional essential dignities - Modern rulerships - Vedic dignity system

calculate_dignities(position)[source]

Calculate dignities for a celestial position.

Parameters:

position (CelestialPosition) – Position to calculate dignities for

Return type:

dict[str, Any]

Returns:

Dictionary with dignity information

class stellium.core.protocols.EphemerisEngine(*args, **kwargs)[source]

Bases: Protocol

Protocol for planetary position calculation engines.

Different implementations might use: - Swiss Ephemeris - JPL Ephemeris - Custom calculation algorithms - Mock data for testing

calculate_positions(datetime, location, objects=None, config=None)[source]

Calculate positions for celestial objects.

Parameters:
  • datetime (ChartDateTime) – When to calculate positions

  • location (ChartLocation) – Where to calculate from (for topocentric)

  • objects (list[str] | None) – Which objects to calculate (None = all standard objects)

  • config (CalculationConfig | None) – Optional calculation configuration (zodiac type, etc.)

Return type:

list[CelestialPosition]

Returns:

List of CelestialPosition objects

class stellium.core.protocols.HouseSystemEngine(*args, **kwargs)[source]

Bases: Protocol

Protocol for house system calculation engines.

Different implementations for different house systems: - Whole Sign - Placidus - Koch - Equal House - etc

assign_houses(positions, cusps)[source]

Assign house numbers to celestial positions.

Parameters:
Returns:

house_number}

Return type:

A dictionary of {object_name

calculate_house_data(datetime, location)[source]

Calculate house cusps for this system.

Parameters:
Returns:

  1. HouseCusps object with 12 cusp positions (For this specific system)

  2. A List of CelestialPosition objects for the primary angles (ASC, MC, DSC, IC, Vertex)

Return type:

Tuple containing

property system_name: str

Name of this house system (e.g. Placidus)

class stellium.core.protocols.OrbEngine(*args, **kwargs)[source]

Bases: Protocol

Protocol for orb calculation.

Encapsulates logic for determining orb allowance, which can be simple (by aspect) or complex (by planet, by planet pair, by day/night, etc.).

get_orb_allowance(obj1, obj2, aspect_name)[source]

Get the allowed orb for a specific aspect between two objects.

Parameters:
Return type:

float

Returns:

The maximum allowed orb in degrees

class stellium.core.protocols.ReportRenderer(*args, **kwargs)[source]

Bases: Protocol

Protocol for output renderers.

Renderers take structured section data and format it for a specific output medium (terminal, plain text, HTML, etc.).

Why separate renderers? - Same data, multiple output formats - Easy to add new formats without touching section code - Testable in isolation

render_report(sections)[source]

Render a complete report with multiple sections.

Parameters:

sections (list[tuple[str, dict[str, Any]]]) – List of (section_name, section_data) tuples

Return type:

str

Returns:

Complete formatted report

render_section(section_name, section_data)[source]

Render a single section.

Parameters:
  • section_name (str) – Header for the section

  • section_data (dict[str, Any]) – Structured data from section.generate_data()

Return type:

str

Returns:

Formatted string for this section

class stellium.core.protocols.ReportSection(*args, **kwargs)[source]

Bases: Protocol

Protocol for report sections.

Each section knows how to extract data from a chart (single or multi-chart) and format it into a standardized structure that renderers can consume.

Multi-Chart Support: Sections may receive any of: - CalculatedChart: Single natal/event chart - Comparison: Two-chart comparison (deprecated, use MultiChart) - MultiChart: 2-4 charts for synastry, transits, progressions, etc.

Implementations should use stellium.core.chart_utils helpers to handle different chart types consistently: - get_all_charts(chart) - Get list of all charts - get_chart_labels(chart) - Get labels for each chart - chart_count(chart) - Get number of charts

Why a protocol? - Extensibility: Users can create custom sections - Type safety: MyPy/Pyright can verify implementations - No inheritance required: Keep components lightweight

generate_data(chart)[source]

Extract and structure data from the chart.

Returns a standardized dictionary format that renderers understand:

{
    "type": "table" | "text" | "key_value" | "side_by_side_tables" | "grouped_tables",
    "headers": [...],      # For tables
    "rows": [...],         # For tables
    "text": "...",         # For text blocks
    "data": {...},         # For key-value pairs
    "tables": [...],       # For side_by_side_tables or grouped_tables
}
Parameters:

chart (CalculatedChart | Comparison | MultiChart) – The chart to extract data from (may be single or multi-chart)

Return type:

dict[str, Any]

Returns:

Structured data dictionary

property section_name: str

Human-readable name for this section.

Used as a header in rendered output.


Engines (stellium.engines)

Calculation engines for ephemeris, houses, aspects, orbs, dignities, and fixed stars.

Ephemeris calculation engines.

class stellium.engines.ephemeris.MockEphemerisEngine(mock_data=None)[source]

Bases: object

Mock ephemeris engine for testing.

Returns fixed positions instead of calculating them.

Useful for: - Unit tests - Development - Benchmarking other components

calculate_positions(datetime, location, objects=None, config=None)[source]

Return mock positions.

Parameters:
  • datetime (ChartDateTime) – When to calculate positions (ignored in mock)

  • location (ChartLocation) – Where to calculate from (ignored in mock)

  • objects (list[str] | None) – Which objects to calculate (None = all mock objects)

  • config (CalculationConfig | None) – Calculation config (ignored in mock)

Return type:

list[CelestialPosition]

Returns:

List of mock CelestialPosition objects

class stellium.engines.ephemeris.SwissEphemerisEngine(ephe_path=None)[source]

Bases: object

Swiss Ephemeris calculation engine.

This is our default, high-precision ephemeris calculator. Uses the pyswisseph library for accurate planetary positions.

calculate_positions(datetime, location, objects=None, config=None)[source]

Calculate positions using Swiss Ephemeris.

Parameters:
  • datetime (ChartDateTime) – When to calculate

  • location (ChartLocation) – Where to calculate from

  • objects (list[str] | None) – Which objects to calculate (None = all standard)

  • config (CalculationConfig | None) – Calculation configuration (for zodiac type)

Return type:

list[CelestialPosition]

Returns:

List of CelestialPosition objects

House system calculation engines.

class stellium.engines.houses.APCHouses[source]

Bases: SwissHouseSystemBase

APC house system engine.

property system_name: str
class stellium.engines.houses.AlcabitiusHouses[source]

Bases: SwissHouseSystemBase

Alcabitius house system engine.

property system_name: str
class stellium.engines.houses.AxialRotationHouses[source]

Bases: SwissHouseSystemBase

Axial Rotation house system engine.

property system_name: str
class stellium.engines.houses.CampanusHouses[source]

Bases: SwissHouseSystemBase

Campanus house system engine.

property system_name: str
class stellium.engines.houses.EqualHouses[source]

Bases: SwissHouseSystemBase

Equal house system engine.

property system_name: str
class stellium.engines.houses.EqualMCHouses[source]

Bases: SwissHouseSystemBase

Equal (MC) house system engine.

property system_name: str
class stellium.engines.houses.EqualVertexHouses[source]

Bases: SwissHouseSystemBase

Equal (Vertex) house system engine.

property system_name: str
class stellium.engines.houses.GauquelinHouses[source]

Bases: SwissHouseSystemBase

Gauquelin house system engine.

property system_name: str
class stellium.engines.houses.HorizontalHouses[source]

Bases: SwissHouseSystemBase

Horizontal house system engine.

property system_name: str
class stellium.engines.houses.KochHouses[source]

Bases: SwissHouseSystemBase

Koch house system engine.

property system_name: str
class stellium.engines.houses.KrusinskiHouses[source]

Bases: SwissHouseSystemBase

Krusinski house system engine.

property system_name: str
class stellium.engines.houses.MorinusHouses[source]

Bases: SwissHouseSystemBase

Morinus house system engine.

property system_name: str
class stellium.engines.houses.PlacidusHouses[source]

Bases: SwissHouseSystemBase

Placidus house system engine.

property system_name: str
class stellium.engines.houses.PorphyryHouses[source]

Bases: SwissHouseSystemBase

Porphyry house system engine.

property system_name: str
class stellium.engines.houses.RegiomontanusHouses[source]

Bases: SwissHouseSystemBase

Regiomontanus house system engine.

property system_name: str
class stellium.engines.houses.SwissHouseSystemBase[source]

Bases: object

Provides a default implementation for calling swisseph and assigning houses.

This is NOT a protocol, just a helper class for code reuse.

assign_houses(positions, cusps)[source]

Assign house numbers to positions. Returns a simple name: house dict.

Return type:

dict[str, int]

calculate_house_data(datetime, location, config=None)[source]

Calculate house system’s house cusps and chart angles.

Parameters:
  • datetime (ChartDateTime) – Chart datetime

  • location (ChartLocation) – Chart location

  • config (CalculationConfig | None) – Calculation configuration (for zodiac type)

Return type:

tuple[HouseCusps, list[CelestialPosition]]

Returns:

Tuple of (house cusps, angle positions)

property system_name: str
class stellium.engines.houses.TopocentricHouses[source]

Bases: SwissHouseSystemBase

Topocentric house system engine.

property system_name: str
class stellium.engines.houses.VehlowEqualHouses[source]

Bases: SwissHouseSystemBase

Vehlow Equal house system engine.

property system_name: str
class stellium.engines.houses.WholeSignHouses[source]

Bases: SwissHouseSystemBase

Whole sign house system engine.

property system_name: str

Aspect calculation engines.

These engines are responsible for finding angular relationships (aspects) between celestial objects. They follow the AspectEngine protocol.

class stellium.engines.aspects.CrossChartAspectEngine(config=None)[source]

Bases: object

Calculate aspects between two separate charts.

Unlike ModernAspectEngine which finds all aspects within a single chart (using combinations of all positions), this engine specifically handles cross-chart scenarios where we want aspects BETWEEN chart1 objects and chart2 objects (but not within each chart).

Use cases: - Synastry: Person A’s planets aspecting Person B’s planets - Transits: Current sky aspecting natal chart - Progressions: Progressed chart aspecting natal chart

The key difference: controlled iteration. We only check pairs where one object is from chart1 and the other is from chart2. This prevents: - Object identity collision (same planet in both charts) - Redundant calculation (internal aspects already calculated separately) - Incorrect filtering (can’t distinguish sources after merging lists)

calculate_cross_aspects(chart1_positions, chart2_positions, orb_engine)[source]

Calculate aspects between two sets of positions.

This only calculates aspects WHERE one object is from chart1 and the other is from chart2. Internal aspects within each chart are NOT calculated by this method.

Parameters:
  • chart1_positions (list[CelestialPosition]) – Positions from first chart (e.g., natal/inner)

  • chart2_positions (list[CelestialPosition]) – Positions from second chart (e.g., transit/outer)

  • orb_engine (OrbEngine) – The OrbEngine that will provide orb allowances

Return type:

list[Aspect]

Returns:

List of Aspect objects representing cross-chart aspects

Example

>>> engine = CrossChartAspectEngine()
>>> orb_engine = SimpleOrbEngine()
>>> aspects = engine.calculate_cross_aspects(
...     natal_chart.positions,
...     transit_chart.positions,
...     orb_engine
... )
>>> # Gets natal Sun trine transit Jupiter, etc.
>>> # Does NOT get natal Sun trine natal Moon (internal)
class stellium.engines.aspects.DeclinationAspectEngine(orb=1.0, include_types=None)[source]

Bases: object

Calculates declination aspects (Parallel and Contraparallel).

Declination aspects are based on celestial equatorial coordinates: - Parallel: Two bodies at the SAME declination (both north or both south).

Interpreted similarly to a conjunction - blending of energies.

  • Contraparallel: Two bodies at the SAME declination magnitude but OPPOSITE hemispheres. Interpreted similarly to an opposition - polarity.

Unlike longitude-based aspects which use variable orbs by planet, declination aspects traditionally use a fixed tight orb (1.0-1.5 degrees).

Example

>>> engine = DeclinationAspectEngine(orb=1.0)
>>> aspects = engine.calculate_aspects(chart.positions)
>>> for asp in aspects:
...     print(asp)
Sun Parallel Moon (orb: 0.45°)
Mars Contraparallel Saturn (orb: 0.78°)
calculate_aspects(positions, orb_engine=None)[source]

Calculate parallel and contraparallel aspects.

Parameters:
  • positions (list[CelestialPosition]) – List of CelestialPosition objects to check. Only positions with non-None declination are used.

  • orb_engine (OrbEngine | None) – Ignored. Declination aspects use fixed orb. Included for compatibility with AspectEngine protocol.

Return type:

list[Aspect]

Returns:

List of Aspect objects for detected declination aspects.

class stellium.engines.aspects.HarmonicAspectEngine(harmonic)[source]

Bases: object

Calculates harmonic aspects (eg H5, H7, H9). This engine does not use AspectConfig, as it defines its own angles. It does use the OrbEngine, which can be configured to give different orbs for different harmonics.

calculate_aspects(positions, orb_engine)[source]

Calculate harmonic aspects for the configured harmonic number.

Currently only calculates between ObjectType=Planet objects.

Parameters:
  • positions (list[CelestialPosition]) – The list of CelestialPositions objects to check.

  • orb_engine (OrbEngine) – The OrbEngine that will provide the orb allowance.

Return type:

list[Aspect]

Returns:

A list of found Aspect objects.

class stellium.engines.aspects.ModernAspectEngine(config=None)[source]

Bases: object

Calculates standard aspects (conjunction, square, trine, etc.) based on a provided AspectConfig.

calculate_aspects(positions, orb_engine)[source]

Calculate aspects based on the engine’s config and the provided orb engine.

Parameters:
  • positions (list[CelestialPosition]) – The list of CelestialPosition objects to check.

  • orb_engine (OrbEngine) – The OrbEngine that will provide the orb allowance for each potential aspect.

Return type:

list[Aspect]

Returns:

A list of found Aspect objects.

Orb Calculation Engines.

These engines implement the OrbEngine protocol to provide different systems for calculating aspect orbs.

class stellium.engines.orbs.ComplexOrbEngine(config)[source]

Bases: object

Implements OrbEngine with a cascading priority matrix.

This engine can handle the most complex traditions by allowing orbs to be defined by pair, by single planet, or by aspect.

The config is a nested dictionary defining the priority.

get_orb_allowance(obj1, obj2, aspect_name)[source]

Finds the most specific orb available based on priority.

Return type:

float

class stellium.engines.orbs.LuminariesOrbEngine(luminary_orbs=None, default_orbs=None, fallback_orb=None)[source]

Bases: object

Implements OrbEngine with special rules for Luminaries.

This is a common system where aspects to the Sun or Moon are given a wider orb than aspects to other planets.

get_orb_allowance(obj1, obj2, aspect_name)[source]

Gets the orb, checking if a luminary is involved.

Return type:

float

class stellium.engines.orbs.MoietyOrbEngine(orb_map=None, system=None, fallback_orb=None, minor_aspect_multiplier=None)[source]

Bases: object

Implements OrbEngine using the traditional moiety (half-orb) system.

Each planet has its own full orb value. The effective orb for an aspect between two planets is the average of their full orbs:

effective_orb = (full_orb_A + full_orb_B) / 2

This is the universal formula across all traditional sources from Sahl ibn Bishr (820 CE) through William Lilly (1647).

Supports named systems (“lilly”, “ptolemy”) for preset values, or custom orb maps for full control.

Example:

# Default (Lilly / medieval consensus)
engine = MoietyOrbEngine()

# Ptolemaic (larger orbs)
engine = MoietyOrbEngine(system="ptolemy")

# Custom values
engine = MoietyOrbEngine(orb_map={"Sun": 15, "Moon": 12, "Mars": 7})

# With tighter orbs for minor/harmonic aspects
engine = MoietyOrbEngine(minor_aspect_multiplier=0.4)
get_orb_allowance(obj1, obj2, aspect_name)[source]

Calculate effective orb as the average of both planets’ full orbs.

Return type:

float

class stellium.engines.orbs.SimpleOrbEngine(orb_map=None, fallback_orb=None)[source]

Bases: object

Implements OrbEngine for simple, aspect-based orbs.

This engine uses a single dictionary mapping an aspect name to an orb value, regardless of the planets involved.

This is the default engine used by ChartBuilder.

get_orb_allowance(obj1, obj2, aspect_name)[source]

Gets the orb for the given aspect name. Ignores the planets.

Return type:

float

Dignity calculation engines.

This module provides comprehensive essential dignity calculations for both traditional (pre-1781) and modern astrological systems. It evaluates planetary strength through rulership, exaltation, detriment, fall, triplicity, terms/bounds, faces/decans, and mutual reception.

class stellium.engines.dignities.ModernDignityCalculator(decans='chaldean')[source]

Bases: object

Modern essential dignities calculator (post-1781).

Includes outer planets (Uranus, Neptune, Pluto) and uses modern rulership assignments. The scoring system is adapted for modern rulerships while maintaining traditional dignity principles.

Scoring system: - Domicile/Rulership: +5 points (modern ruler), +3 points (traditional ruler) - Exaltation: +4 points - Triplicity ruler: +3 points - Term/Bound ruler: +2 points - Face/Decan ruler: +1 point - Detriment: -5 points (modern), -3 points (traditional) - Fall: -4 points

MODERN_PLANETS = ['Sun', 'Moon', 'Mercury', 'Venus', 'Mars', 'Jupiter', 'Saturn', 'Uranus', 'Neptune', 'Pluto']
calculate_dignities(position, sect='day')[source]

Calculate modern dignities for a position.

Parameters:
  • position (CelestialPosition) – CelestialPosition to analyze

  • is_day_chart – Whether this is a day chart (Sun above horizon). Affects triplicity ruler selection.

Return type:

dict[str, Any]

Returns:

Dictionary with comprehensive dignity information.

property calculator_name: str

Name of this calculator.

class stellium.engines.dignities.MutualReceptionAnalyzer(system='traditional')[source]

Bases: object

Analyze mutual reception between planets in a chart.

Mutual reception occurs when two planets are each in a sign ruled or exalted by the other. This creates a special bond and can modify the expression of both planets.

find_mutual_receptions(positions)[source]

Find all mutual receptions in a set of positions.

Parameters:

positions (list[CelestialPosition]) – List of CelestialPosition objects to analyze

Return type:

list[dict[str, Any]]

Returns:

List of mutual reception dictionaries with details

class stellium.engines.dignities.TraditionalDignityCalculator(decans='chaldean')[source]

Bases: object

Traditional essential dignities calculator (pre-1781).

Uses only the seven traditional planets (Sun through Saturn) and calculates dignities according to classical astrological principles.

Scoring system: - Domicile/Rulership: +5 points - Exaltation: +4 points - Triplicity ruler: +3 points - Term/Bound ruler: +2 points - Face/Decan ruler: +1 point - Detriment: -5 points - Fall: -4 points - Peregrine (no dignities): 0 points

TRADITIONAL_PLANETS = ['Sun', 'Moon', 'Mercury', 'Venus', 'Mars', 'Jupiter', 'Saturn']
calculate_dignities(position, sect='day')[source]

Calculate traditional dignities for a position.

Parameters:
  • position (CelestialPosition) – CelestialPosition to analyze

  • sect (str | None) – Chart sect. Can be “day” or “night” (defaults to day)

Returns:

  • dignities: List of dignity types held

  • score: Total dignity score

  • details: Breakdown of each dignity

  • is_peregrine: Whether planet is peregrine (no dignities)

  • reception_potential: Planets this one could have mutual reception with

Return type:

Dictionary with comprehensive dignity information including

property calculator_name: str

Name of this calculator

Fixed Stars calculation engine using Swiss Ephemeris.

This module provides the engine for calculating fixed star positions at any given time. Swiss Ephemeris handles precession automatically - just pass the Julian Day and it returns the correct ecliptic longitude for that epoch.

Usage:
>>> from stellium.engines.fixed_stars import SwissEphemerisFixedStarsEngine
>>> engine = SwissEphemerisFixedStarsEngine()
>>> stars = engine.calculate_stars(julian_day=2451545.0)  # All registered stars
>>> royal_stars = engine.calculate_stars(julian_day, stars=["Regulus", "Aldebaran"])
class stellium.engines.fixed_stars.FixedStarsEngine(*args, **kwargs)[source]

Bases: Protocol

Protocol for fixed star calculation engines.

Implementations must provide a method to calculate positions for fixed stars at a given Julian Day.

calculate_stars(julian_day, stars=None)[source]

Calculate positions for specified fixed stars.

Parameters:
  • julian_day (float) – The Julian Day for calculation

  • stars (list[str] | None) – List of star names to calculate. If None, calculates all stars in FIXED_STARS_REGISTRY.

Return type:

list[FixedStarPosition]

Returns:

List of FixedStarPosition objects with calculated positions

class stellium.engines.fixed_stars.SwissEphemerisFixedStarsEngine(registry=None)[source]

Bases: object

Swiss Ephemeris implementation of fixed star calculations.

Uses swe.fixstar_ut() to calculate precise ecliptic positions for fixed stars, with automatic precession handling.

The engine pulls metadata from FIXED_STARS_REGISTRY to enrich the position objects with traditional astrological meanings.

registry

The fixed star registry to use (defaults to FIXED_STARS_REGISTRY)

Example

>>> engine = SwissEphemerisFixedStarsEngine()
>>> # Calculate all stars
>>> all_stars = engine.calculate_stars(julian_day=2451545.0)
>>> # Calculate specific stars
>>> royal = engine.calculate_stars(2451545.0, stars=["Regulus", "Aldebaran"])
>>> print(f"{royal[0].name}: {royal[0].sign_position}")
Regulus: 29°50' Leo
calculate_royal_stars(julian_day)[source]

Calculate positions for the four Royal Stars of Persia.

A convenience method for getting just the most important stars: Aldebaran, Regulus, Antares, and Fomalhaut.

Parameters:

julian_day (float) – The Julian Day for calculation

Return type:

list[FixedStarPosition]

Returns:

List of FixedStarPosition objects for the four royal stars

calculate_stars(julian_day, stars=None)[source]

Calculate positions for specified fixed stars.

Parameters:
  • julian_day (float) – The Julian Day for calculation. Swiss Ephemeris handles precession automatically based on this value.

  • stars (list[str] | None) – List of star names to calculate. If None, calculates all stars in the registry.

Return type:

list[FixedStarPosition]

Returns:

List of FixedStarPosition objects with calculated ecliptic positions and registry metadata.

Raises:

ValueError – If a requested star is not in the registry

calculate_stars_by_tier(julian_day, tier)[source]

Calculate positions for all stars of a specific tier.

Parameters:
  • julian_day (float) – The Julian Day for calculation

  • tier (int) – The tier level (1=Royal, 2=Major, 3=Extended)

Return type:

list[FixedStarPosition]

Returns:

List of FixedStarPosition objects for stars of the specified tier

get_magnitude(star_name)[source]

Get the apparent magnitude of a star.

This uses the registry value rather than calling swe.fixstar_mag() since we’ve already populated magnitude in FixedStarInfo.

Parameters:

star_name (str) – Name of the star

Return type:

float | None

Returns:

Apparent magnitude (lower = brighter), or None if not found

Aspect Pattern Analyzer Engine.

Finds major aspect patterns like Grand Trines, T-Squares, Yods, Kites, Grand Crosses, and Stelliums. This module implements the “Analyzer” protocol.

class stellium.engines.patterns.AspectPatternAnalyzer(stellium_min=3)[source]

Bases: object

Implements the Analyzer protocol to find major aspect patterns. The results are stored in chart.metadata[‘aspect_patterns’]

analyze(chart)[source]

Runs all pattern detectors and returns a list of findings serialized as dictionaries for metadata.

Return type:

list[AspectPattern]

property analyzer_name: str
property metadata_name: str

Profection calculation engine.

Profections are a Hellenistic timing technique where the Ascendant (and other points) move forward one sign per year of life. The planet ruling the profected sign becomes the “Lord of the Year” - a key focus for that year’s themes.

Example

>>> from stellium import ChartBuilder
>>> from stellium.engines.profections import ProfectionEngine
>>>
>>> chart = ChartBuilder.from_notable("Albert Einstein").calculate()
>>> engine = ProfectionEngine(chart)
>>>
>>> # Annual profection for age 30
>>> result = engine.annual(30)
>>> print(f"Age 30: {result.profected_sign} year, Lord = {result.ruler}")
>>>
>>> # Multi-point profections
>>> results = engine.multi(30, points=["ASC", "Sun", "Moon", "MC"])
>>> print(f"Lords: {results.lords}")
class stellium.engines.profections.MultiProfectionResult(age, date, results)[source]

Bases: object

Profections from multiple points for the same time period.

Useful for seeing all the lords at once - e.g., who rules the profected ASC, Sun, Moon, MC, and Fortune for age 30.

age

The age for these profections

date

Optional specific date (for monthly profections)

results

Dictionary of ProfectionResult keyed by point name

property activated_houses: dict[str, int]

Get all activated houses by point name.

age: int
date: datetime | None
property lords: dict[str, str]

Get all lords by point name.

results: dict[str, ProfectionResult]
class stellium.engines.profections.ProfectionEngine(chart, house_system=None, rulership='traditional')[source]

Bases: object

General-purpose profection calculator.

Profections move a point forward one sign per unit of time (year, month, day). This engine handles all the complexity of looking up houses, rulers, and finding what planets are activated.

Parameters:
  • chart (CalculatedChart) – The natal chart to profect from

  • house_system (str | None) – House system to use (default “Whole Sign” - traditional)

  • rulership (Literal['traditional', 'modern']) – Rulership system (“traditional” or “modern”)

Example

>>> engine = ProfectionEngine(chart)
>>> result = engine.annual(30)  # Age 30 profection
>>> print(result.ruler)  # Lord of the Year
DEFAULT_POINTS = ['ASC', 'Sun', 'Moon', 'MC']
annual(age, point='ASC')[source]

Annual profection for a given age.

This is the most common use case: what house and lord are activated for a specific year of life?

Parameters:
  • age (int) – Age in completed years (0 = first year of life)

  • point (str) – Point to profect (default “ASC”)

Return type:

ProfectionResult

Returns:

ProfectionResult for that age

Example

>>> result = engine.annual(30)
>>> print(f"At age 30: {result.profected_sign} year")
>>> print(f"Lord of the Year: {result.ruler}")
for_date(date, point='ASC', include_monthly=True)[source]

Calculate profections for a specific date.

If include_monthly is True, returns both annual and monthly profection.

Parameters:
  • date (datetime | str) – Target date (datetime or ISO string)

  • point (str) – Point to profect (default “ASC”)

  • include_monthly (bool) – Whether to calculate monthly profection too

Return type:

ProfectionResult | tuple[ProfectionResult, ProfectionResult]

Returns:

ProfectionResult, or tuple of (annual, monthly) if include_monthly

Example

>>> annual, monthly = engine.for_date("2025-06-15")
>>> print(f"Year: {annual.ruler}, Month: {monthly.ruler}")
lord_of_month(age, month, point='ASC')[source]

Convenience: just get the Lord of the Month.

Parameters:
  • age (int) – Age in completed years

  • month (int) – Month within profection year (0-11)

  • point (str) – Point to profect (default “ASC”)

Return type:

str

Returns:

Name of the ruling planet

lord_of_year(age, point='ASC')[source]

Convenience: just get the Lord of the Year.

Parameters:
  • age (int) – Age in completed years

  • point (str) – Point to profect (default “ASC”)

Return type:

str

Returns:

Name of the ruling planet

Example

>>> print(engine.lord_of_year(30))  # "Saturn"
monthly(age, month, point='ASC')[source]

Monthly profection within a given year.

Profects forward by (age + month) signs total.

Parameters:
  • age (int) – Age in completed years

  • month (int) – Month within the profection year (0-11)

  • point (str) – Point to profect (default “ASC”)

Return type:

ProfectionResult

Returns:

ProfectionResult for that month

Example

>>> # 4th month of age 30 year
>>> result = engine.monthly(age=30, month=4)
>>> print(f"Month 4: {result.profected_sign}")
multi(age, points=None)[source]

Profect multiple points at once.

Useful for seeing all the lords for a given age - who rules the profected ASC, Sun, Moon, MC?

Parameters:
  • age (int) – Age in completed years

  • points (list[str] | None) – Points to profect (default: ASC, Sun, Moon, MC)

Return type:

MultiProfectionResult

Returns:

MultiProfectionResult with all profections

Example

>>> results = engine.multi(30)
>>> print(results.lords)  # {"ASC": "Saturn", "Sun": "Mars", ...}
multi_for_date(date, points=None)[source]

Profect multiple points for a specific date.

Parameters:
  • date (datetime | str) – Target date

  • points (list[str] | None) – Points to profect (default: ASC, Sun, Moon, MC)

Return type:

MultiProfectionResult

Returns:

MultiProfectionResult with date attached

profect(point, units, unit_type='year')[source]

Core profection operation.

Profects any point forward by N signs and returns everything you’d want to know about the result.

Parameters:
  • point (str) – Point to profect (“ASC”, “Sun”, “Moon”, “MC”, etc.)

  • units (int) – Number of signs to move forward (typically age for years)

  • unit_type (str) – Type of profection (“year”, “month”, “day”)

Return type:

ProfectionResult

Returns:

ProfectionResult with full details

Example

>>> result = engine.profect("ASC", units=30, unit_type="year")
>>> print(f"House {result.profected_house}: {result.profected_sign}")
timeline(start_age, end_age, point='ASC')[source]

Generate profections for a range of ages.

Useful for seeing the sequence of lords through life, or for timeline visualizations.

Parameters:
  • start_age (int) – First age (inclusive)

  • end_age (int) – Last age (inclusive)

  • point (str) – Point to profect (default “ASC”)

Return type:

ProfectionTimeline

Returns:

ProfectionTimeline with all entries

Example

>>> timeline = engine.timeline(25, 35)
>>> for entry in timeline.entries:
...     print(f"Age {entry.units}: {entry.ruler}")
class stellium.engines.profections.ProfectionResult(source_point, source_sign, source_house, units, unit_type, profected_house, profected_sign, ruler, ruler_position, ruler_house, ruler_modern, planets_in_house=<factory>)[source]

Bases: object

Result of profecting a single point.

Contains everything you’d want to know about a profection: what was profected, where it landed, who rules it, and what’s there.

source_point

Name of the profected point (“ASC”, “Sun”, etc.)

source_sign

The sign the point is in natally

source_house

The house the point is in natally (1-12)

units

How many signs forward the point moved

unit_type

Type of profection (“year”, “month”, “day”)

profected_house

The house that is activated (1-12)

profected_sign

The sign on that house cusp

ruler

Traditional ruler of the profected sign (Lord of Year/Month)

ruler_position

Natal position of the ruling planet

ruler_house

Which house the ruler is in natally

ruler_modern

Modern ruler if different from traditional

planets_in_house

List of natal planets in the profected house

planets_in_house: tuple[CelestialPosition, ...]
profected_house: int
profected_sign: str
ruler: str
ruler_house: int | None
ruler_modern: str | None
ruler_position: CelestialPosition | None
source_house: int
source_point: str
source_sign: str
unit_type: str
units: int
class stellium.engines.profections.ProfectionTimeline(point, start_age, end_age, entries)[source]

Bases: object

A range of profections over time.

Useful for seeing the sequence of lords through a span of life, or for displaying in a timeline visualization.

point

The point being profected (e.g., “ASC”)

start_age

First age in the timeline

end_age

Last age in the timeline

entries

List of ProfectionResult for each age

end_age: int
entries: tuple[ProfectionResult, ...]
find_by_lord(lord)[source]

Find all years ruled by a specific planet.

Return type:

list[ProfectionResult]

lords_sequence()[source]

Get the sequence of lords.

Return type:

list[str]

point: str
start_age: int
stellium.engines.profections.get_sign_ruler(sign, system='traditional')[source]

Get the planetary ruler of a zodiac sign.

Parameters:
  • sign (str) – The zodiac sign name (e.g., “Aries”, “Leo”)

  • system (Literal['traditional', 'modern']) – “traditional” (classical rulerships) or “modern” (includes outer planets)

Return type:

str

Returns:

The name of the ruling planet

stellium.engines.profections.index_to_sign(index)[source]

Convert index to sign name, wrapping around.

Return type:

str

stellium.engines.profections.sign_to_index(sign)[source]

Convert sign name to index (0-11).

Return type:

int

Dispositor graph calculation engine.

Dispositors trace the “chain of command” in a chart - each planet is disposed by the ruler of the sign it occupies. Following these chains reveals:

  1. Planetary Dispositors: Which planets “dispose” (rule over) which others. The final dispositor is the planet that rules its own sign (e.g., Mars in Aries).

  2. House Dispositors (Kate’s innovation): Which life areas flow into which others. “What planet rules this house’s cusp, and what house is THAT planet in?” The final dispositor house is the life area that supports/feeds the others.

Example

>>> from stellium import ChartBuilder
>>> from stellium.engines.dispositors import DispositorEngine
>>>
>>> chart = ChartBuilder.from_notable("Albert Einstein").calculate()
>>> engine = DispositorEngine(chart)
>>>
>>> # Planetary dispositors
>>> planetary = engine.planetary()
>>> print(f"Final dispositor: {planetary.final_dispositor}")
>>> print(f"Mutual receptions: {planetary.mutual_receptions}")
>>>
>>> # House dispositors (Kate's innovation)
>>> houses = engine.house_based()
>>> print(f"Final dispositor house: {houses.final_dispositor}")
class stellium.engines.dispositors.DispositorEdge(source, target, source_sign, ruler)[source]

Bases: object

A single edge in the dispositor graph.

For planetary: “Sun in Leo is disposed by Sun” (self-disposing) For house: “House 10 (Capricorn) flows to House 11 (where Saturn is)”

ruler: str
source: str
source_sign: str
target: str
class stellium.engines.dispositors.DispositorEngine(chart, rulership_system='traditional', house_system=None)[source]

Bases: object

Calculate dispositor graphs for a chart.

Supports two modes: - Planetary: Traditional planet-rules-planet dispositors - House: Kate’s innovation - life-area-flows-to-life-area dispositors

Example

>>> chart = ChartBuilder.from_notable("Albert Einstein").calculate()
>>> engine = DispositorEngine(chart)
>>>
>>> # Planetary dispositors
>>> p = engine.planetary()
>>> print(p.final_dispositor)
>>>
>>> # House dispositors
>>> h = engine.house_based()
>>> print(h.final_dispositor)
house_based()[source]

Calculate house-based dispositor graph (Kate’s innovation).

For each house: find the ruler of the sign on its cusp, then find what house that ruling planet is in.

This shows how life areas flow into and support each other.

Return type:

DispositorResult

Returns:

DispositorResult with house-based dispositor analysis

planetary()[source]

Calculate planetary dispositor graph.

Each planet is disposed by the ruler of the sign it occupies. A planet in its own sign (e.g., Mars in Aries) is self-disposing.

Return type:

DispositorResult

Returns:

DispositorResult with planetary dispositor analysis

class stellium.engines.dispositors.DispositorResult(mode, edges, final_dispositor, mutual_receptions, chains, rulership_system)[source]

Bases: object

Complete dispositor analysis result.

Contains the full graph structure, final dispositor(s), mutual receptions, and chains for analysis.

mode

“planetary” or “house”

edges

All edges in the dispositor graph

final_dispositor

The node(s) where all chains terminate (or None if loops)

mutual_receptions

List of mutual reception pairs

chains

Dict mapping each starting node to its full chain

rulership_system

“traditional” or “modern”

chains: dict[str, list[str]]
edges: tuple[DispositorEdge, ...]
final_dispositor: str | tuple[str, ...] | None
get_chain(start)[source]

Get the full dispositor chain starting from a node.

Return type:

list[str]

mode: Literal['planetary', 'house']
mutual_receptions: tuple[MutualReception, ...]
rulership_system: str
to_dict()[source]

Serialize to dictionary for JSON export.

Return type:

dict

class stellium.engines.dispositors.MutualReception(node1, node2, planet1=None, planet2=None)[source]

Bases: object

Two nodes that dispose each other.

Planetary: Mars in Capricorn ↔ Saturn in Aries (each rules the other’s sign) House: House 9 ↔ House 11 (their rulers are in each other’s houses)

node1: str
node2: str
planet1: str | None = None
planet2: str | None = None
stellium.engines.dispositors.get_sign_ruler(sign, system='traditional')[source]

Get the planetary ruler of a zodiac sign.

Parameters:
  • sign (str) – The zodiac sign name (e.g., “Aries”, “Leo”)

  • system (Literal['traditional', 'modern']) – “traditional” (classical rulerships) or “modern” (includes outer planets)

Return type:

str

Returns:

The name of the ruling planet

stellium.engines.dispositors.render_both_dispositors(planetary, house, *, use_glyphs=True)[source]

Render both planetary and house dispositors as subgraphs in a single SVG.

Parameters:
  • planetary (DispositorResult) – Planetary DispositorResult

  • house (DispositorResult) – House-based DispositorResult

  • use_glyphs (bool) – Use planet glyphs for planetary graph

Return type:

Digraph

Returns:

graphviz.Digraph with both graphs as labeled clusters

Example

>>> engine = DispositorEngine(chart)
>>> planetary = engine.planetary()
>>> house = engine.house_based()
>>> graph = render_both_dispositors(planetary, house)
>>> graph.render("dispositors", format="svg")
stellium.engines.dispositors.render_dispositor_graph(result, *, use_glyphs=True, title=None)[source]

Render a single dispositor result as a graphviz Digraph.

Parameters:
  • result (DispositorResult) – DispositorResult from DispositorEngine

  • use_glyphs (bool) – Use planet glyphs (☉♀♂) instead of names

  • title (str | None) – Optional title for the graph

Return type:

Digraph

Returns:

graphviz.Digraph object (call .render() to save)

Raises:

ImportError – If graphviz is not installed

Void of Course Moon calculation engine.

The Moon is void of course (VOC) when it will not complete any major (Ptolemaic) aspects before leaving its current sign. This is traditionally considered an inauspicious time for beginning new ventures.

Traditional VOC uses only the seven visible planets (Sun through Saturn). Modern VOC includes the outer planets (Uranus, Neptune, Pluto).

Example

>>> result = chart.voc_moon()
>>> if result.is_void:
...     print(f"Moon is VOC until {result.void_until}")
... else:
...     print(f"Moon will {result.next_aspect} before leaving {result.moon_sign}")
class stellium.engines.voc.VOCMoonResult(is_void, moon_longitude, moon_sign, void_until, ends_by, next_aspect, next_aspect_degree, next_planet, next_sign, ingress_time, aspect_mode)[source]

Bases: object

Result of a void-of-course Moon calculation.

is_void

True if the Moon is currently void of course

moon_longitude

Current Moon longitude in degrees

moon_sign

Current zodiac sign of the Moon

void_until

Datetime when the void period ends

ends_by

How the void ends - “aspect” or “ingress”

next_aspect

Description of next aspect (e.g., “trine Jupiter”) or None

next_aspect_degree

The aspect angle (0, 60, 90, 120, 180) or None

next_planet

Name of planet Moon will next aspect, or None

next_sign

The sign Moon will enter next

ingress_time

Datetime when Moon enters the next sign

aspect_mode

Which planet set was used (“traditional” or “modern”)

aspect_mode: str
ends_by: Literal['aspect', 'ingress']
ingress_time: datetime
is_void: bool
moon_longitude: float
moon_sign: str
next_aspect: str | None
next_aspect_degree: int | None
next_planet: str | None
next_sign: str
void_until: datetime
stellium.engines.voc.calculate_voc_moon(chart, aspects='traditional')[source]

Calculate void-of-course Moon status for a chart.

The Moon is void of course when it will not perfect any major Ptolemaic aspect (conjunction, sextile, square, trine, opposition) to any planet before leaving its current sign.

Parameters:
  • chart (CalculatedChart) – The calculated chart to analyze

  • aspects (Literal['traditional', 'modern']) – “traditional” (Sun-Saturn) or “modern” (includes outers)

Return type:

VOCMoonResult

Returns:

VOCMoonResult with void status and timing details

Raises:

ValueError – If Moon is not found in chart

Longitude search engine for finding when celestial objects reach specific positions.

This module provides efficient search functions for finding exact times when planets and other celestial objects cross specific zodiac degrees. Uses a hybrid Newton-Raphson / bisection algorithm for fast, reliable convergence.

Key features: - Fast convergence using planetary speed from Swiss Ephemeris - Handles retrograde motion and stations gracefully - Proper 360°/0° wraparound handling - Forward and backward search directions - Find single crossing or all crossings in a date range

class stellium.engines.search.AngleCrossing(julian_day, datetime_utc, angle_name, target_longitude, actual_longitude, latitude, longitude)[source]

Bases: object

Result of an angle crossing search.

Represents the moment when a chart angle (ASC, MC, DSC, IC) reaches a specific zodiac longitude.

julian_day

Julian day when angle reaches the longitude

datetime_utc

UTC datetime of the crossing

angle_name

Which angle (“ASC”, “MC”, “DSC”, “IC”)

target_longitude

The target longitude that was crossed

actual_longitude

The actual angle longitude at crossing

latitude

Geographic latitude used

longitude

Geographic longitude used

actual_longitude: float
angle_name: str
datetime_utc: datetime
julian_day: float
latitude: float
longitude: float
target_longitude: float
class stellium.engines.search.AspectExact(julian_day, datetime_utc, object1_name, object2_name, aspect_angle, aspect_name, object1_longitude, object2_longitude, is_applying_before)[source]

Bases: object

Result of an aspect exactitude search.

Represents the exact moment when two celestial objects form a precise aspect (conjunction, sextile, square, trine, or opposition).

julian_day

Julian day when aspect is exact

datetime_utc

UTC datetime when aspect is exact

object1_name

First object name

object2_name

Second object name

aspect_angle

The aspect angle (0=conjunction, 60=sextile, etc.)

aspect_name

Human name (“conjunction”, “trine”, etc.)

object1_longitude

First object’s longitude at exact

object2_longitude

Second object’s longitude at exact

is_applying_before

True if aspect was applying before exact

aspect_angle: float
aspect_name: str
datetime_utc: datetime
is_applying_before: bool
julian_day: float
object1_longitude: float
object1_name: str
object2_longitude: float
object2_name: str
property separation: float

Angular separation between the two objects (should be ~aspect_angle).

class stellium.engines.search.Eclipse(julian_day, datetime_utc, eclipse_type, sun_longitude, moon_longitude, node_longitude, orb_to_node, nearest_node, sign, classification)[source]

Bases: object

Result of an eclipse search.

julian_day

Julian day of the eclipse (exact Sun-Moon conjunction/opposition)

datetime_utc

UTC datetime of the eclipse

eclipse_type

“solar” or “lunar”

sun_longitude

Sun’s longitude at eclipse

moon_longitude

Moon’s longitude at eclipse

node_longitude

True Node longitude at eclipse

orb_to_node

Distance from Sun/Moon to nearest node (degrees)

nearest_node

Which node is involved (“north” or “south”)

sign

Zodiac sign of the eclipse

classification

“total”, “annular”, “partial”, or “penumbral”

classification: str
datetime_utc: datetime
property degree_in_sign: float

Degree within the sign (0-30).

eclipse_type: Literal['solar', 'lunar']
property is_lunar: bool

True if this is a lunar eclipse.

property is_solar: bool

True if this is a solar eclipse.

julian_day: float
moon_longitude: float
nearest_node: Literal['north', 'south']
node_longitude: float
orb_to_node: float
sign: str
sun_longitude: float
class stellium.engines.search.LongitudeCrossing(julian_day, datetime_utc, longitude, speed, is_retrograde, object_name)[source]

Bases: object

Result of a longitude search.

julian_day

Julian day of the crossing

datetime_utc

UTC datetime of the crossing

longitude

Exact longitude at crossing (should be very close to target)

speed

Longitude speed at crossing (degrees/day, negative = retrograde)

is_retrograde

True if object was moving retrograde at crossing

object_name

Name of the celestial object

datetime_utc: datetime
property is_direct: bool

True if object was moving direct (not retrograde) at crossing.

is_retrograde: bool
julian_day: float
longitude: float
object_name: str
speed: float
class stellium.engines.search.SignIngress(julian_day, datetime_utc, object_name, sign, from_sign, longitude, speed, is_retrograde)[source]

Bases: object

Result of a sign ingress search.

julian_day

Julian day of the ingress

datetime_utc

UTC datetime of the ingress

object_name

Name of the celestial object

sign

Sign being entered

from_sign

Sign being left

longitude

Exact longitude at ingress (should be very close to 0° of sign)

speed

Longitude speed at ingress (degrees/day, negative = retrograde)

is_retrograde

True if object was moving retrograde at ingress

datetime_utc: datetime
from_sign: str
property is_direct: bool

True if object was moving direct (not retrograde) at ingress.

is_retrograde: bool
julian_day: float
longitude: float
object_name: str
sign: str
speed: float
class stellium.engines.search.Station(julian_day, datetime_utc, object_name, station_type, longitude, sign)[source]

Bases: object

Result of a planetary station search.

A station occurs when a planet’s apparent motion changes direction - either from direct to retrograde, or retrograde to direct.

julian_day

Julian day of the station

datetime_utc

UTC datetime of the station

object_name

Name of the celestial object

station_type

“retrograde” (turning Rx) or “direct” (turning D)

longitude

Longitude at the station (planet “hovers” here)

sign

Zodiac sign of the station

datetime_utc: datetime
property degree_in_sign: float

Degree within the sign (0-30).

property is_turning_direct: bool

True if planet is turning direct (was Rx, now direct).

property is_turning_retrograde: bool

True if planet is turning retrograde (was direct, now Rx).

julian_day: float
longitude: float
object_name: str
sign: str
station_type: Literal['retrograde', 'direct']
stellium.engines.search.find_all_angle_crossings(target_longitude, latitude, longitude, angle, start, end, max_results=100)[source]

Find all times a chart angle crosses a specific longitude in a date range.

Parameters:
  • target_longitude (float) – Target longitude in degrees (0-360)

  • latitude (float) – Geographic latitude

  • longitude (float) – Geographic longitude (negative = West)

  • angle (str) – Which angle to track (“ASC”, “MC”, “DSC”, “IC”)

  • start (datetime | float) – Start datetime (UTC) or Julian day

  • end (datetime | float) – End datetime (UTC) or Julian day

  • max_results (int) – Safety limit on number of results

Return type:

list[AngleCrossing]

Returns:

List of AngleCrossing objects, chronologically ordered

Example

>>> # Find all times ASC crosses 0° Aries in January 2025
>>> results = find_all_angle_crossings(
...     0.0, 40.7, -74.0, "ASC",
...     datetime(2025, 1, 1), datetime(2025, 2, 1)
... )
>>> print(f"Found {len(results)} crossings")  # ~31 (once per day)
stellium.engines.search.find_all_aspect_exacts(object1_name, object2_name, aspect_angle, start, end, max_results=100)[source]

Find all exact aspects between two objects in a date range.

Useful for: - Finding all Moon-Jupiter trines in a year - Tracking Mercury-Venus aspects for relationship timing - Building aspect timelines

Parameters:
  • object1_name (str) – First object (e.g., “Moon”, “Sun”, “Mars”)

  • object2_name (str) – Second object (e.g., “Jupiter”, “Venus”)

  • aspect_angle (float) – Target angle in degrees (0, 60, 90, 120, 180)

  • start (datetime | float) – Start datetime (UTC) or Julian day

  • end (datetime | float) – End datetime (UTC) or Julian day

  • max_results (int) – Safety limit on number of results (default 100)

Return type:

list[AspectExact]

Returns:

List of AspectExact objects, chronologically ordered

Example

>>> # Find all Moon trine Jupiter in 2024
>>> results = find_all_aspect_exacts(
...     "Moon", "Jupiter", 120.0,
...     datetime(2024, 1, 1),
...     datetime(2024, 12, 31)
... )
>>> print(f"Found {len(results)} exact trines")
>>> for r in results[:5]:
...     print(r)
stellium.engines.search.find_all_eclipses(start, end, eclipse_types='both', max_results=20)[source]

Find all eclipses in a date range.

Parameters:
  • start (datetime | float) – Start datetime (UTC) or Julian day

  • end (datetime | float) – End datetime (UTC) or Julian day

  • eclipse_types (Literal['both', 'solar', 'lunar']) – Which types to find (“both”, “solar”, “lunar”)

  • max_results (int) – Safety limit on number of results (default 20)

Return type:

list[Eclipse]

Returns:

List of Eclipse objects, chronologically ordered

Example

>>> # Find all eclipses in 2024
>>> eclipses = find_all_eclipses(
...     datetime(2024, 1, 1),
...     datetime(2024, 12, 31)
... )
>>> for e in eclipses:
...     print(e)
stellium.engines.search.find_all_ingresses(object_name, sign, start, end, max_results=50)[source]

Find all times a celestial object enters a specific sign in a date range.

Parameters:
  • object_name (str) – Name of celestial object (e.g., “Sun”, “Mars”, “Moon”)

  • sign (str) – Target zodiac sign (e.g., “Aries”, “Taurus”)

  • start (datetime | float) – Start datetime (UTC) or Julian day

  • end (datetime | float) – End datetime (UTC) or Julian day

  • max_results (int) – Safety limit on number of results (default 50)

Return type:

list[SignIngress]

Returns:

List of SignIngress objects, chronologically ordered

Example

>>> # Find all Mars ingresses to Aries in the next 10 years
>>> results = find_all_ingresses(
...     "Mars", "Aries",
...     datetime(2024, 1, 1),
...     datetime(2034, 1, 1)
... )
>>> print(f"Mars enters Aries {len(results)} times")
stellium.engines.search.find_all_longitude_crossings(object_name, target_longitude, start, end, max_results=100)[source]

Find all times a celestial object crosses a specific longitude in a date range.

Useful for: - Finding all Moon transits over a degree (roughly monthly) - Finding multiple Mercury crossings during retrograde (up to 3) - Building transit timelines

Parameters:
  • object_name (str) – Name of celestial object (e.g., “Sun”, “Mars”, “Moon”)

  • target_longitude (float) – Target longitude in degrees (0-360)

  • start (datetime | float) – Start datetime (UTC) or Julian day

  • end (datetime | float) – End datetime (UTC) or Julian day

  • max_results (int) – Safety limit on number of results (default 100)

Return type:

list[LongitudeCrossing]

Returns:

List of LongitudeCrossing objects, chronologically ordered

Example

>>> # Find all times Moon crosses 15° Taurus in 2024
>>> results = find_all_longitude_crossings(
...     "Moon", 45.0,  # 15° Taurus
...     datetime(2024, 1, 1),
...     datetime(2024, 12, 31)
... )
>>> print(f"Moon crosses 15° Taurus {len(results)} times in 2024")
stellium.engines.search.find_all_sign_changes(object_name, start, end, max_results=100)[source]

Find all sign changes for a celestial object in a date range.

Parameters:
  • object_name (str) – Name of celestial object (e.g., “Sun”, “Mars”, “Moon”)

  • start (datetime | float) – Start datetime (UTC) or Julian day

  • end (datetime | float) – End datetime (UTC) or Julian day

  • max_results (int) – Safety limit on number of results (default 100)

Return type:

list[SignIngress]

Returns:

List of SignIngress objects, chronologically ordered

Example

>>> # Find all Mercury sign changes in 2024
>>> results = find_all_sign_changes(
...     "Mercury",
...     datetime(2024, 1, 1),
...     datetime(2024, 12, 31)
... )
>>> for r in results:
...     print(f"{r.datetime_utc.date()}: Mercury enters {r.sign}")
stellium.engines.search.find_all_stations(object_name, start, end, max_results=50)[source]

Find all planetary stations in a date range.

Parameters:
  • object_name (str) – Name of celestial object (e.g., “Mercury”, “Mars”)

  • start (datetime | float) – Start datetime (UTC) or Julian day

  • end (datetime | float) – End datetime (UTC) or Julian day

  • max_results (int) – Safety limit on number of results (default 50)

Return type:

list[Station]

Returns:

List of Station objects, chronologically ordered

Example

>>> # Find all Mercury stations in 2024
>>> results = find_all_stations(
...     "Mercury",
...     datetime(2024, 1, 1),
...     datetime(2024, 12, 31)
... )
>>> for r in results:
...     print(r)
stellium.engines.search.find_angle_crossing(target_longitude, latitude, longitude, angle, start, direction='forward', max_days=2.0, tolerance=0.001, max_iterations=50)[source]

Find when a chart angle crosses a specific longitude.

Since angles rotate with Earth’s rotation (~1° every 4 minutes), any given longitude will be crossed roughly once per sidereal day (~23h 56m) by each angle.

Parameters:
  • target_longitude (float) – Target longitude in degrees (0-360)

  • latitude (float) – Geographic latitude

  • longitude (float) – Geographic longitude (negative = West)

  • angle (str) – Which angle to track (“ASC”, “MC”, “DSC”, “IC”)

  • start (datetime | float) – Starting datetime (UTC) or Julian day

  • direction (Literal['forward', 'backward']) – “forward” to search future, “backward” to search past

  • max_days (float) – Maximum days to search (default 2, since crossings are daily)

  • tolerance (float) – Convergence tolerance in degrees

  • max_iterations (int) – Maximum refinement iterations

Return type:

AngleCrossing | None

Returns:

AngleCrossing with exact timing, or None if not found

Example

>>> # Find when ASC reaches 0° Leo in NYC
>>> result = find_angle_crossing(120.0, 40.7, -74.0, "ASC", datetime.now())
>>> print(f"ASC at 0° Leo: {result.datetime_utc}")
stellium.engines.search.find_aspect_exact(object1_name, object2_name, aspect_angle, start, direction='forward', max_days=366.0, tolerance=0.0001, max_iterations=50)[source]

Find when two objects form an exact aspect.

Uses a hybrid Newton-Raphson / bisection algorithm to find the precise moment when two celestial objects reach a specific angular separation.

Parameters:
  • object1_name (str) – First object (e.g., “Moon”, “Sun”, “Mars”)

  • object2_name (str) – Second object (e.g., “Jupiter”, “Venus”)

  • aspect_angle (float) – Target angle in degrees (0=conjunction, 60=sextile, 90=square, 120=trine, 180=opposition)

  • start (datetime | float) – Starting datetime (UTC) or Julian day

  • direction (Literal['forward', 'backward']) – “forward” to search future, “backward” to search past

  • max_days (float) – Maximum days to search (default 366 = just over a year)

  • tolerance (float) – Convergence tolerance in degrees (default 0.0001 ≈ 0.36 arcsec)

  • max_iterations (int) – Maximum refinement iterations

Return type:

AspectExact | None

Returns:

AspectExact with exact timing, or None if not found

Example

>>> # Find next exact Moon trine Jupiter
>>> result = find_aspect_exact("Moon", "Jupiter", 120.0, datetime(2024, 1, 1))
>>> print(f"Exact trine at {result.datetime_utc}")
>>> # Find next Mercury-Venus conjunction
>>> result = find_aspect_exact("Mercury", "Venus", 0.0, datetime(2024, 1, 1))
>>> print(result)
stellium.engines.search.find_eclipse(start, direction='forward', eclipse_types='both', max_days=200.0)[source]

Find the next eclipse from a starting date.

Parameters:
  • start (datetime | float) – Starting datetime (UTC) or Julian day

  • direction (Literal['forward', 'backward']) – “forward” to search future, “backward” to search past

  • eclipse_types (Literal['both', 'solar', 'lunar']) – Which types to find (“both”, “solar”, “lunar”)

  • max_days (float) – Maximum days to search (default 200 = ~6 months)

Return type:

Eclipse | None

Returns:

Eclipse with details, or None if not found

Example

>>> # Find next eclipse from now
>>> eclipse = find_eclipse(datetime(2024, 1, 1))
>>> print(eclipse)
Partial lunar eclipse at 5°07' Leo (NN) on 2024-03-25 07:00
stellium.engines.search.find_ingress(object_name, sign, start, direction='forward', max_days=730.0)[source]

Find when a celestial object next enters a specific zodiac sign.

Parameters:
  • object_name (str) – Name of celestial object (e.g., “Sun”, “Mars”, “Moon”)

  • sign (str) – Target zodiac sign (e.g., “Aries”, “Taurus”)

  • start (datetime | float) – Starting datetime (UTC) or Julian day

  • direction (Literal['forward', 'backward']) – “forward” to search future, “backward” to search past

  • max_days (float) – Maximum days to search (default 730 = ~2 years)

Return type:

SignIngress | None

Returns:

SignIngress with exact time of ingress, or None if not found

Example

>>> # When does Mars next enter Aries?
>>> result = find_ingress("Mars", "Aries", datetime(2024, 1, 1))
>>> print(result)  # Mars enters Aries on 2024-04-30 12:34
stellium.engines.search.find_longitude_crossing(object_name, target_longitude, start, direction='forward', max_days=366.0, tolerance=0.0001, max_iterations=50)[source]

Find when a celestial object crosses a specific longitude.

Uses a hybrid Newton-Raphson / bisection algorithm: 1. First brackets the crossing with a coarse sweep 2. Then refines with Newton-Raphson (fast when speed is good) 3. Falls back to bisection near stations (speed ≈ 0)

Parameters:
  • object_name (str) – Name of celestial object (e.g., “Sun”, “Mars”, “Moon”)

  • target_longitude (float) – Target longitude in degrees (0-360)

  • start (datetime | float) – Starting datetime (UTC) or Julian day

  • direction (Literal['forward', 'backward']) – “forward” to search future, “backward” to search past

  • max_days (float) – Maximum days to search (default 366 = just over a year)

  • tolerance (float) – Convergence tolerance in degrees (default 0.0001 ≈ 0.36 arcsec)

  • max_iterations (int) – Maximum refinement iterations

Return type:

LongitudeCrossing | None

Returns:

LongitudeCrossing with exact time, or None if not found

Example

>>> # When does the Sun reach 0° Aries (vernal equinox) after Jan 1, 2024?
>>> result = find_longitude_crossing("Sun", 0.0, datetime(2024, 1, 1))
>>> print(result.datetime_utc)  # ~March 20, 2024
stellium.engines.search.find_next_sign_change(object_name, start, direction='forward', max_days=60.0)[source]

Find when a celestial object next changes signs (enters any sign).

This is useful for questions like “when does this transit end?” where you don’t care which sign is entered, just when the object leaves its current sign.

Parameters:
  • object_name (str) – Name of celestial object (e.g., “Sun”, “Mars”, “Moon”)

  • start (datetime | float) – Starting datetime (UTC) or Julian day

  • direction (Literal['forward', 'backward']) – “forward” to search future, “backward” to search past

  • max_days (float) – Maximum days to search (default 60)

Return type:

SignIngress | None

Returns:

SignIngress with exact time of sign change, or None if not found

Example

>>> # When does Mars leave its current sign?
>>> result = find_next_sign_change("Mars", datetime(2024, 1, 15))
>>> print(f"Mars enters {result.sign} on {result.datetime_utc}")
stellium.engines.search.find_station(object_name, start, direction='forward', max_days=400.0)[source]

Find the next planetary station (retrograde or direct).

A station occurs when a planet appears to stop moving and reverse direction. This is an important timing point in astrology.

Note: Sun and Moon do not have stations (they don’t go retrograde).

Parameters:
  • object_name (str) – Name of celestial object (e.g., “Mercury”, “Mars”)

  • start (datetime | float) – Starting datetime (UTC) or Julian day

  • direction (Literal['forward', 'backward']) – “forward” to search future, “backward” to search past

  • max_days (float) – Maximum days to search (default 400, > 1 year for outer planets)

Return type:

Station | None

Returns:

Station with exact time and details, or None if not found

Example

>>> # When does Mercury next station?
>>> result = find_station("Mercury", datetime(2024, 1, 1))
>>> print(result)  # Mercury stations retrograde at 4°51' Capricorn on 2024-04-01

Primary directions calculation engine.

This module provides primary directions calculations for Stellium, supporting both zodiacal (2D/Regiomontanus-style) and mundane (3D/Placidus) methods.

Primary directions track when a “promissor” (moving point) reaches a “significator” (target point) via the Earth’s daily rotation. The resulting arc is converted to years using a time key.

Example

>>> from stellium.engines.directions import DirectionsEngine
>>> engine = DirectionsEngine(chart)
>>> result = engine.direct("Sun", "ASC")
>>> print(f"Sun to ASC: age {result.age:.1f}")
class stellium.engines.directions.DirectionArc(promissor, significator, arc_degrees, method, direction='direct')[source]

Bases: object

The result of a primary direction calculation.

promissor

Name of the moving point

significator

Name of the target point

arc_degrees

The calculated arc in degrees

method

Direction method used (“zodiacal” or “mundane”)

direction

“direct” or “converse”

arc_degrees: float
direction: str = 'direct'
method: str
promissor: str
significator: str
class stellium.engines.directions.DirectionMethod(*args, **kwargs)[source]

Bases: Protocol

Protocol for direction calculation methods.

Different implementations provide different mathematical approaches: - ZodiacalDirections: Projects onto ecliptic plane (2D) - MundaneDirections: Uses house-space proportions (3D/Placidus)

calculate_arc(promissor, significator, latitude, ramc)[source]

Calculate the arc between promissor and significator.

Parameters:
  • promissor (EquatorialPoint) – The moving point

  • significator (EquatorialPoint) – The target point

  • latitude (float) – Geographic latitude of the observer

  • ramc (float) – Right Ascension of the Medium Coeli (MC)

Return type:

float

Returns:

Arc in degrees

property method_name: str

Name of this direction method.

class stellium.engines.directions.DirectionResult(arc, date=None, age=None)[source]

Bases: object

Complete result of directing one point to another.

arc

The direction arc details

date

Calendar date when the direction perfects (if calculable)

age

Age in years when the direction perfects (if calculable)

age: float | None = None
arc: DirectionArc
date: datetime | None = None
class stellium.engines.directions.DirectionsEngine(chart, method='zodiacal', time_key='naibod')[source]

Bases: object

Primary directions calculation engine.

Calculates primary directions between chart points using either zodiacal (2D) or mundane (3D/Placidus) methods.

Parameters:
  • chart (CalculatedChart) – The natal chart to calculate directions for

  • method (str) – Direction method - “zodiacal” (default) or “mundane”

  • time_key (str) – Time key - “naibod” (default) or “ptolemy”

Example

>>> engine = DirectionsEngine(chart)
>>> result = engine.direct("Sun", "ASC")
>>> print(f"Sun to ASC: age {result.age:.1f}")
>>> # Compare methods
>>> z = DirectionsEngine(chart, method="zodiacal").direct("Sun", "ASC")
>>> m = DirectionsEngine(chart, method="mundane").direct("Sun", "ASC")
METHODS: dict[str, type[DirectionMethod]] = {'mundane': <class 'stellium.engines.directions.MundaneDirections'>, 'zodiacal': <class 'stellium.engines.directions.ZodiacalDirections'>}
TIME_KEYS: dict[str, type[TimeKey]] = {'naibod': <class 'stellium.engines.directions.NaibodKey'>, 'ptolemy': <class 'stellium.engines.directions.PtolemyKey'>}
direct(promissor, significator, direction='direct')[source]

Calculate a primary direction.

Parameters:
  • promissor (str | CelestialPosition) – Name or position of moving point (e.g., “Sun”)

  • significator (str | CelestialPosition) – Name or position of target (e.g., “ASC”)

  • direction (str) – “direct” or “converse”

Return type:

DirectionResult

Returns:

DirectionResult with arc, date, and age

direct_all_to(significator, planets=None)[source]

Direct multiple planets to a single significator.

Parameters:
Return type:

list[DirectionResult]

Returns:

List of DirectionResults sorted by age

direct_to_angles(promissor)[source]

Direct a planet to all four angles.

Parameters:

promissor (str | CelestialPosition) – Name or position of the planet

Return type:

dict[str, DirectionResult]

Returns:

Dictionary mapping angle names to DirectionResults

class stellium.engines.directions.DistributionsCalculator(chart, time_key='naibod', bound_system='egypt')[source]

Bases: object

Calculate term/bound distributions.

Distributions track which planetary term (bound) the directed Ascendant occupies at different ages. This creates a timeline of “life chapters” ruled by different planets.

Parameters:
  • chart (CalculatedChart) – The natal chart

  • time_key (str) – Time key - “naibod” (default) or “ptolemy”

  • bound_system (str) – Which bound system to use (default: “egypt”)

Example

>>> calc = DistributionsCalculator(chart)
>>> periods = calc.calculate(years=80)
>>> for p in periods:
...     print(f"Age {p.start_age:.1f}: {p.ruler} ({p.sign})")
TIME_KEYS: dict[str, type[TimeKey]] = {'naibod': <class 'stellium.engines.directions.NaibodKey'>, 'ptolemy': <class 'stellium.engines.directions.PtolemyKey'>}
calculate(years=100)[source]

Calculate term distributions for a lifetime.

Parameters:

years (int) – Maximum years to calculate

Return type:

list[TimeLordPeriod]

Returns:

List of TimeLordPeriod objects sorted by age

class stellium.engines.directions.EquatorialPoint(name, right_ascension, declination)[source]

Bases: object

A point in equatorial coordinates (RA/Dec).

This is the universal coordinate system for primary directions. All chart positions are converted to this format before calculation.

name

Name of the point (e.g., “Sun”, “ASC”)

right_ascension

Right ascension in degrees (0-360)

declination

Declination in degrees (-90 to +90)

declination: float
name: str
right_ascension: float
class stellium.engines.directions.MundaneDirections[source]

Bases: object

Mundane (Placidus) primary directions.

Uses house-space proportions. The promissor must travel to reach the same “mundane ratio” as the significator. This is the “3D” method.

The mundane ratio is how far through its current semi-arc a point has traveled (0 = at meridian, 1 = at horizon).

calculate_arc(promissor, significator, latitude, ramc)[source]

Calculate mundane arc using Placidus proportions.

The promissor must travel until it reaches the same proportional position within its semi-arc as the significator.

Return type:

float

property method_name: str
class stellium.engines.directions.MundanePosition(point, meridian_distance, semi_arc_diurnal, semi_arc_nocturnal, is_above_horizon, is_eastern)[source]

Bases: object

A point with full mundane (house-space) context.

Knows its position relative to the local horizon/meridian system. Used by the MundaneDirections method for Placidus-style calculations.

point

The underlying equatorial point

meridian_distance

Degrees from MC (-180 to +180, positive = east)

semi_arc_diurnal

Degrees of the day arc radius

semi_arc_nocturnal

Degrees of the night arc radius

is_above_horizon

True if point is above the horizon

is_eastern

True if point is on the rising (eastern) side

property current_semi_arc: float

Which semi-arc is currently applicable.

is_above_horizon: bool
is_eastern: bool
meridian_distance: float
property mundane_ratio: float

Position as fraction of semi-arc (0=meridian, 1=horizon).

point: EquatorialPoint
semi_arc_diurnal: float
semi_arc_nocturnal: float
class stellium.engines.directions.NaibodKey[source]

Bases: object

The Precision Key: Based on mean solar motion.

Uses the Sun’s mean daily motion of 59’08” (0.9856 deg per day). This makes 1 deg approx 1.0146 years.

DAYS_PER_DEGREE = 370.5765625
arc_to_date(arc, birth_date)[source]

Convert arc to date using Naibod rate.

Return type:

datetime

arc_to_years(arc)[source]

Convert arc to years using Naibod rate.

Return type:

float

property key_name: str
class stellium.engines.directions.PtolemyKey[source]

Bases: object

The Classic Key: 1 degree = 1 year.

The simplest and oldest time key, attributed to Ptolemy.

arc_to_date(arc, birth_date)[source]

Convert arc to date using 1 deg = 1 year.

Return type:

datetime

arc_to_years(arc)[source]

1 degree = 1 year.

Return type:

float

property key_name: str
class stellium.engines.directions.TermBoundary(absolute_degree, ruler, sign)[source]

Bases: object

Represents the starting boundary of a term.

absolute_degree

Position in the zodiac (0-360)

ruler

Planet ruling this term

sign

Zodiac sign name

absolute_degree: float
ruler: str
sign: str
class stellium.engines.directions.TimeKey(*args, **kwargs)[source]

Bases: Protocol

Protocol for converting arcs to time.

Different keys represent different symbolic rates of motion: - Ptolemy: 1 degree = 1 year - Naibod: Based on mean solar motion (~1.0146 years/degree)

arc_to_date(arc, birth_date)[source]

Convert arc to calendar date from birth.

Return type:

datetime

arc_to_years(arc)[source]

Convert arc to years.

Return type:

float

property key_name: str

Name of this time key.

class stellium.engines.directions.TimeLordPeriod(ruler, start_date, start_age, sign='', end_date=None, end_age=None)[source]

Bases: object

A period ruled by a term/bound lord.

Used in distributions to track which planetary term the directed Ascendant occupies at different ages.

ruler

Name of the ruling planet

start_date

Date this period begins

start_age

Age when this period begins

sign

Zodiac sign containing this term

end_date

Date this period ends (optional)

end_age

Age when this period ends (optional)

end_age: float | None = None
end_date: datetime | None = None
ruler: str
sign: str = ''
start_age: float
start_date: datetime
class stellium.engines.directions.ZodiacalDirections[source]

Bases: object

Zodiacal (Regiomontanus-style) primary directions.

Projects points onto the ecliptic plane. The significator’s pole determines the projection plane. This is the “2D” method.

In zodiacal directions, we compare oblique ascensions calculated using the same pole (typically the geographic latitude for ASC).

calculate_arc(promissor, significator, latitude, ramc)[source]

Calculate zodiacal arc via oblique ascension difference.

The arc is the difference in oblique ascension between the promissor and significator, using the same pole for both.

Return type:

float

property method_name: str
stellium.engines.directions.ascensional_difference(declination, pole)[source]

Calculate ascensional difference (the ‘wobble’ from pole tilt).

The ascensional difference is how much a point’s rising/setting time deviates from 6 hours due to both its own declination and the observer’s latitude (pole).

Formula: sin(AD) = tan(dec) * tan(pole)

Parameters:
  • declination (float) – Declination of the point in degrees

  • pole (float) – Geographic latitude (or pole of a house) in degrees

Return type:

float

Returns:

Ascensional difference in degrees

stellium.engines.directions.ecliptic_to_equatorial(longitude, latitude, obliquity)[source]

Convert ecliptic coordinates to equatorial.

Parameters:
  • longitude (float) – Ecliptic longitude in degrees

  • latitude (float) – Ecliptic latitude in degrees (usually 0 for zodiacal points)

  • obliquity (float) – Obliquity of the ecliptic in degrees

Return type:

tuple[float, float]

Returns:

Tuple of (right_ascension, declination) in degrees

stellium.engines.directions.get_obliquity(julian_day)[source]

Get the true obliquity of the ecliptic for a given time.

Parameters:

julian_day (float) – Julian day number

Return type:

float

Returns:

True obliquity in degrees

stellium.engines.directions.meridian_distance(right_ascension, ramc)[source]

Calculate distance from the upper meridian (MC).

Positive values indicate the point is east (rising toward MC). Negative values indicate the point is west (setting from MC).

Parameters:
  • right_ascension (float) – RA of the point in degrees

  • ramc (float) – Right Ascension of MC in degrees

Return type:

float

Returns:

Meridian distance in degrees (-180 to +180)

stellium.engines.directions.oblique_ascension(right_ascension, declination, pole)[source]

Calculate oblique ascension.

Oblique ascension is the RA adjusted for the pole (geographic latitude or house pole). It’s used in zodiacal directions.

Formula: OA = RA - AD

Parameters:
  • right_ascension (float) – RA in degrees

  • declination (float) – Declination in degrees

  • pole (float) – Pole (latitude) in degrees

Return type:

float

Returns:

Oblique ascension in degrees (0-360)

stellium.engines.directions.semi_arcs(declination, latitude)[source]

Calculate diurnal and nocturnal semi-arcs.

A semi-arc is half the arc a point travels above (diurnal) or below (nocturnal) the horizon. At the equator with 0 declination, both are 90.

Parameters:
  • declination (float) – Declination of the point in degrees

  • latitude (float) – Geographic latitude in degrees

Return type:

tuple[float, float]

Returns:

Tuple of (diurnal_semi_arc, nocturnal_semi_arc) in degrees

Implementation of standard Zodiacal Releasing system.

class stellium.engines.releasing.ZodiacalReleasingAnalyzer(lots, engine=<class 'stellium.engines.releasing.ZodiacalReleasingEngine'>, max_level=4, lifespan=100)[source]

Bases: object

Calculate Zodiacal Releasing timeline and periods.

analyze(chart)[source]

Add zodiacial releasing timeline to metadata.

Parameters:

chart (CalculatedChart) – Chart to analyze

Returns:

ZRTimeline}

Return type:

Dict of {lot name

property analyzer_name: str
property metadata_name: str
class stellium.engines.releasing.ZodiacalReleasingEngine(chart, lot='Part of Fortune', max_level=4, lifespan=100, method='valens')[source]

Bases: object

Calculate Zodiacal Releasing periods.

build_timeline()[source]

Build complete timeline with all periods.

Return type:

ZRTimeline

calculate_all_periods()[source]

Build all periods for all levels

Return type:

dict[int, list[ZRPeriod]]


Components (stellium.components)

Optional calculation components that can be added to charts.

Arabic Parts calculator component.

Arabic Parts (also called Lots) are calculated points based on the distances between three chart objects. They represent themes or areas of life.

Formula: Lot = Asc + Point2 - Point1

Many lots are “sect-aware” - they flip the formula for day vs night charts: - Day Chart: Asc + Point2 - Point1 - Night Chart: Asc + Point1 - Point2

class stellium.components.arabic_parts.ArabicPartsCalculator(parts_to_calculate=None, custom_parts=None)[source]

Bases: object

Calculate Arabic Parts (Lots) for a chart.

Arabic Parts are senstitive points calculated from the distances between three chart objects. They represent specific life themes.

calculate(datetime, location, positions, house_systems_map, house_placements_map)[source]

Calculate Arabic Parts.

Parameters:
Return type:

list[CelestialPosition]

Returns:

List of CelestialPosition objects for each part

property component_name: str
class stellium.components.arabic_parts.PartOfFortuneCalculator[source]

Bases: object

Simplified calculator for just Part of Fortune.

This is useful for when you only need Fortune and don’t want to calculate all Arabic Parts.

calculate(datetime, location, positions, house_systems_map, house_placements_map)[source]

Calculate only Part of Fortune.

Return type:

list[CelestialPosition]

property component_name: str

Midpoint calculator component.

Midpoints are the halfway point between two celestial objects. They represent the synthesis or blend of two planetary energies.

In midpoint astrology: - Direct midpoint: Shortest arc between two points - Indirect midpoint: Opposite point (180° from direct)

Both are significant, but direct midpoint is more commonly used.

class stellium.components.midpoints.MidpointCalculator(pairs=None, calculate_all=False, indirect=False)[source]

Bases: object

Calculate midpoints between celestial objects.

Midpoints reveal how two planetary energies blend or interact. They’re used extensively in Uranian astrology and some modern approaches.

DEFAULT_PAIRS = [('Sun', 'Moon'), ('Sun', 'ASC'), ('Sun', 'MC'), ('Moon', 'ASC'), ('Moon', 'MC'), ('ASC', 'MC'), ('Sun', 'Mercury'), ('Sun', 'Venus'), ('Sun', 'Mars'), ('Moon', 'Mercury'), ('Moon', 'Venus'), ('Moon', 'Mars'), ('Mercury', 'Venus'), ('Mercury', 'Mars'), ('Venus', 'Mars'), ('Sun', 'Jupiter'), ('Sun', 'Saturn'), ('Moon', 'Jupiter'), ('Moon', 'Saturn'), ('Mars', 'Jupiter'), ('Mars', 'Saturn'), ('Venus', 'Jupiter'), ('Jupiter', 'Saturn')]
calculate(datetime, location, positions, house_systems_map, house_placements_map)[source]

Calculate midpoints.

Parameters:
Return type:

list[CelestialPosition]

Returns:

List of CelestialPosition objects for midpoints

property component_name: str

Dignity component for ChartBuilder.

Calculates essential and accidental dignities for all planets in a chart. Integrates seamlessly with the ChartBuilder component system.

class stellium.components.dignity.AccidentalDignityComponent(house_system='Placidus')[source]

Bases: object

Chart component that calculates accidental dignities (house placement, etc).

This should be added AFTER house systems are calculated.

Handles multiple house systems by calculating house-dependent conditions for each system separately, while universal conditions (retrograde, cazimi, etc.) are calculated once.

Usage:

chart = ChartBuilder.from_native(native) .add_component(AccidentalDignityComponent()) .calculate()

# Access for specific system sun_acc = chart.get_planet_accidental(“Sun”, system=”Placidus”)

# Or get all systems sun_all = chart.get_planet_accidental(“Sun”)

ANGULAR_HOUSES = [1, 10, 7, 4]
CADENT_HOUSES = [3, 12, 9, 6]
JOY_PLACEMENTS = {'Jupiter': 11, 'Mars': 6, 'Mercury': 1, 'Moon': 3, 'Saturn': 12, 'Sun': 9, 'Venus': 5}
SUCCEDENT_HOUSES = [2, 11, 8, 5]
calculate(datetime, location, positions, house_systems_map, house_placements_map)[source]

Calculate accidental dignities based on house position.

Parameters:
Return type:

list[CelestialPosition]

Returns:

Empty list (accidental dignities stored in metadata)

property component_name: str

Name of this component.

get_metadata()[source]

Get calculated accidental dignity data.

Return type:

dict[str, Any]

metadata_name = 'accidental_dignities'
class stellium.components.dignity.DignityComponent(traditional=True, modern=True, receptions=True, decans='triplicity')[source]

Bases: object

Chart component that calculates dignities for all planets.

This follows the ChartComponent protocol and can be added to ChartBuilder like any other component.

Usage:

chart = ChartBuilder.from_native(native) .add_component(DignityComponent()) .calculate()

# Access dignities dignities = chart.metadata.get(‘dignities’, {})

calculate(datetime, location, positions, house_systems_map, house_placements_map)[source]

Calculate dignities for all positions.

Note: This component doesn’t return new positions - instead it returns an empty list and stores dignity data in metadata that will be attached to the CalculatedChart.

Parameters:
Return type:

list[CelestialPosition]

Returns:

Empty list (dignities stored in metadata)

property component_name: str

Name of this component.

get_metadata()[source]

Get calculated dignity data.

This should be called after calculate() to retrieve results.

Return type:

dict[str, Any]

metadata_name = 'dignities'
stellium.components.dignity.determine_sect(positions)[source]

Determines if a day or night chart. Returns ‘day’ or ‘night.’

Day chart = Sun above the horizon, between ASC and DSC (going through MC).

Return type:

str

Fixed Stars component for chart calculations.

This component calculates fixed star positions and integrates them into the chart. Stars are calculated for the chart’s Julian Day, with Swiss Ephemeris handling precession automatically.

Usage:
>>> from stellium import ChartBuilder
>>> from stellium.components import FixedStarsComponent
>>>
>>> # All stars
>>> chart = (ChartBuilder.from_native(native)
...     .add_component(FixedStarsComponent())
...     .calculate())
>>>
>>> # Royal stars only
>>> chart = (ChartBuilder.from_native(native)
...     .add_component(FixedStarsComponent(tier=1))
...     .calculate())
>>>
>>> # Specific stars
>>> chart = (ChartBuilder.from_native(native)
...     .add_component(FixedStarsComponent(stars=["Regulus", "Algol"]))
...     .calculate())
class stellium.components.fixed_stars.FixedStarsComponent(stars=None, tier=None, royal_only=False, include_higher_tiers=False)[source]

Bases: object

Component that calculates fixed star positions for a chart.

This component uses SwissEphemerisFixedStarsEngine to calculate precise positions for fixed stars and returns them as FixedStarPosition objects.

Stars can be filtered by: - Specific star names - Tier (1=Royal, 2=Major, 3=Extended) - Royal stars only

The calculated positions include all metadata from FIXED_STARS_REGISTRY, including traditional planetary nature, keywords, and constellation.

stars

List of specific star names to calculate (None = use tier/royal filters)

tier

Tier level to calculate (None = all tiers)

royal_only

If True, calculate only the four Royal Stars

Example

>>> # Add all stars in registry
>>> comp = FixedStarsComponent()
>>>
>>> # Add only Royal stars (Aldebaran, Regulus, Antares, Fomalhaut)
>>> comp = FixedStarsComponent(royal_only=True)
>>>
>>> # Add only tier 1 and 2 stars
>>> comp = FixedStarsComponent(tier=2, include_higher_tiers=True)
>>>
>>> # Add specific stars
>>> comp = FixedStarsComponent(stars=["Sirius", "Algol", "Spica"])
calculate(datetime, location, positions, house_systems_map, house_placements_map)[source]

Calculate fixed star positions for the chart.

Parameters:
  • datetime (ChartDateTime) – Chart datetime (used to get Julian Day)

  • location (ChartLocation) – Chart location (unused for fixed stars, required by protocol)

  • positions (list[CelestialPosition]) – Already-calculated positions (unused, required by protocol)

  • house_systems_map (dict[str, HouseCusps]) – House systems map (unused, required by protocol)

  • house_placements_map (dict[str, dict[str, int]]) – House placements (unused, required by protocol)

Return type:

list[CelestialPosition]

Returns:

List of FixedStarPosition objects for the requested stars

property component_name: str

Name of this component.

Antiscia and Contra-Antiscia calculator component.

Antiscia are reflection points across the solstice axis (0° Cancer / 0° Capricorn). Two planets are “in antiscia” when one planet’s antiscion point is conjunct another planet - this is considered a “hidden conjunction.”

Contra-antiscia are reflections across the equinox axis (0° Aries / 0° Libra).

Traditional astrologers use antiscia to find hidden connections between planets that don’t make conventional aspects.

Formulas: - Antiscion = (360° - longitude + 180°) % 360° = (180° - longitude) % 360°

Equivalently: reflect across Cancer-Capricorn axis

  • Contra-antiscion = (360° - longitude) % 360° Equivalently: reflect across Aries-Libra axis

class stellium.components.antiscia.AntisciaCalculator(planets=None, orb=1.5, include_contra=True)[source]

Bases: object

Calculate antiscia and contra-antiscia points and find conjunctions.

Antiscia are reflection points that reveal “hidden” connections between planets. When planet A’s antiscion is conjunct planet B, they are said to be “in antiscia” - a connection as powerful as a conjunction but operating on a hidden or fated level.

Usage:
chart = (ChartBuilder.from_native(native)

.add_component(AntisciaCalculator()) .calculate())

# Access antiscia points (added to chart.positions) antiscia_points = [p for p in chart.positions

if p.object_type == ObjectType.ANTISCION]

# Access conjunction data (in metadata) antiscia_data = chart.metadata.get(“antiscia”, {}) conjunctions = antiscia_data.get(“conjunctions”, [])

calculate(datetime, location, positions, house_systems_map, house_placements_map)[source]

Calculate antiscia and contra-antiscia points.

Returns the antiscia points as CelestialPosition objects. Also populates internal conjunction data accessible via get_metadata().

Return type:

list[CelestialPosition]

property component_name: str
get_metadata()[source]

Get the calculated antiscia conjunction data.

Returns:

  • conjunctions: List of AntisciaConjunction objects

  • contra_conjunctions: List of contra-antiscia conjunctions

  • orb: The orb used for calculations

Return type:

Dictionary containing

metadata_name = 'antiscia'
class stellium.components.antiscia.AntisciaConjunction(planet1, planet2, orb, is_applying, antiscion_longitude, planet2_longitude)[source]

Bases: object

Represents a conjunction between a planet and another planet’s antiscion.

When planet1’s antiscion point is conjunct planet2, we say “planet1 and planet2 are in antiscia.”

antiscion_longitude: float
property description: str

Human-readable description of this antiscia relationship.

is_applying: bool
orb: float
planet1: str
planet2: str
planet2_longitude: float

Presentation (stellium.presentation)

Report building and rendering.

Report builder for creating chart reports.

The builder pattern allows users to progressively construct reports by adding sections one at a time, then rendering in their chosen format.

class stellium.presentation.builder.ReportBuilder[source]

Bases: object

Builder for chart reports.

Example:

report = (
    ReportBuilder()
    .from_chart(chart)
    .with_chart_overview()
    .with_planet_positions()
    .render(format="rich_table")
)
from_chart(chart)[source]

Set the chart to generate reports from.

Parameters:

chart (CalculatedChart | Comparison | MultiChart) – A CalculatedChart, Comparison, or MultiChart

Return type:

ReportBuilder

Returns:

Self for chaining

preset_aspects_only()[source]

Aspects-only preset: Focus on planetary relationships.

Includes: - Chart overview - All aspects (with orbs) - Aspect patterns (Grand Trines, T-Squares, etc.)

Return type:

ReportBuilder

Returns:

Self for chaining

Note: Aspect patterns require AspectPatternAnalyzer component.

Example

>>> report = ReportBuilder().from_chart(chart).preset_aspects_only().render()
preset_detailed()[source]

Detailed preset: Comprehensive report with all major sections.

Includes: - Chart overview - Moon phase - Planet positions (with speed and all house systems) - Declinations - All aspects (sorted by orb) - House cusps - Essential dignities

Return type:

ReportBuilder

Returns:

Self for chaining

Example

>>> report = ReportBuilder().from_chart(chart).preset_detailed().render()
preset_full()[source]

Full preset: Everything available.

Includes all sections for maximum detail: - Chart overview - Moon phase - Planet positions (with speed and all house systems) - Declinations - All aspects - Aspect patterns (Grand Trines, T-Squares, etc.) - House cusps - Essential dignities - Midpoints and midpoint aspects - Fixed stars - Zodiacal Releasing (Part of Fortune and Part of Spirit)

Note: Some sections require specific components to be added during chart calculation (e.g., DignityComponent, AspectPatternAnalyzer, MidpointCalculator, FixedStarsComponent, ZodiacalReleasingAnalyzer). Missing components show helpful messages rather than errors.

Return type:

ReportBuilder

Returns:

Self for chaining

Example

>>> chart = (ChartBuilder.from_native(native)
...     .with_aspects()
...     .add_component(DignityComponent())
...     .add_analyzer(AspectPatternAnalyzer())
...     .add_component(MidpointCalculator())
...     .add_component(FixedStarsComponent())
...     .add_analyzer(ZodiacalReleasingAnalyzer(["Part of Fortune", "Part of Spirit"]))
...     .calculate())
>>> report = ReportBuilder().from_chart(chart).preset_full().render()
preset_minimal()[source]

Minimal preset: Just the basics.

Includes: - Chart overview (name, date, location) - Planet positions

Return type:

ReportBuilder

Returns:

Self for chaining

Example

>>> report = ReportBuilder().from_chart(chart).preset_minimal().render()
preset_positions_only()[source]

Positions-only preset: Focus on planetary placements.

Includes: - Chart overview - Planet positions (with speed and house placements) - Declinations - House cusps

No aspects or interpretive sections.

Return type:

ReportBuilder

Returns:

Self for chaining

Example

>>> report = ReportBuilder().from_chart(chart).preset_positions_only().render()
preset_standard()[source]

Standard preset: Common report sections for everyday use.

Includes: - Chart overview - Planet positions (with house placements) - Major aspects (sorted by orb) - House cusps

Return type:

ReportBuilder

Returns:

Self for chaining

Example

>>> report = ReportBuilder().from_chart(chart).preset_standard().render()
preset_synastry()[source]

Synastry preset: Optimized for relationship comparison charts.

Designed for Comparison objects, this preset shows: - Chart overview (displays both charts’ info) - Planet positions (side-by-side tables for each chart) - Cross-chart aspects (with chart labels) - House cusps (side-by-side tables for each chart)

Return type:

ReportBuilder

Returns:

Self for chaining

Example

>>> comparison = ComparisonBuilder.synastry(chart1, chart2).calculate()
>>> report = ReportBuilder().from_chart(comparison).preset_synastry().render()
preset_transit()[source]

Transit preset: Optimized for transit comparison charts.

Shows natal chart positions alongside transit positions, with cross-chart aspects showing transiting planets’ aspects to natal positions.

Includes: - Chart overview - Planet positions (side-by-side: natal vs transit) - Cross-chart aspects (all aspects, tight orbs) - House cusps (side-by-side)

Return type:

ReportBuilder

Returns:

Self for chaining

Example

>>> transit = ComparisonBuilder.transit(natal, transit_time).calculate()
>>> report = ReportBuilder().from_chart(transit).preset_transit().render()
preset_transit_calendar(end, start=None, include_minor_planets=False)[source]

Transit calendar preset: Sky events over a date range.

Bundles all three transit calendar sections showing what’s happening in the sky between two dates. Useful for planning around retrogrades, sign changes, and eclipses.

Includes: - Planetary stations (retrograde/direct) - Sign ingresses (planets changing signs) - Eclipses (solar and lunar)

Parameters:
  • end (datetime) – End date for the calendar (required)

  • start (datetime | None) – Start date (optional, defaults to chart date)

  • include_minor_planets (bool) – Include Chiron in stations/ingresses (default: False)

Return type:

ReportBuilder

Returns:

Self for chaining

Example

>>> # Transit calendar for the next year from chart date
>>> from datetime import timedelta
>>> chart_date = chart.datetime.utc_datetime
>>> report = (ReportBuilder()
...     .from_chart(chart)
...     .preset_transit_calendar(end=chart_date + timedelta(days=365))
...     .render())
>>>
>>> # Specific date range
>>> from datetime import datetime
>>> report = (ReportBuilder()
...     .from_chart(chart)
...     .preset_transit_calendar(
...         start=datetime(2025, 1, 1),
...         end=datetime(2025, 12, 31)
...     )
...     .render())

Note

This preset does NOT include natal chart information - it’s purely about sky events. For transits TO your natal chart, use ComparisonBuilder.transit() with preset_transit() instead.

render(format='rich_table', file=None, show=None)[source]

Render the report with flexible output options.

Parameters:
  • format (str) – Output format (“rich_table”, “plain_table”, “text”, “prose”, “pdf”, “html”)

  • file (str | None) – Optional filename to save to

  • show (bool | None) – Whether to display in terminal. Defaults to True for terminal formats (rich_table, plain_table, text, prose) and False for file formats (pdf, html).

Return type:

str | None

Returns:

Filename if saved to file, None otherwise

Raises:

Examples

# Show in terminal with Rich formatting report.render()

# Save to file (with terminal preview) report.render(format=”plain_table”, file=”chart.txt”)

# Save quietly (no terminal output) report.render(format=”plain_table”, file=”chart.txt”, show=False)

# Generate PDF with chart image and title (configured via builder) report.with_chart_image().with_title(“My Report”).render(

format=”pdf”, file=”report.pdf”

)

with_arabic_parts(mode='all', show_formula=True, show_description=False)[source]

Add Arabic Parts (Lots) table.

Parameters:
  • mode (str) – Which parts to display (DEFAULT: “all”) - “all”: All calculated parts - “core”: 7 Hermetic Lots (Fortune, Spirit, Eros, etc.) - “family”: Family & Relationship Lots - “life”: Life Topic Lots - “planetary”: Planetary Exaltation Lots

  • show_formula (bool) – Include the formula column (DEFAULT: True) Formula shows as “ASC + Point2 - Point3” with * for sect-aware parts

  • show_description (bool) – Include part descriptions (DEFAULT: False)

Return type:

ReportBuilder

Returns:

Self for chaining

Example

>>> # Show all Arabic Parts with formulas
>>> report = (ReportBuilder()
...     .from_chart(chart)
...     .with_arabic_parts()
...     .render())
>>>
>>> # Show only core Hermetic Lots with descriptions
>>> report = (ReportBuilder()
...     .from_chart(chart)
...     .with_arabic_parts(
...         mode="core",
...         show_description=True
...     )
...     .render())

Note

Requires ArabicPartsCalculator to be added to chart builder:

from stellium.components.arabic_parts import ArabicPartsCalculator

chart = (ChartBuilder.from_native(native)

.add_component(ArabicPartsCalculator()) .calculate())

with_aspect_patterns(pattern_types='all', sort_by='type')[source]

Add aspect patterns table (Grand Trines, T-Squares, Yods, etc.).

Parameters:
  • pattern_types (str | list[str]) – Which pattern types to show (DEFAULT: “all”) - “all”: Show all detected patterns - list[str]: Show specific pattern types

  • sort_by (str) – How to sort patterns (DEFAULT: “type”) - “type”: Group by pattern type - “element”: Group by element - “count”: Sort by number of planets

Return type:

ReportBuilder

Returns:

Self for chaining

Note

Requires AspectPatternAnalyzer to be added to chart builder. If missing, displays helpful message instead of erroring.

with_aspects(mode='all', orbs=True, sort_by='orb', include_aspectarian=True, aspectarian_detailed=False, aspectarian_cell_size=None, aspectarian_theme=None)[source]

Add aspects table with optional aspectarian grid.

Parameters:
  • mode (str) – “all”, “major”, “minor”, or “harmonic”

  • orbs (bool) – Show orb column

  • sort_by (str) – How to sort aspects (“orb”, “planet”, or “aspect_type”)

  • include_aspectarian (bool) – Include aspectarian grid SVG (default: True)

  • aspectarian_detailed (bool) – Show orb and A/S in aspectarian cells (default: False)

  • aspectarian_cell_size (int | None) – Override cell size for aspectarian (default: config default)

  • aspectarian_theme (str | None) – Theme for aspectarian rendering (default: None)

Return type:

ReportBuilder

Returns:

Self for chaining

Note

The aspectarian SVG is displayed in HTML/PDF output. Terminal output shows a placeholder with dimensions.

with_chart_image(path=None)[source]

Include a chart wheel image in the report.

When called without arguments, automatically generates a chart SVG using the chart’s default draw settings.

Parameters:

path (str | None) – Optional path to an existing SVG file. If not provided, a chart image will be auto-generated when rendering.

Return type:

ReportBuilder

Returns:

Self for chaining

Examples

# Auto-generate chart image report.with_chart_image()

# Use existing SVG file report.with_chart_image(“my_chart.svg”)

with_chart_overview()[source]

Add chart overview section (birth data, chart type, etc.).

Return type:

ReportBuilder

Returns:

Self for chaining

with_cross_aspects(mode='all', orbs=True, sort_by='orb')[source]

Add cross-chart aspects table (for Comparison charts).

Shows aspects between chart1 planets and chart2 planets with appropriate labels for each chart.

Parameters:
  • mode (str) – “all”, “major”, “minor”, or “harmonic”

  • orbs (bool) – Show orb column

  • sort_by (str) – How to sort aspects (“orb”, “planet”, “aspect_type”)

Return type:

ReportBuilder

Returns:

Self for chaining

Note

This section requires a Comparison object (from ComparisonBuilder). If used with a single CalculatedChart, displays a helpful message.

Example

>>> comparison = ComparisonBuilder.synastry(chart1, chart2).calculate()
>>> report = (ReportBuilder()
...     .from_chart(comparison)
...     .with_cross_aspects(mode="major")
...     .render())
with_declination_aspects(mode='all', show_orbs=True, show_oob_status=True, sort_by='orb')[source]

Add declination aspects table (Parallel and Contraparallel).

Declination aspects are based on equatorial coordinates rather than ecliptic longitude. They represent a different type of planetary relationship.

  • Parallel: Two planets at the same declination (same hemisphere). Interpreted like a conjunction.

  • Contraparallel: Two planets at equal declination but opposite hemispheres. Interpreted like an opposition.

Parameters:
  • mode (str) – Which aspects to show (DEFAULT: “all”) - “all”: Both parallel and contraparallel - “parallel”: Only parallel aspects - “contraparallel”: Only contraparallel aspects

  • show_orbs (bool) – Show orb column (DEFAULT: True)

  • show_oob_status (bool) – Show out-of-bounds status (DEFAULT: True)

  • sort_by (str) – How to sort aspects (DEFAULT: “orb”) - “orb”: Tightest aspects first - “planet”: Group by planet - “aspect_type”: Group by Parallel/Contraparallel

Return type:

ReportBuilder

Returns:

Self for chaining

Note

Requires .with_declination_aspects() on ChartBuilder:
chart = (ChartBuilder.from_native(native)

.with_aspects() .with_declination_aspects(orb=1.0) .calculate())

Example

>>> report = (ReportBuilder()
...     .from_chart(chart)
...     .with_chart_overview()
...     .with_declination_aspects(mode="all")
...     .render())
with_declinations()[source]

Add declinations table.

Shows planetary declinations (distance from celestial equator), direction (north/south), and out-of-bounds status.

Out-of-bounds planets have declination beyond the Sun’s maximum (~23°27’) and are considered to have extra intensity or unconventional expression.

Return type:

ReportBuilder

Returns:

Self for chaining

Example

>>> report = (ReportBuilder()
...     .from_chart(chart)
...     .with_chart_overview()
...     .with_declinations()
...     .render())
with_dignities(essential='both', show_details=False)[source]

Add essential dignities table.

Parameters:
  • essential (str) – Which essential dignity system(s) to show (DEFAULT: “both”) - “traditional”: Traditional dignities only - “modern”: Modern dignities only - “both”: Both systems - “none”: Skip essential dignities

  • show_details (bool) – Show dignity names instead of just scores

Return type:

ReportBuilder

Returns:

Self for chaining

Note

Requires DignityComponent to be added to chart builder. If missing, displays helpful message instead of erroring.

with_dispositors(mode='both', rulership='traditional', house_system=None, show_chains=True)[source]

Add dispositor analysis section.

Shows planetary and/or house-based dispositor chains, final dispositor(s), and mutual receptions.

Parameters:
  • mode (str) – Which dispositor analysis to show (DEFAULT: “both”) - “planetary”: Traditional planet-disposes-planet - “house”: Kate’s house-based innovation (life area flow) - “both”: Show both analyses

  • rulership (str) – “traditional” or “modern” rulership system (DEFAULT: “traditional”)

  • house_system (str | None) – House system for house-based mode (defaults to chart’s default)

  • show_chains (bool) – Whether to show full disposition chain details (DEFAULT: True)

Return type:

ReportBuilder

Returns:

Self for chaining

Example

>>> report = (ReportBuilder()
...     .from_chart(chart)
...     .with_chart_overview()
...     .with_dispositors(mode="both")
...     .render())

Note

For graphical output (SVG), use the DispositorEngine directly:

from stellium.engines.dispositors import DispositorEngine, render_both_dispositors engine = DispositorEngine(chart) graph = render_both_dispositors(engine.planetary(), engine.house_based()) graph.render(“dispositors”, format=”svg”)

with_eclipses(end, start=None, eclipse_types='both')[source]

Add eclipses table.

Shows solar and lunar eclipses within a date range. Useful for eclipse calendars and transit planning.

Parameters:
  • end (datetime) – End date for eclipse search (required)

  • start (datetime | None) – Start date for eclipse search (optional, defaults to chart date)

  • eclipse_types (str) – Which types to include (“both”, “solar”, “lunar”)

Return type:

ReportBuilder

Returns:

Self for chaining

Example

>>> # Eclipses for the next 2 years from chart date
>>> from datetime import datetime, timedelta
>>> chart_date = chart.datetime.utc_datetime
>>> report = (ReportBuilder()
...     .from_chart(chart)
...     .with_eclipses(end=chart_date + timedelta(days=730))
...     .render())
>>>
>>> # Only solar eclipses in a specific range
>>> report = (ReportBuilder()
...     .from_chart(chart)
...     .with_eclipses(
...         start=datetime(2025, 1, 1),
...         end=datetime(2025, 12, 31),
...         eclipse_types="solar"
...     )
...     .render())
with_fixed_stars(tier=None, include_keywords=True, sort_by='longitude')[source]

Add fixed stars table.

Shows positions and metadata for fixed stars in the chart. Requires FixedStarsComponent to be added to chart builder.

Parameters:
  • tier (int | None) – Filter to specific tier (DEFAULT: None = all tiers) - 1: Royal Stars only (Aldebaran, Regulus, Antares, Fomalhaut) - 2: Major Stars only - 3: Extended Stars only - None: All tiers

  • include_keywords (bool) – Include interpretive keywords column (DEFAULT: True)

  • sort_by (str) – Sort order (DEFAULT: “longitude”) - “longitude”: Zodiacal order - “magnitude”: Brightest first - “tier”: Royal first, then Major, then Extended

Return type:

ReportBuilder

Returns:

Self for chaining

Example

>>> # Royal stars only
>>> report = (ReportBuilder()
...     .from_chart(chart)
...     .with_fixed_stars(tier=1)
...     .render())
>>>
>>> # All stars sorted by brightness
>>> report = (ReportBuilder()
...     .from_chart(chart)
...     .with_fixed_stars(sort_by="magnitude")
...     .render())

Note

Requires FixedStarsComponent to be added to chart builder:
chart = (ChartBuilder.from_native(native)

.add_component(FixedStarsComponent()) .calculate())

with_house_cusps(systems='all')[source]

Add house cusps table.

Parameters:

systems (str | list[str]) – Which house systems to display (DEFAULT: “all”) - “all”: Show all calculated systems - list[str]: Show specific systems

Return type:

ReportBuilder

Returns:

Self for chaining

with_ingresses(end, start=None, planets=None, include_moon=False, include_minor=False)[source]

Add sign ingresses table.

Shows when planets enter new zodiac signs within a date range. Useful for tracking sign changes and transit planning.

Parameters:
  • end (datetime) – End date for ingress search (required)

  • start (datetime | None) – Start date for ingress search (optional, defaults to chart date)

  • planets (list[str] | None) – Which planets to include (default: Sun through Pluto)

  • include_moon (bool) – Include Moon ingresses (default: False, very frequent)

  • include_minor (bool) – Include Chiron (default: False)

Return type:

ReportBuilder

Returns:

Self for chaining

Example

>>> # Ingresses for the next year from chart date
>>> from datetime import datetime, timedelta
>>> chart_date = chart.datetime.utc_datetime
>>> report = (ReportBuilder()
...     .from_chart(chart)
...     .with_ingresses(end=chart_date + timedelta(days=365))
...     .render())
>>>
>>> # Specific date range with Moon included
>>> report = (ReportBuilder()
...     .from_chart(chart)
...     .with_ingresses(
...         start=datetime(2025, 1, 1),
...         end=datetime(2025, 12, 31),
...         include_moon=True
...     )
...     .render())
with_midpoint_aspects(mode='conjunction', orb=1.5, midpoint_filter='all', sort_by='orb')[source]

Add planets aspecting midpoints table.

This shows which planets activate which midpoints - the most useful way to interpret midpoints. Typically only conjunctions matter (1-2° orb).

Parameters:
  • mode (str) – Which aspects to check (DEFAULT: “conjunction”) - “conjunction”: Only conjunctions (most common, recommended) - “hard”: Conjunction, square, opposition - “all”: All major aspects

  • orb (float) – Maximum orb in degrees (DEFAULT: 1.5°) Midpoints use tighter orbs than regular aspects.

  • midpoint_filter (str) – Which midpoints to check (DEFAULT: “all”) - “all”: All calculated midpoints - “core”: Only Sun/Moon/ASC/MC midpoints

  • sort_by (str) – Sort order (DEFAULT: “orb”) - “orb”: Tightest aspects first - “planet”: Group by aspecting planet - “midpoint”: Group by midpoint

Return type:

ReportBuilder

Returns:

Self for chaining

Example

>>> # Show planets conjunct any midpoint within 1.5°
>>> report = (ReportBuilder()
...     .from_chart(chart)
...     .with_midpoint_aspects()
...     .render())
>>>
>>> # Show hard aspects to core midpoints only
>>> report = (ReportBuilder()
...     .from_chart(chart)
...     .with_midpoint_aspects(
...         mode="hard",
...         midpoint_filter="core",
...         orb=2.0
...     )
...     .render())

Note

Requires MidpointCalculator to be added to chart builder:
chart = (ChartBuilder.from_native(native)

.add_component(MidpointCalculator()) .calculate())

with_midpoint_trees(tree_bases=None, branch_objects=None, orb=1.5, aspect_mode='conjunction', output='both')[source]

Add midpoint tree visualization section.

Generates tree diagrams showing which midpoints aspect focal points. This is a standard Uranian/Hamburg astrology technique for interpreting planetary pictures.

Parameters:
  • tree_bases (list[str] | None) – Focal points to build trees for (DEFAULT: Sun, Moon, MC, ASC)

  • branch_objects (list[str] | None) – Objects to include in midpoint pairs. Default: 10 planets + ASC + MC + True Node

  • orb (float) – Maximum orb in degrees (DEFAULT: 1.5°)

  • aspect_mode (str) – Which aspects to check (DEFAULT: “conjunction”) - “conjunction”: Only conjunctions (0°) - “hard”: Conjunction + 45° series (0°, 45°, 90°, 135°, 180°) - “all”: All major aspects

  • output (str) – What to generate (DEFAULT: “both”) - “svg”: Just SVG visualization - “text”: Just text output - “both”: Both SVG and text

Return type:

ReportBuilder

Returns:

Self for chaining

Example

>>> # Show midpoint trees with hard aspects
>>> report = (ReportBuilder()
...     .from_chart(chart)
...     .with_midpoint_trees(aspect_mode="hard")
...     .render())
>>>
>>> # Custom focal points with conjunction only
>>> report = (ReportBuilder()
...     .from_chart(chart)
...     .with_midpoint_trees(
...         tree_bases=["Sun", "Moon"],
...         orb=2.0,
...         aspect_mode="conjunction"
...     )
...     .render())

Note

Requires MidpointCalculator to be added to chart builder:
chart = (ChartBuilder.from_native(native)

.add_component(MidpointCalculator()) .calculate())

with_midpoints(mode='all', threshold=None)[source]

Add midpoints table.

Parameters:
  • mode (str) – “all” or “core” (Sun/Moon/ASC/MC midpoints)

  • threshold (int | None) – Only show top N midpoints by importance

Return type:

ReportBuilder

Returns:

Self for chaining

with_moon_phase()[source]

Add moon phase section.

Return type:

ReportBuilder

with_planet_positions(include_speed=False, include_house=True, house_systems='all')[source]

Add planet positions table.

Parameters:
  • include_speed (bool) – Show speed in longitude (for retrogrades)

  • include_house (bool) – Show house placement

  • house_systems (str | list[str]) – Which house systems to display (DEFAULT: “all”) - “all”: Show all calculated systems - list[str]: Show specific systems - None: Show default system only

Return type:

ReportBuilder

Returns:

Self for chaining

with_profections(age=None, date=None, include_monthly=True, include_multi_point=True, include_timeline=False, timeline_range=None, points=None, house_system=None, rulership='traditional')[source]

Add profection timing analysis section.

Profections are a Hellenistic technique where the ASC advances one sign per year. The planet ruling that sign becomes the “Lord of the Year.”

Parameters:
  • age (int | None) – Age for profection (either age OR date required)

  • date (str | None) – Target date as ISO string (e.g., “2025-06-15”)

  • include_monthly (bool) – Show monthly profection when date is provided

  • include_multi_point (bool) – Show lords for ASC, Sun, Moon, MC

  • include_timeline (bool) – Show timeline table of Lords

  • timeline_range (tuple[int, int] | None) – Custom range for timeline (e.g., (25, 40))

  • points (list[str] | None) – Custom points for multi-point analysis

  • house_system (str | None) – House system to use (default: prefers Whole Sign)

  • rulership (str) – “traditional” or “modern”

Return type:

ReportBuilder

Returns:

Self for chaining

Example:

# By age
report = (
    ReportBuilder()
    .from_chart(chart)
    .with_profections(age=30)
    .render()
)

# By date with timeline
report = (
    ReportBuilder()
    .from_chart(chart)
    .with_profections(date="2025-06-15", include_timeline=True)
    .render()
)
with_profections_wheel(age=None, date=None, compare_ages=None, show_wheel=True, show_table=True, house_system=None, rulership='traditional')[source]

Add profection wheel visualization section.

Generates a visual wheel diagram showing annual profections: - Circular wheel with ages 0-95 spiraling through 12 houses - Zodiac signs and house labels around the perimeter - Natal planet positions marked on the wheel - Current age highlighted - Summary table with profection details

Parameters:
  • age (int | None) – Current age to highlight (either age OR date required)

  • date (str | None) – Target date as ISO string (e.g., “2025-06-15”)

  • compare_ages (list[int] | None) – List of ages to compare in table (default: current and next)

  • show_wheel (bool) – Whether to show the wheel visualization (default: True)

  • show_table (bool) – Whether to show the summary table (default: True)

  • house_system (str | None) – House system to use (default: prefers Whole Sign)

  • rulership (str) – “traditional” or “modern”

Return type:

ReportBuilder

Returns:

Self for chaining

Example:

# By age with both wheel and table
report = (
    ReportBuilder()
    .from_chart(chart)
    .with_profections_wheel(age=30)
    .render(format="pdf", file="profections.pdf")
)

# Compare specific ages
report = (
    ReportBuilder()
    .from_chart(chart)
    .with_profections_wheel(
        age=30,
        compare_ages=[30, 31, 32]
    )
    .render()
)
with_section(section)[source]

Add a custom section.

This allows users to extend the report system with their own sections.

Parameters:

section (ReportSection) – Any object implementing the ReportSection protocol

Return type:

ReportBuilder

Returns:

Self for chaining

Example:

class MyCustomSection:
    @property
    def section_name(self) -> str:
        return "My Analysis"

    def generate_data(self, chart: CalculatedChart) -> dict:
        return {"type": "text", "text": "Custom analysis..."}

report = (
    ReportBuilder()
    .from_chart(chart)
    .with_section(MyCustomSection())
    .render()
)
with_stations(end, start=None, planets=None, include_minor=False)[source]

Add planetary stations (retrograde/direct) table.

Shows when planets station retrograde or direct within a date range. Useful for retrograde calendars and transit planning.

Parameters:
  • end (datetime) – End date for station search (required)

  • start (datetime | None) – Start date for station search (optional, defaults to chart date)

  • planets (list[str] | None) – Which planets to include (default: Mercury through Pluto)

  • include_minor (bool) – Include Chiron (default: False)

Return type:

ReportBuilder

Returns:

Self for chaining

Example

>>> # Stations for the next year from chart date
>>> from datetime import datetime, timedelta
>>> chart_date = chart.datetime.utc_datetime
>>> report = (ReportBuilder()
...     .from_chart(chart)
...     .with_stations(end=chart_date + timedelta(days=365))
...     .render())
>>>
>>> # Specific date range
>>> report = (ReportBuilder()
...     .from_chart(chart)
...     .with_stations(
...         start=datetime(2025, 1, 1),
...         end=datetime(2025, 12, 31)
...     )
...     .render())
with_title(title)[source]

Set a custom title for the report.

The title appears on the cover page of PDF reports. If not set, a default title is generated from the chart’s name.

Parameters:

title (str) – Custom title string

Return type:

ReportBuilder

Returns:

Self for chaining

Examples

report.with_title(“Birth Chart Analysis”) report.with_title(“Albert Einstein - Complete Natal Analysis”)

with_zodiacal_releasing(lots=None, mode='both', query_date=None, query_age=None, context_periods=2)[source]

Add Zodiacal Releasing timing analysis section.

Zodiacal Releasing is a Hellenistic predictive technique that divides life into major periods ruled by signs, showing when different life themes are activated.

Parameters:
  • lots (str | list[str] | None) – Which lot(s) to display: - str: Single lot name (e.g., “Part of Fortune”) - list[str]: Multiple lots (e.g., [“Part of Fortune”, “Part of Spirit”]) - None: All lots calculated in the chart (DEFAULT)

  • mode (str) – Display mode: - “snapshot”: Current periods only - “timeline”: L1 timeline only - “both”: Both snapshot and timeline (DEFAULT)

  • query_date (str | None) – Date for snapshot as ISO string (defaults to now)

  • query_age (float | None) – Age for snapshot (alternative to query_date)

  • context_periods (int) – Number of L3/L4 periods to show before/after current (default: 2)

Return type:

ReportBuilder

Returns:

Self for chaining

Note

Requires ZodiacalReleasingAnalyzer to be added during chart calculation:

from stellium.engines.releasing import ZodiacalReleasingAnalyzer

chart = (

ChartBuilder.from_native(native) .add_analyzer(ZodiacalReleasingAnalyzer([“Part of Fortune”, “Part of Spirit”])) .calculate()

)

Example:

# Show current ZR state for all calculated lots
report = (
    ReportBuilder()
    .from_chart(chart)
    .with_zodiacal_releasing()
    .render()
)

# Show ZR for specific lot at specific age
report = (
    ReportBuilder()
    .from_chart(chart)
    .with_zodiacal_releasing(
        lots="Part of Fortune",
        mode="snapshot",
        query_age=30
    )
    .render()
)

# Show only L1 timeline for Fortune and Spirit
report = (
    ReportBuilder()
    .from_chart(chart)
    .with_zodiacal_releasing(
        lots=["Part of Fortune", "Part of Spirit"],
        mode="timeline"
    )
    .render()
)
with_zr_visualization(lot='Part of Fortune', year=None, levels=(1, 2, 3), output='both')[source]

Add Zodiacal Releasing visualization (SVG timeline diagram).

Generates visual timeline diagrams in Honeycomb Collective style: - Overview page: natal angles chart + period length reference - Timeline page: stacked L1/L2/L3 timelines with peak shapes

Parameters:
  • lot (str) – Which lot to visualize (default: “Part of Fortune”)

  • year (int | None) – Year to visualize (defaults to current year)

  • levels (tuple[int, ...]) – Which levels to show in timeline (default: 1, 2, 3)

  • output (str) – What to generate: - “overview”: Just the overview page - “timeline”: Just the timeline visualization - “both”: Both pages (DEFAULT)

Return type:

ReportBuilder

Returns:

Self for chaining

Note

Requires ZodiacalReleasingAnalyzer to be added during chart calculation:

from stellium.engines.releasing import ZodiacalReleasingAnalyzer

chart = (

ChartBuilder.from_native(native) .add_analyzer(ZodiacalReleasingAnalyzer([“Part of Fortune”])) .calculate()

)

Example:

# Add ZR visualization to PDF report
report = (
    ReportBuilder()
    .from_chart(chart)
    .with_chart_overview()
    .with_zr_visualization(lot="Part of Fortune", year=2025)
    .render(format="pdf", file="report.pdf")
)

Output renderers for reports.

Renderers take structured data from sections and format it for different output mediums (terminal with Rich, plain text, PDF, HTML, etc.).

class stellium.presentation.renderers.HTMLRenderer(css_style=None)[source]

Bases: object

Renderer that converts report sections to HTML.

Can be used directly for HTML output or as input to PDFRenderer. Generates clean, semantic HTML with embedded CSS styling.

render_report(sections, chart_svg_content=None)[source]

Render complete report to HTML string.

Parameters:
  • sections (list[tuple[str, dict[str, Any]]]) – List of (section_name, section_data) tuples

  • chart_svg_content (str | None) – Optional SVG content to embed

Return type:

str

Returns:

Complete HTML document as string

render_section(section_name, section_data)[source]

Render a single section to HTML.

Return type:

str

class stellium.presentation.renderers.PlainTextRenderer[source]

Bases: object

Plain text renderer with no dependencies.

Creates simple ASCII tables and formatted text suitable for: - Log files - Email - Systems without Rich library - Piping to other tools

render_report(sections)[source]

Render complete report as plain text.

Return type:

str

render_section(section_name, section_data)[source]

Render a single section as plain text.

Return type:

str

class stellium.presentation.renderers.ProseRenderer(bullet='•')[source]

Bases: object

Renderer that converts structured section data to natural language prose.

Designed for pasting chart info into conversations with AI friends or anywhere you want clean, readable text without tables or formatting codes.

Output format: - Chart overview as flowing sentences - Lists of positions/aspects as bullet points - No tables, no headers, no special formatting

Example output:

Kate Louie was born on January 6, 1994 at 11:47 AM in Mountain View, CA. This is a day chart with Aries rising. The chart ruler is Mars.

Planet Positions: • The Sun is at 15°52’ Capricorn in the 9th house • The Moon is at 22°14’ Scorpio in the 6th house …

render_report(sections)[source]

Render complete report as natural language prose.

Parameters:

sections (list[tuple[str, dict[str, Any]]]) – List of (section_name, section_data) tuples

Return type:

str

Returns:

Prose text suitable for pasting into conversations

class stellium.presentation.renderers.RichTableRenderer[source]

Bases: object

Renderer using the Rich library for beautiful terminal output.

Requires: pip install rich

Features: - Colored tables with borders - Automatic column width adjustment - Unicode box characters

print_report(sections)[source]

Print report directly to terminal with Rich formatting.

This method prints the report with full ANSI colors and styling, intended for immediate terminal display.

Return type:

None

render_report(sections)[source]

Render complete report to plaintext string (ANSI codes stripped).

Used for file output and testing. Returns clean text without ANSI escape codes.

Return type:

str

render_section(section_name, section_data)[source]

Render a single section with Rich.

Return type:

str

class stellium.presentation.renderers.TypstRenderer[source]

Bases: object

Renderer that creates beautiful PDFs using Typst typesetting.

Typst is a modern typesetting system with LaTeX-quality output but much simpler syntax and faster compilation.

Requires: pip install typst

Features: - Professional typography (kerning, ligatures, hyphenation) - Clean table styling with alternating row colors - Proper font handling for astrological symbols - Embedded SVG chart support - Page headers/footers with page numbers

render_report(sections, output_file=None, chart_svg_path=None, title='Astrological Report')[source]

Render complete report to PDF using Typst.

Parameters:
  • sections (list[tuple[str, dict[str, Any]]]) – List of (section_name, section_data) tuples

  • output_file (str | None) – Optional file path to save PDF

  • chart_svg_path (str | None) – Optional path to chart SVG file to embed

  • title (str) – Report title

Return type:

bytes

Returns:

PDF as bytes

Report section implementations.

Each section extracts specific data from a CalculatedChart and formats it into a standardized structure that renderers can consume.

This package contains domain-organized section modules: - core: Basic chart info (ChartOverview, PlanetPosition, HouseCusps) - aspects: Aspect-related (AspectSection, AspectPatternSection, CrossChartAspectSection) - dignities: Dignity analysis (DignitySection, DispositorSection) - midpoints: Midpoint analysis (MidpointSection, MidpointAspectsSection) - timing: Time lord techniques (ZodiacalReleasingSection, ProfectionSection) - misc: Other sections (MoonPhase, Declination, FixedStars, ArabicParts, etc.)

class stellium.presentation.sections.AntisciaSection(include_contra=True, show_points=False)[source]

Bases: object

Table of Antiscia and Contra-Antiscia conjunctions.

Antiscia are “hidden conjunctions” - when one planet’s reflection point (across the solstice axis) is conjunct another planet. Contra-antiscia are reflections across the equinox axis.

Shows: - The two planets involved - Whether it’s antiscia or contra-antiscia - The orb of the conjunction - Whether the aspect is applying or separating

generate_data(chart)[source]

Generate antiscia table.

Return type:

dict[str, Any]

property section_name: str
class stellium.presentation.sections.ArabicPartsSection(mode='all', show_formula=True, show_description=False)[source]

Bases: object

Table of Arabic Parts (Lots).

Shows calculated Arabic Parts with their positions, house placements, and optionally their formulas and descriptions.

Modes: - “all”: All calculated parts - “core”: 7 Hermetic Lots (Fortune, Spirit, Eros, Necessity, Courage, Victory, Nemesis) - “family”: Family & Relationship Lots (Father, Mother, Marriage, Children, Siblings) - “life”: Life Topic Lots (Action, Profession, Passion, Illness, Death, etc.) - “planetary”: Planetary Exaltation Lots (Sun, Moon, Mercury, Venus, Mars, Jupiter, Saturn)

CORE_PARTS = {'Part of Courage (Tolma)', 'Part of Eros (Love)', 'Part of Eros (Planetary)', 'Part of Fortune', 'Part of Necessity (Ananke)', 'Part of Nemesis', 'Part of Spirit', 'Part of Victory (Nike)'}
FAMILY_PARTS = {'Part of Children', 'Part of Father', 'Part of Marriage', 'Part of Mother', 'Part of Siblings'}
LIFE_PARTS = {'Part of Action (Praxis)', 'Part of Death', 'Part of Debt / Bondage', 'Part of Friends / Associates', 'Part of Illness / Disease', 'Part of Passion / Lust', 'Part of Profession (User)', 'Part of Travel'}
PLANETARY_PARTS = {'Part of Jupiter (Exaltation)', 'Part of Mars (Exaltation)', 'Part of Mercury (Exaltation)', 'Part of Saturn (Exaltation)', 'Part of Venus (Exaltation)', 'Part of the Moon (Exaltation)', 'Part of the Sun (Exaltation)'}
generate_data(chart)[source]

Generate Arabic Parts table.

Return type:

dict[str, Any]

property section_name: str
class stellium.presentation.sections.AspectPatternSection(pattern_types='all', sort_by='type')[source]

Bases: object

Table of detected aspect patterns.

Shows Grand Trines, T-Squares, Yods, etc. Gracefully handles missing pattern data with helpful message.

generate_data(chart)[source]

Generate aspect pattern table.

For MultiChart/Comparison, shows patterns for each chart grouped by label.

Return type:

dict[str, Any]

property section_name: str
class stellium.presentation.sections.AspectSection(mode='all', orbs=True, sort_by='orb', include_aspectarian=True, aspectarian_detailed=False, aspectarian_cell_size=None, aspectarian_theme=None)[source]

Bases: object

Table of aspects between planets.

Shows: - Planet 1 - Aspect type - Planet 2 - Orb (optional) - Applying/Separating (optional)

Optionally includes an aspectarian grid SVG (triangle for single charts).

generate_data(chart)[source]

Generate aspects table with optional aspectarian SVG.

For MultiChart/Comparison, shows each chart’s internal aspects grouped by chart label.

Return type:

dict[str, Any]

property section_name: str
class stellium.presentation.sections.CacheInfoSection[source]

Bases: object

Display cache statistics in reports.

generate_data(chart)[source]

Generate cache info from chart metadata.

Return type:

dict[str, Any]

property section_name: str
class stellium.presentation.sections.ChartOverviewSection[source]

Bases: object

Overview section with basic chart information.

Shows: - Native name (if available) - Birth date/time - Location - Chart type (day/night) - House system

For Comparison objects, shows info for both charts.

generate_data(chart)[source]

Generate chart overview data.

For Comparison/MultiChart objects, shows all charts’ information.

Why key-value format? - Simple label: value pairs - Easy to render as a list or small table - Human-readable structure

Return type:

dict[str, Any]

property section_name: str
class stellium.presentation.sections.CrossChartAspectSection(mode='all', orbs=True, sort_by='orb')[source]

Bases: object

Table of cross-chart aspects for Comparison charts.

Shows aspects between chart1 planets and chart2 planets: - Chart 1 Planet (with label) - Aspect type - Chart 2 Planet (with label) - Orb (optional) - Applying/Separating (optional)

generate_data(chart)[source]

Generate cross-chart aspects table.

Return type:

dict[str, Any]

property section_name: str
class stellium.presentation.sections.DeclinationAspectSection(mode='all', show_orbs=True, show_oob_status=True, sort_by='orb')[source]

Bases: object

Table of declination aspects (Parallel and Contraparallel).

Shows: - Planet 1 (with glyph) - Aspect type (Parallel ∥ or Contraparallel ⋕) - Planet 2 (with glyph) - Orb (optional) - Out-of-bounds status (if either planet is OOB)

generate_data(chart)[source]

Generate declination aspects table.

Return type:

dict[str, Any]

property section_name: str
class stellium.presentation.sections.DeclinationSection[source]

Bases: object

Table of planetary declinations.

Shows: - Planet name with glyph - Declination value (degrees north/south of celestial equator) - Direction (North/South) - Out-of-bounds status

generate_data(chart)[source]

Generate declination table data.

For MultiChart/Comparison, shows declinations for each chart grouped by label. Shows declination values for all planets with equatorial coordinates. Highlights out-of-bounds planets (beyond Sun’s max declination).

Return type:

dict[str, Any]

property section_name: str
class stellium.presentation.sections.DignitySection(essential='both', show_details=False)[source]

Bases: object

Table of essential dignities for planets.

Shows dignity scores and details for traditional and/or modern systems. Gracefully handles missing dignity data with helpful message.

generate_data(chart)[source]

Generate dignity table.

For MultiChart/Comparison, shows dignities for each chart grouped by label.

Return type:

dict[str, Any]

property section_name: str
class stellium.presentation.sections.DispositorSection(mode='both', rulership='traditional', house_system=None, show_chains=True)[source]

Bases: object

Dispositor analysis section.

Shows planetary and/or house-based dispositor chains, final dispositor(s), and mutual receptions. Text summary only - graphviz rendering is separate.

Example

>>> section = DispositorSection(mode="both")
>>> data = section.generate_data(chart)
generate_data(chart)[source]

Generate dispositor analysis.

For MultiChart/Comparison, shows dispositors for each chart grouped by label. Returns a compound section with subsections for planetary and/or house dispositors, each showing final dispositor and mutual receptions.

Return type:

dict[str, Any]

property section_name: str
class stellium.presentation.sections.EclipseSection(start, end, eclipse_types='both')[source]

Bases: object

Eclipse report section.

Shows solar and lunar eclipses within a date range. Useful for eclipse calendars and transit planning.

Note: This section uses explicit start/end dates rather than analyzing the natal chart. The chart parameter in generate_data() is accepted for protocol compliance but not used internally.

generate_data(chart)[source]

Generate eclipse data for the date range.

Parameters:

chart (CalculatedChart) – CalculatedChart (accepted for protocol, not used internally)

Return type:

dict[str, Any]

Returns:

Dictionary with eclipse data for rendering

property section_name: str
class stellium.presentation.sections.FixedStarsSection(tier=None, include_keywords=True, sort_by='longitude')[source]

Bases: object

Table of fixed star positions.

Shows: - Star name with glyph - Zodiac position (sign + degree) - Constellation - Magnitude (brightness) - Traditional planetary nature - Keywords

generate_data(chart)[source]

Generate fixed stars table data.

Return type:

dict[str, Any]

property section_name: str
class stellium.presentation.sections.HouseCuspsSection(systems='all')[source]

Bases: object

Table of house cusp positions for multiple house systems.

Shows: - House number (1-12) - Cusp position for each calculated house system

generate_data(chart)[source]

Generate house cusps table.

For Comparison/MultiChart objects, generates side-by-side tables for each chart.

Return type:

dict[str, Any]

property section_name: str
class stellium.presentation.sections.IngressSection(start, end, planets=None, include_moon=False, include_minor=False)[source]

Bases: object

Sign ingress report section.

Shows when planets enter new zodiac signs within a date range. Useful for tracking sign changes and transit planning.

Note: This section uses explicit start/end dates rather than analyzing the natal chart. The chart parameter in generate_data() is accepted for protocol compliance but not used internally.

generate_data(chart)[source]

Generate ingress data for the date range.

Parameters:

chart (CalculatedChart) – CalculatedChart (accepted for protocol, not used internally)

Return type:

dict[str, Any]

Returns:

Dictionary with ingress data for rendering

property section_name: str
class stellium.presentation.sections.MidpointAspectsSection(mode='conjunction', orb=1.5, midpoint_filter='all', sort_by='orb')[source]

Bases: object

Table of planets aspecting midpoints.

This is what most people care about with midpoints: which planets activate which midpoints? Typically conjunctions are most important (1-2° orb), but hard aspects (square, opposition) can also be shown.

Shows: - Planet that aspects the midpoint - Aspect type (conjunction, square, etc.) - Midpoint being aspected (e.g., “Sun/Moon”) - Orb in degrees

ASPECT_ANGLES = {'Conjunction': 0, 'Opposition': 180, 'Sextile': 60, 'Square': 90, 'Trine': 120}
CORE_OBJECTS = {'ASC', 'MC', 'Moon', 'Sun'}
generate_data(chart)[source]

Generate midpoint aspects table.

For MultiChart/Comparison, shows midpoint aspects for each chart grouped by label.

Return type:

dict[str, Any]

property section_name: str
class stellium.presentation.sections.MidpointSection(mode='all', threshold=None)[source]

Bases: object

Table of midpoints.

Shows: - Midpoint pair (e.g., “Sun/Moon”) - Degree position - Sign

CORE_OBJECTS = {'ASC', 'MC', 'Moon', 'Sun'}
generate_data(chart)[source]

Generate midpoints table.

For MultiChart/Comparison, shows midpoints for each chart grouped by label.

Return type:

dict[str, Any]

property section_name: str
class stellium.presentation.sections.MidpointTreeSection(tree_bases=None, branch_objects=None, orb=1.5, aspect_mode='conjunction', output='both')[source]

Bases: object

Midpoint Tree visualization section.

Generates tree diagrams showing which midpoints aspect focal points. Standard technique in Uranian/Hamburg astrology for interpreting planetary pictures.

For each focal point (default: Sun, Moon, MC, ASC), shows all midpoints that aspect it within the configured orb.

Example:

section = MidpointTreeSection(
    tree_bases=["Sun", "Moon", "MC", "ASC"],
    orb=1.5,
    aspect_mode="hard",  # conjunction + 45° series
    output="both"
)
ALL_ASPECTS = {'Conjunction': 0, 'Opposition': 180, 'Sextile': 60, 'Square': 90, 'Trine': 120}
DEFAULT_BRANCH_OBJECTS = ['Sun', 'Moon', 'Mercury', 'Venus', 'Mars', 'Jupiter', 'Saturn', 'Uranus', 'Neptune', 'Pluto', 'ASC', 'MC', 'True Node']
DEFAULT_TREE_BASES = ['Sun', 'Moon', 'MC', 'ASC']
HARD_ASPECTS = {'Conjunction': 0, 'Opposition': 180, 'Semisquare': 45, 'Sesquisquare': 135, 'Square': 90}
generate_data(chart)[source]

Generate midpoint tree visualization data.

Return type:

dict[str, Any]

property section_name: str
class stellium.presentation.sections.MoonPhaseSection[source]

Bases: object

Display Moon phase information.

generate_data(chart)[source]

Generate moon phase data.

Return type:

dict[str, Any]

property section_name: str
class stellium.presentation.sections.PlanetPositionSection(include_speed=False, include_house=True, house_systems='all')[source]

Bases: object

Table of planet positions.

Shows: - Planet name - Sign + degree - House (optional) - Speed (optional, shows retrograde status)

generate_data(chart)[source]

Generate planet positions table.

For Comparison/MultiChart objects, generates side-by-side tables for each chart.

Return type:

dict[str, Any]

property section_name: str
class stellium.presentation.sections.ProfectionSection(age=None, date=None, include_monthly=True, include_multi_point=True, include_timeline=False, timeline_range=None, points=None, house_system=None, rulership='traditional')[source]

Bases: object

Profection timing analysis section.

Shows annual profections with Lord of the Year, activated house, and optionally monthly profections and multi-point analysis.

generate_data(chart)[source]

Generate profection analysis data.

Return type:

dict

property section_name: str
class stellium.presentation.sections.ProfectionVisualizationSection(age=None, date=None, compare_ages=None, show_wheel=True, show_table=True, house_system=None, rulership='traditional')[source]

Bases: object

Profection wheel visualization section.

Generates an SVG visualization showing: - Circular wheel with ages spiraling through 12 houses - Zodiac signs and house labels around the perimeter - Natal planet positions - Current age highlighted - Summary table with profection details

Example

section = ProfectionVisualizationSection(

age=30, show_table=True

) data = section.generate_data(chart) # data[“content”] contains SVG string

generate_data(chart)[source]

Generate profection visualization data.

Return type:

dict[str, Any]

property section_name: str
class stellium.presentation.sections.StationSection(start, end, planets=None, include_minor=False)[source]

Bases: object

Planetary stations report section.

Shows when planets station retrograde or direct within a date range. Useful for retrograde calendars and transit planning.

Note: This section uses explicit start/end dates rather than analyzing the natal chart. The chart parameter in generate_data() is accepted for protocol compliance but not used internally.

generate_data(chart)[source]

Generate station data for the date range.

Parameters:

chart (CalculatedChart) – CalculatedChart (accepted for protocol, not used internally)

Return type:

dict[str, Any]

Returns:

Dictionary with station data for rendering

property section_name: str
class stellium.presentation.sections.TransitGanttSection(start, end, transit_planets=None, aspects=None, include_natal_points=None, width=900, row_height=14, exclude_fast_planets=True, theme='dark')[source]

Bases: object

Natal transit periods as an SVG Gantt timeline chart.

Each row is a transit event; bars span the orb window; tick marks indicate exact dates. Rows are grouped by transiting planet.

Defaults to excluding fast planets (Sun, Moon, Mercury, Venus, Mars) since their many short transits make the chart unreadable. Use TransitListSection for comprehensive fast-planet output.

Usage:

section = TransitGanttSection(
    start=datetime(2025, 12, 1),
    end=datetime(2026, 6, 1),
)
report = ReportBuilder().from_chart(natal_chart).add_section(section)
report.render(format="pdf")
generate_data(chart)[source]

Calculate and return transit periods as an SVG Gantt chart.

Parameters:

chart (CalculatedChart) – The natal CalculatedChart to transit to.

Return type:

dict[str, Any]

Returns:

Dict with type=”svg” and SVG content string.

property section_name: str
class stellium.presentation.sections.TransitListSection(start, end, transit_planets=None, aspects=None, include_natal_points=None, exclude_fast_planets=False)[source]

Bases: object

Natal transit aspect periods as a plain-text list.

Shows when transiting planets form aspects to natal positions, with orb entry/exit dates, e.g.:

Dec 2 - Mar 2 '26 — Jupiter △ natal Chiron
Dec 4 — Mercury □ natal Jupiter
Jan 8 - Feb 6 '26 — Uranus △ natal Neptune (3x: Jan 15, Jan 30, Feb 12)

Usage:

section = TransitListSection(
    start=datetime(2025, 12, 1),
    end=datetime(2026, 6, 1),
)
report = ReportBuilder().from_chart(natal_chart).add_section(section)
report.render()

Note: The chart parameter in generate_data() is the natal chart.

generate_data(chart)[source]

Calculate and return transit periods as text rows.

Parameters:

chart (CalculatedChart) – The natal CalculatedChart to transit to.

Return type:

dict[str, Any]

Returns:

Dict with type=”text” and structured period data.

property section_name: str
class stellium.presentation.sections.TransitPeriod(transit_planet, natal_planet, aspect_name, aspect_angle, orb, exact_dates, start, end)[source]

Bases: object

A single transit event: one transiting planet forming one aspect to one natal point.

Includes the orb entry/exit window and all exact dates within the window. Multi-pass transits (retrograde causing 2–3 exact crossings) are represented as a single TransitPeriod with multiple exact_dates entries.

transit_planet

Name of the transiting planet (e.g. “Jupiter”)

natal_planet

Name of the natal point being aspected (e.g. “Sun”)

aspect_name

Name of the aspect (e.g. “Trine”)

aspect_angle

Aspect angle in degrees (e.g. 120.0)

orb

Orb used for this calculation in degrees

exact_dates

One or more exact-aspect datetimes (UTC); 2–3 = retrograde passes

start

When transit entered orb, or None if before the search window

end

When transit exits orb, or None if extends beyond search window

aspect_angle: float
aspect_name: str
property duration_days: float | None

Duration in days, or None if window extends outside the search range.

end: datetime | None
exact_dates: tuple[datetime, ...]
property is_multi_pass: bool

True if the transit crosses the exact point more than once (retrograde).

natal_planet: str
orb: float
property peak_date: datetime

The middle exact date — most representative for sorting.

start: datetime | None
transit_planet: str
class stellium.presentation.sections.ZRVisualizationSection(lot='Part of Fortune', year=None, start_date=None, end_date=None, levels=(1, 2, 3), highlight_date=None, output='both')[source]

Bases: object

Zodiacal Releasing visualization section.

Generates SVG timeline visualizations in Honeycomb Collective style: - Overview page: natal angles chart + period length reference - Timeline page: stacked L1/L2/L3 timelines with peak shapes

Returns SVG content that can be embedded in PDF planners or reports.

Example

section = ZRVisualizationSection(

lot=”Part of Fortune”, year=2025, output=”timeline” # or “overview” or “both”

) data = section.generate_data(chart) # data[“content”] contains SVG string

generate_data(chart)[source]

Generate ZR visualization data.

Return type:

dict[str, Any]

Returns:

Dict with type=”svg” or type=”compound” containing SVG content(s)

property section_name: str
class stellium.presentation.sections.ZodiacalReleasingSection(lots=None, mode='both', query_date=None, query_age=None, context_periods=2)[source]

Bases: object

Zodiacal Releasing timing analysis section.

Shows ZR periods from one or more Lots (Fortune, Spirit, etc.), with options to display current snapshot and/or L1 timeline.

Snapshot mode shows: - Current L1/L2 periods (always shown) - L3/L4 context (current ± 2 periods) for finer timing

Timeline mode shows: - All L1 periods with ages and status indicators - Peak (★), Angular (◆), and Current (⚡) markers

generate_data(chart)[source]

Generate Zodiacal Releasing data.

Return type:

dict[str, Any]

property section_name: str
stellium.presentation.sections.abbreviate_house_system(system_name)[source]

Generate 2-4 character abbreviation for house system names.

Parameters:

system_name (str) – Full house system name (e.g., “Placidus”, “Whole Sign”)

Return type:

str

Returns:

Short abbreviation (e.g., “Pl”, “WS”)

Example

>>> abbreviate_house_system("Placidus")
'Pl'
>>> abbreviate_house_system("Whole Sign")
'WS'
stellium.presentation.sections.calculate_transit_periods(natal_chart, start, end, transit_planets=None, aspects=None, include_natal_points=None)[source]

Calculate transit-to-natal aspect periods for a date range.

For each (transiting planet × natal point × aspect) combination, returns TransitPeriod objects with orb entry/exit dates and all exact dates within the window, including multiple passes from retrograde motion.

Reuses stellium.engines.search functions for all ephemeris lookups — no raw Swiss Ephemeris calls here.

Parameters:
  • natal_chart (CalculatedChart) – The natal CalculatedChart to transit to.

  • start (datetime) – Start of date range (UTC).

  • end (datetime) – End of date range (UTC).

  • transit_planets (list[str] | None) – Planets to use as transits (default: all 12).

  • aspects (dict[str, float] | None) – Dict of {aspect_name: orb_degrees} (default: 5 major).

  • include_natal_points (list[str] | None) – Limit natal points checked (default: all planets).

Return type:

list[TransitPeriod]

Returns:

List of TransitPeriod objects sorted by start date (or first exact date if start is outside the search window).

stellium.presentation.sections.get_aspect_display(aspect_name)[source]

Get display name and glyph for an aspect.

Parameters:

aspect_name (str) – Aspect name (e.g., “Conjunction”, “Trine”)

Return type:

tuple[str, str]

Returns:

Tuple of (name, glyph)

stellium.presentation.sections.get_aspect_sort_key(aspect_name)[source]

Generate sort key for consistent aspect ordering in reports.

Sorting hierarchy: 1. Registry insertion order (aspects ordered by angle: 0°, 60°, 90°, etc.) 2. Angle value (for aspects not in registry) 3. Alphabetical name (final fallback)

Parameters:

aspect_name (str) – Name of the aspect (e.g., “Conjunction”, “Trine”)

Return type:

tuple

Returns:

Tuple sort key for use with sorted()

Example

aspects = sorted(aspects, key=lambda a: get_aspect_sort_key(a.aspect_name))

stellium.presentation.sections.get_object_display(name)[source]

Get display name and glyph for a celestial object.

Parameters:

name (str) – Internal object name (e.g., “Sun”, “True Node”)

Return type:

tuple[str, str]

Returns:

Tuple of (display_name, glyph)

stellium.presentation.sections.get_object_sort_key(position)[source]

Generate sort key for consistent object ordering in reports.

Sorting hierarchy: 1. Object type (Planet < Node < Point < Asteroid < Angle < Midpoint) 2. Registry insertion order (for registered objects) 3. Swiss Ephemeris ID (for unregistered known objects) 4. Alphabetical name (for custom objects)

Parameters:

position – A celestial object position from CalculatedChart

Returns:

Tuple sort key for use with sorted()

Example

positions = sorted(chart.positions, key=get_object_sort_key)

stellium.presentation.sections.get_sign_glyph(sign_name)[source]

Get the zodiac glyph for a sign name.

Return type:

str

Core report sections for basic chart information.

Includes: - ChartOverviewSection: Basic chart metadata (date, time, location) - PlanetPositionSection: Positions of celestial objects - HouseCuspsSection: House cusp positions for multiple systems

class stellium.presentation.sections.core.ChartOverviewSection[source]

Bases: object

Overview section with basic chart information.

Shows: - Native name (if available) - Birth date/time - Location - Chart type (day/night) - House system

For Comparison objects, shows info for both charts.

generate_data(chart)[source]

Generate chart overview data.

For Comparison/MultiChart objects, shows all charts’ information.

Why key-value format? - Simple label: value pairs - Easy to render as a list or small table - Human-readable structure

Return type:

dict[str, Any]

property section_name: str
class stellium.presentation.sections.core.HouseCuspsSection(systems='all')[source]

Bases: object

Table of house cusp positions for multiple house systems.

Shows: - House number (1-12) - Cusp position for each calculated house system

generate_data(chart)[source]

Generate house cusps table.

For Comparison/MultiChart objects, generates side-by-side tables for each chart.

Return type:

dict[str, Any]

property section_name: str
class stellium.presentation.sections.core.PlanetPositionSection(include_speed=False, include_house=True, house_systems='all')[source]

Bases: object

Table of planet positions.

Shows: - Planet name - Sign + degree - House (optional) - Speed (optional, shows retrograde status)

generate_data(chart)[source]

Generate planet positions table.

For Comparison/MultiChart objects, generates side-by-side tables for each chart.

Return type:

dict[str, Any]

property section_name: str

Aspect-related report sections.

Includes: - AspectSection: Table of aspects between planets - AspectPatternSection: Detected aspect patterns (Grand Trines, T-Squares, etc.) - CrossChartAspectSection: Cross-chart aspects for synastry/comparison

class stellium.presentation.sections.aspects.AspectPatternSection(pattern_types='all', sort_by='type')[source]

Bases: object

Table of detected aspect patterns.

Shows Grand Trines, T-Squares, Yods, etc. Gracefully handles missing pattern data with helpful message.

generate_data(chart)[source]

Generate aspect pattern table.

For MultiChart/Comparison, shows patterns for each chart grouped by label.

Return type:

dict[str, Any]

property section_name: str
class stellium.presentation.sections.aspects.AspectSection(mode='all', orbs=True, sort_by='orb', include_aspectarian=True, aspectarian_detailed=False, aspectarian_cell_size=None, aspectarian_theme=None)[source]

Bases: object

Table of aspects between planets.

Shows: - Planet 1 - Aspect type - Planet 2 - Orb (optional) - Applying/Separating (optional)

Optionally includes an aspectarian grid SVG (triangle for single charts).

generate_data(chart)[source]

Generate aspects table with optional aspectarian SVG.

For MultiChart/Comparison, shows each chart’s internal aspects grouped by chart label.

Return type:

dict[str, Any]

property section_name: str
class stellium.presentation.sections.aspects.CrossChartAspectSection(mode='all', orbs=True, sort_by='orb')[source]

Bases: object

Table of cross-chart aspects for Comparison charts.

Shows aspects between chart1 planets and chart2 planets: - Chart 1 Planet (with label) - Aspect type - Chart 2 Planet (with label) - Orb (optional) - Applying/Separating (optional)

generate_data(chart)[source]

Generate cross-chart aspects table.

Return type:

dict[str, Any]

property section_name: str

Dignity-related report sections.

Includes: - DignitySection: Essential dignities table - DispositorSection: Dispositor chains and final dispositors

class stellium.presentation.sections.dignities.DignitySection(essential='both', show_details=False)[source]

Bases: object

Table of essential dignities for planets.

Shows dignity scores and details for traditional and/or modern systems. Gracefully handles missing dignity data with helpful message.

generate_data(chart)[source]

Generate dignity table.

For MultiChart/Comparison, shows dignities for each chart grouped by label.

Return type:

dict[str, Any]

property section_name: str
class stellium.presentation.sections.dignities.DispositorSection(mode='both', rulership='traditional', house_system=None, show_chains=True)[source]

Bases: object

Dispositor analysis section.

Shows planetary and/or house-based dispositor chains, final dispositor(s), and mutual receptions. Text summary only - graphviz rendering is separate.

Example

>>> section = DispositorSection(mode="both")
>>> data = section.generate_data(chart)
generate_data(chart)[source]

Generate dispositor analysis.

For MultiChart/Comparison, shows dispositors for each chart grouped by label. Returns a compound section with subsections for planetary and/or house dispositors, each showing final dispositor and mutual receptions.

Return type:

dict[str, Any]

property section_name: str

Midpoint-related report sections.

Includes: - MidpointSection: Table of calculated midpoints - MidpointAspectsSection: Planets aspecting midpoints

class stellium.presentation.sections.midpoints.MidpointAspectsSection(mode='conjunction', orb=1.5, midpoint_filter='all', sort_by='orb')[source]

Bases: object

Table of planets aspecting midpoints.

This is what most people care about with midpoints: which planets activate which midpoints? Typically conjunctions are most important (1-2° orb), but hard aspects (square, opposition) can also be shown.

Shows: - Planet that aspects the midpoint - Aspect type (conjunction, square, etc.) - Midpoint being aspected (e.g., “Sun/Moon”) - Orb in degrees

ASPECT_ANGLES = {'Conjunction': 0, 'Opposition': 180, 'Sextile': 60, 'Square': 90, 'Trine': 120}
CORE_OBJECTS = {'ASC', 'MC', 'Moon', 'Sun'}
generate_data(chart)[source]

Generate midpoint aspects table.

For MultiChart/Comparison, shows midpoint aspects for each chart grouped by label.

Return type:

dict[str, Any]

property section_name: str
class stellium.presentation.sections.midpoints.MidpointSection(mode='all', threshold=None)[source]

Bases: object

Table of midpoints.

Shows: - Midpoint pair (e.g., “Sun/Moon”) - Degree position - Sign

CORE_OBJECTS = {'ASC', 'MC', 'Moon', 'Sun'}
generate_data(chart)[source]

Generate midpoints table.

For MultiChart/Comparison, shows midpoints for each chart grouped by label.

Return type:

dict[str, Any]

property section_name: str

Midpoint Tree visualization section.

Generates tree diagrams showing which midpoints aspect focal points (planets/angles). This is a standard Uranian/Hamburg astrology technique for interpreting planetary pictures.

Example tree output:

☉ Sun (15°23’ ♑) ├── ☽/♂ ☌ 0.3° Moon/Mars conjunction ├── ♀/♄ □ 1.1° Venus/Saturn square └── ☿/♃ ⊼ 0.8° Mercury/Jupiter semi-square

class stellium.presentation.sections.midpoint_tree.MidpointBranch(midpoint, midpoint_display, aspect_name, aspect_glyph, orb, midpoint_position)[source]

Bases: object

A single branch in a midpoint tree (one midpoint aspecting the focal point).

aspect_glyph: str
aspect_name: str
midpoint: MidpointPosition | CelestialPosition
midpoint_display: str
midpoint_position: str
orb: float
class stellium.presentation.sections.midpoint_tree.MidpointTree(focal_point, focal_display, focal_position, branches)[source]

Bases: object

A complete midpoint tree for one focal point.

branches: list[MidpointBranch]
focal_display: str
focal_point: CelestialPosition
focal_position: str
class stellium.presentation.sections.midpoint_tree.MidpointTreeSection(tree_bases=None, branch_objects=None, orb=1.5, aspect_mode='conjunction', output='both')[source]

Bases: object

Midpoint Tree visualization section.

Generates tree diagrams showing which midpoints aspect focal points. Standard technique in Uranian/Hamburg astrology for interpreting planetary pictures.

For each focal point (default: Sun, Moon, MC, ASC), shows all midpoints that aspect it within the configured orb.

Example:

section = MidpointTreeSection(
    tree_bases=["Sun", "Moon", "MC", "ASC"],
    orb=1.5,
    aspect_mode="hard",  # conjunction + 45° series
    output="both"
)
ALL_ASPECTS = {'Conjunction': 0, 'Opposition': 180, 'Sextile': 60, 'Square': 90, 'Trine': 120}
DEFAULT_BRANCH_OBJECTS = ['Sun', 'Moon', 'Mercury', 'Venus', 'Mars', 'Jupiter', 'Saturn', 'Uranus', 'Neptune', 'Pluto', 'ASC', 'MC', 'True Node']
DEFAULT_TREE_BASES = ['Sun', 'Moon', 'MC', 'ASC']
HARD_ASPECTS = {'Conjunction': 0, 'Opposition': 180, 'Semisquare': 45, 'Sesquisquare': 135, 'Square': 90}
generate_data(chart)[source]

Generate midpoint tree visualization data.

Return type:

dict[str, Any]

property section_name: str

Miscellaneous report sections.

Includes: - CacheInfoSection: Cache statistics - MoonPhaseSection: Moon phase information - DeclinationSection: Planetary declinations - DeclinationAspectSection: Parallel and contraparallel aspects - FixedStarsSection: Fixed star positions - ArabicPartsSection: Arabic Parts (Lots)

class stellium.presentation.sections.misc.AntisciaSection(include_contra=True, show_points=False)[source]

Bases: object

Table of Antiscia and Contra-Antiscia conjunctions.

Antiscia are “hidden conjunctions” - when one planet’s reflection point (across the solstice axis) is conjunct another planet. Contra-antiscia are reflections across the equinox axis.

Shows: - The two planets involved - Whether it’s antiscia or contra-antiscia - The orb of the conjunction - Whether the aspect is applying or separating

generate_data(chart)[source]

Generate antiscia table.

Return type:

dict[str, Any]

property section_name: str
class stellium.presentation.sections.misc.ArabicPartsSection(mode='all', show_formula=True, show_description=False)[source]

Bases: object

Table of Arabic Parts (Lots).

Shows calculated Arabic Parts with their positions, house placements, and optionally their formulas and descriptions.

Modes: - “all”: All calculated parts - “core”: 7 Hermetic Lots (Fortune, Spirit, Eros, Necessity, Courage, Victory, Nemesis) - “family”: Family & Relationship Lots (Father, Mother, Marriage, Children, Siblings) - “life”: Life Topic Lots (Action, Profession, Passion, Illness, Death, etc.) - “planetary”: Planetary Exaltation Lots (Sun, Moon, Mercury, Venus, Mars, Jupiter, Saturn)

CORE_PARTS = {'Part of Courage (Tolma)', 'Part of Eros (Love)', 'Part of Eros (Planetary)', 'Part of Fortune', 'Part of Necessity (Ananke)', 'Part of Nemesis', 'Part of Spirit', 'Part of Victory (Nike)'}
FAMILY_PARTS = {'Part of Children', 'Part of Father', 'Part of Marriage', 'Part of Mother', 'Part of Siblings'}
LIFE_PARTS = {'Part of Action (Praxis)', 'Part of Death', 'Part of Debt / Bondage', 'Part of Friends / Associates', 'Part of Illness / Disease', 'Part of Passion / Lust', 'Part of Profession (User)', 'Part of Travel'}
PLANETARY_PARTS = {'Part of Jupiter (Exaltation)', 'Part of Mars (Exaltation)', 'Part of Mercury (Exaltation)', 'Part of Saturn (Exaltation)', 'Part of Venus (Exaltation)', 'Part of the Moon (Exaltation)', 'Part of the Sun (Exaltation)'}
generate_data(chart)[source]

Generate Arabic Parts table.

Return type:

dict[str, Any]

property section_name: str
class stellium.presentation.sections.misc.CacheInfoSection[source]

Bases: object

Display cache statistics in reports.

generate_data(chart)[source]

Generate cache info from chart metadata.

Return type:

dict[str, Any]

property section_name: str
class stellium.presentation.sections.misc.DeclinationAspectSection(mode='all', show_orbs=True, show_oob_status=True, sort_by='orb')[source]

Bases: object

Table of declination aspects (Parallel and Contraparallel).

Shows: - Planet 1 (with glyph) - Aspect type (Parallel ∥ or Contraparallel ⋕) - Planet 2 (with glyph) - Orb (optional) - Out-of-bounds status (if either planet is OOB)

generate_data(chart)[source]

Generate declination aspects table.

Return type:

dict[str, Any]

property section_name: str
class stellium.presentation.sections.misc.DeclinationSection[source]

Bases: object

Table of planetary declinations.

Shows: - Planet name with glyph - Declination value (degrees north/south of celestial equator) - Direction (North/South) - Out-of-bounds status

generate_data(chart)[source]

Generate declination table data.

For MultiChart/Comparison, shows declinations for each chart grouped by label. Shows declination values for all planets with equatorial coordinates. Highlights out-of-bounds planets (beyond Sun’s max declination).

Return type:

dict[str, Any]

property section_name: str
class stellium.presentation.sections.misc.FixedStarsSection(tier=None, include_keywords=True, sort_by='longitude')[source]

Bases: object

Table of fixed star positions.

Shows: - Star name with glyph - Zodiac position (sign + degree) - Constellation - Magnitude (brightness) - Traditional planetary nature - Keywords

generate_data(chart)[source]

Generate fixed stars table data.

Return type:

dict[str, Any]

property section_name: str
class stellium.presentation.sections.misc.MoonPhaseSection[source]

Bases: object

Display Moon phase information.

generate_data(chart)[source]

Generate moon phase data.

Return type:

dict[str, Any]

property section_name: str

Timing technique report sections.

Includes: - ProfectionSection: Annual and monthly profections - ZodiacalReleasingSection: Zodiacal releasing periods and timeline

class stellium.presentation.sections.timing.ProfectionSection(age=None, date=None, include_monthly=True, include_multi_point=True, include_timeline=False, timeline_range=None, points=None, house_system=None, rulership='traditional')[source]

Bases: object

Profection timing analysis section.

Shows annual profections with Lord of the Year, activated house, and optionally monthly profections and multi-point analysis.

generate_data(chart)[source]

Generate profection analysis data.

Return type:

dict

property section_name: str
class stellium.presentation.sections.timing.ZodiacalReleasingSection(lots=None, mode='both', query_date=None, query_age=None, context_periods=2)[source]

Bases: object

Zodiacal Releasing timing analysis section.

Shows ZR periods from one or more Lots (Fortune, Spirit, etc.), with options to display current snapshot and/or L1 timeline.

Snapshot mode shows: - Current L1/L2 periods (always shown) - L3/L4 context (current ± 2 periods) for finer timing

Timeline mode shows: - All L1 periods with ages and status indicators - Peak (★), Angular (◆), and Current (⚡) markers

generate_data(chart)[source]

Generate Zodiacal Releasing data.

Return type:

dict[str, Any]

property section_name: str

Transit calendar report sections.

These sections show sky events (not natal chart analysis): - StationSection: Planetary stations (retrograde/direct) - IngressSection: Sign ingresses - EclipseSection: Solar and lunar eclipses

Unlike other sections, these are date-range based rather than chart-analysis based. The chart is passed for protocol compliance but the sections use their own start/end dates.

class stellium.presentation.sections.transits.EclipseSection(start, end, eclipse_types='both')[source]

Bases: object

Eclipse report section.

Shows solar and lunar eclipses within a date range. Useful for eclipse calendars and transit planning.

Note: This section uses explicit start/end dates rather than analyzing the natal chart. The chart parameter in generate_data() is accepted for protocol compliance but not used internally.

generate_data(chart)[source]

Generate eclipse data for the date range.

Parameters:

chart (CalculatedChart) – CalculatedChart (accepted for protocol, not used internally)

Return type:

dict[str, Any]

Returns:

Dictionary with eclipse data for rendering

property section_name: str
class stellium.presentation.sections.transits.IngressSection(start, end, planets=None, include_moon=False, include_minor=False)[source]

Bases: object

Sign ingress report section.

Shows when planets enter new zodiac signs within a date range. Useful for tracking sign changes and transit planning.

Note: This section uses explicit start/end dates rather than analyzing the natal chart. The chart parameter in generate_data() is accepted for protocol compliance but not used internally.

generate_data(chart)[source]

Generate ingress data for the date range.

Parameters:

chart (CalculatedChart) – CalculatedChart (accepted for protocol, not used internally)

Return type:

dict[str, Any]

Returns:

Dictionary with ingress data for rendering

property section_name: str
class stellium.presentation.sections.transits.StationSection(start, end, planets=None, include_minor=False)[source]

Bases: object

Planetary stations report section.

Shows when planets station retrograde or direct within a date range. Useful for retrograde calendars and transit planning.

Note: This section uses explicit start/end dates rather than analyzing the natal chart. The chart parameter in generate_data() is accepted for protocol compliance but not used internally.

generate_data(chart)[source]

Generate station data for the date range.

Parameters:

chart (CalculatedChart) – CalculatedChart (accepted for protocol, not used internally)

Return type:

dict[str, Any]

Returns:

Dictionary with station data for rendering

property section_name: str

Transit period sections for natal chart transit analysis.

Computes when transiting planets form aspects to natal positions, with orb entry/exit windows and multi-pass handling for retrograde transits.

Two output modes: - TransitListSection: plain-text rows, e.g.:

Dec 2 - Mar 2 ‘26 — Jupiter △ natal Chiron Dec 4 — Mercury □ natal Jupiter Jan 8 - Feb 6 ‘26 — Uranus △ natal Neptune (3x: Jan 15, Jan 30, Feb 12)

  • TransitGanttSection: SVG horizontal bar chart grouped by transiting planet. Rows are aspect events; bars show orb window; tick marks show exact dates.

Both sections take an explicit start/end date range and accept a natal CalculatedChart via generate_data() following the ReportSection protocol.

class stellium.presentation.sections.transit_periods.TransitGanttSection(start, end, transit_planets=None, aspects=None, include_natal_points=None, width=900, row_height=14, exclude_fast_planets=True, theme='dark')[source]

Bases: object

Natal transit periods as an SVG Gantt timeline chart.

Each row is a transit event; bars span the orb window; tick marks indicate exact dates. Rows are grouped by transiting planet.

Defaults to excluding fast planets (Sun, Moon, Mercury, Venus, Mars) since their many short transits make the chart unreadable. Use TransitListSection for comprehensive fast-planet output.

Usage:

section = TransitGanttSection(
    start=datetime(2025, 12, 1),
    end=datetime(2026, 6, 1),
)
report = ReportBuilder().from_chart(natal_chart).add_section(section)
report.render(format="pdf")
generate_data(chart)[source]

Calculate and return transit periods as an SVG Gantt chart.

Parameters:

chart (CalculatedChart) – The natal CalculatedChart to transit to.

Return type:

dict[str, Any]

Returns:

Dict with type=”svg” and SVG content string.

property section_name: str
class stellium.presentation.sections.transit_periods.TransitListSection(start, end, transit_planets=None, aspects=None, include_natal_points=None, exclude_fast_planets=False)[source]

Bases: object

Natal transit aspect periods as a plain-text list.

Shows when transiting planets form aspects to natal positions, with orb entry/exit dates, e.g.:

Dec 2 - Mar 2 '26 — Jupiter △ natal Chiron
Dec 4 — Mercury □ natal Jupiter
Jan 8 - Feb 6 '26 — Uranus △ natal Neptune (3x: Jan 15, Jan 30, Feb 12)

Usage:

section = TransitListSection(
    start=datetime(2025, 12, 1),
    end=datetime(2026, 6, 1),
)
report = ReportBuilder().from_chart(natal_chart).add_section(section)
report.render()

Note: The chart parameter in generate_data() is the natal chart.

generate_data(chart)[source]

Calculate and return transit periods as text rows.

Parameters:

chart (CalculatedChart) – The natal CalculatedChart to transit to.

Return type:

dict[str, Any]

Returns:

Dict with type=”text” and structured period data.

property section_name: str
class stellium.presentation.sections.transit_periods.TransitPeriod(transit_planet, natal_planet, aspect_name, aspect_angle, orb, exact_dates, start, end)[source]

Bases: object

A single transit event: one transiting planet forming one aspect to one natal point.

Includes the orb entry/exit window and all exact dates within the window. Multi-pass transits (retrograde causing 2–3 exact crossings) are represented as a single TransitPeriod with multiple exact_dates entries.

transit_planet

Name of the transiting planet (e.g. “Jupiter”)

natal_planet

Name of the natal point being aspected (e.g. “Sun”)

aspect_name

Name of the aspect (e.g. “Trine”)

aspect_angle

Aspect angle in degrees (e.g. 120.0)

orb

Orb used for this calculation in degrees

exact_dates

One or more exact-aspect datetimes (UTC); 2–3 = retrograde passes

start

When transit entered orb, or None if before the search window

end

When transit exits orb, or None if extends beyond search window

aspect_angle: float
aspect_name: str
property duration_days: float | None

Duration in days, or None if window extends outside the search range.

end: datetime | None
exact_dates: tuple[datetime, ...]
property is_multi_pass: bool

True if the transit crosses the exact point more than once (retrograde).

natal_planet: str
orb: float
property peak_date: datetime

The middle exact date — most representative for sorting.

start: datetime | None
transit_planet: str
stellium.presentation.sections.transit_periods.calculate_transit_periods(natal_chart, start, end, transit_planets=None, aspects=None, include_natal_points=None)[source]

Calculate transit-to-natal aspect periods for a date range.

For each (transiting planet × natal point × aspect) combination, returns TransitPeriod objects with orb entry/exit dates and all exact dates within the window, including multiple passes from retrograde motion.

Reuses stellium.engines.search functions for all ephemeris lookups — no raw Swiss Ephemeris calls here.

Parameters:
  • natal_chart (CalculatedChart) – The natal CalculatedChart to transit to.

  • start (datetime) – Start of date range (UTC).

  • end (datetime) – End of date range (UTC).

  • transit_planets (list[str] | None) – Planets to use as transits (default: all 12).

  • aspects (dict[str, float] | None) – Dict of {aspect_name: orb_degrees} (default: 5 major).

  • include_natal_points (list[str] | None) – Limit natal points checked (default: all planets).

Return type:

list[TransitPeriod]

Returns:

List of TransitPeriod objects sorted by start date (or first exact date if start is outside the search window).

Profection wheel visualization section.

Generates SVG wheel visualizations for annual profections: - Circular wheel with ages 0-95 spiraling through 12 houses - House labels with zodiac signs around perimeter - Natal planet positions marked on the wheel - Current age highlighting - Summary table with profection details

class stellium.presentation.sections.profection_visualization.ProfectionVisualizationSection(age=None, date=None, compare_ages=None, show_wheel=True, show_table=True, house_system=None, rulership='traditional')[source]

Bases: object

Profection wheel visualization section.

Generates an SVG visualization showing: - Circular wheel with ages spiraling through 12 houses - Zodiac signs and house labels around the perimeter - Natal planet positions - Current age highlighted - Summary table with profection details

Example

section = ProfectionVisualizationSection(

age=30, show_table=True

) data = section.generate_data(chart) # data[“content”] contains SVG string

generate_data(chart)[source]

Generate profection visualization data.

Return type:

dict[str, Any]

property section_name: str
class stellium.presentation.sections.profection_visualization.ProfectionVizConfig(current_age=None, show_wheel=True, show_table=True, compare_ages=None, width=600, colors=<factory>)[source]

Bases: object

Configuration for profection wheel visualization.

colors: dict
compare_ages: list[int] | None = None
current_age: int | None = None
show_table: bool = True
show_wheel: bool = True
width: int = 600

Zodiacal Releasing visualization section.

Generates SVG timeline visualizations similar to Honeycomb Collective style: - Page 1: Overview (natal angles chart + period length reference table) - Page 2: Stacked L1/L2/L3 timelines with peak shapes

class stellium.presentation.sections.zr_visualization.ZRVisualizationSection(lot='Part of Fortune', year=None, start_date=None, end_date=None, levels=(1, 2, 3), highlight_date=None, output='both')[source]

Bases: object

Zodiacal Releasing visualization section.

Generates SVG timeline visualizations in Honeycomb Collective style: - Overview page: natal angles chart + period length reference - Timeline page: stacked L1/L2/L3 timelines with peak shapes

Returns SVG content that can be embedded in PDF planners or reports.

Example

section = ZRVisualizationSection(

lot=”Part of Fortune”, year=2025, output=”timeline” # or “overview” or “both”

) data = section.generate_data(chart) # data[“content”] contains SVG string

generate_data(chart)[source]

Generate ZR visualization data.

Return type:

dict[str, Any]

Returns:

Dict with type=”svg” or type=”compound” containing SVG content(s)

property section_name: str
class stellium.presentation.sections.zr_visualization.ZRVizConfig(year=None, start_date=None, end_date=None, levels=(1, 2, 3), highlight_date=None, show_loosing_bond=True, show_overview=True, show_timeline=True, width=800, colors=<factory>)[source]

Bases: object

Configuration for ZR visualization.

colors: dict
end_date: date | None = None
highlight_date: datetime | None = None
levels: tuple[int, ...] = (1, 2, 3)
show_loosing_bond: bool = True
show_overview: bool = True
show_timeline: bool = True
start_date: date | None = None
width: int = 800
year: int | None = None

Visualization (stellium.visualization)

Chart rendering and SVG generation.

Core Chart Drawing Engine (stellium.visualization.core)

This module provides the core, refactored drawing system. It is based on a “Layer” strategy pattern.

  • ChartRenderer: The main “canvas” and coordinate system.

  • IRenderLayer: The protocol (interface) that all drawable layers must follow.

class stellium.visualization.core.ChartRenderer(size=600, rotation=0.0, theme=None, style_config=None, zodiac_palette=None, aspect_palette=None, planet_glyph_palette=None, color_sign_info=False)[source]

Bases: object

The core chart drawing canvas and coordinate system.

This class holds the SVG drawing object and provides the geometric utilities for layers to draw themselves. It acts as the “Context” in the strategy pattern.

astrological_to_svg_angle(astro_deg)[source]

Converts astrological degrees (0° = Aries) to SVG degrees (0° = 3 o’clock), appling the chart’s rotation.

Our system: 0° Aries is at 9 o’clock (180° SVG). Rotation is COUNTER-CLOCKWISE.

Return type:

float

polar_to_cartesian(astro_deg, radius)[source]

Converts an astrological degree (0 degrees Aries) and radius to an (x,y) coordinate. Accounts for extended canvas offsets when present.

Return type:

tuple[float, float]

class stellium.visualization.core.IRenderLayer(*args, **kwargs)[source]

Bases: Protocol

Protocol (interface) for all drawable chart layers.

Each layer is a self-contained drawing strategy.

render(renderer, dwg, chart)[source]

The main drawing method for the layer.

Parameters:
  • renderer (ChartRenderer) – ChartRenderer instance, used to access coordinate methods

  • definitions. ((.polar_to_cartesian) and style/radius)

  • dwg (Drawing) – The svgwrite.Drawing object to add elements to.

  • chart (CalculatedChart) – The full CalculatedChart data object.

Return type:

None

stellium.visualization.core.embed_svg_glyph(dwg, svg_content, x, y, size, fill_color=None)[source]

Embed an SVG glyph inline as a nested SVG element.

This function parses SVG content and embeds it directly into the drawing as a nested <svg> element with proper positioning and scaling. This approach works across all browsers and SVG viewers, unlike external image references.

Parameters:
  • dwg (Drawing) – The svgwrite Drawing to add the element to

  • svg_content (str) – The raw SVG content string (from get_glyph())

  • x (float) – Center x coordinate for the glyph

  • y (float) – Center y coordinate for the glyph

  • size (float) – Desired size (width and height) in pixels

  • fill_color (str | None) – Optional color to override stroke/fill (for theming)

Return type:

None

stellium.visualization.core.get_aspect_glyph(aspect_name)[source]

Get the glyph for an astrological aspect.

Parameters:

aspect_name (str) – Aspect name (e.g., “Conjunction”, “Trine”, “Conjunct”)

Return type:

str

Returns:

Unicode glyph string or abbreviation if not found

stellium.visualization.core.get_display_name(object_name)[source]

Get the display name for a celestial object.

Parameters:

object_name (str) – Technical name (e.g., “Mean Apogee”)

Return type:

str

Returns:

Display name (e.g., “Black Moon Lilith”) or original name if not in registry

stellium.visualization.core.get_glyph(object_name)[source]

Get the glyph for a celestial object, with registry lookup and fallback.

Parameters:

object_name (str) – Name of the object (e.g., “Sun”, “Mean Apogee”, “ASC”)

Returns:

  • “type”: “unicode” or “svg”

  • ”value”: glyph string (unicode) or SVG content string (for inline embedding)

Return type:

Dictionary with

class stellium.ChartRenderer(size=600, rotation=0.0, theme=None, style_config=None, zodiac_palette=None, aspect_palette=None, planet_glyph_palette=None, color_sign_info=False)[source]

Bases: object

The core chart drawing canvas and coordinate system.

This class holds the SVG drawing object and provides the geometric utilities for layers to draw themselves. It acts as the “Context” in the strategy pattern.

astrological_to_svg_angle(astro_deg)[source]

Converts astrological degrees (0° = Aries) to SVG degrees (0° = 3 o’clock), appling the chart’s rotation.

Our system: 0° Aries is at 9 o’clock (180° SVG). Rotation is COUNTER-CLOCKWISE.

Return type:

float

polar_to_cartesian(astro_deg, radius)[source]

Converts an astrological degree (0 degrees Aries) and radius to an (x,y) coordinate. Accounts for extended canvas offsets when present.

Return type:

tuple[float, float]

Fluent builder API for chart visualization.

Provides a convenient, discoverable API for creating chart visualizations with presets and easy customization.

class stellium.visualization.builder.ChartDrawBuilder(chart)[source]

Bases: object

Fluent builder for chart visualization with preset support.

This builder provides a high-level, user-friendly API for creating chart visualizations. It wraps the lower-level draw_chart() function with a fluent interface and convenient presets.

Example:

# Simple preset
chart.draw("chart.svg").preset_standard().save()

# Custom configuration
chart.draw("custom.svg").with_size(800).with_theme("midnight").with_moon_phase(
    position="top-left", show_label=True
).save()

# Comparison charts
comparison.draw("synastry.svg").preset_synastry().save()
preset_detailed()[source]

Detailed preset: Chart with info boxes and moon phase.

Includes chart info (top-left), aspect counts (top-right), element/modality table (bottom-left), chart shape (bottom-right), and auto-positioned moon phase (center when no aspects, bottom-right when aspects present).

Note: Chart shape is automatically hidden at render time when moon phase is positioned in bottom-right to avoid collision.

Return type:

ChartDrawBuilder

Returns:

Self for chaining

preset_minimal()[source]

Minimal preset: Just the core chart with no decorations.

Return type:

ChartDrawBuilder

Returns:

Self for chaining

preset_standard()[source]

Standard preset: Core chart with moon phase in center.

Return type:

ChartDrawBuilder

Returns:

Self for chaining

preset_synastry()[source]

Synastry preset: Optimized for relationship comparison charts.

For Comparison objects, automatically enables bi-wheel layout with: - Inner wheel: chart1 (native/person1) planets - Outer wheel: chart2 (partner/transit) planets - Extended canvas with position table and aspectarian - Chart info for both people

Return type:

ChartDrawBuilder

Returns:

Self for chaining

save(to_string=False)[source]

Build and save the chart visualization using the composer.

Only user-specified values are passed to config classes. All other values use the config defaults (single source of truth).

Return type:

str

Returns:

The filename of the saved SVG file

Raises:

ValueError – If required configuration is missing

with_adaptive_colors(sign_info=True)[source]

Enable adaptive coloring for sign glyphs in planet info stack.

Parameters:

sign_info (bool) – Color sign glyphs in planet info based on zodiac palette

Return type:

ChartDrawBuilder

Returns:

Self for chaining

Note

Zodiac wheel glyphs are always adaptively colored for accessibility. This setting only controls the tiny sign glyphs in planet info stacks.

with_aspect_counts(position='top-right')[source]

Add aspect counts summary.

Parameters:

position (str) – Corner position

Return type:

ChartDrawBuilder

Returns:

Self for chaining

with_aspect_palette(palette)[source]

Set the aspect line color palette.

Parameters:

palette (str) – Palette name (e.g., “classic”, “dark”, “blues”, “plasma”)

Return type:

ChartDrawBuilder

Returns:

Self for chaining

with_chart_info(position='top-left', fields=None)[source]

Add chart information box.

Parameters:
  • position (str) – Corner position (“top-left”, “top-right”, “bottom-left”, “bottom-right”)

  • fields (list[str] | None) – Fields to display (options: “name”, “location”, “datetime”, “timezone”, “coordinates”, “house_system”)

Return type:

ChartDrawBuilder

Returns:

Self for chaining

with_chart_shape(position='bottom-right')[source]

Add chart shape detection display.

Parameters:

position (str) – Corner position

Return type:

ChartDrawBuilder

Returns:

Self for chaining

with_degree_ticks(enabled=True)[source]

Enable or disable 1-degree tick marks on the zodiac ring.

When enabled, adds small tick marks at every degree (1°-29° within each sign), in addition to the standard 5° and 10° tick marks.

Parameters:

enabled (bool) – True to show 1° ticks, False to hide them (default: True)

Return type:

ChartDrawBuilder

Returns:

Self for chaining

Example

chart.draw().with_degree_ticks().save() # Enable detailed ticks chart.draw().with_degree_ticks(False).save() # Explicitly disable

with_element_modality_table(position='bottom-left')[source]

Add element × modality cross-table.

Parameters:

position (str) – Corner position

Return type:

ChartDrawBuilder

Returns:

Self for chaining

with_filename(filename)[source]

Set the output filename.

Parameters:

filename (str) – Path to save the SVG file

Return type:

ChartDrawBuilder

Returns:

Self for chaining

with_header(height=None)[source]

Enable the chart header band.

The header displays native information prominently at the top of the chart: - Single chart: Name, location (with coordinates), datetime, timezone - Biwheel: Two-column layout with chart1 left-aligned, chart2 right-aligned - Synthesis: “Composite: Name1 & Name2” with midpoint info

When header is enabled, the chart canvas becomes taller (a rectangle instead of a square), and the simplified info corner shows only calculation settings (house system, ephemeris).

Parameters:

height (int | None) – Optional custom height in pixels (default: 70)

Return type:

ChartDrawBuilder

Returns:

Self for chaining

Example

# Default header chart.draw(“chart.svg”).with_header().save()

# Custom header height chart.draw(“chart.svg”).with_header(height=90).save()

with_house_systems(systems)[source]

Configure multiple house systems to overlay on the chart.

Parameters:

systems (str | list[str]) – House system(s) to display. Can be: - Single system name (e.g., “Placidus”) - List of system names (e.g., [“Placidus”, “Whole Sign”]) - “all” to display all available house systems from the chart

Return type:

ChartDrawBuilder

Returns:

Self for chaining

Example

# Single additional system builder.with_house_systems(“Whole Sign”)

# Multiple systems builder.with_house_systems([“Placidus”, “Koch”, “Whole Sign”])

# All available systems builder.with_house_systems(“all”)

with_margin(margin)[source]

Set the margin around the chart.

Parameters:

margin (int) – Margin in pixels (default: 10)

Return type:

ChartDrawBuilder

Returns:

Self for chaining

with_moon_phase(position='center', show_label=True, size=None, label_size=None)[source]

Configure moon phase display.

Parameters:
  • position (str) – Where to place moon (“center”, “top-left”, “top-right”, “bottom-left”, “bottom-right”)

  • show_label (bool) – Whether to show the phase name

  • size (int | None) – Moon radius in pixels (defaults: 60 for center, 30-35 for corners)

  • label_size (str | None) – Label font size (defaults: “14px” for center, “11px” for corners)

Return type:

ChartDrawBuilder

Returns:

Self for chaining

with_planet_glyph_palette(palette)[source]

Set the planet glyph color palette.

Parameters:

palette (str) – Palette name (e.g., “default”, “element”, “chakra”, “rainbow”)

Return type:

ChartDrawBuilder

Returns:

Self for chaining

with_planet_ticks(enabled=True)[source]

Enable or disable colored planet position tick marks.

When enabled (default), draws small colored tick marks on the inner edge of the zodiac ring at each planet’s true position. The ticks use the planet’s glyph color. When planets are spread out due to collision detection, the dashed connector line goes from the glyph to the tick.

Parameters:

enabled (bool) – True to show planet ticks, False to hide them (default: True)

Return type:

ChartDrawBuilder

Returns:

Self for chaining

Example

chart.draw().with_planet_ticks(False).save() # Disable planet ticks

with_size(size)[source]

Set the chart size in pixels.

Parameters:

size (int) – Chart size (width and height)

Return type:

ChartDrawBuilder

Returns:

Self for chaining

with_tables(position='right', show_position_table=True, show_aspectarian=True, show_house_cusps=True, aspectarian_mode='cross_chart', aspectarian_detailed=False, show_object_types=None)[source]

Add extended canvas with position table and/or aspectarian grid.

This enables an extended canvas area (right, left, or below the chart) that can display tabular data like planetary positions and aspect grids.

Parameters:
  • position (str) – Where to place the extended canvas (“right”, “left”, or “below”)

  • show_position_table (bool) – Show planetary position table

  • show_aspectarian (bool) – Show aspectarian grid

  • show_house_cusps (bool) – Show house cusp table (natal charts only)

  • aspectarian_mode (str) – For comparison charts, which aspects to show: - “cross_chart”: Only cross-chart aspects (default) - “all”: All three grids (chart1 internal, chart2 internal, cross-chart) - “chart1”: Only chart1 internal aspects - “chart2”: Only chart2 internal aspects

  • aspectarian_detailed (bool) – If True, show orb and A/S (applying/separating) indicator in each cell. If False (default), show larger glyphs only.

  • show_object_types (list[str] | None) – List of object types to include in tables. If None, uses default (planet, asteroid, point, node, angle). Example values: ["planet", "asteroid", "midpoint"] or ["planet", "asteroid", "point", "node", "angle", "arabic_part"]

Return type:

ChartDrawBuilder

Returns:

Self for chaining

Example:

# Standard extended canvas
builder.with_tables(position="right")

# Position table only
builder.with_tables(position="right", show_aspectarian=False)

# With house cusps table (natal charts)
builder.with_tables(position="right", show_house_cusps=True)

# Custom aspectarian mode for synastry
builder.with_tables(position="right", aspectarian_mode="all")

# Detailed aspectarian with orb and applying/separating
builder.with_tables(position="right", aspectarian_detailed=True)

# Include midpoints and Arabic parts in tables
builder.with_tables(
    position="right",
    show_object_types=["planet", "asteroid", "midpoint", "arabic_part"]
)
with_theme(theme)[source]

Set the chart theme.

Parameters:

theme (str) – Theme name (e.g., “classic”, “dark”, “midnight”, “neon”, “celestial”)

Return type:

ChartDrawBuilder

Returns:

Self for chaining

with_zodiac_palette(palette)[source]

Set the zodiac ring color palette.

Parameters:

palette (str | bool) – Can be: - True: Use theme’s default colorful palette - str: Specific palette name (e.g., “grey”, “rainbow”, “viridis”, “elemental”)

Return type:

ChartDrawBuilder

Returns:

Self for chaining

Usage:

# Default (no call): Monochrome using theme’s zodiac ring_color

.with_zodiac_palette(True) # Use theme’s colorful default palette .with_zodiac_palette(“rainbow”) # Use specific rainbow palette .with_zodiac_palette(“grey”) # Monochrome grey palette

without_header()[source]

Disable the chart header band.

When header is disabled, all native info (name, location, datetime, etc.) is displayed in the chart info corner instead.

Return type:

ChartDrawBuilder

Returns:

Self for chaining

without_moon_phase()[source]

Disable moon phase display.

Return type:

ChartDrawBuilder

Returns:

Self for chaining

without_tables()[source]

Disable extended canvas tables.

Return type:

ChartDrawBuilder

Returns:

Self for chaining

Chart Themes (stellium.visualization.themes)

Defines complete visual themes for chart rendering, including colors, line styles, and default zodiac palettes.

class stellium.visualization.themes.ChartTheme(value)[source]

Bases: StrEnum

Available visual themes for chart rendering.

ATLAS = 'atlas'
CELESTIAL = 'celestial'
CIVIDIS = 'cividis'
CLASSIC = 'classic'
DARK = 'dark'
INFERNO = 'inferno'
MAGMA = 'magma'
MIDNIGHT = 'midnight'
NEON = 'neon'
PASTEL = 'pastel'
PLASMA = 'plasma'
SEPIA = 'sepia'
TURBO = 'turbo'
VIRIDIS = 'viridis'
stellium.visualization.themes.get_theme_default_aspect_palette(theme)[source]

Get the default aspect palette for a theme.

Parameters:

theme (ChartTheme) – The theme

Return type:

AspectPalette

Returns:

Default AspectPalette for this theme

stellium.visualization.themes.get_theme_default_palette(theme)[source]

Get the default zodiac palette for a theme.

Parameters:

theme (ChartTheme) – The theme

Return type:

ZodiacPalette

Returns:

Default ZodiacPalette for this theme

stellium.visualization.themes.get_theme_default_planet_palette(theme)[source]

Get the default planet glyph palette for a theme.

Parameters:

theme (ChartTheme) – The theme

Return type:

PlanetGlyphPalette

Returns:

Default PlanetGlyphPalette for this theme

stellium.visualization.themes.get_theme_description(theme)[source]

Get a human-readable description of a theme.

Parameters:

theme (ChartTheme) – The theme to describe

Return type:

str

Returns:

Description string

stellium.visualization.themes.get_theme_style(theme)[source]

Get the complete style configuration for a theme.

Parameters:

theme (ChartTheme) – The theme to use

Return type:

dict[str, Any]

Returns:

Complete style dictionary for ChartRenderer

Zodiac Color Palettes (stellium.visualization.palettes)

Defines color schemes for the zodiac wheel visualization, aspect lines, planet glyphs, and color utilities for adaptive theming.

class stellium.visualization.palettes.AspectPalette(value)[source]

Bases: StrEnum

Available color palettes for aspect lines.

BLUES = 'blues'
CELESTIAL = 'celestial'
CIVIDIS = 'cividis'
CLASSIC = 'classic'
DARK = 'dark'
EARTH_TONES = 'earth_tones'
GREYSCALE = 'greyscale'
INFERNO = 'inferno'
MAGMA = 'magma'
MIDNIGHT = 'midnight'
NEON = 'neon'
PASTEL = 'pastel'
PLASMA = 'plasma'
PURPLES = 'purples'
SEPIA = 'sepia'
TURBO = 'turbo'
VIRIDIS = 'viridis'
class stellium.visualization.palettes.PlanetGlyphPalette(value)[source]

Bases: StrEnum

Available color palettes for planet glyphs.

CHAKRA = 'chakra'
DEFAULT = 'default'
ELEMENT = 'element'
INFERNO = 'inferno'
LUMINARIES = 'luminaries'
PLANET_TYPE = 'planet_type'
PLASMA = 'plasma'
RAINBOW = 'rainbow'
SIGN_RULER = 'sign_ruler'
TURBO = 'turbo'
VIRIDIS = 'viridis'
class stellium.visualization.palettes.ZodiacPalette(value)[source]

Bases: StrEnum

Available color palettes for the zodiac wheel.

CARDINALITY = 'cardinality'
CIVIDIS = 'cividis'
COOLWARM = 'coolwarm'
ELEMENTAL = 'elemental'
ELEMENTAL_DARK = 'elemental_dark'
ELEMENTAL_MIDNIGHT = 'elemental_midnight'
ELEMENTAL_NEON = 'elemental_neon'
ELEMENTAL_SEPIA = 'elemental_sepia'
GREY = 'grey'
INFERNO = 'inferno'
MAGMA = 'magma'
PLASMA = 'plasma'
RAINBOW = 'rainbow'
RAINBOW_CELESTIAL = 'rainbow_celestial'
RAINBOW_DARK = 'rainbow_dark'
RAINBOW_MIDNIGHT = 'rainbow_midnight'
RAINBOW_NEON = 'rainbow_neon'
RAINBOW_SEPIA = 'rainbow_sepia'
SPECTRAL = 'spectral'
TURBO = 'turbo'
VIRIDIS = 'viridis'
stellium.visualization.palettes.adjust_color_for_contrast(original_color, background_color, min_contrast=4.5, max_iterations=20)[source]

Adjust a color to ensure minimum contrast against a background.

This algorithm: 1. Checks if original color already has sufficient contrast 2. If not, determines if background is light or dark 3. Adjusts the color’s lightness/darkness in the opposite direction 4. Iterates until minimum contrast is achieved

Parameters:
  • original_color (str) – The color to adjust (hex)

  • background_color (str) – The background color (hex)

  • min_contrast (float) – Minimum WCAG contrast ratio (default 4.5 = WCAG AA)

  • max_iterations (int) – Maximum adjustment iterations

Return type:

str

Returns:

Adjusted hex color that meets minimum contrast

stellium.visualization.palettes.build_aspect_styles_from_palette(palette)[source]

Build complete aspect styling dict with palette colors + registry line styles.

This merges palette colors with the ASPECT_REGISTRY’s line_width and dash_pattern, ensuring themes only change colors while preserving the registry’s line styling.

Parameters:

palette (AspectPalette | str) – The aspect palette to use for colors

Return type:

dict[str, dict]

Returns:

Dictionary mapping aspect names to style dicts with “color”, “width”, “dash” keys

stellium.visualization.palettes.get_aspect_palette_colors(palette)[source]

Get aspect colors for a given palette.

Returns a dictionary mapping aspect names to hex colors. Includes Conjunction, Sextile, Square, Trine, Opposition, and minor aspects (Semisextile, Semisquare, Sesquisquare, Quincunx). Results are cached in memory for performance.

Parameters:

palette (AspectPalette) – The aspect palette to use

Return type:

dict[str, str]

Returns:

Dictionary mapping aspect names to hex color strings

stellium.visualization.palettes.get_aspect_palette_description(palette)[source]

Get a human-readable description of an aspect palette.

Parameters:

palette (AspectPalette) – The palette to describe

Return type:

str

Returns:

Description string

stellium.visualization.palettes.get_contrast_ratio(color1, color2)[source]

Calculate the contrast ratio between two colors.

Parameters:
  • color1 (str) – First hex color

  • color2 (str) – Second hex color

Return type:

float

Returns:

Contrast ratio (1.0 = no contrast, 21.0 = maximum)

stellium.visualization.palettes.get_luminance(hex_color)[source]

Calculate the relative luminance of a color.

Uses WCAG formula for luminance calculation.

Parameters:

hex_color (str) – Hex color string

Return type:

float

Returns:

Relative luminance (0.0 = black, 1.0 = white)

stellium.visualization.palettes.get_palette_colors(palette)[source]

Get the color list for a zodiac wheel palette.

Returns a list of 12 colors (one per sign, starting with Aries). Results are cached in memory for performance.

Special case: If palette is a string starting with “single_color:”, extracts the hex color and returns 12 copies of it for a monochrome wheel.

Parameters:

palette (ZodiacPalette | str) – The palette to use (ZodiacPalette enum or “single_color:#RRGGBB” string)

Return type:

list[str]

Returns:

List of 12 hex color strings

stellium.visualization.palettes.get_palette_description(palette)[source]

Get a human-readable description of a palette.

Parameters:

palette (ZodiacPalette) – The palette to describe

Return type:

str

Returns:

Description string

stellium.visualization.palettes.get_planet_glyph_color(planet_name, palette, theme_default_color='#222222')[source]

Get the color for a planet glyph based on palette.

Parameters:
  • planet_name (str) – Name of the planet/object

  • palette (PlanetGlyphPalette) – The palette to use

  • theme_default_color (str) – Default color from theme (used for DEFAULT palette)

Return type:

str

Returns:

Hex color string

stellium.visualization.palettes.get_planet_glyph_palette_description(palette)[source]

Get a human-readable description of a planet glyph palette.

Parameters:

palette (PlanetGlyphPalette) – The palette to describe

Return type:

str

Returns:

Description string

stellium.visualization.palettes.get_sign_info_color(sign_index, zodiac_palette, background_color, min_contrast=4.5)[source]

Get an adaptive color for sign glyph in planet info stack.

This function: 1. Gets the sign’s zodiac wheel color from the palette 2. Adjusts it for contrast against the background 3. Returns a color that’s readable while maintaining zodiac color story

Parameters:
  • sign_index (int) – Zodiac sign index (0=Aries, 1=Taurus, etc.)

  • zodiac_palette (ZodiacPalette) – The active zodiac palette

  • background_color (str) – Background color of the planet/info area

  • min_contrast (float) – Minimum WCAG contrast ratio

Return type:

str

Returns:

Hex color for the sign glyph that contrasts with background

stellium.visualization.palettes.hex_to_rgb(hex_color)[source]

Convert hex color to RGB tuple.

Parameters:

hex_color (str) – Hex color string (e.g., “#FF00AA” or “FF00AA”)

Return type:

tuple[int, int, int]

Returns:

RGB tuple (r, g, b) where each value is 0-255

stellium.visualization.palettes.rgb_to_hex(r, g, b)[source]

Convert RGB values to hex color string.

Parameters:
  • r (int) – Red (0-255)

  • g (int) – Green (0-255)

  • b (int) – Blue (0-255)

Return type:

str

Returns:

Hex color string (e.g., “#FF00AA”)

class stellium.visualization.composer.ChartComposer(config)[source]

Bases: object

Main orchestrator for chart visualization.

This is the new public API that replaces draw_chart() and draw_comparison_chart().

compose(chart, to_string=False)[source]

Compose and render a complete chart visualization.

This is a pure, testable pipeline: 1. Calculate layout 2. Create SVG canvas 3. Create layers 4. Render layers 5. Save

Return type:

str

class stellium.visualization.config.ChartVisualizationConfig(wheel, corners, tables, header=None, base_size=600, filename='chart.svg', auto_center=True, auto_grow_wheel=False, min_margin=10)[source]

Bases: object

Complete configuration for chart visualization.

auto_center: bool = True
auto_grow_wheel: bool = False
base_size: int = 600
corners: InfoCornerConfig
filename: str = 'chart.svg'
header: HeaderConfig = None
min_margin: int = 10
tables: TableConfig
wheel: ChartWheelConfig
class stellium.visualization.config.ChartWheelConfig(chart_type, house_systems=None, single_radii=<factory>, biwheel_radii=<factory>, multiwheel_2_radii=<factory>, multiwheel_3_radii=<factory>, multiwheel_4_radii=<factory>, theme=None, zodiac_palette=None, aspect_palette=None, planet_glyph_palette=None, color_sign_info=False, show_degree_ticks=False, show_planet_ticks=True, multiwheel_glyph_sizes=<factory>, multiwheel_info_distances=<factory>, multiwheel_canvas_scales=<factory>)[source]

Bases: object

Configuration for the main chart wheel.

aspect_palette: str | None = None
biwheel_radii: dict[str, float]
chart_type: Literal['single', 'biwheel', 'multiwheel']
color_sign_info: bool = False
get_multiwheel_canvas_scale(chart_count)[source]

Get the canvas scale factor for a multiwheel with N charts.

Parameters:

chart_count (int) – Number of charts (2, 3, or 4)

Return type:

float

Returns:

Scale factor to multiply against base_size

get_multiwheel_radii(chart_count)[source]

Get the appropriate radii config for a multiwheel with N charts.

Parameters:

chart_count (int) – Number of charts (2, 3, or 4)

Return type:

dict[str, float]

Returns:

Radii dictionary for the specified chart count

Raises:

ValueError – If chart_count is not 2, 3, or 4

house_systems: list[str] | str | None = None
multiwheel_2_radii: dict[str, float]
multiwheel_3_radii: dict[str, float]
multiwheel_4_radii: dict[str, float]
multiwheel_canvas_scales: dict[int, float]
multiwheel_glyph_sizes: dict[int, str | None]
multiwheel_info_distances: dict[int, float]
planet_glyph_palette: str | None = None
show_degree_ticks: bool = False
show_planet_ticks: bool = True
single_radii: dict[str, float]
theme: ChartTheme | None = None
zodiac_palette: str | None = None
class stellium.visualization.config.Dimensions(width, height)[source]

Bases: object

Represents width and height.

height: float
width: float
class stellium.visualization.config.HeaderConfig(enabled=True, height=70, name_font_size='18px', name_font_family='Cinzel, serif', details_font_size='12px', line_height=16, coord_precision=4)[source]

Bases: object

Configuration for the chart header band.

coord_precision: int = 4
details_font_size: str = '12px'
enabled: bool = True
height: int = 70
line_height: int = 16
name_font_family: str = 'Cinzel, serif'
name_font_size: str = '18px'
class stellium.visualization.config.InfoCornerConfig(chart_info=True, chart_info_position='top-left', chart_info_fields=None, aspect_counts=False, aspect_counts_position='top-right', element_modality=False, element_modality_position='bottom-left', chart_shape=False, chart_shape_position='bottom-right', moon_phase=True, moon_phase_position='bottom-right', moon_phase_show_label=True, moon_phase_size=None, moon_phase_label_size=None)[source]

Bases: object

Configuration for the 4 info corners (now simplified when header is enabled).

aspect_counts: bool = False
aspect_counts_position: str = 'top-right'
chart_info: bool = True
chart_info_fields: list[str] | None = None
chart_info_position: Literal['top-left', 'top-right', 'bottom-left', 'bottom-right'] = 'top-left'
chart_shape: bool = False
chart_shape_position: str = 'bottom-right'
element_modality: bool = False
element_modality_position: str = 'bottom-left'
moon_phase: bool = True
moon_phase_label_size: str | None = None
moon_phase_position: str | None = 'bottom-right'
moon_phase_show_label: bool = True
moon_phase_size: int | None = None
class stellium.visualization.config.TableConfig(enabled=False, placement='right', show_positions=True, show_houses=True, show_aspectarian=True, aspectarian_mode='cross_chart', aspectarian_detailed=False, padding=10, gap_between_tables=20, gap_between_columns=5, position_col_widths=<factory>, house_col_widths=<factory>, aspectarian_cell_size=24, object_types=None)[source]

Bases: object

Configuration for extended tables.

aspectarian_cell_size: int = 24
aspectarian_detailed: bool = False
aspectarian_mode: str = 'cross_chart'
enabled: bool = False
gap_between_columns: int = 5
gap_between_tables: int = 20
house_col_widths: dict[str, int]
object_types: list[str] | None = None
padding: int = 10
placement: Literal['right', 'left', 'below'] = 'right'
position_col_widths: dict[str, int]
show_aspectarian: bool = True
show_houses: bool = True
show_positions: bool = True

Layer factory for creating visualization layers based on configuration.

This factory encapsulates the logic for creating the right layers in the right order based on the user’s configuration.

class stellium.visualization.layer_factory.IRenderLayer(*args, **kwargs)[source]

Bases: Protocol

Protocol for render layers.

render(renderer, dwg, chart)[source]

Render this layer to the SVG drawing.

Return type:

None

class stellium.visualization.layer_factory.LayerFactory(config)[source]

Bases: object

Creates visualization layers based on configuration.

This encapsulates all the logic for determining which layers to create, in what order, with what settings - based on the ChartVisualizationConfig.

create_layers(chart, layout)[source]

Create all configured layers for this chart.

Layers are returned in render order (bottom to top).

Parameters:
  • chart (CalculatedChart | Comparison | MultiWheel | MultiChart) – The chart to visualize (single, comparison, multiwheel, or multichart)

  • layout (LayoutResult) – The calculated layout (for positioning info)

Return type:

list[IRenderLayer]

Returns:

List of layers ready to render

Concrete Render Layers (stellium.visualization.layers)

These are the concrete implementations of the IRenderLayer protocol. Each class knows how to draw one specific part of a chart, reading its data from the CalculatedChart object.

This package is organized into submodules by layer type: - chart_frame: Header, borders, and ring boundaries - zodiac: Zodiac ring with signs and degrees - houses: House cusp rendering (inner and outer) - angles: Angle markers (ASC, MC, DSC, IC) - planets: Planet glyphs and positions - aspects: Aspect lines and patterns - info_corners: Chart info, aspect counts, element/modality tables

All layers are re-exported from this __init__ for backward compatibility.

class stellium.visualization.layers.AngleLayer(style_override=None, wheel_index=0, chart=None)[source]

Bases: object

Renders the primary chart angles (ASC, MC, DSC, IC).

For multiwheel charts, use wheel_index to specify which chart’s angles to render. Typically only wheel_index=0 (innermost chart) has meaningful angles since transit/progressed charts use the natal houses.

render(renderer, dwg, chart)[source]

Render chart angles.

Handles CalculatedChart, Comparison, MultiWheel, and MultiChart objects. Uses wheel_index to determine which chart’s angles to render.

Return type:

None

class stellium.visualization.layers.AspectCountsLayer(position='top-right', style_override=None)[source]

Bases: object

Renders aspect counts summary in a corner of the chart.

Displays count of each aspect type with glyphs.

DEFAULT_STYLE = {'font_weight': 'normal', 'line_height': 14, 'text_color': '#333333', 'text_size': '11px', 'title_weight': 'bold'}
render(renderer, dwg, chart)[source]

Render aspect counts.

Return type:

None

class stellium.visualization.layers.AspectLayer(style_override=None)[source]

Bases: object

Renders the aspect lines within the chart.

render(renderer, dwg, chart)[source]
Return type:

None

class stellium.visualization.layers.ChartInfoLayer(position='top-left', fields=None, style_override=None, house_systems=None, header_enabled=False)[source]

Bases: object

Renders chart metadata information in a corner of the chart.

When header is disabled: displays all native info (name, location, datetime, etc.) When header is enabled: displays only calculation settings (house system, ephemeris)

CALCULATION_FIELDS = {'ephemeris', 'house_system'}
DEFAULT_STYLE = {'font_weight': 'normal', 'line_height': 14, 'name_size': '16px', 'name_weight': 'bold', 'text_color': '#333333', 'text_size': '11px'}
NATIVE_INFO_FIELDS = {'coordinates', 'datetime', 'location', 'name', 'timezone'}
render(renderer, dwg, chart)[source]

Render chart information.

Return type:

None

class stellium.visualization.layers.ChartShapeLayer(position='bottom-right', style_override=None)[source]

Bases: object

Renders chart shape information in a corner.

Displays the overall pattern/distribution of planets (Bundle, Bowl, Bucket, etc.).

DEFAULT_STYLE = {'font_weight': 'normal', 'line_height': 14, 'text_color': '#333333', 'text_size': '11px', 'title_weight': 'bold'}
render(renderer, dwg, chart)[source]

Render chart shape information.

Return type:

None

class stellium.visualization.layers.ElementModalityTableLayer(position='bottom-left', style_override=None)[source]

Bases: object

Renders element × modality cross-table in a corner.

Shows distribution of planets across elements (Fire, Earth, Air, Water) and modalities (Cardinal, Fixed, Mutable).

DEFAULT_STYLE = {'col_width': 28, 'font_weight': 'normal', 'line_height': 13, 'text_color': '#333333', 'text_size': '10px', 'title_weight': 'bold'}
ELEMENT_SYMBOLS = {'Air': '🜁', 'Earth': '🜃', 'Fire': '🜂', 'Water': '🜄'}
render(renderer, dwg, chart)[source]

Render element/modality cross-table.

Return type:

None

class stellium.visualization.layers.HeaderLayer(height=70, name_font_size='18px', name_font_family="Baskerville, 'Libre Baskerville', Georgia, serif", name_font_weight='600', name_font_style='italic', details_font_size='12px', line_height=16, coord_precision=4)[source]

Bases: object

Renders the chart header band at the top of the canvas.

Displays native information prominently: - Single chart: Name, location, datetime, timezone, coordinates - Biwheel: Two-column layout with chart1 info left-aligned, chart2 right-aligned - Synthesis: “Composite: Name1 & Name2” or “Davison: Name1 & Name2” with midpoint info

The header uses Baskerville italic-semibold for names (elegant, classical feel) and the normal text font for details.

render(renderer, dwg, chart)[source]

Render the header band.

Return type:

None

class stellium.visualization.layers.HouseCuspLayer(house_system_name, style_override=None, wheel_index=0, chart=None)[source]

Bases: object

Renders a single set of house cusps and numbers.

To draw multiple systems, add multiple layers.

For multiwheel charts, use wheel_index to specify which chart ring to render: - wheel_index=0: Chart 1 (innermost) - wheel_index=1: Chart 2 - wheel_index=2: Chart 3 - wheel_index=3: Chart 4 (outermost, just inside zodiac)

The layer will look up radii from the renderer using keys like: - chart{N}_ring_outer, chart{N}_ring_inner (ring bounds) - chart{N}_house_number (number placement)

And fill colors from theme: - chart{N}_fill_1, chart{N}_fill_2 (alternating fills)

render(renderer, dwg, chart)[source]

Render house cusps and house numbers.

Handles CalculatedChart, Comparison, MultiWheel, and MultiChart objects. Uses wheel_index to determine which chart ring to render and which radii to use.

Return type:

None

class stellium.visualization.layers.MoonRangeLayer(arc_color=None, arc_opacity=0.4)[source]

Bases: object

Renders a shaded arc showing the Moon’s possible position range.

Used for unknown birth time charts where the Moon could be anywhere within a ~12-14° range throughout the day.

The arc is drawn as a semi-transparent wedge from the day-start position to the day-end position, with the Moon glyph at the noon position.

render(renderer, dwg, chart)[source]

Render the Moon range arc for unknown time charts.

Return type:

None

class stellium.visualization.layers.MultiWheelAspectLayer(style_override=None)[source]

Bases: object

Renders cross-chart aspect lines for MultiWheel charts.

Only used for 2-chart multiwheels (biwheels), where showing aspects between the two charts is useful and not too cluttered. For 3-4 chart multiwheels, aspect lines are omitted due to visual complexity.

render(renderer, dwg, chart)[source]
Return type:

None

class stellium.visualization.layers.OuterAngleLayer(style_override=None)[source]

Bases: object

Renders the outer wheel angles (for comparison charts).

Deprecated since version Use: AngleLayer(wheel_index=1) instead. This class renders outside the zodiac ring (legacy biwheel style), while the new multiwheel system renders all charts inside the zodiac ring.

render(renderer, dwg, chart)[source]

Render outer wheel angles.

For Comparison/MultiChart, uses chart2 (outer wheel) angles. Uses outer_wheel_angles styling from theme for visual distinction.

Return type:

None

class stellium.visualization.layers.OuterBorderLayer[source]

Bases: object

Renders the outer containment border for comparison/biwheel charts.

render(renderer, dwg, chart)[source]

Render the outer containment border using config radius and style.

Return type:

None

class stellium.visualization.layers.OuterHouseCuspLayer(house_system_name, style_override=None)[source]

Bases: object

Renders house cusps for the OUTER wheel (chart2 in comparisons).

This draws house cusp lines and numbers outside the zodiac ring, with a distinct visual style from the inner chart’s houses.

Deprecated since version Use: HouseCuspLayer(wheel_index=1) instead. This class renders outside the zodiac ring (legacy biwheel style), while the new multiwheel system renders all charts inside the zodiac ring.

render(renderer, dwg, chart)[source]

Render outer house cusps for chart2 (biwheel only).

Handles both CalculatedChart and Comparison/MultiChart objects. For Comparison/MultiChart, uses chart2 (outer wheel). For single charts, this layer doesn’t apply.

Return type:

None

class stellium.visualization.layers.PlanetLayer(planet_set, radius_key='planet_ring', style_override=None, use_outer_wheel_color=False, info_stack_direction='inward', show_info_stack=True, show_position_ticks=False, wheel_index=0, info_mode='full', info_stack_distance=0.8, glyph_size_override=None)[source]

Bases: object

Renders a set of planets at a specific radius.

For multiwheel charts, use wheel_index to specify which chart ring to render: - wheel_index=0: Chart 1 (innermost) - wheel_index=1: Chart 2 - wheel_index=2: Chart 3 - wheel_index=3: Chart 4 (outermost, just inside zodiac)

The info_mode parameter controls how much detail to show: - “full”: Degree + sign glyph + minutes (default for single charts) - “compact”: Degree only, e.g. “15°” (good for multiwheel) - “no_sign”: Degree + minutes, no sign glyph, e.g. “15°32’” - “none”: No info stack, glyph only

render(renderer, dwg, chart)[source]
Return type:

None

class stellium.visualization.layers.RingBoundaryLayer(chart_count=2, style_override=None)[source]

Bases: object

Renders circular boundary lines between chart rings in a multiwheel chart.

Draws circles at the boundaries between: - Each chart ring (chart1_ring_outer, chart2_ring_outer, etc.) - The outermost chart and the zodiac ring (zodiac_ring_inner)

Uses the theme’s ring_border styling for color and width.

render(renderer, dwg, chart)[source]

Render ring boundary circles.

Return type:

None

class stellium.visualization.layers.ZodiacLayer(palette=ZodiacPalette.GREY, style_override=None, show_degree_ticks=False)[source]

Bases: object

Renders the outer Zodiac ring, including glyphs and tick marks.

render(renderer, dwg, chart)[source]
Return type:

None

Base imports and utilities for render layers.

This module provides shared imports and utilities used across all layer modules.

class stellium.visualization.layers.base.Any(*args, **kwargs)[source]

Bases: object

Special type indicating an unconstrained type.

  • Any is compatible with every type.

  • Any assumed to have all methods.

  • All values assumed to be instances of Any.

Note that all the above statements are true from the point of view of static type checkers. At runtime, Any should not be used with instance checks.

class stellium.visualization.layers.base.AspectPalette(value)[source]

Bases: StrEnum

Available color palettes for aspect lines.

BLUES = 'blues'
CELESTIAL = 'celestial'
CIVIDIS = 'cividis'
CLASSIC = 'classic'
DARK = 'dark'
EARTH_TONES = 'earth_tones'
GREYSCALE = 'greyscale'
INFERNO = 'inferno'
MAGMA = 'magma'
MIDNIGHT = 'midnight'
NEON = 'neon'
PASTEL = 'pastel'
PLASMA = 'plasma'
PURPLES = 'purples'
SEPIA = 'sepia'
TURBO = 'turbo'
VIRIDIS = 'viridis'
class stellium.visualization.layers.base.CalculatedChart(datetime, location, positions, house_systems=<factory>, house_placements=<factory>, aspects=(), declination_aspects=(), metadata=<factory>, zodiac_type=None, ayanamsa=None, ayanamsa_value=None, calculation_timestamp=<factory>, chart_tags=())[source]

Bases: object

Complete calculated chart - the final output.

This is what a ChartBuilder returns. It’s immutable and contains everything you need to analyze or visualize the chart.

aspects: tuple[Aspect, ...] = ()
available_components()[source]

List all components and analyzers whose results are available.

Return type:

list[str]

Returns:

Sorted list of component/analyzer names that can be passed to get_component_result().

Examples:

chart.available_components()
# ['Arabic Parts', 'Aspect Patterns', 'Essential Dignities']
ayanamsa: str | None = None
ayanamsa_value: float | None = None
bazi()[source]

Calculate the BaZi (Four Pillars / 八字) chart for this birth data.

Uses the chart’s datetime and location to compute the Chinese Four Pillars, reusing the already-resolved timezone information.

Returns:

A BaZiChart with all four pillars, ready for analysis.

Example:

chart = ChartBuilder.from_details("1994-01-06 11:47", "Palo Alto, CA").calculate()
bazi = chart.bazi()
print(bazi.hanzi)           # Eight characters
print(bazi.day_master)      # Day Master stem
print(bazi.strength())      # Strength analysis
calculation_timestamp: datetime
chart_tags: tuple[str, ...] = ()
datetime: ChartDateTime
declination_aspects: tuple[Aspect, ...] = ()
property default_house_system: str
draconic()[source]

Return a draconic version of this chart.

The draconic chart rotates all positions so that the North Node is at 0° Aries. This is sometimes called the “soul chart” and represents the soul’s orientation before incarnation.

The transformation subtracts the North Node’s longitude from all positions and normalizes to 0-360°.

Return type:

CalculatedChart

Returns:

A new CalculatedChart with all longitudes rotated and “draconic” added to chart_tags.

Example:

draconic = chart.draconic()
print(draconic.get_object("Sun").sign_position)
draconic.draw("draconic.svg").save()
draw(filename='chart.svg')[source]

Start building a chart visualization with fluent API.

This is a convenience method that creates a ChartDrawBuilder for easy, discoverable chart visualization. It provides presets and a fluent interface for customization.

Parameters:

filename (str) – Output filename for the SVG

Return type:

ChartDrawBuilder

Returns:

ChartDrawBuilder instance for chaining

Example:

# Simple preset
chart.draw("my_chart.svg").preset_standard().save()

# Custom configuration
chart.draw("custom.svg").with_theme("dark").with_moon_phase(
    position="top-left", show_label=True
).with_chart_info(position="top-right").save()
draw_dial(filename='dial.svg', degrees=90)[source]

Draw a Uranian/Hamburg school dial chart.

Creates a dial visualization that compresses the zodiac to reveal hard aspects. On a 90° dial, conjunctions, squares, and oppositions all appear as conjunctions.

Parameters:
  • filename (str) – Output filename for the SVG

  • degrees (int) – Dial size - 90 (default), 45, or 360

Return type:

DialDrawBuilder

Returns:

DialDrawBuilder instance for chaining

Example:

# Basic 90° dial
chart.draw_dial("dial.svg").save()

# With theme
chart.draw_dial("dial.svg").with_theme("midnight").save()

# With transits on outer ring
chart.draw_dial("dial.svg")
    .with_outer_ring(transit_chart.get_planets(), label="Transits")
    .save()

# 360° dial with pointer to Sun
chart.draw_dial("dial.svg", degrees=360)
    .with_pointer(pointing_to="Sun")
    .save()
draw_vedic(filename='vedic_chart.svg', style='north_indian', theme='classic', label_style='abbreviation', show_degrees=True, size=500)[source]

Draw a Vedic (Jyotish) chart in North Indian or South Indian style.

Parameters:
  • filename (str) – Output filename for the SVG

  • style (str) – “north_indian” (diamond) or “south_indian” (grid)

  • theme (str) – “classic”, “dark”, or “traditional”

  • label_style (str) – “abbreviation” (Ari, Su), “number” (1, 2), “glyph” (unicode symbols), or “full” (Aries, Sun)

  • show_degrees (bool) – Show degree + minutes for each planet

  • size (int) – SVG width/height in pixels

Return type:

CalculatedChart

Returns:

self (for chaining)

Example:

chart.draw_vedic("north.svg", style="north_indian")
chart.draw_vedic("south.svg", style="south_indian", theme="traditional")
get_accidental_dignities(system=None)[source]

Get accidental dignity calculations.

Parameters:

system (str | None) – Specific house system (“Placidus”). If None returns all systems.

Return type:

dict[str, Any]

Returns:

Dictionary of planetary accidental dignities

get_all_accidental_dignities()[source]

Get all accidental dignities (entire object).

Return type:

dict[str, Any]

get_angles()[source]

Get all chart angles.

Return type:

list[CelestialPosition]

get_component_result(name)[source]

Get the result of a component or analyzer by name.

Works with any component added via .add_component() or analyzer added via .add_analyzer() on ChartBuilder.

Parameters:

name (str) – The component or analyzer name (e.g., “Arabic Parts”, “Essential Dignities”, “Aspect Patterns”). Also accepts the metadata key as an alias (e.g., “dignities”).

Returns:

list of CelestialPosition objects - For metadata-based components/analyzers: the stored dict or list - For dual-storage components: dict with “positions” and “metadata” keys

Return type:

  • For position-based components

Raises:

KeyError – If no component with that name was used. The error message lists all available component names.

Examples:

parts = chart.get_component_result("Arabic Parts")
dignities = chart.get_component_result("Essential Dignities")
patterns = chart.get_component_result("Aspect Patterns")
chart.available_components()
get_contraparallels()[source]

Get all contraparallel aspects (same declination, opposite hemispheres).

Return type:

list[Aspect]

get_declination_aspects(aspect_type=None)[source]

Get declination aspects (Parallel and Contraparallel).

Parameters:

aspect_type (str | None) – Filter to “Parallel” or “Contraparallel”. None returns all declination aspects.

Return type:

list[Aspect]

Returns:

List of declination aspects

get_dignities(system='traditional')[source]

Get essential dignity calculations.

Parameters:

system (str) – “traditional” or “modern”

Return type:

dict[str, Any]

Returns:

Dictionary of planet dignities, or empty dict if not calculated

get_house(object_name, system_name=None)[source]

Helper method to get the house number for a specific object in a specific system.

Return type:

int | None

get_houses(system_name=None)[source]

Get all cusps for a specific system (or default system).

Return type:

HouseCusps

get_mutual_receptions(system='traditional')[source]

Get all mutual receptions in the chart.

Parameters:

system (str) – “traditional” or “modern”

Return type:

list[dict[str, Any]]

Returns:

List of mutual reception dictionaries

get_nodes()[source]

Get all nodes (True Node, South Node, etc.).

Return type:

list[CelestialPosition]

get_object(name)[source]

Get a celestial object by name.

Return type:

CelestialPosition | None

get_parallels()[source]

Get all parallel aspects (same declination, same hemisphere).

Return type:

list[Aspect]

get_planet_accidental(planet_name, system=None)[source]

Get accidental dignity for a specific planet.

Parameters:
  • planet_name (str) – Name of the planet

  • system (str | None) – House system (defaults to default house system if None)

Return type:

dict[str, Any] | None

Returns:

Accidental dignity data, or None if not found

get_planet_dignity(planet_name, system='traditional')[source]

Get dignity calculation for a specific planet.

Parameters:
  • planet_name (str) – Name of the planet (e.g., “Sun”, “Moon”)

  • system (str) – “traditional” or “modern”

Return type:

dict[str, Any] | None

Returns:

Dignity data for the planet, or None if not found

get_planet_total_score(planet_name, essential_system='traditional', accidental_system=None)[source]

Get combined essential + accidental dignity score.

Parameters:
  • planet_name (str) – Name of the planet

  • essential_system (str) – “traditional” or “modern”

  • accidental_system (str | None) – House system name (defaults to default system)

Return type:

dict[str, Any]

Returns:

Dict with essential, accidental, and total scores

get_planets()[source]

Get all planetary objects.

Return type:

list[CelestialPosition]

get_points()[source]

Get all calculated points (Vertex, Lilith, etc.).

Return type:

list[CelestialPosition]

get_strongest_planet(system='traditional')[source]

Find the planet with the highest dignity score (Almuten).

Parameters:

system (str) – “traditional” or “modern”

Return type:

tuple[str, int] | None

Returns:

Tuple of (planet_name, score) or None if no dignities calculated

house_placements: dict[str, dict[str, int]]
house_systems: dict[str, HouseCusps]
location: ChartLocation
lord_of_year(age, point='ASC', house_system=None, rulership='traditional')[source]

Quick access to Lord of the Year.

The Lord of the Year is the planet ruling the profected sign for a given age. It’s one of the most important predictive indicators in Hellenistic astrology.

Parameters:
  • age (int) – Age in completed years

  • point (str) – Point to profect (default “ASC”)

  • house_system (str | None) – House system to use

  • rulership (str) – “traditional” or “modern” rulers

Return type:

str

Returns:

Name of the ruling planet

Example:

print(chart.lord_of_year(30))  # "Saturn"
metadata: dict[str, Any]
positions: tuple[CelestialPosition, ...]
profection(age=None, date=None, point='ASC', include_monthly=True, house_system=None, rulership='traditional')[source]

Calculate profections for this chart.

Profections move a point forward one sign per year. The planet ruling the profected sign becomes the “Lord of the Year.”

Parameters:
  • age (int | None) – Age in completed years (either age OR date required)

  • date (datetime | str | None) – Specific date (datetime or ISO string)

  • point (str) – Point to profect (default “ASC”)

  • include_monthly (bool) – Whether to include monthly profection (date only)

  • house_system (str | None) – House system to use (default: prefers Whole Sign)

  • rulership (str) – “traditional” or “modern” rulers

Returns:

ProfectionResult, or tuple(annual, monthly) if include_monthly

Example:

# By age
result = chart.profection(age=30)
print(f"Lord of Year 30: {result.ruler}")

# By date (gets both annual and monthly)
annual, monthly = chart.profection(date="2025-06-15")
print(f"Annual: {annual.ruler}, Monthly: {monthly.ruler}")
profection_timeline(start_age, end_age, point='ASC', house_system=None, rulership='traditional')[source]

Generate profections for a range of ages.

Parameters:
  • start_age (int) – First age (inclusive)

  • end_age (int) – Last age (inclusive)

  • point (str) – Point to profect (default “ASC”)

  • house_system (str | None) – House system to use

  • rulership (str) – “traditional” or “modern” rulers

Returns:

ProfectionTimeline with all entries

Example:

timeline = chart.profection_timeline(25, 35)
for entry in timeline.entries:
    print(f"Age {entry.units}: {entry.ruler}")
profections(age=None, date=None, points=None, house_system=None, rulership='traditional')[source]

Profect multiple points at once.

Parameters:
  • age (int | None) – Age in completed years (either age OR date required)

  • date (datetime | str | None) – Specific date (datetime or ISO string)

  • points (list[str] | None) – Points to profect (default: ASC, Sun, Moon, MC)

  • house_system (str | None) – House system to use

  • rulership (str) – “traditional” or “modern” rulers

Returns:

MultiProfectionResult with all profections

Example:

result = chart.profections(age=30)
print(result.lords)  # {"ASC": "Saturn", "Sun": "Mars", ...}
sect()[source]

Check which sect this chart is (day or night) (Sun above the horizon).

Return type:

str | None

Returns:

“day” or “night”

to_dict()[source]

Serialize to dictionary for JSON export.

This enables web API integration, storage, etc.

Return type:

dict[str, Any]

to_prompt_text(sections=None, include_extras=True)[source]

Export chart data as clean, human-readable text suitable for LLM prompts.

Produces a structured markdown-style summary of the chart including planetary positions, house cusps, aspects, declination aspects, and any component results (Arabic Parts, midpoints, fixed stars, dignities, antiscia, aspect patterns, etc.).

Parameters:
  • sections (set[str] | None) – Set of section names to include. When None (the default), every section that has data is included. Valid names: “info”, “positions”, “angles”, “houses”, “aspects”, “declination_aspects”, “arabic_parts”, “midpoints”, “fixed_stars”, “dignities”, “antiscia”, “patterns”, “nodes”, “points”, “extras”.

  • include_extras (bool) – When True (default), automatically picks up data from unknown/future components that aren’t handled by a dedicated section. Set to False to only show data from known, explicitly-supported sections.

Return type:

str

Returns:

A multi-line string ready to paste into an LLM prompt.

Example:

text = chart.to_prompt_text()
prompt = f"Interpret this birth chart:\n\n{text}"

# Only specific sections
text = chart.to_prompt_text(sections={"info", "positions", "aspects"})
voc_moon(aspects='traditional')[source]

Check if the Moon is void of course.

The Moon is void of course (VOC) when it will not complete any major Ptolemaic aspect (conjunction, sextile, square, trine, opposition) before leaving its current sign. This is traditionally considered an inauspicious time for beginning new ventures.

Parameters:

aspects (Literal['traditional', 'modern']) – Planet set to consider: - “traditional”: Sun through Saturn (visible planets) - “modern”: Includes Uranus, Neptune, Pluto

Return type:

VOCMoonResult

Returns:

VOCMoonResult with void status and timing details

Example:

voc = chart.voc_moon()
if voc.is_void:
    print(f"Moon is VOC until {voc.void_until}")
    print(f"Will enter {voc.next_sign}")
else:
    print(f"Moon will {voc.next_aspect}")
    print(f"Aspect perfects at {voc.void_until}")
zodiac_type: Any = None
zodiacal_releasing(lot='Part of Fortune')[source]

Get the zodiacal releasing full life timeline for a given lot.

Parameters:

lot (str) – Name of timeline’s lot (defaults to ‘Part of Fortune’)

Return type:

ZRTimeline

Returns:

Zodiacal releasing full timeline object

zr_at_age(age, lot='Part of Fortune')[source]

Get the zodiacal releasing periods for a given age.

Parameters:
  • age (float) – age in years (float) to fetch for

  • lot (str) – Name of lot (defaults to ‘Part of Fortune’)

Return type:

ZRSnapshot

Returns:

Snapshot of ZR for that age’s datetime

zr_at_date(date, lot='Part of Fortune')[source]

Get the zodiacal releasing periods for a given date.

Parameters:
  • date (datetime) – datetime to fetch for

  • lot (str) – Name of lot (defaults to ‘Part of Fortune’)

Return type:

ZRSnapshot

Returns:

Snapshot of ZR for that datetime

class stellium.visualization.layers.base.CelestialPosition(name, object_type, longitude, latitude=0.0, distance=0.0, speed_longitude=0.0, speed_latitude=0.0, speed_distance=0.0, declination=None, right_ascension=None, phase=None)[source]

Bases: object

Immutable representation of a celestial object’s position.

This is the OUTPUT of ephemeris calculations.

declination: float | None = None
property declination_direction: str

‘north’, ‘south’, or ‘none’.

Type:

Direction of declination

distance: float = 0.0
property is_out_of_bounds: bool

Planet is beyond the Sun’s maximum declination (~23°27’).

Out-of-bounds planets are considered to have extra intensity, unpredictability, or unconventional expression in their significations.

The Sun’s declination varies between approximately +23.4367° and -23.4367° (the Tropic of Cancer and Tropic of Capricorn). When a planet exceeds these bounds, it’s “out of bounds.”

Moon, Mercury, Mars, and Venus can go out of bounds. Jupiter, Saturn, and outer planets rarely or never do.

is_retrograde: bool
latitude: float = 0.0
longitude: float
name: str
object_type: ObjectType
phase: PhaseData | None = None
right_ascension: float | None = None
sign: str
sign_degree: float
property sign_position: str

Human-readable sign position (e.g. 15°23’ Aries)

speed_distance: float = 0.0
speed_latitude: float = 0.0
speed_longitude: float = 0.0
class stellium.visualization.layers.base.ChartRenderer(size=600, rotation=0.0, theme=None, style_config=None, zodiac_palette=None, aspect_palette=None, planet_glyph_palette=None, color_sign_info=False)[source]

Bases: object

The core chart drawing canvas and coordinate system.

This class holds the SVG drawing object and provides the geometric utilities for layers to draw themselves. It acts as the “Context” in the strategy pattern.

astrological_to_svg_angle(astro_deg)[source]

Converts astrological degrees (0° = Aries) to SVG degrees (0° = 3 o’clock), appling the chart’s rotation.

Our system: 0° Aries is at 9 o’clock (180° SVG). Rotation is COUNTER-CLOCKWISE.

Return type:

float

polar_to_cartesian(astro_deg, radius)[source]

Converts an astrological degree (0 degrees Aries) and radius to an (x,y) coordinate. Accounts for extended canvas offsets when present.

Return type:

tuple[float, float]

class stellium.visualization.layers.base.HouseCusps(system, cusps)[source]

Bases: object

Immutable house cusp data.

cusps: tuple[float, ...]
get_cusp(house_number)[source]

Get cusp for a specific house (1-12)

Return type:

float

get_description(house_number)[source]

Get human-readable cusp description for a specific house.

Return type:

str

get_sign(house_number)[source]

Get sign name for a specific house (1-12)

Return type:

str

get_sign_degree(house_number)[source]

Get sign name for a specific house (1-12)

Return type:

str

system: str
class stellium.visualization.layers.base.PlanetGlyphPalette(value)[source]

Bases: StrEnum

Available color palettes for planet glyphs.

CHAKRA = 'chakra'
DEFAULT = 'default'
ELEMENT = 'element'
INFERNO = 'inferno'
LUMINARIES = 'luminaries'
PLANET_TYPE = 'planet_type'
PLASMA = 'plasma'
RAINBOW = 'rainbow'
SIGN_RULER = 'sign_ruler'
TURBO = 'turbo'
VIRIDIS = 'viridis'
class stellium.visualization.layers.base.UnknownTimeChart(datetime, location, positions, house_systems=<factory>, house_placements=<factory>, aspects=(), declination_aspects=(), metadata=<factory>, zodiac_type=None, ayanamsa=None, ayanamsa_value=None, calculation_timestamp=<factory>, chart_tags=(), moon_range=None)[source]

Bases: CalculatedChart

A natal chart calculated without known birth time.

Inherits from CalculatedChart for compatibility with all existing code, but has some key differences: - Houses are empty (no house cusps without birth time) - Angles are not calculated (no Asc/MC/Dsc/IC) - Moon is shown as a range rather than single position - Planetary positions are calculated for noon

The chart can still be: - Visualized (without houses, with moon arc) - Exported to JSON - Used for aspect calculations (using noon Moon) - Used for dignity calculations (planets only)

moon_range

MoonRange showing the Moon’s possible positions throughout the day

get_angles()[source]

Angles are not available for unknown time charts.

Return type:

list[CelestialPosition]

get_house(object_name, system_name=None)[source]

Houses are not available for unknown time charts.

Return type:

None

get_houses(system_name=None)[source]

Houses are not available for unknown time charts.

Return type:

None

property is_time_unknown: bool

Always True for this chart type.

moon_range: MoonRange = None
to_dict()[source]

Serialize to dictionary, including moon_range.

Return type:

dict[str, Any]

to_prompt_text(sections=None, include_extras=True)[source]

Export unknown-time chart as prompt text.

Automatically excludes houses and angles sections since they are not available without a birth time.

Return type:

str

class stellium.visualization.layers.base.ZodiacPalette(value)[source]

Bases: StrEnum

Available color palettes for the zodiac wheel.

CARDINALITY = 'cardinality'
CIVIDIS = 'cividis'
COOLWARM = 'coolwarm'
ELEMENTAL = 'elemental'
ELEMENTAL_DARK = 'elemental_dark'
ELEMENTAL_MIDNIGHT = 'elemental_midnight'
ELEMENTAL_NEON = 'elemental_neon'
ELEMENTAL_SEPIA = 'elemental_sepia'
GREY = 'grey'
INFERNO = 'inferno'
MAGMA = 'magma'
PLASMA = 'plasma'
RAINBOW = 'rainbow'
RAINBOW_CELESTIAL = 'rainbow_celestial'
RAINBOW_DARK = 'rainbow_dark'
RAINBOW_MIDNIGHT = 'rainbow_midnight'
RAINBOW_NEON = 'rainbow_neon'
RAINBOW_SEPIA = 'rainbow_sepia'
SPECTRAL = 'spectral'
TURBO = 'turbo'
VIRIDIS = 'viridis'
stellium.visualization.layers.base.adjust_color_for_contrast(original_color, background_color, min_contrast=4.5, max_iterations=20)[source]

Adjust a color to ensure minimum contrast against a background.

This algorithm: 1. Checks if original color already has sufficient contrast 2. If not, determines if background is light or dark 3. Adjusts the color’s lightness/darkness in the opposite direction 4. Iterates until minimum contrast is achieved

Parameters:
  • original_color (str) – The color to adjust (hex)

  • background_color (str) – The background color (hex)

  • min_contrast (float) – Minimum WCAG contrast ratio (default 4.5 = WCAG AA)

  • max_iterations (int) – Maximum adjustment iterations

Return type:

str

Returns:

Adjusted hex color that meets minimum contrast

stellium.visualization.layers.base.embed_svg_glyph(dwg, svg_content, x, y, size, fill_color=None)[source]

Embed an SVG glyph inline as a nested SVG element.

This function parses SVG content and embeds it directly into the drawing as a nested <svg> element with proper positioning and scaling. This approach works across all browsers and SVG viewers, unlike external image references.

Parameters:
  • dwg (Drawing) – The svgwrite Drawing to add the element to

  • svg_content (str) – The raw SVG content string (from get_glyph())

  • x (float) – Center x coordinate for the glyph

  • y (float) – Center y coordinate for the glyph

  • size (float) – Desired size (width and height) in pixels

  • fill_color (str | None) – Optional color to override stroke/fill (for theming)

Return type:

None

stellium.visualization.layers.base.get_aspect_palette_colors(palette)[source]

Get aspect colors for a given palette.

Returns a dictionary mapping aspect names to hex colors. Includes Conjunction, Sextile, Square, Trine, Opposition, and minor aspects (Semisextile, Semisquare, Sesquisquare, Quincunx). Results are cached in memory for performance.

Parameters:

palette (AspectPalette) – The aspect palette to use

Return type:

dict[str, str]

Returns:

Dictionary mapping aspect names to hex color strings

stellium.visualization.layers.base.get_glyph(object_name)[source]

Get the glyph for a celestial object, with registry lookup and fallback.

Parameters:

object_name (str) – Name of the object (e.g., “Sun”, “Mean Apogee”, “ASC”)

Returns:

  • “type”: “unicode” or “svg”

  • ”value”: glyph string (unicode) or SVG content string (for inline embedding)

Return type:

Dictionary with

stellium.visualization.layers.base.get_palette_colors(palette)[source]

Get the color list for a zodiac wheel palette.

Returns a list of 12 colors (one per sign, starting with Aries). Results are cached in memory for performance.

Special case: If palette is a string starting with “single_color:”, extracts the hex color and returns 12 copies of it for a monochrome wheel.

Parameters:

palette (ZodiacPalette | str) – The palette to use (ZodiacPalette enum or “single_color:#RRGGBB” string)

Return type:

list[str]

Returns:

List of 12 hex color strings

stellium.visualization.layers.base.get_planet_glyph_color(planet_name, palette, theme_default_color='#222222')[source]

Get the color for a planet glyph based on palette.

Parameters:
  • planet_name (str) – Name of the planet/object

  • palette (PlanetGlyphPalette) – The palette to use

  • theme_default_color (str) – Default color from theme (used for DEFAULT palette)

Return type:

str

Returns:

Hex color string

stellium.visualization.layers.base.get_sign_info_color(sign_index, zodiac_palette, background_color, min_contrast=4.5)[source]

Get an adaptive color for sign glyph in planet info stack.

This function: 1. Gets the sign’s zodiac wheel color from the palette 2. Adjusts it for contrast against the background 3. Returns a color that’s readable while maintaining zodiac color story

Parameters:
  • sign_index (int) – Zodiac sign index (0=Aries, 1=Taurus, etc.)

  • zodiac_palette (ZodiacPalette) – The active zodiac palette

  • background_color (str) – Background color of the planet/info area

  • min_contrast (float) – Minimum WCAG contrast ratio

Return type:

str

Returns:

Hex color for the sign glyph that contrasts with background

Zodiac ring layer - renders the zodiac wheel with signs and degrees.

class stellium.visualization.layers.zodiac.ZodiacLayer(palette=ZodiacPalette.GREY, style_override=None, show_degree_ticks=False)[source]

Bases: object

Renders the outer Zodiac ring, including glyphs and tick marks.

render(renderer, dwg, chart)[source]
Return type:

None

House cusp layers - inner and outer house cusp rendering.

class stellium.visualization.layers.houses.HouseCuspLayer(house_system_name, style_override=None, wheel_index=0, chart=None)[source]

Bases: object

Renders a single set of house cusps and numbers.

To draw multiple systems, add multiple layers.

For multiwheel charts, use wheel_index to specify which chart ring to render: - wheel_index=0: Chart 1 (innermost) - wheel_index=1: Chart 2 - wheel_index=2: Chart 3 - wheel_index=3: Chart 4 (outermost, just inside zodiac)

The layer will look up radii from the renderer using keys like: - chart{N}_ring_outer, chart{N}_ring_inner (ring bounds) - chart{N}_house_number (number placement)

And fill colors from theme: - chart{N}_fill_1, chart{N}_fill_2 (alternating fills)

render(renderer, dwg, chart)[source]

Render house cusps and house numbers.

Handles CalculatedChart, Comparison, MultiWheel, and MultiChart objects. Uses wheel_index to determine which chart ring to render and which radii to use.

Return type:

None

class stellium.visualization.layers.houses.OuterHouseCuspLayer(house_system_name, style_override=None)[source]

Bases: object

Renders house cusps for the OUTER wheel (chart2 in comparisons).

This draws house cusp lines and numbers outside the zodiac ring, with a distinct visual style from the inner chart’s houses.

Deprecated since version Use: HouseCuspLayer(wheel_index=1) instead. This class renders outside the zodiac ring (legacy biwheel style), while the new multiwheel system renders all charts inside the zodiac ring.

render(renderer, dwg, chart)[source]

Render outer house cusps for chart2 (biwheel only).

Handles both CalculatedChart and Comparison/MultiChart objects. For Comparison/MultiChart, uses chart2 (outer wheel). For single charts, this layer doesn’t apply.

Return type:

None

Planet layers - planet glyphs, positions, and moon range.

class stellium.visualization.layers.planets.MoonRangeLayer(arc_color=None, arc_opacity=0.4)[source]

Bases: object

Renders a shaded arc showing the Moon’s possible position range.

Used for unknown birth time charts where the Moon could be anywhere within a ~12-14° range throughout the day.

The arc is drawn as a semi-transparent wedge from the day-start position to the day-end position, with the Moon glyph at the noon position.

render(renderer, dwg, chart)[source]

Render the Moon range arc for unknown time charts.

Return type:

None

class stellium.visualization.layers.planets.PlanetLayer(planet_set, radius_key='planet_ring', style_override=None, use_outer_wheel_color=False, info_stack_direction='inward', show_info_stack=True, show_position_ticks=False, wheel_index=0, info_mode='full', info_stack_distance=0.8, glyph_size_override=None)[source]

Bases: object

Renders a set of planets at a specific radius.

For multiwheel charts, use wheel_index to specify which chart ring to render: - wheel_index=0: Chart 1 (innermost) - wheel_index=1: Chart 2 - wheel_index=2: Chart 3 - wheel_index=3: Chart 4 (outermost, just inside zodiac)

The info_mode parameter controls how much detail to show: - “full”: Degree + sign glyph + minutes (default for single charts) - “compact”: Degree only, e.g. “15°” (good for multiwheel) - “no_sign”: Degree + minutes, no sign glyph, e.g. “15°32’” - “none”: No info stack, glyph only

render(renderer, dwg, chart)[source]
Return type:

None

Aspect layers - aspect lines, patterns, and multiwheel aspects.

class stellium.visualization.layers.aspects.AspectLayer(style_override=None)[source]

Bases: object

Renders the aspect lines within the chart.

render(renderer, dwg, chart)[source]
Return type:

None

class stellium.visualization.layers.aspects.ChartShapeLayer(position='bottom-right', style_override=None)[source]

Bases: object

Renders chart shape information in a corner.

Displays the overall pattern/distribution of planets (Bundle, Bowl, Bucket, etc.).

DEFAULT_STYLE = {'font_weight': 'normal', 'line_height': 14, 'text_color': '#333333', 'text_size': '11px', 'title_weight': 'bold'}
render(renderer, dwg, chart)[source]

Render chart shape information.

Return type:

None

class stellium.visualization.layers.aspects.MultiWheelAspectLayer(style_override=None)[source]

Bases: object

Renders cross-chart aspect lines for MultiWheel charts.

Only used for 2-chart multiwheels (biwheels), where showing aspects between the two charts is useful and not too cluttered. For 3-4 chart multiwheels, aspect lines are omitted due to visual complexity.

render(renderer, dwg, chart)[source]
Return type:

None

Angle layers - ASC, MC, DSC, IC angle rendering.

class stellium.visualization.layers.angles.AngleLayer(style_override=None, wheel_index=0, chart=None)[source]

Bases: object

Renders the primary chart angles (ASC, MC, DSC, IC).

For multiwheel charts, use wheel_index to specify which chart’s angles to render. Typically only wheel_index=0 (innermost chart) has meaningful angles since transit/progressed charts use the natal houses.

render(renderer, dwg, chart)[source]

Render chart angles.

Handles CalculatedChart, Comparison, MultiWheel, and MultiChart objects. Uses wheel_index to determine which chart’s angles to render.

Return type:

None

class stellium.visualization.layers.angles.OuterAngleLayer(style_override=None)[source]

Bases: object

Renders the outer wheel angles (for comparison charts).

Deprecated since version Use: AngleLayer(wheel_index=1) instead. This class renders outside the zodiac ring (legacy biwheel style), while the new multiwheel system renders all charts inside the zodiac ring.

render(renderer, dwg, chart)[source]

Render outer wheel angles.

For Comparison/MultiChart, uses chart2 (outer wheel) angles. Uses outer_wheel_angles styling from theme for visual distinction.

Return type:

None

Chart frame layers - header, borders, and ring boundaries.

class stellium.visualization.layers.chart_frame.HeaderLayer(height=70, name_font_size='18px', name_font_family="Baskerville, 'Libre Baskerville', Georgia, serif", name_font_weight='600', name_font_style='italic', details_font_size='12px', line_height=16, coord_precision=4)[source]

Bases: object

Renders the chart header band at the top of the canvas.

Displays native information prominently: - Single chart: Name, location, datetime, timezone, coordinates - Biwheel: Two-column layout with chart1 info left-aligned, chart2 right-aligned - Synthesis: “Composite: Name1 & Name2” or “Davison: Name1 & Name2” with midpoint info

The header uses Baskerville italic-semibold for names (elegant, classical feel) and the normal text font for details.

render(renderer, dwg, chart)[source]

Render the header band.

Return type:

None

class stellium.visualization.layers.chart_frame.OuterBorderLayer[source]

Bases: object

Renders the outer containment border for comparison/biwheel charts.

render(renderer, dwg, chart)[source]

Render the outer containment border using config radius and style.

Return type:

None

class stellium.visualization.layers.chart_frame.RingBoundaryLayer(chart_count=2, style_override=None)[source]

Bases: object

Renders circular boundary lines between chart rings in a multiwheel chart.

Draws circles at the boundaries between: - Each chart ring (chart1_ring_outer, chart2_ring_outer, etc.) - The outermost chart and the zodiac ring (zodiac_ring_inner)

Uses the theme’s ring_border styling for color and width.

render(renderer, dwg, chart)[source]

Render ring boundary circles.

Return type:

None

Info corner layers - chart info, aspect counts, element/modality tables.

class stellium.visualization.layers.info_corners.AspectCountsLayer(position='top-right', style_override=None)[source]

Bases: object

Renders aspect counts summary in a corner of the chart.

Displays count of each aspect type with glyphs.

DEFAULT_STYLE = {'font_weight': 'normal', 'line_height': 14, 'text_color': '#333333', 'text_size': '11px', 'title_weight': 'bold'}
render(renderer, dwg, chart)[source]

Render aspect counts.

Return type:

None

class stellium.visualization.layers.info_corners.ChartInfoLayer(position='top-left', fields=None, style_override=None, house_systems=None, header_enabled=False)[source]

Bases: object

Renders chart metadata information in a corner of the chart.

When header is disabled: displays all native info (name, location, datetime, etc.) When header is enabled: displays only calculation settings (house system, ephemeris)

CALCULATION_FIELDS = {'ephemeris', 'house_system'}
DEFAULT_STYLE = {'font_weight': 'normal', 'line_height': 14, 'name_size': '16px', 'name_weight': 'bold', 'text_color': '#333333', 'text_size': '11px'}
NATIVE_INFO_FIELDS = {'coordinates', 'datetime', 'location', 'name', 'timezone'}
render(renderer, dwg, chart)[source]

Render chart information.

Return type:

None

class stellium.visualization.layers.info_corners.ElementModalityTableLayer(position='bottom-left', style_override=None)[source]

Bases: object

Renders element × modality cross-table in a corner.

Shows distribution of planets across elements (Fire, Earth, Air, Water) and modalities (Cardinal, Fixed, Mutable).

DEFAULT_STYLE = {'col_width': 28, 'font_weight': 'normal', 'line_height': 13, 'text_color': '#333333', 'text_size': '10px', 'title_weight': 'bold'}
ELEMENT_SYMBOLS = {'Air': '🜁', 'Earth': '🜃', 'Fire': '🜂', 'Water': '🜄'}
render(renderer, dwg, chart)[source]

Render element/modality cross-table.

Return type:

None

Extended canvas layers for position tables and aspectarian grids.

These layers render tabular data outside the main chart wheel, requiring an extended canvas with additional space.

class stellium.visualization.extended_canvas.AspectarianLayer(x_offset=0, y_offset=0, style_override=None, object_types=None, config=None, detailed=False)[source]

Bases: object

Renders an aspectarian grid (triangle aspect table).

Shows aspects between all planets in a classic triangle grid format. Respects chart theme colors.

Supports two modes: - Simple (default): Large aspect glyphs only - Detailed: Smaller glyphs with orb value and A/S (applying/separating) indicator

DEFAULT_STYLE = {'cell_size': 24, 'font_weight': 'normal', 'grid_color': '#CCCCCC', 'header_color': '#222222', 'header_size': '10px', 'header_weight': 'bold', 'orb_size': '6px', 'show_grid': True, 'text_color': '#333333', 'text_size': '14px', 'text_size_detailed': '11px'}
render(renderer, dwg, chart)[source]

Render aspectarian grid.

Handles CalculatedChart, Comparison, and MultiChart objects. For Comparison/MultiChart, displays cross-chart aspects including Asc and MC from both charts.

Return type:

None

class stellium.visualization.extended_canvas.HouseCuspTableLayer(x_offset=0, y_offset=0, style_override=None, config=None)[source]

Bases: object

Renders a table of house cusps with sign placements.

Shows house number, cusp longitude, sign, and degree in sign. Respects chart theme colors.

DEFAULT_STYLE = {'col_spacing': 55, 'font_weight': 'normal', 'header_color': '#222222', 'header_size': '11px', 'header_weight': 'bold', 'line_height': 16, 'text_color': '#333333', 'text_size': '10px'}
render(renderer, dwg, chart)[source]

Render house cusp table.

Handles CalculatedChart, Comparison, and MultiChart objects. For Comparison/MultiChart, displays two separate side-by-side tables.

Return type:

None

class stellium.visualization.extended_canvas.PositionTableLayer(x_offset=0, y_offset=0, style_override=None, object_types=None, config=None)[source]

Bases: object

Renders a table of planetary positions.

Shows planet name, sign, degree, house, and speed in a tabular format. Respects chart theme colors.

DEFAULT_STYLE = {'col_spacing': 55, 'font_weight': 'normal', 'header_color': '#222222', 'header_size': '11px', 'header_weight': 'bold', 'line_height': 16, 'show_house': True, 'show_speed': True, 'text_color': '#333333', 'text_size': '10px'}
render(renderer, dwg, chart)[source]

Render position table.

Handles CalculatedChart, Comparison, and MultiChart objects. For Comparison/MultiChart, displays two separate side-by-side tables.

Return type:

None

stellium.visualization.extended_canvas.generate_aspectarian_svg(chart, output_path=None, cell_size=None, detailed=False, theme=None, aspect_palette=None, padding=None)[source]

Generate a standalone aspectarian SVG (triangle for single charts, square for comparisons).

Parameters:
  • chart – CalculatedChart or Comparison object

  • output_path (str | None) – Optional path to save SVG file. If None, returns SVG string.

  • cell_size (int | None) – Size of each grid cell in pixels (default: from config, typically 24)

  • detailed (bool) – If True, show orb and applying/separating indicator (default: False)

  • theme (str | None) – Optional theme name (e.g., “dark”, “midnight”)

  • aspect_palette (str | None) – Optional aspect color palette

  • padding (int | None) – Padding around the grid in pixels (default: from config, typically 10)

Return type:

str

Returns:

SVG string if output_path is None, otherwise the output_path

Example

# Generate and save triangle aspectarian chart = ChartBuilder.from_notable(“Albert Einstein”).with_aspects().calculate() generate_aspectarian_svg(chart, “einstein_aspects.svg”)

# Generate square aspectarian for synastry comparison = ComparisonBuilder.synastry(chart1, chart2).calculate() svg_string = generate_aspectarian_svg(comparison)

# With styling generate_aspectarian_svg(

chart, “aspects.svg”, cell_size=28, detailed=True, theme=”midnight”,

)

stellium.visualization.extended_canvas.get_aspectarian_dimensions(chart, cell_size=None, padding=None)[source]

Calculate the dimensions of an aspectarian SVG without rendering.

Uses the ContentMeasurer for consistency with the rest of the visualization system.

Parameters:
  • chart – CalculatedChart or Comparison object

  • cell_size (int | None) – Size of each grid cell in pixels (default: from config, typically 24)

  • padding (int | None) – Padding around the grid in pixels (default: from config, typically 10)

Return type:

tuple[int, int]

Returns:

Tuple of (width, height) in pixels

Moon phase visualization layer.

Renders accurate moon phase symbols showing the illuminated portion with curved terminator lines.

class stellium.visualization.moon_phase.MoonPhaseLayer(position=None, show_label=True, style_override=None)[source]

Bases: object

Renders the moon phase on the chart.

This layer draws an accurate representation of the moon’s current phase using curved terminator lines to show the illuminated portion.

The moon can be positioned in the center or in any corner, and can optionally display the phase name as a text label.

DEFAULT_STYLE = {'border_color': '#2C3E50', 'border_width': 2, 'label_color': '#2C3E50', 'label_offset': 10, 'label_size': '11px', 'lit_color': '#F8F9FA', 'opacity': 0.95, 'shadow_color': '#2C3E50', 'size': 40}
render(renderer, dwg, chart)[source]

Render the moon phase.

Parameters:
  • renderer (ChartRenderer) – ChartRenderer instance

  • dwg (Drawing) – SVG drawing object

  • chart (CalculatedChart) – Calculated chart (or MultiWheel - uses innermost chart)

Return type:

None

stellium.visualization.moon_phase.draw_moon_phase_standalone(phase_frac, phase_angle, filename='moon_phase.svg', size=200, style=None)[source]

Draw a standalone moon phase SVG.

Useful for testing or standalone moon phase displays.

Parameters:
  • phase_frac (float) – Illuminated fraction (0-1)

  • phase_angle (float) – Phase angle in degrees (0-360)

  • filename (str) – Output filename

  • size (int) – SVG size in pixels

  • style (dict[str, Any] | None) – Style overrides

Return type:

str

Returns:

Filename of saved SVG

Example

# Draw a waxing crescent draw_moon_phase_standalone(0.25, 90, “waxing_crescent.svg”)

# Draw a full moon draw_moon_phase_standalone(1.0, 180, “full_moon.svg”)

SVG Grid Layout (stellium.visualization.grid)

Creates subplot-like grid arrangements of multiple charts in a single SVG file. Similar to matplotlib’s subplot functionality but for astrology charts.

stellium.visualization.grid.draw_chart_grid(charts, filename='chart_grid.svg', labels=None, rows=None, cols=None, chart_size=400, padding=30, themes=None, zodiac_palettes=None, aspect_palettes=None, planet_glyph_palettes=None, color_sign_info=False, color_zodiac_glyphs=False, moon_phase=True, background_color='#FAFAFA')[source]

Draw multiple charts in a grid layout in a single SVG file.

This function creates a subplot-like arrangement of multiple charts, perfect for comparing different chart styles, themes, or palettes.

Parameters:
  • charts (list[CalculatedChart]) – List of CalculatedChart objects to render

  • filename (str) – Output SVG filename

  • labels (list[str] | None) – Optional labels for each chart (displayed at bottom)

  • rows (int | None) – Number of rows (auto-calculated if None)

  • cols (int | None) – Number of columns (auto-calculated if None)

  • chart_size (int) – Size of each individual chart in pixels

  • padding (int) – Padding between charts in pixels

  • themes (list[ChartTheme | str] | None) – Optional list of themes (one per chart, or None for default)

  • zodiac_palettes (list[ZodiacPalette | str] | None) – Optional list of zodiac palettes (one per chart)

  • aspect_palettes (list[str] | None) – Optional list of aspect palettes (one per chart)

  • planet_glyph_palettes (list[str] | None) – Optional list of planet glyph palettes (one per chart)

  • color_sign_info (bool) – Apply adaptive sign info coloring to all charts

  • color_zodiac_glyphs (bool) – Apply adaptive zodiac glyph coloring to all charts

  • moon_phase (bool) – Show moon phase in all charts

  • background_color (str) – Background color for the entire grid

Return type:

str

Returns:

The filename of the saved chart grid

Example

>>> from stellium import ChartBuilder
>>> chart = ChartBuilder.from_notable("Albert Einstein").calculate()
>>> # Create grid showing same chart with different themes
>>> draw_chart_grid(
...     charts=[chart] * 4,
...     labels=["Classic", "Dark", "Midnight", "Neon"],
...     themes=["classic", "dark", "midnight", "neon"],
...     rows=2,
...     cols=2,
... )
stellium.visualization.grid.draw_palette_comparison(chart, filename='palette_comparison.svg', palettes=None, theme=ChartTheme.DARK, chart_size=300, color_zodiac_glyphs=True)[source]

Create a grid comparing the same chart with different zodiac palettes.

Parameters:
  • chart (CalculatedChart) – The chart to render with different palettes

  • filename (str) – Output SVG filename

  • palettes (list[ZodiacPalette | str] | None) – List of zodiac palettes to compare (defaults to popular ones)

  • theme (ChartTheme | str) – Base theme to use (default: dark, works well with colorful palettes)

  • chart_size (int) – Size of each chart in pixels

  • color_zodiac_glyphs (bool) – Enable adaptive zodiac glyph coloring

Return type:

str

Returns:

The filename of the saved comparison grid

Example

>>> from stellium import ChartBuilder
>>> chart = ChartBuilder.from_notable("Albert Einstein").calculate()
>>> draw_palette_comparison(chart, "einstein_palettes.svg")
stellium.visualization.grid.draw_theme_comparison(chart, filename='theme_comparison.svg', themes=None, chart_size=300)[source]

Create a grid comparing the same chart rendered in different themes.

Parameters:
  • chart (CalculatedChart) – The chart to render in different themes

  • filename (str) – Output SVG filename

  • themes (list[ChartTheme | str] | None) – List of themes to compare (defaults to all built-in themes)

  • chart_size (int) – Size of each chart in pixels

Return type:

str

Returns:

The filename of the saved comparison grid

Example

>>> from stellium import ChartBuilder
>>> chart = ChartBuilder.from_notable("Albert Einstein").calculate()
>>> draw_theme_comparison(chart, "einstein_themes.svg")

HTML Reference Sheet Generator (stellium.visualization.reference_sheet)

Generates comprehensive HTML reference sheets showing all available themes, palettes, and their colors for easy reference.

stellium.visualization.reference_sheet.generate_aspect_palette_reference(filename='aspect_palettes.html')[source]

Generate a reference sheet showing only aspect palettes.

Return type:

str

stellium.visualization.reference_sheet.generate_html_reference(filename='stellium_color_reference.html', include_zodiac=True, include_aspects=True, include_planet_glyphs=True, include_themes=True)[source]

Generate a comprehensive HTML reference sheet for all Stellium colors and palettes.

Parameters:
  • filename (str) – Output HTML filename

  • include_zodiac (bool) – Include zodiac palettes section

  • include_aspects (bool) – Include aspect palettes section

  • include_planet_glyphs (bool) – Include planet glyph palettes section

  • include_themes (bool) – Include themes section

Return type:

str

Returns:

The filename of the generated HTML file

Example

>>> from stellium.visualization import generate_html_reference
>>> generate_html_reference("colors.html")
stellium.visualization.reference_sheet.generate_theme_reference(filename='themes.html')[source]

Generate a reference sheet showing only themes.

Return type:

str

stellium.visualization.reference_sheet.generate_zodiac_palette_reference(filename='zodiac_palettes.html')[source]

Generate a reference sheet showing only zodiac palettes.

Return type:

str

Graphic Ephemeris Visualization (stellium.visualization.ephemeris)

Renders a graphic ephemeris showing planetary positions over time. The X-axis represents time, the Y-axis represents zodiacal position (optionally compressed to 90° or 45° harmonic).

Example

>>> from stellium.visualization import GraphicEphemeris
>>> eph = GraphicEphemeris(
...     start_date="2025-01-01",
...     end_date="2025-12-31",
...     harmonic=90,
... )
>>> eph.draw("ephemeris_2025.svg")
class stellium.visualization.ephemeris.AspectCrossing(date, harmonic_position, planet1, planet2, aspect_type, longitude1, longitude2, is_transit_to_natal=False)[source]

Bases: object

A point where two planet lines cross (aspect in harmonic view).

aspect_type: str
date: date
harmonic_position: float
is_transit_to_natal: bool = False
longitude1: float
longitude2: float
planet1: str
planet2: str
class stellium.visualization.ephemeris.EphemerisDataPoint(date, julian_day, longitude, speed, harmonic_position)[source]

Bases: object

A single data point for one planet at one time.

date: date
harmonic_position: float
julian_day: float
longitude: float
speed: float
class stellium.visualization.ephemeris.GraphicEphemeris(start_date, end_date, harmonic=90, planets=None, natal_chart=None, natal_planets=None, width=1400, height=900, show_stations=True, show_grid=True, show_legend=True, show_aspects=True, title=None)[source]

Bases: object

Graphic Ephemeris visualization.

Renders planetary positions over time as a graph, with optional harmonic compression (90° or 45°) to show hard aspects as conjunctions.

Example

>>> eph = GraphicEphemeris(
...     start_date="2025-01-01",
...     end_date="2025-12-31",
...     harmonic=90,
... )
>>> eph.draw("ephemeris_2025.svg")

# Include Chiron and North Node >>> eph = GraphicEphemeris( … start_date=”2025-01-01”, … end_date=”2025-12-31”, … planets=EXTENDED_PLANETS, … )

# With natal chart overlay (shows transit-to-natal aspects) >>> from stellium import ChartBuilder >>> natal = ChartBuilder.from_native(my_native).calculate() >>> eph = GraphicEphemeris( … start_date=”2025-01-01”, … end_date=”2025-12-31”, … natal_chart=natal, … ) >>> eph.draw(“transits_2025.svg”)

draw(filename='ephemeris.svg')[source]

Render the graphic ephemeris to SVG.

Parameters:

filename (str | Path) – Output filename for the SVG

Return type:

Drawing

Returns:

The svgwrite.Drawing object (already saved to disk)

property plot_height: int

Height of the plot area (excluding margins).

property plot_width: int

Width of the plot area (excluding margins).

class stellium.visualization.ephemeris.GraphicEphemerisConfig(start_date, end_date, harmonic=90, planets=<factory>, width=1400, height=900, margin_left=90, margin_right=80, margin_top=60, margin_bottom=80, background_color='#FFFFFF', grid_color='#E8E8E8', grid_color_major='#D0D0D0', axis_color='#666666', text_color='#333333', line_width=2.5, show_stations=True, show_grid=True, show_title=True, show_legend=True, show_aspects=True, title=None, days_per_point=1)[source]

Bases: object

Configuration for graphic ephemeris rendering.

axis_color: str = '#666666'
background_color: str = '#FFFFFF'
days_per_point: int = 1
end_date: date
grid_color: str = '#E8E8E8'
grid_color_major: str = '#D0D0D0'
harmonic: Literal[360, 90, 45] = 90
height: int = 900
line_width: float = 2.5
margin_bottom: int = 80
margin_left: int = 90
margin_right: int = 80
margin_top: int = 60
planets: list[str]
show_aspects: bool = True
show_grid: bool = True
show_legend: bool = True
show_stations: bool = True
show_title: bool = True
start_date: date
text_color: str = '#333333'
title: str | None = None
width: int = 1400
class stellium.visualization.ephemeris.NatalPosition(planet, longitude, harmonic_position)[source]

Bases: object

A natal planet position for overlay on the ephemeris.

harmonic_position: float
longitude: float
planet: str
class stellium.visualization.ephemeris.StationPoint(date, julian_day, longitude, harmonic_position, station_type)[source]

Bases: object

A retrograde or direct station point.

date: date
harmonic_position: float
julian_day: float
longitude: float
station_type: Literal['retrograde', 'direct']

Atlas PDF Generation (stellium.visualization.atlas)

Generate multi-page PDF chart atlases.

AtlasBuilder - Fluent API for creating chart atlas PDFs.

Generates multi-page PDFs with one chart per page, like an old-school astrologer’s chart atlas.

class stellium.visualization.atlas.builder.AtlasBuilder[source]

Bases: object

Fluent builder for chart atlas PDF generation.

Generates a PDF document with multiple charts, one per page. Supports both natal wheel charts and Uranian dial charts.

Example:

# Basic atlas from list of natives
AtlasBuilder().add_natives([native1, native2]).save("atlas.pdf")

# With configuration
(AtlasBuilder()
    .add_notable("Albert Einstein")
    .add_notable("Marie Curie")
    .with_chart_type("dial", degrees=90)
    .with_header()
    .with_theme("midnight")
    .save("scientists.pdf"))

# Mixed chart types
(AtlasBuilder()
    .add_entry(native1, chart_type="wheel")
    .add_entry(native2, chart_type="dial", degrees=90)
    .save("mixed.pdf"))

# Entire notables database
AtlasBuilder.from_all_notables().save("complete_atlas.pdf")
add_entry(native, chart_type=None, **chart_options)[source]

Add an entry with custom chart configuration.

Allows per-entry chart type and options, overriding defaults.

Parameters:
  • native (Native) – Native object with birth data

  • chart_type (str | None) – Chart type (“wheel” or “dial”), defaults to builder default

  • **chart_options (Any) – Additional options (e.g., degrees=90 for dial)

Return type:

AtlasBuilder

Returns:

Self for chaining

Example:

builder.add_entry(native1, chart_type="wheel")
builder.add_entry(native2, chart_type="dial", degrees=90)
add_native(native)[source]

Add a single native to the atlas.

Uses the default chart type and options.

Parameters:

native (Native) – Native object with birth data

Return type:

AtlasBuilder

Returns:

Self for chaining

add_natives(natives)[source]

Add multiple natives to the atlas.

Uses the default chart type and options for all.

Parameters:

natives (list[Native]) – List of Native objects

Return type:

AtlasBuilder

Returns:

Self for chaining

add_notable(name)[source]

Add a notable person by name lookup.

Looks up the notable in the registry and adds their chart.

Parameters:

name (str) – Name of the notable (e.g., “Albert Einstein”)

Return type:

AtlasBuilder

Returns:

Self for chaining

Raises:

ValueError – If notable not found in registry

add_notables(names)[source]

Add multiple notables by name lookup.

Parameters:

names (list[str]) – List of notable names

Return type:

AtlasBuilder

Returns:

Self for chaining

classmethod from_all_notables(category=None, sort_by='name')[source]

Create an atlas from all notables in the registry.

Parameters:
  • category (str | None) – Optional category filter (e.g., “scientist”, “artist”)

  • sort_by (str) – Sort order - “name” (alphabetical) or “date” (chronological)

Return type:

AtlasBuilder

Returns:

AtlasBuilder pre-populated with all matching notables

Example:

# All notables
AtlasBuilder.from_all_notables().save("complete_atlas.pdf")

# Only scientists, sorted by birth date
(AtlasBuilder.from_all_notables(category="scientist", sort_by="date")
    .with_title_page("Famous Scientists")
    .save("scientists.pdf"))
render()[source]

Generate the atlas PDF and return as bytes.

Useful for serving directly or further processing.

Return type:

bytes

Returns:

PDF content as bytes

Raises:
save(filename=None)[source]

Generate the atlas PDF and save to file.

Parameters:

filename (str | None) – Output filename (overrides with_filename if provided)

Return type:

str

Returns:

Path to the saved PDF file

Raises:
with_aspect_counts(enabled=True)[source]

Enable or disable aspect counts corner display.

Shows a summary of aspect counts (conjunctions, trines, etc.) in the top-right corner of each chart.

Parameters:

enabled (bool) – True to show aspect counts (default), False to hide

Return type:

AtlasBuilder

Returns:

Self for chaining

with_aspects(enabled=True)[source]

Enable or disable aspect lines on charts.

Parameters:

enabled (bool) – True to show aspects (default), False to hide

Return type:

AtlasBuilder

Returns:

Self for chaining

with_chart_type(chart_type, **options)[source]

Set the default chart type for all entries.

Parameters:
  • chart_type (str) – “wheel” or “dial”

  • **options (Any) – Chart-specific options (e.g., degrees=90 for dial)

Return type:

AtlasBuilder

Returns:

Self for chaining

Example:

builder.with_chart_type("dial", degrees=90)
with_element_modality(enabled=True)[source]

Enable or disable element/modality table corner display.

Shows a cross-table of elements (Fire, Earth, Air, Water) and modalities (Cardinal, Fixed, Mutable) in the bottom-left corner.

Parameters:

enabled (bool) – True to show table (default), False to hide

Return type:

AtlasBuilder

Returns:

Self for chaining

with_extended_tables(enabled=True)[source]

Enable extended tables (positions, aspects, houses).

When enabled, pages are rendered in landscape orientation to accommodate the additional table columns.

Parameters:

enabled (bool) – True to show extended tables

Return type:

AtlasBuilder

Returns:

Self for chaining

with_filename(filename)[source]

Set the output filename.

Parameters:

filename (str) – Output PDF filename

Return type:

AtlasBuilder

Returns:

Self for chaining

with_header(enabled=True)[source]

Enable or disable chart headers.

When enabled, each chart shows native name and birth info.

Parameters:

enabled (bool) – True to show headers, False to hide

Return type:

AtlasBuilder

Returns:

Self for chaining

with_page_size(size)[source]

Set the page size for the PDF.

Parameters:

size (str) – Page size (“letter”, “a4”, “half-letter”)

Return type:

AtlasBuilder

Returns:

Self for chaining

with_theme(theme)[source]

Set the visual theme for all charts.

Parameters:

theme (str) – Theme name (e.g., “classic”, “midnight”, “dark”, “celestial”)

Return type:

AtlasBuilder

Returns:

Self for chaining

with_title_page(title)[source]

Add a title page to the atlas.

Parameters:

title (str) – Title text for the title page

Return type:

AtlasBuilder

Returns:

Self for chaining

Example:

builder.with_title_page("Famous Scientists")
with_zodiac_palette(palette)[source]

Set the zodiac ring color palette.

Parameters:

palette (str) – Palette name (default: “rainbow”)

Return type:

AtlasBuilder

Returns:

Self for chaining

without_aspect_counts()[source]

Disable aspect counts corner display.

Return type:

AtlasBuilder

Returns:

Self for chaining

without_aspects()[source]

Disable aspect lines on charts.

Return type:

AtlasBuilder

Returns:

Self for chaining

without_element_modality()[source]

Disable element/modality table corner display.

Return type:

AtlasBuilder

Returns:

Self for chaining

without_header()[source]

Disable chart headers.

Return type:

AtlasBuilder

Returns:

Self for chaining

without_info_corners()[source]

Disable all info corner displays (aspect counts and element/modality).

Return type:

AtlasBuilder

Returns:

Self for chaining

Configuration dataclasses for atlas generation.

class stellium.visualization.atlas.config.AtlasConfig(entries=<factory>, page_size='letter', theme='classic', zodiac_palette='rainbow', show_header=True, show_aspects=True, show_extended_tables=False, show_aspect_counts=True, show_element_modality=True, title=None, filename='atlas.pdf')[source]

Bases: object

Configuration for atlas PDF generation.

entries

List of AtlasEntry objects (charts to include)

page_size

Paper size (“letter”, “a4”, “half-letter”)

theme

Visual theme for charts (e.g., “classic”, “midnight”)

zodiac_palette

Zodiac ring color palette (default: “rainbow”)

show_header

Whether to show native info header on each chart

show_aspects

Whether to show aspect lines on charts

show_extended_tables

Whether to show extended tables (enables landscape)

show_aspect_counts

Whether to show aspect counts corner

show_element_modality

Whether to show element/modality table corner

title

Optional title for title page (None = no title page)

filename

Output PDF filename

entries: list[AtlasEntry]
filename: str = 'atlas.pdf'
page_size: str = 'letter'
show_aspect_counts: bool = True
show_aspects: bool = True
show_element_modality: bool = True
show_extended_tables: bool = False
show_header: bool = True
theme: str = 'classic'
title: str | None = None
zodiac_palette: str = 'rainbow'
class stellium.visualization.atlas.config.AtlasEntry(native, chart_type='wheel', chart_options=<factory>)[source]

Bases: object

Single entry in the atlas.

Each entry represents one chart page in the PDF.

native

The Native (birth data) for this chart

chart_type

Type of chart to render (“wheel” or “dial”)

chart_options

Additional options for the chart type - For “dial”: {“degrees”: 90} (90, 45, or 360)

chart_options: dict[str, Any]
chart_type: str = 'wheel'
native: Native

AtlasRenderer - Generate atlas PDFs using Typst typesetting.

Renders chart SVGs and compiles them into a multi-page PDF.

class stellium.visualization.atlas.renderer.AtlasRenderer(config)[source]

Bases: object

Renders atlas PDF using Typst typesetting.

Generates chart SVGs for each entry, embeds them in a Typst document, and compiles to PDF.

render()[source]

Render the complete atlas to PDF.

Return type:

bytes

Returns:

PDF as bytes

Dial Charts (stellium.visualization.dial)

Uranian dial chart visualization.

Dial Chart Builder (stellium.visualization.dial.builder)

Fluent API for creating dial chart visualizations.

class stellium.visualization.dial.builder.DialDrawBuilder(chart, filename='dial.svg', dial_degrees=90)[source]

Bases: object

Fluent builder for dial chart visualization.

Provides a convenient API for creating Uranian/Hamburg school dial charts with support for 90°, 45°, and 360° dials.

Example:

# Basic 90° dial
chart.draw_dial("dial.svg").save()

# With theme
chart.draw_dial("dial.svg").with_theme("midnight").save()

# With transits on outer ring
chart.draw_dial("dial.svg", degrees=90)
    .with_outer_ring(transit_chart.get_planets(), label="Transits")
    .save()

# 360° dial with pointer
chart.draw_dial("dial.svg", degrees=360)
    .with_pointer(pointing_to="Sun")
    .save()

# Minimal dial without midpoints
chart.draw_dial("dial.svg")
    .without_midpoints()
    .save()
save(to_string=False)[source]

Build and save the dial chart.

Parameters:

to_string (bool) – If True, return SVG as string instead of saving to file

Return type:

str

Returns:

Filename of saved SVG, or SVG string if to_string=True

with_filename(filename)[source]

Set the output filename.

Return type:

DialDrawBuilder

with_header()[source]

Add a header with chart name and birth details.

The header appears at the top of the dial, showing: - Name (from chart metadata or “Natal Chart”) - Birth date/time, location, and coordinates

Return type:

DialDrawBuilder

with_midpoints(ring='outer_ring_1', notation='full')[source]

Show midpoints on an outer ring.

Midpoints are enabled by default. Use this to customize.

Parameters:
  • ring (str) – Which ring to display on (“outer_ring_1”, “outer_ring_2”, “outer_ring_3”)

  • notation (str) – “full” (☉/☽), “abbreviated”, or “tick”

Return type:

DialDrawBuilder

with_outer_ring(positions, ring='outer_ring_2', label='', color=None)[source]

Add positions to an outer ring.

Use this to display transit planets, solar arc directions, progressed positions, etc.

Parameters:
  • positions (list[CelestialPosition]) – List of CelestialPosition objects to display

  • ring (str) – Which ring to use (“outer_ring_1”, “outer_ring_2”, “outer_ring_3”)

  • label (str) – Optional label for this ring

  • color (str | None) – Optional color override for glyphs

Return type:

DialDrawBuilder

Example:

# Add transit planets
chart.draw_dial("dial.svg")
    .with_outer_ring(transit_chart.get_planets(), label="Transits")
    .save()
with_pointer(pointing_to=0.0)[source]

Add a rotatable pointer (primarily for 360° dials).

Parameters:

pointing_to (float | str) – Where the pointer should point. Can be: - A degree value (0-360 for 360° dial, 0-90 for 90° dial) - A planet name (e.g., “Sun”, “Moon”) - will point to that planet’s position

Return type:

DialDrawBuilder

Example:

# Point to 45°
chart.draw_dial("dial.svg", degrees=360).with_pointer(45).save()

# Point to natal Sun
chart.draw_dial("dial.svg", degrees=360).with_pointer("Sun").save()
with_rotation(rotation)[source]

Set the dial rotation.

By default, 0° is at the top (12 o’clock). This shifts which degree appears at the top.

Parameters:

rotation (float) – Degrees to rotate (positive = clockwise)

Return type:

DialDrawBuilder

with_size(size)[source]

Set the dial size in pixels.

Parameters:

size (int) – Dial diameter in pixels (default: 600)

Return type:

DialDrawBuilder

with_theme(theme)[source]

Set the visual theme.

Uses the same themes as the main chart visualization: - “classic” (default), “dark”, “midnight”, “neon”, “sepia”, “pastel”, “celestial” - Data science themes: “viridis”, “plasma”, “inferno”, “magma”, “cividis”, “turbo”

Parameters:

theme (str | ChartTheme) – Theme name or ChartTheme enum

Return type:

DialDrawBuilder

with_tnos()[source]

Include Trans-Neptunian Objects on the dial.

TNOs are included by default. This method is provided for clarity when you want to explicitly enable them after disabling.

Return type:

DialDrawBuilder

with_uranian()[source]

Include Hamburg/Uranian hypothetical planets on the dial.

Uranian planets are included by default. This method is provided for clarity when you want to explicitly enable them after disabling.

Return type:

DialDrawBuilder

without_cardinal_points()[source]

Hide the cardinal point markers.

Return type:

DialDrawBuilder

without_graduation()[source]

Hide the graduation tick marks and labels.

Return type:

DialDrawBuilder

without_header()[source]

Hide the header.

Return type:

DialDrawBuilder

without_midpoints()[source]

Hide midpoints.

Return type:

DialDrawBuilder

without_modality_wheel()[source]

Hide the inner modality wheel.

Return type:

DialDrawBuilder

without_planets()[source]

Hide the planet glyphs.

Return type:

DialDrawBuilder

without_pointer()[source]

Hide the pointer.

Return type:

DialDrawBuilder

without_tnos()[source]

Exclude Trans-Neptunian Objects from the dial.

By default, TNOs (Eris, Sedna, Makemake, Haumea, Orcus, Quaoar) are included because they are important in Uranian astrology. Use this to show only traditional planets.

Return type:

DialDrawBuilder

without_uranian()[source]

Exclude Hamburg/Uranian hypothetical planets from the dial.

By default, the 8 Uranian planets (Cupido, Hades, Zeus, Kronos, Apollon, Admetos, Vulkanus, Poseidon) are included because they are fundamental to Uranian astrology. Use this to exclude them.

Return type:

DialDrawBuilder

Dial Chart Renderer (stellium.visualization.dial.renderer)

Core coordinate system and rendering context for dial charts. Similar to ChartRenderer but with longitude compression for 90°/45°/360° dials.

class stellium.visualization.dial.renderer.DialRenderer(config)[source]

Bases: object

Core renderer for dial chart visualization.

Provides coordinate transformation for compressed dial charts (90°, 45°, or 360°). The dial compresses the zodiac so that hard aspects appear as conjunctions.

For a 90° dial: - 0° Aries, Cancer, Libra, Capricorn all map to 0° on the dial - 0° Taurus, Leo, Scorpio, Aquarius all map to 30° on the dial - 0° Gemini, Virgo, Sagittarius, Pisces all map to 60° on the dial

The coordinate system places 0° at the top (12 o’clock) and progresses clockwise.

compress_longitude(longitude)[source]

Compress 360° zodiac longitude to dial degrees.

Parameters:

longitude (float) – Zodiac longitude (0-360°)

Return type:

float

Returns:

Compressed dial position (0 to dial_degrees)

Examples

For 90° dial: - 0° (Aries) → 0° - 90° (Cancer) → 0° - 180° (Libra) → 0° - 270° (Capricorn) → 0° - 45° (mid-Taurus) → 45° - 135° (mid-Leo) → 45°

create_drawing()[source]

Create the SVG drawing with background.

Return type:

Drawing

Returns:

svgwrite.Drawing ready for layers to render into

dial_to_svg_angle(dial_deg)[source]

Convert dial degrees to SVG angle.

SVG coordinate system: - 0° = 3 o’clock (right) - 90° = 6 o’clock (bottom) - Angles increase clockwise

Dial coordinate system: - 0° = 12 o’clock (top) - Angles increase clockwise - Rotation shifts where 0° appears

Parameters:

dial_deg (float) – Position on the dial (0 to dial_degrees)

Return type:

float

Returns:

SVG angle in degrees (0-360)

draw_arc(dwg, start_deg, end_deg, radius, **kwargs)[source]

Draw an arc on the dial.

Parameters:
  • dwg (Drawing) – SVG drawing

  • start_deg (float) – Start position in dial degrees

  • end_deg (float) – End position in dial degrees

  • radius (float) – Radius of the arc

  • **kwargs (Any) – Additional SVG path attributes (stroke, fill, etc.)

Return type:

Path

Returns:

SVG path element

draw_circle(dwg, radius, **kwargs)[source]

Draw a circle centered on the dial.

Parameters:
  • dwg (Drawing) – SVG drawing

  • radius (float) – Circle radius in pixels

  • **kwargs (Any) – Additional SVG circle attributes

Return type:

Circle

Returns:

SVG circle element

draw_line_radial(dwg, dial_deg, inner_radius, outer_radius, **kwargs)[source]

Draw a radial line from inner to outer radius at a given dial degree.

Parameters:
  • dwg (Drawing) – SVG drawing

  • dial_deg (float) – Position in dial degrees

  • inner_radius (float) – Inner radius in pixels

  • outer_radius (float) – Outer radius in pixels

  • **kwargs (Any) – Additional SVG line attributes

Return type:

Line

Returns:

SVG line element

get_cardinal_points()[source]

Get the cardinal point positions for this dial size.

For 90° dial: 0°, 22.5°, 45°, 67.5° For 45° dial: 0°, 11.25°, 22.5°, 33.75° For 360° dial: 0°, 90°, 180°, 270°

Return type:

list[float]

Returns:

List of dial degrees for cardinal points

get_modality_sectors()[source]

Get the modality sector definitions for this dial.

For 90° dial, each 30° sector represents one modality: - 0°-30°: Cardinal (Aries, Cancer, Libra, Capricorn) - 30°-60°: Fixed (Taurus, Leo, Scorpio, Aquarius) - 60°-90°: Mutable (Gemini, Virgo, Sagittarius, Pisces)

Return type:

list[tuple[float, float, str]]

Returns:

List of (start_deg, end_deg, modality_name) tuples

longitude_to_cartesian(longitude, radius)[source]

Convert zodiac longitude directly to cartesian coordinates.

Convenience method that compresses longitude and converts to cartesian in one step.

Parameters:
  • longitude (float) – Zodiac longitude (0-360°)

  • radius (float) – Distance from center in pixels

Return type:

tuple[float, float]

Returns:

Tuple of (x, y) coordinates

polar_to_cartesian(dial_deg, radius)[source]

Convert dial degree and radius to (x, y) cartesian coordinates.

Parameters:
  • dial_deg (float) – Position on the dial (0 to dial_degrees, or any value which will be taken modulo dial_degrees)

  • radius (float) – Distance from center in pixels

Return type:

tuple[float, float]

Returns:

Tuple of (x, y) coordinates

Configuration dataclasses for dial visualization.

Colors and styling are derived from themes (same as main chart) to ensure visual consistency. The DialConfig only stores structural settings; actual colors come from the theme at render time.

class stellium.visualization.dial.config.DialCardinalConfig(show_arrows=True, arrow_width=2.0, show_accents=True, accent_width=4.0)[source]

Bases: object

Configuration for cardinal point markers (structural settings only).

accent_width: float = 4.0
arrow_width: float = 2.0
show_accents: bool = True
show_arrows: bool = True
class stellium.visualization.dial.config.DialConfig(dial_degrees=90, size=600, rotation=0.0, theme=None, filename='dial.svg', radii=<factory>, graduation=<factory>, cardinal=<factory>, modality=<factory>, planet=<factory>, pointer=<factory>, header=<factory>, show_graduation=True, show_cardinal_points=True, show_modality_wheel=True, show_planets=True, show_midpoints=True, show_pointer=False, show_header=False, midpoint_ring='outer_ring_1', midpoint_notation='full', pointer_target=0.0)[source]

Bases: object

Complete configuration for dial visualization.

This is the main config class that contains all dial settings. Colors are derived from the theme at render time for consistency with the main chart visualization system.

cardinal: DialCardinalConfig
dial_degrees: int = 90
filename: str = 'dial.svg'
get_dial_style()[source]

Get dial-specific style settings derived from the theme.

Maps theme colors to dial elements for easy access by layers.

Return type:

DialStyle

get_style()[source]

Get the complete style dictionary from the theme.

Returns theme-derived colors for all dial elements. Falls back to classic theme if no theme specified.

Return type:

dict[str, Any]

graduation: DialGraduationConfig
header: DialHeaderConfig
midpoint_notation: str = 'full'
midpoint_ring: str = 'outer_ring_1'
modality: DialModalityConfig
planet: DialPlanetConfig
pointer: DialPointerConfig
pointer_target: float | str = 0.0
radii: DialRadii
rotation: float = 0.0
show_cardinal_points: bool = True
show_graduation: bool = True
show_header: bool = False
show_midpoints: bool = True
show_modality_wheel: bool = True
show_planets: bool = True
show_pointer: bool = False
size: int = 600
theme: ChartTheme | str | None = None
class stellium.visualization.dial.config.DialGraduationConfig(tick_1_degree=0.3, tick_5_degree=0.5, tick_10_degree=0.7, show_labels=True, label_interval=5, label_font_size='8px', tick_width=0.5)[source]

Bases: object

Configuration for graduation tick marks (structural settings only).

label_font_size: str = '8px'
label_interval: int = 5
show_labels: bool = True
tick_10_degree: float = 0.7
tick_1_degree: float = 0.3
tick_5_degree: float = 0.5
tick_width: float = 0.5
class stellium.visualization.dial.config.DialHeaderConfig(height=60, name_font_size='16px', name_font_family="Baskerville, 'Libre Baskerville', Georgia, serif", name_font_weight='600', name_font_style='italic', details_font_size='11px', line_height=14)[source]

Bases: object

Configuration for dial header (structural settings only).

details_font_size: str = '11px'
height: int = 60
line_height: int = 14
name_font_family: str = "Baskerville, 'Libre Baskerville', Georgia, serif"
name_font_size: str = '16px'
name_font_style: str = 'italic'
name_font_weight: str = '600'
class stellium.visualization.dial.config.DialModalityConfig(sector_line_width=1.0, glyph_font_size='14px')[source]

Bases: object

Configuration for inner modality wheel (structural settings only).

glyph_font_size: str = '14px'
sector_line_width: float = 1.0
class stellium.visualization.dial.config.DialPlanetConfig(glyph_font_size='18px', show_ticks=True, tick_width=1.0, tick_length=8.0, connector_dash='2,2', connector_width=0.5, min_glyph_spacing=15.0)[source]

Bases: object

Configuration for planet display (structural settings only).

connector_dash: str = '2,2'
connector_width: float = 0.5
glyph_font_size: str = '18px'
min_glyph_spacing: float = 15.0
show_ticks: bool = True
tick_length: float = 8.0
tick_width: float = 1.0
class stellium.visualization.dial.config.DialPointerConfig(width=2.0, arrow_size=10.0, show_center_circle=True, center_circle_radius=5.0)[source]

Bases: object

Configuration for 360° dial pointer (structural settings only).

arrow_size: float = 10.0
center_circle_radius: float = 5.0
show_center_circle: bool = True
width: float = 2.0
class stellium.visualization.dial.config.DialRadii(outer_ring_3=0.48, outer_ring_2=0.44, outer_ring_1=0.4, graduation_outer=0.36, graduation_inner=0.32, planet_ring=0.26, modality_outer=0.2, modality_inner=0.08)[source]

Bases: object

Radii configuration for dial chart rings.

All values are multipliers of the base size (0.0 to 0.5). Rings are listed from outermost to innermost.

graduation_inner: float = 0.32
graduation_outer: float = 0.36
modality_inner: float = 0.08
modality_outer: float = 0.2
outer_ring_1: float = 0.4
outer_ring_2: float = 0.44
outer_ring_3: float = 0.48
planet_ring: float = 0.26
to_absolute(size)[source]

Convert relative radii to absolute pixel values.

Return type:

dict[str, float]

class stellium.visualization.dial.config.DialStyle(background_color='#FFFFFF', graduation_ring_color='#EEEEEE', graduation_tick_color='#333333', graduation_label_color='#444444', cardinal_arrow_color='#000000', cardinal_accent_color='#000000', modality_sector_color_1='#F5F5F5', modality_sector_color_2='#FFFFFF', modality_line_color='#CCCCCC', modality_glyph_color='#555555', planet_glyph_color='#222222', planet_tick_color='#666666', planet_connector_color='#999999', pointer_color='#000000', font_family_glyphs='"Symbola", "Noto Sans Symbols", serif', font_family_text='"Arial", "Helvetica", sans-serif')[source]

Bases: object

Dial-specific style settings derived from a theme.

This maps the main chart theme colors to dial-specific elements, providing a convenient interface for layers to access colors.

background_color: str = '#FFFFFF'
cardinal_accent_color: str = '#000000'
cardinal_arrow_color: str = '#000000'
font_family_glyphs: str = '"Symbola", "Noto Sans Symbols", serif'
font_family_text: str = '"Arial", "Helvetica", sans-serif'
classmethod from_theme_style(style)[source]

Create DialStyle from a theme style dictionary.

Maps theme colors to dial elements: - Graduation uses zodiac ring colors - Cardinal points use angle colors - Modality uses house fill colors - Planets use planet colors

Return type:

DialStyle

graduation_label_color: str = '#444444'
graduation_ring_color: str = '#EEEEEE'
graduation_tick_color: str = '#333333'
modality_glyph_color: str = '#555555'
modality_line_color: str = '#CCCCCC'
modality_sector_color_1: str = '#F5F5F5'
modality_sector_color_2: str = '#FFFFFF'
planet_connector_color: str = '#999999'
planet_glyph_color: str = '#222222'
planet_tick_color: str = '#666666'
pointer_color: str = '#000000'

Dial Chart Layers (stellium.visualization.dial.layers)

Concrete layer implementations for dial chart visualization. Each layer draws one specific part of the dial chart.

class stellium.visualization.dial.layers.DialBackgroundLayer[source]

Bases: object

Renders the dial background and outer border.

render(renderer, dwg, chart)[source]

Draw the dial background.

Return type:

None

class stellium.visualization.dial.layers.DialCardinalLayer[source]

Bases: object

Renders the cardinal point markers (arrows and accent marks).

For 90° dial: marks at 0°, 22.5°, 45°, 67.5° These represent the cardinal cross of the zodiac.

render(renderer, dwg, chart)[source]

Draw cardinal point markers.

Return type:

None

class stellium.visualization.dial.layers.DialGraduationLayer[source]

Bases: object

Renders the graduated tick marks and degree labels on the outer ring.

Draws: - Small tick marks every 1° - Medium tick marks every 5° - Labels at configured intervals (default every 5°)

render(renderer, dwg, chart)[source]

Draw graduation tick marks and labels.

Return type:

None

class stellium.visualization.dial.layers.DialHeaderLayer(config=None)[source]

Bases: object

Renders the chart header at the top of the dial canvas.

Displays: - Name (from chart metadata, or “Natal Chart” as fallback) - Birth date/time, location, and coordinates

render(renderer, dwg, chart)[source]

Render the header at the top of the canvas.

Return type:

None

class stellium.visualization.dial.layers.DialMidpointLayer(ring='outer_ring_1', notation='full')[source]

Bases: object

Renders midpoints on an outer ring of the dial.

Midpoints are the halfway points between two planets. Each midpoint has: - A tick mark at its true position - A label/glyph with collision avoidance - A dashed connector line if displaced

render(renderer, dwg, chart)[source]

Draw midpoints on the outer ring with collision detection.

Return type:

None

class stellium.visualization.dial.layers.DialModalityLayer[source]

Bases: object

Renders the inner modality wheel with zodiac glyphs.

The wheel is divided into 3 sectors (Cardinal, Fixed, Mutable), each containing the 4 zodiac signs of that modality.

render(renderer, dwg, chart)[source]

Draw the modality wheel.

Return type:

None

class stellium.visualization.dial.layers.DialOuterRingLayer(positions, ring='outer_ring_2', label='', glyph_color=None)[source]

Bases: object

Generic outer ring layer for additional chart data.

Can display transit planets, solar arc directions, progressions, etc. Each outer ring has: - Border circles defining the ring area - Tick marks at true positions - Glyphs with collision detection - Dashed connector lines when displaced

render(renderer, dwg, chart)[source]

Draw positions on the outer ring with border, ticks, and collision detection.

Return type:

None

class stellium.visualization.dial.layers.DialPlanetLayer(ring='planet_ring', include_tnos=True, include_uranian=True)[source]

Bases: object

Renders planet glyphs on the dial with collision detection.

By default, includes: - All 10 planets (Sun through Pluto) - Trans-Neptunian Objects (Eris, Sedna, Makemake, Haumea, Orcus, Quaoar) - Hamburg/Uranian hypothetical planets (Cupido, Hades, Zeus, Kronos,

Apollon, Admetos, Vulkanus, Poseidon)

Draws: - Tick marks at true (compressed) positions - Planet glyphs with collision avoidance - Dashed connector lines when glyphs are displaced

render(renderer, dwg, chart)[source]

Draw planet glyphs with collision detection.

Return type:

None

class stellium.visualization.dial.layers.DialPointerLayer(pointing_to=0.0)[source]

Bases: object

Renders the rotatable pointer for 360° dials.

The pointer is a double-ended arrow that can point to any degree.

render(renderer, dwg, chart)[source]

Draw the pointer arrow.

Return type:

None

class stellium.visualization.dial.layers.IDialLayer(*args, **kwargs)[source]

Bases: Protocol

Protocol for dial chart layers.

render(renderer, dwg, chart)[source]

Render this layer onto the dial chart.

Return type:

None

stellium.visualization.dial.layers.resolve_dial_collisions(positions, dial_degrees, min_spacing_360=8.0)[source]

Resolve collisions between glyphs on a dial chart.

Uses a spreading algorithm that scales appropriately for the dial size. The min_spacing is specified for a 360° chart and automatically scaled.

Parameters:
  • positions (list[dict]) – List of dicts with “true_deg” and “display_deg” keys

  • dial_degrees (int) – Size of the dial (90, 45, or 360)

  • min_spacing_360 (float) – Minimum spacing in degrees for a 360° chart (default: 8°)

Return type:

list[dict]

Returns:

Updated positions list with adjusted display_deg values

Layout Engine (stellium.visualization.layout)

Planet label collision avoidance and layout measurement.

class stellium.visualization.layout.engine.BoundingBox(position, dimensions)[source]

Bases: object

Represents a positioned element with dimensions.

property bottom: float
dimensions: Dimensions
position: Position
property right: float
class stellium.visualization.layout.engine.ChartElement(*args, **kwargs)[source]

Bases: Protocol

Protocol for measurable chart elements.

measure()[source]

Calculate dimensions without rendering.

Return type:

Dimensions

class stellium.visualization.layout.engine.LayoutEngine(config)[source]

Bases: object

The heart of the system - calculates all positions before rendering.

This is a pure calculation engine - no rendering, no side effects.

calculate_layout(chart)[source]

Calculate complete layout for the chart.

Steps: 1. Measure all enabled elements 2. Calculate table positions and sizes 3. Calculate required canvas size (scaled for multiwheel) 4. Calculate wheel size (possibly grown) 5. Center everything 6. Position info corners (with collision detection) 7. Return complete layout specification

Return type:

LayoutResult

class stellium.visualization.layout.engine.LayoutResult(canvas_dimensions, wheel_position, wheel_size, wheel_radii, corners, tables, wheel_grew, actual_margins, header_enabled=False, header_height=0)[source]

Bases: object

Complete layout specification - everything positioned!

actual_margins: dict[str, float]
canvas_dimensions: Dimensions
corners: dict[str, BoundingBox]
header_enabled: bool = False
header_height: int = 0
tables: dict[str, BoundingBox]
wheel_grew: bool
wheel_position: Position
wheel_radii: dict[str, float]
wheel_size: int
class stellium.visualization.layout.engine.Position(x, y)[source]

Bases: object

Represents x, y coordinates.

x: float
y: float
class stellium.visualization.layout.engine.TableLayoutSpec(total_width, total_height, table_positions, table_dimensions)[source]

Bases: object

Specification for table layout (before final positioning).

This represents the relative layout of tables to each other, which will be finalized once we know the wheel position. It’s the blueprint for the tables.

static empty()[source]

Create an empty layout spec when tables are disabled.

Return type:

TableLayoutSpec

table_dimensions: dict[str, Dimensions]
table_positions: dict[str, Position]
total_height: float
total_width: float

Measures elements before rendering.

class stellium.visualization.layout.measurer.ContentMeasurer[source]

Bases: object

Measures chart elements without rendering them.

This is crucial for calculating layout before creating the SVG.

measure_aspectarian(chart, config)[source]

Measure aspectarian grid dimensions.

Triangle for single charts, square for comparisons/multicharts.

Return type:

Dimensions

measure_corner_element(element_name, chart, config)[source]

Estimate dimensions for corner elements.

These are roughly fixed size, but we can be more precise by counting lines of text, etc.

Return type:

Dimensions

measure_house_table(chart, config)[source]

Measure house cusp table dimensions.

Always 12 rows (houses). For multiple house systems, adds columns (sign + degree) for each system. For comparison/multichart, shows separate tables for each chart.

Return type:

Dimensions

measure_position_table(chart, config)[source]

Measure position table dimensions.

For comparison/multichart with 2 charts, this measures TWO side-by-side tables.

Return type:

Dimensions

Vedic Charts (stellium.visualization.vedic)

North and South Indian chart rendering.

Vedic chart rendering — North Indian (diamond) and South Indian (grid) styles.

class stellium.visualization.vedic.NorthIndianRenderer(size=500, theme='classic', show_degrees=False, label_style='abbreviation')[source]

Bases: object

Render a North Indian style Vedic chart as SVG.

Usage:

renderer = NorthIndianRenderer(size=500, theme="classic")
svg_string = renderer.render(chart)
renderer.render_to_file(chart, "vedic_north.svg")
render(chart)[source]
Return type:

str

render_to_file(chart, path)[source]
Return type:

None

class stellium.visualization.vedic.SouthIndianRenderer(size=500, theme='classic', show_degrees=False, show_house_numbers=True, label_style='abbreviation')[source]

Bases: object

Render a South Indian style Vedic chart as SVG.

Usage:

renderer = SouthIndianRenderer(size=500, theme="classic")
svg_string = renderer.render(chart)

# Or save directly:
renderer.render_to_file(chart, "vedic_south.svg")
render(chart)[source]

Render the chart as an SVG string.

Return type:

str

render_to_file(chart, path)[source]

Render and save to an SVG file.

Return type:

None

North Indian style Vedic chart renderer.

The North Indian chart is a square with an inner diamond (X through it), creating 12 triangular houses. House positions are FIXED:

House 1 = top diamond (ASC — always here) House 4 = left diamond House 7 = bottom diamond House 10 = right diamond Houses 2,3 / 5,6 / 8,9 / 11,12 = side triangles in each quadrant

Signs ROTATE based on the ascendant. The traditional way to indicate which sign is in each house is to write the sign’s ordinal number (1=Aries, 2=Taurus, etc.) at the inner corners of the chart.

class stellium.visualization.vedic.north_indian.NorthIndianRenderer(size=500, theme='classic', show_degrees=False, label_style='abbreviation')[source]

Bases: object

Render a North Indian style Vedic chart as SVG.

Usage:

renderer = NorthIndianRenderer(size=500, theme="classic")
svg_string = renderer.render(chart)
renderer.render_to_file(chart, "vedic_north.svg")
render(chart)[source]
Return type:

str

render_to_file(chart, path)[source]
Return type:

None

South Indian style Vedic chart renderer.

The South Indian chart is a 4×4 grid where the 4 center cells are merged, creating 12 outer cells. Each cell represents a fixed zodiac sign (Aries is always in the same position). Planets are placed in the cell corresponding to their sign.

Grid layout (sign indices 0-11, where 0=Aries):

┌──────┬──────┬──────┬──────┐ │ 11 │ 0 │ 1 │ 2 │ │ Pisc │ Arie │ Taur │ Gemi │ ├──────┼──────┴──────┼──────┤ │ 10 │ │ 3 │ │ Aqua │ (center) │ Canc │ ├──────┤ ├──────┤ │ 9 │ │ 4 │ │ Capr │ │ Leo │ ├──────┼──────┬──────┼──────┤ │ 8 │ 7 │ 6 │ 5 │ │ Sagi │ Scor │ Libr │ Virg │ └──────┴──────┴──────┴──────┘

class stellium.visualization.vedic.south_indian.SouthIndianRenderer(size=500, theme='classic', show_degrees=False, show_house_numbers=True, label_style='abbreviation')[source]

Bases: object

Render a South Indian style Vedic chart as SVG.

Usage:

renderer = SouthIndianRenderer(size=500, theme="classic")
svg_string = renderer.render(chart)

# Or save directly:
renderer.render_to_file(chart, "vedic_south.svg")
render(chart)[source]

Render the chart as an SVG string.

Return type:

str

render_to_file(chart, path)[source]

Render and save to an SVG file.

Return type:

None

class stellium.visualization.vedic.south_indian.VedicPlanetInfo(name, glyph, sign_index, degree, is_retrograde)[source]

Bases: object

Planet data for placement in a Vedic chart cell.

degree: float
glyph: str
is_retrograde: bool
name: str
sign_index: int

Returns (stellium.returns)

Solar, lunar, and planetary return calculations.

ReturnBuilder: Fluent builder for planetary return charts.

A “return” is a chart cast for the moment when a transiting planet returns to its exact natal position. Common returns include: - Solar Return: Sun returns to natal Sun position (~birthday) - Lunar Return: Moon returns to natal Moon position (~monthly) - Saturn Return: Saturn returns to natal Saturn (~age 29, 58)

ReturnBuilder wraps ChartBuilder using composition, delegating all chainable configuration methods while handling the return-specific calculations internally.

class stellium.returns.builder.ReturnBuilder(natal, planet, *, year=None, near_date=None, occurrence=None, location=None)[source]

Bases: object

Fluent builder for planetary return charts.

Uses composition: wraps ChartBuilder rather than inheriting from it. This allows us to: - Lazily calculate the return moment before building the inner chart - Inject return-specific metadata into the final chart - Delegate all chainable methods without tight coupling

Usage:
>>> # Solar Return for 2025
>>> sr = ReturnBuilder.solar(natal_chart, 2025).calculate()
>>>
>>> # Lunar Return near a date
>>> lr = ReturnBuilder.lunar(natal_chart, near_date="2025-03-15").calculate()
>>>
>>> # First Saturn Return
>>> saturn = ReturnBuilder.planetary(natal_chart, "Saturn", occurrence=1).calculate()
>>>
>>> # Relocated Solar Return
>>> sr_relocated = (
...     ReturnBuilder.solar(natal_chart, 2025, location="Tokyo, Japan")
...     .calculate()
... )
add_analyzer(analyzer)[source]

Add a chart analyzer.

Return type:

ReturnBuilder

add_component(component)[source]

Add a calculation component.

Return type:

ReturnBuilder

add_house_system(engine)[source]

Add an additional house system.

Return type:

ReturnBuilder

calculate()[source]

Calculate the return chart.

This: 1. Finds the exact moment of the planetary return 2. Creates a ChartBuilder for that moment 3. Applies any deferred configuration 4. Injects return metadata 5. Returns the calculated chart

Return type:

CalculatedChart

Returns:

CalculatedChart with return metadata in chart.metadata

classmethod lunar(natal, *, near_date=None, occurrence=None, location=None)[source]

Create a Lunar Return builder.

A Lunar Return is the chart cast for when the Moon returns to its exact natal position. This happens approximately every 27.3 days.

Parameters:
Return type:

ReturnBuilder

Returns:

ReturnBuilder configured for Lunar Return

Example

>>> # Lunar Return nearest to March 15, 2025
>>> lr = ReturnBuilder.lunar(natal, near_date="2025-03-15").calculate()
>>>
>>> # The 100th Lunar Return
>>> lr_100 = ReturnBuilder.lunar(natal, occurrence=100).calculate()
classmethod planetary(natal, planet, *, near_date=None, occurrence=None, location=None)[source]

Create a planetary return builder for any planet.

Parameters:
Return type:

ReturnBuilder

Returns:

ReturnBuilder configured for the specified planetary return

Example

>>> # First Saturn Return (~age 29)
>>> sr1 = ReturnBuilder.planetary(natal, "Saturn", occurrence=1).calculate()
>>>
>>> # Jupiter Return nearest to 2025
>>> jr = ReturnBuilder.planetary(
...     natal, "Jupiter", near_date="2025-06-01"
... ).calculate()
classmethod solar(natal, year, *, location=None)[source]

Create a Solar Return builder.

A Solar Return is the chart cast for when the Sun returns to its exact natal position. This happens approximately on your birthday each year (but the exact time varies).

Parameters:
Return type:

ReturnBuilder

Returns:

ReturnBuilder configured for Solar Return

Example

>>> sr_2025 = ReturnBuilder.solar(natal, 2025).calculate()
with_aspects(engine=None)[source]

Set the aspect engine.

Return type:

ReturnBuilder

with_config(config)[source]

Set the calculation configuration.

Return type:

ReturnBuilder

with_ephemeris(engine)[source]

Set the ephemeris engine.

Return type:

ReturnBuilder

with_house_systems(engines)[source]

Set the house system engines.

Return type:

ReturnBuilder

with_orbs(engine=None)[source]

Set the orb engine.

Return type:

ReturnBuilder

class stellium.returns.builder.ReturnInfo(return_jd, return_datetime, natal_longitude, return_number, location)[source]

Bases: object

Internal data about a calculated return moment.

location: ChartLocation
natal_longitude: float
return_datetime: datetime
return_jd: float
return_number: int | None

Utilities (stellium.utils)

Utility functions and helpers.

Caching utilities for expensive operations like Swiss Ephemeris and geocoding.

class stellium.utils.cache.Cache(cache_dir='.cache', max_age_seconds=86400, enabled=True)[source]

Bases: object

File-based cache for expensive operations.

clear(cache_type=None)[source]

Clear cache entries. Returns number of files removed.

Return type:

int

get(cache_type, key)[source]

Get a value from cache if it exists and is not expired.

Return type:

Any | None

get_stats()[source]

Get comprehensive cache statistics.

Returns:

  • total_files: Number of cached files

  • total_size_bytes: Total cache size

  • by_type: Breakdown by cache type

  • hit_rate: Cache hit rate (if tracking enabled)

Return type:

Dictionary with cache statistics including

set(cache_type, key, value)[source]

Store a value in cache.

Return type:

None

size(cache_type=None)[source]

Get cache size information.

Return type:

dict[str, int]

stellium.utils.cache.cache_info()[source]

Get comprehensive cache information.

Return type:

dict[str, Any]

stellium.utils.cache.cache_size(cache_type=None)[source]

Get cache size information.

Return type:

dict[str, int]

stellium.utils.cache.cached(cache_type='general', max_age_seconds=86400, cache_instance=None)[source]

Decorator to cache function results.

Parameters:
  • cache_type (str) – Type of cache (‘ephemeris’, ‘geocoding’, ‘general’)

  • max_age_seconds (int) – Maximum age of cache entries in seconds

  • cache_instance (Cache | None) – Custom cache instance (uses global if None)

stellium.utils.cache.clear_cache(cache_type=None)[source]

Clear cache entries. Returns number of files removed.

Return type:

int

stellium.utils.cache.get_default_cache()[source]

Get the default global cache instance.

Return type:

Cache

Cache utilities for easy access from within stellium package.

stellium.utils.cache_utils.clear_all_cache()[source]

Clear all cache files.

stellium.utils.cache_utils.clear_ephemeris_cache()[source]

Clear only the ephemeris cache.

stellium.utils.cache_utils.clear_geocoding_cache()[source]

Clear only the geocoding cache.

stellium.utils.cache_utils.get_cache_stats()[source]

Get cache statistics as a dictionary.

stellium.utils.cache_utils.print_cache_info()[source]

Print detailed cache information.

Time and Julian Day conversion utilities.

These utilities handle conversion between Python datetime objects and Julian Day numbers, which are used internally by Swiss Ephemeris for all astronomical calculations.

stellium.utils.time.datetime_to_julian_day(datetime_obj)[source]

Convert a Python datetime to Julian Day (UT).

Parameters:

datetime_obj (datetime) – A timezone-aware datetime object. If naive (no timezone), UTC is assumed.

Return type:

float

Returns:

Julian Day number (Universal Time)

Example

>>> from datetime import datetime
>>> import pytz
>>> dt = datetime(2025, 1, 6, 12, 0, 0, tzinfo=pytz.UTC)
>>> jd = datetime_to_julian_day(dt)
>>> print(f"{jd:.6f}")  # ~2460682.0
stellium.utils.time.julian_day_to_datetime(jd, timezone='UTC')[source]

Convert Julian Day to Python datetime.

Parameters:
  • jd (float) – Julian day number (Universal Time)

  • timezone (str) – Target timezone string (default UTC). The datetime is first calculated in UTC, then converted to the target timezone.

Return type:

datetime

Returns:

Timezone-aware datetime object

Example

>>> jd = 2460682.0  # Noon on Jan 6, 2025
>>> dt_obj = julian_day_to_datetime(jd)
>>> print(dt_obj.strftime("%Y-%m-%d %H:%M"))
2025-01-06 12:00
stellium.utils.time.offset_julian_day(jd, days)[source]

Offset a Julian Day by a number of days.

Parameters:
  • jd (float) – Starting Julian Day number

  • days (float) – Number of days to add (can be negative)

Return type:

float

Returns:

New Julian Day number

Example

>>> jd = 2460682.0  # Jan 6, 2025
>>> jd_tomorrow = offset_julian_day(jd, 1.0)
>>> jd_yesterday = offset_julian_day(jd, -1.0)

Chart shape detection utilities.

Identifies the overall pattern/distribution of planets in a chart. Classic chart shapes include: Bundle, Bowl, Bucket, Locomotive, Seesaw, Splay, and Splash.

stellium.utils.chart_shape.detect_chart_shape(chart)[source]

Detect the overall shape/pattern of planets in a chart.

Parameters:

chart (CalculatedChart) – Calculated chart

Return type:

tuple[Literal['Bundle', 'Bowl', 'Bucket', 'Locomotive', 'Seesaw', 'Splay', 'Splash'], dict]

Returns:

Tuple of (shape_name, metadata_dict) where metadata contains additional info like span, leading planet, handle planet, etc.

stellium.utils.chart_shape.get_chart_shape_description(shape, metadata)[source]

Get a human-readable description of a chart shape.

Parameters:
  • shape (Literal['Bundle', 'Bowl', 'Bucket', 'Locomotive', 'Seesaw', 'Splay', 'Splash']) – Chart shape name

  • metadata (dict) – Metadata dict from detect_chart_shape()

Return type:

str

Returns:

Description string

Progression calculation utilities.

Supports multiple progression types, each using a different time key:

  • Secondary (day-for-a-year): 1 day of motion = 1 year of life. The most common type. To find progressions at age 30, look at day 30.

  • Tertiary (day-for-a-lunar-month): 1 day of motion = 1 lunar month (~27.3 days). Faster-moving, useful for timing within a year.

  • Minor (lunar-month-for-a-year): 1 lunar month of motion = 1 year of life. Intermediate rate between secondary and tertiary.

This module provides: - Progressed datetime calculation for all three types - Angle adjustment methods (Solar Arc, Naibod)

stellium.utils.progressions.adjust_angles_by_arc(positions, arc)[source]

Adjust angle positions (ASC, MC, etc.) by a given arc.

Used for Solar Arc and Naibod angle progressions. In these methods, angles are moved forward by the calculated arc rather than using their natural (quotidian) progressed positions.

Parameters:
  • positions (tuple[CelestialPosition, ...]) – Tuple of celestial positions from progressed chart

  • arc (float) – Arc in degrees to add to angle positions

Return type:

tuple[CelestialPosition, ...]

Returns:

New tuple with adjusted angle positions

stellium.utils.progressions.calculate_lunar_arc(natal_moon_longitude, progressed_moon_longitude)[source]

Calculate lunar arc (difference between progressed Moon and natal Moon).

Lunar arc directions move all points at the rate the progressed Moon moves. The Moon moves ~12-13 degrees per year in progressions.

Parameters:
  • natal_moon_longitude (float) – Natal Moon position in degrees

  • progressed_moon_longitude (float) – Progressed Moon position in degrees

Return type:

float

Returns:

Lunar arc in degrees (normalized to 0-360)

Example

>>> arc = calculate_lunar_arc(150.0, 280.0)  # Moon moved ~130°
>>> print(f"Lunar arc: {arc:.2f}°")
stellium.utils.progressions.calculate_naibod_arc(years_elapsed)[source]

Calculate Naibod arc (mean Sun rate: 59’08” per year).

Naibod uses the Sun’s average daily motion to progress angles, rather than the actual progressed Sun position. This gives a consistent, predictable rate of angle progression.

Parameters:

years_elapsed (float) – Years since birth

Return type:

float

Returns:

Naibod arc in degrees

Example

>>> arc = calculate_naibod_arc(30)  # At age 30
>>> print(f"Naibod arc: {arc:.2f}°")  # ~29.57°
stellium.utils.progressions.calculate_planetary_arc(natal_planet_longitude, progressed_planet_longitude)[source]

Calculate arc based on any planet’s motion.

This generic function supports Mars arc, Venus arc, Jupiter arc, etc. Used for custom planetary arcs and chart ruler arcs.

Parameters:
  • natal_planet_longitude (float) – Natal planet position in degrees

  • progressed_planet_longitude (float) – Progressed planet position in degrees

Return type:

float

Returns:

Planetary arc in degrees (normalized to 0-360)

Example

>>> arc = calculate_planetary_arc(45.0, 75.0)  # Planet moved ~30°
>>> print(f"Planet arc: {arc:.2f}°")
stellium.utils.progressions.calculate_progressed_datetime(natal_datetime, target_date, progression_type='secondary')[source]

Calculate progressed datetime using the appropriate time key.

Each progression type maps real elapsed time to symbolic chart time at a different rate:

  • Secondary (day-for-a-year): 1 real year → 1 progressed day. At age 30, look at day 30 after birth.

  • Tertiary (day-for-a-lunar-month): 1 real lunar month → 1 progressed day. Moves ~13.4x faster than secondary.

  • Minor (lunar-month-for-a-year): 1 real year → 1 progressed lunar month. Moves ~27.3x faster than secondary.

Parameters:
  • natal_datetime (datetime) – Birth datetime

  • target_date (datetime) – The date you want to progress TO (e.g., “2025-06-15”)

  • progression_type (Literal['secondary', 'tertiary', 'minor']) – “secondary” (default), “tertiary”, or “minor”

Return type:

datetime

Returns:

The datetime to cast the progressed chart for

Example

>>> from datetime import datetime
>>> birth = datetime(1994, 1, 6, 11, 47)
>>> target = datetime(2024, 1, 6)  # 30th birthday
>>> # Secondary: ~30 days after birth
>>> progressed = calculate_progressed_datetime(birth, target)
>>> # Tertiary: ~401 days after birth (30 years × 13.4 lunar months/year)
>>> progressed = calculate_progressed_datetime(birth, target, "tertiary")
stellium.utils.progressions.calculate_solar_arc(natal_sun_longitude, progressed_sun_longitude)[source]

Calculate solar arc (difference between progressed Sun and natal Sun).

Solar arc is used to progress angles (ASC, MC) at the same rate as the progressed Sun moves.

Parameters:
  • natal_sun_longitude (float) – Natal Sun position in degrees

  • progressed_sun_longitude (float) – Progressed Sun position in degrees

Return type:

float

Returns:

Solar arc in degrees (always positive, 0-360)

Example

>>> arc = calculate_solar_arc(285.5, 315.2)  # Sun moved ~30°
>>> print(f"Solar arc: {arc:.2f}°")
stellium.utils.progressions.calculate_years_elapsed(natal_datetime, target_date)[source]

Calculate years elapsed between natal date and target date.

Parameters:
  • natal_datetime (datetime) – Birth datetime

  • target_date (datetime) – Target date

Return type:

float

Returns:

Years elapsed as a float (e.g., 30.5 for 30 years 6 months)

stellium.utils.progressions.normalize_arc(arc)[source]

Normalize an arc to the range 0-360 degrees.

Parameters:

arc (float) – Arc in degrees (can be negative or > 360)

Return type:

float

Returns:

Normalized arc in 0-360 range

Chart ruler calculation utilities.

The chart ruler is the planet that rules the Ascendant sign.

stellium.utils.chart_ruler.get_chart_ruler(ascendant_sign, system='traditional')[source]

Get the chart ruler based on the Ascendant sign.

The chart ruler is the planet that rules the rising sign. It is considered one of the most important planets in the natal chart.

Parameters:
  • ascendant_sign (str) – The zodiac sign on the Ascendant (e.g., “Leo”, “Scorpio”)

  • system (Literal['traditional', 'modern']) – “traditional” (classical rulerships) or “modern” (includes outer planets)

Return type:

str

Returns:

The name of the chart ruler planet

Example

>>> get_chart_ruler("Leo")
'Sun'
>>> get_chart_ruler("Aquarius", system="modern")
'Uranus'
>>> get_chart_ruler("Aquarius", system="traditional")
'Saturn'
stellium.utils.chart_ruler.get_chart_ruler_from_chart(chart, system='traditional')[source]

Get the chart ruler from a CalculatedChart object.

Parameters:
  • chart – A CalculatedChart instance

  • system (Literal['traditional', 'modern']) – “traditional” or “modern” rulership system

Return type:

tuple[str, str]

Returns:

A tuple of (ruler_planet_name, ascendant_sign)

Example

>>> chart = ChartBuilder.from_notable("Kate Louie").calculate()
>>> ruler, sign = get_chart_ruler_from_chart(chart)
>>> print(f"Chart ruler: {ruler} (ruling {sign} rising)")
Chart ruler: Sun (ruling Leo rising)
stellium.utils.chart_ruler.get_sign_ruler(sign, system='traditional')[source]

Get the planetary ruler of a zodiac sign.

Parameters:
  • sign (str) – The zodiac sign name (e.g., “Aries”, “Leo”)

  • system (Literal['traditional', 'modern']) – “traditional” (classical rulerships) or “modern” (includes outer planets)

Return type:

str

Returns:

The name of the ruling planet

Example

>>> get_sign_ruler("Aries")
'Mars'
>>> get_sign_ruler("Scorpio", system="modern")
'Pluto'
>>> get_sign_ruler("Scorpio", system="traditional")
'Mars'

House calculation utilities.

stellium.utils.houses.find_house_for_longitude(longitude, cusps)[source]

Find which house a longitude falls into.

This function determines which of the 12 houses a given ecliptic longitude occupies, based on the house cusp positions. It correctly handles houses that wrap around the 360°/0° boundary.

Parameters:
  • longitude (float) – Ecliptic longitude in degrees (0-360°)

  • cusps (tuple[float, ...]) – Tuple of 12 house cusp longitudes (in degrees)

Return type:

int

Returns:

House number (1-12)

Example

>>> cusps = (0.0, 30.0, 60.0, 90.0, 120.0, 150.0,
...          180.0, 210.0, 240.0, 270.0, 300.0, 330.0)
>>> find_house_for_longitude(45.0, cusps)
2
>>> find_house_for_longitude(355.0, cusps)  # Wraps around
12

Note

A planet at the exact cusp belongs to the house it’s entering, not the one it’s leaving. The logic uses cusp_start <= longitude < cusp_end.

Utility functions for finding planetary crossings.

A “crossing” is when a planet reaches a specific zodiacal longitude. Used for returns, ingresses, and other timing techniques.

stellium.utils.planetary_crossing.find_nth_return(planet, natal_longitude, birth_jd, n=1)[source]

Find the Nth planetary return after birth.

A “return” is when a transiting planet returns to its natal position.

Parameters:
  • planet (str) – Planet name

  • natal_longitude (float) – Natal longitude of the planet (0-360)

  • birth_jd (float) – Julian Day of birth

  • n (int) – Which return (1 = first, 2 = second, etc.)

Return type:

float

Returns:

Julian Day of the Nth return

Raises:

ValueError – If n < 1 or planet not found

Example

>>> # Find first Saturn return (~age 29)
>>> birth_jd = 2449718.0  # Jan 6, 1994
>>> natal_saturn = 330.5  # Saturn's natal position
>>> sr1 = find_nth_return("Saturn", natal_saturn, birth_jd, n=1)
stellium.utils.planetary_crossing.find_planetary_crossing(planet, target_longitude, start_jd, direction=1, precision=1e-06)[source]

Find the Julian Day when a planet reaches a target longitude.

Uses a two-phase algorithm: 1. Coarse search: Step forward/backward until we bracket the crossing 2. Binary search: Refine to sub-second precision

Parameters:
  • planet (str) – Planet name (must be in CELESTIAL_REGISTRY)

  • target_longitude (float) – Target ecliptic longitude (0-360)

  • start_jd (float) – Julian Day to start searching from

  • direction (int) – 1 for forward in time, -1 for backward

  • precision (float) – Desired precision in Julian Days (default ~0.08 seconds)

Return type:

float

Returns:

Julian Day of the crossing

Raises:

ValueError – If planet not found or crossing not found within bounds

Example

>>> # Find when Sun reaches 15° Capricorn after Jan 1, 2025
>>> from stellium.utils.time import datetime_to_julian_day
>>> from datetime import datetime
>>> start = datetime_to_julian_day(datetime(2025, 1, 1))
>>> jd = find_planetary_crossing("Sun", 285.0, start)  # 285° = 15° Cap
stellium.utils.planetary_crossing.find_return_near_date(planet, natal_longitude, target_jd)[source]

Find the planetary return nearest to a target date.

Searches both forward and backward, returns the closer one. Note: For retrograde planets, this may return a retrograde crossing.

Parameters:
  • planet (str) – Planet name

  • natal_longitude (float) – Natal longitude of the planet (0-360)

  • target_jd (float) – Julian Day to search around

Return type:

float

Returns:

Julian Day of the nearest return

Example

>>> # Find lunar return nearest to March 15, 2025
>>> from stellium.utils.time import datetime_to_julian_day
>>> from datetime import datetime
>>> target = datetime_to_julian_day(datetime(2025, 3, 15))
>>> natal_moon = 105.5
>>> lr = find_return_near_date("Moon", natal_moon, target)

Data (stellium.data)

Data access and notable births registry.

stellium.data.get_notable_registry()[source]

Get the global notable registry instance (lazy import).

stellium.data.get_ephe_dir()[source]

Get the ephemeris directory, initializing if necessary.

This is the main function that should be used throughout the codebase to get the ephemeris path. Respects any override previously set via initialize_ephemeris() or the STELLIUM_EPHE_PATH env var.

Return type:

Path

Returns:

Path to the ephemeris directory currently in use.

stellium.data.get_user_data_dir()[source]

Get the user data directory, creating it if necessary.

Return type:

Path

Returns:

Path to ~/.stellium/

stellium.data.get_user_ephe_dir()[source]

Get the user ephemeris directory, creating it if necessary.

Return type:

Path

Returns:

Path to ~/.stellium/ephe/

stellium.data.has_ephe_file(filename)[source]

Check if a specific ephemeris file exists in the active directory.

Parameters:

filename (str) – Name of the ephemeris file (e.g., “se136199.se1”)

Return type:

bool

Returns:

True if the file exists in the directory currently being used.

stellium.data.initialize_ephemeris(ephe_path=None)[source]

Initialize the ephemeris system.

This function: 1. Resolves which ephemeris directory to use (explicit arg >

STELLIUM_EPHE_PATH env var > default ~/.stellium/ephe/)

  1. For the default location: ensures the directory exists and copies bundled ephemeris files to it (first run only)

  2. Sets the Swiss Ephemeris path via swe.set_ephe_path

When a custom path is supplied the directory is used as-is: Stellium will not create it or copy its bundled files into it. This makes it safe to point at an existing Swiss Ephemeris installation managed by another tool, or at a read-only folder.

If initialize_ephemeris is called a second time with a different path, the ephemeris is re-initialized against the new location.

Parameters:

ephe_path (str | Path | None) – Optional override for the ephemeris directory. Accepts a str or pathlib.Path. If omitted, falls back to the STELLIUM_EPHE_PATH environment variable, then to ~/.stellium/ephe/.

Return type:

Path

Returns:

Path to the ephemeris directory that is now active.

Paths (stellium.data.paths)

Centralized data path management for Stellium.

This module handles: 1. User data directory (~/.stellium/) for ephemeris files and user data 2. Bundled package data (notables, essential ephemeris files) 3. First-run initialization (copying bundled ephemeris to user directory)

The user directory structure:

~/.stellium/ ├── ephe/ # Swiss Ephemeris files (copied from package + user downloads) │ ├── sepl_18.se1 │ ├── semo_18.se1 │ └── … └── cache/ # Future: cache files

stellium.data.paths.get_ephe_dir()[source]

Get the ephemeris directory, initializing if necessary.

This is the main function that should be used throughout the codebase to get the ephemeris path. Respects any override previously set via initialize_ephemeris() or the STELLIUM_EPHE_PATH env var.

Return type:

Path

Returns:

Path to the ephemeris directory currently in use.

stellium.data.paths.get_user_data_dir()[source]

Get the user data directory, creating it if necessary.

Return type:

Path

Returns:

Path to ~/.stellium/

stellium.data.paths.get_user_ephe_dir()[source]

Get the user ephemeris directory, creating it if necessary.

Return type:

Path

Returns:

Path to ~/.stellium/ephe/

stellium.data.paths.has_ephe_file(filename)[source]

Check if a specific ephemeris file exists in the active directory.

Parameters:

filename (str) – Name of the ephemeris file (e.g., “se136199.se1”)

Return type:

bool

Returns:

True if the file exists in the directory currently being used.

stellium.data.paths.initialize_ephemeris(ephe_path=None)[source]

Initialize the ephemeris system.

This function: 1. Resolves which ephemeris directory to use (explicit arg >

STELLIUM_EPHE_PATH env var > default ~/.stellium/ephe/)

  1. For the default location: ensures the directory exists and copies bundled ephemeris files to it (first run only)

  2. Sets the Swiss Ephemeris path via swe.set_ephe_path

When a custom path is supplied the directory is used as-is: Stellium will not create it or copy its bundled files into it. This makes it safe to point at an existing Swiss Ephemeris installation managed by another tool, or at a read-only folder.

If initialize_ephemeris is called a second time with a different path, the ephemeris is re-initialized against the new location.

Parameters:

ephe_path (str | Path | None) – Optional override for the ephemeris directory. Accepts a str or pathlib.Path. If omitted, falls back to the STELLIUM_EPHE_PATH environment variable, then to ~/.stellium/ephe/.

Return type:

Path

Returns:

Path to the ephemeris directory that is now active.

stellium.data.paths.reset_ephe_initialization()[source]

Reset the ephemeris initialization flag.

Useful for testing or if you need to reinitialize against a different directory.

Return type:

None

Notable Registry (stellium.data.registry)

Registry for curated notable births and events.

Provides access to a compendium of famous births and historical events that can be used for examples, testing, and research.

class stellium.data.registry.NotableRegistry[source]

Bases: object

Registry for curated notable births and events.

This provides access to a compendium of famous births and historical events that can be used for examples, testing, and research.

Example

>>> from stellium.data import get_notable_registry
>>> registry = get_notable_registry()
>>> einstein = registry.get_by_name("Albert Einstein")
>>> print(einstein.name, einstein.category)
Albert Einstein scientist
get_all()[source]

Get all notable data entries.

Return type:

list[Notable]

get_births()[source]

Get all birth records.

Return type:

list[Notable]

get_by_category(category)[source]

Get all notables in a category.

Parameters:

category (str) – Category name (scientist, artist, leader, etc.)

Return type:

list[Notable]

Returns:

List of Notable objects in that category

get_by_event_type(event_type)[source]

Get all births or all events.

Parameters:

event_type (str) – “birth” or “event”

Return type:

list[Notable]

Returns:

List of Notable objects of that type

get_by_name(name)[source]

Get notable by name (case-insensitive).

Parameters:

name (str) – Name of person or event

Return type:

Notable | None

Returns:

Notable object or None if not found

get_events()[source]

Get all event records.

Return type:

list[Notable]

search(**filters)[source]

Search with arbitrary filters.

Examples

>>> registry.search(category="scientist")
>>> registry.search(event_type="birth", verified=True)
>>> registry.search(data_quality="AA")
Parameters:

**filters – Keyword arguments matching Notable attributes

Return type:

list[Notable]

Returns:

List of Notable objects matching all filters

stellium.data.registry.get_notable_registry()[source]

Get the global notable registry instance.

Return type:

NotableRegistry

Returns:

The singleton NotableRegistry instance

Example

>>> registry = get_notable_registry()
>>> einstein = registry.get_by_name("Albert Einstein")

Planner (stellium.planner)

PDF planner generation with astrological data.

PlannerBuilder - Fluent API for creating personalized astrological planners.

This module provides a builder pattern for configuring and generating PDF planners with charts, transits, and daily astrological events.

class stellium.planner.builder.PlannerBuilder(native)[source]

Bases: object

Fluent builder for creating personalized astrological planners.

Example

>>> from stellium import Native
>>> from stellium.planner import PlannerBuilder
>>>
>>> native = Native("1990-05-15 14:30", "San Francisco, CA")
>>> planner = (PlannerBuilder.for_native(native)
...     .year(2025)
...     .timezone("America/Los_Angeles")
...     .with_natal_chart()
...     .with_solar_return()
...     .include_natal_transits()
...     .generate("my_planner.pdf"))
binding_margin(inches)[source]

Add extra margin for binding.

Parameters:

inches (float) – Extra margin in inches (added to inner edge)

Return type:

PlannerBuilder

Returns:

Self for chaining

date_range(start, end)[source]

Set a custom date range for the planner.

Parameters:
  • start (date) – Start date

  • end (date) – End date

Return type:

PlannerBuilder

Returns:

Self for chaining

exclude_voc()[source]

Exclude Void of Course Moon periods.

Return type:

PlannerBuilder

classmethod for_native(native)[source]

Start building a planner for a native.

Parameters:

native (Native) – The Native whose planner to create

Return type:

PlannerBuilder

Returns:

PlannerBuilder instance for chaining

generate(output_path=None)[source]

Generate the PDF planner.

Parameters:

output_path (str | None) – Optional file path to save the PDF. If None, only returns bytes.

Return type:

bytes

Returns:

PDF as bytes

include_ingresses(planets=None)[source]

Include planet sign ingresses.

Parameters:

planets (list[str] | None) – Which planets to track. Default (None) includes all.

Return type:

PlannerBuilder

Returns:

Self for chaining

include_moon_phases(enabled=True)[source]

Include Moon phases (new, full, quarters).

Return type:

PlannerBuilder

include_mundane_transits(enabled=True)[source]

Include mundane transits (planet-to-planet in sky).

Return type:

PlannerBuilder

include_natal_transits(planets=None)[source]

Include transits to natal planets.

Parameters:

planets (list[str] | None) – Which transiting planets to include. Default (None) uses outer planets: Jupiter, Saturn, Uranus, Neptune, Pluto

Return type:

PlannerBuilder

Returns:

Self for chaining

include_stations(planets=None)[source]

Include retrograde/direct stations.

Parameters:

planets (list[str] | None) – Which planets to track. Default (None) uses Mercury-Pluto.

Return type:

PlannerBuilder

Returns:

Self for chaining

include_voc(mode='traditional')[source]

Include Void of Course Moon periods.

Parameters:

mode (Literal['traditional', 'modern']) – “traditional” (Sun-Saturn) or “modern” (includes outer planets)

Return type:

PlannerBuilder

Returns:

Self for chaining

location(location)[source]

Set location for angle calculations and planetary hours.

Defaults to the native’s birth location if not specified.

Parameters:

location (str | tuple[float, float]) – City name or (latitude, longitude) tuple

Return type:

PlannerBuilder

Returns:

Self for chaining

page_size(size)[source]

Set page size.

Parameters:

size (Literal['a4', 'a5', 'letter', 'half-letter']) – “a4” (default), “a5”, “letter”, or “half-letter” (alias for a5)

Return type:

PlannerBuilder

Returns:

Self for chaining

timezone(tz)[source]

Set the timezone for transit times.

This is required - transit times will be displayed in this timezone.

Parameters:

tz (str) – Timezone string (e.g., “America/Los_Angeles”, “Europe/London”)

Return type:

PlannerBuilder

Returns:

Self for chaining

week_starts_on(day)[source]

Set the first day of the week for calendar grids.

Parameters:

day (Literal['sunday', 'monday']) – “sunday” (default) or “monday”

Return type:

PlannerBuilder

Returns:

Self for chaining

with_graphic_ephemeris(harmonic=360, enabled=True)[source]

Include graphic ephemeris for the planner period.

Parameters:
  • harmonic (int) – Harmonic compression (360=full zodiac, 90=cardinal, 45=semi-square)

  • enabled (bool) – Whether to include graphic ephemeris

Return type:

PlannerBuilder

Returns:

Self for chaining

with_natal_chart(enabled=True)[source]

Include natal chart wheel in front matter.

Return type:

PlannerBuilder

with_profections(enabled=True)[source]

Include annual profection info (Lord of the Year).

Return type:

PlannerBuilder

with_progressed_chart(enabled=True)[source]

Include secondary progressed chart in front matter.

Return type:

PlannerBuilder

with_solar_return(enabled=True)[source]

Include solar return chart for the planner year.

Return type:

PlannerBuilder

with_zr_timeline(lot='Part of Fortune', enabled=True)[source]

Include Zodiacal Releasing timeline visualization.

Parameters:
  • lot (str) – Which lot to release from (default: “Part of Fortune”)

  • enabled (bool) – Whether to include ZR timeline

Return type:

PlannerBuilder

Returns:

Self for chaining

year(year)[source]

Set the calendar year for the planner.

Parameters:

year (int) – Calendar year (e.g., 2025)

Return type:

PlannerBuilder

Returns:

Self for chaining

class stellium.planner.builder.PlannerConfig(native, timezone, start_date=None, end_date=None, year=None, location=None, include_natal_chart=True, include_progressed_chart=True, include_solar_return=True, include_profections=True, include_zr_timeline=True, zr_lot='Part of Fortune', include_graphic_ephemeris=True, graphic_ephemeris_harmonic=360, natal_transit_planets=None, include_mundane_transits=True, include_moon_phases=True, include_voc=True, voc_mode='traditional', ingress_planets=None, station_planets=None, page_size='a4', binding_margin=0.0, week_starts_on='sunday')[source]

Bases: object

Configuration for planner generation.

binding_margin: float = 0.0
end_date: date | None = None
graphic_ephemeris_harmonic: int = 360
include_graphic_ephemeris: bool = True
include_moon_phases: bool = True
include_mundane_transits: bool = True
include_natal_chart: bool = True
include_profections: bool = True
include_progressed_chart: bool = True
include_solar_return: bool = True
include_voc: bool = True
include_zr_timeline: bool = True
ingress_planets: list[str] | None = None
location: str | tuple[float, float] | None = None
natal_transit_planets: list[str] | None = None
native: Native
page_size: Literal['a4', 'a5', 'letter', 'half-letter'] = 'a4'
start_date: date | None = None
station_planets: list[str] | None = None
timezone: str
voc_mode: Literal['traditional', 'modern'] = 'traditional'
week_starts_on: Literal['sunday', 'monday'] = 'sunday'
year: int | None = None
zr_lot: str = 'Part of Fortune'

PlannerRenderer - Generate beautiful PDF planners using Typst.

This module handles: - Generating front matter (charts, ZR timeline, graphic ephemeris) - Rendering daily pages with events - Typst compilation to PDF

class stellium.planner.renderer.ChartPaths(natal=None, progressed=None, solar_return=None, graphic_ephemeris=None, zr_overview=None, zr_timeline=None, profection_wheel=None, profection_table=None)[source]

Bases: object

Paths to generated chart SVG files.

graphic_ephemeris: str | None = None
natal: str | None = None
profection_table: str | None = None
profection_wheel: str | None = None
progressed: str | None = None
solar_return: str | None = None
zr_overview: str | None = None
zr_timeline: str | None = None
class stellium.planner.renderer.PlannerRenderer(config)[source]

Bases: object

Renders PDF planners using Typst typesetting.

Generates: - Title page with planner info - Front matter section with charts - Month overview pages - Daily pages with transit events

render()[source]

Render the complete planner to PDF.

Return type:

bytes

Returns:

PDF as bytes

DailyEventCollector - Gather astrological events for planner pages.

This module collects and organizes transits, ingresses, stations, Moon phases, and VOC periods for each day of the planner.

class stellium.planner.events.DailyEvent(time, event_type, description, symbol, priority=3)[source]

Bases: object

A single astrological event for display in the planner.

time

Event time in the user’s timezone

event_type

Category of event

description

Human-readable description

symbol

Glyph representation for compact display

priority

Sorting priority (1=highest, 5=lowest)

description: str
event_type: Literal['transit_natal', 'transit_mundane', 'ingress', 'station', 'moon_phase', 'voc_start', 'voc_end', 'eclipse']
priority: int = 3
symbol: str
time: datetime
class stellium.planner.events.DailyEventCollector(natal_chart, start, end, timezone)[source]

Bases: object

Collects all astrological events for a date range.

This class gathers events from various sources (transits, ingresses, stations, Moon phases, VOC periods) and organizes them by date.

Example

>>> collector = DailyEventCollector(
...     natal_chart=chart,
...     start=date(2025, 1, 1),
...     end=date(2025, 12, 31),
...     timezone="America/Los_Angeles"
... )
>>> collector.collect_all()
>>> events = collector.get_events_for_day(date(2025, 1, 15))
collect_all(natal_transits=True, transit_planets=None, ingresses=True, ingress_planets=None, stations=True, station_planets=None, moon_phases=True, voc=True, voc_mode='traditional', eclipses=True)[source]

Collect all configured event types.

Parameters:
  • natal_transits (bool) – Include transits to natal planets

  • transit_planets (list[str] | None) – Which transiting planets

  • ingresses (bool) – Include sign ingresses

  • ingress_planets (list[str] | None) – Which planets for ingresses

  • stations (bool) – Include retrograde/direct stations

  • station_planets (list[str] | None) – Which planets for stations

  • moon_phases (bool) – Include Moon phases

  • voc (bool) – Include VOC periods

  • voc_mode (Literal['traditional', 'modern']) – VOC calculation mode

  • eclipses (bool) – Include eclipses

Return type:

None

collect_eclipses()[source]

Collect solar and lunar eclipses.

Return type:

None

collect_ingresses(planets=None)[source]

Collect planet sign ingresses.

Parameters:

planets (list[str] | None) – Which planets to track (default: all)

Return type:

None

collect_moon_phases()[source]

Collect New Moon, Full Moon, and quarter phases.

Return type:

None

collect_natal_transits(transit_planets=None, aspects=None)[source]

Collect transits from outer planets to natal planets.

Uses longitude crossing search to find when transit planets reach aspect positions to fixed natal planet longitudes.

Parameters:
  • transit_planets (list[str] | None) – Which transiting planets (default: Jupiter-Pluto)

  • aspects (list[int] | None) – Which aspects to include (default: major Ptolemaic)

Return type:

None

collect_stations(planets=None)[source]

Collect retrograde and direct stations.

Parameters:

planets (list[str] | None) – Which planets to track (default: Mercury-Pluto)

Return type:

None

collect_voc_periods(mode='traditional')[source]

Collect Void of Course Moon periods.

Adds both start and end times for each VOC period.

Parameters:

mode (Literal['traditional', 'modern']) – “traditional” (Sun-Saturn) or “modern” (includes outer planets)

Return type:

None

end: date
get_all_events()[source]

Get all collected events, sorted.

Return type:

list[DailyEvent]

get_events_for_day(day)[source]

Get all events for a specific day, sorted by time.

Parameters:

day (date) – The date to get events for

Return type:

list[DailyEvent]

Returns:

List of DailyEvent objects, sorted by time then priority

natal_chart: CalculatedChart
start: date
timezone: str

Electional (stellium.electional)

Electional astrology tools for finding optimal times.

Time window generation and set operations for electional search optimization.

Instead of checking every time point, we pre-compute windows where conditions are true and intersect them. This transforms O(N) point-checks into fast set intersection math.

Example

>>> from stellium.electional.intervals import waxing_windows, intersect_windows
>>> from datetime import datetime
>>>
>>> # Get all waxing moon windows in 2025
>>> windows = waxing_windows(datetime(2025, 1, 1), datetime(2025, 12, 31))
>>> print(f"Found {len(windows)} waxing periods")
class stellium.electional.intervals.TimeWindow(start_jd, end_jd)[source]

Bases: object

A time interval where a condition is true.

TimeWindow stores times as Julian Day numbers, which are UTC-based. This is intentional for astronomical correctness and clean interval math.

Note

The start_datetime and end_datetime properties return naive datetimes in UTC. If you need local time, convert using pytz:

import pytz
local_tz = pytz.timezone("America/Los_Angeles")
local_start = window.start_datetime.replace(tzinfo=pytz.UTC).astimezone(local_tz)

See also

ElectionWindow: User-facing result type that stores local datetimes.

start_jd

Start of window as Julian Day (UTC-based)

end_jd

End of window as Julian Day (UTC-based)

property duration_days: float

Duration of the window in days.

property duration_hours: float

Duration of the window in hours.

property end_datetime: datetime

End as datetime (UTC).

end_jd: float
property start_datetime: datetime

Start as datetime (UTC).

start_jd: float
stellium.electional.intervals.angle_at_longitude_windows(target_longitude, latitude, longitude, angle, start, end, orb=1.0)[source]

Get windows when a chart angle is within orb of a specific longitude.

Since angles rotate with Earth’s rotation (~1° every 4 minutes), the window duration depends on the orb: - 1° orb → ~8 minute window - 3° orb → ~24 minute window

Parameters:
  • target_longitude (float) – Target longitude in degrees (0-360)

  • latitude (float) – Geographic latitude

  • longitude (float) – Geographic longitude (negative = West)

  • angle (str) – Which angle (“ASC”, “MC”, “DSC”, “IC”)

  • start (datetime | float) – Start of search range (datetime or Julian day)

  • end (datetime | float) – End of search range (datetime or Julian day)

  • orb (float) – Maximum orb in degrees (default 1°)

Return type:

list[TimeWindow]

Returns:

List of TimeWindow objects where angle is within orb of target

Example

>>> # Get windows when MC is within 1° of 0° Aries
>>> windows = angle_at_longitude_windows(
...     0.0, 40.7, -74.0, "MC",
...     datetime(2025, 1, 1), datetime(2025, 1, 8), orb=1.0
... )
>>> for w in windows:
...     print(f"{w.start_datetime} - {w.end_datetime}")
stellium.electional.intervals.aspect_exact_windows(object1, object2, aspect_angle, start, end, orb=3.0)[source]

Get windows when two objects are within orb of exact aspect.

For each exact aspect in the range, computes the window where the aspect is within the specified orb. Uses the relative speed of the objects to calculate how long before and after exact the aspect stays within orb.

Parameters:
  • object1 (str) – First object name (e.g., “Moon”)

  • object2 (str) – Second object name (e.g., “Jupiter”)

  • aspect_angle (float) – Target angle (0=conjunction, 60=sextile, 90=square, 120=trine, 180=opposition)

  • start (datetime | float) – Start of search range (datetime or Julian day)

  • end (datetime | float) – End of search range (datetime or Julian day)

  • orb (float) – Maximum orb in degrees (default 3°)

Return type:

list[TimeWindow]

Returns:

List of TimeWindow objects where aspect is within orb

Example

>>> # Get windows when Moon is within 2° of exact trine to Jupiter
>>> windows = aspect_exact_windows("Moon", "Jupiter", 120.0,
...     datetime(2025, 1, 1), datetime(2025, 2, 1), orb=2.0)
>>> for w in windows:
...     print(f"{w.start_datetime} - {w.end_datetime}")
stellium.electional.intervals.direct_windows(planet, start, end)[source]

Get windows when a planet is direct (not retrograde).

Parameters:
  • planet (str) – Planet name (e.g., “Mercury”, “Venus”, “Mars”)

  • start (datetime | float) – Start of search range

  • end (datetime | float) – End of search range

Return type:

list[TimeWindow]

Returns:

List of TimeWindow objects for direct periods

stellium.electional.intervals.intersect_windows(windows_a, windows_b)[source]

Compute intersection of two sorted window lists.

For each overlapping pair, emits the overlap: (max(start_a, start_b), min(end_a, end_b))

Parameters:
  • windows_a (list[TimeWindow]) – First list of windows (must be sorted by start_jd)

  • windows_b (list[TimeWindow]) – Second list of windows (must be sorted by start_jd)

Return type:

list[TimeWindow]

Returns:

List of windows representing the intersection

stellium.electional.intervals.invert_windows(windows, start_jd, end_jd)[source]

Get complement windows (gaps between the given windows).

Parameters:
  • windows (list[TimeWindow]) – List of windows to invert (must be sorted by start_jd)

  • start_jd (float) – Start of the range to consider

  • end_jd (float) – End of the range to consider

Return type:

list[TimeWindow]

Returns:

List of windows representing the gaps

stellium.electional.intervals.moon_sign_not_in_windows(signs, start, end)[source]

Get windows when Moon is NOT in specified signs.

Parameters:
Return type:

list[TimeWindow]

Returns:

List of TimeWindow objects

stellium.electional.intervals.moon_sign_windows(signs, start, end)[source]

Get windows when Moon is in specified signs.

Parameters:
  • signs (list[str]) – List of sign names (e.g., [“Taurus”, “Cancer”])

  • start (datetime | float) – Start of search range

  • end (datetime | float) – End of search range

Return type:

list[TimeWindow]

Returns:

List of TimeWindow objects

stellium.electional.intervals.not_voc_windows(start, end, mode='traditional')[source]

Get windows when Moon is NOT void of course.

Parameters:
  • start (datetime | float) – Start of search range

  • end (datetime | float) – End of search range

  • mode (Literal['traditional', 'modern']) – “traditional” (Sun-Saturn) or “modern” (includes outer planets)

Return type:

list[TimeWindow]

Returns:

List of TimeWindow objects for non-VOC periods

stellium.electional.intervals.retrograde_windows(planet, start, end)[source]

Get windows when a planet is retrograde.

Parameters:
  • planet (str) – Planet name (e.g., “Mercury”, “Venus”, “Mars”)

  • start (datetime | float) – Start of search range

  • end (datetime | float) – End of search range

Return type:

list[TimeWindow]

Returns:

List of TimeWindow objects for retrograde periods

stellium.electional.intervals.union_windows(windows_a, windows_b)[source]

Merge two window lists, combining overlapping windows.

Parameters:
  • windows_a (list[TimeWindow]) – First list of windows (must be sorted by start_jd)

  • windows_b (list[TimeWindow]) – Second list of windows (must be sorted by start_jd)

Return type:

list[TimeWindow]

Returns:

List of merged windows

stellium.electional.intervals.voc_windows(start, end, mode='traditional')[source]

Get windows when Moon is void of course.

A void of course Moon has made its last major Ptolemaic aspect (conjunction, sextile, square, trine, opposition) before leaving its current sign.

This implementation uses the actual VOC calculation engine for accuracy, with binary search to find VOC transition times.

Parameters:
  • start (datetime | float) – Start of search range

  • end (datetime | float) – End of search range

  • mode (Literal['traditional', 'modern']) – “traditional” (Sun-Saturn) or “modern” (includes outer planets)

Return type:

list[TimeWindow]

Returns:

List of TimeWindow objects for VOC periods

stellium.electional.intervals.waning_windows(start, end)[source]

Get windows when Moon is waning (from Full Moon to New Moon).

Parameters:
  • start (datetime | float) – Start of search range (datetime or Julian Day)

  • end (datetime | float) – End of search range (datetime or Julian Day)

Return type:

list[TimeWindow]

Returns:

List of TimeWindow objects for waning periods

stellium.electional.intervals.waxing_windows(start, end)[source]

Get windows when Moon is waxing (from New Moon to Full Moon).

Parameters:
  • start (datetime | float) – Start of search range (datetime or Julian Day)

  • end (datetime | float) – End of search range (datetime or Julian Day)

Return type:

list[TimeWindow]

Returns:

List of TimeWindow objects for waxing periods

Planetary Hours calculation for electional astrology.

Planetary hours are a traditional timing system where each hour of the day is ruled by one of the seven classical planets in the Chaldean order.

The day is divided into 12 “hours” from sunrise to sunset (variable length), and night into 12 “hours” from sunset to sunrise. The first hour of each day is ruled by the planet that rules that weekday.

Chaldean order (from slowest to fastest): Saturn → Jupiter → Mars → Sun → Venus → Mercury → Moon

Day rulers: - Sunday: Sun - Monday: Moon - Tuesday: Mars - Wednesday: Mercury - Thursday: Jupiter - Friday: Venus - Saturday: Saturn

Example usage:
>>> from stellium.electional.planetary_hours import get_planetary_hour, get_planetary_hours_for_day
>>> hour = get_planetary_hour(datetime.now(), latitude=37.7, longitude=-122.4)
>>> print(f"Current planetary hour: {hour.ruler}")
>>> hours = get_planetary_hours_for_day(datetime(2025, 1, 1), latitude=37.7, longitude=-122.4)
>>> for h in hours[:3]:
...     print(f"{h.ruler}: {h.start_local} - {h.end_local}")
class stellium.electional.planetary_hours.PlanetaryHour(ruler, hour_number, is_day_hour, start_jd, end_jd, start_utc, end_utc)[source]

Bases: object

A single planetary hour.

ruler

The planet ruling this hour

hour_number

Hour number (1-12 for day, 13-24 for night)

is_day_hour

True if this is a day hour (sunrise to sunset)

start_jd

Start time as Julian day

end_jd

End time as Julian day

start_utc

Start time as UTC datetime

end_utc

End time as UTC datetime

property duration_minutes: float

Duration of this hour in clock minutes.

end_jd: float
end_utc: datetime
hour_number: int
is_day_hour: bool
ruler: str
start_jd: float
start_utc: datetime
stellium.electional.planetary_hours.get_day_ruler(date)[source]

Get the planetary ruler of a given day.

Parameters:

date (datetime) – The date

Return type:

str

Returns:

Name of the ruling planet

stellium.electional.planetary_hours.get_hour_ruler(day_ruler, hour_number)[source]

Get the planetary ruler of a specific hour.

Parameters:
  • day_ruler (str) – The planet ruling the first hour of the day

  • hour_number (int) – Hour number (1-24, where 1-12 are day hours, 13-24 are night)

Return type:

str

Returns:

Name of the ruling planet for that hour

stellium.electional.planetary_hours.get_planetary_hour(dt, latitude, longitude, altitude=0.0)[source]

Get the planetary hour for a specific datetime.

Parameters:
  • dt (datetime) – The datetime (UTC)

  • latitude (float) – Geographic latitude

  • longitude (float) – Geographic longitude

  • altitude (float) – Altitude in meters

Return type:

PlanetaryHour

Returns:

The PlanetaryHour active at the given time

stellium.electional.planetary_hours.get_planetary_hour_at_jd(jd, latitude, longitude, altitude=0.0)[source]

Get the planetary hour for a specific Julian day.

Parameters:
  • jd (float) – Julian day number

  • latitude (float) – Geographic latitude

  • longitude (float) – Geographic longitude

  • altitude (float) – Altitude in meters

Return type:

PlanetaryHour

Returns:

The PlanetaryHour active at the given time

stellium.electional.planetary_hours.get_planetary_hours_for_day(date, latitude, longitude, altitude=0.0)[source]

Get all 24 planetary hours for a given day.

Returns hours from sunrise of the given date to sunrise of the next date.

Parameters:
  • date (datetime) – The date

  • latitude (float) – Geographic latitude

  • longitude (float) – Geographic longitude

  • altitude (float) – Altitude in meters

Return type:

list[PlanetaryHour]

Returns:

List of 24 PlanetaryHour objects (12 day + 12 night)

stellium.electional.planetary_hours.get_sunrise_sunset(date, latitude, longitude, altitude=0.0)[source]

Get sunrise and sunset times for a given LOCAL date and location.

The “date” is interpreted as the local calendar date at the given longitude. For example, if date is Jan 1, 2025 and longitude is -122° (San Francisco), this returns the sunrise/sunset that occur on Jan 1 in Pacific time.

Parameters:
  • date (datetime) – The LOCAL date (time component is ignored)

  • latitude (float) – Geographic latitude (positive = North)

  • longitude (float) – Geographic longitude (positive = East, negative = West)

  • altitude (float) – Altitude in meters above sea level

Return type:

tuple[float, float]

Returns:

Tuple of (sunrise_jd, sunset_jd) as Julian day numbers

stellium.electional.planetary_hours.planetary_hour_windows(planet, latitude, longitude, start, end)[source]

Get all windows when a specific planet rules the planetary hour.

Parameters:
  • planet (str) – Planet name (“Sun”, “Moon”, “Mars”, “Mercury”, “Jupiter”, “Venus”, “Saturn”)

  • latitude (float) – Geographic latitude

  • longitude (float) – Geographic longitude

  • start (datetime | float) – Start of search range (datetime or Julian day)

  • end (datetime | float) – End of search range (datetime or Julian day)

Return type:

list[tuple[float, float]]

Returns:

List of (start_jd, end_jd) tuples for each planetary hour of that planet

Helper predicates for electional astrology conditions.

These factory functions return Condition callables that can be used with ElectionalSearch. They provide readable, reusable building blocks for common astrological filters.

All predicates return Callable[[CalculatedChart], bool] (the Condition type).

Each predicate is tagged with a “speed hint” indicating how quickly the condition changes, enabling hierarchical filtering for performance: - SPEED_DAY: Stable conditions (phase, retrograde) - checked once at noon - SPEED_DAY_SIGN: Sign-based conditions - checked at start+end of day - SPEED_HOUR: Hour-level conditions (VOC, aspects) - SPEED_MINUTE: House/angular positions (change with Earth’s rotation)

Example

>>> from stellium.electional import ElectionalSearch, is_waxing, not_voc, on_angle
>>> results = (ElectionalSearch("2025-01-01", "2025-12-31", "New York, NY")
...     .where(is_waxing())
...     .where(not_voc())
...     .where(on_angle("Jupiter"))
...     .find_moments())
stellium.electional.predicates.angle_at_degree(target_longitude, angle='ASC', orb=1.0)[source]

Chart angle is within orb of a specific zodiac degree.

This predicate checks if a chart angle (ASC, MC, DSC, IC) is within the specified orb of a target longitude. Useful for finding moments when specific degrees rise or culminate.

Parameters:
  • target_longitude (float) – Target longitude in degrees (0-360)

  • angle (str) – Which angle (“ASC”, “MC”, “DSC”, “IC”)

  • orb (float) – Maximum orb in degrees (default 1°)

Return type:

Callable[[CalculatedChart], bool]

Returns:

Condition that checks if angle is within orb of target

Example

>>> # Find moments when 0° Aries is rising
>>> search.where(angle_at_degree(0.0, "ASC", orb=1.0))
>>> # Find moments when 15° Leo is culminating
>>> search.where(angle_at_degree(135.0, "MC", orb=0.5))
stellium.electional.predicates.aspect_applying(obj1, obj2, aspects=None, orb_max=None)[source]

Applying aspect between two objects.

An applying aspect is getting tighter (objects moving toward exact aspect).

Parameters:
  • obj1 (str) – First object name

  • obj2 (str) – Second object name

  • aspects (list[str] | None) – List of aspect types (e.g., [“conjunction”, “trine”, “sextile”]). If None, matches any aspect type.

  • orb_max (float | None) – Maximum orb in degrees. If None, uses default orbs.

Return type:

Callable[[CalculatedChart], bool]

Returns:

Condition that checks for applying aspect between the objects

stellium.electional.predicates.aspect_exact_within(obj1, obj2, aspect, orb=1.0)[source]

Aspect between objects is within orb of exact.

This predicate checks if two objects are within a tight orb of an exact aspect. Useful for finding moments near perfection of an aspect.

Parameters:
  • obj1 (str) – First object name (e.g., “Moon”)

  • obj2 (str) – Second object name (e.g., “Jupiter”)

  • aspect (str) – Aspect name (“conjunction”, “sextile”, “square”, “trine”, “opposition”)

  • orb (float) – Maximum orb from exact in degrees (default 1°)

Return type:

Callable[[CalculatedChart], bool]

Returns:

Condition that checks if aspect is within orb of exact

Example

>>> # Find moments when Moon is within 0.5° of exact trine to Jupiter
>>> search.where(aspect_exact_within("Moon", "Jupiter", "trine", orb=0.5))
stellium.electional.predicates.aspect_separating(obj1, obj2, aspects=None, orb_max=None)[source]

Separating aspect between two objects.

A separating aspect is getting looser (objects moving away from exact aspect).

Parameters:
  • obj1 (str) – First object name

  • obj2 (str) – Second object name

  • aspects (list[str] | None) – List of aspect types. If None, matches any type.

  • orb_max (float | None) – Maximum orb in degrees.

Return type:

Callable[[CalculatedChart], bool]

Returns:

Condition that checks for separating aspect between the objects

stellium.electional.predicates.cadent(name)[source]

Object is in a cadent house (3, 6, 9, 12).

Parameters:

name (str) – Object name

Return type:

Callable[[CalculatedChart], bool]

Returns:

Condition that checks if object is in a cadent house

stellium.electional.predicates.get_speed_hint(condition)[source]

Get the speed hint for a condition, defaulting to SPEED_MINUTE.

Return type:

int

stellium.electional.predicates.get_window_generator(condition)[source]

Get the window generator for a condition, if available.

Return type:

Callable[..., Any] | None

stellium.electional.predicates.has_aspect(obj1, obj2, aspects=None, orb_max=None)[source]

Objects are in aspect (regardless of applying/separating).

Parameters:
  • obj1 (str) – First object name

  • obj2 (str) – Second object name

  • aspects (list[str] | None) – List of aspect types. If None, matches any type.

  • orb_max (float | None) – Maximum orb in degrees.

Return type:

Callable[[CalculatedChart], bool]

Returns:

Condition that checks if objects are in aspect

stellium.electional.predicates.in_house(name, houses)[source]

Object is in one of the specified houses.

Parameters:
  • name (str) – Object name (e.g., “Moon”, “Jupiter”)

  • houses (list[int]) – List of house numbers (1-12)

Return type:

Callable[[CalculatedChart], bool]

Returns:

Condition that checks if object is in any of the houses

stellium.electional.predicates.in_planetary_hour(planet)[source]

Check if the current time is in a planetary hour ruled by the specified planet.

Planetary hours are a traditional timing system where each hour of the day is ruled by one of the seven classical planets in Chaldean order.

Parameters:

planet (str) – Planet name (“Sun”, “Moon”, “Mars”, “Mercury”, “Jupiter”, “Venus”, “Saturn”)

Return type:

Callable[[CalculatedChart], bool]

Returns:

Condition that checks if current time is in that planet’s hour

Example

>>> # Find Jupiter hours (good for expansion, luck, legal matters)
>>> search.where(in_planetary_hour("Jupiter"))
>>> # Find Venus hours on Friday for love matters
>>> search.where(in_planetary_hour("Venus"))
stellium.electional.predicates.is_combust(name, orb=8.5)[source]

Planet is combust (too close to the Sun).

A combust planet is weakened by proximity to the Sun.

Parameters:
  • name (str) – Planet name

  • orb (float) – Maximum degrees from Sun to consider combust (default 8.5)

Return type:

Callable[[CalculatedChart], bool]

Returns:

Condition that checks if planet is combust

stellium.electional.predicates.is_debilitated(name, debilities=None, system='traditional')[source]

Planet is debilitated (in detriment or fall).

Parameters:
  • name (str) – Planet name

  • debilities (list[str] | None) – List of debility types to check [“detriment”, “fall”]. If None, checks for either.

  • system (str) – “traditional” or “modern” dignity system

Return type:

Callable[[CalculatedChart], bool]

Returns:

Condition that checks if planet is debilitated

stellium.electional.predicates.is_dignified(name, dignities=None, system='traditional')[source]

Planet has essential dignity.

Essential dignity means the planet is strengthened by its sign position: - ruler: Planet rules the sign (e.g., Mars in Aries) - exaltation: Planet is exalted (e.g., Sun in Aries) - triplicity: Planet rules the element (depends on sect) - bound/term: Planet rules the degree range - decan/face: Planet rules the 10° section

Parameters:
  • name (str) – Planet name

  • dignities (list[str] | None) – List of dignity types to check. If None, checks for major dignities (ruler or exaltation)

  • system (str) – “traditional” or “modern” dignity system

Return type:

Callable[[CalculatedChart], bool]

Returns:

Condition that checks if planet has specified dignities

stellium.electional.predicates.is_out_of_bounds(name)[source]

Object is out of bounds (declination beyond ~23.4°).

Out of bounds planets are considered to operate outside normal rules.

Parameters:

name (str) – Object name

Return type:

Callable[[CalculatedChart], bool]

Returns:

Condition that checks if object is out of bounds

stellium.electional.predicates.is_retrograde(name)[source]

Planet is retrograde.

Parameters:

name (str) – Planet name (e.g., “Mercury”, “Venus”, “Mars”)

Return type:

Callable[[CalculatedChart], bool]

Returns:

Condition that checks if planet is retrograde

stellium.electional.predicates.is_voc(mode='traditional')[source]

Moon is void of course.

A void of course Moon has no major aspects before leaving its current sign.

Parameters:

mode (str) – “traditional” (Sun-Saturn) or “modern” (includes outer planets)

Return type:

Callable[[CalculatedChart], bool]

Returns:

Condition that checks if Moon is void of course

stellium.electional.predicates.is_waning()[source]

Moon is waning (between Full and New Moon).

Return type:

Callable[[CalculatedChart], bool]

Returns:

Condition that checks if Moon phase is waning

stellium.electional.predicates.is_waxing()[source]

Moon is waxing (between New and Full Moon).

Return type:

Callable[[CalculatedChart], bool]

Returns:

Condition that checks if Moon phase is waxing

Example

>>> search.where(is_waxing())
stellium.electional.predicates.moon_phase(phases)[source]

Moon is in one of the specified phases.

Parameters:

phases (list[str]) – List of phase names, e.g., [“New Moon”, “Full Moon”] Valid phases: “New Moon”, “Waxing Crescent”, “First Quarter”, “Waxing Gibbous”, “Full Moon”, “Waning Gibbous”, “Last Quarter”, “Waning Crescent”

Return type:

Callable[[CalculatedChart], bool]

Returns:

Condition that checks if Moon phase matches any in the list

stellium.electional.predicates.no_aspect(obj1, obj2, aspects=None, orb_max=None)[source]

Objects are NOT in aspect.

Parameters:
  • obj1 (str) – First object name

  • obj2 (str) – Second object name

  • aspects (list[str] | None) – List of aspect types. If None, means no aspect at all.

  • orb_max (float | None) – Maximum orb to consider.

Return type:

Callable[[CalculatedChart], bool]

Returns:

Condition that checks if objects are NOT in aspect

stellium.electional.predicates.no_hard_aspect(name, exclude_objects=None, applying_only=True)[source]

Object has no hard aspects (square, opposition) from any planet.

Hard aspects from malefics (Mars, Saturn) are particularly problematic in electional astrology.

Parameters:
  • name (str) – Object name to check

  • exclude_objects (list[str] | None) – Objects to ignore (e.g., [“Mars”] if Mars is dignified)

  • applying_only (bool) – If True, only count applying aspects (default True)

Return type:

Callable[[CalculatedChart], bool]

Returns:

Condition that checks object has no hard aspects

stellium.electional.predicates.no_malefic_aspect(name, applying_only=True)[source]

Object has no hard aspects from Mars or Saturn.

Parameters:
  • name (str) – Object name to check

  • applying_only (bool) – If True, only count applying aspects

Return type:

Callable[[CalculatedChart], bool]

Returns:

Condition that checks object has no Mars/Saturn hard aspects

stellium.electional.predicates.not_combust(name, orb=8.5)[source]

Planet is NOT combust.

Parameters:
  • name (str) – Planet name

  • orb (float) – Minimum degrees from Sun to NOT be combust

Return type:

Callable[[CalculatedChart], bool]

Returns:

Condition that checks if planet is NOT combust

stellium.electional.predicates.not_debilitated(name, system='traditional')[source]

Planet is NOT in detriment or fall.

Parameters:
  • name (str) – Planet name

  • system (str) – “traditional” or “modern” dignity system

Return type:

Callable[[CalculatedChart], bool]

Returns:

Condition that checks planet is NOT debilitated

stellium.electional.predicates.not_in_house(name, houses)[source]

Object is NOT in any of the specified houses.

Parameters:
  • name (str) – Object name

  • houses (list[int]) – List of house numbers to avoid

Return type:

Callable[[CalculatedChart], bool]

Returns:

Condition that checks if object is NOT in any of the houses

stellium.electional.predicates.not_out_of_bounds(name)[source]

Object is NOT out of bounds.

Parameters:

name (str) – Object name

Return type:

Callable[[CalculatedChart], bool]

Returns:

Condition that checks if object is NOT out of bounds

stellium.electional.predicates.not_retrograde(name)[source]

Planet is NOT retrograde (direct motion).

Parameters:

name (str) – Planet name (e.g., “Mercury”, “Venus”, “Mars”)

Return type:

Callable[[CalculatedChart], bool]

Returns:

Condition that checks if planet is NOT retrograde

stellium.electional.predicates.not_voc(mode='traditional')[source]

Moon is NOT void of course.

Parameters:

mode (str) – “traditional” (Sun-Saturn) or “modern” (includes outer planets)

Return type:

Callable[[CalculatedChart], bool]

Returns:

Condition that checks Moon is NOT void of course

stellium.electional.predicates.on_angle(name)[source]

Object is angular (in houses 1, 4, 7, or 10).

Angular houses are the most powerful positions in electional astrology.

Parameters:

name (str) – Object name (e.g., “Jupiter”, “Venus”, “Moon”)

Return type:

Callable[[CalculatedChart], bool]

Returns:

Condition that checks if object is in an angular house

stellium.electional.predicates.sign_in(name, signs)[source]

Object is in one of the specified signs.

Parameters:
  • name (str) – Object name (e.g., “Moon”, “Sun”, “Mars”)

  • signs (list[str]) – List of sign names (e.g., [“Aries”, “Leo”, “Sagittarius”])

Return type:

Callable[[CalculatedChart], bool]

Returns:

Condition that checks if object is in any of the signs

stellium.electional.predicates.sign_not_in(name, signs)[source]

Object is NOT in any of the specified signs.

Parameters:
  • name (str) – Object name (e.g., “Moon”, “Sun”, “Mars”)

  • signs (list[str]) – List of sign names to exclude

Return type:

Callable[[CalculatedChart], bool]

Returns:

Condition that checks if object is NOT in any of the signs

stellium.electional.predicates.star_on_angle(star_name, angle='ASC', orb=1.0)[source]

Fixed star is conjunct a chart angle.

This is a convenience wrapper around angle_at_degree() that looks up the star’s current longitude and checks if the specified angle is within orb of it.

Note: Fixed stars move very slowly (~50 arcseconds/year due to precession), so for practical purposes within a year search, the star’s longitude is effectively constant.

Parameters:
  • star_name (str) – Name of the fixed star (e.g., “Regulus”, “Spica”, “Algol”)

  • angle (str) – Which angle (“ASC”, “MC”, “DSC”, “IC”)

  • orb (float) – Maximum orb in degrees (default 1°)

Return type:

Callable[[CalculatedChart], bool]

Returns:

Condition that checks if star is conjunct angle

Example

>>> # Find moments when Regulus is rising
>>> search.where(star_on_angle("Regulus", "ASC", orb=1.0))
>>> # Find moments when Spica is culminating
>>> search.where(star_on_angle("Spica", "MC", orb=0.5))
stellium.electional.predicates.succedent(name)[source]

Object is in a succedent house (2, 5, 8, 11).

Parameters:

name (str) – Object name

Return type:

Callable[[CalculatedChart], bool]

Returns:

Condition that checks if object is in a succedent house


Analysis (stellium.analysis)

Batch chart analysis and data processing.

Batch chart calculation for large-scale analysis.

BatchCalculator provides efficient calculation of many charts at once, with support for progress tracking, filtering, and generator-based processing.

class stellium.analysis.batch.BatchCalculator(sources)[source]

Bases: object

Efficient batch calculation of multiple charts.

Supports calculation from: - NotableRegistry (with optional filters) - List of Native objects - Any iterable of chart data

Example:

# From NotableRegistry
charts = (BatchCalculator
    .from_registry(category="scientist", verified=True)
    .with_aspects()
    .calculate_all())

# From list of Natives
charts = BatchCalculator.from_natives(natives).calculate_all()

# Generator for memory efficiency
for chart in BatchCalculator.from_registry().calculate():
    process(chart)
add_analyzer(analyzer)[source]

Add a chart analyzer (e.g., PatternAnalysisEngine).

Return type:

BatchCalculator

calculate()[source]

Calculate charts as a generator (memory efficient).

Yields charts one at a time, suitable for processing large datasets without loading all charts into memory.

Yields:

CalculatedChart for each source

Example:

for chart in BatchCalculator.from_registry().calculate():
    # Process one chart at a time
    print(chart.get_object("Sun").sign)
Return type:

Generator[CalculatedChart, None, None]

calculate_all()[source]

Calculate all charts and return as a list.

Loads all charts into memory. Use calculate() generator for large datasets that don’t fit in memory.

Return type:

list[CalculatedChart]

Returns:

List of all calculated charts

Example:

charts = BatchCalculator.from_registry(category="artist").calculate_all()
print(f"Calculated {len(charts)} artist charts")
count()[source]

Get the count of sources (if known).

Return type:

int

Returns:

Number of sources, or -1 if unknown (for streaming iterables)

classmethod from_iterable(sources)[source]

Create BatchCalculator from any iterable of Native objects.

Use this for streaming data or custom data sources.

Parameters:

sources (Iterable[Native]) – Iterable yielding Native objects

Return type:

BatchCalculator

Returns:

BatchCalculator ready to configure and run

classmethod from_natives(natives)[source]

Create BatchCalculator from a list of Native objects.

Parameters:

natives (list[Native]) – List of Native objects to calculate charts for

Return type:

BatchCalculator

Returns:

BatchCalculator ready to configure and run

Example:

natives = [
    Native("2000-01-01 12:00", "New York, NY", name="Person 1"),
    Native("1990-06-15 08:30", "Los Angeles, CA", name="Person 2"),
]
batch = BatchCalculator.from_natives(natives)
classmethod from_registry(*, category=None, event_type=None, verified=None, data_quality=None, **filters)[source]

Create BatchCalculator from NotableRegistry with optional filters.

Parameters:
  • category (str | None) – Filter by category (e.g., “scientist”, “artist”)

  • event_type (str | None) – Filter by event type (“birth” or “event”)

  • verified (bool | None) – Filter by verified status

  • data_quality (str | None) – Filter by data quality (“AA”, “A”, “B”, “C”)

  • **filters (Any) – Additional filters passed to registry.search()

Return type:

BatchCalculator

Returns:

BatchCalculator ready to configure and run

Example:

# All verified scientists
batch = BatchCalculator.from_registry(
    category="scientist",
    verified=True
)

# High-quality birth data only
batch = BatchCalculator.from_registry(
    event_type="birth",
    data_quality="AA"
)
with_aspects(engine=None)[source]

Enable aspect calculation with optional custom engine.

Return type:

BatchCalculator

with_house_systems(engines)[source]

Set the house systems to calculate.

Return type:

BatchCalculator

with_orbs(engine=None)[source]

Set the orb calculation engine.

Return type:

BatchCalculator

with_progress(callback)[source]

Set progress callback for tracking calculation progress.

The callback receives: - current: Current chart number (1-based) - total: Total number of charts (or -1 if unknown) - name: Name of current chart being calculated

Parameters:

callback (Callable[[int, int, str], None]) – Function to call with progress updates

Return type:

BatchCalculator

Example:

def show_progress(current, total, name):
    if total > 0:
        print(f"Calculating {current}/{total}: {name}")
    else:
        print(f"Calculating {current}: {name}")

batch = BatchCalculator.from_registry().with_progress(show_progress)

DataFrame conversion utilities for chart analysis.

Provides functions to convert CalculatedChart objects to pandas DataFrames in various schemas optimized for different analysis use cases.

Requires pandas: pip install stellium[analysis]

stellium.analysis.frames.aspects_to_dataframe(charts, include_declination=False)[source]

Convert charts to a DataFrame with one row per aspect.

This schema is best for: - Aspect frequency analysis - Aspect pattern research - Orb distribution analysis

Parameters:
  • charts – Sequence of CalculatedChart objects

  • include_declination – Include declination aspects (parallel/contraparallel)

Returns:

  • chart_id: Links to chart-level data

  • chart_name: Chart name

  • object1: First object name

  • object2: Second object name

  • aspect_name: “Conjunction”, “Square”, etc.

  • aspect_degree: 0, 60, 90, 120, 180, etc.

  • orb: Actual orb in degrees

  • is_applying: Applying vs separating

  • aspect_type: “longitude” or “declination”

Return type:

DataFrame with columns

Example:

from stellium.analysis import BatchCalculator, aspects_to_dataframe

charts = BatchCalculator.from_registry().with_aspects().calculate_all()
df = aspects_to_dataframe(charts)

# Most common aspects
df['aspect_name'].value_counts()

# Sun-Moon aspects
sun_moon = df[(df['object1'] == 'Sun') & (df['object2'] == 'Moon')]
stellium.analysis.frames.charts_to_dataframe(charts, include_patterns=True)[source]

Convert charts to a DataFrame with one row per chart.

This schema is best for: - Comparing charts across a dataset - Element/modality distribution analysis - Chart-wide pattern matching

Parameters:
  • charts – Sequence of CalculatedChart objects

  • include_patterns – Include pattern detection columns (requires patterns in metadata)

Returns:

  • chart_id: Unique identifier

  • name: Chart name (from metadata)

  • datetime_utc: UTC datetime

  • julian_day: Julian day number

  • latitude, longitude: Location coordinates

  • location_name: Location name

  • sun_longitude, sun_sign, moon_longitude, moon_sign, moon_phase

  • asc_longitude, asc_sign, mc_longitude, mc_sign

  • fire_count, earth_count, air_count, water_count

  • cardinal_count, fixed_count, mutable_count

  • sect: “day” or “night”

  • retrograde_count: Number of retrograde planets

  • has_grand_trine, has_t_square, has_grand_cross (if include_patterns)

Return type:

DataFrame with columns

Example:

from stellium.analysis import BatchCalculator, charts_to_dataframe

charts = BatchCalculator.from_registry(category="artist").calculate_all()
df = charts_to_dataframe(charts)

# Filter by sun sign
aries_suns = df[df['sun_sign'] == 'Aries']
stellium.analysis.frames.positions_to_dataframe(charts, object_types=None)[source]

Convert charts to a DataFrame with one row per celestial position.

This schema is best for: - Position distributions across many charts - Sign/house analysis - Speed and retrograde analysis

Parameters:
  • charts – Sequence of CalculatedChart objects

  • object_types – Filter to specific ObjectTypes (default: all)

Returns:

  • chart_id: Links to chart-level data

  • chart_name: Chart name

  • object_name: “Sun”, “Moon”, etc.

  • object_type: “planet”, “angle”, etc.

  • longitude: Ecliptic longitude (0-360)

  • latitude: Ecliptic latitude

  • sign: Zodiac sign

  • sign_degree: Degree within sign (0-30)

  • house: House placement (1-12, if available)

  • speed: Longitude speed (deg/day)

  • is_retrograde: Retrograde flag

  • declination: Declination (nullable)

  • is_out_of_bounds: OOB flag

Return type:

DataFrame with columns

Example:

from stellium.analysis import BatchCalculator, positions_to_dataframe

charts = BatchCalculator.from_registry().calculate_all()
df = positions_to_dataframe(charts)

# Sun sign distribution
sun_df = df[df['object_name'] == 'Sun']
sun_df['sign'].value_counts()
stellium.analysis.frames.to_aspects_dataframe(charts, include_declination=False)

Convert charts to a DataFrame with one row per aspect.

This schema is best for: - Aspect frequency analysis - Aspect pattern research - Orb distribution analysis

Parameters:
  • charts – Sequence of CalculatedChart objects

  • include_declination – Include declination aspects (parallel/contraparallel)

Returns:

  • chart_id: Links to chart-level data

  • chart_name: Chart name

  • object1: First object name

  • object2: Second object name

  • aspect_name: “Conjunction”, “Square”, etc.

  • aspect_degree: 0, 60, 90, 120, 180, etc.

  • orb: Actual orb in degrees

  • is_applying: Applying vs separating

  • aspect_type: “longitude” or “declination”

Return type:

DataFrame with columns

Example:

from stellium.analysis import BatchCalculator, aspects_to_dataframe

charts = BatchCalculator.from_registry().with_aspects().calculate_all()
df = aspects_to_dataframe(charts)

# Most common aspects
df['aspect_name'].value_counts()

# Sun-Moon aspects
sun_moon = df[(df['object1'] == 'Sun') & (df['object2'] == 'Moon')]
stellium.analysis.frames.to_chart_dataframe(charts, include_patterns=True)

Convert charts to a DataFrame with one row per chart.

This schema is best for: - Comparing charts across a dataset - Element/modality distribution analysis - Chart-wide pattern matching

Parameters:
  • charts – Sequence of CalculatedChart objects

  • include_patterns – Include pattern detection columns (requires patterns in metadata)

Returns:

  • chart_id: Unique identifier

  • name: Chart name (from metadata)

  • datetime_utc: UTC datetime

  • julian_day: Julian day number

  • latitude, longitude: Location coordinates

  • location_name: Location name

  • sun_longitude, sun_sign, moon_longitude, moon_sign, moon_phase

  • asc_longitude, asc_sign, mc_longitude, mc_sign

  • fire_count, earth_count, air_count, water_count

  • cardinal_count, fixed_count, mutable_count

  • sect: “day” or “night”

  • retrograde_count: Number of retrograde planets

  • has_grand_trine, has_t_square, has_grand_cross (if include_patterns)

Return type:

DataFrame with columns

Example:

from stellium.analysis import BatchCalculator, charts_to_dataframe

charts = BatchCalculator.from_registry(category="artist").calculate_all()
df = charts_to_dataframe(charts)

# Filter by sun sign
aries_suns = df[df['sun_sign'] == 'Aries']
stellium.analysis.frames.to_positions_dataframe(charts, object_types=None)

Convert charts to a DataFrame with one row per celestial position.

This schema is best for: - Position distributions across many charts - Sign/house analysis - Speed and retrograde analysis

Parameters:
  • charts – Sequence of CalculatedChart objects

  • object_types – Filter to specific ObjectTypes (default: all)

Returns:

  • chart_id: Links to chart-level data

  • chart_name: Chart name

  • object_name: “Sun”, “Moon”, etc.

  • object_type: “planet”, “angle”, etc.

  • longitude: Ecliptic longitude (0-360)

  • latitude: Ecliptic latitude

  • sign: Zodiac sign

  • sign_degree: Degree within sign (0-30)

  • house: House placement (1-12, if available)

  • speed: Longitude speed (deg/day)

  • is_retrograde: Retrograde flag

  • declination: Declination (nullable)

  • is_out_of_bounds: OOB flag

Return type:

DataFrame with columns

Example:

from stellium.analysis import BatchCalculator, positions_to_dataframe

charts = BatchCalculator.from_registry().calculate_all()
df = positions_to_dataframe(charts)

# Sun sign distribution
sun_df = df[df['object_name'] == 'Sun']
sun_df['sign'].value_counts()

Research query interface for filtering chart collections.

ChartQuery provides a fluent API for filtering charts by astrological criteria like sun sign, moon phase, aspects, patterns, and more.

class stellium.analysis.queries.ChartQuery(charts)[source]

Bases: object

Fluent query interface for filtering chart collections.

Supports chained method calls to build complex filters. Filters are lazily evaluated when results() or count() is called.

Example:

from stellium.analysis import ChartQuery

# Find charts with Sun in Aries and Moon in Cancer
matches = (ChartQuery(charts)
    .where_sun(sign="Aries")
    .where_moon(sign=["Cancer", "Scorpio"])
    .where_planet("Mars", house=10)
    .results())

# Get as DataFrame
df = ChartQuery(charts).where_sun(sign="Aries").to_dataframe()

# Count results
count = ChartQuery(charts).where_moon(sign="Leo").count()
count()[source]

Execute the query and return count of matching charts.

Return type:

int

Returns:

Number of charts matching all filters

first()[source]

Return the first matching chart, or None if no matches.

Return type:

CalculatedChart | None

Returns:

First CalculatedChart matching all filters, or None

results()[source]

Execute the query and return matching charts.

Return type:

list[CalculatedChart]

Returns:

List of CalculatedChart objects matching all filters

to_dataframe(include_patterns=True)[source]

Execute the query and return results as a DataFrame.

Requires pandas: pip install stellium[analysis]

Parameters:

include_patterns (bool) – Include pattern detection columns

Return type:

Any

Returns:

pandas DataFrame of matching charts

where_angle(name, sign=None, degree_min=None, degree_max=None)[source]

Filter charts by angle position (ASC, MC, DSC, IC).

Parameters:
  • name (str) – Angle name (“ASC”, “MC”, “DSC”, “IC”)

  • sign (str | Sequence[str] | None) – Zodiac sign(s)

  • degree_min (float | None) – Minimum sign degree

  • degree_max (float | None) – Maximum sign degree

Return type:

ChartQuery

Example:

query.where_angle("ASC", sign="Leo")
query.where_angle("MC", sign=["Aries", "Capricorn"])
where_aspect(object1, object2, aspect=None, orb_max=None, applying=None)[source]

Filter charts by presence of specific aspects.

Parameters:
  • object1 (str) – First object name

  • object2 (str) – Second object name

  • aspect (str | Sequence[str] | None) – Aspect type(s) (e.g., “Conjunction”, [“Square”, “Opposition”])

  • orb_max (float | None) – Maximum orb in degrees

  • applying (bool | None) – Filter to applying (True) or separating (False) aspects

Return type:

ChartQuery

Example:

query.where_aspect("Sun", "Moon", aspect="conjunction")
query.where_aspect("Venus", "Mars", orb_max=3.0)
query.where_aspect("Saturn", "Sun", applying=True)
where_custom(predicate)[source]

Filter charts with a custom predicate function.

Parameters:

predicate (Callable[[CalculatedChart], bool]) – Function that takes a CalculatedChart and returns bool

Return type:

ChartQuery

Example:

# Charts with more than 3 retrograde planets
query.where_custom(
    lambda chart: sum(1 for p in chart.get_planets() if p.is_retrograde) > 3
)
where_element_dominant(element, min_count=4)[source]

Filter charts where an element is dominant.

Parameters:
  • element (str) – Element name (“fire”, “earth”, “air”, “water”)

  • min_count (int) – Minimum planet count to consider dominant (default 4)

Return type:

ChartQuery

Example:

query.where_element_dominant("fire")
query.where_element_dominant("water", min_count=5)
where_modality_dominant(modality, min_count=4)[source]

Filter charts where a modality is dominant.

Parameters:
  • modality (str) – Modality name (“cardinal”, “fixed”, “mutable”)

  • min_count (int) – Minimum planet count to consider dominant (default 4)

Return type:

ChartQuery

Example:

query.where_modality_dominant("cardinal")
query.where_modality_dominant("fixed", min_count=5)
where_moon(sign=None, house=None, degree_min=None, degree_max=None, phase=None)[source]

Filter charts by Moon position or phase.

Parameters:
Return type:

ChartQuery

Example:

query.where_moon(sign="Cancer")
query.where_moon(phase="Full Moon")
query.where_moon(phase=["New Moon", "Full Moon"])
where_pattern(pattern_name)[source]

Filter charts by presence of aspect patterns.

Parameters:

pattern_name (str) – Pattern name (e.g., “Grand Trine”, “T-Square”, “Yod”)

Return type:

ChartQuery

Example:

query.where_pattern("Grand Trine")
query.where_pattern("T-Square")
where_planet(name, sign=None, house=None, degree_min=None, degree_max=None, retrograde=None, out_of_bounds=None)[source]

Filter charts by any planet’s position.

Parameters:
  • name (str) – Planet name (e.g., “Mars”, “Venus”)

  • sign (str | Sequence[str] | None) – Zodiac sign(s)

  • house (int | Sequence[int] | None) – House number(s)

  • degree_min (float | None) – Minimum sign degree

  • degree_max (float | None) – Maximum sign degree

  • retrograde (bool | None) – Filter by retrograde status

  • out_of_bounds (bool | None) – Filter by out-of-bounds status

Return type:

ChartQuery

Example:

query.where_planet("Mars", sign="Aries", retrograde=True)
query.where_planet("Venus", house=[5, 7])
query.where_planet("Mercury", out_of_bounds=True)
where_sect(sect)[source]

Filter charts by sect (day or night).

Parameters:

sect (str) – “day” or “night”

Return type:

ChartQuery

Example:

query.where_sect("day")  # Day charts only
where_sun(sign=None, house=None, degree_min=None, degree_max=None)[source]

Filter charts by Sun position.

Parameters:
  • sign (str | Sequence[str] | None) – Zodiac sign(s) (e.g., “Aries” or [“Aries”, “Leo”])

  • house (int | Sequence[int] | None) – House number(s) (e.g., 10 or [1, 10])

  • degree_min (float | None) – Minimum sign degree (0-30)

  • degree_max (float | None) – Maximum sign degree (0-30)

Return type:

ChartQuery

Example:

query.where_sun(sign="Aries")
query.where_sun(sign=["Aries", "Leo", "Sagittarius"])  # Fire signs
query.where_sun(house=10)
query.where_sun(degree_min=0, degree_max=5)  # Early degrees

Statistical aggregation for chart collections.

ChartStats provides methods for computing aggregate statistics across multiple charts, including element distributions, sign frequencies, aspect counts, and cross-tabulations.

class stellium.analysis.stats.ChartStats(charts)[source]

Bases: object

Statistical aggregation across chart collections.

Computes distributions, frequencies, and cross-tabulations for research and analysis purposes.

Example:

from stellium.analysis import BatchCalculator, ChartStats

charts = BatchCalculator.from_registry(category="scientist").calculate_all()
stats = ChartStats(charts)

# Element distribution
print(stats.element_distribution())

# Sun sign frequency
print(stats.sign_distribution("Sun"))

# Cross-tabulation
print(stats.cross_tab("sun_sign", "moon_sign"))
aspect_frequency(normalize=False)[source]

Count aspect types across all charts.

Parameters:

normalize (bool) – Return proportions instead of counts

Return type:

dict[str, int | float]

Returns:

Dictionary with aspect names as keys and counts as values

Example:

stats.aspect_frequency()
# {'Conjunction': 1234, 'Square': 987, 'Trine': 876, ...}
aspect_pair_frequency(object1, object2)[source]

Count aspect types between two specific objects.

Parameters:
  • object1 (str) – First object name

  • object2 (str) – Second object name

Return type:

dict[str, int]

Returns:

Dictionary with aspect names as keys and counts as values

Example:

stats.aspect_pair_frequency("Sun", "Moon")
# {'Conjunction': 45, 'Sextile': 38, 'Square': 42, ...}
property chart_count: int

Number of charts in the collection.

cross_tab(row_var, col_var)[source]

Create a cross-tabulation (contingency table) of two variables.

Requires pandas: pip install stellium[analysis]

Supported variables: - “sun_sign”, “moon_sign”, “asc_sign”, “mc_sign” - “sun_house”, “moon_house”, etc. (any object + “_house”) - “sect” (day/night) - Any object name followed by “_sign” or “_house”

Parameters:
  • row_var (str) – Variable for rows

  • col_var (str) – Variable for columns

Return type:

Any

Returns:

pandas DataFrame with cross-tabulation

Example:

# Sun sign vs Moon sign
df = stats.cross_tab("sun_sign", "moon_sign")

# Sun sign vs sect
df = stats.cross_tab("sun_sign", "sect")
element_distribution(normalize=True)[source]

Calculate element distribution across all charts.

Counts planets in each element across all charts and returns the distribution as proportions (default) or raw counts.

Parameters:

normalize (bool) – Return proportions (0-1) instead of counts

Return type:

dict[str, float]

Returns:

Dictionary with element names as keys and counts/proportions as values

Example:

stats.element_distribution()
# {'fire': 0.28, 'earth': 0.31, 'air': 0.22, 'water': 0.19}

stats.element_distribution(normalize=False)
# {'fire': 280, 'earth': 310, 'air': 220, 'water': 190}
house_distribution(object_name, house_system=None, normalize=False)[source]

Count house placements for a specific object across all charts.

Parameters:
  • object_name (str) – Name of the object (e.g., “Sun”, “Moon”)

  • house_system (str | None) – House system to use (default: chart’s default)

  • normalize (bool) – Return proportions instead of counts

Return type:

dict[int, int | float]

Returns:

Dictionary with house numbers (1-12) as keys and counts as values

Example:

stats.house_distribution("Sun")
# {1: 35, 2: 42, 3: 38, ..., 12: 41}
modality_distribution(normalize=True)[source]

Calculate modality distribution across all charts.

Parameters:

normalize (bool) – Return proportions (0-1) instead of counts

Return type:

dict[str, float]

Returns:

Dictionary with modality names as keys and counts/proportions as values

Example:

stats.modality_distribution()
# {'cardinal': 0.35, 'fixed': 0.33, 'mutable': 0.32}
pattern_frequency()[source]

Count aspect patterns across all charts.

Return type:

dict[str, int]

Returns:

Dictionary with pattern names as keys and counts as values

Example:

stats.pattern_frequency()
# {'Grand Trine': 23, 'T-Square': 45, 'Yod': 12, ...}
retrograde_frequency(normalize=False)[source]

Count retrograde occurrences by planet.

Parameters:

normalize (bool) – Return proportions instead of counts

Return type:

dict[str, int | float]

Returns:

Dictionary with planet names as keys and retrograde counts as values

Example:

stats.retrograde_frequency()
# {'Mercury': 89, 'Venus': 23, 'Mars': 45, ...}
sect_distribution()[source]

Count day vs night charts.

Return type:

dict[str, int]

Returns:

Dictionary with “day” and “night” keys and counts as values

Example:

stats.sect_distribution()
# {'day': 523, 'night': 477}
sign_distribution(object_name, normalize=False)[source]

Count sign placements for a specific object across all charts.

Parameters:
  • object_name (str) – Name of the object (e.g., “Sun”, “Moon”, “ASC”)

  • normalize (bool) – Return proportions instead of counts

Return type:

dict[str, int | float]

Returns:

Dictionary with sign names as keys and counts as values

Example:

stats.sign_distribution("Sun")
# {'Aries': 45, 'Taurus': 38, 'Gemini': 42, ...}

stats.sign_distribution("Moon", normalize=True)
# {'Aries': 0.08, 'Taurus': 0.09, ...}
summary()[source]

Generate a comprehensive summary of the chart collection.

Return type:

dict[str, Any]

Returns:

Dictionary with various statistics

Example:

summary = stats.summary()
print(summary['chart_count'])
print(summary['element_distribution'])

Export utilities for chart collections.

Provides functions to export charts to CSV, JSON, and other formats for external analysis tools.

stellium.analysis.export.export_csv(charts, path, schema='charts', **kwargs)[source]

Export charts to a CSV file.

Requires pandas: pip install stellium[analysis]

Parameters:
  • charts (Sequence[CalculatedChart]) – Sequence of CalculatedChart objects

  • path (str | Path) – Output file path

  • schema (Literal['charts', 'positions', 'aspects']) – Data schema to use: - “charts”: One row per chart (default) - “positions”: One row per celestial position - “aspects”: One row per aspect

  • **kwargs – Additional arguments passed to DataFrame.to_csv()

Return type:

None

Example:

from stellium.analysis import BatchCalculator, export_csv

charts = BatchCalculator.from_registry(category="artist").calculate_all()

# Export chart-level data
export_csv(charts, "artists.csv")

# Export all positions
export_csv(charts, "artists_positions.csv", schema="positions")

# Export aspects
export_csv(charts, "artists_aspects.csv", schema="aspects")
stellium.analysis.export.export_json(charts, path, indent=2, lines=False)[source]

Export charts to JSON format.

Parameters:
  • charts (Sequence[CalculatedChart]) – Sequence of CalculatedChart objects

  • path (str | Path) – Output file path

  • indent (int | None) – JSON indentation (None for compact)

  • lines (bool) – If True, write JSON Lines format (one object per line)

Return type:

None

Example:

from stellium.analysis import BatchCalculator, export_json

charts = BatchCalculator.from_registry(category="artist").calculate_all()

# Standard JSON array
export_json(charts, "artists.json")

# JSON Lines (for streaming/large datasets)
export_json(charts, "artists.jsonl", lines=True)
stellium.analysis.export.export_parquet(charts, path, schema='charts')[source]

Export charts to Parquet format (columnar, efficient for big data).

Requires pandas and pyarrow: pip install stellium[analysis] pyarrow

Parameters:
  • charts (Sequence[CalculatedChart]) – Sequence of CalculatedChart objects

  • path (str | Path) – Output file path

  • schema (Literal['charts', 'positions', 'aspects']) – Data schema (same as export_csv)

Return type:

None

Example:

from stellium.analysis import BatchCalculator, export_parquet

charts = BatchCalculator.from_registry().calculate_all()
export_parquet(charts, "all_charts.parquet")
stellium.analysis.export.to_csv(charts, path, schema='charts', **kwargs)

Export charts to a CSV file.

Requires pandas: pip install stellium[analysis]

Parameters:
  • charts (Sequence[CalculatedChart]) – Sequence of CalculatedChart objects

  • path (str | Path) – Output file path

  • schema (Literal['charts', 'positions', 'aspects']) – Data schema to use: - “charts”: One row per chart (default) - “positions”: One row per celestial position - “aspects”: One row per aspect

  • **kwargs – Additional arguments passed to DataFrame.to_csv()

Return type:

None

Example:

from stellium.analysis import BatchCalculator, export_csv

charts = BatchCalculator.from_registry(category="artist").calculate_all()

# Export chart-level data
export_csv(charts, "artists.csv")

# Export all positions
export_csv(charts, "artists_positions.csv", schema="positions")

# Export aspects
export_csv(charts, "artists_aspects.csv", schema="aspects")
stellium.analysis.export.to_json(charts, path, indent=2, lines=False)

Export charts to JSON format.

Parameters:
  • charts (Sequence[CalculatedChart]) – Sequence of CalculatedChart objects

  • path (str | Path) – Output file path

  • indent (int | None) – JSON indentation (None for compact)

  • lines (bool) – If True, write JSON Lines format (one object per line)

Return type:

None

Example:

from stellium.analysis import BatchCalculator, export_json

charts = BatchCalculator.from_registry(category="artist").calculate_all()

# Standard JSON array
export_json(charts, "artists.json")

# JSON Lines (for streaming/large datasets)
export_json(charts, "artists.jsonl", lines=True)
stellium.analysis.export.to_parquet(charts, path, schema='charts')

Export charts to Parquet format (columnar, efficient for big data).

Requires pandas and pyarrow: pip install stellium[analysis] pyarrow

Parameters:
  • charts (Sequence[CalculatedChart]) – Sequence of CalculatedChart objects

  • path (str | Path) – Output file path

  • schema (Literal['charts', 'positions', 'aspects']) – Data schema (same as export_csv)

Return type:

None

Example:

from stellium.analysis import BatchCalculator, export_parquet

charts = BatchCalculator.from_registry().calculate_all()
export_parquet(charts, "all_charts.parquet")

Generate embedding vectors to represent individual charts for fast comparison.

class stellium.analysis.vector.ChartVectorizer(include_speed=True, include_houses=True)[source]

Bases: object

Transforms a stellium CalculatedChart into a dense vector embedding.

encode(chart)[source]
Return type:

ndarray

similarity(vec_a, vec_b)[source]

Cosine similarity between two charts.

Return type:

float


I/O (stellium.io)

Input/output formats for chart data.

Parser for AAF (Astrodienst Astrological Format) files.

AAF is the export format used by astro.com (Astrodienst). It contains birth data in a structured text format with two lines per record: - #A93: Human-readable data (name, date, time, location) - #B93: Computed data (Julian day, coordinates, timezone)

Example AAF record:

#A93:Louie,Kate,f,6.1.1994,11:47,Mountain View (Santa Clara County),CA (US) #B93:2449359.32431,37n23,122w05,8hw00,0

stellium.io.aaf.parse_aaf(path)[source]

Parse an AAF (Astrodienst Astrological Format) file into Native objects.

AAF is the export format from astro.com. Each chart record consists of two lines: #A93 (human-readable data) and #B93 (computed values).

Parameters:

path (str | Path) – Path to the AAF file

Return type:

list[Native]

Returns:

List of Native objects, one per chart in the file

Raises:

Example

>>> natives = parse_aaf("my_charts.aaf")
>>> len(natives)
20
>>> natives[0].name
'Kate Louie'
>>> chart = ChartBuilder.from_native(natives[0]).calculate()

Parser for CSV files containing birth data.

CSV files are a common format for batch chart data. This module provides flexible parsing with configurable column mapping to accommodate different CSV formats and naming conventions.

Example CSV formats supported:

# Standard format (auto-detected): name,date,time,location Kate Louie,1994-01-06,11:47,Mountain View CA

# Combined datetime: name,datetime,place Kate,1994-01-06 11:47,37.3861,-122.0839

# Separate date components: first_name,last_name,year,month,day,hour,minute,latitude,longitude Kate,Louie,1994,1,6,11,47,37.3861,-122.0839

# With timezone: Name,Birth Date,Birth Time,City,Timezone Kate Louie,01/06/1994,11:47 AM,Mountain View CA,America/Los_Angeles

class stellium.io.csv.CSVColumnMapping(name=None, datetime=None, date=None, time=None, year=None, month=None, day=None, hour=None, minute=None, second=None, location=None, latitude=None, longitude=None, timezone=None, time_unknown=None, date_format=None, time_format=None, datetime_format=None)[source]

Bases: object

Configuration for mapping CSV columns to Native fields.

This allows flexible handling of different CSV formats. All column names are case-insensitive and support multiple aliases.

name

Column(s) for person/event name. Can be a single column name or a tuple for (first_name, last_name) to combine.

datetime

Column for combined datetime string (e.g., “1994-01-06 11:47”)

date

Column for date only (when datetime is split)

time

Column for time only (when datetime is split)

year

Column for year (when date is split into components)

month

Column for month

day

Column for day

hour

Column for hour (when time is split into components)

minute

Column for minute

second

Column for second

location

Column for location string (geocoded if no lat/lon, or used as display name if lat/lon are provided)

latitude

Column for latitude (when using coordinates)

longitude

Column for longitude (when using coordinates)

timezone

Column for timezone name (e.g., “America/Los_Angeles”)

time_unknown

Column indicating if birth time is unknown (bool/flag)

Location handling:
  • If latitude + longitude are provided: Uses coordinates directly. If location is also provided, it’s used as the display name.

  • If only location is provided (no lat/lon): Geocodes the string.

date: str | None = None
date_format: str | None = None
datetime: str | None = None
datetime_format: str | None = None
day: str | None = None
hour: str | None = None
latitude: str | None = None
location: str | None = None
longitude: str | None = None
minute: str | None = None
month: str | None = None
name: str | tuple[str, str] | None = None
second: str | None = None
time: str | None = None
time_format: str | None = None
time_unknown: str | None = None
timezone: str | None = None
year: str | None = None
stellium.io.csv.parse_csv(path, mapping=None, *, delimiter=',', encoding='utf-8', skip_errors=True)[source]

Parse a CSV file containing birth data into Native objects.

This function supports flexible CSV formats through column mapping. If no mapping is provided, it will auto-detect columns based on common naming conventions.

Parameters:
  • path (str | Path) – Path to the CSV file

  • mapping (CSVColumnMapping | None) – Optional column mapping configuration. If None, auto-detects columns from headers.

  • delimiter (str) – CSV delimiter character (default: comma)

  • encoding (str) – File encoding (default: utf-8)

  • skip_errors (bool) – If True, skip rows that fail to parse and continue. If False, raise an exception on the first error.

Return type:

list[Native]

Returns:

List of Native objects, one per valid row in the CSV

Raises:
  • FileNotFoundError – If the file doesn’t exist

  • ValueError – If required columns are missing or skip_errors=False and a row fails to parse

Example

# Auto-detect columns >>> natives = parse_csv(“birth_data.csv”)

# Custom column mapping >>> mapping = CSVColumnMapping( … name=”Full Name”, … date=”DOB”, … time=”Birth Time”, … location=”Birth Place”, … ) >>> natives = parse_csv(“birth_data.csv”, mapping=mapping)

# With date format hint for ambiguous dates >>> mapping = CSVColumnMapping( … date=”date”, … date_format=”%d/%m/%Y”, # European format … ) >>> natives = parse_csv(“european_data.csv”, mapping=mapping)

stellium.io.csv.read_csv(path, *, name=None, datetime=None, date=None, time=None, location=None, latitude=None, longitude=None, date_format=None, time_format=None)[source]

Simple interface for reading CSV files with common column configurations.

This is a convenience wrapper around parse_csv() that allows specifying column names as keyword arguments.

Parameters:
  • path (str | Path) – Path to the CSV file

  • name (str | tuple[str, str] | None) – Column name for person/event name, or tuple of (first, last)

  • datetime (str | None) – Column name for combined datetime

  • date (str | None) – Column name for date

  • time (str | None) – Column name for time

  • location (str | None) – Column name for location string

  • latitude (str | None) – Column name for latitude

  • longitude (str | None) – Column name for longitude

  • date_format (str | None) – strptime format for dates (e.g., “%d/%m/%Y”)

  • time_format (str | None) – strptime format for times (e.g., “%I:%M %p”)

Return type:

list[Native]

Returns:

List of Native objects

Example

# Simple auto-detection >>> natives = read_csv(“data.csv”)

# Specify key columns >>> natives = read_csv( … “data.csv”, … name=”Full Name”, … date=”DOB”, … time=”Birth Time”, … location=”City”, … )

# Combined first/last name >>> natives = read_csv( … “data.csv”, … name=(“First Name”, “Last Name”), … datetime=”Birth DateTime”, … latitude=”Lat”, … longitude=”Long”, … )

Parser for pandas DataFrames containing birth data.

This module provides the same flexible parsing as the CSV module, but works directly with in-memory pandas DataFrames. This is useful when data comes from databases, Excel files, or other pandas-compatible sources.

Example usage:
>>> import pandas as pd
>>> from stellium.io import parse_dataframe, read_dataframe
>>>
>>> # Load data from any source
>>> df = pd.read_excel("birth_data.xlsx")
>>> # Or: df = pd.read_sql("SELECT * FROM births", connection)
>>> # Or: df = pd.read_parquet("data.parquet")
>>>
>>> # Auto-detect columns
>>> natives = parse_dataframe(df)
>>>
>>> # Or specify columns explicitly
>>> natives = read_dataframe(
...     df,
...     name="Full Name",
...     date="DOB",
...     time="Birth Time",
...     latitude="Lat",
...     longitude="Long",
... )
stellium.io.dataframe.dataframe_from_natives(natives, *, include_coords=True, include_timezone=False)[source]

Convert a list of Native objects back to a pandas DataFrame.

This is useful for exporting processed data or for round-trip operations.

Parameters:
  • natives (list[Native]) – List of Native objects to convert

  • include_coords (bool) – Include latitude/longitude columns (default: True)

  • include_timezone (bool) – Include timezone column (default: False)

Return type:

pd.DataFrame

Returns:

pandas DataFrame with birth data

Example

>>> from stellium.io import parse_csv, dataframe_from_natives
>>>
>>> natives = parse_csv("birth_data.csv")
>>> df = dataframe_from_natives(natives)
>>> df.to_excel("birth_data.xlsx")  # Export to Excel
stellium.io.dataframe.parse_dataframe(df, mapping=None, *, skip_errors=True)[source]

Parse a pandas DataFrame containing birth data into Native objects.

This function supports flexible DataFrame formats through column mapping. If no mapping is provided, it will auto-detect columns based on common naming conventions.

Parameters:
  • df (pd.DataFrame) – pandas DataFrame with birth data

  • mapping (CSVColumnMapping | None) – Optional column mapping configuration. If None, auto-detects columns from DataFrame column names.

  • skip_errors (bool) – If True, skip rows that fail to parse and continue. If False, raise an exception on the first error.

Return type:

list[Native]

Returns:

List of Native objects, one per valid row in the DataFrame

Raises:
  • ImportError – If pandas is not installed

  • ValueError – If required columns are missing or skip_errors=False and a row fails to parse

Example

>>> import pandas as pd
>>> from stellium.io import parse_dataframe
>>>
>>> df = pd.DataFrame({
...     "name": ["Kate Louie", "Albert Einstein"],
...     "date": ["1994-01-06", "1879-03-14"],
...     "time": ["11:47", "11:30"],
...     "latitude": [37.3861, 48.4011],
...     "longitude": [-122.0839, 9.9876],
... })
>>> natives = parse_dataframe(df)
>>> len(natives)
2
>>> # With custom column mapping
>>> mapping = CSVColumnMapping(
...     name="Full Name",
...     date="DOB",
...     latitude="Lat",
...     longitude="Lon",
... )
>>> natives = parse_dataframe(df, mapping=mapping)
stellium.io.dataframe.read_dataframe(df, *, name=None, datetime=None, date=None, time=None, location=None, latitude=None, longitude=None, date_format=None, time_format=None)[source]

Simple interface for reading pandas DataFrames with common column configurations.

This is a convenience wrapper around parse_dataframe() that allows specifying column names as keyword arguments.

Parameters:
  • df (pd.DataFrame) – pandas DataFrame with birth data

  • name (str | tuple[str, str] | None) – Column name for person/event name, or tuple of (first, last)

  • datetime (str | None) – Column name for combined datetime

  • date (str | None) – Column name for date

  • time (str | None) – Column name for time

  • location (str | None) – Column name for location string

  • latitude (str | None) – Column name for latitude

  • longitude (str | None) – Column name for longitude

  • date_format (str | None) – strptime format for dates (e.g., “%d/%m/%Y”)

  • time_format (str | None) – strptime format for times (e.g., “%I:%M %p”)

Return type:

list[Native]

Returns:

List of Native objects

Example

>>> import pandas as pd
>>> from stellium.io import read_dataframe
>>>
>>> df = pd.DataFrame({
...     "Person": ["Kate Louie"],
...     "Birthday": ["1994-01-06"],
...     "Birth Time": ["11:47"],
...     "Lat": [37.3861],
...     "Long": [-122.0839],
... })
>>>
>>> natives = read_dataframe(
...     df,
...     name="Person",
...     date="Birthday",
...     time="Birth Time",
...     latitude="Lat",
...     longitude="Long",
... )

Chinese Astrology (stellium.chinese)

Ba Zi (Four Pillars) and related Chinese astrological systems.

Chinese astrology systems for Stellium.

This module provides implementations of various Chinese astrology systems: - Bazi (Four Pillars / 八字) - implemented - Zi Wei Dou Shu (Purple Star / 紫微斗數) - planned

Core primitives (stems, branches, elements) are shared across all systems. All chart types implement the ChineseChart protocol for interoperability.

Example

>>> from stellium.chinese import BaZiEngine
>>> from datetime import datetime
>>>
>>> engine = BaZiEngine(timezone_offset_hours=8)  # Beijing time
>>> chart = engine.calculate(datetime(1990, 5, 15, 10, 30))
>>> print(chart.display())
stellium.chinese.BaZiCalculator

alias of BaZiEngine

class stellium.chinese.BaZiChart(year, month, day, hour, birth_datetime)[source]

Bases: object

A complete Four Pillars (Bazi / 八字) chart.

The Day Stem represents the “Day Master” (日主), which is the self.

Implements the ChineseChart protocol.

property all_branches: tuple[EarthlyBranch, ...]

All four earthly branches.

property all_hidden_stems: list[HeavenlyStem]

All hidden stems across all four pillars.

property all_stems: tuple[HeavenlyStem, ...]

All four heavenly stems.

birth_datetime: datetime
day: Pillar
property day_master: HeavenlyStem

The Day Master (日主) - the stem that represents the self.

property day_master_element: Element

The element of the Day Master.

display()[source]

Human-readable prose display of the chart.

Return type:

str

display_detailed()[source]

Detailed prose display including hidden stems and Ten Gods.

Return type:

str

element_counts(include_hidden=False)[source]

Count occurrences of each element across stems and branches.

Parameters:

include_hidden (bool) – If True, includes hidden stems in the count. Hidden stems are weighted: main=1.0, middle=0.5, residual=0.3

Return type:

dict[Element, int]

Note: For weighted hidden stem analysis, use element_strength() instead.

property hanzi: str

The eight characters (八字) in Chinese.

hour: Pillar
month: Pillar
property pillars: tuple[Pillar, Pillar, Pillar, Pillar]

year, month, day, hour.

Type:

All four pillars in order

polarity_counts()[source]

Count Yin vs Yang across all stems and branches.

Return type:

dict[Polarity, int]

strength()[source]

Analyze the Day Master’s strength.

Returns a StrengthAnalysis with classification (Strong/Weak/etc.), component scores, and favorable/unfavorable elements.

Example:

bazi = ChartBuilder.from_details("1994-01-06 11:47", "Palo Alto, CA").bazi()
result = bazi.strength()
print(result.strength.english)  # "Strong", "Weak", etc.
print(result.favorable_elements)
Return type:

StrengthAnalysis

property system_name: str

The name of this system.

ten_gods(include_hidden=True)[source]

Analyze Ten Gods (十神) relationships in the chart.

Parameters:

include_hidden (bool) – Whether to include hidden stems in analysis

Returns:

List of TenGodRelation objects

to_dict()[source]

Export chart data as a dictionary (JSON-serializable).

Return type:

dict[str, Any]

year: Pillar
class stellium.chinese.BaZiEngine(timezone_offset_hours=0.0)[source]

Bases: object

Calculate Bazi (Four Pillars) charts.

Implements the ChineseChartEngine protocol.

Example

>>> from datetime import datetime
>>> engine = BaZiEngine(timezone_offset_hours=-8)  # PST
>>> chart = engine.calculate(datetime(1994, 1, 6, 11, 47))
>>> print(chart.display())
calculate(birth_datetime)[source]

Calculate the Four Pillars chart for a birth datetime.

Parameters:

birth_datetime (datetime) – The birth date and time. Should be in local time if timezone_offset was provided, otherwise UTC.

Return type:

BaZiChart

Returns:

Complete BaZiChart with all four pillars.

property system_name: str

The name of the system this engine calculates.

class stellium.chinese.ChineseChart(*args, **kwargs)[source]

Bases: Protocol

Protocol for all Chinese astrology chart types.

This defines the minimum interface that Bazi, Zi Wei, Qi Men, etc. should all implement, enabling shared visualization and export logic.

property birth_datetime: datetime

The birth/event datetime used to calculate this chart.

display()[source]

Human-readable text representation of the chart.

Return type:

str

element_counts()[source]

Count of each element in the chart.

Return type:

dict[Element, int]

property system_name: str

The name of the system (e.g., ‘Bazi’, ‘Zi Wei Dou Shu’).

to_dict()[source]

Export chart data as a dictionary (for JSON serialization).

Return type:

dict[str, Any]

class stellium.chinese.ChineseChartEngine(*args, **kwargs)[source]

Bases: Protocol

Protocol for Chinese chart calculation engines.

Mirrors the pattern from Western astrology engines.

calculate(birth_datetime)[source]

Calculate a chart for the given datetime.

Return type:

ChineseChart

property system_name: str

The name of the system this engine calculates.

class stellium.chinese.ChineseChartRenderer(*args, **kwargs)[source]

Bases: Protocol

Protocol for rendering Chinese charts to visual formats.

Each system (Bazi, Zi Wei, etc.) will have its own renderer due to their vastly different visual structures.

render_svg(chart, **options)[source]

Render the chart as an SVG string.

Return type:

str

render_text(chart, **options)[source]

Render the chart as formatted text.

Return type:

str

class stellium.chinese.EarthlyBranch(value)[source]

Bases: Enum

The Twelve Earthly Branches (Di Zhi / 地支).

CHEN = BranchMeta(index=4, hanzi='辰', pinyin='Chén', jyutping='San4', animal='Dragon', element=<Element.EARTH: ('Earth', '土', '#795548')>, polarity=<Polarity.YANG: 'Yang'>, hidden_stems=('WU', 'YI', 'GUI'))
CHOU = BranchMeta(index=1, hanzi='丑', pinyin='Chǒu', jyutping='Cau2', animal='Ox', element=<Element.EARTH: ('Earth', '土', '#795548')>, polarity=<Polarity.YIN: 'Yin'>, hidden_stems=('JI', 'GUI', 'XIN'))
HAI = BranchMeta(index=11, hanzi='亥', pinyin='Hài', jyutping='Hoi6', animal='Pig', element=<Element.WATER: ('Water', '水', '#2196f3')>, polarity=<Polarity.YIN: 'Yin'>, hidden_stems=('REN', 'JIA'))
MAO = BranchMeta(index=3, hanzi='卯', pinyin='Mǎo', jyutping='Maau5', animal='Rabbit', element=<Element.WOOD: ('Wood', '木', '#4caf50')>, polarity=<Polarity.YIN: 'Yin'>, hidden_stems=('YI',))
SHEN = BranchMeta(index=8, hanzi='申', pinyin='Shēn', jyutping='San1', animal='Monkey', element=<Element.METAL: ('Metal', '金', '#9e9e9e')>, polarity=<Polarity.YANG: 'Yang'>, hidden_stems=('GENG', 'REN', 'WU'))
SI = BranchMeta(index=5, hanzi='巳', pinyin='Sì', jyutping='Zi6', animal='Snake', element=<Element.FIRE: ('Fire', '火', '#f44336')>, polarity=<Polarity.YIN: 'Yin'>, hidden_stems=('BING', 'WU', 'GENG'))
WEI = BranchMeta(index=7, hanzi='未', pinyin='Wèi', jyutping='Mei6', animal='Goat', element=<Element.EARTH: ('Earth', '土', '#795548')>, polarity=<Polarity.YIN: 'Yin'>, hidden_stems=('JI', 'DING', 'YI'))
WU_BRANCH = BranchMeta(index=6, hanzi='午', pinyin='Wǔ', jyutping='Ng5', animal='Horse', element=<Element.FIRE: ('Fire', '火', '#f44336')>, polarity=<Polarity.YANG: 'Yang'>, hidden_stems=('DING', 'JI'))
XU = BranchMeta(index=10, hanzi='戌', pinyin='Xū', jyutping='Seot1', animal='Dog', element=<Element.EARTH: ('Earth', '土', '#795548')>, polarity=<Polarity.YANG: 'Yang'>, hidden_stems=('WU', 'XIN', 'DING'))
YIN = BranchMeta(index=2, hanzi='寅', pinyin='Yín', jyutping='Jan4', animal='Tiger', element=<Element.WOOD: ('Wood', '木', '#4caf50')>, polarity=<Polarity.YANG: 'Yang'>, hidden_stems=('JIA', 'BING', 'WU'))
YOU = BranchMeta(index=9, hanzi='酉', pinyin='Yǒu', jyutping='Jau5', animal='Rooster', element=<Element.METAL: ('Metal', '金', '#9e9e9e')>, polarity=<Polarity.YIN: 'Yin'>, hidden_stems=('XIN',))
ZI = BranchMeta(index=0, hanzi='子', pinyin='Zǐ', jyutping='Zi2', animal='Rat', element=<Element.WATER: ('Water', '水', '#2196f3')>, polarity=<Polarity.YANG: 'Yang'>, hidden_stems=('GUI',))
property animal: str
property display: str

Human-readable display string.

property element: Element
classmethod from_index(index)[source]

Get branch by 0-11 index (modulo safe for cyclic calculations).

Return type:

EarthlyBranch

get_hidden_stem_objects()[source]

Get the actual HeavenlyStem objects for this branch’s hidden stems.

Return type:

list[HeavenlyStem]

property hanzi: str
property hidden_stems: tuple[str, ...]
property index: int
property jyutping: str
property pinyin: str
property polarity: Polarity
class stellium.chinese.Element(english, hanzi, color_hex)[source]

Bases: Enum

The Five Elements (Wu Xing / 五行).

EARTH = ('Earth', '土', '#795548')
FIRE = ('Fire', '火', '#f44336')
METAL = ('Metal', '金', '#9e9e9e')
WATER = ('Water', '水', '#2196f3')
WOOD = ('Wood', '木', '#4caf50')
property controlled_by: Element

What controls this element in the controlling cycle (克我).

property controls: Element

What this element controls/overcomes in the controlling cycle (克).

property produced_by: Element

What produces this element in the generative cycle (生我).

property produces: Element

What this element produces in the generative cycle (生).

class stellium.chinese.HeavenlyStem(value)[source]

Bases: Enum

The Ten Heavenly Stems (Tian Gan / 天干).

BING = StemMeta(index=2, hanzi='丙', pinyin='Bǐng', jyutping='Bing2', element=<Element.FIRE: ('Fire', '火', '#f44336')>, polarity=<Polarity.YANG: 'Yang'>)
DING = StemMeta(index=3, hanzi='丁', pinyin='Dīng', jyutping='Ding1', element=<Element.FIRE: ('Fire', '火', '#f44336')>, polarity=<Polarity.YIN: 'Yin'>)
GENG = StemMeta(index=6, hanzi='庚', pinyin='Gēng', jyutping='Gang1', element=<Element.METAL: ('Metal', '金', '#9e9e9e')>, polarity=<Polarity.YANG: 'Yang'>)
GUI = StemMeta(index=9, hanzi='癸', pinyin='Guǐ', jyutping='Gwai3', element=<Element.WATER: ('Water', '水', '#2196f3')>, polarity=<Polarity.YIN: 'Yin'>)
JI = StemMeta(index=5, hanzi='己', pinyin='Jǐ', jyutping='Gei2', element=<Element.EARTH: ('Earth', '土', '#795548')>, polarity=<Polarity.YIN: 'Yin'>)
JIA = StemMeta(index=0, hanzi='甲', pinyin='Jiǎ', jyutping='Gaap3', element=<Element.WOOD: ('Wood', '木', '#4caf50')>, polarity=<Polarity.YANG: 'Yang'>)
REN = StemMeta(index=8, hanzi='壬', pinyin='Rén', jyutping='Jam4', element=<Element.WATER: ('Water', '水', '#2196f3')>, polarity=<Polarity.YANG: 'Yang'>)
WU = StemMeta(index=4, hanzi='戊', pinyin='Wù', jyutping='Mou6', element=<Element.EARTH: ('Earth', '土', '#795548')>, polarity=<Polarity.YANG: 'Yang'>)
XIN = StemMeta(index=7, hanzi='辛', pinyin='Xīn', jyutping='San1', element=<Element.METAL: ('Metal', '金', '#9e9e9e')>, polarity=<Polarity.YIN: 'Yin'>)
YI = StemMeta(index=1, hanzi='乙', pinyin='Yǐ', jyutping='Jyut6', element=<Element.WOOD: ('Wood', '木', '#4caf50')>, polarity=<Polarity.YIN: 'Yin'>)
property display: str

Human-readable display string.

property display_canto: str

Display string with Cantonese romanization.

property element: Element
classmethod from_index(index)[source]

Get stem by 0-9 index (modulo safe for cyclic calculations).

Return type:

HeavenlyStem

property hanzi: str
property index: int
property jyutping: str
property pinyin: str
property polarity: Polarity
class stellium.chinese.Pillar(stem, branch)[source]

Bases: object

A single pillar (柱) consisting of a Stem and Branch.

property animal: str

The zodiac animal of the branch.

branch: EarthlyBranch
property branch_element: Element

The element of the branch.

property hanzi: str

The two-character Chinese representation.

property hidden_stems: list[HeavenlyStem]

The hidden stems (藏干) within the branch.

property pinyin: str

Pinyin romanization.

stem: HeavenlyStem
property stem_element: Element

The element of the stem (primary element of the pillar).

to_dict()[source]

Export pillar data as a dictionary.

Return type:

dict[str, Any]

class stellium.chinese.Polarity(value)[source]

Bases: Enum

Yin and Yang polarities.

YANG = 'Yang'
YIN = 'Yin'
property hanzi: str
class stellium.chinese.SolarTerm(index, longitude, hanzi, english)[source]

Bases: Enum

The 24 Solar Terms (Jie Qi / 节气).

Values are (index, solar_longitude, chinese_name, english_name). Index 0 = Li Chun (Start of Spring) at 315°.

BAI_LU = (14, 165, '白露', 'White Dew')
CHUN_FEN = (3, 0, '春分', 'Spring Equinox')
CHU_SHU = (13, 150, '处暑', 'End of Heat')
DA_HAN = (23, 300, '大寒', 'Major Cold')
DA_SHU = (11, 120, '大暑', 'Major Heat')
DA_XUE = (20, 255, '大雪', 'Major Snow')
DONG_ZHI = (21, 270, '冬至', 'Winter Solstice')
GU_YU = (5, 30, '谷雨', 'Grain Rain')
HAN_LU = (16, 195, '寒露', 'Cold Dew')
JING_ZHE = (2, 345, '惊蛰', 'Awakening of Insects')
LI_CHUN = (0, 315, '立春', 'Start of Spring')
LI_DONG = (18, 225, '立冬', 'Start of Winter')
LI_QIU = (12, 135, '立秋', 'Start of Autumn')
LI_XIA = (6, 45, '立夏', 'Start of Summer')
MANG_ZHONG = (8, 75, '芒种', 'Grain in Ear')
QING_MING = (4, 15, '清明', 'Clear and Bright')
QIU_FEN = (15, 180, '秋分', 'Autumn Equinox')
SHUANG_JIANG = (17, 210, '霜降', "Frost's Descent")
XIAO_HAN = (22, 285, '小寒', 'Minor Cold')
XIAO_MAN = (7, 60, '小满', 'Grain Buds')
XIAO_SHU = (10, 105, '小暑', 'Minor Heat')
XIAO_XUE = (19, 240, '小雪', 'Minor Snow')
XIA_ZHI = (9, 90, '夏至', 'Summer Solstice')
YU_SHUI = (1, 330, '雨水', 'Rain Water')
classmethod from_longitude(longitude)[source]

Get the solar term for a given solar longitude.

Return type:

SolarTerm

property is_major_term: bool

Major terms (Jie) mark Bazi month boundaries. They’re the odd-indexed terms.

class stellium.chinese.SolarTermEngine[source]

Bases: object

Calculates solar terms (Jie Qi) for Chinese calendar systems.

LI_CHUN_DEGREE = 315.0
classmethod find_month_start(jd)[source]

Find the exact moment the current Bazi month started.

Returns the SolarTermEvent for the Jie (major term) that began this month.

Return type:

SolarTermEvent

classmethod find_term_crossing(target_longitude, jd_start, direction='forward')[source]

Find the exact Julian Day when the Sun crosses a specific longitude.

Uses the existing search engine with hybrid Newton-Raphson / bisection.

Parameters:
  • target_longitude (float) – The solar longitude to find (0-360)

  • jd_start (float) – Julian Day to start searching from

  • direction (Literal['forward', 'backward']) – “forward” or “backward”

Return type:

float

Returns:

Julian Day of the crossing

classmethod get_bazi_month_index(jd)[source]

Get the Bazi month index (0-11) for a given Julian Day.

Month 0 = Tiger month (starts at Li Chun, 315°) Month 11 = Ox month (starts at Xiao Han, 285°)

Return type:

int

classmethod get_current_term(jd)[source]

Get the solar term that’s currently active at this Julian Day.

Return type:

SolarTerm

static get_solar_longitude(jd)[source]

Returns the Sun’s tropical longitude for a given Julian Day.

Return type:

float

class stellium.chinese.SolarTermEvent(term, datetime_utc, julian_day)[source]

Bases: object

A solar term occurrence at a specific moment in time.

datetime_utc: datetime
julian_day: float
term: SolarTerm
class stellium.chinese.TenGod(english, chinese, hanzi, short_code)[source]

Bases: Enum

The Ten Gods (十神) relationship types.

Each value contains: (english_name, chinese_name, hanzi, short_code)

BI_JIAN = ('Friend', '比肩', '比', 'BJ')
JIE_CAI = ('Rob Wealth', '劫财', '劫', 'JC')
PIAN_CAI = ('Indirect Wealth', '偏财', '偏财', 'PC')
PIAN_YIN = ('Indirect Seal', '偏印', '枭', 'PY')
QI_SHA = ('Seven Killings', '七杀', '杀', 'QS')
SELF = ('Self', '日主', '我', 'DM')
SHANG_GUAN = ('Hurting Officer', '伤官', '伤', 'SG')
SHI_SHEN = ('Eating God', '食神', '食', 'SS')
ZHENG_CAI = ('Direct Wealth', '正财', '正财', 'ZC')
ZHENG_GUAN = ('Direct Officer', '正官', '官', 'ZG')
ZHENG_YIN = ('Direct Seal', '正印', '印', 'ZY')
property category: str

The category this god belongs to.

property is_direct: bool

Whether this is a ‘direct/正’ relationship (different polarities).

class stellium.chinese.TenGodRelation(stem, ten_god, pillar_name, is_hidden=False)[source]

Bases: object

A Ten God relationship for a specific stem in the chart.

property display: str

Human-readable display.

is_hidden: bool = False
pillar_name: str
stem: HeavenlyStem
ten_god: TenGod
stellium.chinese.analyze_ten_gods(chart, include_hidden=True)[source]

Analyze all Ten God relationships in a Bazi chart.

Parameters:
  • chart (BaZiChart) – The BaZiChart to analyze

  • include_hidden (bool) – Whether to include hidden stems in the analysis

Return type:

list[TenGodRelation]

Returns:

List of TenGodRelation objects for all stems in the chart

stellium.chinese.calculate_ten_god(day_master, other_stem)[source]

Calculate the Ten God relationship between Day Master and another stem.

Parameters:
  • day_master (HeavenlyStem) – The Day Master (日主) stem

  • other_stem (HeavenlyStem) – The stem to analyze

Return type:

TenGod

Returns:

The TenGod relationship

Core primitives for Chinese astrology systems.

This module contains the fundamental building blocks shared across different Chinese astrology systems (Bazi, Zi Wei Dou Shu, etc.): - Polarity (Yin/Yang) - Five Elements (Wu Xing) - Ten Heavenly Stems (Tian Gan) - Twelve Earthly Branches (Di Zhi)

class stellium.chinese.core.BranchMeta(index, hanzi, pinyin, jyutping, animal, element, polarity, hidden_stems=())[source]

Bases: object

Metadata for an Earthly Branch.

animal: str
element: Element
hanzi: str
hidden_stems: tuple[str, ...] = ()
index: int
jyutping: str
pinyin: str
polarity: Polarity
class stellium.chinese.core.EarthlyBranch(value)[source]

Bases: Enum

The Twelve Earthly Branches (Di Zhi / 地支).

CHEN = BranchMeta(index=4, hanzi='辰', pinyin='Chén', jyutping='San4', animal='Dragon', element=<Element.EARTH: ('Earth', '土', '#795548')>, polarity=<Polarity.YANG: 'Yang'>, hidden_stems=('WU', 'YI', 'GUI'))
CHOU = BranchMeta(index=1, hanzi='丑', pinyin='Chǒu', jyutping='Cau2', animal='Ox', element=<Element.EARTH: ('Earth', '土', '#795548')>, polarity=<Polarity.YIN: 'Yin'>, hidden_stems=('JI', 'GUI', 'XIN'))
HAI = BranchMeta(index=11, hanzi='亥', pinyin='Hài', jyutping='Hoi6', animal='Pig', element=<Element.WATER: ('Water', '水', '#2196f3')>, polarity=<Polarity.YIN: 'Yin'>, hidden_stems=('REN', 'JIA'))
MAO = BranchMeta(index=3, hanzi='卯', pinyin='Mǎo', jyutping='Maau5', animal='Rabbit', element=<Element.WOOD: ('Wood', '木', '#4caf50')>, polarity=<Polarity.YIN: 'Yin'>, hidden_stems=('YI',))
SHEN = BranchMeta(index=8, hanzi='申', pinyin='Shēn', jyutping='San1', animal='Monkey', element=<Element.METAL: ('Metal', '金', '#9e9e9e')>, polarity=<Polarity.YANG: 'Yang'>, hidden_stems=('GENG', 'REN', 'WU'))
SI = BranchMeta(index=5, hanzi='巳', pinyin='Sì', jyutping='Zi6', animal='Snake', element=<Element.FIRE: ('Fire', '火', '#f44336')>, polarity=<Polarity.YIN: 'Yin'>, hidden_stems=('BING', 'WU', 'GENG'))
WEI = BranchMeta(index=7, hanzi='未', pinyin='Wèi', jyutping='Mei6', animal='Goat', element=<Element.EARTH: ('Earth', '土', '#795548')>, polarity=<Polarity.YIN: 'Yin'>, hidden_stems=('JI', 'DING', 'YI'))
WU_BRANCH = BranchMeta(index=6, hanzi='午', pinyin='Wǔ', jyutping='Ng5', animal='Horse', element=<Element.FIRE: ('Fire', '火', '#f44336')>, polarity=<Polarity.YANG: 'Yang'>, hidden_stems=('DING', 'JI'))
XU = BranchMeta(index=10, hanzi='戌', pinyin='Xū', jyutping='Seot1', animal='Dog', element=<Element.EARTH: ('Earth', '土', '#795548')>, polarity=<Polarity.YANG: 'Yang'>, hidden_stems=('WU', 'XIN', 'DING'))
YIN = BranchMeta(index=2, hanzi='寅', pinyin='Yín', jyutping='Jan4', animal='Tiger', element=<Element.WOOD: ('Wood', '木', '#4caf50')>, polarity=<Polarity.YANG: 'Yang'>, hidden_stems=('JIA', 'BING', 'WU'))
YOU = BranchMeta(index=9, hanzi='酉', pinyin='Yǒu', jyutping='Jau5', animal='Rooster', element=<Element.METAL: ('Metal', '金', '#9e9e9e')>, polarity=<Polarity.YIN: 'Yin'>, hidden_stems=('XIN',))
ZI = BranchMeta(index=0, hanzi='子', pinyin='Zǐ', jyutping='Zi2', animal='Rat', element=<Element.WATER: ('Water', '水', '#2196f3')>, polarity=<Polarity.YANG: 'Yang'>, hidden_stems=('GUI',))
property animal: str
property display: str

Human-readable display string.

property element: Element
classmethod from_index(index)[source]

Get branch by 0-11 index (modulo safe for cyclic calculations).

Return type:

EarthlyBranch

get_hidden_stem_objects()[source]

Get the actual HeavenlyStem objects for this branch’s hidden stems.

Return type:

list[HeavenlyStem]

property hanzi: str
property hidden_stems: tuple[str, ...]
property index: int
property jyutping: str
property pinyin: str
property polarity: Polarity
class stellium.chinese.core.Element(english, hanzi, color_hex)[source]

Bases: Enum

The Five Elements (Wu Xing / 五行).

EARTH = ('Earth', '土', '#795548')
FIRE = ('Fire', '火', '#f44336')
METAL = ('Metal', '金', '#9e9e9e')
WATER = ('Water', '水', '#2196f3')
WOOD = ('Wood', '木', '#4caf50')
property controlled_by: Element

What controls this element in the controlling cycle (克我).

property controls: Element

What this element controls/overcomes in the controlling cycle (克).

property produced_by: Element

What produces this element in the generative cycle (生我).

property produces: Element

What this element produces in the generative cycle (生).

class stellium.chinese.core.HeavenlyStem(value)[source]

Bases: Enum

The Ten Heavenly Stems (Tian Gan / 天干).

BING = StemMeta(index=2, hanzi='丙', pinyin='Bǐng', jyutping='Bing2', element=<Element.FIRE: ('Fire', '火', '#f44336')>, polarity=<Polarity.YANG: 'Yang'>)
DING = StemMeta(index=3, hanzi='丁', pinyin='Dīng', jyutping='Ding1', element=<Element.FIRE: ('Fire', '火', '#f44336')>, polarity=<Polarity.YIN: 'Yin'>)
GENG = StemMeta(index=6, hanzi='庚', pinyin='Gēng', jyutping='Gang1', element=<Element.METAL: ('Metal', '金', '#9e9e9e')>, polarity=<Polarity.YANG: 'Yang'>)
GUI = StemMeta(index=9, hanzi='癸', pinyin='Guǐ', jyutping='Gwai3', element=<Element.WATER: ('Water', '水', '#2196f3')>, polarity=<Polarity.YIN: 'Yin'>)
JI = StemMeta(index=5, hanzi='己', pinyin='Jǐ', jyutping='Gei2', element=<Element.EARTH: ('Earth', '土', '#795548')>, polarity=<Polarity.YIN: 'Yin'>)
JIA = StemMeta(index=0, hanzi='甲', pinyin='Jiǎ', jyutping='Gaap3', element=<Element.WOOD: ('Wood', '木', '#4caf50')>, polarity=<Polarity.YANG: 'Yang'>)
REN = StemMeta(index=8, hanzi='壬', pinyin='Rén', jyutping='Jam4', element=<Element.WATER: ('Water', '水', '#2196f3')>, polarity=<Polarity.YANG: 'Yang'>)
WU = StemMeta(index=4, hanzi='戊', pinyin='Wù', jyutping='Mou6', element=<Element.EARTH: ('Earth', '土', '#795548')>, polarity=<Polarity.YANG: 'Yang'>)
XIN = StemMeta(index=7, hanzi='辛', pinyin='Xīn', jyutping='San1', element=<Element.METAL: ('Metal', '金', '#9e9e9e')>, polarity=<Polarity.YIN: 'Yin'>)
YI = StemMeta(index=1, hanzi='乙', pinyin='Yǐ', jyutping='Jyut6', element=<Element.WOOD: ('Wood', '木', '#4caf50')>, polarity=<Polarity.YIN: 'Yin'>)
property display: str

Human-readable display string.

property display_canto: str

Display string with Cantonese romanization.

property element: Element
classmethod from_index(index)[source]

Get stem by 0-9 index (modulo safe for cyclic calculations).

Return type:

HeavenlyStem

property hanzi: str
property index: int
property jyutping: str
property pinyin: str
property polarity: Polarity
class stellium.chinese.core.Polarity(value)[source]

Bases: Enum

Yin and Yang polarities.

YANG = 'Yang'
YIN = 'Yin'
property hanzi: str
class stellium.chinese.core.StemMeta(index, hanzi, pinyin, jyutping, element, polarity)[source]

Bases: object

Metadata for a Heavenly Stem.

element: Element
hanzi: str
index: int
jyutping: str
pinyin: str
polarity: Polarity

Chinese calendar calculations: solar terms, lunar calendar conversions.

This module handles the astronomical calculations needed for Chinese astrology: - Solar terms (Jie Qi / 节气) for Bazi month determination - Lunar calendar conversions (future)

Solar terms are based on the Sun’s ecliptic longitude: - Li Chun (立春, Start of Spring) = 315° - Each of the 24 terms is 15° apart - The 12 “major” terms (Zhong Qi) mark Bazi month boundaries

class stellium.chinese.calendar.SolarTerm(index, longitude, hanzi, english)[source]

Bases: Enum

The 24 Solar Terms (Jie Qi / 节气).

Values are (index, solar_longitude, chinese_name, english_name). Index 0 = Li Chun (Start of Spring) at 315°.

BAI_LU = (14, 165, '白露', 'White Dew')
CHUN_FEN = (3, 0, '春分', 'Spring Equinox')
CHU_SHU = (13, 150, '处暑', 'End of Heat')
DA_HAN = (23, 300, '大寒', 'Major Cold')
DA_SHU = (11, 120, '大暑', 'Major Heat')
DA_XUE = (20, 255, '大雪', 'Major Snow')
DONG_ZHI = (21, 270, '冬至', 'Winter Solstice')
GU_YU = (5, 30, '谷雨', 'Grain Rain')
HAN_LU = (16, 195, '寒露', 'Cold Dew')
JING_ZHE = (2, 345, '惊蛰', 'Awakening of Insects')
LI_CHUN = (0, 315, '立春', 'Start of Spring')
LI_DONG = (18, 225, '立冬', 'Start of Winter')
LI_QIU = (12, 135, '立秋', 'Start of Autumn')
LI_XIA = (6, 45, '立夏', 'Start of Summer')
MANG_ZHONG = (8, 75, '芒种', 'Grain in Ear')
QING_MING = (4, 15, '清明', 'Clear and Bright')
QIU_FEN = (15, 180, '秋分', 'Autumn Equinox')
SHUANG_JIANG = (17, 210, '霜降', "Frost's Descent")
XIAO_HAN = (22, 285, '小寒', 'Minor Cold')
XIAO_MAN = (7, 60, '小满', 'Grain Buds')
XIAO_SHU = (10, 105, '小暑', 'Minor Heat')
XIAO_XUE = (19, 240, '小雪', 'Minor Snow')
XIA_ZHI = (9, 90, '夏至', 'Summer Solstice')
YU_SHUI = (1, 330, '雨水', 'Rain Water')
classmethod from_longitude(longitude)[source]

Get the solar term for a given solar longitude.

Return type:

SolarTerm

property is_major_term: bool

Major terms (Jie) mark Bazi month boundaries. They’re the odd-indexed terms.

class stellium.chinese.calendar.SolarTermEngine[source]

Bases: object

Calculates solar terms (Jie Qi) for Chinese calendar systems.

LI_CHUN_DEGREE = 315.0
classmethod find_month_start(jd)[source]

Find the exact moment the current Bazi month started.

Returns the SolarTermEvent for the Jie (major term) that began this month.

Return type:

SolarTermEvent

classmethod find_term_crossing(target_longitude, jd_start, direction='forward')[source]

Find the exact Julian Day when the Sun crosses a specific longitude.

Uses the existing search engine with hybrid Newton-Raphson / bisection.

Parameters:
  • target_longitude (float) – The solar longitude to find (0-360)

  • jd_start (float) – Julian Day to start searching from

  • direction (Literal['forward', 'backward']) – “forward” or “backward”

Return type:

float

Returns:

Julian Day of the crossing

classmethod get_bazi_month_index(jd)[source]

Get the Bazi month index (0-11) for a given Julian Day.

Month 0 = Tiger month (starts at Li Chun, 315°) Month 11 = Ox month (starts at Xiao Han, 285°)

Return type:

int

classmethod get_current_term(jd)[source]

Get the solar term that’s currently active at this Julian Day.

Return type:

SolarTerm

static get_solar_longitude(jd)[source]

Returns the Sun’s tropical longitude for a given Julian Day.

Return type:

float

class stellium.chinese.calendar.SolarTermEvent(term, datetime_utc, julian_day)[source]

Bases: object

A solar term occurrence at a specific moment in time.

datetime_utc: datetime
julian_day: float
term: SolarTerm

Protocols (interfaces) for Chinese astrology systems.

These define the common operations that all Chinese chart types should support, enabling polymorphic handling while allowing each system its unique structure.

class stellium.chinese.protocols.ChineseChart(*args, **kwargs)[source]

Bases: Protocol

Protocol for all Chinese astrology chart types.

This defines the minimum interface that Bazi, Zi Wei, Qi Men, etc. should all implement, enabling shared visualization and export logic.

property birth_datetime: datetime

The birth/event datetime used to calculate this chart.

display()[source]

Human-readable text representation of the chart.

Return type:

str

element_counts()[source]

Count of each element in the chart.

Return type:

dict[Element, int]

property system_name: str

The name of the system (e.g., ‘Bazi’, ‘Zi Wei Dou Shu’).

to_dict()[source]

Export chart data as a dictionary (for JSON serialization).

Return type:

dict[str, Any]

class stellium.chinese.protocols.ChineseChartEngine(*args, **kwargs)[source]

Bases: Protocol

Protocol for Chinese chart calculation engines.

Mirrors the pattern from Western astrology engines.

calculate(birth_datetime)[source]

Calculate a chart for the given datetime.

Return type:

ChineseChart

property system_name: str

The name of the system this engine calculates.

class stellium.chinese.protocols.ChineseChartRenderer(*args, **kwargs)[source]

Bases: Protocol

Protocol for rendering Chinese charts to visual formats.

Each system (Bazi, Zi Wei, etc.) will have its own renderer due to their vastly different visual structures.

render_svg(chart, **options)[source]

Render the chart as an SVG string.

Return type:

str

render_text(chart, **options)[source]

Render the chart as formatted text.

Return type:

str

Ba Zi (stellium.chinese.bazi)

Four Pillars of Destiny calculation engine.

Bazi (Four Pillars / 八字) astrology system.

Bazi, also known as Four Pillars of Destiny, is a Chinese astrological system that uses the year, month, day, and hour of birth to create a chart of eight characters (four pairs of Heavenly Stem + Earthly Branch).

Example

>>> from stellium.chinese.bazi import BaZiEngine
>>> from datetime import datetime
>>>
>>> engine = BaZiEngine(timezone_offset_hours=-8)  # PST
>>> chart = engine.calculate(datetime(1994, 1, 6, 11, 47))
>>>
>>> print(chart.hanzi)  # The eight characters
>>> print(chart.day_master)  # The Day Master (self)
>>> print(chart.display())  # Formatted table
>>> print(chart.display_detailed())  # With hidden stems and Ten Gods
stellium.chinese.bazi.BaZiCalculator

alias of BaZiEngine

class stellium.chinese.bazi.BaZiChart(year, month, day, hour, birth_datetime)[source]

Bases: object

A complete Four Pillars (Bazi / 八字) chart.

The Day Stem represents the “Day Master” (日主), which is the self.

Implements the ChineseChart protocol.

property all_branches: tuple[EarthlyBranch, ...]

All four earthly branches.

property all_hidden_stems: list[HeavenlyStem]

All hidden stems across all four pillars.

property all_stems: tuple[HeavenlyStem, ...]

All four heavenly stems.

birth_datetime: datetime
day: Pillar
property day_master: HeavenlyStem

The Day Master (日主) - the stem that represents the self.

property day_master_element: Element

The element of the Day Master.

display()[source]

Human-readable prose display of the chart.

Return type:

str

display_detailed()[source]

Detailed prose display including hidden stems and Ten Gods.

Return type:

str

element_counts(include_hidden=False)[source]

Count occurrences of each element across stems and branches.

Parameters:

include_hidden (bool) – If True, includes hidden stems in the count. Hidden stems are weighted: main=1.0, middle=0.5, residual=0.3

Return type:

dict[Element, int]

Note: For weighted hidden stem analysis, use element_strength() instead.

property hanzi: str

The eight characters (八字) in Chinese.

hour: Pillar
month: Pillar
property pillars: tuple[Pillar, Pillar, Pillar, Pillar]

year, month, day, hour.

Type:

All four pillars in order

polarity_counts()[source]

Count Yin vs Yang across all stems and branches.

Return type:

dict[Polarity, int]

strength()[source]

Analyze the Day Master’s strength.

Returns a StrengthAnalysis with classification (Strong/Weak/etc.), component scores, and favorable/unfavorable elements.

Example:

bazi = ChartBuilder.from_details("1994-01-06 11:47", "Palo Alto, CA").bazi()
result = bazi.strength()
print(result.strength.english)  # "Strong", "Weak", etc.
print(result.favorable_elements)
Return type:

StrengthAnalysis

property system_name: str

The name of this system.

ten_gods(include_hidden=True)[source]

Analyze Ten Gods (十神) relationships in the chart.

Parameters:

include_hidden (bool) – Whether to include hidden stems in analysis

Returns:

List of TenGodRelation objects

to_dict()[source]

Export chart data as a dictionary (JSON-serializable).

Return type:

dict[str, Any]

year: Pillar
class stellium.chinese.bazi.BaZiEngine(timezone_offset_hours=0.0)[source]

Bases: object

Calculate Bazi (Four Pillars) charts.

Implements the ChineseChartEngine protocol.

Example

>>> from datetime import datetime
>>> engine = BaZiEngine(timezone_offset_hours=-8)  # PST
>>> chart = engine.calculate(datetime(1994, 1, 6, 11, 47))
>>> print(chart.display())
calculate(birth_datetime)[source]

Calculate the Four Pillars chart for a birth datetime.

Parameters:

birth_datetime (datetime) – The birth date and time. Should be in local time if timezone_offset was provided, otherwise UTC.

Return type:

BaZiChart

Returns:

Complete BaZiChart with all four pillars.

property system_name: str

The name of the system this engine calculates.

class stellium.chinese.bazi.BaziProseRenderer(bullet='•')[source]

Bases: object

Render Bazi charts as natural language prose.

Designed for pasting into conversations or documents.

Example

>>> renderer = BaziProseRenderer()
>>> prose = renderer.render(chart)
>>> print(prose)
render(chart, include_hidden_stems=True, include_ten_gods=True)[source]

Render chart as prose text.

Parameters:
  • chart (BaZiChart) – The BaZiChart to render

  • include_hidden_stems (bool) – Include hidden stem analysis

  • include_ten_gods (bool) – Include Ten Gods relationships

Return type:

str

Returns:

Natural language description of the chart

class stellium.chinese.bazi.BaziRichRenderer[source]

Bases: object

Render Bazi charts using Rich library for beautiful terminal output.

Requires: pip install rich

Example

>>> from stellium.chinese.bazi import BaZiEngine
>>> from stellium.chinese.bazi.renderers import BaziRichRenderer
>>> from datetime import datetime
>>>
>>> engine = BaZiEngine(timezone_offset_hours=-8)
>>> chart = engine.calculate(datetime(1994, 1, 6, 11, 47))
>>>
>>> renderer = BaziRichRenderer()
>>> renderer.print_chart(chart)  # Prints to terminal
print_chart(chart, show_hidden_stems=True, show_ten_gods=True, show_summary=True)[source]

Print Bazi chart to terminal with Rich formatting.

Parameters:
  • chart (BaZiChart) – The BaZiChart to render

  • show_hidden_stems (bool) – Whether to show hidden stems in branches

  • show_ten_gods (bool) – Whether to show Ten Gods relationships

  • show_summary (bool) – Whether to show element/polarity summary

Return type:

None

render_chart(chart, show_hidden_stems=True, show_ten_gods=True, show_summary=True)[source]

Render Bazi chart to string (for file output).

Returns plain text with ANSI codes stripped.

Return type:

str

class stellium.chinese.bazi.BaziSVGRenderer(width=600, height=400, font_family='Noto Sans SC, SimSun, Microsoft YaHei, sans-serif')[source]

Bases: object

Render Bazi charts as SVG images.

Creates a visual representation of the Four Pillars with: - Color-coded elements - Hidden stems shown below main characters - Ten Gods labels - Element balance visualization

Example

>>> renderer = BaziSVGRenderer()
>>> svg_content = renderer.render(chart)
>>> with open("chart.svg", "w") as f:
...     f.write(svg_content)
ELEMENT_COLORS = {Element.EARTH: '#795548', Element.FIRE: '#f44336', Element.METAL: '#9e9e9e', Element.WATER: '#2196f3', Element.WOOD: '#4caf50'}
render(chart, show_hidden_stems=True, show_ten_gods=True, title=None)[source]

Render chart as SVG string.

Parameters:
  • chart (BaZiChart) – The BaZiChart to render

  • show_hidden_stems (bool) – Show hidden stems row

  • show_ten_gods (bool) – Show Ten Gods labels

  • title (str | None) – Optional title (defaults to birth datetime)

Return type:

str

Returns:

SVG content as string

render_to_file(chart, filepath, **kwargs)[source]

Render chart and save to file.

Parameters:
  • chart (BaZiChart) – The BaZiChart to render

  • filepath (str) – Output file path (should end in .svg)

  • **kwargs (Any) – Additional arguments passed to render()

Return type:

None

class stellium.chinese.bazi.DayMasterStrength(english, hanzi, pinyin)[source]

Bases: Enum

Classification of Day Master strength.

MODERATE = ('Moderate', '中和', 'zhōng hé')
STRONG = ('Strong', '旺', 'wàng')
VERY_STRONG = ('Very Strong', '极旺', 'jí wàng')
VERY_WEAK = ('Very Weak', '极弱', 'jí ruò')
WEAK = ('Weak', '弱', 'ruò')
class stellium.chinese.bazi.Pillar(stem, branch)[source]

Bases: object

A single pillar (柱) consisting of a Stem and Branch.

property animal: str

The zodiac animal of the branch.

branch: EarthlyBranch
property branch_element: Element

The element of the branch.

property hanzi: str

The two-character Chinese representation.

property hidden_stems: list[HeavenlyStem]

The hidden stems (藏干) within the branch.

property pinyin: str

Pinyin romanization.

stem: HeavenlyStem
property stem_element: Element

The element of the stem (primary element of the pillar).

to_dict()[source]

Export pillar data as a dictionary.

Return type:

dict[str, Any]

class stellium.chinese.bazi.StrengthAnalysis(day_master, day_master_element, strength, score, seasonal_score, root_count, support_count, drain_count, month_branch)[source]

Bases: object

Complete Day Master strength analysis result.

day_master: HeavenlyStem
day_master_element: Element
display()[source]

Human-readable summary.

Return type:

str

drain_count: int
property favorable_elements: list[Element]

Elements that are favorable for this Day Master.

If strong: needs Output, Wealth, Power to drain excess If weak: needs Companion, Resource to build strength

property is_strong: bool

Whether the Day Master is considered strong (旺 or 极旺).

property is_weak: bool

Whether the Day Master is considered weak (弱 or 极弱).

month_branch: EarthlyBranch
root_count: float
score: float
seasonal_score: int
strength: DayMasterStrength
support_count: int
to_dict()[source]

Export to JSON-serializable dictionary.

Return type:

dict

property unfavorable_elements: list[Element]

Elements that are unfavorable for this Day Master.

Opposite of favorable.

class stellium.chinese.bazi.TenGod(english, chinese, hanzi, short_code)[source]

Bases: Enum

The Ten Gods (十神) relationship types.

Each value contains: (english_name, chinese_name, hanzi, short_code)

BI_JIAN = ('Friend', '比肩', '比', 'BJ')
JIE_CAI = ('Rob Wealth', '劫财', '劫', 'JC')
PIAN_CAI = ('Indirect Wealth', '偏财', '偏财', 'PC')
PIAN_YIN = ('Indirect Seal', '偏印', '枭', 'PY')
QI_SHA = ('Seven Killings', '七杀', '杀', 'QS')
SELF = ('Self', '日主', '我', 'DM')
SHANG_GUAN = ('Hurting Officer', '伤官', '伤', 'SG')
SHI_SHEN = ('Eating God', '食神', '食', 'SS')
ZHENG_CAI = ('Direct Wealth', '正财', '正财', 'ZC')
ZHENG_GUAN = ('Direct Officer', '正官', '官', 'ZG')
ZHENG_YIN = ('Direct Seal', '正印', '印', 'ZY')
property category: str

The category this god belongs to.

property is_direct: bool

Whether this is a ‘direct/正’ relationship (different polarities).

class stellium.chinese.bazi.TenGodRelation(stem, ten_god, pillar_name, is_hidden=False)[source]

Bases: object

A Ten God relationship for a specific stem in the chart.

property display: str

Human-readable display.

is_hidden: bool = False
pillar_name: str
stem: HeavenlyStem
ten_god: TenGod
stellium.chinese.bazi.analyze_strength(chart)[source]

Analyze the Day Master’s strength in the chart.

This is the main entry point for strength analysis.

Parameters:

chart (BaZiChart) – A calculated BaZiChart

Return type:

StrengthAnalysis

Returns:

Complete StrengthAnalysis with classification and component scores

Example

>>> from stellium.chinese import BaZiEngine
>>> engine = BaZiEngine(timezone_offset_hours=8)
>>> chart = engine.calculate(datetime(1990, 5, 15, 10, 30))
>>> result = analyze_strength(chart)
>>> print(result.display())
stellium.chinese.bazi.analyze_ten_gods(chart, include_hidden=True)[source]

Analyze all Ten God relationships in a Bazi chart.

Parameters:
  • chart (BaZiChart) – The BaZiChart to analyze

  • include_hidden (bool) – Whether to include hidden stems in the analysis

Return type:

list[TenGodRelation]

Returns:

List of TenGodRelation objects for all stems in the chart

stellium.chinese.bazi.calculate_ten_god(day_master, other_stem)[source]

Calculate the Ten God relationship between Day Master and another stem.

Parameters:
  • day_master (HeavenlyStem) – The Day Master (日主) stem

  • other_stem (HeavenlyStem) – The stem to analyze

Return type:

TenGod

Returns:

The TenGod relationship

stellium.chinese.bazi.count_ten_god_categories(relations)[source]

Count Ten Gods by category (Companion, Output, Wealth, Power, Resource).

Parameters:

relations (list[TenGodRelation]) – List of TenGodRelation from analyze_ten_gods()

Return type:

dict[str, int]

Returns:

Dictionary mapping category name to count

stellium.chinese.bazi.count_ten_gods(relations)[source]

Count occurrences of each Ten God in the chart.

Parameters:

relations (list[TenGodRelation]) – List of TenGodRelation from analyze_ten_gods()

Return type:

dict[TenGod, int]

Returns:

Dictionary mapping TenGod to count

Bazi (Four Pillars) calculation engine.

This module implements the traditional calculation methods: - Year Pillar: Based on the Chinese year (starting at Li Chun) - Month Pillar: Based on solar terms, using the “Five Tigers” (五虎遁) formula - Day Pillar: Based on the day count from a reference date - Hour Pillar: Based on the two-hour periods, using “Five Rats” (五鼠遁) formula

Reference date for day pillar: Feb 12, 1900 = Jia Zi day (甲子日)

Implements the ChineseChartEngine protocol.

stellium.chinese.bazi.engine.BaZiCalculator

alias of BaZiEngine

class stellium.chinese.bazi.engine.BaZiEngine(timezone_offset_hours=0.0)[source]

Bases: object

Calculate Bazi (Four Pillars) charts.

Implements the ChineseChartEngine protocol.

Example

>>> from datetime import datetime
>>> engine = BaZiEngine(timezone_offset_hours=-8)  # PST
>>> chart = engine.calculate(datetime(1994, 1, 6, 11, 47))
>>> print(chart.display())
calculate(birth_datetime)[source]

Calculate the Four Pillars chart for a birth datetime.

Parameters:

birth_datetime (datetime) – The birth date and time. Should be in local time if timezone_offset was provided, otherwise UTC.

Return type:

BaZiChart

Returns:

Complete BaZiChart with all four pillars.

property system_name: str

The name of the system this engine calculates.

stellium.chinese.bazi.engine.hour_to_branch_index(hour, minute=0)[source]

Convert clock hour to Earthly Branch index.

The Chinese day starts at 23:00 (Zi hour). Each branch covers 2 hours: - Zi (子): 23:00-00:59 - Chou (丑): 01:00-02:59 - Yin (寅): 03:00-04:59 - etc.

Parameters:
  • hour (int) – Hour in 24-hour format (0-23)

  • minute (int) – Minutes (0-59)

Return type:

int

Returns:

Branch index 0-11

Bazi (Four Pillars) chart data models.

A Bazi chart consists of four pillars: - Year Pillar (年柱) - represents ancestors, early childhood - Month Pillar (月柱) - represents parents, career - Day Pillar (日柱) - represents self, spouse - Hour Pillar (时柱) - represents children, later life

Each pillar has a Heavenly Stem and an Earthly Branch.

Implements the ChineseChart protocol for interoperability with other systems.

class stellium.chinese.bazi.models.BaZiChart(year, month, day, hour, birth_datetime)[source]

Bases: object

A complete Four Pillars (Bazi / 八字) chart.

The Day Stem represents the “Day Master” (日主), which is the self.

Implements the ChineseChart protocol.

property all_branches: tuple[EarthlyBranch, ...]

All four earthly branches.

property all_hidden_stems: list[HeavenlyStem]

All hidden stems across all four pillars.

property all_stems: tuple[HeavenlyStem, ...]

All four heavenly stems.

birth_datetime: datetime
day: Pillar
property day_master: HeavenlyStem

The Day Master (日主) - the stem that represents the self.

property day_master_element: Element

The element of the Day Master.

display()[source]

Human-readable prose display of the chart.

Return type:

str

display_detailed()[source]

Detailed prose display including hidden stems and Ten Gods.

Return type:

str

element_counts(include_hidden=False)[source]

Count occurrences of each element across stems and branches.

Parameters:

include_hidden (bool) – If True, includes hidden stems in the count. Hidden stems are weighted: main=1.0, middle=0.5, residual=0.3

Return type:

dict[Element, int]

Note: For weighted hidden stem analysis, use element_strength() instead.

property hanzi: str

The eight characters (八字) in Chinese.

hour: Pillar
month: Pillar
property pillars: tuple[Pillar, Pillar, Pillar, Pillar]

year, month, day, hour.

Type:

All four pillars in order

polarity_counts()[source]

Count Yin vs Yang across all stems and branches.

Return type:

dict[Polarity, int]

strength()[source]

Analyze the Day Master’s strength.

Returns a StrengthAnalysis with classification (Strong/Weak/etc.), component scores, and favorable/unfavorable elements.

Example:

bazi = ChartBuilder.from_details("1994-01-06 11:47", "Palo Alto, CA").bazi()
result = bazi.strength()
print(result.strength.english)  # "Strong", "Weak", etc.
print(result.favorable_elements)
Return type:

StrengthAnalysis

property system_name: str

The name of this system.

ten_gods(include_hidden=True)[source]

Analyze Ten Gods (十神) relationships in the chart.

Parameters:

include_hidden (bool) – Whether to include hidden stems in analysis

Returns:

List of TenGodRelation objects

to_dict()[source]

Export chart data as a dictionary (JSON-serializable).

Return type:

dict[str, Any]

year: Pillar
class stellium.chinese.bazi.models.Pillar(stem, branch)[source]

Bases: object

A single pillar (柱) consisting of a Stem and Branch.

property animal: str

The zodiac animal of the branch.

branch: EarthlyBranch
property branch_element: Element

The element of the branch.

property hanzi: str

The two-character Chinese representation.

property hidden_stems: list[HeavenlyStem]

The hidden stems (藏干) within the branch.

property pinyin: str

Pinyin romanization.

stem: HeavenlyStem
property stem_element: Element

The element of the stem (primary element of the pillar).

to_dict()[source]

Export pillar data as a dictionary.

Return type:

dict[str, Any]

Ten Gods (十神) analysis for Bazi charts.

The Ten Gods represent the relationship between the Day Master (日主) and other stems in the chart. Each relationship has both productive and challenging aspects.

The Ten Gods are determined by two factors: 1. Element relationship (same, produces, produced-by, controls, controlled-by) 2. Polarity match (same polarity = “indirect/偏”, different polarity = “direct/正”)

Note: The Day Master stem always has relationship “Self” (日主/我) with itself.

class stellium.chinese.bazi.analysis.TenGod(english, chinese, hanzi, short_code)[source]

Bases: Enum

The Ten Gods (十神) relationship types.

Each value contains: (english_name, chinese_name, hanzi, short_code)

BI_JIAN = ('Friend', '比肩', '比', 'BJ')
JIE_CAI = ('Rob Wealth', '劫财', '劫', 'JC')
PIAN_CAI = ('Indirect Wealth', '偏财', '偏财', 'PC')
PIAN_YIN = ('Indirect Seal', '偏印', '枭', 'PY')
QI_SHA = ('Seven Killings', '七杀', '杀', 'QS')
SELF = ('Self', '日主', '我', 'DM')
SHANG_GUAN = ('Hurting Officer', '伤官', '伤', 'SG')
SHI_SHEN = ('Eating God', '食神', '食', 'SS')
ZHENG_CAI = ('Direct Wealth', '正财', '正财', 'ZC')
ZHENG_GUAN = ('Direct Officer', '正官', '官', 'ZG')
ZHENG_YIN = ('Direct Seal', '正印', '印', 'ZY')
property category: str

The category this god belongs to.

property is_direct: bool

Whether this is a ‘direct/正’ relationship (different polarities).

class stellium.chinese.bazi.analysis.TenGodRelation(stem, ten_god, pillar_name, is_hidden=False)[source]

Bases: object

A Ten God relationship for a specific stem in the chart.

property display: str

Human-readable display.

is_hidden: bool = False
pillar_name: str
stem: HeavenlyStem
ten_god: TenGod
stellium.chinese.bazi.analysis.analyze_ten_gods(chart, include_hidden=True)[source]

Analyze all Ten God relationships in a Bazi chart.

Parameters:
  • chart (BaZiChart) – The BaZiChart to analyze

  • include_hidden (bool) – Whether to include hidden stems in the analysis

Return type:

list[TenGodRelation]

Returns:

List of TenGodRelation objects for all stems in the chart

stellium.chinese.bazi.analysis.calculate_ten_god(day_master, other_stem)[source]

Calculate the Ten God relationship between Day Master and another stem.

Parameters:
  • day_master (HeavenlyStem) – The Day Master (日主) stem

  • other_stem (HeavenlyStem) – The stem to analyze

Return type:

TenGod

Returns:

The TenGod relationship

stellium.chinese.bazi.analysis.count_ten_god_categories(relations)[source]

Count Ten Gods by category (Companion, Output, Wealth, Power, Resource).

Parameters:

relations (list[TenGodRelation]) – List of TenGodRelation from analyze_ten_gods()

Return type:

dict[str, int]

Returns:

Dictionary mapping category name to count

stellium.chinese.bazi.analysis.count_ten_gods(relations)[source]

Count occurrences of each Ten God in the chart.

Parameters:

relations (list[TenGodRelation]) – List of TenGodRelation from analyze_ten_gods()

Return type:

dict[TenGod, int]

Returns:

Dictionary mapping TenGod to count

stellium.chinese.bazi.analysis.get_element_relationship(day_master_element, other_element)[source]

Determine the Wu Xing relationship between Day Master and another element.

Returns one of: “same”, “produces”, “produced_by”, “controls”, “controlled_by”

Return type:

str

stellium.chinese.bazi.analysis.get_ten_gods_for_pillar(relations, pillar_name)[source]

Get all Ten God relations for a specific pillar (including hidden stems).

Parameters:
  • relations (list[TenGodRelation]) – List of TenGodRelation from analyze_ten_gods()

  • pillar_name (str) – One of “year”, “month”, “day”, “hour”

Return type:

list[TenGodRelation]

Returns:

List of TenGodRelation for that pillar

Renderers for Bazi (Four Pillars) charts.

This module provides different output formats for Bazi charts: - BaziRichRenderer: Beautiful terminal output using Rich library - BaziSVGRenderer: Visual SVG chart rendering - BaziProseRenderer: Natural language prose output

class stellium.chinese.bazi.renderers.BaziProseRenderer(bullet='•')[source]

Bases: object

Render Bazi charts as natural language prose.

Designed for pasting into conversations or documents.

Example

>>> renderer = BaziProseRenderer()
>>> prose = renderer.render(chart)
>>> print(prose)
render(chart, include_hidden_stems=True, include_ten_gods=True)[source]

Render chart as prose text.

Parameters:
  • chart (BaZiChart) – The BaZiChart to render

  • include_hidden_stems (bool) – Include hidden stem analysis

  • include_ten_gods (bool) – Include Ten Gods relationships

Return type:

str

Returns:

Natural language description of the chart

class stellium.chinese.bazi.renderers.BaziRichRenderer[source]

Bases: object

Render Bazi charts using Rich library for beautiful terminal output.

Requires: pip install rich

Example

>>> from stellium.chinese.bazi import BaZiEngine
>>> from stellium.chinese.bazi.renderers import BaziRichRenderer
>>> from datetime import datetime
>>>
>>> engine = BaZiEngine(timezone_offset_hours=-8)
>>> chart = engine.calculate(datetime(1994, 1, 6, 11, 47))
>>>
>>> renderer = BaziRichRenderer()
>>> renderer.print_chart(chart)  # Prints to terminal
print_chart(chart, show_hidden_stems=True, show_ten_gods=True, show_summary=True)[source]

Print Bazi chart to terminal with Rich formatting.

Parameters:
  • chart (BaZiChart) – The BaZiChart to render

  • show_hidden_stems (bool) – Whether to show hidden stems in branches

  • show_ten_gods (bool) – Whether to show Ten Gods relationships

  • show_summary (bool) – Whether to show element/polarity summary

Return type:

None

render_chart(chart, show_hidden_stems=True, show_ten_gods=True, show_summary=True)[source]

Render Bazi chart to string (for file output).

Returns plain text with ANSI codes stripped.

Return type:

str

class stellium.chinese.bazi.renderers.BaziSVGRenderer(width=600, height=400, font_family='Noto Sans SC, SimSun, Microsoft YaHei, sans-serif')[source]

Bases: object

Render Bazi charts as SVG images.

Creates a visual representation of the Four Pillars with: - Color-coded elements - Hidden stems shown below main characters - Ten Gods labels - Element balance visualization

Example

>>> renderer = BaziSVGRenderer()
>>> svg_content = renderer.render(chart)
>>> with open("chart.svg", "w") as f:
...     f.write(svg_content)
ELEMENT_COLORS = {Element.EARTH: '#795548', Element.FIRE: '#f44336', Element.METAL: '#9e9e9e', Element.WATER: '#2196f3', Element.WOOD: '#4caf50'}
render(chart, show_hidden_stems=True, show_ten_gods=True, title=None)[source]

Render chart as SVG string.

Parameters:
  • chart (BaZiChart) – The BaZiChart to render

  • show_hidden_stems (bool) – Show hidden stems row

  • show_ten_gods (bool) – Show Ten Gods labels

  • title (str | None) – Optional title (defaults to birth datetime)

Return type:

str

Returns:

SVG content as string

render_to_file(chart, filepath, **kwargs)[source]

Render chart and save to file.

Parameters:
  • chart (BaZiChart) – The BaZiChart to render

  • filepath (str) – Output file path (should end in .svg)

  • **kwargs (Any) – Additional arguments passed to render()

Return type:

None


CLI (stellium.cli)

Command-line interface for chart generation, cache management, and ephemeris downloads.


Indices and tables