astroceleste_engine/
lunar.rs1use serde::Serialize;
4
5use crate::catalog::{LunarMansion, LUNAR_MANSIONS};
6use crate::chart::Placement;
7use crate::pyfloat;
8
9#[derive(Debug, Clone, PartialEq, Serialize)]
11pub struct LunarStatus {
12 pub phase_key: &'static str,
14 pub phase_name: &'static str,
16 pub phase_quarter: u8,
18 pub glyph: &'static str,
20 pub elongation: f64,
22 pub illumination_percentage: f64,
24 pub moon_age_days: f64,
26 pub is_waxing: bool,
28 pub moon_sign: &'static str,
30 pub moon_degree: i64,
32 pub moon_minute: i64,
34 pub moon_longitude: f64,
36 pub moon_house: u8,
38 pub moon_speed: f64,
40 pub speed_status: &'static str,
42 pub essential_dignity: &'static str,
44 pub lunar_mansion: LunarMansion,
46}
47
48pub fn lunar_status(planets: &[Placement]) -> Option<LunarStatus> {
50 let moon = planets.iter().find(|p| p.name == "Moon")?;
51 let sun_lon = planets
52 .iter()
53 .find(|p| p.name == "Sun")
54 .map_or(0.0, |s| s.ecliptic_longitude);
55 let moon_lon = moon.ecliptic_longitude;
56 let diff = pyfloat::rem(moon_lon - sun_lon, 360.0);
57
58 let (phase_key, phase_name, phase_quarter, glyph) = if !(22.5..337.5).contains(&diff) {
59 ("new_moon", "New Moon", 1, "🌑")
60 } else if diff < 67.5 {
61 ("waxing_crescent", "Waxing Crescent", 1, "🌒")
62 } else if diff < 112.5 {
63 ("first_quarter", "First Quarter", 2, "🌓")
64 } else if diff < 157.5 {
65 ("waxing_gibbous", "Waxing Gibbous", 2, "🌔")
66 } else if diff < 202.5 {
67 ("full_moon", "Full Moon", 3, "🌕")
68 } else if diff < 247.5 {
69 ("waning_gibbous", "Waning Gibbous", 3, "🌖")
70 } else if diff < 292.5 {
71 ("third_quarter", "Third Quarter", 4, "🌗")
72 } else {
73 ("waning_crescent", "Waning Crescent", 4, "🌘")
74 };
75
76 let speed = moon.speed;
77 let mansion_index = ((moon_lon / (360.0 / 28.0)).floor() as i64).rem_euclid(28) as usize;
78 Some(LunarStatus {
79 phase_key,
80 phase_name,
81 phase_quarter,
82 glyph,
83 elongation: pyfloat::round(diff, 2),
84 illumination_percentage: pyfloat::round(((1.0 - diff.to_radians().cos()) / 2.0) * 100.0, 1),
85 moon_age_days: pyfloat::round((diff / 360.0) * 29.530588853, 1),
86 is_waxing: diff < 180.0,
87 moon_sign: moon.sign,
88 moon_degree: moon.degree,
89 moon_minute: moon.minute,
90 moon_longitude: pyfloat::round(moon_lon, 3),
91 moon_house: moon.house,
92 moon_speed: pyfloat::round(speed, 2),
93 speed_status: if speed > 13.5 {
94 "swift"
95 } else if speed < 12.5 {
96 "slow"
97 } else {
98 "average"
99 },
100 essential_dignity: match moon.sign {
101 "Cancer" => "Domicile",
102 "Taurus" => "Exaltation",
103 "Capricorn" => "Detriment",
104 "Scorpio" => "Fall",
105 _ => "Peregrine",
106 },
107 lunar_mansion: LUNAR_MANSIONS[mansion_index],
108 })
109}