panchanga 0.1.1

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/dist/rules.js ADDED
@@ -0,0 +1,1058 @@
1
+ /**
2
+ * src/rules.ts — per-festival rule DATA.
3
+ *
4
+ * This module encodes the HSNA v1 festival rule set as `FestivalRule[]`
5
+ * instances, following the grammar declared in `src/types.ts` and sourced
6
+ * verbatim from the verified spec:
7
+ * `docs/superpowers/plans/2026-06-23-festivals-decisions-and-spec.md`
8
+ * §4 "Verified v1 festival spec" (24 core festivals)
9
+ * §4b "Extended set" (Ekadashi / Sankashti Chaturthi / Chhath Puja)
10
+ *
11
+ * CALENDRICAL ONLY — no HSNA editorial copy (deity/significance/links live
12
+ * in web/ later). `meta.note` is used only to flag mapping imperfections.
13
+ *
14
+ * AUTHORITIES:
15
+ * • Sampradāya: Smārta (Drik Panchang default)
16
+ * • Month system: Pūrṇimānta (primary)
17
+ * • Month-name spellings match LUNAR_MONTH_NAMES in elements.ts exactly.
18
+ */
19
+ // ═══════════════════════════════════════════════════════════════════════════
20
+ // §4 — CORE RULES (24 festivals, calendar order)
21
+ // ═══════════════════════════════════════════════════════════════════════════
22
+ /**
23
+ * The 24 individually named festivals from §4 of the spec. Ordered by 2026
24
+ * date (calendar year order, Jan–Dec).
25
+ *
26
+ * Month-name spellings must match the strings in `LUNAR_MONTH_NAMES`
27
+ * (elements.ts) exactly, since the evaluator normalizes and compares them:
28
+ * Chaitra · Vaishakha · Jyeshtha · Ashadha · Shravana · Bhadrapada ·
29
+ * Ashwina · Kartika · Margashirsha · Pausha · Magha · Phalguna
30
+ */
31
+ export const CORE_RULES = [
32
+ // ─────────────────────────────────────────────────────────────────────────
33
+ // 1. Makar Sankranti — solar-ingress, rashi 9 (Makara = Capricorn, 270°)
34
+ // Spec: puṇya-kāla = 40 ghaṭis from ingress moment.
35
+ // Note: rashi 9 is Makara (0=Mesha, 9=Makara) per elements.ts solarIngress.
36
+ // ─────────────────────────────────────────────────────────────────────────
37
+ {
38
+ id: "makar-sankranti",
39
+ displayName: "Makar Sankranti",
40
+ month: { purnimanta: "Pausha" },
41
+ category: "solar",
42
+ observance: {
43
+ kind: "solar-ingress",
44
+ rashi: 9,
45
+ punyaKala: "after-moment-to-sunset",
46
+ },
47
+ },
48
+ // ─────────────────────────────────────────────────────────────────────────
49
+ // 2. Vasant Panchami — Magha Shukla 5, pūrvāhna (forenoon)
50
+ // Spec: Panchami pervading forenoon (sunrise→midday).
51
+ // ─────────────────────────────────────────────────────────────────────────
52
+ {
53
+ id: "vasant-panchami",
54
+ displayName: "Vasant Panchami",
55
+ month: { purnimanta: "Magha" },
56
+ category: "lunar-tithi",
57
+ observance: {
58
+ kind: "tithi-pervades",
59
+ paksha: "shukla",
60
+ tithi: 5,
61
+ window: "purvahna",
62
+ precedence: "max-window-fraction",
63
+ },
64
+ },
65
+ // ─────────────────────────────────────────────────────────────────────────
66
+ // 3. Maha Shivratri — Phalguna Krishna 14, niśīta (midnight window)
67
+ // Spec: Chaturdashi pervading niśīta.
68
+ // ─────────────────────────────────────────────────────────────────────────
69
+ {
70
+ id: "maha-shivratri",
71
+ displayName: "Maha Shivratri",
72
+ month: { purnimanta: "Phalguna" },
73
+ category: "lunar-tithi",
74
+ observance: {
75
+ kind: "tithi-pervades",
76
+ paksha: "krishna",
77
+ tithi: 14,
78
+ window: "nishita",
79
+ precedence: "max-window-fraction",
80
+ },
81
+ },
82
+ // ─────────────────────────────────────────────────────────────────────────
83
+ // 4. Holika Dahan — Phalguna Shukla Pūrṇimā, pradoṣa, Bhadra-free
84
+ // Spec: Purnima in pradoṣa AND Bhadra-free (vishti karana exclusion).
85
+ // ─────────────────────────────────────────────────────────────────────────
86
+ {
87
+ id: "holika-dahan",
88
+ displayName: "Holika Dahan",
89
+ month: { purnimanta: "Phalguna" },
90
+ category: "lunar-tithi",
91
+ observance: {
92
+ kind: "tithi-pervades",
93
+ paksha: "shukla",
94
+ tithi: "purnima",
95
+ window: "pradosha",
96
+ precedence: "max-window-fraction",
97
+ avoidKarana: "vishti",
98
+ },
99
+ },
100
+ // ─────────────────────────────────────────────────────────────────────────
101
+ // 5. Holi (Dhulandi) — derived +1 day from Holika Dahan
102
+ // Spec: Chaitra Krishna 1 at sunrise (derived from Holika +1).
103
+ // ─────────────────────────────────────────────────────────────────────────
104
+ {
105
+ id: "holi",
106
+ displayName: "Holi (Dhulandi)",
107
+ month: { purnimanta: "Chaitra" },
108
+ category: "derived",
109
+ observance: {
110
+ kind: "derived",
111
+ from: "holika-dahan",
112
+ offsetDays: 1,
113
+ },
114
+ },
115
+ // ─────────────────────────────────────────────────────────────────────────
116
+ // 6. Rama Navami — Chaitra Shukla 9, madhyāhna
117
+ // Spec: Navami pervading madhyāhna (Rama's birth).
118
+ // ─────────────────────────────────────────────────────────────────────────
119
+ {
120
+ id: "rama-navami",
121
+ displayName: "Rama Navami",
122
+ month: { purnimanta: "Chaitra" },
123
+ category: "lunar-tithi",
124
+ observance: {
125
+ kind: "tithi-pervades",
126
+ paksha: "shukla",
127
+ tithi: 9,
128
+ window: "madhyahna",
129
+ precedence: "max-window-fraction",
130
+ },
131
+ },
132
+ // ─────────────────────────────────────────────────────────────────────────
133
+ // 7. Hanuman Jayanti — Chaitra Shukla Pūrṇimā, sunrise
134
+ // Spec: Purnima at sunrise (purnima-vyāpti).
135
+ // PRECEDENCE: udaya. A sunrise-anchored vyāpti festival is observed on the
136
+ // day whose tithi is PRESENT AT SUNRISE (udaya-tithi), not the day with the
137
+ // larger window-fraction. In 2026 Pūrṇimā spans both Apr 1 and Apr 2
138
+ // sunrises with a *larger* fraction on Apr 1, yet Drik picks Apr 2 because
139
+ // Pūrṇimā prevails at Apr 2 sunrise — exactly the udaya rule.
140
+ // ─────────────────────────────────────────────────────────────────────────
141
+ {
142
+ id: "hanuman-jayanti",
143
+ displayName: "Hanuman Jayanti",
144
+ month: { purnimanta: "Chaitra" },
145
+ category: "lunar-tithi",
146
+ observance: {
147
+ kind: "tithi-pervades",
148
+ paksha: "shukla",
149
+ tithi: "purnima",
150
+ window: "sunrise",
151
+ precedence: "udaya",
152
+ },
153
+ },
154
+ // ─────────────────────────────────────────────────────────────────────────
155
+ // 8. Mesha Sankranti / Baisakhi — solar-ingress rashi 0 (Mesha = Aries)
156
+ // Spec: optional; sidereal Sun → Mesha (0°); solar new year.
157
+ // Marked extended:true per task brief ("optional").
158
+ // ─────────────────────────────────────────────────────────────────────────
159
+ {
160
+ id: "mesha-sankranti",
161
+ displayName: "Mesha Sankranti / Baisakhi",
162
+ month: { purnimanta: "Chaitra" },
163
+ category: "solar",
164
+ extended: true,
165
+ observance: {
166
+ kind: "solar-ingress",
167
+ rashi: 0,
168
+ punyaKala: "after-moment-to-sunset",
169
+ },
170
+ },
171
+ // ─────────────────────────────────────────────────────────────────────────
172
+ // 9. Akshaya Tritiya — Vaishakha Shukla 3, pūrvāhna
173
+ // Spec: Tritiya pervading forenoon; Rohini+Wed auspicious but not required.
174
+ // ─────────────────────────────────────────────────────────────────────────
175
+ {
176
+ id: "akshaya-tritiya",
177
+ displayName: "Akshaya Tritiya",
178
+ month: { purnimanta: "Vaishakha" },
179
+ category: "lunar-tithi",
180
+ observance: {
181
+ kind: "tithi-pervades",
182
+ paksha: "shukla",
183
+ tithi: 3,
184
+ window: "purvahna",
185
+ precedence: "max-window-fraction",
186
+ },
187
+ },
188
+ // ─────────────────────────────────────────────────────────────────────────
189
+ // 10. Guru Purnima — Ashadha Shukla Pūrṇimā, sunrise
190
+ // Spec: Purnima-vyāpti.
191
+ // ─────────────────────────────────────────────────────────────────────────
192
+ {
193
+ id: "guru-purnima",
194
+ displayName: "Guru Purnima",
195
+ month: { purnimanta: "Ashadha" },
196
+ category: "lunar-tithi",
197
+ observance: {
198
+ kind: "tithi-pervades",
199
+ paksha: "shukla",
200
+ tithi: "purnima",
201
+ window: "sunrise",
202
+ precedence: "max-window-fraction",
203
+ },
204
+ },
205
+ // ─────────────────────────────────────────────────────────────────────────
206
+ // 11. Raksha Bandhan — Shravana Shukla Pūrṇimā, aparāhna, Bhadra-free
207
+ // Spec: tie thread in aparāhna, Bhadra-free.
208
+ // ─────────────────────────────────────────────────────────────────────────
209
+ {
210
+ id: "raksha-bandhan",
211
+ displayName: "Raksha Bandhan",
212
+ month: { purnimanta: "Shravana" },
213
+ category: "lunar-tithi",
214
+ observance: {
215
+ kind: "tithi-pervades",
216
+ paksha: "shukla",
217
+ tithi: "purnima",
218
+ window: "aparahna",
219
+ precedence: "max-window-fraction",
220
+ avoidKarana: "vishti",
221
+ },
222
+ },
223
+ // ─────────────────────────────────────────────────────────────────────────
224
+ // 12. Krishna Janmashtami — Bhadrapada Krishna 8, niśīta
225
+ // Spec: Smārta: Ashtami at niśīta + Rohini preference (preferred, not required);
226
+ // Saptami-viddha OK (so no required filter).
227
+ // sampradaya:"smarta" explicit per spec.
228
+ // ─────────────────────────────────────────────────────────────────────────
229
+ {
230
+ id: "krishna-janmashtami",
231
+ displayName: "Krishna Janmashtami",
232
+ month: { purnimanta: "Bhadrapada" },
233
+ category: "lunar-tithi",
234
+ sampradaya: "smarta",
235
+ observance: {
236
+ kind: "tithi-pervades",
237
+ paksha: "krishna",
238
+ tithi: 8,
239
+ window: "nishita",
240
+ precedence: "max-window-fraction",
241
+ nakshatra: { name: "Rohini", window: "nishita", mode: "preferred" },
242
+ },
243
+ },
244
+ // ─────────────────────────────────────────────────────────────────────────
245
+ // 13. Ganesh Chaturthi — Bhadrapada Shukla 4, madhyāhna
246
+ // Spec: Chaturthi pervading madhyāhna.
247
+ // ─────────────────────────────────────────────────────────────────────────
248
+ {
249
+ id: "ganesh-chaturthi",
250
+ displayName: "Ganesh Chaturthi",
251
+ month: { purnimanta: "Bhadrapada" },
252
+ category: "lunar-tithi",
253
+ observance: {
254
+ kind: "tithi-pervades",
255
+ paksha: "shukla",
256
+ tithi: 4,
257
+ window: "madhyahna",
258
+ precedence: "max-window-fraction",
259
+ },
260
+ },
261
+ // ─────────────────────────────────────────────────────────────────────────
262
+ // 14. Navratri / Ghatasthapana (Sharadiya) — Ashwina Shukla 1, pūrvāhna
263
+ // Spec: Pratipadā in first ⅓ of day.
264
+ // Note: The spec also mentions nakshatra/yoga prohibitions (Chitra, Vaidhrti)
265
+ // as flags for 2026, but this is editorial/diagnostic, not a rule constraint
266
+ // in the grammar; not encoding here (Phase 4).
267
+ // ─────────────────────────────────────────────────────────────────────────
268
+ {
269
+ id: "sharadiya-navratri",
270
+ displayName: "Navratri / Ghatasthapana",
271
+ month: { purnimanta: "Ashwina" },
272
+ category: "lunar-tithi",
273
+ observance: {
274
+ kind: "tithi-pervades",
275
+ paksha: "shukla",
276
+ tithi: 1,
277
+ window: "purvahna",
278
+ // Ghaṭasthāpana is udaya-vyāpinī: observed on the day Pratipadā is present
279
+ // at sunrise. max-window-fraction coincides with udaya at New Delhi (the
280
+ // tithi sits in the evening there) but diverges at far-western longitudes
281
+ // where Pratipadā straddles sunrise — Calgary 2026 confirmed udaya matches
282
+ // Drik (Oct 11) while max-window-fraction wrongly gave Oct 10.
283
+ precedence: "udaya",
284
+ },
285
+ },
286
+ // ─────────────────────────────────────────────────────────────────────────
287
+ // 15. Durga Ashtami — Ashwina Shukla 8, sunrise
288
+ // Spec: Ashtami at sunrise.
289
+ // ─────────────────────────────────────────────────────────────────────────
290
+ {
291
+ id: "durga-ashtami",
292
+ displayName: "Durga Ashtami",
293
+ month: { purnimanta: "Ashwina" },
294
+ category: "lunar-tithi",
295
+ observance: {
296
+ kind: "tithi-pervades",
297
+ paksha: "shukla",
298
+ tithi: 8,
299
+ window: "sunrise",
300
+ precedence: "max-window-fraction",
301
+ },
302
+ },
303
+ // ─────────────────────────────────────────────────────────────────────────
304
+ // 16. Maha Navami — Ashwina Shukla 9, sunrise
305
+ // Spec: Navami-vyāpti → PRECEDENCE: udaya (present-at-sunrise), the same
306
+ // sunrise-anchored udaya rule used by Hanuman Jayanti. NOTE: Durga Ashtami
307
+ // (the adjacent sunrise festival) intentionally stays on
308
+ // max-window-fraction — it resolves correctly that way for 2026; do NOT
309
+ // switch it to udaya.
310
+ // KNOWN EXPECTED-DIFF (2026): Drik publishes Maha Navami on Oct 19, the
311
+ // SAME civil day as Maha Ashtami, with Sandhi Pūjā 10:27–11:15 IST. In
312
+ // 2026 Navami runs Oct 19 10:52 → Oct 20 12:51 IST, so it prevails at the
313
+ // Oct 20 sunrise, NOT Oct 19's (Oct 19 sunrise carries Ashtami). Pure
314
+ // udaya-tithi therefore yields Oct 20. Drik's Oct 19 comes from the
315
+ // Durga-Pūjā Sandhi / Navami-conjoined-with-Ashtami convention (Navami
316
+ // observed on the Ashtami-udaya day when the Sandhi junction falls that
317
+ // morning) — a Durga-Pūjā-specific rule NOT expressible in the generic
318
+ // tithi-pervasion grammar. Left as a documented +1 expected-diff rather
319
+ // than hardcoded or hacked. (Vijayadashami/aparāhna on Oct 20 still
320
+ // matches Drik.)
321
+ // ─────────────────────────────────────────────────────────────────────────
322
+ {
323
+ id: "maha-navami",
324
+ displayName: "Maha Navami",
325
+ month: { purnimanta: "Ashwina" },
326
+ category: "lunar-tithi",
327
+ observance: {
328
+ kind: "tithi-pervades",
329
+ paksha: "shukla",
330
+ tithi: 9,
331
+ window: "sunrise",
332
+ precedence: "udaya",
333
+ },
334
+ },
335
+ // ─────────────────────────────────────────────────────────────────────────
336
+ // 17. Vijayadashami (Dussehra) — Ashwina Shukla 10, aparāhna
337
+ // Spec: Dashami pervading aparāhna.
338
+ // ─────────────────────────────────────────────────────────────────────────
339
+ {
340
+ id: "vijayadashami",
341
+ displayName: "Vijayadashami (Dussehra)",
342
+ month: { purnimanta: "Ashwina" },
343
+ category: "lunar-tithi",
344
+ observance: {
345
+ kind: "tithi-pervades",
346
+ paksha: "shukla",
347
+ tithi: 10,
348
+ window: "aparahna",
349
+ precedence: "max-window-fraction",
350
+ },
351
+ },
352
+ // ─────────────────────────────────────────────────────────────────────────
353
+ // 18. Karva Chauth — Kartika Krishna 4, moonrise
354
+ // Spec: Chaturthi live at moonrise; fast broken at chandrodaya.
355
+ // ─────────────────────────────────────────────────────────────────────────
356
+ {
357
+ id: "karva-chauth",
358
+ displayName: "Karva Chauth",
359
+ month: { purnimanta: "Kartika" },
360
+ category: "moonrise",
361
+ observance: {
362
+ kind: "moonrise",
363
+ paksha: "krishna",
364
+ tithi: 4,
365
+ },
366
+ },
367
+ // ─────────────────────────────────────────────────────────────────────────
368
+ // 19. Dhanteras — Kartika Krishna 13, pradoṣa
369
+ // Spec: Trayodashi pervading pradoṣa.
370
+ // ─────────────────────────────────────────────────────────────────────────
371
+ {
372
+ id: "dhanteras",
373
+ displayName: "Dhanteras",
374
+ month: { purnimanta: "Kartika" },
375
+ category: "lunar-tithi",
376
+ observance: {
377
+ kind: "tithi-pervades",
378
+ paksha: "krishna",
379
+ tithi: 13,
380
+ window: "pradosha",
381
+ precedence: "max-window-fraction",
382
+ },
383
+ },
384
+ // ─────────────────────────────────────────────────────────────────────────
385
+ // 20. Naraka Chaturdashi (Choti Diwali) — Kartika Krishna 14
386
+ // Spec: Abhyaṅga-snāna at moonrise-before-sunrise with Chaturdashi current.
387
+ // IMPERFECT FIT FLAG: The grammar has no "moonrise-before-sunrise" kind.
388
+ // Mapped to `moonrise` (Kartika Krishna 14) as the closest available
389
+ // primitive. The evaluator will find the day whose moonrise falls within
390
+ // the Chaturdashi interval — which for a pre-dawn moonrise will be the
391
+ // same civil day as the Abhyanga Snana. Phase 4 should add a dedicated
392
+ // "moonrise-before-sunrise" window or a "pratahkala-moonrise" kind.
393
+ // ─────────────────────────────────────────────────────────────────────────
394
+ {
395
+ id: "naraka-chaturdashi",
396
+ displayName: "Naraka Chaturdashi (Choti Diwali)",
397
+ month: { purnimanta: "Kartika" },
398
+ category: "moonrise",
399
+ observance: {
400
+ kind: "moonrise",
401
+ paksha: "krishna",
402
+ tithi: 14,
403
+ },
404
+ meta: {
405
+ note: "IMPERFECT FIT: spec requires moonrise-before-sunrise (Abhyanga Snana). Mapped to `moonrise` kind (nearest available). Phase 4 should add a moonrise-before-sunrise primitive or dedicate a pre-dawn window.",
406
+ },
407
+ },
408
+ // ─────────────────────────────────────────────────────────────────────────
409
+ // 21. Diwali / Lakshmi Puja — Kartika Krishna Amāvāsyā, pradoṣa
410
+ // Spec: 2-day tie-break: day where Amāvāsyā pervades pradoṣa
411
+ // (+ sthira/Vṛṣabha lagna — editorial, not in grammar).
412
+ // ─────────────────────────────────────────────────────────────────────────
413
+ {
414
+ id: "diwali-lakshmi-puja",
415
+ displayName: "Diwali / Lakshmi Puja",
416
+ month: { purnimanta: "Kartika" },
417
+ category: "lunar-tithi",
418
+ observance: {
419
+ kind: "tithi-pervades",
420
+ paksha: "krishna",
421
+ tithi: "amavasya",
422
+ window: "pradosha",
423
+ precedence: "max-window-fraction",
424
+ },
425
+ },
426
+ // ─────────────────────────────────────────────────────────────────────────
427
+ // 22. Govardhan Puja / Annakut — Kartika Shukla 1, prātaḥ-kāla
428
+ // Spec: Pratipadā at forenoon.
429
+ // Note: "pratahkala" maps to the sunriseWindow function in time.ts
430
+ // (alias of sunrise kāla). This is correct per §5 and the kāla definitions.
431
+ // ─────────────────────────────────────────────────────────────────────────
432
+ {
433
+ id: "govardhan-puja",
434
+ displayName: "Govardhan Puja / Annakut",
435
+ month: { purnimanta: "Kartika" },
436
+ category: "lunar-tithi",
437
+ observance: {
438
+ kind: "tithi-pervades",
439
+ paksha: "shukla",
440
+ tithi: 1,
441
+ window: "pratahkala",
442
+ precedence: "max-window-fraction",
443
+ },
444
+ },
445
+ // ─────────────────────────────────────────────────────────────────────────
446
+ // 23. Bhai Dooj — Kartika Shukla 2, aparāhna
447
+ // Spec: Dwitiya pervading aparāhna.
448
+ // ─────────────────────────────────────────────────────────────────────────
449
+ {
450
+ id: "bhai-dooj",
451
+ displayName: "Bhai Dooj",
452
+ month: { purnimanta: "Kartika" },
453
+ category: "lunar-tithi",
454
+ observance: {
455
+ kind: "tithi-pervades",
456
+ paksha: "shukla",
457
+ tithi: 2,
458
+ window: "aparahna",
459
+ precedence: "max-window-fraction",
460
+ },
461
+ },
462
+ // ─────────────────────────────────────────────────────────────────────────
463
+ // 24. Gita Jayanti (Mokshada Ekadashi) — Margashirsha Shukla 11, sunrise
464
+ // Spec: Ekadashi-vyāpti; Gita Jayanti date coincides with the Ekadashi
465
+ // (Smārta / Vaishnava split is on the underlying vrata, not this date).
466
+ // Also generated by the ekadashi() generator below, but kept here as a
467
+ // named core festival per §4. The two rules produce the same date.
468
+ // ─────────────────────────────────────────────────────────────────────────
469
+ {
470
+ id: "gita-jayanti",
471
+ displayName: "Gita Jayanti (Mokshada Ekadashi)",
472
+ month: { purnimanta: "Margashirsha" },
473
+ category: "lunar-tithi",
474
+ observance: {
475
+ kind: "tithi-pervades",
476
+ paksha: "shukla",
477
+ tithi: 11,
478
+ window: "sunrise",
479
+ precedence: "udaya",
480
+ },
481
+ },
482
+ ];
483
+ // ═══════════════════════════════════════════════════════════════════════════
484
+ // §4b — EXTENDED SET GENERATORS
485
+ // ═══════════════════════════════════════════════════════════════════════════
486
+ /**
487
+ * The 12 regular pūrṇimānta months in calendar order (amānta names;
488
+ * pūrṇimānta derivation is handled by the evaluator from `elements.ts`).
489
+ *
490
+ * Month-name spellings match LUNAR_MONTH_NAMES in elements.ts exactly.
491
+ */
492
+ const PURNIMANTA_MONTHS = [
493
+ "Chaitra", // 0
494
+ "Vaishakha", // 1
495
+ "Jyeshtha", // 2
496
+ "Ashadha", // 3
497
+ "Shravana", // 4
498
+ "Bhadrapada", // 5
499
+ "Ashwina", // 6
500
+ "Kartika", // 7
501
+ "Margashirsha", // 8
502
+ "Pausha", // 9
503
+ "Magha", // 10
504
+ "Phalguna", // 11
505
+ ];
506
+ /**
507
+ * Canonical Ekadashi names for each (month, paksha) pair where well known.
508
+ * Indexed as `EKADASHI_NAMES[monthIndex][paksha]`.
509
+ * Source: Drik Panchang Ekadashi calendar; widely attested in tradition.
510
+ * Pairs not listed here fall back to "<Month> <Paksha> Ekadashi".
511
+ *
512
+ * Note: this list covers the 12 regular months only.
513
+ * Adhika months (Padmini = adhika Shukla Ekadashi, Parama = adhika Krishna
514
+ * Ekadashi) use their own canonical names.
515
+ */
516
+ // NOTE on krishna names: the engine labels months pūrṇimānta, so the Krishna
517
+ // pakṣa of month M falls within month M (the fortnight before M's Pūrṇimā). The
518
+ // canonical Ekadashi names follow that same pūrṇimānta attribution — e.g. Māgha
519
+ // Kṛṣṇa = Ṣaṭtilā, Phālguna Kṛṣṇa = Vijayā — which is what Drik Panchang lists.
520
+ // (An earlier version keyed the krishna column one month early, an amānta-style
521
+ // offset, which mislabelled all twelve Krishna Ekadashis.)
522
+ const EKADASHI_NAMES = {
523
+ Chaitra: { shukla: "Kamada Ekadashi", krishna: "Papamochani Ekadashi" },
524
+ Vaishakha: { shukla: "Mohini Ekadashi", krishna: "Varuthini Ekadashi" },
525
+ Jyeshtha: { shukla: "Nirjala Ekadashi", krishna: "Apara Ekadashi" },
526
+ Ashadha: { shukla: "Devshayani Ekadashi", krishna: "Yogini Ekadashi" },
527
+ Shravana: { shukla: "Putrada Ekadashi", krishna: "Kamika Ekadashi" },
528
+ Bhadrapada: { shukla: "Parsva Ekadashi", krishna: "Aja Ekadashi" },
529
+ Ashwina: { shukla: "Papankusha Ekadashi", krishna: "Indira Ekadashi" },
530
+ Kartika: { shukla: "Devutthana Ekadashi", krishna: "Rama Ekadashi" },
531
+ Margashirsha: { shukla: "Mokshada Ekadashi", krishna: "Utpanna Ekadashi" },
532
+ Pausha: { shukla: "Putrada Ekadashi", krishna: "Saphala Ekadashi" },
533
+ Magha: { shukla: "Jaya Ekadashi", krishna: "Sat-tila Ekadashi" },
534
+ Phalguna: { shukla: "Amalaki Ekadashi", krishna: "Vijaya Ekadashi" },
535
+ };
536
+ /**
537
+ * Canonical names for Ekadashis falling in an Adhika (leap) month.
538
+ * Source: tradition / Drik Panchang.
539
+ */
540
+ const ADHIKA_EKADASHI_NAMES = {
541
+ shukla: "Padmini Ekadashi",
542
+ krishna: "Parama Ekadashi",
543
+ };
544
+ /**
545
+ * Generate all Ekadashi rules for `year`.
546
+ *
547
+ * Rule (§4b, Smārta householder):
548
+ * Observe the day on which Ekadashi prevails at sunrise (udaya-tithi).
549
+ * When two candidate days arise (Dashami-vedha), take the first — Smārta
550
+ * householders accept Dashami-vedha (the Vaishnava arunodaya/Dashami-vedha
551
+ * skip is NOT used here).
552
+ *
553
+ * Count (2026): 24 Ekadashis total.
554
+ * 12 regular months × 2 pakshas = 24.
555
+ * The Adhika Jyeshtha in 2026 contributes Padmini (Shukla-11) + Parama
556
+ * (Krishna-11), which ARE included in the 24.
557
+ *
558
+ * Implementation strategy: generate rules for all 12 regular months (both
559
+ * pakshas), PLUS the Adhika Jyeshtha pair. The evaluator's nija-preference
560
+ * logic (findTithiIntervalInMonth) will resolve the regular "Jyeshtha" rules
561
+ * to the Nija lunation, while the adhika rules get their own month label
562
+ * "Adhika Jyeshtha".
563
+ *
564
+ * Precedence "udaya" → Ekadashi present at sunrise (window start) wins.
565
+ */
566
+ export function ekadashiRules(year) {
567
+ const rules = [];
568
+ // 12 regular months, both pakshas.
569
+ for (const month of PURNIMANTA_MONTHS) {
570
+ const names = EKADASHI_NAMES[month];
571
+ // Shukla Ekadashi (tithi 11 of shukla paksha).
572
+ rules.push({
573
+ id: `ekadashi-${month.toLowerCase()}-shukla`,
574
+ displayName: names?.shukla ?? `${month} Shukla Ekadashi`,
575
+ month: { purnimanta: month },
576
+ category: "lunar-tithi",
577
+ extended: true,
578
+ observance: {
579
+ kind: "tithi-pervades",
580
+ paksha: "shukla",
581
+ tithi: 11,
582
+ window: "sunrise",
583
+ precedence: "udaya",
584
+ },
585
+ });
586
+ // Krishna Ekadashi (tithi 11 of krishna paksha).
587
+ // Note: in pūrṇimānta reckoning, the Krishna paksha of e.g. Chaitra
588
+ // belongs to Chaitra month (the fortnight BEFORE Chaitra Shukla).
589
+ // The evaluator's absoluteTithi + findTithiIntervalInMonth handles this
590
+ // correctly via the pūrṇimānta label on the tithi midpoint.
591
+ rules.push({
592
+ id: `ekadashi-${month.toLowerCase()}-krishna`,
593
+ displayName: names?.krishna ?? `${month} Krishna Ekadashi`,
594
+ month: { purnimanta: month },
595
+ category: "lunar-tithi",
596
+ extended: true,
597
+ observance: {
598
+ kind: "tithi-pervades",
599
+ paksha: "krishna",
600
+ tithi: 11,
601
+ window: "sunrise",
602
+ precedence: "udaya",
603
+ },
604
+ });
605
+ }
606
+ // Adhika Jyeshtha pair (present in 2026; generator is year-aware via
607
+ // the month label "Adhika Jyeshtha" which the evaluator normalizes and
608
+ // matches against the adhika lunation in the year's new-moon scan).
609
+ // These are only emitted when an adhika Jyeshtha is expected. For now we
610
+ // always emit them; the evaluator will return an empty date (with a
611
+ // diagnostic) for years without an adhika Jyeshtha, and the integration
612
+ // test checks the 2026 count. In a future revision, emit conditionally.
613
+ rules.push({
614
+ id: "ekadashi-adhika-jyeshtha-shukla",
615
+ displayName: ADHIKA_EKADASHI_NAMES.shukla,
616
+ month: { purnimanta: "Adhika Jyeshtha" },
617
+ category: "lunar-tithi",
618
+ extended: true,
619
+ meta: {
620
+ note: "Adhika Jyeshtha Shukla Ekadashi. Present in 2026; evaluator returns empty date with diagnostic in non-adhika years.",
621
+ },
622
+ observance: {
623
+ kind: "tithi-pervades",
624
+ paksha: "shukla",
625
+ tithi: 11,
626
+ window: "sunrise",
627
+ precedence: "udaya",
628
+ },
629
+ });
630
+ rules.push({
631
+ id: "ekadashi-adhika-jyeshtha-krishna",
632
+ displayName: ADHIKA_EKADASHI_NAMES.krishna,
633
+ month: { purnimanta: "Adhika Jyeshtha" },
634
+ category: "lunar-tithi",
635
+ extended: true,
636
+ meta: {
637
+ note: "Adhika Jyeshtha Krishna Ekadashi. Present in 2026; evaluator returns empty date with diagnostic in non-adhika years.",
638
+ },
639
+ observance: {
640
+ kind: "tithi-pervades",
641
+ paksha: "krishna",
642
+ tithi: 11,
643
+ window: "sunrise",
644
+ precedence: "udaya",
645
+ },
646
+ });
647
+ void year; // year parameter reserved for future conditional adhika emission
648
+ return rules;
649
+ }
650
+ /**
651
+ * Generate Sankashti Chaturthi rules for `year`.
652
+ *
653
+ * Rule (§4b): Each month's Krishna-paksha Chaturthi, the day Chaturthi is
654
+ * current at moonrise (moonrise-vyāpti). If Chaturthi touches no moonrise,
655
+ * falls on the day whose moonrise is nearest to the Chaturthi interval.
656
+ * Kind: `moonrise` (Kartika Krishna 4 is the model; this extends to all months).
657
+ *
658
+ * Note: Angāraki (when Sankashti falls on a Tuesday) is an editorial flag,
659
+ * not a rule-selection criterion — not encoded here.
660
+ *
661
+ * Count (2026): 13 Sankashti Chaturthis (Adhika Jyeshtha contributes one extra).
662
+ *
663
+ * Like ekadashiRules, always emits the Adhika Jyeshtha entry; the evaluator
664
+ * handles years without an adhika month gracefully (empty date + diagnostic).
665
+ */
666
+ export function sankashtiRules(year) {
667
+ const rules = [];
668
+ for (const month of PURNIMANTA_MONTHS) {
669
+ rules.push({
670
+ id: `sankashti-chaturthi-${month.toLowerCase()}`,
671
+ displayName: `Sankashti Chaturthi (${month})`,
672
+ month: { purnimanta: month },
673
+ category: "moonrise",
674
+ extended: true,
675
+ observance: {
676
+ kind: "moonrise",
677
+ paksha: "krishna",
678
+ tithi: 4,
679
+ },
680
+ });
681
+ }
682
+ // Adhika Jyeshtha Sankashti (present in 2026).
683
+ rules.push({
684
+ id: "sankashti-chaturthi-adhika-jyeshtha",
685
+ displayName: "Sankashti Chaturthi (Adhika Jyeshtha)",
686
+ month: { purnimanta: "Adhika Jyeshtha" },
687
+ category: "moonrise",
688
+ extended: true,
689
+ meta: {
690
+ note: "Adhika Jyeshtha Sankashti Chaturthi. Present in 2026; evaluator returns empty date with diagnostic in non-adhika years.",
691
+ },
692
+ observance: {
693
+ kind: "moonrise",
694
+ paksha: "krishna",
695
+ tithi: 4,
696
+ },
697
+ });
698
+ void year;
699
+ return rules;
700
+ }
701
+ /**
702
+ * The single Chhath Puja rule (§4b): anchored on Kartika Shukla Shashthi.
703
+ *
704
+ * Spec: main day = Shashthi prevails at SUNSET (Sandhya Arghya).
705
+ * Grammar kind: `solar-arghya` (tithi at sunset + next sunrise).
706
+ * This is a clean mapping: solar-arghya resolves to the day whose sunset
707
+ * falls within the Shashthi interval (Sandhya Arghya = evening offering),
708
+ * and records the next morning's sunrise (Uṣā Arghya).
709
+ *
710
+ * The 4-day festival (Nahay Khay → Kharna → Shashthi → Saptami) is not
711
+ * modelled here as separate rules; only the anchor day is captured.
712
+ */
713
+ export const CHHATH_RULE = {
714
+ id: "chhath-puja",
715
+ displayName: "Chhath Puja (Sandhya Arghya)",
716
+ month: { purnimanta: "Kartika" },
717
+ category: "lunar-tithi",
718
+ extended: true,
719
+ observance: {
720
+ kind: "solar-arghya",
721
+ paksha: "shukla",
722
+ tithi: 6,
723
+ },
724
+ };
725
+ // ═══════════════════════════════════════════════════════════════════════════
726
+ // Recurring monthly vratas (§4c) — Pradoṣa, Masik Śivarātri, Pūrṇimā,
727
+ // Amāvāsyā, and the minor (non-Makar/Mesha) Sankrāntis.
728
+ //
729
+ // The mechanism already exists (tithi-pervades / solar-ingress); these supply
730
+ // the rule DATA. Each generator mirrors ekadashiRules: the 12 regular months
731
+ // plus the Adhika Jyeshtha entries (present in 2026), which the evaluator
732
+ // resolves to empty in non-adhika years.
733
+ // ═══════════════════════════════════════════════════════════════════════════
734
+ const PAKSHAS = ["shukla", "krishna"];
735
+ const ADHIKA = "Adhika Jyeshtha";
736
+ const ADHIKA_NOTE = "Adhika-month entry; resolves to empty with a diagnostic in non-adhika years.";
737
+ /** Slugify a month label for an id: lower-case, spaces → hyphens. */
738
+ const monthSlug = (month) => month.toLowerCase().replace(/\s+/g, "-");
739
+ /**
740
+ * Shared scaffold for the recurring monthly vratas. Emits one rule per
741
+ * pūrṇimānta month plus the trailing Adhika Jyeṣṭha entry (present in 2026; the
742
+ * evaluator resolves it to an empty date with a diagnostic in non-adhika
743
+ * years). `build(month, slug, adhika)` returns the rule(s) for one month —
744
+ * `adhika` is true only on the Adhika pass, so callers attach `ADHIKA_NOTE`
745
+ * (and any extra pakṣa fan-out) themselves. This collapses four near-identical
746
+ * push-loop bodies into one.
747
+ */
748
+ function monthlyVrataRules(build) {
749
+ const out = [];
750
+ for (const month of PURNIMANTA_MONTHS)
751
+ out.push(...build(month, monthSlug(month), false));
752
+ out.push(...build(ADHIKA, monthSlug(ADHIKA), true));
753
+ return out;
754
+ }
755
+ /**
756
+ * Pradoṣa Vrata — Trayodaśī (13) pervading the pradoṣa (post-sunset) window, in
757
+ * both pakṣas of every month. The (S)/(K) suffix marks śukla/kṛṣṇa.
758
+ */
759
+ export function pradoshRules(year) {
760
+ void year;
761
+ return monthlyVrataRules((month, slug, adhika) => PAKSHAS.map((paksha) => ({
762
+ id: `pradosh-${slug}-${paksha}`,
763
+ displayName: `Pradosh Vrat (${paksha === "shukla" ? "S" : "K"}, ${month})`,
764
+ month: { purnimanta: month },
765
+ category: "lunar-tithi",
766
+ extended: true,
767
+ ...(adhika ? { meta: { note: ADHIKA_NOTE } } : {}),
768
+ observance: { kind: "tithi-pervades", paksha, tithi: 13, window: "pradosha", precedence: "max-window-fraction" },
769
+ })));
770
+ }
771
+ /**
772
+ * Masik Śivarātri — Kṛṣṇa Caturdaśī (14) in the niśīta (midnight) window.
773
+ *
774
+ * `fallback: "nearest-window"`: at far-western longitudes the Caturdaśī can
775
+ * straddle two midnights without covering either niśīta (it ends ~1h before the
776
+ * later one and starts after the earlier one), so no day pervades. The vrata
777
+ * still occurs — on the day the Caturdaśī is current going into the night — so
778
+ * we keep the candidate whose niśīta is nearest the tithi instead of dropping
779
+ * the date. (Closes the 2 undated Calgary 2026 entries; no effect where a day
780
+ * does pervade, i.e. New Delhi and the other 11 months.)
781
+ */
782
+ export function masikShivaratriRules(year) {
783
+ void year;
784
+ return monthlyVrataRules((month, slug, adhika) => [{
785
+ id: `masik-shivaratri-${slug}`,
786
+ displayName: `Masik Shivaratri (${month})`,
787
+ month: { purnimanta: month },
788
+ category: "lunar-tithi",
789
+ extended: true,
790
+ ...(adhika ? { meta: { note: ADHIKA_NOTE } } : {}),
791
+ observance: { kind: "tithi-pervades", paksha: "krishna", tithi: 14, window: "nishita", precedence: "max-window-fraction", fallback: "nearest-window" },
792
+ }]);
793
+ }
794
+ /**
795
+ * Pūrṇimā Vrata — the full-moon day of every pūrṇimānta month. The vrata is kept
796
+ * on the day the full Moon is up during Pūrṇimā (moonrise-vyāpti), which is the
797
+ * day panchāṅgas list as "Purnima Vrat" (distinct from the next-morning
798
+ * snāna-dāna Pūrṇimā).
799
+ *
800
+ * KNOWN ISSUE (vs Drik Panchang Calgary 2026): this fires a day early in ~3/13
801
+ * months (e.g. Jyeṣṭha Jun 28 vs DP Jun 29) when Pūrṇimā begins the prior
802
+ * evening. A plain udaya rule overcorrects (drops months where Pūrṇimā never
803
+ * touches a sunrise, and pushes others a day late), so the correct fix needs a
804
+ * proper udaya-with-fallback / vedha policy — tracked, not yet implemented.
805
+ */
806
+ export function purnimaVratRules(year) {
807
+ void year;
808
+ return monthlyVrataRules((month, slug, adhika) => [{
809
+ id: `purnima-vrat-${slug}`,
810
+ displayName: `${month} Purnima Vrat`,
811
+ month: { purnimanta: month },
812
+ category: "moonrise",
813
+ extended: true,
814
+ ...(adhika ? { meta: { note: ADHIKA_NOTE } } : {}),
815
+ observance: { kind: "moonrise", paksha: "shukla", tithi: "purnima" },
816
+ }]);
817
+ }
818
+ /**
819
+ * Pūrṇimā snāna-dāna — the full-moon day Drik lists as "X Purnima" (distinct
820
+ * from the moonrise "Purnima Vrat" above). It is the day Pūrṇimā prevails at
821
+ * sunrise (udaya), when the morning snāna/dāna is performed; at far-western
822
+ * longitudes this is typically the civil day AFTER the vrat. `nearest-window`
823
+ * covers the rare month whose Pūrṇimā touches no sunrise.
824
+ */
825
+ export function purnimaSnanaRules(year) {
826
+ void year;
827
+ return monthlyVrataRules((month, slug, adhika) => [{
828
+ id: `purnima-snana-${slug}`,
829
+ displayName: `${month} Purnima (Snana-Dana)`,
830
+ month: { purnimanta: month },
831
+ category: "lunar-tithi",
832
+ extended: true,
833
+ ...(adhika ? { meta: { note: ADHIKA_NOTE } } : {}),
834
+ observance: { kind: "tithi-pervades", paksha: "shukla", tithi: "purnima", window: "sunrise", precedence: "udaya", fallback: "nearest-window" },
835
+ }]);
836
+ }
837
+ /** Amāvāsyā — the new-moon (amāvāsyā) day of every pūrṇimānta month. */
838
+ export function amavasyaRules(year) {
839
+ void year;
840
+ return monthlyVrataRules((month, slug, adhika) => [{
841
+ id: `amavasya-${slug}`,
842
+ displayName: `${month} Amavasya`,
843
+ month: { purnimanta: month },
844
+ category: "lunar-tithi",
845
+ extended: true,
846
+ ...(adhika ? { meta: { note: ADHIKA_NOTE } } : {}),
847
+ observance: { kind: "tithi-pervades", paksha: "krishna", tithi: "amavasya", window: "sunrise", precedence: "max-window-fraction" },
848
+ }]);
849
+ }
850
+ /**
851
+ * The ten minor Sankrāntis (the Sun's sidereal ingress into each rāśi), i.e.
852
+ * all twelve except Makara and Meṣa, which are CORE festivals. `punyaKala`
853
+ * follows Makar Sankranti's convention.
854
+ */
855
+ export function sankrantiRules(year) {
856
+ // [displayName, id-suffix, rāśi index]; rāśi 0 = Mesha … 11 = Mīna.
857
+ const minor = [
858
+ ["Kumbha Sankranti", "kumbha", 10],
859
+ ["Meena Sankranti", "meena", 11],
860
+ ["Vrishabha Sankranti", "vrishabha", 1],
861
+ ["Mithuna Sankranti", "mithuna", 2],
862
+ ["Karka Sankranti", "karka", 3],
863
+ ["Simha Sankranti", "simha", 4],
864
+ ["Kanya Sankranti", "kanya", 5],
865
+ ["Tula Sankranti", "tula", 6],
866
+ ["Vrishchika Sankranti", "vrishchika", 7],
867
+ ["Dhanu Sankranti", "dhanu", 8],
868
+ ];
869
+ void year;
870
+ // `month` is omitted: solar-ingress keys on the rāśi, not a lunar-month label.
871
+ return minor.map(([displayName, slug, rashi]) => ({
872
+ id: `sankranti-${slug}`,
873
+ displayName,
874
+ category: "solar",
875
+ extended: true,
876
+ observance: { kind: "solar-ingress", rashi, punyaKala: "after-moment-to-sunset" },
877
+ }));
878
+ }
879
+ /**
880
+ * One-off regional festivals & jayantis on the HSNA calendar. Most are plain
881
+ * udaya-tithi observances (the tithi present at sunrise); the full-moon
882
+ * jayantis use moonrise-vyāpti; Lohri and the Navrātri Pāraṇā are offsets from
883
+ * another festival. Validated against HSNA 2026 (test/hsna-oneoff.test.ts).
884
+ */
885
+ export function oneOffFestivalRules(year) {
886
+ // udaya-tithi at sunrise by default; some festivals key on a later kāla (the
887
+ // tithi prevailing at that ritual window), which is given explicitly.
888
+ const T = (id, displayName, month, paksha, tithi, window = "sunrise", precedence = "udaya") => ({
889
+ id,
890
+ displayName,
891
+ month: { purnimanta: month },
892
+ category: "lunar-tithi",
893
+ extended: true,
894
+ observance: { kind: "tithi-pervades", paksha, tithi, window, precedence },
895
+ });
896
+ // full-moon (moonrise-vyāpti)
897
+ const P = (id, displayName, month) => ({
898
+ id,
899
+ displayName,
900
+ month: { purnimanta: month },
901
+ category: "moonrise",
902
+ extended: true,
903
+ observance: { kind: "moonrise", paksha: "shukla", tithi: "purnima" },
904
+ });
905
+ // offset from another rule (`month` omitted — derived rules take their date,
906
+ // and thus their month label, from the referenced festival).
907
+ const D = (id, displayName, from, offsetDays) => ({
908
+ id,
909
+ displayName,
910
+ category: "derived",
911
+ extended: true,
912
+ observance: { kind: "derived", from, offsetDays },
913
+ });
914
+ void year;
915
+ return [
916
+ D("lohri", "Lohri", "makar-sankranti", -1),
917
+ T("phulera-dooj", "Phulera Dhooj", "Phalguna", "shukla", 2),
918
+ T("ugadi-gudi-padwa", "Chaitra Navratri / Ugadi / Gudi Padwa", "Chaitra", "shukla", 1),
919
+ D("chaitra-navratri-parana", "Chaitra Navratri Parana", "rama-navami", 1),
920
+ P("koorm-jayanti", "Koorm Jayanti", "Vaishakha"),
921
+ T("narad-jayanti", "Narad Jayanti", "Jyeshtha", "krishna", 1),
922
+ // Ganga Dussehra — Jyeṣṭha Śukla Daśamī, observed in the ADHIKA Jyeṣṭha when
923
+ // the year has one (Drik 2026: 25 May, Adhika Jyeṣṭha) and in the nija
924
+ // Jyeṣṭha otherwise. `adhika:"prefer-adhika"` encodes exactly that — it
925
+ // resolves every year without hardcoding the leap month.
926
+ {
927
+ id: "ganga-dussehra",
928
+ displayName: "Ganga Dussehra",
929
+ month: { purnimanta: "Jyeshtha" },
930
+ category: "lunar-tithi",
931
+ extended: true,
932
+ observance: {
933
+ kind: "tithi-pervades", paksha: "shukla", tithi: 10,
934
+ window: "sunrise", precedence: "udaya", adhika: "prefer-adhika",
935
+ },
936
+ },
937
+ T("jagannath-rath-yatra", "Jagannath Rath Yatra", "Ashadha", "shukla", 2),
938
+ T("hariyali-teej", "Hariyali Teej", "Shravana", "shukla", 3),
939
+ T("nag-panchami", "Nag Panchami", "Shravana", "shukla", 5),
940
+ // Kajari Teej — evening (candra) worship → pradoṣa-vyāpinī Tṛtīyā.
941
+ T("kajari-teej", "Kajari Teej", "Bhadrapada", "krishna", 3, "pradosha", "max-window-fraction"),
942
+ T("balram-jayanti", "Balram Jayanti", "Bhadrapada", "krishna", 6),
943
+ T("hartalika-teej", "Hartalika Teej", "Bhadrapada", "shukla", 3),
944
+ // Rishi Panchami — canonically madhyāhna-vyāpinī (midday) Pañcamī.
945
+ T("rishi-panchami", "Rishi Panchami", "Bhadrapada", "shukla", 5, "madhyahna", "max-window-fraction"),
946
+ T("anant-chaturdashi", "Anant Chaturdashi", "Bhadrapada", "shukla", 14),
947
+ P("pitru-paksha-begins", "Pitru Paksha Begins (Bhadrapada Purnima)", "Bhadrapada"),
948
+ T("kalparambha", "Kalparambha", "Ashwina", "shukla", 6),
949
+ T("navpatrika-puja", "Navpatrika Puja", "Ashwina", "shukla", 7),
950
+ // Ahoi Ashtami — fast broken at evening star-sight → pradoṣa-vyāpinī Aṣṭamī.
951
+ T("ahoi-ashtami", "Ahoi Ashtami", "Kartika", "krishna", 8, "pradosha", "max-window-fraction"),
952
+ T("kansh-vadh", "Kansh Vadh", "Kartika", "shukla", 10),
953
+ T("tulsi-vivah", "Tulsi Vivah", "Kartika", "shukla", 12),
954
+ P("dattatreya-jayanti", "Dattatreya Jayanti", "Margashirsha"),
955
+ ];
956
+ }
957
+ /**
958
+ * Additional regional festivals & jayantis from the Drik Panchang Calgary
959
+ * calendar, beyond the §4 core and the HSNA one-offs. Each is anchored on its
960
+ * ritual kāla (deity-birth festivals on madhyāhna/pradoṣa/niśīta, snāna rites at
961
+ * sunrise) and verified against Drik Panchang Calgary 2026
962
+ * (test/conformance-calgary-regional.test.ts).
963
+ *
964
+ * Helpers mirror oneOffFestivalRules: T (tithi-pervades), P (moonrise pūrṇimā),
965
+ * A (kṛṣṇa amāvāsyā at sunrise), SI (solar ingress / saṅkrānti-day festival).
966
+ */
967
+ export function regionalFestivalRules(year) {
968
+ const T = (id, displayName, month, paksha, tithi, window = "sunrise", precedence = "udaya") => ({
969
+ id, displayName, month: { purnimanta: month }, category: "lunar-tithi", extended: true,
970
+ observance: { kind: "tithi-pervades", paksha, tithi, window, precedence },
971
+ });
972
+ const P = (id, displayName, month) => ({
973
+ id, displayName, month: { purnimanta: month }, category: "moonrise", extended: true,
974
+ observance: { kind: "moonrise", paksha: "shukla", tithi: "purnima" },
975
+ });
976
+ const A = (id, displayName, month) => ({
977
+ id, displayName, month: { purnimanta: month }, category: "lunar-tithi", extended: true,
978
+ observance: { kind: "tithi-pervades", paksha: "krishna", tithi: "amavasya", window: "sunrise", precedence: "max-window-fraction" },
979
+ });
980
+ const SI = (id, displayName, rashi) => ({
981
+ id, displayName, category: "solar", extended: true,
982
+ observance: { kind: "solar-ingress", rashi, punyaKala: "after-moment-to-sunset" },
983
+ });
984
+ void year;
985
+ return [
986
+ // ── Māgha ──
987
+ T("ratha-saptami", "Ratha Saptami", "Magha", "shukla", 7),
988
+ T("bhishma-ashtami", "Bhishma Ashtami", "Magha", "shukla", 8, "madhyahna", "max-window-fraction"),
989
+ // ── Chaitra ──
990
+ T("gangaur", "Gangaur / Gauri Puja", "Chaitra", "shukla", 3),
991
+ T("yamuna-chhath", "Yamuna Chhath", "Chaitra", "shukla", 6),
992
+ T("swaminarayan-jayanti", "Swaminarayan Jayanti", "Chaitra", "shukla", 9),
993
+ // ── Vaiśākha — deity-birth jayantis on their ritual kāla ──
994
+ T("parashurama-jayanti", "Parashurama Jayanti", "Vaishakha", "shukla", 3, "pradosha", "max-window-fraction"),
995
+ T("ganga-saptami", "Ganga Saptami", "Vaishakha", "shukla", 7, "madhyahna", "max-window-fraction"),
996
+ T("sita-navami", "Sita Navami", "Vaishakha", "shukla", 9, "madhyahna", "max-window-fraction"),
997
+ T("narasimha-jayanti", "Narasimha Jayanti", "Vaishakha", "shukla", 14, "pradosha", "max-window-fraction"),
998
+ // ── Jyeṣṭha ──
999
+ A("vat-savitri-vrat", "Vat Savitri Vrat", "Jyeshtha"),
1000
+ A("shani-jayanti", "Shani Jayanti", "Jyeshtha"),
1001
+ P("vat-purnima-vrat", "Vat Purnima Vrat", "Jyeshtha"),
1002
+ // ── Bhādrapada ──
1003
+ T("radha-ashtami", "Radha Ashtami", "Bhadrapada", "shukla", 8),
1004
+ T("ganesh-visarjan", "Ganesh Visarjan", "Bhadrapada", "shukla", 14),
1005
+ // ── Āśvina ──
1006
+ SI("vishwakarma-puja", "Vishwakarma Puja", 5), // on Kanya Sankranti day
1007
+ // ── Kārtika ──
1008
+ T("govatsa-dwadashi", "Govatsa Dwadashi", "Kartika", "krishna", 12),
1009
+ T("kali-chaudas", "Kali Chaudas", "Kartika", "krishna", 14, "nishita", "max-window-fraction"),
1010
+ // ── Mārgaśīrṣa ──
1011
+ T("kalabhairav-jayanti", "Kalabhairav Jayanti", "Margashirsha", "krishna", 8, "nishita", "max-window-fraction"),
1012
+ T("vivah-panchami", "Vivah Panchami", "Margashirsha", "shukla", 5),
1013
+ // ── Nakṣatra- and weekday-anchored ──
1014
+ // Onam — Śravaṇa (Thiruvoṇam) nakṣatra at sunrise while the Sun is in Siṃha.
1015
+ {
1016
+ id: "onam", displayName: "Onam (Thiruvonam)", category: "nakshatra", extended: true,
1017
+ observance: { kind: "nakshatra-pervades", nakshatra: "Shravana", solarRashi: 4 },
1018
+ },
1019
+ // Varalakṣmī Vrat — the Friday (weekday 5) before Śrāvaṇa Pūrṇimā.
1020
+ {
1021
+ id: "varalakshmi-vrat", displayName: "Varalakshmi Vrat", category: "derived", extended: true,
1022
+ observance: { kind: "weekday-relative", from: "purnima-snana-shravana", weekday: 5 },
1023
+ },
1024
+ ];
1025
+ }
1026
+ // ═══════════════════════════════════════════════════════════════════════════
1027
+ // Public API — allRules(year)
1028
+ // ═══════════════════════════════════════════════════════════════════════════
1029
+ /**
1030
+ * Assemble the complete rule set for `year`: core (§4) + extended (§4b).
1031
+ *
1032
+ * The extended generators are year-aware so that Adhika-month entries can in
1033
+ * future be emitted conditionally; for now they always include the Adhika
1034
+ * Jyeshtha entries (the evaluator handles gracefully for non-adhika years).
1035
+ *
1036
+ * Note: `gita-jayanti` appears in both CORE_RULES and
1037
+ * `ekadashiRules` (as `ekadashi-margashirsha-shukla`). These are separate
1038
+ * rules with different IDs that produce the same date — gita-jayanti as a
1039
+ * named festival and the Margashirsha Shukla Ekadashi as part of the full
1040
+ * Ekadashi calendar. No deduplication is applied; both are surfaced.
1041
+ */
1042
+ export function allRules(year) {
1043
+ return [
1044
+ ...CORE_RULES,
1045
+ ...ekadashiRules(year),
1046
+ ...sankashtiRules(year),
1047
+ ...pradoshRules(year),
1048
+ ...masikShivaratriRules(year),
1049
+ ...purnimaVratRules(year),
1050
+ ...purnimaSnanaRules(year),
1051
+ ...amavasyaRules(year),
1052
+ ...sankrantiRules(year),
1053
+ ...oneOffFestivalRules(year),
1054
+ ...regionalFestivalRules(year),
1055
+ CHHATH_RULE,
1056
+ ];
1057
+ }
1058
+ //# sourceMappingURL=rules.js.map