1use ifc_model::{Entity, EntityId, Transaction, Value};
4
5use crate::error::{TabularError, TabularResult};
6
7const REGULAR: &str = "IFCREGULARTIMESERIES";
8const IRREGULAR: &str = "IFCIRREGULARTIMESERIES";
9const VALUE: &str = "IFCTIMESERIESVALUE";
10const IRREGULAR_VALUE: &str = "IFCIRREGULARTIMESERIESVALUE";
11
12pub fn add_time_series_value(tx: &mut Transaction, values: Vec<Value>) -> TabularResult<EntityId> {
18 if values.is_empty() {
19 return Err(TabularError::EmptyList {
20 entity: VALUE,
21 attribute: "ListValues",
22 });
23 }
24 Ok(tx.create(Entity::new(VALUE, vec![Value::List(values)])))
25}
26
27pub fn add_irregular_value(
36 tx: &mut Transaction,
37 timestamp: &str,
38 values: Vec<Value>,
39) -> TabularResult<EntityId> {
40 if timestamp.trim().is_empty() {
41 return Err(TabularError::BlankRequired {
42 entity: IRREGULAR_VALUE,
43 attribute: "TimeStamp",
44 });
45 }
46 if values.is_empty() {
47 return Err(TabularError::EmptyList {
48 entity: IRREGULAR_VALUE,
49 attribute: "ListValues",
50 });
51 }
52 let attributes = vec![Value::Text(timestamp.into()), Value::List(values)];
53 Ok(tx.create(Entity::new(IRREGULAR_VALUE, attributes)))
54}
55
56#[derive(Debug, Clone, Copy, Default)]
58#[non_exhaustive]
59pub struct SeriesDraft<'a> {
60 pub name: &'a str,
62 pub description: Option<&'a str>,
64 pub start_time: &'a str,
66 pub end_time: &'a str,
68 pub data_type: &'a str,
70 pub data_origin: &'a str,
72 pub user_defined_data_origin: Option<&'a str>,
74 pub unit: Option<EntityId>,
76}
77
78impl<'a> SeriesDraft<'a> {
79 #[must_use]
81 pub fn new(
82 name: &'a str,
83 start_time: &'a str,
84 end_time: &'a str,
85 data_type: &'a str,
86 data_origin: &'a str,
87 ) -> Self {
88 Self {
89 name,
90 description: None,
91 start_time,
92 end_time,
93 data_type,
94 data_origin,
95 user_defined_data_origin: None,
96 unit: None,
97 }
98 }
99
100 #[must_use]
102 pub fn description(mut self, value: &'a str) -> Self {
103 self.description = Some(value);
104 self
105 }
106
107 #[must_use]
109 pub fn user_defined_data_origin(mut self, value: &'a str) -> Self {
110 self.user_defined_data_origin = Some(value);
111 self
112 }
113
114 #[must_use]
116 pub fn unit(mut self, value: EntityId) -> Self {
117 self.unit = Some(value);
118 self
119 }
120}
121
122fn supertype_slots(entity: &'static str, draft: SeriesDraft<'_>) -> TabularResult<Vec<Value>> {
123 for (attribute, text) in [
124 ("Name", draft.name),
125 ("StartTime", draft.start_time),
126 ("EndTime", draft.end_time),
127 ("TimeSeriesDataType", draft.data_type),
128 ("DataOrigin", draft.data_origin),
129 ] {
130 if text.trim().is_empty() {
131 return Err(TabularError::BlankRequired { entity, attribute });
132 }
133 }
134 Ok(vec![
135 Value::Text(draft.name.into()),
136 draft
137 .description
138 .map_or(Value::Null, |text| Value::Text(text.into())),
139 Value::Text(draft.start_time.into()),
140 Value::Text(draft.end_time.into()),
141 Value::Enum(draft.data_type.into()),
142 Value::Enum(draft.data_origin.into()),
143 draft
144 .user_defined_data_origin
145 .map_or(Value::Null, |text| Value::Text(text.into())),
146 draft.unit.map_or(Value::Null, Value::Ref),
147 ])
148}
149
150pub fn add_regular_time_series(
160 tx: &mut Transaction,
161 draft: SeriesDraft<'_>,
162 time_step: f64,
163 values: &[EntityId],
164) -> TabularResult<EntityId> {
165 let mut attributes = supertype_slots(REGULAR, draft)?;
166 if !time_step.is_finite() || time_step <= 0.0 {
167 return Err(TabularError::NotFinite {
168 entity: REGULAR,
169 attribute: "TimeStep",
170 });
171 }
172 if values.is_empty() {
173 return Err(TabularError::EmptyList {
174 entity: REGULAR,
175 attribute: "Values",
176 });
177 }
178 attributes.push(Value::Real(time_step));
179 attributes.push(Value::List(
180 values.iter().copied().map(Value::Ref).collect(),
181 ));
182 Ok(tx.create(Entity::new(REGULAR, attributes)))
183}
184
185pub fn add_irregular_time_series(
191 tx: &mut Transaction,
192 draft: SeriesDraft<'_>,
193 values: &[EntityId],
194) -> TabularResult<EntityId> {
195 let mut attributes = supertype_slots(IRREGULAR, draft)?;
196 if values.is_empty() {
197 return Err(TabularError::EmptyList {
198 entity: IRREGULAR,
199 attribute: "Values",
200 });
201 }
202 attributes.push(Value::List(
203 values.iter().copied().map(Value::Ref).collect(),
204 ));
205 Ok(tx.create(Entity::new(IRREGULAR, attributes)))
206}