astroceleste_engine/election/
mod.rs1use serde::{Deserialize, Serialize};
13use serde_json::Value;
14
15use crate::almanac::SunEvents;
16use crate::chart::{chart_without_hours, placements, sky, Chart, ChartRequest, Placement};
17use crate::degree_qualities::degree_qualities;
18use crate::dignities::{is_debilitated, is_dignified, sign_index, solar_phase};
19use crate::ephemeris::KernelSet;
20use crate::error::EngineError;
21use crate::horary::{
22 hours_in, jd_utc, moon_status, planetary_hours, solar_day, traditional_ruler, MoonStatus,
23 PlanetaryHours, SolarDay, SIGNS,
24};
25use crate::houses::{houses_at_sidereal_time, HouseSystem};
26use crate::instant::UtcInstant;
27use crate::planets::{planets_and_sidereal_time, require_kernel, BodyPosition, NodeType};
28use crate::pyfloat;
29use crate::zodiac::{ayanamsa, ayanamsa_info};
30
31mod assess;
32mod criteria;
33mod search;
34#[cfg(test)]
35mod tests;
36
37use assess::*;
38pub use criteria::*;
39pub use search::search_elections;
40
41pub const MAX_SEARCH_DAYS: f64 = 92.0;
43const FAVOURABLE: f64 = 65.0;
45const MIXED: f64 = 45.0;
47const NATAL_ORB: f64 = 3.0;
49const SOLAR_DAY_CACHE_MAX_LAT: f64 = 60.0;
52
53const BENEFICS: [&str; 2] = ["Venus", "Jupiter"];
54const MALEFICS: [&str; 2] = ["Mars", "Saturn"];
55
56#[derive(Debug, Clone, PartialEq, Serialize)]
58pub struct ElectionFactor {
59 pub code: &'static str,
61 pub weight: f64,
63 pub planet: Option<&'static str>,
65 pub target: Option<String>,
67 pub aspect: Option<&'static str>,
69 pub house: Option<u8>,
71 pub sign: Option<&'static str>,
73}
74
75impl ElectionFactor {
76 fn new(code: &'static str, weight: f64) -> Self {
77 ElectionFactor {
78 code,
79 weight,
80 planet: None,
81 target: None,
82 aspect: None,
83 house: None,
84 sign: None,
85 }
86 }
87 fn planet(mut self, planet: &'static str) -> Self {
88 self.planet = Some(planet);
89 self
90 }
91 fn target(mut self, target: impl Into<String>) -> Self {
92 self.target = Some(target.into());
93 self
94 }
95 fn aspect(mut self, aspect: &'static str) -> Self {
96 self.aspect = Some(aspect);
97 self
98 }
99 fn house(mut self, house: u8) -> Self {
100 self.house = Some(house);
101 self
102 }
103 fn sign(mut self, sign: &'static str) -> Self {
104 self.sign = Some(sign);
105 self
106 }
107}
108
109#[derive(Debug, Clone, PartialEq, Serialize)]
111pub struct ElectionData {
112 pub score: f64,
114 pub verdict: &'static str,
116 pub purpose: Purpose,
118 pub factors: Vec<ElectionFactor>,
121 pub excluded_by: Vec<&'static str>,
124 pub planetary_hours: PlanetaryHours,
126 pub moon_status: MoonStatus,
128}
129
130#[derive(Debug, Clone, PartialEq, Serialize)]
132pub struct ElectionChart {
133 #[serde(flatten)]
135 pub chart: Chart,
136 pub election_data: ElectionData,
138}
139
140#[derive(Debug, Clone, PartialEq, Serialize)]
142pub struct ElectionWindow {
143 pub start: String,
145 pub end: String,
147 pub best: String,
149 pub score: f64,
151 pub verdict: &'static str,
153 pub factors: Vec<ElectionFactor>,
155}
156
157#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize)]
159pub struct Exclusions {
160 pub void_moon: u32,
162 pub mercury_retrograde: u32,
164 pub venus_retrograde: u32,
166 pub night: u32,
168 pub outside_hours: u32,
170}
171
172impl Exclusions {
173 fn count(&mut self, reason: &str) {
174 match reason {
175 "void_moon" => self.void_moon += 1,
176 "mercury_retrograde" => self.mercury_retrograde += 1,
177 "venus_retrograde" => self.venus_retrograde += 1,
178 "night" => self.night += 1,
179 _ => self.outside_hours += 1,
180 }
181 }
182}
183
184#[derive(Debug, Clone, PartialEq, Serialize)]
186pub struct ElectionSearch {
187 pub start: String,
189 pub end: String,
191 pub windows: Vec<ElectionWindow>,
193 pub evaluated: u32,
195 pub excluded: Exclusions,
197 pub criteria: CriteriaSummary,
199}
200
201pub fn calculate_election_chart(
203 kernels: &KernelSet,
204 req: &ChartRequest,
205 criteria: &ElectionCriteria,
206) -> Result<ElectionChart, EngineError> {
207 let resolved = criteria.resolve()?;
208 let mut chart = chart_without_hours(kernels, req)?;
209 let hours = planetary_hours(kernels, req.instant, req.latitude, req.longitude);
210 chart.planetary_hours = Some(hours.clone());
211 let cusps: Vec<f64> = chart.houses.iter().map(|h| h.ecliptic_longitude).collect();
212 let election_data = assess(
213 &Moment {
214 instant: req.instant,
215 planets: &chart.planets,
216 cusps: &cusps,
217 hours: &hours,
218 },
219 &resolved,
220 )?;
221 Ok(ElectionChart {
222 chart,
223 election_data,
224 })
225}