qualia_client_core/wellfair/
physiology_prefs.rs1use std::path::Path;
14
15use wellfare_core::anatomy::PhysiologicalState;
16
17pub const PHYSIOLOGY_STATE_FILE: &str = "wellfair/physiology_state.json";
19
20pub fn load(storage_root: impl AsRef<Path>) -> Option<PhysiologicalState> {
23 let path = storage_root.as_ref().join(PHYSIOLOGY_STATE_FILE);
24 let bytes = std::fs::read(path).ok()?;
25 serde_json::from_slice(&bytes).ok()
26}
27
28pub fn save(storage_root: impl AsRef<Path>, state: &PhysiologicalState) -> Result<(), String> {
31 let path = storage_root.as_ref().join(PHYSIOLOGY_STATE_FILE);
32 if let Some(parent) = path.parent() {
33 std::fs::create_dir_all(parent).map_err(|e| e.to_string())?;
34 }
35 let bytes = serde_json::to_vec_pretty(state).map_err(|e| e.to_string())?;
36 std::fs::write(path, bytes).map_err(|e| e.to_string())
37}
38
39pub fn clear(storage_root: impl AsRef<Path>) -> Result<(), String> {
42 let path = storage_root.as_ref().join(PHYSIOLOGY_STATE_FILE);
43 match std::fs::remove_file(path) {
44 Ok(()) => Ok(()),
45 Err(e) if e.kind() == std::io::ErrorKind::NotFound => Ok(()),
46 Err(e) => Err(e.to_string()),
47 }
48}
49
50#[cfg(test)]
51mod tests {
52 use super::*;
53 use wellfare_core::anatomy::{CyclePhase, PhysiologicalState, ReproductiveState, Trimester};
54
55 #[test]
56 fn declared_state_persists_and_clears_back_to_none() {
57 let dir = tempfile::tempdir().unwrap();
58 assert!(load(dir.path()).is_none());
60
61 let state = PhysiologicalState::Reproductive(ReproductiveState::Pregnant(Trimester::Third));
63 save(dir.path(), &state).unwrap();
64 assert_eq!(
65 load(dir.path()),
66 Some(state),
67 "the person's state is theirs, persisted"
68 );
69
70 clear(dir.path()).unwrap();
72 assert!(
73 load(dir.path()).is_none(),
74 "cleared returns to the implicit baseline"
75 );
76 }
77
78 #[test]
79 fn every_continuum_state_round_trips_through_serde() {
80 let states = vec![
81 PhysiologicalState::Baseline,
82 PhysiologicalState::Reproductive(ReproductiveState::PreMenarche),
83 PhysiologicalState::Reproductive(ReproductiveState::Cycling(CyclePhase::Menstrual)),
84 PhysiologicalState::Reproductive(ReproductiveState::Cycling(CyclePhase::Follicular)),
85 PhysiologicalState::Reproductive(ReproductiveState::Cycling(CyclePhase::Ovulatory)),
86 PhysiologicalState::Reproductive(ReproductiveState::Cycling(CyclePhase::Luteal)),
87 PhysiologicalState::Reproductive(ReproductiveState::Pregnant(Trimester::First)),
88 PhysiologicalState::Reproductive(ReproductiveState::Pregnant(Trimester::Second)),
89 PhysiologicalState::Reproductive(ReproductiveState::Pregnant(Trimester::Third)),
90 PhysiologicalState::Reproductive(ReproductiveState::Postpartum),
91 PhysiologicalState::Reproductive(ReproductiveState::Lactating),
92 PhysiologicalState::Reproductive(ReproductiveState::Perimenopause),
93 PhysiologicalState::Reproductive(ReproductiveState::PostMenopause),
94 ];
95 let dir = tempfile::tempdir().unwrap();
96 for state in &states {
97 save(dir.path(), state).unwrap();
98 let loaded = load(dir.path()).unwrap();
99 assert_eq!(&loaded, state, "round-trip failed for {:?}", state);
100 }
101 }
102}