Skip to content

Examples — Python API

All examples below assume:

from datetime import date, datetime, time, timedelta
from kpastro import (
    BirthInfo, compute_chart, render_chart,
    kp_divisions, ascendant_from_kp_number, kp_number_for_longitude,
    dasha_balance, current_periods, planet_significations,
    house_significations, ruling_planets, point_info,
    LifeEvent, IdentityInfo, rectify, render_rectification,
    transit_confirmation,
    download_ephemeris,
)

1. A complete birth chart

birth = BirthInfo(
    date=date(1990, 1, 15), time=time(14, 30),
    latitude=28.6139, longitude=77.2090,
    tz_hours=5.5, place="New Delhi",
)
chart = compute_chart(birth, ayanamsa="lahiri")
print(render_chart(chart))

chart is a Chart — you can ignore the text rendering and use the data directly:

for p in chart.planets:
    print(f"{p.name:8} {p.sign:10} star {p.star:16} sub {p.sub_lord:8} "
          f"house {p.house:2} retro {'R' if p.retrograde else '.'}")

print("Cusp 1 sub-lord:", chart.cusp_sublords[1])
print("Ascendant:", chart.ascendant, "deg", "| MC:", chart.midheaven, "deg")
print("At birth:",
      chart.current[1].lord, "/", chart.current[2].lord, "/", chart.current[3].lord)

Typed access to every layer

balance = chart.balance
print(f"Maha-dasha: {balance.mahadasha_lord} "
      f"({balance.mahadasha_years:.2f} y / {balance.mahadasha_days:.1f} d) "
      f"in {balance.nakshatra}")

for house, tiers in enumerate(chart.house_significators, start=1):
    sig = ", ".join(f"{pl}({t})" for pl, t in tiers) or "—"
    print(f"H{house:>2}: {sig}")

2. Reuse one engine across many charts

The Swiss Ephemeris holds process-global state, so constructing one SwissEphemeris and passing it in is both faster and cleaner:

from kpastro import SwissEphemeris

eph = SwissEphemeris(ayanamsa="lahiri", node="mean")

charts = [
    compute_chart(BirthInfo(date(1990,1,15), time(14,30), 28.6139, 77.2090, 5.5, "New Delhi"), eph=eph),
    compute_chart(BirthInfo(date(1988,12,9), time(9,15), 19.0760, 72.8777, 5.5, "Mumbai"), eph=eph),
]

3. Vimshottari dasha

from datetime import datetime, timedelta

moon = compute_chart(birth).planet_lon["Moon"]

bal = dasha_balance(moon)
print(f"{bal.mahadasha_lord}: MD {bal.mahadasha_days:.0f}d "
      f"| AD {bal.active_ad_lord} {bal.active_ad_days:.0f}d "
      f"| PD {bal.active_pd_lord} {bal.active_pd_days:.0f}d")

epoch = datetime(1990, 1, 15, 14, 30) - timedelta(hours=5.5)
active = current_periods(moon, epoch, datetime(2026, 1, 1, 12, 0))
print(f"On 2026-01-01: MD {active[1].lord}, AD {active[2].lord}, PD {active[3].lord}")

4. KP horary (prashna)

q = ascendant_from_kp_number(45)
print(f"Number {q['number']}: asc {q['ascendant']:.4f} deg {q['sign']} "
      f"star {q['star']} ({q['star_lord']}), sub {q['sub_lord']}")

division_count = len(kp_divisions())      # exactly 249
back = kp_number_for_longitude(q["ascendant"])
print(division_count, "divisions;", back, "contains the midpoint")

5. Sub-division math without an ephemeris

Everything below uses only a sidereal longitude:

info = point_info(48.9167)     # e.g. 18 Cancer 55
print(info.star, info.sub_lord, info.sub_sub_lord, "pada", info.pada)

6. Ruling planets

rps = ruling_planets(chart.ascendant, chart.planet_lon["Moon"], 2)  # 2 = Wednesday
for rp in rps:
    print(f"{rp.planet:<10} {rp.source}")

7. Significators (Grah Nirdeshan)

sig = planet_significations(chart.planet_lon, [c.longitude for c in chart.cusps])
for planet, entries in sig.items():
    why = []
    if any(e.by_occupation for e in entries): why.append("occupies")
    if any(e.by_sign_lordship for e in entries): why.append("owns")
    if any(e.by_star_lord for e in entries): why.append("star-lord")
    print(f"{planet:8} -> houses {[e.house for e in entries]} ({', '.join(why)})")

8. Full precision live

paths = download_ephemeris()
print("Data files:", [p.name for p in paths])

After the files are present, all computations silently use the full-precision VSOP87 / JPL DE431 ephemeris.

9. Birth-time rectification

Only the approximate birth time is known. Dated life events are mapped to the KP houses they fell into, and the scanner finds the most probable minute:

from kpastro import BirthInfo, LifeEvent, IdentityInfo, rectify, render_rectification

birth = BirthInfo(
    date(1990, 1, 15), time(14, 30),   # approximate time
    28.6139, 77.2090, 5.5, "New Delhi",
)

events = [
    LifeEvent(date(1995, 9, 3),  2, (), "School admission"),
    LifeEvent(date(2007, 4, 1),  4, (), "Joined college"),
    LifeEvent(date(2013, 2, 14), 4, (), "First job"),
    LifeEvent(date(2018, 1, 20), 7, (), "Marriage"),
]

result = rectify(birth, time(14, 30), events,
                 window_min=60, step_min=1,
                 use_rp=True,
                 identity=IdentityInfo(siblings=1),
                 analysis_time=datetime(2026, 1, 1, 12, 0))

print(render_rectification(result, birth))

# Typed access instead of the text report:
best = result.best
print(f"Best birth time: {best.lsl} total={best.total:.2f} strikes={best.strikes}")
ci = result.credible
# credible._ut fields are UTC datetimes; convert to local (IST here) for display
tz = timedelta(hours=birth.tz_hours)
print(f"Credible range: {(ci.start_ut + tz):%H:%M} - {(ci.end_ut + tz):%H:%M} "
      f"local ({ci.mass:.0%} mass)")
# every ranked candidate, in order
for c in result.candidates[:5]:
    print(f"{c.offset_minutes:+5.0f} min  {c.lsl:<8} total={c.total:.2f}")

events need only be dated and judged to a primary house (1–12) plus optional secondary houses. Add transit_confirmation for the Jupiter/Saturn cross-check:

conf = transit_confirmation(best.jd_ut, birth.latitude, birth.longitude,
                            events, tz_hours=birth.tz_hours)
print(f"Jupiter/Saturn transit confirmations: {conf.matched}/{conf.total}")