jyotish-calc 1.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.
@@ -0,0 +1,362 @@
1
+ /**
2
+ * Divisional Charts (Vargas) Constants
3
+ * Definitions and sign classifications for Parashari Jyotish
4
+ */
5
+
6
+ // ============================================================================
7
+ // SIGN DATA
8
+ // ============================================================================
9
+
10
+ /**
11
+ * Sign names in full format
12
+ */
13
+ const SIGNS = [
14
+ 'Aries', 'Taurus', 'Gemini', 'Cancer',
15
+ 'Leo', 'Virgo', 'Libra', 'Scorpio',
16
+ 'Sagittarius', 'Capricorn', 'Aquarius', 'Pisces'
17
+ ];
18
+
19
+ /**
20
+ * Sign abbreviations
21
+ */
22
+ const SIGNS_SHORT = ['Ar', 'Ta', 'Ge', 'Cn', 'Le', 'Vi', 'Li', 'Sc', 'Sg', 'Cp', 'Aq', 'Pi'];
23
+
24
+ /**
25
+ * Sign lords (planets ruling each sign)
26
+ */
27
+ const SIGN_LORDS = {
28
+ 0: 'Mars', // Aries
29
+ 1: 'Venus', // Taurus
30
+ 2: 'Mercury', // Gemini
31
+ 3: 'Moon', // Cancer
32
+ 4: 'Sun', // Leo
33
+ 5: 'Mercury', // Virgo
34
+ 6: 'Venus', // Libra
35
+ 7: 'Mars', // Scorpio
36
+ 8: 'Jupiter', // Sagittarius
37
+ 9: 'Saturn', // Capricorn
38
+ 10: 'Saturn', // Aquarius
39
+ 11: 'Jupiter' // Pisces
40
+ };
41
+
42
+ // ============================================================================
43
+ // SIGN CLASSIFICATIONS
44
+ // ============================================================================
45
+
46
+ /**
47
+ * Movable (Chara) signs - indices
48
+ */
49
+ const MOVABLE_SIGNS = [0, 3, 6, 9]; // Aries, Cancer, Libra, Capricorn
50
+
51
+ /**
52
+ * Fixed (Sthira) signs - indices
53
+ */
54
+ const FIXED_SIGNS = [1, 4, 7, 10]; // Taurus, Leo, Scorpio, Aquarius
55
+
56
+ /**
57
+ * Dual (Dwiswabhava) signs - indices
58
+ */
59
+ const DUAL_SIGNS = [2, 5, 8, 11]; // Gemini, Virgo, Sagittarius, Pisces
60
+
61
+ /**
62
+ * Odd (Masculine) signs - indices
63
+ */
64
+ const ODD_SIGNS = [0, 2, 4, 6, 8, 10]; // Aries, Gemini, Leo, Libra, Sagittarius, Aquarius
65
+
66
+ /**
67
+ * Even (Feminine) signs - indices
68
+ */
69
+ const EVEN_SIGNS = [1, 3, 5, 7, 9, 11]; // Taurus, Cancer, Virgo, Scorpio, Capricorn, Pisces
70
+
71
+ /**
72
+ * Fire signs - indices
73
+ */
74
+ const FIRE_SIGNS = [0, 4, 8]; // Aries, Leo, Sagittarius
75
+
76
+ /**
77
+ * Earth signs - indices
78
+ */
79
+ const EARTH_SIGNS = [1, 5, 9]; // Taurus, Virgo, Capricorn
80
+
81
+ /**
82
+ * Air signs - indices
83
+ */
84
+ const AIR_SIGNS = [2, 6, 10]; // Gemini, Libra, Aquarius
85
+
86
+ /**
87
+ * Water signs - indices
88
+ */
89
+ const WATER_SIGNS = [3, 7, 11]; // Cancer, Scorpio, Pisces
90
+
91
+ // ============================================================================
92
+ // VARGA DEFINITIONS
93
+ // ============================================================================
94
+
95
+ /**
96
+ * All 21 divisional charts with metadata
97
+ */
98
+ const VARGAS = {
99
+ D1: {
100
+ id: 'D1',
101
+ name: 'Rashi',
102
+ divisions: 1,
103
+ degreesPerDivision: 30,
104
+ focus: 'Overall life, body, health, general promise'
105
+ },
106
+ D2: {
107
+ id: 'D2',
108
+ name: 'Hora',
109
+ divisions: 2,
110
+ degreesPerDivision: 15,
111
+ focus: 'Wealth, prosperity, food, resources'
112
+ },
113
+ D3: {
114
+ id: 'D3',
115
+ name: 'Drekkana',
116
+ divisions: 3,
117
+ degreesPerDivision: 10,
118
+ focus: 'Siblings, courage, efforts'
119
+ },
120
+ D4: {
121
+ id: 'D4',
122
+ name: 'Chaturthamsha',
123
+ divisions: 4,
124
+ degreesPerDivision: 7.5,
125
+ focus: 'Fortune, property, fixed and movable assets'
126
+ },
127
+ D5: {
128
+ id: 'D5',
129
+ name: 'Panchamsha',
130
+ divisions: 5,
131
+ degreesPerDivision: 6,
132
+ focus: 'Purva punya, inherent talent, creative intelligence'
133
+ },
134
+ D6: {
135
+ id: 'D6',
136
+ name: 'Shashtamsha',
137
+ divisions: 6,
138
+ degreesPerDivision: 5,
139
+ focus: 'Diseases, obstacles, enemies, debt'
140
+ },
141
+ D7: {
142
+ id: 'D7',
143
+ name: 'Saptamsha',
144
+ divisions: 7,
145
+ degreesPerDivision: 30 / 7,
146
+ focus: 'Children, progeny, creativity passed to next generation'
147
+ },
148
+ D8: {
149
+ id: 'D8',
150
+ name: 'Ashtamsha',
151
+ divisions: 8,
152
+ degreesPerDivision: 3.75,
153
+ focus: 'Longevity, sudden events, transformations'
154
+ },
155
+ D9: {
156
+ id: 'D9',
157
+ name: 'Navamsha',
158
+ divisions: 9,
159
+ degreesPerDivision: 30 / 9,
160
+ focus: 'Marriage, spouse, dharma, inner strength, maturity of planets'
161
+ },
162
+ D10: {
163
+ id: 'D10',
164
+ name: 'Dashamsha',
165
+ divisions: 10,
166
+ degreesPerDivision: 3,
167
+ focus: 'Profession, status, karma in action'
168
+ },
169
+ D11: {
170
+ id: 'D11',
171
+ name: 'Rudramsha',
172
+ divisions: 11,
173
+ degreesPerDivision: 30 / 11,
174
+ focus: 'Gains, destruction, sudden rise or fall'
175
+ },
176
+ D12: {
177
+ id: 'D12',
178
+ name: 'Dvadashamsha',
179
+ divisions: 12,
180
+ degreesPerDivision: 2.5,
181
+ focus: 'Parents, ancestry, heredity, lineage'
182
+ },
183
+ D16: {
184
+ id: 'D16',
185
+ name: 'Shodasamsha',
186
+ divisions: 16,
187
+ degreesPerDivision: 1.875,
188
+ focus: 'Vehicles, comforts, luxuries'
189
+ },
190
+ D20: {
191
+ id: 'D20',
192
+ name: 'Vimsamsha',
193
+ divisions: 20,
194
+ degreesPerDivision: 1.5,
195
+ focus: 'Spiritual inclination, mantra, sadhana'
196
+ },
197
+ D24: {
198
+ id: 'D24',
199
+ name: 'Chaturvimshamsha',
200
+ divisions: 24,
201
+ degreesPerDivision: 1.25,
202
+ focus: 'Education, learning, scriptural knowledge'
203
+ },
204
+ D27: {
205
+ id: 'D27',
206
+ name: 'Saptavimshamsha',
207
+ divisions: 27,
208
+ degreesPerDivision: 30 / 27,
209
+ focus: 'Strengths and weaknesses, resilience, internal grit'
210
+ },
211
+ D30: {
212
+ id: 'D30',
213
+ name: 'Trimsamsha',
214
+ divisions: 30,
215
+ degreesPerDivision: 1,
216
+ focus: 'Mishaps, misfortunes, character, inner flaws'
217
+ },
218
+ D32: {
219
+ id: 'D32',
220
+ name: 'Trimshadvamsha',
221
+ divisions: 32,
222
+ degreesPerDivision: 30 / 32,
223
+ focus: 'Misfortunes, specific karmic debts, subtle strengths'
224
+ },
225
+ D40: {
226
+ id: 'D40',
227
+ name: 'Khavedamsha',
228
+ divisions: 40,
229
+ degreesPerDivision: 0.75,
230
+ focus: 'Auspicious and inauspicious results, family karma'
231
+ },
232
+ D45: {
233
+ id: 'D45',
234
+ name: 'Akshavedamsha',
235
+ divisions: 45,
236
+ degreesPerDivision: 30 / 45,
237
+ focus: 'Character, conduct, general auspiciousness'
238
+ },
239
+ D60: {
240
+ id: 'D60',
241
+ name: 'Shashtyamsha',
242
+ divisions: 60,
243
+ degreesPerDivision: 0.5,
244
+ focus: 'Past-life karma, fine destiny, minute strengths/weaknesses'
245
+ }
246
+ };
247
+
248
+ /**
249
+ * D30 Trimsamsha planet-based unequal segments for ODD signs
250
+ * Segment boundaries and their ruling planets/signs
251
+ */
252
+ const TRIMSAMSHA_ODD = [
253
+ { start: 0, end: 5, planet: 'Mars', sign: 0 }, // 0°-5° → Aries
254
+ { start: 5, end: 10, planet: 'Saturn', sign: 10 }, // 5°-10° → Aquarius
255
+ { start: 10, end: 18, planet: 'Jupiter', sign: 8 }, // 10°-18° → Sagittarius
256
+ { start: 18, end: 25, planet: 'Mercury', sign: 2 }, // 18°-25° → Gemini
257
+ { start: 25, end: 30, planet: 'Venus', sign: 6 } // 25°-30° → Libra
258
+ ];
259
+
260
+ /**
261
+ * D30 Trimsamsha planet-based unequal segments for EVEN signs
262
+ * Reverse order from ODD signs
263
+ */
264
+ const TRIMSAMSHA_EVEN = [
265
+ { start: 0, end: 5, planet: 'Venus', sign: 1 }, // 0°-5° → Taurus
266
+ { start: 5, end: 12, planet: 'Mercury', sign: 5 }, // 5°-12° → Virgo
267
+ { start: 12, end: 20, planet: 'Jupiter', sign: 11 }, // 12°-20° → Pisces
268
+ { start: 20, end: 25, planet: 'Saturn', sign: 9 }, // 20°-25° → Capricorn
269
+ { start: 25, end: 30, planet: 'Mars', sign: 7 } // 25°-30° → Scorpio
270
+ ];
271
+
272
+ /**
273
+ * D60 Shashtyamsha names (60 divisions with auspicious/inauspicious nature)
274
+ */
275
+ const SHASHTYAMSHA_NAMES = [
276
+ { name: 'Ghora', nature: 'inauspicious' },
277
+ { name: 'Rakshasa', nature: 'inauspicious' },
278
+ { name: 'Deva', nature: 'auspicious' },
279
+ { name: 'Kubera', nature: 'auspicious' },
280
+ { name: 'Yaksha', nature: 'auspicious' },
281
+ { name: 'Kinnara', nature: 'auspicious' },
282
+ { name: 'Bhrashta', nature: 'inauspicious' },
283
+ { name: 'Kulaghna', nature: 'inauspicious' },
284
+ { name: 'Garala', nature: 'inauspicious' },
285
+ { name: 'Vahni', nature: 'inauspicious' },
286
+ { name: 'Maya', nature: 'inauspicious' },
287
+ { name: 'Purishaka', nature: 'inauspicious' },
288
+ { name: 'Apampati', nature: 'auspicious' },
289
+ { name: 'Marut', nature: 'auspicious' },
290
+ { name: 'Kaala', nature: 'inauspicious' },
291
+ { name: 'Sarpa', nature: 'inauspicious' },
292
+ { name: 'Amrita', nature: 'auspicious' },
293
+ { name: 'Indu', nature: 'auspicious' },
294
+ { name: 'Mridu', nature: 'auspicious' },
295
+ { name: 'Komala', nature: 'auspicious' },
296
+ { name: 'Heramba', nature: 'auspicious' },
297
+ { name: 'Brahma', nature: 'auspicious' },
298
+ { name: 'Vishnu', nature: 'auspicious' },
299
+ { name: 'Maheshwara', nature: 'auspicious' },
300
+ { name: 'Deva', nature: 'auspicious' },
301
+ { name: 'Ardra', nature: 'inauspicious' },
302
+ { name: 'Kalinasha', nature: 'auspicious' },
303
+ { name: 'Kshiteesa', nature: 'auspicious' },
304
+ { name: 'Kamalakara', nature: 'auspicious' },
305
+ { name: 'Gulika', nature: 'inauspicious' },
306
+ { name: 'Mrityu', nature: 'inauspicious' },
307
+ { name: 'Kaala', nature: 'inauspicious' },
308
+ { name: 'Davagni', nature: 'inauspicious' },
309
+ { name: 'Ghora', nature: 'inauspicious' },
310
+ { name: 'Yama', nature: 'inauspicious' },
311
+ { name: 'Kantaka', nature: 'inauspicious' },
312
+ { name: 'Sudha', nature: 'auspicious' },
313
+ { name: 'Amrita', nature: 'auspicious' },
314
+ { name: 'Poornachandra', nature: 'auspicious' },
315
+ { name: 'Vishadagdha', nature: 'inauspicious' },
316
+ { name: 'Kulanasha', nature: 'inauspicious' },
317
+ { name: 'Vamshakshaya', nature: 'inauspicious' },
318
+ { name: 'Utpata', nature: 'inauspicious' },
319
+ { name: 'Kaala', nature: 'inauspicious' },
320
+ { name: 'Saumya', nature: 'auspicious' },
321
+ { name: 'Komala', nature: 'auspicious' },
322
+ { name: 'Sheetala', nature: 'auspicious' },
323
+ { name: 'Karala', nature: 'inauspicious' },
324
+ { name: 'Chandramukhi', nature: 'auspicious' },
325
+ { name: 'Praveena', nature: 'auspicious' },
326
+ { name: 'Kalagni', nature: 'inauspicious' },
327
+ { name: 'Dandayudha', nature: 'inauspicious' },
328
+ { name: 'Nirmala', nature: 'auspicious' },
329
+ { name: 'Saumya', nature: 'auspicious' },
330
+ { name: 'Kroora', nature: 'inauspicious' },
331
+ { name: 'Atisheetala', nature: 'auspicious' },
332
+ { name: 'Amrita', nature: 'auspicious' },
333
+ { name: 'Payodhi', nature: 'auspicious' },
334
+ { name: 'Brahma', nature: 'auspicious' },
335
+ { name: 'Chandrarekha', nature: 'auspicious' }
336
+ ];
337
+
338
+ module.exports = {
339
+ // Sign data
340
+ SIGNS,
341
+ SIGNS_SHORT,
342
+ SIGN_LORDS,
343
+
344
+ // Sign classifications
345
+ MOVABLE_SIGNS,
346
+ FIXED_SIGNS,
347
+ DUAL_SIGNS,
348
+ ODD_SIGNS,
349
+ EVEN_SIGNS,
350
+ FIRE_SIGNS,
351
+ EARTH_SIGNS,
352
+ AIR_SIGNS,
353
+ WATER_SIGNS,
354
+
355
+ // Varga definitions
356
+ VARGAS,
357
+
358
+ // Special varga data
359
+ TRIMSAMSHA_ODD,
360
+ TRIMSAMSHA_EVEN,
361
+ SHASHTYAMSHA_NAMES
362
+ };
@@ -0,0 +1,121 @@
1
+ /**
2
+ * Vargas Module - Divisional Charts for Vedic Astrology
3
+ *
4
+ * Implements all 21 standard divisional charts (D1-D60) following Parashari tradition
5
+ *
6
+ * Usage:
7
+ * const { vargas } = require('jyotish-calculations');
8
+ *
9
+ * // Calculate Navamsha (D9) for a planet at 45°
10
+ * const navamsha = vargas.calculateVarga(45, 'D9');
11
+ *
12
+ * // Calculate all vargas for a planet
13
+ * const allVargas = vargas.calculateAllVargas(45);
14
+ *
15
+ * // Calculate varga chart for multiple planets
16
+ * const chart = vargas.calculateVargaChart({
17
+ * Sun: 45.5,
18
+ * Moon: 120.3,
19
+ * Mars: 200.7
20
+ * }, 'D9');
21
+ */
22
+
23
+ const { SIGNS, SIGNS_SHORT, VARGAS, SHASHTYAMSHA_NAMES } = require('./constants');
24
+
25
+ const {
26
+ normalizeLongitude,
27
+ getSignIndex,
28
+ getDegreeInSign,
29
+ normalizeSign,
30
+ isOddSign,
31
+ isEvenSign,
32
+ isMovableSign,
33
+ isFixedSign,
34
+ isDualSign,
35
+ getSignQuality,
36
+ getElement,
37
+ getDivisionIndex
38
+ } = require('./utils');
39
+
40
+ const {
41
+ calculateVarga,
42
+ calculateAllVargas,
43
+ calculateVargaChart,
44
+ getVargaInfo,
45
+ getSupportedVargas,
46
+ calculateD1,
47
+ calculateD2,
48
+ calculateD3,
49
+ calculateD4,
50
+ calculateD5,
51
+ calculateD6,
52
+ calculateD7,
53
+ calculateD8,
54
+ calculateD9,
55
+ calculateD10,
56
+ calculateD11,
57
+ calculateD12,
58
+ calculateD16,
59
+ calculateD20,
60
+ calculateD24,
61
+ calculateD27,
62
+ calculateD30,
63
+ calculateD32,
64
+ calculateD40,
65
+ calculateD45,
66
+ calculateD60,
67
+ VARGA_CALCULATORS
68
+ } = require('./varga');
69
+
70
+ module.exports = {
71
+ // Main API
72
+ calculateVarga,
73
+ calculateAllVargas,
74
+ calculateVargaChart,
75
+ getVargaInfo,
76
+ getSupportedVargas,
77
+
78
+ // Individual varga calculators
79
+ calculateD1,
80
+ calculateD2,
81
+ calculateD3,
82
+ calculateD4,
83
+ calculateD5,
84
+ calculateD6,
85
+ calculateD7,
86
+ calculateD8,
87
+ calculateD9,
88
+ calculateD10,
89
+ calculateD11,
90
+ calculateD12,
91
+ calculateD16,
92
+ calculateD20,
93
+ calculateD24,
94
+ calculateD27,
95
+ calculateD30,
96
+ calculateD32,
97
+ calculateD40,
98
+ calculateD45,
99
+ calculateD60,
100
+
101
+ // Utility functions
102
+ normalizeLongitude,
103
+ getSignIndex,
104
+ getDegreeInSign,
105
+ normalizeSign,
106
+ isOddSign,
107
+ isEvenSign,
108
+ isMovableSign,
109
+ isFixedSign,
110
+ isDualSign,
111
+ getSignQuality,
112
+ getElement,
113
+ getDivisionIndex,
114
+
115
+ // Constants
116
+ SIGNS,
117
+ SIGNS_SHORT,
118
+ VARGAS,
119
+ SHASHTYAMSHA_NAMES,
120
+ VARGA_CALCULATORS
121
+ };
@@ -0,0 +1,196 @@
1
+ /**
2
+ * Varga Utility Functions
3
+ * Core helpers for divisional chart calculations
4
+ */
5
+
6
+ const {
7
+ MOVABLE_SIGNS,
8
+ FIXED_SIGNS,
9
+ DUAL_SIGNS,
10
+ ODD_SIGNS,
11
+ EVEN_SIGNS,
12
+ FIRE_SIGNS,
13
+ EARTH_SIGNS,
14
+ AIR_SIGNS,
15
+ WATER_SIGNS
16
+ } = require('./constants');
17
+
18
+ /**
19
+ * Normalize longitude to 0-360 range
20
+ * @param {number} longitude - Longitude in degrees
21
+ * @returns {number} Normalized longitude (0-360)
22
+ */
23
+ function normalizeLongitude(longitude) {
24
+ if (typeof longitude !== 'number' || isNaN(longitude)) {
25
+ throw new TypeError('Longitude must be a valid number');
26
+ }
27
+ return ((longitude % 360) + 360) % 360;
28
+ }
29
+
30
+ /**
31
+ * Get sign index (0-11) from longitude
32
+ * @param {number} longitude - Longitude in degrees (0-360)
33
+ * @returns {number} Sign index (0 = Aries, 11 = Pisces)
34
+ */
35
+ function getSignIndex(longitude) {
36
+ const normalized = normalizeLongitude(longitude);
37
+ return Math.floor(normalized / 30);
38
+ }
39
+
40
+ /**
41
+ * Get degree within sign (0-30)
42
+ * @param {number} longitude - Longitude in degrees
43
+ * @returns {number} Degree within the sign (0-30)
44
+ */
45
+ function getDegreeInSign(longitude) {
46
+ const normalized = normalizeLongitude(longitude);
47
+ return normalized % 30;
48
+ }
49
+
50
+ /**
51
+ * Normalize sign index to 0-11 range
52
+ * @param {number} signIndex - Sign index (can be any integer)
53
+ * @returns {number} Normalized sign index (0-11)
54
+ */
55
+ function normalizeSign(signIndex) {
56
+ return ((signIndex % 12) + 12) % 12;
57
+ }
58
+
59
+ /**
60
+ * Check if sign is odd (masculine)
61
+ * @param {number} signIndex - Sign index (0-11)
62
+ * @returns {boolean}
63
+ */
64
+ function isOddSign(signIndex) {
65
+ return ODD_SIGNS.includes(normalizeSign(signIndex));
66
+ }
67
+
68
+ /**
69
+ * Check if sign is even (feminine)
70
+ * @param {number} signIndex - Sign index (0-11)
71
+ * @returns {boolean}
72
+ */
73
+ function isEvenSign(signIndex) {
74
+ return EVEN_SIGNS.includes(normalizeSign(signIndex));
75
+ }
76
+
77
+ /**
78
+ * Check if sign is movable (chara)
79
+ * @param {number} signIndex - Sign index (0-11)
80
+ * @returns {boolean}
81
+ */
82
+ function isMovableSign(signIndex) {
83
+ return MOVABLE_SIGNS.includes(normalizeSign(signIndex));
84
+ }
85
+
86
+ /**
87
+ * Check if sign is fixed (sthira)
88
+ * @param {number} signIndex - Sign index (0-11)
89
+ * @returns {boolean}
90
+ */
91
+ function isFixedSign(signIndex) {
92
+ return FIXED_SIGNS.includes(normalizeSign(signIndex));
93
+ }
94
+
95
+ /**
96
+ * Check if sign is dual (dwiswabhava)
97
+ * @param {number} signIndex - Sign index (0-11)
98
+ * @returns {boolean}
99
+ */
100
+ function isDualSign(signIndex) {
101
+ return DUAL_SIGNS.includes(normalizeSign(signIndex));
102
+ }
103
+
104
+ /**
105
+ * Get sign quality (movable, fixed, or dual)
106
+ * @param {number} signIndex - Sign index (0-11)
107
+ * @returns {string} 'movable', 'fixed', or 'dual'
108
+ */
109
+ function getSignQuality(signIndex) {
110
+ const normalized = normalizeSign(signIndex);
111
+ if (MOVABLE_SIGNS.includes(normalized)) return 'movable';
112
+ if (FIXED_SIGNS.includes(normalized)) return 'fixed';
113
+ return 'dual';
114
+ }
115
+
116
+ /**
117
+ * Get element type for a sign
118
+ * @param {number} signIndex - Sign index (0-11)
119
+ * @returns {string} 'fire', 'earth', 'air', or 'water'
120
+ */
121
+ function getElement(signIndex) {
122
+ const normalized = normalizeSign(signIndex);
123
+ if (FIRE_SIGNS.includes(normalized)) return 'fire';
124
+ if (EARTH_SIGNS.includes(normalized)) return 'earth';
125
+ if (AIR_SIGNS.includes(normalized)) return 'air';
126
+ return 'water';
127
+ }
128
+
129
+ /**
130
+ * Check if sign is a fire sign
131
+ * @param {number} signIndex - Sign index (0-11)
132
+ * @returns {boolean}
133
+ */
134
+ function isFireSign(signIndex) {
135
+ return FIRE_SIGNS.includes(normalizeSign(signIndex));
136
+ }
137
+
138
+ /**
139
+ * Check if sign is an earth sign
140
+ * @param {number} signIndex - Sign index (0-11)
141
+ * @returns {boolean}
142
+ */
143
+ function isEarthSign(signIndex) {
144
+ return EARTH_SIGNS.includes(normalizeSign(signIndex));
145
+ }
146
+
147
+ /**
148
+ * Check if sign is an air sign
149
+ * @param {number} signIndex - Sign index (0-11)
150
+ * @returns {boolean}
151
+ */
152
+ function isAirSign(signIndex) {
153
+ return AIR_SIGNS.includes(normalizeSign(signIndex));
154
+ }
155
+
156
+ /**
157
+ * Check if sign is a water sign
158
+ * @param {number} signIndex - Sign index (0-11)
159
+ * @returns {boolean}
160
+ */
161
+ function isWaterSign(signIndex) {
162
+ return WATER_SIGNS.includes(normalizeSign(signIndex));
163
+ }
164
+
165
+ /**
166
+ * Calculate division index within a sign
167
+ * @param {number} degreeInSign - Degree within sign (0-30)
168
+ * @param {number} divisions - Number of divisions
169
+ * @returns {number} Division index (0 to divisions-1)
170
+ */
171
+ function getDivisionIndex(degreeInSign, divisions) {
172
+ const degreesPerDivision = 30 / divisions;
173
+ let divIndex = Math.floor(degreeInSign / degreesPerDivision);
174
+ // Handle edge case at exactly 30°
175
+ if (divIndex >= divisions) divIndex = divisions - 1;
176
+ return divIndex;
177
+ }
178
+
179
+ module.exports = {
180
+ normalizeLongitude,
181
+ getSignIndex,
182
+ getDegreeInSign,
183
+ normalizeSign,
184
+ isOddSign,
185
+ isEvenSign,
186
+ isMovableSign,
187
+ isFixedSign,
188
+ isDualSign,
189
+ getSignQuality,
190
+ getElement,
191
+ isFireSign,
192
+ isEarthSign,
193
+ isAirSign,
194
+ isWaterSign,
195
+ getDivisionIndex
196
+ };