panchang-ts 5.0.0 → 5.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,11 +1,17 @@
1
1
  # panchang-ts
2
2
 
3
3
  [![npm version](https://img.shields.io/npm/v/panchang-ts)](https://www.npmjs.com/package/panchang-ts)
4
+ [![license](https://img.shields.io/npm/l/panchang-ts)](./LICENSE)
4
5
 
5
6
  Pure TypeScript Hindu Panchang (almanac), Jyotish, and Birth Chart calculations.
6
- Zero native dependencies. Works offline in React Native (Hermes), Node.js, and browsers.
7
+ Zero runtime dependencies. Works offline in React Native (Hermes), Node.js, and browsers.
7
8
 
8
- **Fast** (~0.25 ms trimmed, ~0.41 ms full) · **Typed** (full TypeScript) · **Offline** (pure JS math) · **8,368 tests across 121 files**
9
+ **Fast** (~0.25 ms trimmed, ~0.41 ms full) · **Typed** (full TypeScript) · **Offline** (pure JS math) · **8,737 tests across 136 files**
10
+
11
+ > 📖 **Full documentation: [dharmagya.app/docs/panchang-ts](https://dharmagya.app/docs/panchang-ts)**
12
+ > This README covers install, quick start, and the 5.0 → 5.1 and 4.x → 5 migrations in full,
13
+ > plus a per-feature quick reference. The complete reference — every option, result field,
14
+ > table format, accuracy bound and performance note — lives on the docs site.
9
15
 
10
16
  ---
11
17
 
@@ -124,6 +130,142 @@ Ekadashi split. For reliable festival dating, use `getDailyPanchang`.
124
130
 
125
131
  ---
126
132
 
133
+ ## Upgrading from 5.1 (unreleased)
134
+
135
+ Two deliberate type breaks, both correcting fields that did not match
136
+ DrikPanchang (the project's parity oracle), in the same style as 5.1's
137
+ `varjyam` break.
138
+
139
+ ### `inauspicious.durMuhurta` is now `DurMuhurtaPeriod[]`
140
+
141
+ ```diff
142
+ - const [dm1, dm2] = r.inauspicious.durMuhurta;
143
+ + r.inauspicious.durMuhurta.forEach((dm) => show(dm)); // 1–2 windows
144
+ + // dm.segment is 'day' or 'night' (only Tuesday carries a night window)
145
+ ```
146
+
147
+ The old table emitted two day windows every day and matched drik on none of
148
+ the seven weekdays. The corrected classical Muhurta-Chintamani table gives
149
+ one window on Sunday and Wednesday, two elsewhere — and Tuesday's second
150
+ window falls at **night** (the 7th of the 15 sunset→sunrise muhurtas), so
151
+ the exactly-two-day-windows tuple could not survive. Verified against 58
152
+ consecutive drik day-pages across two cities; a full drik week is pinned.
153
+
154
+ ### `muhurtas.amritKala` is now `TimePeriod[]`
155
+
156
+ ```diff
157
+ - if (r.muhurtas.amritKala) show(r.muhurtas.amritKala);
158
+ + r.muhurtas.amritKala.forEach(show); // [] when the day has none
159
+ ```
160
+
161
+ Amrit Kala shares Varjyam's architecture (drik prints them from the same
162
+ frame): each window anchors at its nakshatra's own start, offset by a
163
+ per-nakshatra count of nakshatra-elastic ghatikas, spans exactly 4 such
164
+ ghatikas, and belongs to the Hindu day its **start** falls in — 0–2 windows
165
+ per day. The old sunrise-anchored single window disagreed with drik by up
166
+ to ~16 h. `computeAmritKala` is replaced by
167
+ `computeAmritKalaWindows(sunriseUtc, nextSunriseUtc, getMoon)`.
168
+
169
+ ### Ekadashi splits: Smarta first, Vaishnava second
170
+
171
+ On the days drik prints an Ekadashi twice, the **earlier** day is the Smarta
172
+ fast and the **later** one the Vaishnava fast — drik says so in prose on every
173
+ Ekadashi date-time page. Two corrections bring the library in line:
174
+
175
+ ```diff
176
+ // Dashami-viddha day (e.g. Rama Ekadashi, 2027-10-25)
177
+ - ['vaishnava_ekadashi', 'smarta_ekadashi' /* deferred */, 'ekadashi']
178
+ + ['smarta_ekadashi', 'ekadashi'] // vaishnava_ekadashi is tomorrow
179
+
180
+ // First day of a vriddha Dwadashi (e.g. 2026-08-24)
181
+ - []
182
+ + ['vaishnava_ekadashi']
183
+ ```
184
+
185
+ Both the Dashami-viddha and the vriddha-Dwadashi (Pakshavardhini) splits now
186
+ match drik across every pair it publishes in 2024–2028. `getDailyPanchang`
187
+ callers that keyed off `smarta_ekadashi` / `vaishnava_ekadashi` on split days
188
+ will see the two swap places; `computeEkadashiDatesForYear` is unchanged.
189
+ Custom locale packs need the renamed viddha description keys — see the
190
+ CHANGELOG.
191
+
192
+ ### Regional solar new years land on their own days
193
+
194
+ `vishu`, `baisakhi` and `pohela_boishakh` no longer share Puthandu's day.
195
+ Each keys off the Mesha transit moment its own way, so in 2027 Vishu and
196
+ Pohela Boishakh fall on April 15 while Puthandu falls on April 14, and in
197
+ 2028 Vaisakhi falls on April 13 while the rest fall on April 14.
198
+ `getHinduNewYear(year, region, …)` follows the same per-region rules.
199
+
200
+ Several other **value-level** corrections ride along without shape changes:
201
+ dur muhurta ordinals, night choghadiya names, Bhadra vasa (now Moon-rashi
202
+ keyed, with a piecewise `vasa` segment list on `BhadraInfo`), night-transit
203
+ Sankranti dates (+ a new `moment` field on `SankrantiEvent`), kshaya-Dwadashi
204
+ Ekadashi advance, Vijayadashami/Karva Chauth/Janmashtami kala rules, the
205
+ Kali Yuga year boundary, `scoreMuhurta` special-yoga parity, eastern-
206
+ longitude Gulika/Mandi, and the opt-in Ashtakavarga reductions. See the
207
+ CHANGELOG for each rule and its drik evidence.
208
+
209
+ ---
210
+
211
+ ## Upgrading from 5.0
212
+
213
+ One deliberate type break, two corrected dasha tables, and one opt-in flag.
214
+
215
+ ### `inauspicious.varjyam` is now `TimePeriod[]`
216
+
217
+ ```diff
218
+ - if (r.inauspicious.varjyam) show(r.inauspicious.varjyam);
219
+ + r.inauspicious.varjyam.forEach(show); // [] when the day has none
220
+ ```
221
+
222
+ 5.0 evaluated only the nakshatra active at sunrise and dropped the second
223
+ Varjyam window printed panchangs show on transition days. 5.1 publishes every
224
+ window, in start order, under DrikPanchang's attribution rule: a window belongs
225
+ to the Hindu day its **start** falls in (one that begins before sunrise and
226
+ runs past it is yesterday's), and window instants are unclamped — an end can
227
+ land after next sunrise. Validated window-for-window against a 61-day
228
+ DrikPanchang sweep (Aug–Sep 2026, two full nakshatra cycles): 62/62 match.
229
+
230
+ Two value-level corrections ride along: **Mula carries a second tyajya spell**
231
+ (elapsed ghatikas 20 *and* 56 — DrikPanchang, ProKerala and B.V. Raman's
232
+ *Muhurta* concur), so Mula days now emit the window 5.0 missed; and the
233
+ standalone `computeVarjyam` primitive returns the *earliest* of a nakshatra's
234
+ spells overlapping the day. New export: `computeVarjyamWindows(sunriseUtc,
235
+ nextSunriseUtc, getMoon)`.
236
+
237
+ ### Yogini and Ashtottari starting lords were wrong — now classical
238
+
239
+ - **Yogini** used `nakshatraIndex % 8`, off by three Yoginis for every birth.
240
+ Now the classical Devi-Bhagavata formula — (1-based janma nakshatra + 3) mod
241
+ 8; remainder 1 = Mangala … 0 = Sankata — so Ashwini → Bhramari, Pushya →
242
+ Dhanya. Verified against published worked examples and PyJHora.
243
+ - **Ashtottari** used a years-proportional split of the zodiac from a Krittika
244
+ anchor, matching no source. Now the classical Ardradi **group table**
245
+ (malefics rule four nakshatras each, benefics three; Sun = Ardra…Ashlesha,
246
+ Venus = Krittika…Mrigashira; exported as `ASHTOTTARI_NAKSHATRA_GROUPS`),
247
+ with the balance from the elapsed fraction of the group. Verified against
248
+ PyJHora and Maitreya 8, which agree on every output.
249
+
250
+ Both functions keep their signatures; recorded outputs from 5.0 will differ
251
+ and should be discarded.
252
+
253
+ ### Opt-in Gana-dosha cancellation in `computeAshtakoot`
254
+
255
+ ```typescript
256
+ computeAshtakoot(boy, girl, { ganaCancellation: true });
257
+ ```
258
+
259
+ Default output is byte-identical to 5.0 (DrikPanchang's published 36-guna
260
+ table applies no Gana cancellation, and drik parity stays the default
261
+ standard). With the flag raised, a doshic Gana score (≤ 1) is restored to the
262
+ full 6 when the two Moons' sign lords are the same graha or mutual naisargika
263
+ friends, recorded in `cancellations` — the condition set attested across
264
+ independent pandit corpora; weaker ones are documented on `AshtakootOptions`
265
+ and deliberately not encoded.
266
+
267
+ ---
268
+
127
269
  ## Upgrading from 4.x
128
270
 
129
271
  Three changes move numbers that 4.x produced, and one option is gone.
@@ -318,10 +460,8 @@ a `*Local` companion: `sun.riseLocal`, `inauspicious.rahuKalam.startLocal`,
318
460
  timezone, so there is no zone to render a wall clock in.
319
461
 
320
462
  **Cost:** rendering the strings adds ~0.04 ms per daily panchang — invisible on
321
- a cold call (0.7885 → 0.7924 ms) and ~20% of a fully cached warm one
322
- (0.176 → 0.215 ms). Both pairs are the tree measured against itself when the
323
- change landed, mid-Phase-36; the release finally warms to **0.17 ms**, against
324
- published 4.3.1's **6.20 ms**.
463
+ a cold call and ~20% of a fully cached warm one. The release warms to
464
+ **0.17 ms**, against published 4.3.1's **6.20 ms**.
325
465
 
326
466
  ### `result.timezone` is now an object
327
467
 
@@ -391,7 +531,8 @@ For `getInstantPanchang`: `tithi` / `nakshatra` / `yoga` / `karana` / `vara` →
391
531
  `| null` for `bhadra` / `varjyam` / `eclipse`, `?`-optional for `chandraBalam` /
392
532
  `tarabala`, and an empty array for `panchakaRahita` / `festivals`. v5 has one
393
533
  rule: **every field is always present**, a value that does not apply is `null`,
394
- and a collection that does not apply is `[]`.
534
+ and a collection that does not apply is `[]`. (Since 5.1, `varjyam` is a
535
+ collection and follows the `[]` arm — see [Upgrading from 5.0](#upgrading-from-50).)
395
536
 
396
537
  ```diff
397
538
  - if ('chandraBalam' in r) … // 4.x: field absent without janmaRashi
@@ -485,1109 +626,223 @@ the **lunar** one, which was previously left to guesswork.
485
626
 
486
627
  # Feature Reference
487
628
 
488
- Each daily field is also returned from `getDailyPanchang` if you prefer one call
489
- over per-feature helpers.
629
+ A quick tour with runnable snippets. **Each section links to its full page on
630
+ the docs site** — every option, field, and caveat lives there.
631
+
632
+ ## Pancha Anga & the daily result
490
633
 
491
- ## Pancha Anga
634
+ 📖 [Daily Panchang →](https://dharmagya.app/docs/panchang-ts/daily-panchang)
492
635
 
493
636
  ```typescript
494
637
  const r = getDailyPanchang(date, location, { timezone: 330 })!;
495
638
 
496
- r.angas.tithis.forEach(t => console.log(t.name, t.paksha, t.completionPercentage, t.endTime));
497
- r.angas.nakshatras.forEach(n => console.log(n.name, n.pada, n.endTime));
498
- r.angas.yogas.forEach(y => console.log(y.name, y.endTime));
499
- r.angas.karanas.forEach(k => console.log(k.name, k.type, k.endTime));
500
- console.log(r.angas.vara.name, r.angas.vara.englishName); // "Mangalawara", "Tuesday"
639
+ r.angas.tithis.forEach(t => console.log(t.name, t.paksha, t.endTime));
640
+ r.angas.vara.name; // "Mangalawara"
641
+ r.calendar.chandramasa.isAdhika; // true during leap months
642
+ r.muhurtas.brahma; // TimePeriod | null — and 9 more muhurtas
643
+ r.inauspicious.rahuKalam; // { start, end } — and 9 more windows
644
+ r.periods.choghadiya.day[0].name; // 16 Choghadiya + Gowri + 24 Hora slots
645
+ r.anandadiYoga.name; r.specialYogas; // Anandadi + Amrit/Sarvartha Siddhi, …
501
646
 
502
647
  // Single-instant snapshot:
503
648
  import { getInstantPanchang } from 'panchang-ts';
504
649
  const i = getInstantPanchang(new Date(), location)!;
505
- console.log(i.angas.tithi.name, i.angas.nakshatra.name, i.angas.yoga.name, i.angas.karana.name, i.angas.vara.name);
506
- ```
507
-
508
- ## Lunar & Solar Calendar
509
-
510
- ```typescript
511
- const r = getDailyPanchang(date, loc, { timezone: 330, masaSystem: 'purnimanta' })!;
512
-
513
- r.calendar.chandramasa.name; // active system (default: Purnimanta / North Indian)
514
- r.calendar.chandramasa.amantaName; // South Indian
515
- r.calendar.chandramasa.purnimantaName; // North Indian
516
- r.calendar.chandramasa.isAdhika; // true during leap months
517
- r.calendar.samvat.vikramSamvat; // 2081
518
- r.calendar.samvat.shakaSamvat; // 1946
519
-
520
- r.calendar.masa.name; // current solar month (Mesha … Meena)
521
- r.sun.nakshatra.name; // Sun's nakshatra
522
- r.moon.rashi.name; // Moon sign
523
- ```
524
-
525
- ## Sun, Moon & Muhurta
526
-
527
- ```typescript
528
- import { getSunrise, getSunset, getMoonrise, getMoonset } from 'panchang-ts';
529
-
530
- const sunrise = getSunrise(localMidnightUtc, loc);
531
- const sunset = getSunset(sunrise, loc);
532
- const moonrise = getMoonrise(localMidnightUtc, loc); // null on some days (normal)
533
- const moonset = getMoonset(localMidnightUtc, loc);
534
-
535
- // Or read off the daily result:
536
- const r = getDailyPanchang(date, loc, { timezone: 330 })!;
537
- r.sun.rise; r.sun.set; r.moon.rise; r.moon.set; r.sun.nextRise;
538
- r.sun.dayDurationMinutes; r.sun.nightDurationMinutes;
539
-
540
- // Auspicious muhurtas
541
- r.muhurtas.brahma; // two muhurtas before sunrise
542
- r.muhurtas.abhijit; // 8th day-muhurta; null on Wednesday (Drik convention)
543
- r.muhurtas.vijaya; // 11th day-muhurta
544
- r.muhurtas.godhuli; // "cow-dust" sunset muhurta
545
- r.muhurtas.nishita; // midnight muhurta (Shivaratri)
546
- r.muhurtas.madhyahna; // solar noon ±24 min
547
- r.muhurtas.pratahSandhya; // dawn twilight, ends at sunrise
548
- r.muhurtas.sayahnaSandhya; // dusk twilight, starts at sunset
549
- r.muhurtas.amritKala; // nakshatra-specific window (null when nakshatra has none)
550
- ```
551
-
552
- `muhurtas.pratahSandhya` / `muhurtas.sayahnaSandhya` width =
553
- `sun.nightDurationMinutes / 10` (~62–81 min).
554
-
555
- ## Inauspicious Periods
556
-
557
- ```typescript
558
- const r = getDailyPanchang(date, loc, { timezone: 330 })!;
559
-
560
- r.inauspicious.rahuKalam; // { start, end }
561
- r.inauspicious.gulikaKalam;
562
- r.inauspicious.yamaganda;
563
- r.inauspicious.durMuhurta; // two ~48-min windows
564
- r.inauspicious.varjyam; // { start, end } | null
565
- r.inauspicious.gandaMula; // { active, severity: 'mild'|'severe'|null, ... }
566
- r.inauspicious.bhadra; // { start, end, location: 'earth'|'heaven'|'paatal', isActive } | null
567
- r.inauspicious.panchaka; // boolean — Moon in last 5 nakshatras
568
- r.inauspicious.panchakaInfo; // which of the five, and whether it's a dosha
569
- ```
570
-
571
- Panchaka is not one undifferentiated affliction. The tradition names five and
572
- picks between them by **the weekday the spell began on** — so the type belongs
573
- to the spell, not the day, and two days with identical tithi, nakshatra and
574
- vara can carry different ones. A spell begun on a Wednesday or Thursday
575
- (`'samanya'`) carries no named affliction at all:
576
-
577
- ```typescript
578
- const pk = r.inauspicious.panchakaInfo;
579
- if (pk.active && pk.isDosha) {
580
- console.log(pk.name, '— began on vara', pk.onsetVara); // e.g. "Mrityu Panchaka"
581
- }
582
- ```
583
-
584
- ## Time-Slot Systems
585
-
586
- ```typescript
587
- const r = getDailyPanchang(date, loc, { timezone: 330 })!;
588
-
589
- // Choghadiya — 8 day + 8 night named, rated slots (Amrit, Kaal, Shubh, Rog, …)
590
- r.periods.choghadiya.day.forEach(s => console.log(s.name, s.qualityName, s.start, s.end));
591
-
592
- // Gowri Panchangam ("Nalla Neram") — 8 day + 8 night Tamil slots
593
- r.periods.gowri.day.forEach(s => console.log(s.name, s.qualityName));
594
-
595
- // Hora — 12 day + 12 night planetary hours (Chaldean order)
596
- r.periods.hora.day.forEach(h => console.log(h.planet, h.start, h.end));
597
-
598
- // Do Ghati Muhurta — 15 day + 15 night ~48-min deity-keyed slots (no vara rotation)
599
- r.muhurtas.doGhati.day.forEach(g => console.log(g.name, g.start, g.end));
600
-
601
- // Panchaka Rahita — slices of the day FREE of Panchaka ([] when it pervades)
602
- r.inauspicious.panchakaRahita.forEach(slice => console.log(slice.start, slice.end));
603
- ```
604
-
605
- ## Special Yogas
606
-
607
- ```typescript
608
- const r = getDailyPanchang(date, loc, { timezone: 330 })!;
609
-
610
- r.anandadiYoga.name; // 28-cycle name e.g. "Ananda"
611
- r.specialYogas.forEach(y => {
612
- // type: amrit_siddhi | sarvartha_siddhi | ravi_pushya | guru_pushya
613
- // | dwipushkar | tripushkar | jwalamukhi (inauspicious)
614
- // | aadal | vidaal | ravi (Moon-from-Sun nakshatra-distance rules)
615
- console.log(y.name, y.type);
616
- });
650
+ console.log(i.angas.tithi.name, i.angas.nakshatra.name);
617
651
  ```
618
652
 
619
653
  ## Festivals (80+)
620
654
 
621
- Covers Ekadashi (26 variants, Smarta/Vaishnava split via Dashami-viddha; Smarta
622
- fast emits a `deferralDate` for Dwadashi), Pradosha, Sankranti + regional
623
- variants (Pongal, Vishu, Baisakhi, Pohela Boishakh, Bihu, Uttarayan, Lohri…),
624
- canonical-time classical (Janmashtami, Shivaratri, Ganesh Chaturthi, Diwali,
625
- Holi, Raksha Bandhan — Bhadra-aware, Karva Chauth, Akshaya Tritiya…),
626
- regional (Gudi Padwa, Gangaur, Teej variants, Onam, Chhath…), monthly
627
- observances (Masik Shivaratri, Pushya days, Shravan Somvar…).
655
+ 📖 [Festivals →](https://dharmagya.app/docs/panchang-ts/festivals)
628
656
 
629
657
  ```typescript
630
- r.festivals.forEach(f => {
631
- // key: stable, language-independent id'diwali', 'makar_sankranti',
632
- // type: major | minor | ekadashi | smarta_ekadashi | vaishnava_ekadashi
633
- // | pradosha | sankranti | eclipse
634
- console.log(f.key, f.name, f.type, f.deferralDate);
635
- });
636
-
637
- // `name` is localized, so match on `key` — never on `name`.
658
+ r.festivals.forEach(f => console.log(f.key, f.name, f.type, f.deferralDate));
659
+ // `name` is localized, so match on `key` never on `name`:
638
660
  const hasDiwali = r.festivals.some(f => f.key === 'diwali');
639
- ```
640
-
641
- `key` is on engine results (`getDailyPanchang`, `getInstantPanchang`,
642
- `computeFestivalsInRange`). Entries read back out of a `buildFestivalsTable` table
643
- carry `name` / `type` / `description` only.
644
-
645
- ### Regional scoping
646
661
 
647
- `region` scopes regional variants to one Indian state. Pan-Indian festivals
648
- emit regardless.
662
+ // Scope regional variants: 21 state slugs + 'nepal' + 'all' (default)
663
+ getDailyPanchang(jan14, chennai, { timezone: 330, region: 'tamil-nadu' });
649
664
 
650
- ```typescript
651
- // All regional variants (default):
652
- getDailyPanchang(jan14, chennai, { timezone: 330 })!.festivals.map(f => f.name);
653
- // → ["Sankranti","Makar Sankranti","Pongal","Uttarayan","Magh Bihu","Ayyappa Makara Jyothi"]
654
-
655
- // Tamil Nadu only:
656
- getDailyPanchang(jan14, chennai, { timezone: 330, region: 'tamil-nadu' })!
657
- .festivals.map(f => f.name);
658
- // → ["Sankranti","Makar Sankranti","Pongal"]
659
-
660
- // Lohri fires on the Hindu day BEFORE Makara transit, in Punjab/Haryana/Himachal scope:
661
- getDailyPanchang(jan13, amritsar, { timezone: 330, region: 'punjab' })!
662
- .festivals.some(f => f.name === 'Lohri'); // true
665
+ // Pre-computed table: build once, cache the JSON, read engine-free.
666
+ import { buildFestivalsTable } from 'panchang-ts';
667
+ import { readFestivalsForYear, readFestivalsForDate } from 'panchang-ts/festivals';
663
668
  ```
664
669
 
665
- `FestivalRegion` covers 21 Indian states + `'nepal'` + `'all'` (default). The
666
- legacy slugs `'tamil'`, `'bengal'`, `'north-india'` are still accepted and
667
- mapped internally.
668
-
669
- ### Pre-computed table — build your own and cache it
670
+ No table ships with the package festival dates are observer-dependent, so you
671
+ build one for your users' location and years (`npm run festivals:gen` is a
672
+ worked example).
670
673
 
671
- If you want festival *dates* without running the engine in your app, compute a
672
- table once with `buildFestivalsTable`, cache the JSON, and read it back through
673
- the engine-free `panchang-ts/festivals` entry point.
674
+ ## Eclipses & Moon Phases
674
675
 
675
- **The library ships no pre-computed table.** Festival dates are
676
- observer-dependent — canonical times (nishita / pradosha / chandrodaya …) shift
677
- with the timezone offset, so a table built for one place can be ±1 day wrong
678
- elsewhere — and any table baked into the package would also go stale. Building
679
- your own means it is correct for *your* users and covers whatever years you
680
- want.
676
+ 📖 [Eclipses & Moon Phases →](https://dharmagya.app/docs/panchang-ts/eclipses-moon-phases)
681
677
 
682
678
  ```typescript
683
- import { buildFestivalsTable } from 'panchang-ts'; // uses the engine
684
- import {
685
- readFestivalsForYear,
686
- readFestivalsForDate,
687
- readFestivalsYearRange,
688
- } from 'panchang-ts/festivals'; // engine-free
689
-
690
- // Build once — at your build time, or on first launch in the background.
691
- const table = buildFestivalsTable({
692
- location: { latitude: 25.3176, longitude: 82.9739 }, // Varanasi
693
- timezoneOffsetMinutes: 330, // IST; -300 = US Eastern, 0 = UK
694
- startYear: 2024,
695
- endYear: 2031,
696
- languages: ['en', 'hi'], // drop 'hi' to halve the size
697
- referenceLocation: 'Varanasi',
698
- });
699
- // …persist `table` as JSON (disk / MMKV / your bundler's asset pipeline).
700
-
701
- // Later reads are instant lookups — no engine, no ephemeris.
702
- readFestivalsYearRange(table); // { start: 2024, end: 2031 }
703
- readFestivalsForYear(table, 2026)!.length; // ~150 festival days
704
- const diwali = readFestivalsForYear(table, 2026)!
705
- .find(d => d.festivals.some(f => f.name === 'Diwali'))!.date;
706
- readFestivalsForDate(table, diwali); // [Narak Chaturdashi, Diwali]
707
- readFestivalsForDate(table, diwali, 'hi'); // [नरक चतुर्दशी, दिवाली]
708
- ```
709
-
710
- `panchang-ts/festivals` imports no astronomy code, so a client bundle that only
711
- *reads* a table never pulls in the engine. Keep `buildFestivalsTable` on the
712
- build/server side (or behind a one-time on-device warm-up) and ship only the
713
- JSON.
714
-
715
- Eclipses are excluded here — visibility is location-dependent, so they get their
716
- own table at `panchang-ts/eclipses` (see [Eclipses](#eclipses)).
717
-
718
- `npm run festivals:gen` is a worked example of the whole pattern; it writes a
719
- rolling 2-past / 5-future window to `./festivals.json` (or a path you pass).
720
-
721
- **Other notes:** Karva Chauth / Dhanteras / Diwali emit with Purnimanta paksha
722
- naming.
723
-
724
- ## Eclipses
725
-
726
- ```typescript
727
- const r = getDailyPanchang(date, loc, { timezone: 330 })!;
728
679
  if (r.eclipse) {
729
- r.eclipse.kind; // 'solar' | 'lunar'
730
- r.eclipse.subtype; // 'partial' | 'total' | 'annular' | 'penumbral'
731
- r.eclipse.obscuration; // 0..1 fraction of the disc AREA covered
732
- r.eclipse.magnitude; // catalogue magnitude — DIAMETER fraction;
733
- // >1 when total, negative when penumbral
734
- r.eclipse.visibleFromLocation; // body above horizon at peak?
735
- r.eclipse.start; r.eclipse.peak; r.eclipse.end;
680
+ r.eclipse.kind; r.eclipse.subtype; // 'solar'|'lunar', 'partial'|'total'|…
681
+ r.eclipse.obscuration; // disc AREA covered, 0..1
682
+ r.eclipse.magnitude; // catalogue DIAMETER fraction
736
683
  r.eclipse.sutakStart; r.eclipse.sutakEnd;
737
- // Sutak: 12 h (4 prahara) before solar, 9 h (3 prahara) before lunar
738
684
  }
739
685
 
740
- import { getUpcomingSolarEclipse, getUpcomingLunarEclipse } from 'panchang-ts';
741
- const next = getUpcomingSolarEclipse(new Date(), loc, 365 /* days */);
742
- ```
743
-
744
- ### Pre-computed table — build your own and cache it
745
-
746
- Same pattern as festivals: build a table with `buildEclipsesTable`, cache it,
747
- read it back through the engine-free `panchang-ts/eclipses` entry point.
748
-
749
- **No table is bundled.** Which eclipses are visible — and therefore which carry
750
- `sutak` — is location-dependent, so a table is only meaningful for the place it
751
- was built for.
686
+ import { getUpcomingSolarEclipse, computeMoonPhasesInRange } from 'panchang-ts';
687
+ getUpcomingSolarEclipse(new Date(), loc, 365);
688
+ computeMoonPhasesInRange(start, end); // precise new/quarter/full instants
752
689
 
753
- ```typescript
754
- import { buildEclipsesTable } from 'panchang-ts'; // uses the engine
755
- import {
756
- readEclipsesForYear,
757
- readEclipsesForDate,
758
- readEclipsesYearRange,
759
- } from 'panchang-ts/eclipses'; // engine-free
760
-
761
- const table = buildEclipsesTable({
762
- location: { latitude: 25.3176, longitude: 82.9739 }, // Varanasi
763
- timezoneOffsetMinutes: 330,
764
- startYear: 2024,
765
- endYear: 2031,
766
- languages: ['en', 'hi'],
767
- // visibleOnly: false → also include eclipses below the horizon (no sutak)
768
- });
769
- // …persist `table` as JSON, then:
770
-
771
- readEclipsesYearRange(table); // { start: 2024, end: 2031 }
772
- const e = readEclipsesForYear(table, 2025)![0].eclipses[0];
773
- e.kind; // 'lunar'
774
- e.subtype; // 'total'
775
- e.start; e.peak; e.end; // ISO UTC strings
776
- e.obscuration; // 0..1 disc area covered at peak
777
- e.magnitude; // catalogue magnitude (diameter); >1 total, <0 penumbral
778
- e.visibleFromLocation; // visible during any phase?
779
- e.visibleAtPeak; // is greatest eclipse itself above the horizon?
780
- e.sutak; // { start, end } — see note below
781
- readEclipsesForDate(table, '2025-09-07', 'hi'); // [पूर्ण चंद्र ग्रहण]
690
+ // Engine-free tables: panchang-ts/eclipses and panchang-ts/moon-phases
782
691
  ```
783
692
 
784
- By default a table lists every eclipse **visible from the location during any
785
- phase** (so one already in progress at moon/sunrise or moon/sunset is included);
786
- `visibleAtPeak` tells you whether greatest eclipse itself is observable.
787
-
788
- Solar eclipses report the subtype seen **locally** (a globally-total eclipse may
789
- read `partial` from a given place). The `sutak` window is present only where it
790
- applies — all visible solar eclipses and visible **umbral** (partial/total)
791
- lunar eclipses; **penumbral** lunar eclipses carry no `sutak` and are not
792
- religiously observed (drik / pandit consensus).
793
-
794
- For one-off astronomical detail without building a table, use
795
- `getUpcomingEclipses` / `computeEclipsesInRange` from the main entry.
796
-
797
- `npm run eclipses:gen` is a worked example; it writes a rolling 2-past /
798
- 5-future window to `./eclipses.json` (or a path you pass).
799
-
800
- ## Moon Phases
693
+ ## Muhurta Engine
801
694
 
802
- The four principal lunar phases — **new** (Amavasya), **first quarter**,
803
- **full** (Purnima), **last quarter** — as precise instants. (These are the
804
- astronomical quarter moments, distinct from the same-named *tithis*, which are
805
- ~24h windows.)
695
+ 📖 [Muhurta Engine →](https://dharmagya.app/docs/panchang-ts/muhurta)
806
696
 
807
697
  ```typescript
808
- import { computeMoonPhasesInRange } from 'panchang-ts';
809
- const phases = computeMoonPhasesInRange(new Date('2026-01-01'), new Date('2026-12-31'));
810
- phases.forEach(p => console.log(p.phase, p.time.toISOString())); // ~49 / year
811
- ```
812
-
813
- ### Pre-computed table — build your own and cache it
814
-
815
- Same pattern again, at `panchang-ts/moon-phases`. Phases are **global instants**,
816
- so `buildMoonPhasesTable` takes only a `timezoneOffsetMinutes` (no coordinates)
817
- — the timezone just decides which calendar date each instant lands on (a new
818
- moon at 19:52 UTC on Jan 18 is listed under Jan 19 in IST).
698
+ import { scoreMuhurta, computeAuspiciousDatesInRange, vivahRule } from 'panchang-ts';
819
699
 
820
- ```typescript
821
- import { buildMoonPhasesTable } from 'panchang-ts'; // uses the engine
822
- import {
823
- readMoonPhasesForYear,
824
- readMoonPhasesForDate,
825
- readMoonPhasesYearRange,
826
- } from 'panchang-ts/moon-phases'; // engine-free
827
-
828
- const table = buildMoonPhasesTable({
829
- timezoneOffsetMinutes: 330, // IST; -300 = US Eastern
830
- startYear: 2024,
831
- endYear: 2031,
832
- languages: ['en', 'hi'],
833
- });
834
- // …persist `table` as JSON, then:
700
+ const s = scoreMuhurta(new Date('2026-05-12'), DELHI, vivahRule, { timezone: 330 });
701
+ s.score; // 0..100; passes when 50
702
+ s.factors; // structured, stable codes — localize/filter on these
703
+ s.reasons; // diagnostic English
835
704
 
836
- readMoonPhasesYearRange(table); // { start: 2024, end: 2031 }
837
- readMoonPhasesForYear(table, 2026)!.length; // ~49 phase days
838
- readMoonPhasesForDate(table, '2026-01-03'); // [{ phase: 'full', name: 'Full Moon', … }]
839
- readMoonPhasesForDate(table, '2026-01-03', 'hi'); // [{ phase: 'full', name: 'पूर्णिमा', … }]
705
+ computeAuspiciousDatesInRange(vivahRule, start, end, DELHI, { timezone: 330 });
840
706
  ```
841
707
 
842
- Each entry carries `phase`, the phase `time` (ISO UTC), and `en` + `hi` text.
708
+ 13 stock rules (vivah, griha pravesh, namakarana, ) or your own pure-data
709
+ `MuhurtaRule`. Vara × Tithi yogas (Siddha, Amrita, Dagdha, …) are scored
710
+ jointly. Pre-compute a table with `buildMuhurtaTable` and read it back through
711
+ `panchang-ts/muhurta` (~1.7 KB, no astronomy code).
843
712
 
844
- `npm run moon-phases:gen` is a worked example; it writes a rolling 2-past /
845
- 5-future window to `./moonPhases.json` (or a path you pass).
713
+ ## Planetary Positions & Birth Charts
846
714
 
847
- ## Planetary Positions
715
+ 📖 [Birth Charts →](https://dharmagya.app/docs/panchang-ts/birth-chart)
848
716
 
849
717
  ```typescript
850
- import { computePlanetaryPositions, GRAHA_ABBR } from 'panchang-ts';
718
+ import {
719
+ computePlanetaryPositions, computeLagna, computeBhava,
720
+ computeRashiChart, computeNavamsa, computeDivisionalChart, computeDignity,
721
+ } from 'panchang-ts';
851
722
 
852
723
  const g = computePlanetaryPositions(new Date(), 'lahiri');
853
- g.jupiter.rashi.name; // "Dhanu"
854
- g.jupiter.degreeInRashi; // 18.42
855
- g.jupiter.nakshatra.name; // "Purva Ashadha"
856
- g.jupiter.nakshatra.pada; // 3
857
- g.saturn.isRetrograde;
858
- GRAHA_ABBR['Jupiter']; // "Ju"
859
-
860
- // True node (sharper Rahu/Ketu via Meeus periodic correction)
861
- const gT = computePlanetaryPositions(new Date(), 'lahiri', undefined, 'true');
724
+ g.jupiter.rashi.name; g.jupiter.nakshatra.pada; g.saturn.isRetrograde;
725
+
726
+ const d1 = computeRashiChart(birth, loc); // houseSystem: whole-sign | equal | placidus-kp
727
+ d1.byPlanet.Mars.house; // keyed lookup, no linear scan
728
+ const d9 = computeNavamsa(birth, loc); // + D2/D3/D7/D10/D12/D30
729
+ computeDignity('Mars', 9); // 'exalted'
862
730
  ```
863
731
 
864
- ## Dashas
732
+ ## Dashas & Personal Transits
865
733
 
866
- Five classical systems:
734
+ 📖 [Dashas & Transits →](https://dharmagya.app/docs/panchang-ts/dashas)
867
735
 
868
736
  ```typescript
869
737
  import {
870
738
  computeVimshottariDashaFromBirth, computeVimshottariPratyantar,
871
- computeAshtottariDasha, computeYoginiDasha, computeCharaDasha, computeNarayanDasha,
739
+ computeAshtottariDasha, computeYoginiDasha, computeCharaDasha,
740
+ computeNarayanDasha, computeSadeSati,
872
741
  } from 'panchang-ts';
873
742
 
874
- // 1. Vimshottari — 120-year, 9-lord, with 3-level Maha→Antar→Pratyantar.
875
- const vim = computeVimshottariDashaFromBirth(birth, 'lahiri');
876
- const pratyantars = computeVimshottariPratyantar(vim.mahaDashas[0]!.antarDashas[0]!);
877
-
878
- // 2. Ashtottari — 108-year, 8-lord cycle (no Ketu).
879
- const ash = computeAshtottariDasha(birth, moonLon);
880
-
881
- // 3. Yogini — 36-year, 8 yoginis.
882
- const yog = computeYoginiDasha(birth, moonLon);
883
- yog.mahaDashas[0]!.yogini; // 'Dhanya'
884
- yog.mahaDashas[0]!.lord; // 'Jupiter'
885
-
886
- // 4. Chara (Jaimini) — sign-based, 9-8-7 years per modality, forward only.
887
- const cha = computeCharaDasha(birth, loc);
888
-
889
- // 5. Narayan (Jaimini) — sign-based, parity-based direction.
890
- // Vishama-pada lagna {Aries, Taurus, Gemini, Libra, Scorpio, Sag} → forward
891
- // Sama-pada lagna {Cancer, Leo, Virgo, Capricorn, Aquarius, Pisces} → backward
892
- const nar = computeNarayanDasha(birth, loc);
893
- nar.direction; // 'forward' | 'backward'
894
-
895
- // Narayan variable-duration variant (Sanjay Rath):
896
- const narV = computeNarayanDasha(birth, loc, 'lahiri', { duration: 'variable' });
897
- narV.mahaDashas[0]!.years; // 0..12 from rashi-to-lord count (+1 exalt, -1 debil)
898
- ```
899
-
900
- ## Personal Transits
901
-
902
- ```typescript
903
- const r = getDailyPanchang(date, loc, {
904
- timezone: 330,
905
- janmaRashi: 3, // 0 = Mesha … 11 = Meena
906
- janmaNakshatra: 0, // 0 = Ashwini … 26 = Revati
907
- })!;
908
- r.chandraBalam; // { house, quality: 'strong'|'weak', name, englishName } — null without janmaRashi
909
- r.tarabala; // { taraIndex, name, englishName, quality } — null without janmaNakshatra
910
-
911
- import { computeSadeSati } from 'panchang-ts';
912
- const ss = computeSadeSati(natalMoonRashiIndex, new Date());
913
- // → { active, phase: 1|2|3|null, currentArcStart, currentArcEnd, nextArcStart }
743
+ const vim = computeVimshottariDashaFromBirth(birth, 'lahiri'); // 3-level
744
+ computeSadeSati(natalMoonRashiIndex, new Date());
745
+ // Daily transits: pass janmaRashi / janmaNakshatra to getDailyPanchang
746
+ // and read r.chandraBalam / r.tarabala.
914
747
  ```
915
748
 
916
- ## Birth Chart
749
+ ## Strength, Yogas & Karakas
917
750
 
918
- Sidereal Lagna, Bhava under three house systems, D1 + six classical divisional
919
- charts (D2/D3/D7/D9/D10/D12/D30), and Planetary Dignity.
751
+ 📖 [Strength, Yogas & Karakas →](https://dharmagya.app/docs/panchang-ts/strength-yogas)
920
752
 
921
753
  ```typescript
922
754
  import {
923
- computeLagna, computeBhava, computeRashiChart, computeNavamsa,
924
- computeDivisionalChart, computeDignity,
755
+ computeAspects, computeShadbala, computeBhavaBala,
756
+ computeAshtakavarga, computeYogas, computeJaiminiKarakas,
925
757
  } from 'panchang-ts';
926
758
 
927
- const birth = new Date('1995-08-15T05:30:00Z');
928
- const loc = { latitude: 28.6139, longitude: 77.2090 };
929
-
930
- const lagna = computeLagna(birth, loc, 'lahiri', 'en');
931
-
932
- // Bhava — 'whole-sign' (default) | 'equal' | 'placidus-kp'.
933
- // Placidus-KP throws PanchangError('CIRCUMPOLAR') beyond ±66.5°.
934
- const houses = computeBhava(birth, loc, { houseSystem: 'whole-sign' });
935
-
936
- // `chart.planets` is the ordered list; `chart.byPlanet` is the same nine
937
- // placements keyed by graha, for direct lookup without a linear scan.
938
- const chart = computeRashiChart(birth, loc);
939
- chart.byPlanet.Mars.house; // instead of chart.planets.find(...)!
940
- chart.planets.map(p => p.rashi); // iterate the list as before
941
-
942
- // D1 — full Rashi chart with 9-graha house placement.
943
- const d1 = computeRashiChart(birth, loc, { houseSystem: 'whole-sign' });
944
- d1.planets.find(p => p.planet === 'Jupiter')?.house;
945
- d1.planets.find(p => p.planet === 'Saturn')?.isRetrograde;
946
-
947
- // Divisional charts (D2 Hora, D3 Drekkana, D7 Saptamsa, D9 Navamsa,
948
- // D10 Dasamsa, D12 Dwadasamsa, D30 Trimsamsa).
949
- const d9 = computeNavamsa(birth, loc);
950
- const d10 = computeDivisionalChart(birth, loc, 'D10');
951
- const d30 = computeDivisionalChart(birth, loc, 'D30');
952
-
953
- // Planetary dignity (BPHS Ch.3-4).
954
- computeDignity('Mars', 0); // 'moolatrikona' (Aries)
955
- computeDignity('Mars', 9); // 'exalted' (Capricorn)
956
- computeDignity('Sun', 6); // 'debilitated' (Libra)
759
+ computeShadbala(birth, loc); // 6-fold, in Virupas
760
+ computeAshtakavarga(d1, { reductions: true }); // Bhinna + Sarva + Sodhana
761
+ computeYogas(d1); // ~25 named, with bhanga
762
+ computeJaiminiKarakas(d1, { variant: '8-jaimini' });
957
763
  ```
958
764
 
959
- Birth-chart helpers accept the full ayanamsa set including `'true-chitra'` and
960
- `'thirukanitham'`.
961
-
962
765
  ## Compatibility & Doshas
963
766
 
767
+ 📖 [Matching & Doshas →](https://dharmagya.app/docs/panchang-ts/matching-doshas)
768
+
964
769
  ```typescript
965
770
  import {
966
771
  computeAshtakoot, computePathuPorutham,
967
- computeMangalDosha, computeKaalSarp, computePitruDosha,
772
+ computeMangalDosha, computeMangalCompatibility, computeKaalSarp, computePitruDosha,
968
773
  } from 'panchang-ts';
969
774
 
970
- // Ashtakoot (North Indian, 36-point) Varna, Vashya, Tara, Yoni,
971
- // Graha Maitri, Gana, Bhakoot, Nadi (max 1/2/3/4/5/6/7/8).
972
- const match = computeAshtakoot(
973
- { rashi: 4, nakshatra: 9 },
974
- { rashi: 0, nakshatra: 1 },
975
- );
775
+ computeAshtakoot({ rashi: 4, nakshatra: 9 }, { rashi: 0, nakshatra: 1 });
976
776
  // → { totalScore: 0..36, koots: KootScore[8], cancellations: string[] }
777
+ // Opt-in Gana-dosha cancellation (default off — preserves drik 36-guna parity):
778
+ computeAshtakoot(boy, girl, { ganaCancellation: true });
977
779
 
978
- // Opt-in Bhakoot cancellations need extra natal data:
979
- // `lagnaRashi` enables same-lagna-lord + same-7th-lord rules;
980
- // `navamsaRashi` enables the same-Navamsa-lord rule.
981
- const richer = computeAshtakoot(
982
- { rashi: 4, nakshatra: 9, lagnaRashi: 7, navamsaRashi: 2 },
983
- { rashi: 0, nakshatra: 1, lagnaRashi: 1, navamsaRashi: 5 },
984
- );
985
-
986
- // Manglik is a PAIRWISE verdict, not a per-chart one: when both partners are
987
- // Manglik the two afflictions neutralise each other, so the pair is clean
988
- // where a Manglik/non-Manglik pair is not.
989
- const m = computeMangalCompatibility(boyChart, girlChart);
990
- m.afflicted; // false when neither is Manglik AND when both are
991
- m.cancellations; // ['both natives Manglik — mutual cancellation']
992
- m.boy; m.girl; // each native's own MangalDoshaInfo, severity included
993
-
994
- // Pathu Porutham (Tamil/Kerala, 10-fold) — binary pass/fail per koot.
995
- // Three vetoes (Yoni, Rajju, Vedha) flip `recommended` regardless of count.
996
- const tp = computePathuPorutham(
997
- { rashi: 4, nakshatra: 9 },
998
- { rashi: 0, nakshatra: 1 },
999
- );
1000
- tp.totalPasses; // 0..10
1001
- tp.recommended; // no veto + ≥5 passes
1002
-
1003
- // Doshas
1004
- computeMangalDosha(d1);
1005
- // Mars in 1/2/4/7/8/12 from Lagna, Moon, AND Venus (Drik rule set).
1006
- // Cancellations: Mars in own sign/exalted, conjunct Jup/Moon/Venus,
1007
- // or aspected by Jupiter (5/7/9 sign-aspect).
1008
- // Severity (anshik/purna) is computed pre-cancellation.
1009
-
1010
- computeKaalSarp(d1);
1011
- // 12 subtypes by Rahu's house: anant, kulik, vasuki, shankhpal, padma,
1012
- // mahapadma, takshak, karkotak, shankhachud, ghatak, vishdhar, sheshnag.
1013
-
1014
- computePitruDosha(d1);
1015
- // Pandit-consensus 4-trigger set (rules cited by ≥3 of 6 surveyed
1016
- // pandit sources): Sun+Rahu conjunction (any house), Sun+Saturn
1017
- // conjunction (any house), Rahu in 9th house, 9th-lord conjunct Rahu.
1018
- // Drik panchang publishes no Pitru calculator; minority/expansive
1019
- // rules (Sun in 9th alone, Ketu in 4th, 9th lord in dusthana, etc.)
1020
- // are intentionally excluded.
1021
- ```
1022
-
1023
- **Limitations.** Ashtakoot Vashya koot uses single-vashya per rashi.
1024
- Bhakoot Parivartana (rashi-lord exchange) cancellation needs per-graha
1025
- position data not carried by the `NatalMoon` shape and is not modelled.
1026
-
1027
- ## Strength & Aspects
1028
-
1029
- ```typescript
1030
- import {
1031
- computeAspects, computeShadbala, computeBhavaBala, computeAshtakavarga,
1032
- } from 'panchang-ts';
1033
-
1034
- // Drishti — every graha aspects the 7th; malefics gain extras
1035
- // (Mars 4+8, Jupiter 5+9, Saturn 3+10). Node aspect mode is configurable:
1036
- const aspects = computeAspects(d1); // BPHS 7th-only on nodes
1037
- const aspExt = computeAspects(d1, { nodeAspects: '5-and-9' }); // KP/BV Raman extension
1038
-
1039
- // Shadbala — 7 visible grahas, 6 components, in Virupas (60 V = 1 Rupa).
1040
- // Sthana = Uchcha + Saptavargaja (D1/D2/D3/D7/D9/D12/D30 dignity sum)
1041
- // + Ojha-Yugma (rashi+navamsa parity) + Drekkana (gender decanate).
1042
- // Range [0, 420 V]. Dig is directional cusp; Kala = Nathonatha + Paksha;
1043
- // Chesta is retrograde-bucket; Naisargika is fixed rank; Drik is weighted aspects.
1044
- const bala = computeShadbala(birth, loc);
1045
-
1046
- // Bhava Bala — 12-bhava strength built on top of Shadbala.
1047
- // Per-bhava: { bhavadhipati, dik, drik, sthana, total }.
1048
- const bhavaBala = computeBhavaBala(birth, loc);
1049
-
1050
- // Ashtakavarga — 12-rashi bindu grids (BPHS Ch. 66).
1051
- const av = computeAshtakavarga(d1);
1052
- av.sarvashtaka; // 12 cells, each 0..56, total 336
1053
- av.bhinnashtaka.Jupiter; // 12-cell grid; Jupiter total = 56 (chart-invariant)
1054
- // Other invariants: Sun=47, Moon=49, Mars=39, Mercury=54, Venus=52, Saturn=39.
1055
-
1056
- // Trikona + Ekadhipatya Sodhana reductions (BPHS Ch. 67):
1057
- const avR = computeAshtakavarga(d1, { reductions: true });
1058
- avR.reduced!.sarvashtaka;
1059
- ```
1060
-
1061
- Rahu and Ketu are not Ashtakavarga receivers or contributors (classical
1062
- Parashara scheme).
1063
-
1064
- ## Yogas & Karakas
1065
-
1066
- ```typescript
1067
- import { computeYogas, computeJaiminiKarakas } from 'panchang-ts';
1068
-
1069
- // ~25 named yogas — Pancha Mahapurusha (Ruchaka/Bhadra/Hamsa/Malavya/Sasha),
1070
- // lunar (Gajakesari, Sunapha, Anapha, Durudhura, Kemadruma), solar
1071
- // (Budha-Aditya, Veshi, Vasi, Ubhayachari), Raja (kendra/trikona-lord,
1072
- // Dharma-Karmadhipati, Vipareeta, Lakshmi), Dhana (2-11, 5-9, Vasumati),
1073
- // Vargottama, Yogakaraka, Neecha Bhanga, Daridra.
1074
- const yogas = computeYogas(d1);
1075
- // → [{ name, type, reasons[], bhanga?: { applies, reasons[] } }, …]
1076
-
1077
- // Optional cancellation annotations: 5 Pancha Mahapurusha + Gajakesari
1078
- // surface `bhanga` (Sun/Moon conjunct or Jupiter combust/debilitated).
1079
- // Neecha Bhanga: dispositor in kendra from Lagna OR Moon; lord-of-
1080
- // exaltation-rashi in kendra from Lagna or Moon; mutual exchange;
1081
- // dispositor aspecting the debilitated planet.
1082
-
1083
- // Filter by type / pass D9 for Vargottama:
1084
- const d9 = computeNavamsa(birth, loc);
1085
- const all = computeYogas(d1, { types: ['raja','dhana'], navamsa: d9 });
1086
-
1087
- // Jaimini Karakas — Atmakaraka (highest degree-in-rashi) … Darakaraka (lowest).
1088
- const k7 = computeJaiminiKarakas(d1); // 7-graha Parashara default
1089
- const k8 = computeJaiminiKarakas(d1, { variant: '8-jaimini' }); // adds Rahu (degree reversed),
1090
- // inserts Pitrukaraka at 5th
780
+ computeMangalCompatibility(boyChart, girlChart); // Manglik is a PAIRWISE verdict
781
+ computeKaalSarp(d1); // 12 subtypes by Rahu's house
1091
782
  ```
1092
783
 
1093
- Yoga and Karaka names are English/transliterated proper nouns and intentionally
1094
- **not** locale-resolved.
784
+ ## Annual Charts, Sensitive Points, KP & Prashna
1095
785
 
1096
- ## Annual & Sensitive Layers
786
+ 📖 [Annual Charts →](https://dharmagya.app/docs/panchang-ts/annual-charts) ·
787
+ [KP & Prashna →](https://dharmagya.app/docs/panchang-ts/kp-prashna)
1097
788
 
1098
789
  ```typescript
1099
790
  import {
1100
791
  computeVarshaphala, computeTithiPravesha, computeArudhas,
1101
792
  computeHoraLagna, computeGhatiLagna, computeBhavaLagna, computeSripatiLagna,
1102
793
  computeUpagrahas, computeArgala,
1103
- } from 'panchang-ts';
1104
-
1105
- // Varshaphala — Tajik annual chart for the Nth solar return.
1106
- const v = computeVarshaphala(birth, 30, loc);
1107
- v.solarReturnInstant;
1108
- v.varshaLagna.rashi.name;
1109
- v.muntha.rashi; v.muntha.house; // muntha = (natalLagnaRashi + 30) mod 12
1110
- v.yearLord; // strongest of 4 candidates by Shadbala
1111
- v.sahams.Punya.house;
1112
- v.sahams.Vivaha.rashi;
1113
- // 27 Sahams: Punya, Vidya, Yasas, Mitra, Karma, Vivaha, Putra, Roga, Marana,
1114
- // Rajya, Raja, Bandhu, Dharma, Gnati, Apamrityu, Bhratri, Matri, Pitri, Sama,
1115
- // Bandhana, Karyasiddhi, Vyapara, Sastra, Asha, Labha, Susha, Tapas.
1116
-
1117
- // Tithi Pravesha — annual chart cast when Sun is in natal sidereal sign AND
1118
- // Sun-Moon separation equals natal separation. Preserves natal tithi exactly.
1119
- const tp = computeTithiPravesha(birth, 30, loc);
1120
- tp.natalTithi === tp.praveshTithi; // always true
1121
-
1122
- // Arudha padas — image/reflection of each bhava. Arudha[0] = Arudha Lagna (AL).
1123
- const a = computeArudhas(d1);
1124
- a[0]!.bhava; // 1 — AL
1125
- a[0]!.arudhaRashi; // 0..11
1126
- a[6]!.bhava; // 7 — Darapada (spouse pada)
1127
-
1128
- // Special lagnas — time-derived sensitive points from sunrise on/before birth.
1129
- computeHoraLagna(birth, loc); // 30°/hour (1 rashi/hour)
1130
- computeGhatiLagna(birth, loc); // 75°/hour (1 rashi/24 min)
1131
- computeBhavaLagna(birth, loc); // 15°/hour (1 rashi/2 hours)
1132
- computeSripatiLagna(birth, loc); // = natal lagna (cusp 1)
1133
-
1134
- // Sripati cusps 2–12 (opt-in) — 4 angular cusps + trisected intermediates.
1135
- // Defined at every latitude (unlike Placidus).
1136
- const sripati = computeSripatiLagna(birth, loc, 'lahiri', 'en', { includeCusps: true });
1137
- sripati.cusps; // number[12] of bhava madhyas; cusps[0/3/6/9] = ASC/IC/DSC/MC
1138
-
1139
- // Upagrahas — Gulika, Mandi (rising-asc at Saturn segment start/midpoint),
1140
- // plus Sun-derived Dhuma, Vyatipata, Parivesha, Indrachapa, Upaketu.
1141
- const u = computeUpagrahas(birth, loc);
1142
- u.gulika.longitude; u.gulika.rashi; u.gulika.house;
1143
-
1144
- // Argala (Jaimini) — planets in 2/4/11 from a bhava form Argala (intervention);
1145
- // 3/10/12 form Virodhargala (counter). Each planet hits exactly 6 of 12 bhavas.
1146
- const arg = computeArgala(d1);
1147
- arg[0]!.argala; arg[0]!.virodhargala;
1148
-
1149
- // Trikonargala (5/9 trine, opt-in) — Ketu reversal: 5th-from → virodhaka,
1150
- // 9th-from → source.
1151
- const argT = computeArgala(d1, { includeTrikonargala: true });
1152
- argT[0]!.trikona!.sources;
1153
- argT[0]!.trikona!.virodhakas;
1154
- ```
1155
-
1156
- ## KP & Prashna
1157
-
1158
- ```typescript
1159
- import {
1160
794
  computeKpSubLord, computeKpCuspalSubLords, computeKpSignificators,
1161
795
  computePrashnaChart,
1162
796
  } from 'panchang-ts';
1163
797
 
1164
- // KP sub-lord at any sidereal longitude (243 sub-divisions across the zodiac,
1165
- // proportional to Vimshottari years).
1166
- const info = computeKpSubLord(45.5); // 15°30' Taurus
1167
- info.signLord; // 'Venus'
1168
- info.starLord; // 'Moon'
1169
- info.subLord;
1170
-
1171
- // Cuspal sub-lords (always Placidus-KP — KP's anchor scheme).
1172
- const cusps = computeKpCuspalSubLords(birth, loc);
1173
- cusps.cusps[0]!.subLord; // ascendant
1174
- cusps.cusps[6]!.subLord; // descendant
1175
-
1176
- // Significators — for each planet, the houses it signifies via the 4-fold KP rule
1177
- // (occupant + star-lord-occupant + owner + star-lord-owner).
1178
- const sig = computeKpSignificators(d1);
1179
- sig.byPlanet.Sun;
1180
- sig.byHouse[10];
1181
-
1182
- // Prashna (horary) chart — cast at question moment from querent's location.
1183
- const pchart = computePrashnaChart(
1184
- new Date('2026-05-09T14:30:00Z'),
1185
- { latitude: 19.0760, longitude: 72.8777 },
1186
- );
1187
- pchart.lagna.rashi.name;
1188
- pchart.bhava.system; // 'placidus-kp' by default (KP horary anchor)
1189
- pchart.planets[1]!.house; // Moon — primary mind significator
1190
- ```
1191
-
1192
- Same return shape as a natal `BirthChart`. Pass `{ houseSystem: 'whole-sign' }`
1193
- to `computePrashnaChart` for traditional Vedic Prashna.
1194
-
1195
- ## Muhurta Engine
1196
-
1197
- ```typescript
1198
- import { scoreMuhurta, computeAuspiciousDatesInRange, vivahRule } from 'panchang-ts';
1199
-
1200
- const r = scoreMuhurta(new Date('2026-05-12'), DELHI, vivahRule, { timezone: 330 });
1201
- // → { date, score: 0..100, passes: boolean,
1202
- // reasons: string[], // diagnostic English
1203
- // factors: MuhurtaFactor[] } // { code, axis, index?, delta } — stable
1204
-
1205
- r.factors.filter(f => f.delta < 0); // what cost the day points
1206
- r.factors.some(f => f.axis === 'exclusion'); // hard-excluded?
1207
-
1208
- const dates = computeAuspiciousDatesInRange(
1209
- vivahRule,
1210
- new Date('2026-05-01'),
1211
- new Date('2026-05-31'),
1212
- DELHI,
1213
- { timezone: 330 },
1214
- ); // MuhurtaDay[] sorted by score desc; full panchang attached
1215
-
1216
- // Custom rule (pure data, no engine code needed)
1217
- const myRule: MuhurtaRule = {
1218
- occasion: 'launch_party',
1219
- auspiciousVaras: [3, 4, 5],
1220
- auspiciousNakshatras: [11, 12, 21],
1221
- bhadra: 'penalize', // 'ignore' | 'penalize' | 'exclude'
1222
- excludeEkadashi: true,
1223
- excludeAdhikaMasa: true,
1224
- };
798
+ const v = computeVarshaphala(birth, 30, loc); // Tajik + Muntha + 27 Sahams
799
+ computeTithiPravesha(birth, 30, loc); // preserves natal tithi exactly
800
+ computeKpSubLord(45.5).subLord; // 243 sub-divisions
801
+ computePrashnaChart(questionTime, querentLoc); // horary, Placidus-KP default
1225
802
  ```
1226
803
 
1227
- **Tithi and vara are scored jointly, not just per-anga.** The classical Vara ×
1228
- Tithi yogas — Siddha, Amrita, Dagdha, Visha, Hutasana, Krakacha, Samvartaka —
1229
- are applied to every rule, so a Rikta tithi landing on a Saturday is partly
1230
- redeemed by Siddha yoga rather than flatly penalised. Set
1231
- `varaTithiYogas: false` for the older per-anga-only scoring, or call
1232
- `computeVaraTithiYogas(vara, tithi)` directly. Where an auspicious and an
1233
- inauspicious yoga both fire — a documented ambiguity in the sources — both are
1234
- surfaced as separate factors and allowed to net out.
1235
-
1236
- `bhadra` defaults to `'ignore'`; the stock rules use `'penalize'`. A whole-day
1237
- `'exclude'` is rarely what you want: Vishti karana sits at fixed positions in
1238
- the tithi cycle, so vetoing the day removes seven tithis outright — among them
1239
- Shukla Ekadashi, which the same sources list as *preferred* for vivah. Read
1240
- `panchang.inauspicious.bhadra` for the window and schedule around it.
1241
- `excludeBhadra: true` still works as an alias for `bhadra: 'exclude'`.
1242
-
1243
- 13 stock rules: vivah, griha pravesh, namakarana, vidyarambh, vahan kharidi,
1244
- annaprashan, mundan, upanayanam, karnavedha, aksharabhyasam, seemantham, shop
1245
- opening, travel start.
1246
-
1247
- ### Pre-computed table — build your own and cache it
1248
-
1249
- Scoring a year of days runs the engine ~365 times. If your app asks the same
1250
- question repeatedly, compute the answer once and ship the JSON — the same
1251
- pattern the festival, eclipse and Moon-phase tables use.
1252
-
1253
- ```typescript
1254
- import { buildMuhurtaTable, vivahRule } from 'panchang-ts';
1255
-
1256
- const table = buildMuhurtaTable({
1257
- rule: vivahRule,
1258
- location: { latitude: 25.3176, longitude: 82.9739 },
1259
- timezoneOffsetMinutes: 330,
1260
- startYear: 2026,
1261
- endYear: 2031,
1262
- referenceLocation: 'Varanasi',
1263
- });
1264
- // persist JSON.stringify(table) — 6 years of vivah dates is ~74 KB
1265
- ```
1266
-
1267
- Read it back through the engine-free `panchang-ts/muhurta` entry (~1.7 KB, no
1268
- astronomy code in your bundle):
1269
-
1270
- ```typescript
1271
- import {
1272
- readMuhurtaForYear,
1273
- readMuhurtaForDate,
1274
- readMuhurtaYearRange,
1275
- readMuhurtaOccasion,
1276
- readBestMuhurtaDays,
1277
- } from 'panchang-ts/muhurta';
1278
-
1279
- const table = JSON.parse(await (await fetch('/muhurta-vivah.json')).text());
1280
-
1281
- readMuhurtaOccasion(table); // 'vivah'
1282
- readMuhurtaYearRange(table); // { start: 2026, end: 2031 }
1283
- readMuhurtaForYear(table, 2026); // MuhurtaTableDay[] — passing days, by date
1284
- readMuhurtaForDate(table, '2026-11-11');
1285
- readBestMuhurtaDays(table, 5); // top 5 across the table, highest first
1286
- ```
1287
-
1288
- Only days that **pass** the rule are stored by default; pass
1289
- `includeFailures: true` to keep every day with its score. Scores are location-
1290
- *and* rule-dependent, so a table built for Varanasi and `vivah` says nothing
1291
- about another place or occasion.
1292
-
1293
- `npm run muhurta:gen` is a worked example script
1294
- ([scripts/generate-muhurta-json.ts](scripts/generate-muhurta-json.ts)):
1295
-
1296
- ```bash
1297
- npm run muhurta:gen -- muhurta-vivah.json vivah
1298
- ```
1299
-
1300
- Scoring: starts at 50; +10 per matching auspicious axis (tithi / nakshatra /
1301
- vara / yoga), -15 per inauspicious axis, hard exclusions zero the score.
1302
- Special yogas (Amrit Siddhi, Sarvartha Siddhi, Ravi/Guru Pushya) add +5;
1303
- Jwalamukhi subtracts -10. Clamped 0..100; `passes: true` when score ≥ 50.
1304
-
1305
- Every scoring input appears in both `reasons` (English prose, diagnostic, not a
1306
- stable format) and `factors` (structured, with a stable `code`). Localize and
1307
- filter on `factors`.
1308
-
1309
- `scoreMuhurta` computes only the sections it actually scores against, so it is
1310
- cheaper than a full `getDailyPanchang`. `computeAuspiciousDatesInRange` does not narrow —
1311
- each returned day carries its complete `panchang` for callers to drill into.
1312
-
1313
804
  ## Calendar Conversion
1314
805
 
806
+ 📖 [Calendar Conversion →](https://dharmagya.app/docs/panchang-ts/calendar-conversion)
807
+
1315
808
  ```typescript
1316
809
  import {
1317
810
  convertGregorianToHindu, convertHinduToGregorian,
1318
811
  getKaliYugaYear, getHinduNewYear,
1319
812
  computeEkadashiDatesForYear, computeSankrantisForYear,
1320
- computeFestivalsInRange, getUpcomingEclipses, computeEclipsesInRange,
1321
813
  } from 'panchang-ts';
1322
814
 
1323
- // Gregorian → Hindu coords at sunrise
1324
- const h = convertGregorianToHindu(new Date('2026-04-15'), DELHI, { timezone: 330 });
1325
-
1326
- // Hindu → Gregorian
1327
- const dates = convertHinduToGregorian(
815
+ convertHinduToGregorian(
1328
816
  { vikramSamvat: 2083, masaIndex: 0, paksha: 'shukla', pakshaTithi: 9 },
1329
817
  DELHI, { timezone: 330 },
1330
- ); // → Rama Navami in VS 2083
1331
-
1332
- getKaliYugaYear(new Date('2026-04-01')); // 5127
1333
- getHinduNewYear(2026, 'tamil-nadu', DELHI, { timezone: 330 }); // Puthandu
1334
-
1335
- computeEkadashiDatesForYear(2026, DELHI, { timezone: 330 }); // ~24 Date[]
1336
- computeSankrantisForYear(2026, DELHI, { timezone: 330 }); // 12 SankrantiEvent[]
1337
- computeFestivalsInRange(start, end, DELHI, { timezone: 330 }); // FestivalDay[]
1338
- getUpcomingEclipses(new Date(), DELHI, 5);
818
+ ); // → Date[] (Rama Navami VS 2083)
819
+ getHinduNewYear(2026, 'tamil-nadu', DELHI, { timezone: 330 }); // region-aware
1339
820
  ```
1340
821
 
1341
- `getHinduNewYear` is region-aware: Tamil Nadu / Kerala / Punjab / Bengal / Assam
1342
- use the **solar** (Mesha Sankranti) anchor; elsewhere uses **Chaitra Shukla
1343
- Pratipada** (Ugadi / Gudi Padwa / Cheti Chand). When Pratipada is a kshaya
1344
- tithi (e.g. Ugadi 2026), falls back to the Amanta-Chaitra-masa boundary.
1345
-
1346
822
  ## Localization & Configuration
1347
823
 
1348
- ```typescript
1349
- const hi = getDailyPanchang(date, loc, { timezone: 330, language: 'hi' })!;
1350
- hi.angas.tithis[0].name; // "कृष्ण चतुर्दशी"
1351
- hi.angas.vara.name; // "मंगलवार"
1352
- hi.calendar.chandramasa.name; // "माघ"
1353
- hi.periods.choghadiya.day[0].name; // "अमृत"
1354
- hi.angas.vara.englishName; // "Tuesday" — englishName always English
1355
-
1356
- // All options:
1357
- const r = getDailyPanchang(date, loc, {
1358
- timezone: 330, // number (UTC offset min) or IANA string
1359
- ayanamsa: 'lahiri', // lahiri | raman | krishnamurti | true-chitra | thirukanitham
1360
- language: 'en', // en | hi
1361
- masaSystem: 'purnimanta', // purnimanta | amanta
1362
- region: 'all', // 21 state slugs + 'nepal' + 'all'
1363
- computeEndTimes: true, // false → skip transition searches
1364
- sections: undefined, // undefined = all; see Performance
1365
- janmaRashi: undefined, // pass to populate r.chandraBalam (else null)
1366
- janmaNakshatra: undefined, // pass to populate r.tarabala (else null)
1367
- });
1368
- ```
1369
-
1370
- **Timezone.** Number (minutes from UTC, e.g. `330` for IST) or an IANA zone
1371
- name (e.g. `'America/New_York'`). IANA strings need `Intl`, which older Hermes
1372
- versions lack — pass a number on those targets. DST resolves automatically for
1373
- IANA zones.
1374
-
1375
- ---
1376
-
1377
- ### Localized vs machine-readable fields
1378
-
1379
- Every user-facing string follows `language`. Where a value is also meaningful to
1380
- code, the two are separate fields — the stable key never changes with language:
1381
-
1382
- | Machine-readable | Localized display |
1383
- |---|---|
1384
- | `festival.key` (`'diwali'`) | `festival.name` (`"दिवाली"`) |
1385
- | `bhadra.location` (`'paatal'`) | `bhadra.locationName` (`"पाताल"`) |
1386
- | `eclipse.kind` / `eclipse.subtype` | `eclipse.description` |
1387
- | `muhurtaScore.factors[].code` | `muhurtaScore.reasons` (English only) |
1388
-
1389
- `MuhurtaScore.reasons` is diagnostic English and not a stable format; use
1390
- `factors` for anything shown to a user or branched on in code.
1391
-
1392
- ## Types & Exports
1393
-
1394
- <details>
1395
- <summary><strong>Core, Pancha Anga, Festivals</strong></summary>
824
+ 📖 [Options & Localization →](https://dharmagya.app/docs/panchang-ts/localization)
1396
825
 
1397
826
  ```typescript
1398
- interface GeoLocation { latitude: number; longitude: number; elevation?: number; }
1399
- interface TimePeriod { start: Date; end: Date; }
1400
-
1401
- interface TithiInfo {
1402
- index: number; // 0-29
1403
- name: string;
1404
- paksha: string; // "Shukla"/"Krishna" (en), "शुक्ल"/"कृष्ण" (hi)
1405
- number: number; // 1-15 within the paksha
1406
- completionPercentage: number;
1407
- endTime: Date | null;
1408
- }
1409
- // NakshatraInfo, YogaInfo, KaranaInfo follow the same pattern.
1410
- // DailyTithiInfo extends with startTime + isActiveAtSunrise.
1411
-
1412
- interface VaraInfo {
1413
- index: number; // 0 = Sunday … 6 = Saturday
1414
- name: string; // localized (e.g. "Raviwara")
1415
- shortName: string;
1416
- englishName: string; // always English
1417
- }
1418
-
1419
- interface FestivalInfo {
1420
- key: string; // stable, language-independent id — match on this
1421
- name: string; // localized — display only
1422
- type: 'major' | 'minor' | 'ekadashi' | 'smarta_ekadashi' | 'vaishnava_ekadashi'
1423
- | 'pradosha' | 'sankranti' | 'eclipse';
1424
- description?: string;
1425
- deferralDate?: Date; // Smarta Ekadashi → Dwadashi fast date
1426
- }
1427
-
1428
- type FestivalRegion =
1429
- | 'all'
1430
- | 'tamil-nadu' | 'kerala' | 'karnataka' | 'andhra-pradesh' | 'telangana'
1431
- | 'west-bengal' | 'odisha' | 'assam' | 'bihar' | 'jharkhand'
1432
- | 'gujarat' | 'maharashtra' | 'goa' | 'rajasthan'
1433
- | 'punjab' | 'haryana' | 'himachal-pradesh' | 'uttarakhand'
1434
- | 'uttar-pradesh' | 'madhya-pradesh'
1435
- | 'nepal';
1436
-
1437
- // Legacy slugs accepted (mapped internally): 'tamil' → 'tamil-nadu',
1438
- // 'bengal' → 'west-bengal', 'north-india' → 'all'.
1439
- ```
1440
-
1441
- </details>
1442
-
1443
- <details>
1444
- <summary><strong>Eclipses & Bhadra</strong></summary>
1445
-
1446
- ```typescript
1447
- interface EclipseInfo {
1448
- kind: 'solar' | 'lunar';
1449
- subtype: 'partial' | 'total' | 'annular' | 'penumbral';
1450
- start: Date; peak: Date; end: Date;
1451
- visibleFromLocation: boolean;
1452
- obscuration: number; // disc AREA covered at peak, [0, 1]
1453
- magnitude: number; // catalogue magnitude — disc DIAMETER covered.
1454
- // Not [0, 1]: >1 for a total eclipse, negative
1455
- // for a penumbral lunar one (the Moon misses
1456
- // the umbra), exactly as NASA's canon prints it.
1457
- sutakStart: Date; // 12 h pre-solar / 9 h pre-lunar
1458
- sutakEnd: Date;
1459
- description: string;
1460
- }
1461
-
1462
- interface BhadraInfo {
1463
- start: Date; end: Date;
1464
- location: 'earth' | 'heaven' | 'paatal'; // 'earth' = malefic for all work
1465
- locationName: string; // localized display name
1466
- isActive: boolean;
1467
- }
1468
- ```
1469
-
1470
- </details>
1471
-
1472
- <details>
1473
- <summary><strong>Jyotish</strong></summary>
1474
-
1475
- ```typescript
1476
- type GrahaName = 'Sun' | 'Moon' | 'Mars' | 'Mercury' | 'Jupiter'
1477
- | 'Venus' | 'Saturn' | 'Rahu' | 'Ketu';
1478
-
1479
- interface GrahaPosition {
1480
- planet: GrahaName;
1481
- siderealLongitude: number;
1482
- rashi: RashiInfo;
1483
- degreeInRashi: number;
1484
- nakshatra: NakshatraInfo;
1485
- isRetrograde: boolean; // always false for Sun/Moon; always true for Rahu/Ketu
1486
- }
1487
-
1488
- type DashaLord = 'Ketu' | 'Venus' | 'Sun' | 'Moon' | 'Mars'
1489
- | 'Rahu' | 'Jupiter' | 'Saturn' | 'Mercury';
1490
-
1491
- interface MahaDasha { lord: DashaLord; startDate: Date; endDate: Date;
1492
- years: number; antarDashas: AntarDasha[]; }
1493
- interface VimshottariDashaResult {
1494
- currentMahaDashaLord: DashaLord;
1495
- currentIndex: number;
1496
- mahaDashas: MahaDasha[];
1497
- }
1498
-
1499
- interface ChandraBalamInfo {
1500
- house: number; // 1 = janma rashi; 12 = rashi before janma
1501
- quality: 'strong' | 'weak'; // Shubha houses = 1,3,6,7,10,11
1502
- englishName: string; // "Shubha" | "Ashubha"
1503
- name: string;
1504
- }
827
+ const hi = getDailyPanchang(date, loc, { timezone: 330, language: 'hi' })!;
828
+ hi.angas.tithis[0].name; // "कृष्ण चतुर्दशी"
829
+ hi.angas.vara.englishName; // "Tuesday" — englishName always English
1505
830
 
1506
- interface TarabalaInfo {
1507
- taraIndex: number; // 0..8 in 9-tara cycle from janma nakshatra
1508
- englishName: string; // Janma | Sampat | Vipat | Kshema | Pratyari
1509
- // | Sadhaka | Vadha | Mitra | Ati-Mitra
1510
- name: string;
1511
- quality: 'auspicious' | 'inauspicious';
1512
- }
831
+ // All options: timezone (number | IANA string), ayanamsa (5), language (en|hi),
832
+ // masaSystem (purnimanta|amanta), region, computeEndTimes, sections,
833
+ // janmaRashi, janmaNakshatra.
1513
834
  ```
1514
835
 
1515
- </details>
1516
-
1517
- <details>
1518
- <summary><strong>Full export list</strong></summary>
836
+ Machine-readable keys never change with language: match on `festival.key`,
837
+ `bhadra.location`, `eclipse.kind`, `factors[].code` — render `name` /
838
+ `locationName` / `description` / `reasons`.
1519
839
 
1520
- ```typescript
1521
- // Primary entry points
1522
- getDailyPanchang, getInstantPanchang
1523
-
1524
- // Astronomy
1525
- getSunrise, getSunset, getMoonrise, getMoonset
1526
- getSiderealSunLongitude, getSiderealMoonLongitude, getAyanamsa
1527
-
1528
- // Inauspicious / Muhurta
1529
- computeRahuKalam, computeGulikaKalam, computeYamaganda
1530
- computeVarjyam, computeGandaMula, computeAnandadiYoga
1531
- computePanchakaRahita, computeDoGhati, computeGowriPanchangam
1532
- computePanchaka, classifyPanchaka, isPanchakaDosha, findPanchakaOnset
1533
- computeAbhijitMuhurta, computeBrahmaMuhurta, computeVijayaMuhurta
1534
- computeGodhuliMuhurta, computeNishitaMuhurta, computeAmritKala
1535
- computeMadhyahna, computePratahSandhya, computeSayahnaSandhya
1536
-
1537
- // Eclipses
1538
- getUpcomingSolarEclipse, getUpcomingLunarEclipse, getEclipseDuringDay
1539
- isEclipseVisibleAnyPhase
1540
-
1541
- // Moon phases (new / quarters / full as precise instants)
1542
- computeMoonPhasesInRange
1543
-
1544
- // Jyotish — planets, dashas, transits
1545
- computePlanetaryPositions, GRAHA_ABBR
1546
- computeVimshottariDasha, computeVimshottariDashaFromBirth, computeVimshottariPratyantar
1547
- computeAshtottariDasha, computeYoginiDasha, computeCharaDasha, computeNarayanDasha
1548
- computeChandraBalam, computeTarabala, computeSadeSati
1549
-
1550
- // Jyotish — chart
1551
- computeLagna, computeBhava, computeRashiChart, computeNavamsa, computeDivisionalChart
1552
- computeHoraLagna, computeGhatiLagna, computeBhavaLagna, computeSripatiLagna
1553
- computeDignity
1554
-
1555
- // Jyotish — strength, yogas, sensitive
1556
- computeAspects, computeShadbala, computeBhavaBala, computeAshtakavarga
1557
- computeYogas, computeJaiminiKarakas
1558
- computeVarshaphala, computeTithiPravesha, computeArudhas, computeUpagrahas, computeArgala
1559
-
1560
- // Jyotish — compatibility, doshas
1561
- computeAshtakoot, computePathuPorutham
1562
- computeMangalDosha, computeMangalCompatibility, computeKaalSarp, computePitruDosha
1563
-
1564
- // KP / Prashna
1565
- computeKpSubLord, computeKpCuspalSubLords, computeKpSignificators
1566
- computePrashnaChart
1567
-
1568
- // Muhurta engine
1569
- scoreMuhurta, computeAuspiciousDatesInRange, STOCK_MUHURTA_RULES
1570
- computeVaraTithiYogas
1571
- vivahRule, grihaPraveshRule, namakaranaRule, vidyarambhRule, vahanKharidiRule
1572
- annaprashanRule, mundanRule, upanayanamRule, karnavedhaRule
1573
- aksharabhyasamRule, seemanthamRule, shopOpeningRule, travelStartRule
1574
-
1575
- // Calendar conversion + yearly listings
1576
- convertGregorianToHindu, convertHinduToGregorian
1577
- getKaliYugaYear, getHinduNewYear, computeSamvat
1578
- computeEkadashiDatesForYear, computeSankrantisForYear, computeFestivalsInRange
1579
- getUpcomingEclipses, computeEclipsesInRange
1580
-
1581
- // Static data tables — build one, cache the JSON, then read it back through
1582
- // the engine-free panchang-ts/festivals · /eclipses · /moon-phases entries.
1583
- // No table ships with the package.
1584
- buildFestivalsTable, buildEclipsesTable, buildMoonPhasesTable
1585
-
1586
- // Errors
1587
- PanchangError
1588
- ```
840
+ ## Types & Exports
1589
841
 
1590
- </details>
842
+ 📖 [Types & Exports →](https://dharmagya.app/docs/panchang-ts/types) — the key
843
+ interfaces (`TithiInfo`, `FestivalInfo`, `EclipseInfo`, `GrahaPosition`, …) and
844
+ the complete export list of the main entry and the four engine-free subpaths
845
+ (`panchang-ts/festivals`, `/eclipses`, `/moon-phases`, `/muhurta`).
1591
846
 
1592
847
  ---
1593
848
 
@@ -1602,10 +857,7 @@ Two-pass rendering pattern for smooth UI:
1602
857
  import { getDailyPanchang } from 'panchang-ts';
1603
858
  import { InteractionManager } from 'react-native';
1604
859
 
1605
- // Pass 1 — cheapest useful result: elements, slots, muhurtas
1606
- // (~0.25 ms Node on a new date, ~0.14 ms on one already seen).
1607
- // `sections` is the lever; `computeEndTimes: false` only helps once it is
1608
- // narrowed, and slightly hurts on a full-section call.
860
+ // Pass 1 — cheapest useful result: elements, slots, muhurtas (~0.25 ms).
1609
861
  const fast = getDailyPanchang(date, location, {
1610
862
  timezone: 330,
1611
863
  sections: [],
@@ -1613,7 +865,7 @@ const fast = getDailyPanchang(date, location, {
1613
865
  });
1614
866
  setState(fast);
1615
867
 
1616
- // Pass 2 — background, everything (~0.41 ms Node)
868
+ // Pass 2 — background, everything (~0.41 ms).
1617
869
  InteractionManager.runAfterInteractions(() => {
1618
870
  setState(getDailyPanchang(date, location, { timezone: 330 }));
1619
871
  });
@@ -1623,7 +875,9 @@ InteractionManager.runAfterInteractions(() => {
1623
875
 
1624
876
  ## Accuracy
1625
877
 
1626
- 8,368 tests across 121 files, including fixtures cross-verified against reference
878
+ 📖 [Full accuracy notes →](https://dharmagya.app/docs/panchang-ts/accuracy)
879
+
880
+ 8,737 tests across 136 files, including fixtures cross-verified against reference
1627
881
  panchang calculations spanning 2025–2026 across 10 Indian cities plus New York,
1628
882
  London, Sydney, Dubai, Singapore (diaspora fixtures cover DST on
1629
883
  `America/New_York`).
@@ -1633,213 +887,70 @@ London, Sydney, Dubai, Singapore (diaspora fixtures cover DST on
1633
887
  | Sunrise / Sunset | ≤29 s observed vs reference minute-midpoint (±45 s tolerance) |
1634
888
  | Moonrise / Moonset | Meeus apparent-upper-limb (refraction + parallax); ~3–5 min vs simpler-horizon authorities is expected |
1635
889
  | Tithi / Nakshatra / Yoga / Karana names | Exact match vs reference |
1636
- | Tithi / Nakshatra / Yoga / Karana end-times | ≤60 s vs Drik across all 20 audited comparisons (tithi 46 s, karana 51 s, nakshatra 24 s, yoga 60 s) |
890
+ | Tithi / Nakshatra / Yoga / Karana end-times | ≤60 s vs Drik across all 20 audited comparisons |
1637
891
  | Ayanamsa (Lahiri) | Reproduces DrikPanchang's published value to ~0.01″ across 1950–2050 |
1638
892
  | Planetary positions (Sun–Saturn) | ±0.02° sidereal |
1639
- | Planetary positions (Rahu/Ketu, mean node) | ≤0.5° typical; ±2° tolerance |
1640
- | Planetary positions (Rahu/Ketu, true node) | ≤0.6° typical (Meeus periodic correction) |
893
+ | Planetary positions (Rahu/Ketu) | ≤0.5° mean node, ≤0.6° true node (typical) |
1641
894
  | Lagna sidereal longitude | Cross-checked against Jagannath Hora reference charts |
1642
895
  | D1 / D9 house placement | Exact match vs reference for 9-graha placement |
1643
896
  | Ashtakoot total | ±1 point per pair across 30+ matched pairs |
897
+ | Varjyam windows | count + position vs DrikPanchang over a 61-day / two-nakshatra-cycle sweep, ≤2 min (62/62 windows) |
1644
898
  | Sade Sati arc start/end | ±1–2 days vs authoritative ephemerides |
1645
899
 
1646
- **End-time drift.** Drik publishes end times to the minute, so each comparison
1647
- above carries ±30 s of quantization that, not the search, dominates what is
1648
- left. Two independent checks bound the library's own contribution: the reported
1649
- value matches an exact bisection of the same index function to ≤24 ms, and Sun
1650
- and Moon agree with Drik's sidereal positions to well under an arcsecond
1651
- (`tests/validation/element-endtime-audit.test.ts` carries the working).
1652
-
1653
- **Ayanamsa.** Only Lahiri is verified against an external reference — Drik
1654
- publishes no value for the other four. Raman, KP, True Chitrapaksha and
1655
- Thirukanitham are held at their historical offsets from Lahiri, so correcting
1656
- Lahiri carried them along rather than silently changing how each relates to it.
1657
-
1658
- **Detection notes.** **Aadal / Vidaal** follow the classical Moon-from-Sun
1659
- nakshatra-distance rule (AstroShastra, HoraSarvam, Ernst Wilhelm), NOT the
1660
- Tamil-Vakya weekday rule used by some online panchangs. **Varjyam** emits the
1661
- sunrise-anchored nakshatra's window only. **Do Ghati Muhurta** does not rotate
1662
- by weekday — the same 30-name deity-keyed sequence applies every day.
1663
-
1664
- ### Festival Detection — Documented Tradeoff
1665
-
1666
- The library uses **tithi-at-sunrise** to resolve a festival to a calendar day.
1667
- Some authorities use other classical rules for certain festivals; where those
1668
- rules pick a different day, output can drift ±1 day:
1669
-
1670
- | Alternative rule | Affects |
1671
- |---|---|
1672
- | Tithi-at-midnight | Krishna Janmashtami, Maha Shivaratri, Diwali / Lakshmi Puja |
1673
- | Madhyahna-vyapini | Ganesh Chaturthi (edge years), Akshaya Tritiya 2026 |
1674
- | Kshaya-tithi handling | Ugadi 2026-03-19 (Pratipada is Kshaya) |
900
+ **Festival dating** uses tithi-at-sunrise; a few festivals have authorities on
901
+ other rules (tithi-at-midnight for Janmashtami / Shivaratri / Diwali,
902
+ madhyahna-vyapini for Ganesh Chaturthi edge years) where output can drift
903
+ ±1 day the exact list is
904
+ [documented](https://dharmagya.app/docs/panchang-ts/accuracy#festival-tradeoff).
1675
905
 
1676
- Everything else Holi, Ugadi (non-Kshaya years), Rama Navami, Raksha Bandhan,
1677
- Ganesh Chaturthi (normal years), Navaratri, Dussehra, Karva Chauth, Hanuman
1678
- Jayanti matches the canonical date across 2025 and 2026 fixtures.
906
+ **Detection conventions:** Aadal / Vidaal follow the classical Moon-from-Sun
907
+ nakshatra-distance rule, not the Tamil-Vakya weekday rule. Varjyam lists every
908
+ window whose start falls in the Hindu day (Drik's attribution; Mula carries two
909
+ tyajya spells). Do Ghati does not rotate by weekday.
1679
910
 
1680
911
  ---
1681
912
 
1682
913
  ## Performance
1683
914
 
1684
- Measured at Pune on an Apple M-series laptop under Node 24, median of 11
1685
- processes per configuration. Treat them as relative guidance, not a spec — they
1686
- move with hardware, latitude and date.
915
+ 📖 [Full performance notes →](https://dharmagya.app/docs/panchang-ts/performance)
1687
916
 
1688
- Two columns, because they differ and both are real. **Distinct days** is the
1689
- calendar-scan cost: every call misses the solar rise/set cache. **Same day
1690
- repeated** is what a UI that re-renders one date sees, and what `npm run bench`
1691
- reports. The last column is the **published 4.3.1 package**, installed from npm
1692
- and benchmarked beside this one.
917
+ Measured at Pune, Apple M-series, Node median of 11 processes. **Distinct
918
+ days** is the calendar-scan cost; **same day repeated** is what a UI
919
+ re-rendering one date sees. Last column is published 4.3.1, benchmarked beside
920
+ this release.
1693
921
 
1694
922
  | `getDailyPanchang` call | Distinct days | Same day repeated | 4.3.1 (distinct) |
1695
923
  |---|---|---|---|
1696
924
  | Default (all sections + end-times) | **~0.41 ms** | **~0.17 ms** | ~6.06 ms |
1697
- | `computeEndTimes: false` | ~0.39 ms | ~0.15 ms | ~5.63 ms |
1698
- | Without `'festivals'` | ~0.39 ms | — | n/a |
1699
- | `sections: ['festivals', 'eclipse']` | ~0.40 ms | — | n/a |
1700
- | `sections: []` | ~0.27 ms | — | n/a |
1701
925
  | `sections: []` + `computeEndTimes: false` | ~0.25 ms | ~0.14 ms | n/a |
1702
926
  | `getInstantPanchang` | ~0.21 ms | ~0.10 ms | ~0.43 ms |
1703
927
 
1704
- `sections` did not exist before v5, so the rows using it have no 4.3.1
1705
- counterpart — passing it to 4.3.1 is silently ignored and you get a full run.
1706
-
1707
- **A default day is ~15× cheaper than in 4.3.1**, and a repeated day ~37×. Most
1708
- of that is not tuning: 4.3.1 ran `astronomy-engine`'s full lunar theory inside
1709
- the eclipse search on every day containing a syzygy, and had no cache that
1710
- survived a call.
1711
-
1712
- One surface moved the other way, and it is deliberate: the **raw longitude
1713
- getters** (`getSiderealSunLongitude`, `getSiderealMoonLongitude`) cost about
1714
- twice what they did in 4.3.1 per call — ~14 µs against ~7 µs for the Moon,
1715
- ~2.8 µs against ~1.4 µs for the Sun — because the own-ephemeris series keep
1716
- roughly three times `astronomy-engine`'s accuracy against JPL DE441, and the
1717
- evaluation is sine-bound, so more terms cost proportionally more. Every
1718
- documented workflow (`getDailyPanchang`, `getInstantPanchang`, the year/range
1719
- APIs, the static tables) amortizes those reads through caches and is faster
1720
- than 4.3.1 by the factors above; the per-call price is only visible to code
1721
- calling the raw getters in a tight loop over distinct instants. For scanning
1722
- workloads, prefer the range APIs or `getDailyPanchang` — they read through
1723
- interpolated longitude blocks precisely so that this cost is paid once per day
1724
- rather than once per read.
1725
-
1726
- The same trade surfaces once more in the birth-chart *primitives*:
1727
- `computeRashiChart` and `computeNavamsa` measure ~0.31 ms against ~0.09 ms on
1728
- published 4.3.1 — a chart is fifteen full-accuracy planet evaluations (three
1729
- per planet, for the retrograde probes) and nothing amortizes them. The deeper
1730
- chart stack inverts it again: `computeShadbala` and `computeBhavaBala` come
1731
- out ~2× *faster* than 4.3.1, because v5 computes the positions once and reuses
1732
- them. At a third of a millisecond per chart this is irrelevant interactively;
1733
- it is visible only to code building thousands of charts in a batch.
1734
-
1735
- Cost is dominated by ephemeris evaluations, so the lever that matters is the one
1736
- that avoids them:
1737
-
1738
- - **`sections`** — skip the optional ephemeris-backed blocks you don't need.
1739
- Dropping `'festivals'` takes a default call from ~0.41 ms to ~0.39 ms cold,
1740
- and dropping everything takes it to ~0.27 ms. See
1741
- [Narrowing the work](#narrowing-the-work).
1742
- - **`computeEndTimes: false`** — a small win, never a large one. It skips the
1743
- transition searches, but those read through the same interpolated longitude
1744
- blocks the rest of the call has already built, so what it saves is arithmetic
1745
- rather than ephemeris: ~5% cold, ~10% warm. Use it to drop `endTime` fields
1746
- you don't want, not to go faster.
1747
-
1748
- *Changed in v5.* Both entry points now always interpolate, so output depends
1749
- on neither `sections` nor `computeEndTimes` — see `INTERPOLATE_ALWAYS` in
1750
- `src/core/panchang.ts`. Earlier development builds chose the longitude cache's
1751
- mode from `computeEndTimes`, which made asking for *less* output cost *more*
1752
- on a full call; that is gone.
1753
-
1754
- Repeated calls for the same location-day are cheaper because solar rise/set
1755
- events are cached process-wide, keyed on `(direction, lat, lon, elevation, UTC
1756
- day)` and bounded at 20,000 entries. The cache makes sunrise single-valued as
1757
- well as fast — see [Upgrading from 4.x](#sunrise-is-single-valued-per-location-day).
1758
- It does not make a *single* cold rise/set call cheaper — against 4.3.1
1759
- `getSunrise` is +1.8%, `getSunset` +8.8%, `getMoonrise` +5.5% and `getMoonset`
1760
- +6.6%, i.e. unchanged to slightly worse — what it removes is the second and
1761
- every later call for the same day.
1762
-
1763
- Range helpers apply the same narrowing internally:
1764
- `computeEkadashiDatesForYear` reads only the tithi at sunrise and so runs with
1765
- every optional section off (**~18 ms** for a full year, against ~2,360 ms in
1766
- 4.3.1); `computeFestivalsInRange` keeps only `'festivals'` and `'eclipse'`
1767
- (**~131 ms/year**, against ~2,180); `computeSankrantisForYear` needs only the
1768
- Sun, so it scans one solar longitude per day and bisects the 12 transits rather
1769
- than building a panchang each day (**~3.3 ms/year**, against ~154).
1770
-
1771
- Birth-chart helpers are independent — calling them does not add work to
1772
- `getDailyPanchang`. Within them, `computeShadbala` and `computeBhavaBala` build
1773
- the natal positions once and derive all seven charts from them (~0.33 ms each,
1774
- against ~0.72 in 4.3.1).
1775
-
1776
- **Charts are the one place v5 is slower.** `computeRashiChart` and
1777
- `computeNavamsa` cost **~0.32 ms** against ~0.10 in 4.3.1 — 3.3×, entirely the
1778
- planetary ephemeris, and the deliberate price of an order-of-magnitude accuracy
1779
- gain against JPL DE441 (Mercury 6.50″ → 0.30″, Venus 19.59″ → 0.86″). If you
1780
- build many charts and do not need that precision, 4.x was cheaper; nothing else
1781
- in the library regressed.
1782
-
1783
- ### Narrowing the work
1784
-
1785
- `PanchangSection` lists the four optional blocks. Everything else a daily
1786
- panchang returns — the five elements, slot systems, muhurtas, inauspicious
1787
- periods, masa / samvat / rashi — is arithmetic over the sunrise / sunset /
1788
- next-sunrise triplet and is always computed, because skipping it would save
1789
- nothing.
1790
-
1791
- | Section | Covers | Fields when omitted |
1792
- |---|---|---|
1793
- | `'festivals'` | Festival detection — needs the prior day's sunrise/sunset, the next day's transit, per-kala tithi anchors, and the prior day's Chandra Masa | `festivals: []` — but an eclipse entry is still prepended when `'eclipse'` is on |
1794
- | `'eclipse'` | Eclipse overlapping the Hindu day | `eclipse: null` |
1795
- | `'moonTimes'` | `moon.rise` / `moon.set` | `null` |
1796
- | `'lunarWindows'` | Bhadra, Varjyam, Panchaka-Rahita — each binary-searches lunar longitude across the day | `null` / `[]` |
1797
-
1798
- ```typescript
1799
- // Everything (default).
1800
- getDailyPanchang(date, loc, { timezone: 330 });
1801
-
1802
- // Festivals only — no moon times, no Bhadra/Varjyam windows.
1803
- getDailyPanchang(date, loc, {
1804
- timezone: 330,
1805
- sections: ['festivals', 'eclipse'],
1806
- });
1807
-
1808
- // Cheapest useful call: elements, slots, muhurtas and inauspicious periods
1809
- // only. Those are arithmetic on the sunrise triplet and are always computed.
1810
- getDailyPanchang(date, loc, {
1811
- timezone: 330,
1812
- sections: [],
1813
- computeEndTimes: false,
1814
- });
1815
- ```
928
+ **A default day is ~15× cheaper than 4.3.1**, a repeated day ~37×. The levers:
1816
929
 
1817
- Omitting a section leaves its fields at their documented empty value (`null`
1818
- or `[]`) never a half-filled one.
930
+ - **`sections`** skip the optional ephemeris-backed blocks (`'festivals'`,
931
+ `'eclipse'`, `'moonTimes'`, `'lunarWindows'`). Narrowing is exactly
932
+ output-neutral: every field a narrowed call computes is identical to the full
933
+ call's; omitted sections sit at their documented `null` / `[]`.
934
+ - **`computeEndTimes: false`** — drops the `endTime` transition searches;
935
+ ~5–10% — use it to drop fields you don't want, not to go faster.
1819
936
 
1820
- Narrowing is **exactly output-neutral**: every field a narrowed call does
1821
- compute is identical, to the millisecond, to what the full call would have
1822
- returned. `sections` only decides what is skipped, never what a computed value
1823
- is. (This is guaranteed by `LongitudeCache` memoizing on the exact instant. It
1824
- was not true while that memo binned longitudes into 60-second buckets, when
1825
- narrowing could shift transition times by up to 63 s.)
937
+ Range helpers narrow internally: `computeEkadashiDatesForYear` **~18 ms/year**
938
+ (4.3.1: ~2,360), `computeFestivalsInRange` **~131 ms/year** (~2,180),
939
+ `computeSankrantisForYear` **~3.3 ms/year** (~154). Solar rise/set events are
940
+ cached process-wide (bounded, 20k entries), which also makes them
941
+ single-valued.
1826
942
 
1827
- ### A note on Hermes / React Native
1828
-
1829
- Earlier versions of this table also quoted Hermes figures. Those were budget
1830
- targets from the project plan, never measurements: `npm run test:hermes` runs
1831
- `hermes-parser` over the built bundle to prove the syntax is Hermes-compatible,
1832
- which is a *parse* check and does not execute anything. Hermes numbers will be
1833
- published here once they are actually measured on device.
1834
-
1835
- What does carry over is the shape of the cost: it is dominated by ephemeris
1836
- math, so the `sections` and `computeEndTimes` levers above have the same
1837
- proportional effect on any runtime.
943
+ One deliberate regression: raw chart primitives (`computeRashiChart`,
944
+ `computeNavamsa`) cost ~0.32 ms vs ~0.10 in 4.3.1 — the price of an
945
+ order-of-magnitude accuracy gain against JPL DE441. `computeShadbala` /
946
+ `computeBhavaBala` went the other way, ~2× faster.
1838
947
 
1839
948
  ---
1840
949
 
1841
950
  ## Error Handling
1842
951
 
952
+ 📖 [Errors & Compatibility →](https://dharmagya.app/docs/panchang-ts/errors)
953
+
1843
954
  ```typescript
1844
955
  import { PanchangError } from 'panchang-ts';
1845
956
 
@@ -1854,7 +965,7 @@ try {
1854
965
  ```
1855
966
 
1856
967
  Error codes: `INVALID_DATE`, `INVALID_LATITUDE`, `INVALID_LONGITUDE`,
1857
- `INVALID_ELEVATION`, `INVALID_TIMEZONE`, `INVALID_AYANAMSA`,
968
+ `INVALID_ELEVATION`, `INVALID_TIMEZONE`, `INVALID_AYANAMSA`, `INVALID_INPUT`,
1858
969
  `TIMEZONE_RESOLUTION_FAILED`, `NO_SUNRISE`, `NO_SUNSET`, `SEARCH_DIVERGED`,
1859
970
  `CIRCUMPOLAR` (Placidus-KP houses above ±66.5°).
1860
971
 
@@ -1871,18 +982,25 @@ for the Moon).
1871
982
 
1872
983
  | Environment | Support |
1873
984
  |---|---|
1874
- | Node.js 18+ | Supported |
985
+ | Node.js 22 (per the package `engines` field) | Supported |
1875
986
  | React Native (Hermes) | Supported (pass `timezone` as number) |
1876
987
  | Expo (managed + bare) | Supported |
1877
988
  | Browser (modern, ESM) | Supported |
1878
989
  | Browser (legacy / IE) | Not supported |
1879
990
 
991
+ The build targets ES2020 (ESM + CJS, full `.d.ts`), has **zero runtime
992
+ dependencies**, and is `sideEffects: false`.
993
+
1880
994
  ---
1881
995
 
1882
996
  ## Acknowledgements
1883
997
 
1884
- [astronomy-engine](https://github.com/cosinekitty/astronomy) by Don Cross — the
1885
- sole runtime dependency. MIT licensed.
998
+ As of v5 the package has **no runtime dependencies** — the ephemeris, ΔT model
999
+ and event searches are the library's own. Two projects still deserve credit:
1000
+ [astronomy-engine](https://github.com/cosinekitty/astronomy) by Don Cross (MIT),
1001
+ the runtime engine through 4.x and now the dev-time baseline the own ephemeris
1002
+ is tested against, and the algorithms of Jean Meeus's *Astronomical Algorithms*,
1003
+ which underpin the rise/set and node models.
1886
1004
 
1887
1005
  ## License
1888
1006