Skip to main content

amlich_core/
insight_data.rs

1use serde::Deserialize;
2use std::collections::HashMap;
3use std::sync::OnceLock;
4
5const CANCHI_JSON: &str = include_str!("../data/canchi.json");
6const TIET_KHI_JSON: &str = include_str!("../data/tiet-khi.json");
7const TRUC_INSIGHT_JSON: &str = include_str!("../data/truc-insight.json");
8const DAY_DEITY_INSIGHT_JSON: &str = include_str!("../data/day-deity-insight.json");
9const NA_AM_INSIGHT_JSON: &str = include_str!("../data/na-am-insight.json");
10const TEN_GODS_INSIGHT_JSON: &str = include_str!("../data/ten-gods-insight.json");
11const TU_MENH_INSIGHT_JSON: &str = include_str!("../data/tu-menh-insight.json");
12const DAI_VAN_INSIGHT_JSON: &str = include_str!("../data/dai-van-insight.json");
13
14#[derive(Debug, Deserialize, Clone)]
15pub struct BilingualText {
16    pub vi: String,
17    pub en: String,
18}
19
20#[derive(Debug, Deserialize, Clone)]
21pub struct BilingualList {
22    pub vi: Vec<String>,
23    pub en: Vec<String>,
24}
25
26#[derive(Debug, Deserialize, Clone)]
27pub struct CanInfo {
28    pub name: String,
29    pub element: String,
30    pub meaning: BilingualText,
31    pub nature: BilingualText,
32}
33
34#[derive(Debug, Deserialize, Clone)]
35pub struct ChiInfo {
36    pub name: String,
37    pub animal: BilingualText,
38    pub element: String,
39    pub meaning: BilingualText,
40    pub hours: String,
41}
42
43#[derive(Debug, Deserialize, Clone)]
44pub struct ElementInfo {
45    pub name: BilingualText,
46    pub nature: BilingualText,
47}
48
49#[derive(Debug, Deserialize, Clone)]
50#[serde(rename_all = "camelCase")]
51pub struct DayGuidance {
52    pub good_for: BilingualList,
53    pub avoid_for: BilingualList,
54}
55
56#[derive(Debug, Deserialize)]
57#[serde(rename_all = "camelCase")]
58struct CanChiFile {
59    can: Vec<CanInfo>,
60    chi: Vec<ChiInfo>,
61    elements: HashMap<String, ElementInfo>,
62    day_guidance: HashMap<String, DayGuidance>,
63}
64
65#[derive(Debug, Deserialize, Clone)]
66pub struct TietKhiInsight {
67    pub id: String,
68    pub name: BilingualText,
69    pub longitude: i32,
70    pub meaning: BilingualText,
71    pub astronomy: BilingualText,
72    pub agriculture: BilingualList,
73    pub health: BilingualList,
74    pub weather: BilingualText,
75}
76
77#[derive(Debug, Deserialize)]
78#[serde(rename_all = "camelCase")]
79struct TietKhiFile {
80    tiet_khi: Vec<TietKhiInsight>,
81}
82
83#[derive(Debug, Clone, Deserialize)]
84pub struct TrucInsight {
85    pub id: String,
86    pub meaning: BilingualText,
87    pub good_for: BilingualList,
88    pub avoid_for: BilingualList,
89}
90
91#[derive(Debug, Deserialize)]
92struct TrucInsightFile {
93    truc: Vec<TrucInsight>,
94}
95
96#[derive(Debug, Clone, Deserialize)]
97pub struct DeityClassificationInsight {
98    pub id: String,
99    pub name: BilingualText,
100    pub meaning: BilingualText,
101}
102
103#[derive(Debug, Clone, Deserialize)]
104pub struct DeityInsight {
105    pub name: String,
106    pub classification: String,
107    pub meaning: BilingualText,
108}
109
110#[derive(Debug, Deserialize)]
111struct DayDeityInsightFile {
112    classifications: Vec<DeityClassificationInsight>,
113    deities: Vec<DeityInsight>,
114}
115
116#[derive(Debug, Clone, Deserialize)]
117pub struct NaAmInsight {
118    pub na_am: String,
119    pub element: String,
120    pub meaning: BilingualText,
121}
122
123#[derive(Debug, Deserialize)]
124struct NaAmInsightFile {
125    pairs: Vec<NaAmInsight>,
126}
127
128#[derive(Debug, Clone, Deserialize)]
129pub struct TenGodsInsight {
130    pub id: String,
131    pub name: BilingualText,
132    pub meaning: BilingualText,
133}
134
135#[derive(Debug, Deserialize)]
136struct TenGodsInsightFile {
137    gods: Vec<TenGodsInsight>,
138}
139
140#[derive(Debug, Clone, Deserialize)]
141pub struct KuaGroupInsight {
142    pub id: String,
143    pub name: BilingualText,
144    pub meaning: BilingualText,
145}
146
147#[derive(Debug, Clone, Deserialize)]
148pub struct KuaInsight {
149    pub number: u8,
150    pub trigram: BilingualText,
151    pub direction: BilingualText,
152    pub meaning: BilingualText,
153}
154
155#[derive(Debug, Deserialize)]
156struct TuMenhInsightFile {
157    groups: Vec<KuaGroupInsight>,
158    kua: Vec<KuaInsight>,
159}
160
161#[derive(Debug, Clone, Deserialize)]
162pub struct DaiVanDirectionInsight {
163    pub id: String,
164    pub name: BilingualText,
165    pub meaning: BilingualText,
166}
167
168#[derive(Debug, Clone, Deserialize)]
169pub struct DaiVanPhasesInsight {
170    pub meaning: BilingualText,
171}
172
173#[derive(Debug, Clone, Deserialize)]
174pub struct DaiVanElementInsight {
175    pub element: String,
176    pub meaning: BilingualText,
177}
178
179#[derive(Debug, Deserialize)]
180struct DaiVanInsightFile {
181    directions: Vec<DaiVanDirectionInsight>,
182    phases: DaiVanPhasesInsight,
183    elements: Vec<DaiVanElementInsight>,
184}
185
186static CANCHI_DATA: OnceLock<CanChiFile> = OnceLock::new();
187static TIET_KHI_DATA: OnceLock<TietKhiFile> = OnceLock::new();
188static TRUC_INSIGHT_DATA: OnceLock<Vec<TrucInsight>> = OnceLock::new();
189static DAY_DEITY_INSIGHT_DATA: OnceLock<DayDeityInsightFile> = OnceLock::new();
190static NA_AM_INSIGHT_DATA: OnceLock<Vec<NaAmInsight>> = OnceLock::new();
191static TEN_GODS_INSIGHT_DATA: OnceLock<Vec<TenGodsInsight>> = OnceLock::new();
192static TU_MENH_INSIGHT_DATA: OnceLock<TuMenhInsightFile> = OnceLock::new();
193static DAI_VAN_INSIGHT_DATA: OnceLock<DaiVanInsightFile> = OnceLock::new();
194
195fn canchi_data() -> &'static CanChiFile {
196    CANCHI_DATA.get_or_init(|| {
197        serde_json::from_str(CANCHI_JSON).expect("Failed to parse data/canchi.json")
198    })
199}
200
201fn tiet_khi_data() -> &'static TietKhiFile {
202    TIET_KHI_DATA.get_or_init(|| {
203        serde_json::from_str(TIET_KHI_JSON).expect("Failed to parse data/tiet-khi.json")
204    })
205}
206
207pub fn all_can() -> &'static [CanInfo] {
208    &canchi_data().can
209}
210
211pub fn all_chi() -> &'static [ChiInfo] {
212    &canchi_data().chi
213}
214
215pub fn all_elements() -> &'static HashMap<String, ElementInfo> {
216    &canchi_data().elements
217}
218
219pub fn all_day_guidance() -> &'static HashMap<String, DayGuidance> {
220    &canchi_data().day_guidance
221}
222
223pub fn all_tiet_khi_insights() -> &'static [TietKhiInsight] {
224    &tiet_khi_data().tiet_khi
225}
226
227pub fn find_can(name: &str) -> Option<&'static CanInfo> {
228    all_can().iter().find(|item| item.name == name)
229}
230
231pub fn find_chi(name: &str) -> Option<&'static ChiInfo> {
232    all_chi().iter().find(|item| item.name == name)
233}
234
235pub fn get_day_guidance(chi_name: &str) -> Option<&'static DayGuidance> {
236    all_day_guidance().get(chi_name)
237}
238
239pub fn find_tiet_khi_insight(term_name: &str) -> Option<&'static TietKhiInsight> {
240    all_tiet_khi_insights()
241        .iter()
242        .find(|item| item.name.vi == term_name || item.name.en == term_name)
243}
244
245pub fn all_truc_insights() -> &'static [TrucInsight] {
246    TRUC_INSIGHT_DATA
247        .get_or_init(|| {
248            let parsed: TrucInsightFile = serde_json::from_str(TRUC_INSIGHT_JSON)
249                .expect("Failed to parse data/truc-insight.json");
250            parsed.truc
251        })
252        .as_slice()
253}
254
255pub fn find_truc_insight(name: &str) -> Option<&'static TrucInsight> {
256    all_truc_insights().iter().find(|t| t.id == name)
257}
258
259fn day_deity_insight_data() -> &'static DayDeityInsightFile {
260    DAY_DEITY_INSIGHT_DATA.get_or_init(|| {
261        serde_json::from_str(DAY_DEITY_INSIGHT_JSON)
262            .expect("Failed to parse data/day-deity-insight.json")
263    })
264}
265
266pub fn find_deity_classification_insight(id: &str) -> Option<&'static DeityClassificationInsight> {
267    day_deity_insight_data()
268        .classifications
269        .iter()
270        .find(|c| c.id == id)
271}
272
273pub fn find_deity_insight(name: &str) -> Option<&'static DeityInsight> {
274    day_deity_insight_data()
275        .deities
276        .iter()
277        .find(|d| d.name == name)
278}
279
280pub fn all_na_am_insights() -> &'static [NaAmInsight] {
281    NA_AM_INSIGHT_DATA
282        .get_or_init(|| {
283            let parsed: NaAmInsightFile = serde_json::from_str(NA_AM_INSIGHT_JSON)
284                .expect("Failed to parse data/na-am-insight.json");
285            parsed.pairs
286        })
287        .as_slice()
288}
289
290pub fn find_na_am_insight(na_am: &str) -> Option<&'static NaAmInsight> {
291    all_na_am_insights().iter().find(|n| n.na_am == na_am)
292}
293
294pub fn all_ten_gods_insights() -> &'static [TenGodsInsight] {
295    TEN_GODS_INSIGHT_DATA
296        .get_or_init(|| {
297            let parsed: TenGodsInsightFile = serde_json::from_str(TEN_GODS_INSIGHT_JSON)
298                .expect("Failed to parse data/ten-gods-insight.json");
299            parsed.gods
300        })
301        .as_slice()
302}
303
304pub fn find_ten_gods_insight(id: &str) -> Option<&'static TenGodsInsight> {
305    all_ten_gods_insights().iter().find(|g| g.id == id)
306}
307
308fn tu_menh_insight_data() -> &'static TuMenhInsightFile {
309    TU_MENH_INSIGHT_DATA.get_or_init(|| {
310        serde_json::from_str(TU_MENH_INSIGHT_JSON)
311            .expect("Failed to parse data/tu-menh-insight.json")
312    })
313}
314
315pub fn all_kua_group_insights() -> &'static [KuaGroupInsight] {
316    &tu_menh_insight_data().groups
317}
318
319pub fn all_kua_insights() -> &'static [KuaInsight] {
320    &tu_menh_insight_data().kua
321}
322
323pub fn find_kua_insight(number: u8) -> Option<&'static KuaInsight> {
324    all_kua_insights().iter().find(|k| k.number == number)
325}
326
327pub fn find_kua_group_insight(id: &str) -> Option<&'static KuaGroupInsight> {
328    all_kua_group_insights().iter().find(|g| g.id == id)
329}
330
331fn dai_van_insight_data() -> &'static DaiVanInsightFile {
332    DAI_VAN_INSIGHT_DATA.get_or_init(|| {
333        serde_json::from_str(DAI_VAN_INSIGHT_JSON)
334            .expect("Failed to parse data/dai-van-insight.json")
335    })
336}
337
338pub fn all_dai_van_direction_insights() -> &'static [DaiVanDirectionInsight] {
339    &dai_van_insight_data().directions
340}
341
342pub fn dai_van_phases_insight() -> &'static DaiVanPhasesInsight {
343    &dai_van_insight_data().phases
344}
345
346pub fn all_dai_van_element_insights() -> &'static [DaiVanElementInsight] {
347    &dai_van_insight_data().elements
348}
349
350pub fn find_dai_van_element_insight(element: &str) -> Option<&'static DaiVanElementInsight> {
351    all_dai_van_element_insights()
352        .iter()
353        .find(|e| e.element == element)
354}
355
356pub fn find_dai_van_direction_insight(id: &str) -> Option<&'static DaiVanDirectionInsight> {
357    all_dai_van_direction_insights().iter().find(|d| d.id == id)
358}
359
360#[cfg(test)]
361mod tests {
362    use super::{
363        all_can, all_chi, all_day_guidance, all_elements, all_tiet_khi_insights,
364        find_tiet_khi_insight,
365    };
366
367    #[test]
368    fn parses_canchi_collections() {
369        assert_eq!(all_can().len(), 10);
370        assert_eq!(all_chi().len(), 12);
371        assert_eq!(all_elements().len(), 5);
372        assert_eq!(all_day_guidance().len(), 12);
373    }
374
375    #[test]
376    fn parses_all_tiet_khi() {
377        assert_eq!(all_tiet_khi_insights().len(), 24);
378    }
379
380    #[test]
381    fn lookup_tiet_khi_by_vi_name() {
382        let term = find_tiet_khi_insight("Xuân Phân").expect("Xuân Phân should exist");
383        assert_eq!(term.longitude, 0);
384        assert!(!term.health.vi.is_empty());
385    }
386
387    #[test]
388    fn all_truc_insights_has_12_entries() {
389        assert_eq!(super::all_truc_insights().len(), 12);
390    }
391
392    #[test]
393    fn find_truc_insight_returns_entry() {
394        let truc = super::find_truc_insight("Kiến");
395        assert!(truc.is_some());
396        assert!(!truc.unwrap().meaning.vi.is_empty());
397    }
398
399    #[test]
400    fn all_na_am_insights_has_30_entries() {
401        assert_eq!(super::all_na_am_insights().len(), 30);
402    }
403
404    #[test]
405    fn find_na_am_insight_returns_entry() {
406        let na_am = super::find_na_am_insight("Hải Trung Kim");
407        assert!(na_am.is_some());
408    }
409
410    #[test]
411    fn all_ten_gods_insights_has_10_entries() {
412        assert_eq!(super::all_ten_gods_insights().len(), 10);
413    }
414
415    #[test]
416    fn find_deity_classification_returns_entry() {
417        let cls = super::find_deity_classification_insight("HoangDao");
418        assert!(cls.is_some());
419    }
420
421    #[test]
422    fn find_deity_by_name_returns_entry() {
423        let deity = super::find_deity_insight("Thanh Long");
424        assert!(deity.is_some());
425    }
426
427    #[test]
428    fn tu_menh_kua_insights_has_8_entries() {
429        assert_eq!(super::all_kua_insights().len(), 8);
430    }
431
432    #[test]
433    fn find_kua_insight_by_number() {
434        let kua = super::find_kua_insight(1);
435        assert!(kua.is_some());
436    }
437
438    #[test]
439    fn tu_menh_group_insights_has_2_entries() {
440        assert_eq!(super::all_kua_group_insights().len(), 2);
441    }
442
443    #[test]
444    fn dai_van_element_insights_has_5_entries() {
445        assert_eq!(super::all_dai_van_element_insights().len(), 5);
446    }
447}