use serde::{Deserialize, Serialize};
use serde_json::Value;
use crate::almanac::SunEvents;
use crate::chart::{chart_without_hours, placements, sky, Chart, ChartRequest, Placement};
use crate::degree_qualities::degree_qualities;
use crate::dignities::{is_debilitated, is_dignified, sign_index, solar_phase};
use crate::ephemeris::KernelSet;
use crate::error::EngineError;
use crate::horary::{
hours_in, jd_utc, moon_status, planetary_hours, solar_day, traditional_ruler, MoonStatus,
PlanetaryHours, SolarDay, SIGNS,
};
use crate::houses::{houses_at_sidereal_time, HouseSystem};
use crate::instant::UtcInstant;
use crate::planets::{planets_and_sidereal_time, require_kernel, BodyPosition, NodeType};
use crate::pyfloat;
use crate::zodiac::{ayanamsa, ayanamsa_info};
mod assess;
mod criteria;
mod search;
#[cfg(test)]
mod tests;
use assess::*;
pub use criteria::*;
pub use search::search_elections;
pub const MAX_SEARCH_DAYS: f64 = 92.0;
const FAVOURABLE: f64 = 65.0;
const MIXED: f64 = 45.0;
const NATAL_ORB: f64 = 3.0;
const SOLAR_DAY_CACHE_MAX_LAT: f64 = 60.0;
const BENEFICS: [&str; 2] = ["Venus", "Jupiter"];
const MALEFICS: [&str; 2] = ["Mars", "Saturn"];
#[derive(Debug, Clone, PartialEq, Serialize)]
pub struct ElectionFactor {
pub code: &'static str,
pub weight: f64,
pub planet: Option<&'static str>,
pub target: Option<String>,
pub aspect: Option<&'static str>,
pub house: Option<u8>,
pub sign: Option<&'static str>,
}
impl ElectionFactor {
fn new(code: &'static str, weight: f64) -> Self {
ElectionFactor {
code,
weight,
planet: None,
target: None,
aspect: None,
house: None,
sign: None,
}
}
fn planet(mut self, planet: &'static str) -> Self {
self.planet = Some(planet);
self
}
fn target(mut self, target: impl Into<String>) -> Self {
self.target = Some(target.into());
self
}
fn aspect(mut self, aspect: &'static str) -> Self {
self.aspect = Some(aspect);
self
}
fn house(mut self, house: u8) -> Self {
self.house = Some(house);
self
}
fn sign(mut self, sign: &'static str) -> Self {
self.sign = Some(sign);
self
}
}
#[derive(Debug, Clone, PartialEq, Serialize)]
pub struct ElectionData {
pub score: f64,
pub verdict: &'static str,
pub purpose: Purpose,
pub factors: Vec<ElectionFactor>,
pub excluded_by: Vec<&'static str>,
pub planetary_hours: PlanetaryHours,
pub moon_status: MoonStatus,
}
#[derive(Debug, Clone, PartialEq, Serialize)]
pub struct ElectionChart {
#[serde(flatten)]
pub chart: Chart,
pub election_data: ElectionData,
}
#[derive(Debug, Clone, PartialEq, Serialize)]
pub struct ElectionWindow {
pub start: String,
pub end: String,
pub best: String,
pub score: f64,
pub verdict: &'static str,
pub factors: Vec<ElectionFactor>,
}
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize)]
pub struct Exclusions {
pub void_moon: u32,
pub mercury_retrograde: u32,
pub venus_retrograde: u32,
pub night: u32,
pub outside_hours: u32,
}
impl Exclusions {
fn count(&mut self, reason: &str) {
match reason {
"void_moon" => self.void_moon += 1,
"mercury_retrograde" => self.mercury_retrograde += 1,
"venus_retrograde" => self.venus_retrograde += 1,
"night" => self.night += 1,
_ => self.outside_hours += 1,
}
}
}
#[derive(Debug, Clone, PartialEq, Serialize)]
pub struct ElectionSearch {
pub start: String,
pub end: String,
pub windows: Vec<ElectionWindow>,
pub evaluated: u32,
pub excluded: Exclusions,
pub criteria: CriteriaSummary,
}
pub fn calculate_election_chart(
kernels: &KernelSet,
req: &ChartRequest,
criteria: &ElectionCriteria,
) -> Result<ElectionChart, EngineError> {
let resolved = criteria.resolve()?;
let mut chart = chart_without_hours(kernels, req)?;
let hours = planetary_hours(kernels, req.instant, req.latitude, req.longitude);
chart.planetary_hours = Some(hours.clone());
let cusps: Vec<f64> = chart.houses.iter().map(|h| h.ecliptic_longitude).collect();
let election_data = assess(
&Moment {
instant: req.instant,
planets: &chart.planets,
cusps: &cusps,
hours: &hours,
},
&resolved,
)?;
Ok(ElectionChart {
chart,
election_data,
})
}