reactive_graph_std_date_time/implementation/
time_graph_impl.rs

1use std::sync::Arc;
2use std::time::Instant;
3
4use async_trait::async_trait;
5use chrono::Datelike;
6use log::info;
7use log::trace;
8use serde_json::json;
9
10use crate::api::TimeGraph;
11use reactive_graph_graph::prelude::*;
12use reactive_graph_plugin_api::Component;
13use reactive_graph_plugin_api::EntityInstanceManager;
14use reactive_graph_plugin_api::RelationInstanceManager;
15use reactive_graph_plugin_api::component_alias;
16
17use reactive_graph_reactive_model_impl::ReactiveEntity;
18use reactive_graph_reactive_model_impl::ReactiveProperties;
19use reactive_graph_reactive_model_impl::ReactiveRelation;
20use reactive_graph_std_date_time_model::DayProperties::DAY_OF_MONTH;
21use reactive_graph_std_date_time_model::DayProperties::ISO8601;
22use reactive_graph_std_date_time_model::ENTITY_TYPE_DAY;
23use reactive_graph_std_date_time_model::ENTITY_TYPE_MONTH;
24use reactive_graph_std_date_time_model::ENTITY_TYPE_YEAR;
25use reactive_graph_std_date_time_model::MonthProperties::MONTH_AND_YEAR;
26use reactive_graph_std_date_time_model::MonthProperties::MONTH_OF_YEAR;
27use reactive_graph_std_date_time_model::RELATION_TYPE_DAY_OF_MONTH;
28use reactive_graph_std_date_time_model::RELATION_TYPE_FIRST_DAY;
29use reactive_graph_std_date_time_model::RELATION_TYPE_FIRST_MONTH;
30use reactive_graph_std_date_time_model::RELATION_TYPE_LAST_DAY;
31use reactive_graph_std_date_time_model::RELATION_TYPE_LAST_MONTH;
32use reactive_graph_std_date_time_model::RELATION_TYPE_MONTH_OF_YEAR;
33use reactive_graph_std_date_time_model::RELATION_TYPE_NEXT_DAY;
34use reactive_graph_std_date_time_model::RELATION_TYPE_NEXT_MONTH;
35use reactive_graph_std_date_time_model::RELATION_TYPE_NEXT_YEAR;
36use reactive_graph_std_date_time_model::YearProperties::LEAP;
37use reactive_graph_std_date_time_model::YearProperties::YEAR;
38use uuid::Uuid;
39
40#[derive(Component)]
41pub struct TimeGraphImpl {
42    #[component(default = "crate::plugin::entity_instance_manager")]
43    entity_instance_manager: Arc<dyn EntityInstanceManager + Send + Sync>,
44
45    #[component(default = "crate::plugin::relation_instance_manager")]
46    relation_instance_manager: Arc<dyn RelationInstanceManager + Send + Sync>,
47}
48
49impl TimeGraphImpl {
50    async fn create_time_graph(&self) {
51        self.create_years().await;
52    }
53
54    async fn create_years(&self) {
55        let mut previous_year = None;
56        let mut previous_year_last_month: Option<ReactiveEntity> = None;
57        let mut previous_year_last_day: Option<ReactiveEntity> = None;
58        let current_year = chrono::Utc::now().year() as i64;
59        for year in current_year - 1..current_year + 1 {
60            previous_year = match self.create_year(year).await {
61                Some(current_year) => {
62                    self.create_next_year(&previous_year, &current_year).await;
63                    let (current_year_last_month, current_year_last_day) =
64                        self.create_months(&current_year, previous_year_last_month, previous_year_last_day).await;
65                    previous_year_last_month = current_year_last_month;
66                    previous_year_last_day = current_year_last_day;
67                    Some(current_year)
68                }
69                None => None,
70            }
71        }
72    }
73
74    async fn create_year(&self, year: i64) -> Option<ReactiveEntity> {
75        trace!("Create year {}", year);
76        let id = Uuid::new_v4();
77        let properties = PropertyInstances::new().property(YEAR, json!(year)).property(LEAP, json!(is_leap_year(year)));
78        let reactive_entity = ReactiveEntity::builder()
79            .ty(ENTITY_TYPE_YEAR.clone())
80            .id(id)
81            .properties(ReactiveProperties::new_with_id_from_properties(id, properties))
82            .build();
83        self.entity_instance_manager.register(reactive_entity).ok()
84    }
85
86    async fn create_next_year(&self, previous_year: &Option<ReactiveEntity>, next_year: &ReactiveEntity) {
87        let Some(previous_year) = previous_year else {
88            return;
89        };
90        let instance_id = format!("{}__{}", previous_year.as_i64(YEAR).unwrap_or(0), next_year.as_i64(YEAR).unwrap_or(0));
91        let ty = RelationInstanceTypeId::new_unique_for_instance_id(RELATION_TYPE_NEXT_YEAR.clone(), instance_id);
92        let reactive_relation = ReactiveRelation::builder_with_entities(previous_year.clone(), &ty, next_year.clone()).build();
93        let _ = self.relation_instance_manager.register(reactive_relation);
94    }
95
96    async fn create_months(
97        &self,
98        current_year: &ReactiveEntity,
99        previous_year_last_month: Option<ReactiveEntity>,
100        previous_year_last_day: Option<ReactiveEntity>,
101    ) -> (Option<ReactiveEntity>, Option<ReactiveEntity>) {
102        let Some(year) = current_year.as_i64(YEAR) else {
103            return (None, None);
104        };
105        let mut previous_month = previous_year_last_month;
106        let mut previous_month_last_day = previous_year_last_day;
107        // let mut last_month_last_day: Option<Arc<ReactiveEntityInstance>> = None;
108        for month in 1..=12 {
109            let current_month = match self.create_month(year, month).await {
110                Some(current_month) => {
111                    self.create_next_month(&previous_month, &current_month).await;
112                    self.create_month_of_year(current_year, &current_month).await;
113                    previous_month_last_day = self.create_days(current_year, &current_month, previous_month_last_day).await;
114                    Some(current_month)
115                }
116                None => None,
117            };
118            if month == 1 {
119                previous_month = match current_month {
120                    Some(current_month) => {
121                        self.create_first_month(current_year, &current_month).await;
122                        // create_next_month(context, previous_year_last_month, &current_month);
123                        Some(current_month)
124                    }
125                    None => None,
126                };
127            } else if month == 12 {
128                previous_month = match current_month {
129                    Some(current_month) => {
130                        self.create_last_month(current_year, &current_month).await;
131                        Some(current_month)
132                    }
133                    None => None,
134                };
135                // last_month_last_day = previous_month_last_day;
136            } else {
137                previous_month = current_month;
138            }
139        }
140        (previous_month, previous_month_last_day)
141    }
142
143    async fn create_month(&self, year: i64, month: u64) -> Option<ReactiveEntity> {
144        let id = Uuid::new_v4();
145        let properties = PropertyInstances::new()
146            .property(MONTH_OF_YEAR, json!(month))
147            .property(MONTH_AND_YEAR, json!(format!("{:04}-{:02}", year, month)));
148        let reactive_entity = ReactiveEntity::builder()
149            .ty(ENTITY_TYPE_MONTH.clone())
150            .id(id)
151            .properties(ReactiveProperties::new_with_id_from_properties(id, properties))
152            .build();
153        self.entity_instance_manager.register(reactive_entity).ok()
154    }
155
156    async fn create_month_of_year(&self, current_year: &ReactiveEntity, month: &ReactiveEntity) {
157        let Some(year) = current_year.as_i64(YEAR) else {
158            return;
159        };
160        let Some(month_of_year) = month.as_u64(MONTH_OF_YEAR) else {
161            return;
162        };
163        let instance_id = format!("{:04}__{:02}", year, month_of_year);
164        let ty = RelationInstanceTypeId::new_unique_for_instance_id(RELATION_TYPE_MONTH_OF_YEAR.clone(), instance_id);
165        let reactive_relation = ReactiveRelation::builder_with_entities(current_year.clone(), &ty, month.clone()).build();
166        let _ = self.relation_instance_manager.register(reactive_relation);
167    }
168
169    async fn create_first_month(&self, current_year: &ReactiveEntity, first_month: &ReactiveEntity) {
170        let instance_id = format!("{:04}__{:02}", current_year.as_i64(YEAR).unwrap_or(0), first_month.as_u64(MONTH_OF_YEAR).unwrap_or(0));
171        let ty = RelationInstanceTypeId::new_unique_for_instance_id(RELATION_TYPE_FIRST_MONTH.clone(), instance_id);
172        let reactive_relation = ReactiveRelation::builder_with_entities(current_year.clone(), &ty, first_month.clone()).build();
173        let _ = self.relation_instance_manager.register(reactive_relation);
174    }
175
176    async fn create_last_month(&self, current_year: &ReactiveEntity, last_month: &ReactiveEntity) {
177        let instance_id = format!("{:04}__{:02}", current_year.as_i64(YEAR).unwrap_or(0), last_month.as_u64(MONTH_OF_YEAR).unwrap_or(0));
178        let ty = RelationInstanceTypeId::new_unique_for_instance_id(RELATION_TYPE_LAST_MONTH.clone(), instance_id);
179        let reactive_relation = ReactiveRelation::builder_with_entities(current_year.clone(), &ty, last_month.clone()).build();
180        let _ = self.relation_instance_manager.register(reactive_relation);
181    }
182
183    async fn create_next_month(&self, previous_month: &Option<ReactiveEntity>, next_month: &ReactiveEntity) {
184        let Some(previous_month) = previous_month else {
185            return;
186        };
187        let instance_id = format!(
188            "{}__{}",
189            previous_month.as_string(MONTH_AND_YEAR).unwrap_or_default(),
190            next_month.as_string(MONTH_AND_YEAR).unwrap_or_default()
191        );
192        let ty = RelationInstanceTypeId::new_unique_for_instance_id(RELATION_TYPE_NEXT_MONTH.clone(), instance_id);
193        let reactive_relation = ReactiveRelation::builder_with_entities(previous_month.clone(), &ty, next_month.clone()).build();
194        let _ = self.relation_instance_manager.register(reactive_relation);
195    }
196
197    async fn create_days(
198        &self,
199        current_year: &ReactiveEntity,
200        current_month: &ReactiveEntity,
201        previous_month_last_day: Option<ReactiveEntity>,
202    ) -> Option<ReactiveEntity> {
203        let year = current_year.as_i64(YEAR)?;
204        let month_of_year = current_month.as_u64(MONTH_OF_YEAR)?;
205        let last_day_of_month = last_day_of_month(year, month_of_year)?;
206        let mut previous_day = previous_month_last_day;
207        for day_of_month in 1..=last_day_of_month {
208            let current_day = match self.create_day(year, month_of_year, day_of_month).await {
209                Some(current_day) => {
210                    self.create_next_day(&previous_day, &current_day).await;
211                    self.create_day_of_month(current_month, &current_day).await;
212                    Some(current_day)
213                }
214                None => None,
215            };
216            if day_of_month == 1 {
217                previous_day = match current_day {
218                    Some(current_day) => {
219                        self.create_first_day(current_month, &current_day).await;
220                        // create_next_day(context, previous_month_last_day, &current_day);
221                        Some(current_day)
222                    }
223                    None => None,
224                };
225            } else if day_of_month == last_day_of_month {
226                previous_day = match current_day {
227                    Some(current_day) => {
228                        self.create_last_day(current_month, &current_day).await;
229                        Some(current_day)
230                    }
231                    None => None,
232                };
233            } else {
234                previous_day = current_day;
235            }
236        }
237        previous_day
238    }
239
240    async fn create_day(&self, year: i64, month: u64, day: u64) -> Option<ReactiveEntity> {
241        let iso8601 = format!("{:04}-{:02}-{:02}", year, month, day);
242        let id = Uuid::new_v4();
243        let properties = PropertyInstances::new().property(DAY_OF_MONTH, json!(day)).property(ISO8601, json!(iso8601));
244        let reactive_entity = ReactiveEntity::builder()
245            .ty(ENTITY_TYPE_DAY.clone())
246            .id(id)
247            .properties(ReactiveProperties::new_with_id_from_properties(id, properties))
248            .build();
249        self.entity_instance_manager.register(reactive_entity).ok()
250    }
251
252    async fn create_day_of_month(&self, current_month: &ReactiveEntity, current_day: &ReactiveEntity) {
253        let Some(month_and_year) = current_month.as_string(MONTH_AND_YEAR) else {
254            return;
255        };
256        let Some(day_of_month) = current_day.as_u64(DAY_OF_MONTH) else {
257            return;
258        };
259        let instance_id = format!("{:04}__{:02}", month_and_year, day_of_month);
260        let ty = RelationInstanceTypeId::new_unique_for_instance_id(RELATION_TYPE_DAY_OF_MONTH.clone(), instance_id);
261        let reactive_relation = ReactiveRelation::builder_with_entities(current_month.clone(), &ty, current_day.clone()).build();
262        let _ = self.relation_instance_manager.register(reactive_relation);
263    }
264
265    async fn create_first_day(&self, current_month: &ReactiveEntity, first_day: &ReactiveEntity) {
266        let Some(month_and_year) = current_month.as_string(MONTH_AND_YEAR) else {
267            return;
268        };
269        let Some(day_of_month) = first_day.as_u64(DAY_OF_MONTH) else {
270            return;
271        };
272        let instance_id = format!("{:04}__{:02}", month_and_year, day_of_month);
273        let ty = RelationInstanceTypeId::new_unique_for_instance_id(RELATION_TYPE_FIRST_DAY.clone(), instance_id);
274        let reactive_relation = ReactiveRelation::builder_with_entities(current_month.clone(), &ty, first_day.clone()).build();
275        let _ = self.relation_instance_manager.register(reactive_relation);
276    }
277
278    async fn create_last_day(&self, current_month: &ReactiveEntity, last_day: &ReactiveEntity) {
279        let Some(month_and_year) = current_month.as_string(MONTH_AND_YEAR) else {
280            return;
281        };
282        let Some(day_of_month) = last_day.as_u64(DAY_OF_MONTH) else {
283            return;
284        };
285        let instance_id = format!("{:04}__{:02}", month_and_year, day_of_month);
286        let ty = RelationInstanceTypeId::new_unique_for_instance_id(RELATION_TYPE_LAST_DAY.clone(), instance_id);
287        let reactive_relation = ReactiveRelation::builder_with_entities(current_month.clone(), &ty, last_day.clone()).build();
288        let _ = self.relation_instance_manager.register(reactive_relation);
289    }
290
291    async fn create_next_day(&self, previous_day: &Option<ReactiveEntity>, next_day: &ReactiveEntity) {
292        let Some(previous_day) = previous_day else {
293            return;
294        };
295        let instance_id = format!("{}__{}", previous_day.as_string(ISO8601).unwrap_or_default(), next_day.as_string(ISO8601).unwrap_or_default());
296        let ty = RelationInstanceTypeId::new_unique_for_instance_id(RELATION_TYPE_NEXT_DAY.clone(), instance_id);
297        let reactive_relation = ReactiveRelation::builder_with_entities(previous_day.clone(), &ty, next_day.clone()).build();
298        let _ = self.relation_instance_manager.register(reactive_relation);
299    }
300}
301
302#[async_trait]
303#[component_alias]
304impl TimeGraph for TimeGraphImpl {
305    async fn init(&self) {
306        info!("Start generating time graph");
307        let start = Instant::now();
308        self.create_time_graph().await;
309        let duration = start.elapsed();
310        info!("Successfully generated time graph in {:?}", duration);
311    }
312
313    async fn shutdown(&self) {}
314}
315
316fn is_leap_year(year: i64) -> bool {
317    year % 4 == 0 && (year % 100 != 0 || year % 400 == 0)
318}
319
320fn last_day_of_month(year: i64, month: u64) -> Option<u64> {
321    match month {
322        1 | 3 | 5 | 7 | 8 | 10 | 12 => Some(31),
323        4 | 6 | 9 | 11 => Some(30),
324        2 => {
325            if is_leap_year(year) {
326                Some(29)
327            } else {
328                Some(28)
329            }
330        }
331        _ => None,
332    }
333}