Skip to content

Sukuyō (宿曜)

A Sukuyō astrology engine that derives a birth mansion (本命宿) from the Moon's sidereal ecliptic longitude at birth, using the bundled ephemeris. The 27 mansions (二十七宿) are equal 13°20′ sidereal divisions coinciding with the nakshatras; the sidereal zero-point is selected via the Ayanamsa option (default Lahiri). The traditional lunisolar-table method is not bundled.

from fortune_telling_core import Querent, ReadingRequest
from fortune_telling_core.traditions.sukuyo import SUKUYO_DECK, SUKUYO_SPREAD, build_engine

request = ReadingRequest(
    deck_id=SUKUYO_DECK.id,
    spread_id=SUKUYO_SPREAD.id,
    querent=Querent(
        id="sample",
        display_name="Sample",
        attributes={"birth_datetime": "1990-05-17T09:30:00+09:00"},
    ),
)
reading = build_engine().cast(request)

sukuyo

SUKUYO_DECK module-attribute

SUKUYO_DECK = Deck(
    id="sukuyo.deck.mansions27.v1",
    symbols=tuple(
        (
            Symbol(
                id=f"sukuyo.mansion.{mansion.slug}",
                name=f"{mansion.cjk} ({mansion.nakshatra})",
                attributes={
                    "cjk": mansion.cjk,
                    "nakshatra": mansion.nakshatra,
                    "index": str(mansion.index),
                },
            )
        )
        for mansion in MANSIONS
    ),
)

SUKUYO_SPREAD module-attribute

SUKUYO_SPREAD = Spread(
    id="sukuyo.spread.birth_mansion.v1",
    name="Sukuyō Birth Mansion",
    positions=(
        Position(
            "birth_mansion",
            "Birth Mansion",
            "The 本命宿 — the lunar mansion the Moon occupied at birth.",
        ),
    ),
)

Ayanamsa

Bases: StrEnum

Sidereal zero-point (ayanamsa) used to derive the birth mansion.

The 27 mansions are equal 13°20′ sidereal divisions, so the ayanamsa fixes which mansion a Moon longitude falls in. LAHIRI is the default and the most common choice for nakshatra-derived systems. FAGAN_BRADLEY is the Western sidereal standard. NONE keeps tropical longitudes (no correction), mainly for testing and comparison.

Both non-zero ayanamsas use the same precession rate and differ only by a fixed epoch offset; they are documented linear approximations adequate for mansion determination, not high-precision astrology.

LAHIRI class-attribute instance-attribute

LAHIRI = 'lahiri'

FAGAN_BRADLEY class-attribute instance-attribute

FAGAN_BRADLEY = 'fagan_bradley'

NONE class-attribute instance-attribute

NONE = 'none'

Method

Bases: StrEnum

Birth-mansion determination method.

MOON_LONGITUDE derives the mansion from the Moon's sidereal ecliptic longitude at birth using the bundled ephemeris. The traditional lunisolar-table (月宿傍通暦) method is not bundled; it can be added here as a future value.

MOON_LONGITUDE class-attribute instance-attribute

MOON_LONGITUDE = 'moon_longitude'

SukuyoEngine

SukuyoEngine(
    ephemeris: Ephemeris | None = None,
    *,
    ayanamsa: Ayanamsa = Ayanamsa.LAHIRI,
    method: Method = Method.MOON_LONGITUDE,
)

Bases: AbstractEngine

Sukuyō birth-mansion engine.

Parameters:

Name Type Description Default
ephemeris Ephemeris | None

Optional backend implementing the shared ephemeris protocol. Defaults to BuiltinEphemeris.

None
ayanamsa Ayanamsa

Default sidereal zero-point used when the request does not specify one.

LAHIRI
method Method

Default birth-mansion method used when the request does not specify one.

MOON_LONGITUDE
Source code in src/fortune_telling_core/traditions/sukuyo/engine.py
def __init__(
    self,
    ephemeris: Ephemeris | None = None,
    *,
    ayanamsa: Ayanamsa = Ayanamsa.LAHIRI,
    method: Method = Method.MOON_LONGITUDE,
) -> None:
    self.ephemeris = ephemeris or BuiltinEphemeris()
    self.ayanamsa = ayanamsa
    self.method = method

id class-attribute instance-attribute

id = 'sukuyo.engine'

version class-attribute instance-attribute

version = '0.1.0'

ephemeris instance-attribute

ephemeris = ephemeris or BuiltinEphemeris()

ayanamsa instance-attribute

ayanamsa = ayanamsa

method instance-attribute

method = method

deck

deck(request: ReadingRequest) -> Deck

Return the Sukuyō 27-mansion deck.

Source code in src/fortune_telling_core/traditions/sukuyo/engine.py
def deck(self, request: ReadingRequest) -> Deck:
    """Return the Sukuyō 27-mansion deck."""

    if request.deck_id != SUKUYO_DECK.id:
        raise ValidationError(f"unsupported Sukuyō deck: {request.deck_id}")
    return SUKUYO_DECK

spread

spread(request: ReadingRequest) -> Spread

Return the Sukuyō birth-mansion spread.

Source code in src/fortune_telling_core/traditions/sukuyo/engine.py
def spread(self, request: ReadingRequest) -> Spread:
    """Return the Sukuyō birth-mansion spread."""

    if request.spread_id != SUKUYO_SPREAD.id:
        raise ValidationError(f"unsupported Sukuyō spread: {request.spread_id}")
    return SUKUYO_SPREAD

draw

draw(request: ReadingRequest, rng: Rng) -> Draw

Compute the birth mansion as a deterministic draw.

Source code in src/fortune_telling_core/traditions/sukuyo/engine.py
def draw(self, request: ReadingRequest, rng: Rng) -> Draw:
    """Compute the birth mansion as a deterministic draw."""

    del rng
    birth = self._birth(request)
    jd_tt = jd_tt_from_utc(julian_day_utc(birth.birth_datetime))
    moon_longitude = self.ephemeris.position(Body.MOON, jd_tt).longitude
    mansion = mansion_for_longitude(moon_longitude, jd_tt, birth.ayanamsa)
    sidereal = (moon_longitude - ayanamsa_degrees(jd_tt, birth.ayanamsa)) % 360.0
    selection = Selection(
        "birth_mansion",
        f"sukuyo.mansion.{mansion.slug}",
        {
            "cjk": mansion.cjk,
            "nakshatra": mansion.nakshatra,
            "mansion_index": str(mansion.index),
            "moon_longitude": f"{moon_longitude:.6f}",
            "sidereal_longitude": f"{sidereal:.6f}",
            "ayanamsa": birth.ayanamsa.value,
        },
    )
    return Draw(SUKUYO_DECK.id, SUKUYO_SPREAD.id, (selection,))

cast

cast(request: ReadingRequest) -> Reading

Compute a Sukuyō reading without a caller RNG.

Source code in src/fortune_telling_core/traditions/sukuyo/engine.py
def cast(self, request: ReadingRequest) -> Reading:
    """Compute a Sukuyō reading without a caller RNG."""

    draw = self.draw(request, _NULL_RNG)
    return self._interpret(request, draw, rng=None)

build_engine

build_engine(
    ephemeris: Ephemeris | None = None,
    *,
    ayanamsa: Ayanamsa = Ayanamsa.LAHIRI,
    method: Method = Method.MOON_LONGITUDE,
) -> SukuyoEngine

Create a Sukuyō engine.

Source code in src/fortune_telling_core/traditions/sukuyo/engine.py
def build_engine(
    ephemeris: Ephemeris | None = None,
    *,
    ayanamsa: Ayanamsa = Ayanamsa.LAHIRI,
    method: Method = Method.MOON_LONGITUDE,
) -> SukuyoEngine:
    """Create a Sukuyō engine."""

    return SukuyoEngine(ephemeris=ephemeris, ayanamsa=ayanamsa, method=method)