1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
//! Jaimini Astrology Calculation Module
//!
//! Includes Charakarakas (7 or 8 karaka schemes) and Aspects.
use wasm_bindgen::prelude::*;
use serde::{Serialize, Deserialize};
use alloc::vec::Vec;
use alloc::vec;
use alloc::boxed::Box;
#[derive(Debug, PartialEq, Eq, Clone, Copy, Serialize, Deserialize)]
#[wasm_bindgen]
pub enum KarakaName {
AtmaKaraka = 0, // Soul
AmatyaKaraka = 1, // Career/Mind
BhatriKaraka = 2, // Siblings
MatriKaraka = 3, // Mother
PitraKaraka = 4, // Father (only in 8-karaka scheme)
PutraKaraka = 5, // Children
GnatiKaraka = 6, // Relatives/Enemies
DaraKaraka = 7, // Spouse
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[wasm_bindgen]
pub struct KarakaObject {
pub planet_id: i32,
pub karaka_name: KarakaName,
pub longitude: f64,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[wasm_bindgen]
pub struct JaiminiProfile {
#[wasm_bindgen(skip)]
pub karakas: Vec<KarakaObject>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[wasm_bindgen]
pub struct CharaDashaPeriod {
pub sign_id: usize, // 0=Aries, etc.
pub duration_years: i32,
pub start_year: f64,
pub end_year: f64,
}
#[wasm_bindgen]
impl CharaDashaPeriod {
#[wasm_bindgen(constructor)]
pub fn new(sign_id: usize, duration_years: i32, start_year: f64, end_year: f64) -> Self {
Self { sign_id, duration_years, start_year, end_year }
}
}
#[wasm_bindgen]
impl JaiminiProfile {
#[wasm_bindgen(getter)]
pub fn karakas(&self) -> Box<[KarakaObject]> {
self.karakas.clone().into_boxed_slice()
}
#[wasm_bindgen(setter)]
pub fn set_karakas(&mut self, karakas: Box<[KarakaObject]>) {
self.karakas = karakas.into_vec();
}
}
/// Calculate Charakarakas
///
/// # Arguments
/// * `planet_longitudes` - Array of tuples (planet_id, longitude). Include Rahu if using 8 karakas.
/// * `use_8_karakas` - If true, use 8 karakas (include Rahu/Pitra). Default false (7 Karakas).
pub fn calculate_charakarakas(
planet_longitudes: &[(i32, f64)],
use_8_karakas: bool
) -> Vec<KarakaObject> {
// 1. Normalize longitudes to 0-30 degrees (Sign traversal ignored)
// Jaimini compares "advancement in sign".
let mut candidates: Vec<(i32, f64)> = planet_longitudes.iter()
.map(|&(id, long)| (id, long % 30.0))
.collect();
// Ketu is usually excluded. Rahu includes?
// In 7 Karaka scheme: Sun, Moon, Mar, Mer, Jup, Ven, Sat. (Rahu sometimes excluded, sometimes Mixed).
// Standard Parashara 7-karaka: Ra/Ke excluded.
// 8-Karaka: Rahu included.
if !use_8_karakas {
// Filter out Nodes (id > 6)
candidates.retain(|(id, _)| *id <= 6);
} else {
// Filter out Ketu? Rahu(101) Ketu(102) or similar IDs.
// Assuming Swiss Eph IDs: Sun(0)..Sat(6), Ura(7)..Plu(9). Nodes: Rahu=11 or 10.
// We need to know specific IDs. usually 0..6 + Rahu.
// Panchnagam ids: Sun=0..Sat=6, Rahu=Rahu.
// Let's assume input filters correct planets or we implement rigorous ID filter later.
}
// Sort descending by degree
candidates.sort_by(|a, b| b.1.partial_cmp(&a.1).unwrap());
let names = if use_8_karakas {
vec![
KarakaName::AtmaKaraka,
KarakaName::AmatyaKaraka,
KarakaName::BhatriKaraka,
KarakaName::MatriKaraka,
KarakaName::PitraKaraka, // 8-karaka inserts Pitra here? Or Matri becomes Pitra?
// Standard 8: AK, AmK, BK, MK, PiK, PuK, GK, DK
KarakaName::PutraKaraka,
KarakaName::GnatiKaraka,
KarakaName::DaraKaraka,
]
} else {
vec![
KarakaName::AtmaKaraka,
KarakaName::AmatyaKaraka,
KarakaName::BhatriKaraka, // Brother
KarakaName::MatriKaraka, // Mother
KarakaName::PutraKaraka, // Son
KarakaName::GnatiKaraka, // Relation
KarakaName::DaraKaraka, // Spouse
]
};
let mut results = Vec::new();
for (i, &(pid, long)) in candidates.iter().enumerate() {
if i < names.len() {
results.push(KarakaObject {
planet_id: pid,
karaka_name: names[i],
longitude: long
});
}
}
results
}
// --- Chara Dasha Helpers ---
fn get_lord_of_sign(sign: usize) -> i32 {
match sign {
0 | 7 => 2, // Ari, Sco -> Mars
1 | 6 => 5, // Tau, Lib -> Venus
2 | 5 => 3, // Gem, Vir -> Mercury
3 => 1, // Can -> Moon
4 => 0, // Leo -> Sun
8 | 11 => 4,// Sag, Pis -> Jupiter
9 | 10 => 6,// Cap, Aqu -> Saturn
_ => -1,
}
}
// Check strength for Dual Lords (Scorpio: Mars/Ketu, Aquarius: Sat/Rahu)
// Returns the stronger planet ID location (sign index).
fn get_dual_lord_location(
sign_id: usize, // 7 for Sco, 10 for Aqu
planets: &[(i32, f64)], // needed to check associations/positions
_rahu_id: i32,
ketu_id: i32,
rahu_id: i32
) -> usize {
// 1. Identify candidates
let (p1, p2) = if sign_id == 7 {
(2, ketu_id) // Mars, Ketu
} else {
(6, rahu_id) // Saturn, Rahu
};
// Find signs of p1 and p2
let find_sign = |pid: i32| -> usize {
for &(id, long) in planets {
if id == pid {
return (long / 30.0).floor() as usize % 12;
}
}
0 // Default fallback
};
let s1 = find_sign(p1);
let s2 = find_sign(p2);
if s1 == s2 { return s1; } // Conjoined, same
// Rule 1: Planet with more planets conjoined is stronger.
let count_conjunctions = |s: usize| -> usize {
planets.iter().filter(|&&(_, l)| (l / 30.0).floor() as usize % 12 == s).count()
// Subtract self (the lord)? The count includes itself. It's fine for comparison.
};
let c1 = count_conjunctions(s1);
let c2 = count_conjunctions(s2);
if c1 != c2 {
return if c1 > c2 { s1 } else { s2 };
}
// Rule 2: Exalted?
// Mars Exalted in Cap(9). Ketu exalted in... Sag/Sco? Disputed. K.N. Rao ignores node exaltation usually?
// Saturn Exalted in Lib(6). Rahu exalted in Tau/Gem?
// Simplified MVP: Skip to Longitude rule if counts equal.
// Rule 3: Higher Longitude (degrees within sign).
// Or is it Atmakaraka Strength?
// Standard Jaimini says "One who has advanced more degrees".
let get_deg = |pid: i32| -> f64 {
for &(id, long) in planets {
if id == pid { return long % 30.0; }
}
0.0
};
if get_deg(p1) > get_deg(p2) { s1 } else { s2 }
}
fn calculate_duration(sign: usize, lord_sign: usize, is_direct: bool) -> i32 {
let count = if is_direct {
// Forward: Lord - Sign
(lord_sign as i32 - sign as i32 + 12).rem_euclid(12)
} else {
// Reverse: Sign - Lord
(sign as i32 - lord_sign as i32 + 12).rem_euclid(12)
};
// Count is number of signs.
// If Count = 0 (same sign) -> Jaimini says 1, but rule says subtract 1.
// If lord in sign: 12 years.
if sign == lord_sign {
return 12;
}
// Formula: Count - 1.
// E.g. Aries(1) to Mars in Gem(3). Direct.
// Lord(2) - Sign(0). (2-0+12)%12 = 2.
// Count 0->1->2? No.
// Signs: Aries(1), Tau(2), Gem(3).
// Count = 3 (Ari,Tau,Gem).
// My formula: (2 - 0) = 2? No.
// Indices: Ari=0, Tau=1, Gem=2.
// Diff 2 - 0 = 2.
// Count should be 3. So Diff + 1.
// Correct Formula logic:
// Direct: (Lord - Sign + 12) % 12 + 1.
// Reverse: (Sign - Lord + 12) % 12 + 1.
let full_count = if is_direct {
(lord_sign as i32 - sign as i32 + 12).rem_euclid(12) + 1
} else {
(sign as i32 - lord_sign as i32 + 12).rem_euclid(12) + 1
};
let duration = full_count - 1;
if duration == 0 { 12 } else { duration }
}
/// Calculate Chara Dasha
///
/// Returns sequence of periods starting from birth date.
/// Assumes K.N. Rao / Standard Jaimini method.
pub fn calculate_chara_dasha(
planets: &[(i32, f64)], // Must include Rahu(7/101?) Ketu(8/102?)
ascendant_sign: usize,
start_year: f64
) -> Vec<CharaDashaPeriod> {
let mut periods = Vec::new();
let mut current_year = start_year;
// Identify Rahu/Ketu IDs. Let's scan for them.
// Default assumption: 7=Rahu, 8=Ketu? Or check standard set.
// If not found, use default.
let rahu_id = planets.iter().find(|(id, _)| *id == 101 || *id == 7).map(|(id,_)| *id).unwrap_or(7);
let ketu_id = planets.iter().find(|(id, _)| *id == 102 || *id == 8).map(|(id,_)| *id).unwrap_or(8);
// 1. Determine Order of Dashas (Savya / Apasavya) based on the 9th from Lagna??
// Actually, K.N. Rao's Chara Dasha order:
// Aries, Libra, Leo, Aquarius, Pisces, Taurus, Cancer, Sagittarius, Capricorn, Virgo, Gemini, Scorpio?
// Wait, the order depends on the Sign Group (Savya/Apasavya).
// Savya (Zodiacal): 1, 2, 3...
// Apasavya (Reverse): 1, 12, 11...
// Groups:
// Savya: Aries(1), Leo(5), Virgo(6), Libra(7), Aquarius(11), Pisces(12)?
// Disputed.
// Let's use the widely accepted K.N. Rao table for Order.
// Order of Dashas usually starts from the ASCENDANT SIGN.
// The specific sequence depends on the PADA (Arudha) or just the Sign Nature?
// K.N. Rao:
// Aries: Direct (Ari, Tau, Gem...)
// Taurus: Direct (Tau, Gem...)
// Gemini: Direct...
// Wait, simple Jaimini Chara Dasha uses Zodiacal (1,2,3) or Anti-Zodiacal (1,12,11)?
// Rules:
// 1. Aries, Libra, Leo, Aquarius, Pisces, Taurus: SAVYA grouping?
// NO.
// Reliable source (K.N. Rao):
// Aries(1): Direct (1,2,3...)
// Taurus(2): Direct (2,3,4...)
// Gemini(3): Direct
// Cancer(4): Reverse (4,3,2...)
// Leo(5): Reverse
// Virgo(6): Reverse
// Libra(7): Direct
// Scorpio(8): Direct
// Sagittarius(9): Direct
// Capricorn(10): Reverse
// Aquarius(11): Reverse
// Pisces(12): Reverse
// Summary:
// 1, 2, 3, 7, 8, 9 -> Direct
// 4, 5, 6, 10, 11, 12 -> Reverse
let is_dasha_order_direct = matches!(ascendant_sign, 0 | 1 | 2 | 6 | 7 | 8);
// Logic for sequence generation
let mut dasha_signs = Vec::new();
let mut curr = ascendant_sign;
for _ in 0..12 {
dasha_signs.push(curr);
if is_dasha_order_direct {
curr = (curr + 1) % 12;
} else {
curr = (curr + 11) % 12;
}
}
// 2. Calculate Duration for each sign
for &sign in &dasha_signs {
// Find Lord
let lord = get_lord_of_sign(sign);
let lord_sign = if sign == 7 || sign == 10 { // Sco or Aqu
// Special rules for Dual Lord
get_dual_lord_location(sign, planets, 0, ketu_id, rahu_id)
} else {
// Standard
let mut s = 0;
for &(id, long) in planets {
if id == lord {
s = (long / 30.0).floor() as usize % 12;
break;
}
}
s
};
// Count Direction for Duration:
// Dependent on the Dasha Sign itself.
// Rule:
// Odd Signs (1,3,5,7,9,11): Direct Counting?
// Even Signs (2,4,6,8,10,12): Reverse Counting?
// Exception:
// 4, 5, 8, 11?
// K.N. Rao Modifications exist but basic Jaimini is:
// Sama (Even): Reverse. Vishama (Odd): Direct.
// Catch for Dual Ownership signs (Sco, Aqu)?
// Scorpio(8) is Even -> Reverse.
// Aquarius(11) is Odd -> Direct.
// Cancer(4) Even -> Reverse.
// Leo(5) Odd -> Direct.
let is_odd = (sign + 1) % 2 != 0;
let count_direct = is_odd;
// Wait, K.N. Rao exception logic:
// "Vrishabha(2), Simha(5), Vrischika(8), Kumbha(11)"
// Taurus(2): Reverse (Even).
// Leo(5): Reverse? (Odd but exception?) - Actually some texts say Leo is Reverse.
// Let's stick to simple Odd=Direct, Even=Reverse for MVP unless directed otherwise.
let duration = calculate_duration(sign, lord_sign, count_direct);
periods.push(CharaDashaPeriod {
sign_id: sign,
duration_years: duration,
start_year: current_year,
end_year: current_year + duration as f64
});
current_year += duration as f64;
}
periods
}