1use crate::dto::{
2 ActiveRecommendationPackDto, ActivityLabelDto, CanChiDto, CanChiInfoDto, CanInsightDto,
3 ChiInsightDto, ConventionMetadataDto, DailyRecommendationsDto, DayConflictDto, DayDeityDto,
4 DayElementDto, DayFortuneDto, DayGuidanceDto, DayInfoDto, DayStarDto, DayStarsDto, DayTabooDto,
5 DayTenGodsDto, ElementInsightDto, FestivalInsightDto, FoodInsightDto, GioHoangDaoDto,
6 HolidayDto, HolidayInsightDto, HourInfoDto, KuaResultDto, LocalizedListDto, LocalizedTextDto,
7 LunarDto, NaAmErrorDto, NaAmLookupResultDto, NguHanhDto, ProverbInsightDto,
8 RecommendationBucketDto, RecommendationEvidenceDto, RecommendationEvidenceSourceDto,
9 RecommendationPackCatalogEntryDto, RecommendationReasonDto, RecommendationScopeDto,
10 RecommendationSeverityDto, RegionsInsightDto, RuleEvidenceDto, RulesetCatalogEntryDto,
11 RulesetDefaultsDto, RulesetSourceNoteDto, SolarDto, StarRuleEvidenceDto,
12 SynthesizedRecommendationDto, TabooInsightDto, TangCanDto, ThapThanResultDto, TietKhiDto,
13 TietKhiInsightDto, TravelDirectionDto, TrucDto, XungHopDto,
14};
15
16impl From<&amlich_core::NguHanh> for NguHanhDto {
17 fn from(value: &amlich_core::NguHanh) -> Self {
18 Self {
19 can: value.can.clone(),
20 chi: value.chi.clone(),
21 }
22 }
23}
24
25impl From<&amlich_core::CanChi> for CanChiDto {
26 fn from(value: &amlich_core::CanChi) -> Self {
27 Self {
28 can_index: value.can_index,
29 chi_index: value.chi_index,
30 can: value.can.clone(),
31 chi: value.chi.clone(),
32 full: value.full.clone(),
33 con_giap: value.con_giap.clone(),
34 ngu_hanh: NguHanhDto::from(&value.ngu_hanh),
35 }
36 }
37}
38
39impl From<&amlich_core::CanChiSet> for CanChiInfoDto {
40 fn from(value: &amlich_core::CanChiSet) -> Self {
41 Self {
42 day: CanChiDto::from(&value.day),
43 month: CanChiDto::from(&value.month),
44 year: CanChiDto::from(&value.year),
45 full: format!(
46 "{}, tháng {}, năm {}",
47 value.day.full, value.month.full, value.year.full
48 ),
49 }
50 }
51}
52
53impl From<&amlich_core::SolarDate> for SolarDto {
54 fn from(value: &amlich_core::SolarDate) -> Self {
55 Self {
56 day: value.day,
57 month: value.month,
58 year: value.year,
59 day_of_week: value.day_of_week,
60 day_of_week_name: amlich_core::THU[value.day_of_week].to_string(),
61 date_string: format!("{}-{:02}-{:02}", value.year, value.month, value.day),
62 }
63 }
64}
65
66impl From<&amlich_core::lunar::LunarDate> for LunarDto {
67 fn from(value: &amlich_core::lunar::LunarDate) -> Self {
68 Self {
69 day: value.day,
70 month: value.month,
71 year: value.year,
72 is_leap_month: value.is_leap,
73 date_string: format!(
74 "{}/{}/{}{}",
75 value.day,
76 value.month,
77 value.year,
78 if value.is_leap { " (nhuận)" } else { "" }
79 ),
80 }
81 }
82}
83
84impl From<&amlich_core::tietkhi::SolarTerm> for TietKhiDto {
85 fn from(value: &amlich_core::tietkhi::SolarTerm) -> Self {
86 Self {
87 index: value.index,
88 name: value.name.clone(),
89 description: value.description.clone(),
90 longitude: value.longitude,
91 current_longitude: value.current_longitude,
92 season: value.season.clone(),
93 }
94 }
95}
96
97impl From<&amlich_core::gio_hoang_dao::HourInfo> for HourInfoDto {
98 fn from(value: &amlich_core::gio_hoang_dao::HourInfo) -> Self {
99 Self {
100 hour_index: value.hour_index,
101 hour_chi: value.hour_chi.clone(),
102 time_range: value.time_range.clone(),
103 star: value.star.clone(),
104 is_good: value.is_good,
105 }
106 }
107}
108
109impl From<&amlich_core::gio_hoang_dao::GioHoangDao> for GioHoangDaoDto {
110 fn from(value: &amlich_core::gio_hoang_dao::GioHoangDao) -> Self {
111 Self {
112 day_chi: value.day_chi.clone(),
113 good_hour_count: value.good_hour_count,
114 good_hours: value.good_hours.iter().map(HourInfoDto::from).collect(),
115 all_hours: value.all_hours.iter().map(HourInfoDto::from).collect(),
116 summary: value.summary.clone(),
117 }
118 }
119}
120
121impl From<&amlich_core::almanac::types::DayElement> for DayElementDto {
122 fn from(value: &amlich_core::almanac::types::DayElement) -> Self {
123 Self {
124 na_am: value.na_am.clone(),
125 element: value.element.clone(),
126 can_element: value.can_element.clone(),
127 chi_element: value.chi_element.clone(),
128 evidence: value.evidence.as_ref().map(RuleEvidenceDto::from),
129 }
130 }
131}
132
133impl From<&amlich_core::almanac::types::RuleEvidence> for RuleEvidenceDto {
134 fn from(value: &amlich_core::almanac::types::RuleEvidence) -> Self {
135 Self {
136 source_id: value.source_id.clone(),
137 method: value.method.clone(),
138 profile: value.profile.clone(),
139 }
140 }
141}
142
143impl From<&amlich_core::almanac::types::DayConflict> for DayConflictDto {
144 fn from(value: &amlich_core::almanac::types::DayConflict) -> Self {
145 Self {
146 opposing_chi: value.opposing_chi.clone(),
147 opposing_con_giap: value.opposing_con_giap.clone(),
148 tuoi_xung: value.tuoi_xung.clone(),
149 sat_huong: value.sat_huong.clone(),
150 evidence: value.evidence.as_ref().map(RuleEvidenceDto::from),
151 }
152 }
153}
154
155impl From<&amlich_core::almanac::types::TravelDirection> for TravelDirectionDto {
156 fn from(value: &amlich_core::almanac::types::TravelDirection) -> Self {
157 Self {
158 xuat_hanh_huong: value.xuat_hanh_huong.clone(),
159 tai_than: value.tai_than.clone(),
160 hy_than: value.hy_than.clone(),
161 evidence: value.evidence.as_ref().map(RuleEvidenceDto::from),
162 }
163 }
164}
165
166impl From<&amlich_core::almanac::types::DayStar> for DayStarDto {
167 fn from(value: &amlich_core::almanac::types::DayStar) -> Self {
168 let system = match value.system {
169 amlich_core::almanac::types::StarSystem::NhiThapBatTu => "nhi-thap-bat-tu",
170 }
171 .to_string();
172 let quality = match value.quality {
173 amlich_core::almanac::types::StarQuality::Cat => "cat",
174 amlich_core::almanac::types::StarQuality::Hung => "hung",
175 amlich_core::almanac::types::StarQuality::Binh => "binh",
176 }
177 .to_string();
178 Self {
179 system,
180 index: value.index,
181 name: value.name.clone(),
182 quality,
183 evidence: value.evidence.as_ref().map(RuleEvidenceDto::from),
184 }
185 }
186}
187
188impl From<&amlich_core::almanac::types::StarRuleEvidence> for StarRuleEvidenceDto {
189 fn from(value: &amlich_core::almanac::types::StarRuleEvidence) -> Self {
190 let quality = match value.quality {
191 amlich_core::almanac::types::StarQuality::Cat => "cat",
192 amlich_core::almanac::types::StarQuality::Hung => "hung",
193 amlich_core::almanac::types::StarQuality::Binh => "binh",
194 }
195 .to_string();
196 Self {
197 name: value.name.clone(),
198 quality,
199 category: value.category.clone(),
200 source_id: value.source_id.clone(),
201 method: value.method.clone(),
202 profile: value.profile.clone(),
203 }
204 }
205}
206
207impl From<&amlich_core::almanac::types::DayStars> for DayStarsDto {
208 fn from(value: &amlich_core::almanac::types::DayStars) -> Self {
209 let star_system = value.star_system.as_ref().map(|system| match system {
210 amlich_core::almanac::types::StarSystem::NhiThapBatTu => "nhi-thap-bat-tu",
211 });
212
213 Self {
214 cat_tinh: value.cat_tinh.clone(),
215 sat_tinh: value.sat_tinh.clone(),
216 day_star: value.day_star.as_ref().map(DayStarDto::from),
217 star_system: star_system.map(str::to_string),
218 evidence: value.evidence.as_ref().map(RuleEvidenceDto::from),
219 matched_rules: value
220 .matched_rules
221 .iter()
222 .map(StarRuleEvidenceDto::from)
223 .collect(),
224 }
225 }
226}
227
228impl From<&amlich_core::almanac::types::XungHopResult> for XungHopDto {
229 fn from(value: &amlich_core::almanac::types::XungHopResult) -> Self {
230 Self {
231 luc_xung: value.luc_xung.clone(),
232 tam_hop: value.tam_hop.clone(),
233 tu_hanh_xung: value.tu_hanh_xung.clone(),
234 liu_he: value.liu_he.clone(),
235 xiang_hai: value.xiang_hai.clone(),
236 xiang_xing: value.xiang_xing.clone(),
237 }
238 }
239}
240
241impl From<&amlich_core::almanac::types::TangCan> for TangCanDto {
242 fn from(value: &amlich_core::almanac::types::TangCan) -> Self {
243 Self {
244 main: value.main.clone(),
245 central: value.central.clone(),
246 residual: value.residual.clone(),
247 strength: value.strength,
248 }
249 }
250}
251
252impl From<&amlich_core::almanac::types::TrucInfo> for TrucDto {
253 fn from(value: &amlich_core::almanac::types::TrucInfo) -> Self {
254 Self {
255 index: value.index,
256 name: value.name.clone(),
257 quality: value.quality.clone(),
258 evidence: value.evidence.as_ref().map(RuleEvidenceDto::from),
259 }
260 }
261}
262
263impl From<&amlich_core::almanac::types::DayTaboo> for DayTabooDto {
264 fn from(value: &amlich_core::almanac::types::DayTaboo) -> Self {
265 Self {
266 rule_id: value.rule_id.clone(),
267 name: value.name.clone(),
268 severity: value.severity.clone(),
269 reason: value.reason.clone(),
270 evidence: value.evidence.as_ref().map(RuleEvidenceDto::from),
271 }
272 }
273}
274
275impl From<&amlich_core::almanac::types::DayDeity> for DayDeityDto {
276 fn from(value: &amlich_core::almanac::types::DayDeity) -> Self {
277 let classification = match value.classification {
278 amlich_core::almanac::types::DayDeityClassification::HoangDao => "hoang_dao",
279 amlich_core::almanac::types::DayDeityClassification::HacDao => "hac_dao",
280 }
281 .to_string();
282
283 Self {
284 name: value.name.clone(),
285 classification,
286 evidence: value.evidence.as_ref().map(RuleEvidenceDto::from),
287 }
288 }
289}
290
291impl From<&amlich_core::almanac::types::DayTenGods> for DayTenGodsDto {
292 fn from(value: &amlich_core::almanac::types::DayTenGods) -> Self {
293 Self {
294 to_year_stem: value.to_year_stem.as_ref().map(ThapThanResultDto::from),
295 to_self: value.to_self.as_ref().map(ThapThanResultDto::from),
296 }
297 }
298}
299
300impl From<&amlich_core::almanac::types::ThapThanResult> for ThapThanResultDto {
301 fn from(value: &amlich_core::almanac::types::ThapThanResult) -> Self {
302 let relation = match value.relation {
303 amlich_core::almanac::types::FiveElementRelation::Same => "same".to_string(),
304 amlich_core::almanac::types::FiveElementRelation::DayGeneratesTarget => {
305 "day_generates_target".to_string()
306 }
307 amlich_core::almanac::types::FiveElementRelation::TargetGeneratesDay => {
308 "target_generates_day".to_string()
309 }
310 amlich_core::almanac::types::FiveElementRelation::DayControlsTarget => {
311 "day_controls_target".to_string()
312 }
313 amlich_core::almanac::types::FiveElementRelation::TargetControlsDay => {
314 "target_controls_day".to_string()
315 }
316 };
317
318 let label = match value.label {
319 amlich_core::almanac::types::ThapThanLabel::TyKien => "ty_kien".to_string(),
320 amlich_core::almanac::types::ThapThanLabel::KiepTai => "kiep_tai".to_string(),
321 amlich_core::almanac::types::ThapThanLabel::ThucThan => "thuc_than".to_string(),
322 amlich_core::almanac::types::ThapThanLabel::ThuongQuan => "thuong_quan".to_string(),
323 amlich_core::almanac::types::ThapThanLabel::ChinhTai => "chinh_tai".to_string(),
324 amlich_core::almanac::types::ThapThanLabel::ThienTai => "thien_tai".to_string(),
325 amlich_core::almanac::types::ThapThanLabel::ChinhQuan => "chinh_quan".to_string(),
326 amlich_core::almanac::types::ThapThanLabel::ThatSat => "that_sat".to_string(),
327 amlich_core::almanac::types::ThapThanLabel::ChinhAn => "chinh_an".to_string(),
328 amlich_core::almanac::types::ThapThanLabel::ThienAn => "thien_an".to_string(),
329 };
330
331 Self {
332 label,
333 relation,
334 same_polarity: value.same_polarity,
335 evidence: RuleEvidenceDto::from(&value.evidence),
336 }
337 }
338}
339
340impl From<&amlich_core::almanac::tu_menh::KuaResult> for KuaResultDto {
341 fn from(value: &amlich_core::almanac::tu_menh::KuaResult) -> Self {
342 let group = match value.group {
343 amlich_core::almanac::tu_menh::KuaGroup::East => "east".to_string(),
344 amlich_core::almanac::tu_menh::KuaGroup::West => "west".to_string(),
345 };
346
347 let favorable_directions = value
348 .favorable_directions
349 .iter()
350 .map(|d| d.to_string())
351 .collect();
352
353 let unfavorable_directions = value
354 .unfavorable_directions
355 .iter()
356 .map(|d| d.to_string())
357 .collect();
358
359 let convention = ConventionMetadataDto {
360 year_basis: value.convention.year_basis.clone(),
361 kua_five_resolution: value.convention.kua5_resolution.clone(),
362 gender_encoding: value.convention.gender_encoding.clone(),
363 };
364
365 Self {
366 kua: value.kua,
367 group,
368 favorable_directions,
369 unfavorable_directions,
370 convention,
371 }
372 }
373}
374
375impl From<&amlich_core::almanac::types::DayFortune> for DayFortuneDto {
376 fn from(value: &amlich_core::almanac::types::DayFortune) -> Self {
377 Self {
378 ruleset_id: value.ruleset_id.clone(),
379 ruleset_version: value.ruleset_version.clone(),
380 profile: value.profile.clone(),
381 day_element: DayElementDto::from(&value.day_element),
382 conflict: DayConflictDto::from(&value.conflict),
383 travel: TravelDirectionDto::from(&value.travel),
384 stars: DayStarsDto::from(&value.stars),
385 day_deity: value.day_deity.as_ref().map(DayDeityDto::from),
386 taboos: value.taboos.iter().map(DayTabooDto::from).collect(),
387 xung_hop: XungHopDto::from(&value.xung_hop),
388 truc: TrucDto::from(&value.truc),
389 tang_can: value.tang_can.as_ref().map(TangCanDto::from),
390 ten_gods: value.ten_gods.as_ref().map(DayTenGodsDto::from),
391 tu_menh: value.tu_menh.as_ref().map(KuaResultDto::from),
392 }
393 }
394}
395
396fn activity_id_to_snake_case(
397 activity_id: amlich_core::almanac::recommendation::ActivityId,
398) -> String {
399 match activity_id {
400 amlich_core::almanac::recommendation::ActivityId::Travel => "travel",
401 amlich_core::almanac::recommendation::ActivityId::MeetingSocial => "meeting_social",
402 amlich_core::almanac::recommendation::ActivityId::OpeningStart => "opening_start",
403 amlich_core::almanac::recommendation::ActivityId::ContractAgreement => "contract_agreement",
404 amlich_core::almanac::recommendation::ActivityId::BusinessTrade => "business_trade",
405 amlich_core::almanac::recommendation::ActivityId::FinanceInvestment => "finance_investment",
406 amlich_core::almanac::recommendation::ActivityId::ConstructionGroundbreaking => {
407 "construction_groundbreaking"
408 }
409 amlich_core::almanac::recommendation::ActivityId::RepairRenovation => "repair_renovation",
410 amlich_core::almanac::recommendation::ActivityId::MoveRelocation => "move_relocation",
411 amlich_core::almanac::recommendation::ActivityId::WeddingEngagement => "wedding_engagement",
412 amlich_core::almanac::recommendation::ActivityId::LawsuitDispute => "lawsuit_dispute",
413 amlich_core::almanac::recommendation::ActivityId::PrayerOffering => "prayer_offering",
414 amlich_core::almanac::recommendation::ActivityId::MedicalTreatment => "medical_treatment",
415 amlich_core::almanac::recommendation::ActivityId::BurialMemorial => "burial_memorial",
416 amlich_core::almanac::recommendation::ActivityId::CleaningPurging => "cleaning_purging",
417 }
418 .to_string()
419}
420
421impl From<&amlich_core::almanac::recommendation::ActivityLabel> for ActivityLabelDto {
422 fn from(value: &amlich_core::almanac::recommendation::ActivityLabel) -> Self {
423 Self {
424 vi: value.vi.clone(),
425 en: value.en.clone(),
426 }
427 }
428}
429
430impl From<amlich_core::almanac::recommendation::RecommendationScope> for RecommendationScopeDto {
431 fn from(value: amlich_core::almanac::recommendation::RecommendationScope) -> Self {
432 match value {
433 amlich_core::almanac::recommendation::RecommendationScope::GeneralDay => {
434 RecommendationScopeDto::GeneralDay
435 }
436 }
437 }
438}
439
440impl From<amlich_core::almanac::recommendation::RecommendationBucket> for RecommendationBucketDto {
441 fn from(value: amlich_core::almanac::recommendation::RecommendationBucket) -> Self {
442 match value {
443 amlich_core::almanac::recommendation::RecommendationBucket::Nen => {
444 RecommendationBucketDto::Nen
445 }
446 amlich_core::almanac::recommendation::RecommendationBucket::CoThe => {
447 RecommendationBucketDto::CoThe
448 }
449 amlich_core::almanac::recommendation::RecommendationBucket::Tranh => {
450 RecommendationBucketDto::Tranh
451 }
452 amlich_core::almanac::recommendation::RecommendationBucket::KyManh => {
453 RecommendationBucketDto::KyManh
454 }
455 }
456 }
457}
458
459impl From<amlich_core::almanac::recommendation::RecommendationSeverity>
460 for RecommendationSeverityDto
461{
462 fn from(value: amlich_core::almanac::recommendation::RecommendationSeverity) -> Self {
463 match value {
464 amlich_core::almanac::recommendation::RecommendationSeverity::Primary => {
465 RecommendationSeverityDto::Primary
466 }
467 amlich_core::almanac::recommendation::RecommendationSeverity::Supporting => {
468 RecommendationSeverityDto::Supporting
469 }
470 amlich_core::almanac::recommendation::RecommendationSeverity::Override => {
471 RecommendationSeverityDto::Override
472 }
473 }
474 }
475}
476
477impl From<amlich_core::almanac::recommendation::RecommendationEvidenceSource>
478 for RecommendationEvidenceSourceDto
479{
480 fn from(value: amlich_core::almanac::recommendation::RecommendationEvidenceSource) -> Self {
481 match value {
482 amlich_core::almanac::recommendation::RecommendationEvidenceSource::DayGuidance => {
483 RecommendationEvidenceSourceDto::DayGuidance
484 }
485 amlich_core::almanac::recommendation::RecommendationEvidenceSource::Truc => {
486 RecommendationEvidenceSourceDto::Truc
487 }
488 amlich_core::almanac::recommendation::RecommendationEvidenceSource::Stars => {
489 RecommendationEvidenceSourceDto::Stars
490 }
491 amlich_core::almanac::recommendation::RecommendationEvidenceSource::DayDeity => {
492 RecommendationEvidenceSourceDto::DayDeity
493 }
494 amlich_core::almanac::recommendation::RecommendationEvidenceSource::Taboo => {
495 RecommendationEvidenceSourceDto::Taboo
496 }
497 amlich_core::almanac::recommendation::RecommendationEvidenceSource::XungHop => {
498 RecommendationEvidenceSourceDto::XungHop
499 }
500 amlich_core::almanac::recommendation::RecommendationEvidenceSource::TietKhi => {
501 RecommendationEvidenceSourceDto::TietKhi
502 }
503 amlich_core::almanac::recommendation::RecommendationEvidenceSource::GioHoangDao => {
504 RecommendationEvidenceSourceDto::GioHoangDao
505 }
506 amlich_core::almanac::recommendation::RecommendationEvidenceSource::Travel => {
507 RecommendationEvidenceSourceDto::Travel
508 }
509 amlich_core::almanac::recommendation::RecommendationEvidenceSource::ProductRule => {
510 RecommendationEvidenceSourceDto::ProductRule
511 }
512 }
513 }
514}
515
516impl From<&amlich_core::almanac::recommendation::RecommendationEvidence>
517 for RecommendationEvidenceDto
518{
519 fn from(value: &amlich_core::almanac::recommendation::RecommendationEvidence) -> Self {
520 Self {
521 source: RecommendationEvidenceSourceDto::from(value.source),
522 code: value.code.clone(),
523 note: value.note.clone(),
524 }
525 }
526}
527
528impl From<&amlich_core::almanac::recommendation::RecommendationReason> for RecommendationReasonDto {
529 fn from(value: &amlich_core::almanac::recommendation::RecommendationReason) -> Self {
530 Self {
531 rule_id: value.rule_id.clone(),
532 severity: RecommendationSeverityDto::from(value.severity),
533 summary_vi: value.summary_vi.clone(),
534 summary_en: value.summary_en.clone(),
535 evidence: RecommendationEvidenceDto::from(&value.evidence),
536 }
537 }
538}
539
540impl From<&amlich_core::almanac::recommendation::ActiveRecommendationPack>
541 for ActiveRecommendationPackDto
542{
543 fn from(value: &amlich_core::almanac::recommendation::ActiveRecommendationPack) -> Self {
544 Self {
545 pack_id: value.pack_id.clone(),
546 version: value.version.clone(),
547 source_family: value.source_family.clone(),
548 mode: match value.mode {
549 amlich_core::almanac::recommendation::RecommendationPackMode::Advisory => {
550 "advisory"
551 }
552 amlich_core::almanac::recommendation::RecommendationPackMode::TraditionVariant => {
553 "tradition_variant"
554 }
555 amlich_core::almanac::recommendation::RecommendationPackMode::Experimental => {
556 "experimental"
557 }
558 }
559 .to_string(),
560 }
561 }
562}
563
564impl From<&amlich_core::almanac::types::RuleSetDefaults> for RulesetDefaultsDto {
565 fn from(value: &amlich_core::almanac::types::RuleSetDefaults) -> Self {
566 Self {
567 tz_offset: value.tz_offset,
568 meridian: value.meridian.clone(),
569 }
570 }
571}
572
573impl From<&amlich_core::almanac::types::RuleSetSourceNote> for RulesetSourceNoteDto {
574 fn from(value: &amlich_core::almanac::types::RuleSetSourceNote) -> Self {
575 Self {
576 family: value.family.clone(),
577 source_id: value.source_id.clone(),
578 note: value.note.clone(),
579 }
580 }
581}
582
583impl From<&amlich_core::almanac::data::RulesetRegistryEntry> for RulesetCatalogEntryDto {
584 fn from(value: &amlich_core::almanac::data::RulesetRegistryEntry) -> Self {
585 let descriptor = value.descriptor.to_document_descriptor();
586
587 Self {
588 id: descriptor.id,
589 canonical_id: value.descriptor.id.to_string(),
590 version: descriptor.version,
591 region: descriptor.region,
592 profile: descriptor.profile,
593 schema_version: descriptor.schema_version,
594 is_default: value.descriptor.id == amlich_core::almanac::data::DEFAULT_RULESET_ID,
595 aliases: value
596 .aliases
597 .iter()
598 .map(|alias| (*alias).to_string())
599 .collect(),
600 defaults: RulesetDefaultsDto::from(&descriptor.defaults),
601 source_notes: descriptor
602 .source_notes
603 .iter()
604 .map(RulesetSourceNoteDto::from)
605 .collect(),
606 }
607 }
608}
609
610impl From<&amlich_core::almanac::recommendation::RecommendationPackDescriptor>
611 for RecommendationPackCatalogEntryDto
612{
613 fn from(value: &amlich_core::almanac::recommendation::RecommendationPackDescriptor) -> Self {
614 Self {
615 pack_id: value.pack_id.to_string(),
616 request_field: "enabled_pack_ids".to_string(),
617 version: value.version.to_string(),
618 source_family: value.source_family.to_string(),
619 mode: match value.mode {
620 amlich_core::almanac::recommendation::RecommendationPackMode::Advisory => {
621 "advisory"
622 }
623 amlich_core::almanac::recommendation::RecommendationPackMode::TraditionVariant => {
624 "tradition_variant"
625 }
626 amlich_core::almanac::recommendation::RecommendationPackMode::Experimental => {
627 "experimental"
628 }
629 }
630 .to_string(),
631 }
632 }
633}
634
635impl From<&amlich_core::almanac::recommendation::SynthesizedRecommendation>
636 for SynthesizedRecommendationDto
637{
638 fn from(value: &amlich_core::almanac::recommendation::SynthesizedRecommendation) -> Self {
639 Self {
640 activity_id: activity_id_to_snake_case(value.activity_id),
641 label: ActivityLabelDto::from(&value.label),
642 bucket: RecommendationBucketDto::from(value.bucket),
643 reasons: value
644 .reasons
645 .iter()
646 .map(RecommendationReasonDto::from)
647 .collect(),
648 }
649 }
650}
651
652impl From<&amlich_core::almanac::recommendation::DailyRecommendations> for DailyRecommendationsDto {
653 fn from(value: &amlich_core::almanac::recommendation::DailyRecommendations) -> Self {
654 Self {
655 ruleset_id: value.ruleset_id.clone(),
656 ruleset_version: value.ruleset_version.clone(),
657 profile: value.profile.clone(),
658 scope: RecommendationScopeDto::from(value.scope),
659 version: value.version.clone(),
660 summary_vi: value.summary_vi.clone(),
661 summary_en: value.summary_en.clone(),
662 active_packs: value
663 .active_packs
664 .iter()
665 .map(ActiveRecommendationPackDto::from)
666 .collect(),
667 activities: value
668 .activities
669 .iter()
670 .map(SynthesizedRecommendationDto::from)
671 .collect(),
672 }
673 }
674}
675
676impl From<&amlich_core::DaySnapshot> for DayInfoDto {
677 fn from(value: &amlich_core::DaySnapshot) -> Self {
678 Self {
679 ruleset_id: value.ruleset_id.clone(),
680 ruleset_version: value.ruleset_version.clone(),
681 profile: value.profile.clone(),
682 solar: SolarDto::from(&value.context.solar),
683 lunar: LunarDto::from(&value.context.lunar),
684 jd: value.context.jd,
685 canchi: CanChiInfoDto::from(&value.context.canchi),
686 tiet_khi: TietKhiDto::from(&value.context.tiet_khi),
687 gio_hoang_dao: GioHoangDaoDto::from(&value.context.gio_hoang_dao),
688 day_fortune: Some(DayFortuneDto::from(&value.day_fortune)),
689 daily_recommendations: DailyRecommendationsDto::from(&value.daily_recommendations),
690 contextual_recommendations: value
691 .contextual_recommendations
692 .as_ref()
693 .map(DailyRecommendationsDto::from),
694 }
695 }
696}
697
698impl From<&amlich_core::holidays::Holiday> for HolidayDto {
699 fn from(value: &amlich_core::holidays::Holiday) -> Self {
700 Self {
701 name: value.name.clone(),
702 description: value.description.clone(),
703 solar_day: value.solar_day,
704 solar_month: value.solar_month,
705 solar_year: value.solar_year,
706 lunar_day: value.lunar_date.as_ref().map(|d| d.day),
707 lunar_month: value.lunar_date.as_ref().map(|d| d.month),
708 lunar_year: value.lunar_date.as_ref().map(|d| d.year),
709 is_solar: value.is_solar,
710 category: value.category.clone(),
711 is_major: value.is_major,
712 }
713 }
714}
715
716impl From<&amlich_core::holiday_data::BilingualText> for LocalizedTextDto {
717 fn from(value: &amlich_core::holiday_data::BilingualText) -> Self {
718 Self {
719 vi: value.vi.clone(),
720 en: value.en.clone(),
721 }
722 }
723}
724
725impl From<&amlich_core::insight_data::BilingualText> for LocalizedTextDto {
726 fn from(value: &amlich_core::insight_data::BilingualText) -> Self {
727 Self {
728 vi: value.vi.clone(),
729 en: value.en.clone(),
730 }
731 }
732}
733
734impl From<&amlich_core::insight_data::BilingualList> for LocalizedListDto {
735 fn from(value: &amlich_core::insight_data::BilingualList) -> Self {
736 Self {
737 vi: value.vi.clone(),
738 en: value.en.clone(),
739 }
740 }
741}
742
743impl From<&amlich_core::holiday_data::BilingualList> for LocalizedListDto {
744 fn from(value: &amlich_core::holiday_data::BilingualList) -> Self {
745 Self {
746 vi: value.vi.clone(),
747 en: value.en.clone(),
748 }
749 }
750}
751
752impl From<&amlich_core::holiday_data::FoodItem> for FoodInsightDto {
753 fn from(value: &amlich_core::holiday_data::FoodItem) -> Self {
754 Self {
755 name: LocalizedTextDto::from(&value.name),
756 description: LocalizedTextDto::from(&value.description),
757 }
758 }
759}
760
761impl From<&amlich_core::holiday_data::TabooItem> for TabooInsightDto {
762 fn from(value: &amlich_core::holiday_data::TabooItem) -> Self {
763 Self {
764 action: LocalizedTextDto::from(&value.action),
765 reason: LocalizedTextDto::from(&value.reason),
766 }
767 }
768}
769
770impl From<&amlich_core::holiday_data::ProverbItem> for ProverbInsightDto {
771 fn from(value: &amlich_core::holiday_data::ProverbItem) -> Self {
772 Self {
773 text: value.text.clone(),
774 meaning: LocalizedTextDto::from(&value.meaning),
775 }
776 }
777}
778
779impl From<&amlich_core::holiday_data::Regions> for RegionsInsightDto {
780 fn from(value: &amlich_core::holiday_data::Regions) -> Self {
781 Self {
782 north: LocalizedTextDto::from(&value.north),
783 central: LocalizedTextDto::from(&value.central),
784 south: LocalizedTextDto::from(&value.south),
785 }
786 }
787}
788
789impl From<&amlich_core::holiday_data::LunarFestivalData> for FestivalInsightDto {
790 fn from(value: &amlich_core::holiday_data::LunarFestivalData) -> Self {
791 Self {
792 names: LocalizedListDto {
793 vi: value.names.vi.clone(),
794 en: value.names.en.clone(),
795 },
796 origin: value.origin.as_ref().map(LocalizedTextDto::from),
797 activities: value.activities.as_ref().map(LocalizedListDto::from),
798 food: value.food.iter().map(FoodInsightDto::from).collect(),
799 taboos: value.taboos.iter().map(TabooInsightDto::from).collect(),
800 proverbs: value.proverbs.iter().map(ProverbInsightDto::from).collect(),
801 regions: value.regions.as_ref().map(RegionsInsightDto::from),
802 category: value.category.clone(),
803 is_major: value.is_major,
804 }
805 }
806}
807
808impl From<&amlich_core::holiday_data::SolarHolidayData> for HolidayInsightDto {
809 fn from(value: &amlich_core::holiday_data::SolarHolidayData) -> Self {
810 Self {
811 names: LocalizedListDto {
812 vi: value.names.vi.clone(),
813 en: value.names.en.clone(),
814 },
815 origin: value.origin.as_ref().map(LocalizedTextDto::from),
816 significance: value.significance.as_ref().map(LocalizedTextDto::from),
817 activities: value.activities.as_ref().map(LocalizedListDto::from),
818 traditions: value.traditions.as_ref().map(LocalizedListDto::from),
819 food: value.food.iter().map(FoodInsightDto::from).collect(),
820 taboos: value.taboos.iter().map(TabooInsightDto::from).collect(),
821 proverbs: value.proverbs.iter().map(ProverbInsightDto::from).collect(),
822 regions: value.regions.as_ref().map(RegionsInsightDto::from),
823 category: value.category.clone(),
824 is_major: value.is_major,
825 }
826 }
827}
828
829impl From<(&String, &amlich_core::insight_data::ElementInfo)> for ElementInsightDto {
830 fn from((key, value): (&String, &amlich_core::insight_data::ElementInfo)) -> Self {
831 Self {
832 key: key.clone(),
833 name: LocalizedTextDto::from(&value.name),
834 nature: LocalizedTextDto::from(&value.nature),
835 }
836 }
837}
838
839impl From<&amlich_core::insight_data::CanInfo> for CanInsightDto {
840 fn from(value: &amlich_core::insight_data::CanInfo) -> Self {
841 Self {
842 name: value.name.clone(),
843 element: value.element.clone(),
844 meaning: LocalizedTextDto::from(&value.meaning),
845 nature: LocalizedTextDto::from(&value.nature),
846 }
847 }
848}
849
850impl From<&amlich_core::insight_data::ChiInfo> for ChiInsightDto {
851 fn from(value: &amlich_core::insight_data::ChiInfo) -> Self {
852 Self {
853 name: value.name.clone(),
854 animal: LocalizedTextDto::from(&value.animal),
855 element: value.element.clone(),
856 meaning: LocalizedTextDto::from(&value.meaning),
857 hours: value.hours.clone(),
858 }
859 }
860}
861
862impl From<&amlich_core::insight_data::DayGuidance> for DayGuidanceDto {
863 fn from(value: &amlich_core::insight_data::DayGuidance) -> Self {
864 Self {
865 good_for: LocalizedListDto::from(&value.good_for),
866 avoid_for: LocalizedListDto::from(&value.avoid_for),
867 }
868 }
869}
870
871impl From<&amlich_core::insight_data::TietKhiInsight> for TietKhiInsightDto {
872 fn from(value: &amlich_core::insight_data::TietKhiInsight) -> Self {
873 Self {
874 id: value.id.clone(),
875 name: LocalizedTextDto::from(&value.name),
876 longitude: value.longitude,
877 meaning: LocalizedTextDto::from(&value.meaning),
878 astronomy: LocalizedTextDto::from(&value.astronomy),
879 agriculture: LocalizedListDto::from(&value.agriculture),
880 health: LocalizedListDto::from(&value.health),
881 weather: LocalizedTextDto::from(&value.weather),
882 }
883 }
884}
885
886impl From<amlich_core::almanac::na_am::NaAmError> for NaAmErrorDto {
889 fn from(error: amlich_core::almanac::na_am::NaAmError) -> Self {
890 let (error_type, message) = match error {
891 amlich_core::almanac::na_am::NaAmError::InvalidCycleIndex => (
892 "invalid_cycle_index".to_string(),
893 "Cycle index must be between 1 and 60".to_string(),
894 ),
895 amlich_core::almanac::na_am::NaAmError::InvalidStemBranchPair => (
896 "invalid_stem_branch_pair".to_string(),
897 "Stem and branch must have matching parity (both odd or both even)".to_string(),
898 ),
899 amlich_core::almanac::na_am::NaAmError::UnknownStem => (
900 "unknown_stem".to_string(),
901 "Unknown heavenly stem name".to_string(),
902 ),
903 amlich_core::almanac::na_am::NaAmError::UnknownBranch => (
904 "unknown_branch".to_string(),
905 "Unknown earthly branch name".to_string(),
906 ),
907 };
908
909 Self {
910 error: error_type,
911 message,
912 }
913 }
914}
915
916impl From<&amlich_core::almanac::data::NaAmEntry> for NaAmLookupResultDto {
917 fn from(entry: &amlich_core::almanac::data::NaAmEntry) -> Self {
918 use amlich_core::types::{CAN, CHI};
920 let can_idx = CAN.iter().position(|&c| c == entry.can).unwrap_or(0);
921 let chi_idx = CHI.iter().position(|&c| c == entry.chi).unwrap_or(0);
922
923 use amlich_core::almanac::sexagenary_cycle::canchi_to_cycle_index;
925 let cycle_index = canchi_to_cycle_index(can_idx, chi_idx).unwrap_or(1);
926
927 use amlich_core::almanac::data::get_ruleset_data;
929 let ruleset =
930 get_ruleset_data("vn_baseline_v1").expect("default ruleset should be available");
931 let meta = &ruleset.na_am_meta;
932
933 Self {
934 cycle_index,
935 can: entry.can.clone(),
936 chi: entry.chi.clone(),
937 na_am: entry.na_am.clone(),
938 element: entry.element.clone(),
939 source_id: meta.source_id.clone(),
940 method: meta.method.clone(),
941 profile: ruleset.profile.clone(),
942 }
943 }
944}