Astrology¶
A natal-chart engine: tropical or sidereal zodiac, ten planets plus the lunar nodes and the Ascendant/Midheaven, Whole Sign / Equal / Placidus houses, and aspects rendered into the summary. A lightweight sun-sign spread is also available for callers who have only a birthday.
from fortune_telling_core import Querent, ReadingRequest
from fortune_telling_core.traditions.astrology import NATAL_CHART, TROPICAL_ZODIAC, build_engine
engine = build_engine()
request = ReadingRequest(
deck_id=TROPICAL_ZODIAC.id,
spread_id=NATAL_CHART.id,
querent=Querent(
id="sample",
display_name="Sample",
attributes={
"birth_datetime": "1990-01-01T12:00:00+00:00",
"latitude": "51.5074",
"longitude": "-0.1278",
"house_system": "whole_sign",
},
),
)
reading = engine.cast(request)
Sun sign¶
The SUN_SIGN spread is a single-placement reading that needs only the
querent's zodiac sign — no birth time, latitude, or longitude — so it serves
callers who know just a birthday. The sign is taken from an explicit sun_sign
attribute (a Sign, a sign symbol id like astro.sign.leo, or a bare slug like
leo); when that is absent, a birth_date (or birth_datetime — only the
calendar date is used) is classified into a sign using the conventional Western
tropical date ranges (sign_for_date / zodiac_date_range). The placement
reuses the sun position id, so the same Sun-in-sign interpretation data
applies as in the natal chart.
Sun-sign readings are always tropical: the conventional date ranges are a tropical convention, and a sidereal sun sign is not well-defined from a date alone. The reading touches neither the ephemeris nor birth time and place, and like the natal chart it is deterministic and replay-safe.
from fortune_telling_core.traditions.astrology import SUN_SIGN, TROPICAL_ZODIAC
reading = engine.cast(
ReadingRequest(
deck_id=TROPICAL_ZODIAC.id,
spread_id=SUN_SIGN.id,
querent=Querent(
id="sample",
display_name="Sample",
attributes={"birth_date": "1990-04-15"}, # or {"sun_sign": "aries"}
),
)
)
# reading.summary == "Sun sign Aries (fire, cardinal), Mar 21 – Apr 19."
Transits¶
Set the request's as_of moment to add a
transit report: the engine computes the transiting bodies for that instant and
appends every transit-to-natal aspect (transiting planets against natal planets
and angles) to the summary, under a Transits as of … heading. Without as_of
the reading is the pure, timeless natal chart. The transit longitudes are stored
on the draw, so replays are ephemeris-free like the natal chart. The transiting
set drops the South Node (the mirror of the North Node) to avoid doubled lines;
aspect orbs are the same defaults used for natal aspects.
Structured aspects¶
Aspects are also exposed as structured data, not just summary prose, so an
interpretation layer can localize them without recomputing any astronomy. Each
aspect is an entry in Reading.draw.extras — a Selection with
symbol_id = "astro.aspect.<type>" (type ∈ conjunction / opposition / trine /
square / sextile) and modifiers first, second (body ids), orb, and kind
(natal or transit; for a transit, first is the transiting body and
second the natal body). extras are draw selections not bound to a spread
position, so they round-trip through serde and replay like everything else. The
freeform summary is unchanged and always agrees with the structured set.
from datetime import UTC, datetime
reading = engine.cast(
ReadingRequest(
deck_id=TROPICAL_ZODIAC.id,
spread_id=NATAL_CHART.id,
querent=request.querent,
as_of=datetime(2026, 6, 16, tzinfo=UTC), # transits for this moment
)
)
astrology ¶
NATAL_CHART
module-attribute
¶
NATAL_CHART = Spread(
id="astro.spread.natal.v1",
name="Natal Chart",
positions=tuple(
(
Position(
id=position_id,
name=POSITION_NAMES[position_id],
description=f"Natal placement for {POSITION_NAMES[position_id]}.",
)
)
for position_id in NATAL_POSITIONS
),
)
SUN_SIGN
module-attribute
¶
SUN_SIGN = Spread(
id="astro.spread.sun_sign.v1",
name="Sun Sign",
positions=(
Position(
id=Body.SUN.value,
name="Sun Sign",
description="Zodiac sun sign for the birth date.",
),
),
)
SIDEREAL_ZODIAC
module-attribute
¶
TROPICAL_ZODIAC
module-attribute
¶
BuiltinEphemeris ¶
Pure-Python deterministic ephemeris shipped with the package.
The built-in backend keeps runtime dependencies empty and is suitable for deterministic tests and general symbolic work. It is not a replacement for a high-precision professional astronomy backend.
Example
supported_bodies ¶
position ¶
Return an apparent geocentric ecliptic position.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
body
|
Body
|
Body to compute. |
required |
jd_tt
|
float
|
Julian day on the Terrestrial Time scale. |
required |
Returns:
| Type | Description |
|---|---|
EclipticPosition
|
Ecliptic longitude, approximate longitude speed, and Moon latitude. |
Raises:
| Type | Description |
|---|---|
EphemerisError
|
If |
Source code in src/fortune_telling_core/astronomy/ephemeris/builtin.py
FixedEphemeris ¶
Deterministic ephemeris backed by a fixed position mapping.
This backend is intended for tests, examples, and replay fixtures where the caller wants exact symbolic placements without astronomical calculation.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
positions
|
Mapping[Body, EclipticPosition]
|
Mapping from body identifiers to ecliptic positions. If a north node is supplied without a south node, the south node is derived as the opposite longitude. |
required |
Example
Source code in src/fortune_telling_core/astronomy/ephemeris/fixed.py
supported_bodies ¶
position ¶
Return the fixed position for a body.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
body
|
Body
|
Body to look up. |
required |
jd_tt
|
float
|
Ignored Julian day, accepted for protocol compatibility. |
required |
Returns:
| Type | Description |
|---|---|
EclipticPosition
|
The configured ecliptic position. |
Raises:
| Type | Description |
|---|---|
EphemerisError
|
If no position exists for |
Source code in src/fortune_telling_core/astronomy/ephemeris/fixed.py
Ephemeris ¶
Bases: Protocol
Protocol for injectable astronomy backends.
Implementations provide apparent geocentric ecliptic positions in degrees.
Latitude may be None when a backend or body does not compute it. The
fortune-telling traditions depend only on this protocol, so applications
can bring their own precision and licensing choices.
Attributes:
| Name | Type | Description |
|---|---|---|
id |
str
|
Stable backend identifier recorded in reading provenance. |
version |
str
|
Backend version recorded in reading provenance. |
position ¶
Return an apparent geocentric ecliptic position.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
body
|
Body
|
Body to compute. |
required |
jd_tt
|
float
|
Julian day on the Terrestrial Time scale. |
required |
Returns:
| Type | Description |
|---|---|
EclipticPosition
|
Ecliptic position for the requested body. |
Raises:
| Type | Description |
|---|---|
EphemerisError
|
If the backend cannot compute the requested body. |
Source code in src/fortune_telling_core/astronomy/ephemeris/protocol.py
AstrologyEngine ¶
Bases: AbstractEngine
Natal astrology engine using an injectable ephemeris.
The engine casts deterministic natal charts. It ignores caller-supplied randomness and records the ephemeris, house system, and zodiac mode in reading provenance.
The engine also serves the lightweight :data:SUN_SIGN spread, which needs
only the querent's zodiac sign (from an explicit sun_sign or a birth
date) and uses neither the ephemeris nor birth time and location. Sun-sign
readings are always tropical.
When the request carries as_of, the engine additionally computes the
transiting bodies for that moment and appends transit-to-natal aspects to
the summary. The natal chart is timeless, so without as_of the reading
is the pure natal chart. Transit positions are stored on the draw, so a
replay reproduces them without the ephemeris.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
ephemeris
|
Ephemeris | None
|
Optional backend implementing the shared ephemeris protocol.
When omitted, |
None
|
Source code in src/fortune_telling_core/traditions/astrology/engine.py
deck ¶
Return the zodiac deck implied by the request.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
request
|
ReadingRequest
|
Reading request containing birth data and optional
|
required |
Returns:
| Type | Description |
|---|---|
Deck
|
|
Deck
|
when a natal request asks for sidereal astrology, otherwise |
Deck
|
|
Raises:
| Type | Description |
|---|---|
ValidationError
|
If required birth data or configuration is invalid. |
Source code in src/fortune_telling_core/traditions/astrology/engine.py
spread ¶
Return the spread implied by the request.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
request
|
ReadingRequest
|
Reading request whose |
required |
Returns:
| Type | Description |
|---|---|
Spread
|
The natal chart spread or the sun-sign spread. |
Raises:
| Type | Description |
|---|---|
ValidationError
|
If the requested spread is unsupported. |
Source code in src/fortune_telling_core/traditions/astrology/engine.py
draw ¶
Cast the natal chart as a deterministic draw.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
request
|
ReadingRequest
|
Reading request with |
required |
rng
|
Rng
|
Ignored. The argument is present for |
required |
Returns:
| Type | Description |
|---|---|
Draw
|
A draw whose selections place bodies and angles in zodiac signs. |
Raises:
| Type | Description |
|---|---|
ValidationError
|
If request birth data or chart configuration is invalid. |
EphemerisError
|
If the configured ephemeris cannot compute a required body. |
Source code in src/fortune_telling_core/traditions/astrology/engine.py
cast ¶
Cast a natal chart without a caller RNG.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
request
|
ReadingRequest
|
Reading request containing birth data and chart options. |
required |
Returns:
| Type | Description |
|---|---|
Reading
|
A reading with body placements and aspect summary. |
Raises:
| Type | Description |
|---|---|
ValidationError
|
If request birth data or chart configuration is invalid. |
EphemerisError
|
If the configured ephemeris cannot compute a required body. |
Source code in src/fortune_telling_core/traditions/astrology/engine.py
Sign ¶
Bases: StrEnum
The twelve zodiac signs in canonical Aries-to-Pisces order.
Members are the lowercase slugs used throughout the package. This enum is the single source of truth for the set of signs and their order; sign decks, longitude-to-sign lookups and date ranges all derive from it.
sign_for_date ¶
Return the sign symbol id whose conventional range contains a date.
Classification compares on (month, day) against the same Western
tropical boundaries as :func:zodiac_date_range, so it is correct in both
common and leap years (including 29 February). Only the month and day of
value are consulted; the year is otherwise ignored.
These are conventional boundaries, not an astronomically exact ingress: for a precise sun sign on a given instant, cast a reading with the astrology engine instead.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
value
|
date
|
The calendar date to classify. |
required |
Returns:
| Type | Description |
|---|---|
str
|
The matching sign symbol id, e.g. |
Source code in src/fortune_telling_core/traditions/astrology/dates.py
zodiac_date_range ¶
Return the conventional sun-sign date range for a zodiac sign.
The range uses the common Western tropical convention and is expressed as
year-independent (month, day) boundaries, both inclusive. Capricorn and
Pisces wrap across the new year, so their start boundary falls in a later
month than their end boundary.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
sign
|
str | Sign | Symbol
|
A zodiac sign as a :class: |
required |
Returns:
| Type | Description |
|---|---|
tuple[MonthDay, MonthDay]
|
A |
Raises:
| Type | Description |
|---|---|
ValidationError
|
If the sign is not a recognised zodiac sign. |
Source code in src/fortune_telling_core/traditions/astrology/dates.py
build_engine ¶
Create a natal astrology engine.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
ephemeris
|
Ephemeris | None
|
Optional ephemeris backend. Defaults to the built-in pure-Python backend. |
None
|
Returns:
| Type | Description |
|---|---|
AstrologyEngine
|
A new |