Skip to content

Nine Star Ki (九星気学)

A Nine Star Ki engine computing the principal, monthly, daily, and tendency stars, plus the annual and monthly Lo Shu flying-star charts rendered into the summary.

from fortune_telling_core import Querent, ReadingRequest
from fortune_telling_core.traditions.nine_star_ki import (
    DayStarEscapement,
    NINE_STAR_KI_DECK,
    NINE_STAR_KI_SPREAD,
    build_engine,
)

engine = build_engine(
    target_year=2026,
    day_star_escapement=DayStarEscapement.JIAZI_AT_OR_BEFORE_SOLSTICE,
)
request = ReadingRequest(
    deck_id=NINE_STAR_KI_DECK.id,
    spread_id=NINE_STAR_KI_SPREAD.id,
    querent=Querent(
        id="sample",
        display_name="Sample",
        attributes={
            "birth_datetime": "1990-01-01T12:00:00+00:00",
            "latitude": "51.5074",
            "longitude": "-0.1278",
        },
    ),
)

reading = engine.cast(request)

Day-star escapement

Daily stars reverse direction around the winter and summer solstices, but schools differ on the anchor day used for the first star in the new arc. build_engine() accepts day_star_escapement, and requests may override it with the day_star_escapement option.

The default is jiazi_at_or_before_solstice, which preserves this package's original behavior: Star 1 or Star 9 is anchored to the Jia-Zi day at or before the active solstice. The alternative first_jiazi_after_solstice follows the Daily Flying Star convention that anchors Star 1 or Star 9 to the first Jia-Zi day after the relevant solstice. The selected value is recorded in Reading.provenance.notes.

Annual chart year

The annual flying-star chart year is resolved most-specific-first: an explicit target_year attribute wins, then the request-level as_of moment, then the engine's build-time target_year, then requested_at.

nine_star_ki

NINE_STAR_KI_DECK module-attribute

NINE_STAR_KI_DECK = Deck(
    id="nsk.deck.stars.v1",
    symbols=tuple(
        (
            Symbol(
                id=star.symbol_id,
                name=star.cjk,
                attributes={
                    "number": str(star.number),
                    "slug": star.slug,
                    "cjk": star.cjk,
                    "color": star.color,
                    "element": star.element,
                    "trigram": star.trigram,
                    "home_palace": star.home_palace,
                },
            )
        )
        for star in STARS
    ),
)

NINE_STAR_KI_SPREAD module-attribute

NINE_STAR_KI_SPREAD = Spread(
    id="nsk.spread.natal.v1",
    name="Nine Star Ki",
    positions=(
        Position(
            "principal",
            "Principal Star",
            "Year star from the Risshun solar year.",
        ),
        Position(
            "monthly",
            "Monthly Star",
            "Month star from the sectional solar month.",
        ),
        Position(
            "daily",
            "Daily Star",
            "Day star from the solstice escapement cycle.",
        ),
        Position(
            "tendency",
            "Tendency Star",
            "Inclination star from the natal year chart.",
        ),
    ),
)

DayStarEscapement

Bases: StrEnum

Daily-star solstice reversal schools.

JIAZI_AT_OR_BEFORE_SOLSTICE is this package's compatibility default: the active winter or summer solstice is rounded to a civil day, then the nearest preceding Jia-Zi day is assigned Star 1 for the forward arc or Star 9 for the backward arc.

FIRST_JIAZI_AFTER_SOLSTICE follows a documented Daily Flying Star convention in which Star 1 or Star 9 presides on the first Jia-Zi day after the relevant solstice. See the "Daily Flying Star" rules cited on https://en.wikipedia.org/wiki/Flying_Star_Feng_Shui.

JIAZI_AT_OR_BEFORE_SOLSTICE class-attribute instance-attribute

JIAZI_AT_OR_BEFORE_SOLSTICE = 'jiazi_at_or_before_solstice'

FIRST_JIAZI_AFTER_SOLSTICE class-attribute instance-attribute

FIRST_JIAZI_AFTER_SOLSTICE = 'first_jiazi_after_solstice'

NineStarKiEngine

NineStarKiEngine(
    ephemeris: Ephemeris | None = None,
    *,
    time_model: TimeModel = TimeModel.CLOCK,
    day_star_escapement: DayStarEscapement = DayStarEscapement.JIAZI_AT_OR_BEFORE_SOLSTICE,
    target_year: int | None = None,
)

Bases: AbstractEngine

Nine Star Ki engine using shared solar-term astronomy.

The engine deterministically computes principal, monthly, daily, and tendency stars, then renders annual and monthly flying-star charts into the reading summary.

Parameters:

Name Type Description Default
ephemeris Ephemeris | None

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

None
time_model TimeModel

Default time adjustment model used when the request does not specify one.

CLOCK
day_star_escapement DayStarEscapement

Default daily-star solstice reversal school used when the request does not specify day_star_escapement.

JIAZI_AT_OR_BEFORE_SOLSTICE
target_year int | None

Default annual chart year. When omitted, the request year is used.

None
Source code in src/fortune_telling_core/traditions/nine_star_ki/engine.py
def __init__(
    self,
    ephemeris: Ephemeris | None = None,
    *,
    time_model: TimeModel = TimeModel.CLOCK,
    day_star_escapement: DayStarEscapement = DayStarEscapement.JIAZI_AT_OR_BEFORE_SOLSTICE,
    target_year: int | None = None,
) -> None:
    self.ephemeris = ephemeris or BuiltinEphemeris()
    self.time_model = time_model
    self.day_star_escapement = day_star_escapement
    self.target_year = target_year

id class-attribute instance-attribute

id = 'ninestarki.engine'

version class-attribute instance-attribute

version = '0.1.0'

ephemeris instance-attribute

ephemeris = ephemeris or BuiltinEphemeris()

time_model instance-attribute

time_model = time_model

day_star_escapement instance-attribute

day_star_escapement = day_star_escapement

target_year instance-attribute

target_year = target_year

deck

deck(request: ReadingRequest) -> Deck

Return the Nine Star Ki deck.

Parameters:

Name Type Description Default
request ReadingRequest

Reading request whose deck_id must match NINE_STAR_KI_DECK.id.

required

Returns:

Type Description
Deck

The bundled nine-star deck.

Raises:

Type Description
ValidationError

If the requested deck is unsupported.

Source code in src/fortune_telling_core/traditions/nine_star_ki/engine.py
def deck(self, request: ReadingRequest) -> Deck:
    """Return the Nine Star Ki deck.

    Args:
        request: Reading request whose ``deck_id`` must match
            ``NINE_STAR_KI_DECK.id``.

    Returns:
        The bundled nine-star deck.

    Raises:
        ValidationError: If the requested deck is unsupported.
    """

    if request.deck_id != NINE_STAR_KI_DECK.id:
        raise ValidationError(f"unsupported Nine Star Ki deck: {request.deck_id}")
    return NINE_STAR_KI_DECK

spread

spread(request: ReadingRequest) -> Spread

Return the Nine Star Ki spread.

Parameters:

Name Type Description Default
request ReadingRequest

Reading request whose spread_id must match NINE_STAR_KI_SPREAD.id.

required

Returns:

Type Description
Spread

The bundled Nine Star Ki spread.

Raises:

Type Description
ValidationError

If the requested spread is unsupported.

Source code in src/fortune_telling_core/traditions/nine_star_ki/engine.py
def spread(self, request: ReadingRequest) -> Spread:
    """Return the Nine Star Ki spread.

    Args:
        request: Reading request whose ``spread_id`` must match
            ``NINE_STAR_KI_SPREAD.id``.

    Returns:
        The bundled Nine Star Ki spread.

    Raises:
        ValidationError: If the requested spread is unsupported.
    """

    if request.spread_id != NINE_STAR_KI_SPREAD.id:
        raise ValidationError(f"unsupported Nine Star Ki spread: {request.spread_id}")
    return NINE_STAR_KI_SPREAD

draw

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

Compute Nine Star Ki placements as a deterministic draw.

Parameters:

Name Type Description Default
request ReadingRequest

Reading request with birth_datetime, latitude, and longitude in options or querent attributes.

required
rng Rng

Ignored. The argument is present for Engine compatibility.

required

Returns:

Type Description
Draw

A draw with natal and chart selections.

Raises:

Type Description
ValidationError

If required birth data or options are invalid.

EphemerisError

If the configured ephemeris cannot compute solar terms.

Source code in src/fortune_telling_core/traditions/nine_star_ki/engine.py
def draw(self, request: ReadingRequest, rng: Rng) -> Draw:
    """Compute Nine Star Ki placements as a deterministic draw.

    Args:
        request: Reading request with ``birth_datetime``, ``latitude``,
            and ``longitude`` in options or querent attributes.
        rng: Ignored. The argument is present for ``Engine``
            compatibility.

    Returns:
        A draw with natal and chart selections.

    Raises:
        ValidationError: If required birth data or options are invalid.
        EphemerisError: If the configured ephemeris cannot compute solar
            terms.
    """

    del rng
    birth = parse_birth_data(request, self.target_year, self.day_star_escapement)
    time_model = (
        birth.time_model
        if "time_model" in (request.options or {})
        or (request.querent and "time_model" in (request.querent.attributes or {}))
        else self.time_model
    )
    effective = effective_datetime(
        birth.birth_datetime, birth.longitude, time_model, self.ephemeris
    )
    return draw_from_chart(
        compute_chart(
            birth.birth_datetime,
            effective,
            self.ephemeris,
            birth.target_year,
            birth.day_star_escapement,
        )
    )

cast

cast(request: ReadingRequest) -> Reading

Compute a Nine Star Ki chart without a caller RNG.

Parameters:

Name Type Description Default
request ReadingRequest

Reading request containing birth data and optional chart configuration.

required

Returns:

Type Description
Reading

A reading with star placements and chart summary text.

Raises:

Type Description
ValidationError

If required birth data or options are invalid.

EphemerisError

If the configured ephemeris cannot compute solar terms.

Source code in src/fortune_telling_core/traditions/nine_star_ki/engine.py
def cast(self, request: ReadingRequest) -> Reading:
    """Compute a Nine Star Ki chart without a caller RNG.

    Args:
        request: Reading request containing birth data and optional chart
            configuration.

    Returns:
        A reading with star placements and chart summary text.

    Raises:
        ValidationError: If required birth data or options are invalid.
        EphemerisError: If the configured ephemeris cannot compute solar
            terms.
    """

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

build_engine

build_engine(
    ephemeris: Ephemeris | None = None,
    *,
    time_model: TimeModel = TimeModel.CLOCK,
    day_star_escapement: DayStarEscapement = DayStarEscapement.JIAZI_AT_OR_BEFORE_SOLSTICE,
    target_year: int | None = None,
) -> NineStarKiEngine

Create a Nine Star Ki engine.

Parameters:

Name Type Description Default
ephemeris Ephemeris | None

Optional ephemeris backend. Defaults to the built-in pure-Python backend.

None
time_model TimeModel

Default time adjustment model.

CLOCK
day_star_escapement DayStarEscapement

Default daily-star solstice reversal school.

JIAZI_AT_OR_BEFORE_SOLSTICE
target_year int | None

Default annual chart year.

None

Returns:

Type Description
NineStarKiEngine

A new NineStarKiEngine instance.

Source code in src/fortune_telling_core/traditions/nine_star_ki/engine.py
def build_engine(
    ephemeris: Ephemeris | None = None,
    *,
    time_model: TimeModel = TimeModel.CLOCK,
    day_star_escapement: DayStarEscapement = DayStarEscapement.JIAZI_AT_OR_BEFORE_SOLSTICE,
    target_year: int | None = None,
) -> NineStarKiEngine:
    """Create a Nine Star Ki engine.

    Args:
        ephemeris: Optional ephemeris backend. Defaults to the built-in
            pure-Python backend.
        time_model: Default time adjustment model.
        day_star_escapement: Default daily-star solstice reversal school.
        target_year: Default annual chart year.

    Returns:
        A new ``NineStarKiEngine`` instance.
    """

    return NineStarKiEngine(
        ephemeris=ephemeris,
        time_model=time_model,
        day_star_escapement=day_star_escapement,
        target_year=target_year,
    )