Skip to content

Four Pillars of Destiny (BaZi)

A BaZi engine computing the four natal pillars (eight characters), Ten Gods and five-element analysis, luck pillars (大运) and the annual pillar, with a selectable time model.

from fortune_telling_core import Querent, ReadingRequest
from fortune_telling_core.traditions.four_pillars import (
    FOUR_PILLARS_DECK,
    FOUR_PILLARS_SPREAD,
    build_engine,
)

engine = build_engine()
request = ReadingRequest(
    deck_id=FOUR_PILLARS_DECK.id,
    spread_id=FOUR_PILLARS_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",
            "gender": "male",
            "target_year": "2026",
        },
    ),
)

reading = engine.cast(request)

The annual pillar (流年) year defaults to the request-level as_of moment (or requested_at when unset); the target_year attribute overrides it when you need a specific year.

four_pillars

FOUR_PILLARS_DECK module-attribute

FOUR_PILLARS_DECK = Deck(
    id="fp.deck.ganzhi.v1",
    symbols=tuple(
        (
            Symbol(
                id=f"fp.stem.{stem.slug}",
                name=f"{stem.cjk} {stem.slug.title()}",
                attributes={
                    "kind": "stem",
                    "cjk": stem.cjk,
                    "element": stem.element.value,
                    "polarity": stem.polarity.value,
                },
            )
        )
        for stem in STEMS
    )
    + tuple(
        (
            Symbol(
                id=f"fp.branch.{branch.slug}",
                name=f"{branch.cjk} {branch.animal}",
                attributes={
                    "kind": "branch",
                    "cjk": branch.cjk,
                    "element": branch.element.value,
                    "polarity": branch.polarity.value,
                    "animal": branch.animal,
                    "hidden_stems": ",".join(
                        (str(index))
                        for index in (branch.hidden_stems)
                    ),
                },
            )
        )
        for branch in BRANCHES
    ),
)

FOUR_PILLARS_SPREAD module-attribute

FOUR_PILLARS_SPREAD = Spread(
    id="fp.spread.natal.v1",
    name="Four Pillars",
    positions=(
        Position(
            "year_stem",
            "Year Stem",
            "Heavenly stem of the year pillar.",
        ),
        Position(
            "year_branch",
            "Year Branch",
            "Earthly branch of the year pillar.",
        ),
        Position(
            "month_stem",
            "Month Stem",
            "Heavenly stem of the month pillar.",
        ),
        Position(
            "month_branch",
            "Month Branch",
            "Earthly branch of the month pillar.",
        ),
        Position(
            "day_stem",
            "Day Stem",
            "Heavenly stem of the day pillar and Day Master.",
        ),
        Position(
            "day_branch",
            "Day Branch",
            "Earthly branch of the day pillar.",
        ),
        Position(
            "hour_stem",
            "Hour Stem",
            "Heavenly stem of the hour pillar.",
        ),
        Position(
            "hour_branch",
            "Hour Branch",
            "Earthly branch of the hour pillar.",
        ),
    ),
)

FourPillarsEngine

FourPillarsEngine(
    ephemeris: Ephemeris | None = None,
    *,
    time_model: TimeModel = TimeModel.CLOCK,
    day_boundary: DayBoundary = DayBoundary.MIDNIGHT,
    luck_count: int = 8,
)

Bases: AbstractEngine

Four Pillars engine using shared solar-term astronomy.

The engine deterministically computes year, month, day, and hour pillars, then enriches the reading summary with element balance, Day Master strength, Luck Pillars, and the annual pillar.

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_boundary DayBoundary

Default day-boundary rule used when the request does not specify one.

MIDNIGHT
luck_count int

Default number of Luck Pillars to render.

8
Source code in src/fortune_telling_core/traditions/four_pillars/engine.py
def __init__(
    self,
    ephemeris: Ephemeris | None = None,
    *,
    time_model: TimeModel = TimeModel.CLOCK,
    day_boundary: DayBoundary = DayBoundary.MIDNIGHT,
    luck_count: int = 8,
) -> None:
    self.ephemeris = ephemeris or BuiltinEphemeris()
    self.time_model = time_model
    self.day_boundary = day_boundary
    self.luck_count = luck_count

id class-attribute instance-attribute

id = 'fourpillars.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_boundary instance-attribute

day_boundary = day_boundary

luck_count instance-attribute

luck_count = luck_count

deck

deck(request: ReadingRequest) -> Deck

Return the Four Pillars stem and branch deck.

Parameters:

Name Type Description Default
request ReadingRequest

Reading request whose deck_id must match FOUR_PILLARS_DECK.id.

required

Returns:

Type Description
Deck

The bundled stem and branch deck.

Raises:

Type Description
ValidationError

If the requested deck is unsupported.

Source code in src/fortune_telling_core/traditions/four_pillars/engine.py
def deck(self, request: ReadingRequest) -> Deck:
    """Return the Four Pillars stem and branch deck.

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

    Returns:
        The bundled stem and branch deck.

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

    if request.deck_id != FOUR_PILLARS_DECK.id:
        raise ValidationError(f"unsupported Four Pillars deck: {request.deck_id}")
    return FOUR_PILLARS_DECK

spread

spread(request: ReadingRequest) -> Spread

Return the eight-position Four Pillars spread.

Parameters:

Name Type Description Default
request ReadingRequest

Reading request whose spread_id must match FOUR_PILLARS_SPREAD.id.

required

Returns:

Type Description
Spread

The bundled Four Pillars spread.

Raises:

Type Description
ValidationError

If the requested spread is unsupported.

Source code in src/fortune_telling_core/traditions/four_pillars/engine.py
def spread(self, request: ReadingRequest) -> Spread:
    """Return the eight-position Four Pillars spread.

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

    Returns:
        The bundled Four Pillars spread.

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

    if request.spread_id != FOUR_PILLARS_SPREAD.id:
        raise ValidationError(f"unsupported Four Pillars spread: {request.spread_id}")
    return FOUR_PILLARS_SPREAD

draw

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

Compute Four Pillars as a deterministic draw.

Parameters:

Name Type Description Default
request ReadingRequest

Reading request with birth data. birth_datetime, latitude, longitude, and gender are required in options or querent attributes.

required
rng Rng

Ignored. The argument is present for Engine compatibility.

required

Returns:

Type Description
Draw

A draw with stem and branch selections for the four pillars.

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/four_pillars/engine.py
def draw(self, request: ReadingRequest, rng: Rng) -> Draw:
    """Compute Four Pillars as a deterministic draw.

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

    Returns:
        A draw with stem and branch selections for the four pillars.

    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.luck_count)
    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
    )
    day_boundary = (
        birth.day_boundary
        if "day_boundary" in (request.options or {})
        or (request.querent and "day_boundary" in (request.querent.attributes or {}))
        else self.day_boundary
    )
    effective = effective_datetime(
        birth.birth_datetime, birth.longitude, time_model, self.ephemeris
    )
    pillars = compute_pillars(
        birth.birth_datetime, effective, self.ephemeris, birth.gender, day_boundary
    )
    return draw_from_pillars(pillars, birth)

cast

cast(request: ReadingRequest) -> Reading

Compute Four Pillars without a caller RNG.

Parameters:

Name Type Description Default
request ReadingRequest

Reading request containing birth data and optional BaZi configuration.

required

Returns:

Type Description
Reading

A reading with pillar placements and BaZi 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/four_pillars/engine.py
def cast(self, request: ReadingRequest) -> Reading:
    """Compute Four Pillars without a caller RNG.

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

    Returns:
        A reading with pillar placements and BaZi 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_boundary: DayBoundary = DayBoundary.MIDNIGHT,
    luck_count: int = 8,
) -> FourPillarsEngine

Create a Four Pillars 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_boundary DayBoundary

Default day-boundary rule.

MIDNIGHT
luck_count int

Default number of Luck Pillars to render.

8

Returns:

Type Description
FourPillarsEngine

A new FourPillarsEngine instance.

Source code in src/fortune_telling_core/traditions/four_pillars/engine.py
def build_engine(
    ephemeris: Ephemeris | None = None,
    *,
    time_model: TimeModel = TimeModel.CLOCK,
    day_boundary: DayBoundary = DayBoundary.MIDNIGHT,
    luck_count: int = 8,
) -> FourPillarsEngine:
    """Create a Four Pillars engine.

    Args:
        ephemeris: Optional ephemeris backend. Defaults to the built-in
            pure-Python backend.
        time_model: Default time adjustment model.
        day_boundary: Default day-boundary rule.
        luck_count: Default number of Luck Pillars to render.

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

    return FourPillarsEngine(
        ephemeris=ephemeris,
        time_model=time_model,
        day_boundary=day_boundary,
        luck_count=luck_count,
    )