1use crate::domain::entities::{DeviceType, IoTReading, LinkyDevice, LinkyProvider, MetricType};
2use chrono::{DateTime, Utc};
3use serde::{Deserialize, Serialize};
4use uuid::Uuid;
5
6#[derive(Debug, Clone, Serialize, Deserialize)]
12pub struct CreateIoTReadingDto {
13 pub building_id: Uuid,
14 pub device_type: DeviceType,
15 pub metric_type: MetricType,
16 pub value: f64,
17 pub unit: String,
18 pub timestamp: DateTime<Utc>,
19 pub source: String,
20 pub metadata: Option<serde_json::Value>,
21}
22
23#[derive(Debug, Clone, Serialize, Deserialize)]
25pub struct IoTReadingResponseDto {
26 pub id: Uuid,
27 pub building_id: Uuid,
28 pub device_type: DeviceType,
29 pub metric_type: MetricType,
30 pub value: f64,
31 pub normalized_value: f64, pub unit: String,
33 pub timestamp: DateTime<Utc>,
34 pub source: String,
35 pub metadata: Option<serde_json::Value>,
36 pub created_at: DateTime<Utc>,
37}
38
39impl From<IoTReading> for IoTReadingResponseDto {
40 fn from(reading: IoTReading) -> Self {
41 Self {
42 id: reading.id,
43 building_id: reading.building_id,
44 device_type: reading.device_type,
45 metric_type: reading.metric_type,
46 normalized_value: reading.normalized_value(),
47 value: reading.value,
48 unit: reading.unit,
49 timestamp: reading.timestamp,
50 source: reading.source,
51 metadata: reading.metadata,
52 created_at: reading.created_at,
53 }
54 }
55}
56
57#[derive(Debug, Clone, Serialize, Deserialize)]
59pub struct ConsumptionStatsDto {
60 pub building_id: Uuid,
61 pub metric_type: MetricType,
62 pub period_start: DateTime<Utc>,
63 pub period_end: DateTime<Utc>,
64 pub total_consumption: f64,
65 pub average_daily: f64,
66 pub min_value: f64,
67 pub max_value: f64,
68 pub reading_count: i64,
69 pub unit: String,
70 pub source: String,
71}
72
73#[derive(Debug, Clone, Serialize, Deserialize)]
75pub struct AnomalyDetectionDto {
76 pub reading: IoTReadingResponseDto,
77 pub is_anomalous: bool,
78 pub average_value: f64,
79 pub deviation_percentage: f64,
80 pub threshold_percentage: f64,
81}
82
83#[derive(Debug, Clone, Serialize, Deserialize)]
89pub struct ConfigureLinkyDeviceDto {
90 pub building_id: Uuid,
91 pub prm: String,
92 pub provider: LinkyProvider,
93 pub authorization_code: String, }
95
96#[derive(Debug, Clone, Serialize, Deserialize)]
98pub struct LinkyDeviceResponseDto {
99 pub id: Uuid,
100 pub building_id: Uuid,
101 pub prm: String,
102 pub provider: LinkyProvider,
103 pub last_sync_at: Option<DateTime<Utc>>,
104 pub sync_enabled: bool,
105 pub token_expires_at: Option<DateTime<Utc>>,
106 pub is_token_expired: bool,
107 pub needs_sync: bool,
108 pub api_endpoint: String,
109 pub created_at: DateTime<Utc>,
110 pub updated_at: DateTime<Utc>,
111}
112
113impl From<LinkyDevice> for LinkyDeviceResponseDto {
114 fn from(device: LinkyDevice) -> Self {
115 Self {
116 id: device.id,
117 building_id: device.building_id,
118 prm: device.prm.clone(),
119 provider: device.provider,
120 last_sync_at: device.last_sync_at,
121 sync_enabled: device.sync_enabled,
122 token_expires_at: device.token_expires_at,
123 is_token_expired: device.is_token_expired(),
124 needs_sync: device.needs_sync(),
125 api_endpoint: device.api_endpoint().to_string(),
126 created_at: device.created_at,
127 updated_at: device.updated_at,
128 }
129 }
130}
131
132#[derive(Debug, Clone, Serialize, Deserialize)]
134pub struct SyncLinkyDataDto {
135 pub building_id: Uuid,
136 pub start_date: DateTime<Utc>,
137 pub end_date: DateTime<Utc>,
138}
139
140#[derive(Debug, Clone, Serialize, Deserialize)]
142pub struct LinkySyncResponseDto {
143 pub device_id: Uuid,
144 pub sync_started_at: DateTime<Utc>,
145 pub start_date: DateTime<Utc>,
146 pub end_date: DateTime<Utc>,
147 pub readings_fetched: usize,
148 pub success: bool,
149 pub error_message: Option<String>,
150}
151
152#[derive(Debug, Clone, Serialize, Deserialize)]
158pub struct QueryIoTReadingsDto {
159 pub building_id: Uuid,
160 pub device_type: Option<DeviceType>,
161 pub metric_type: Option<MetricType>,
162 pub start_date: DateTime<Utc>,
163 pub end_date: DateTime<Utc>,
164 pub limit: Option<usize>,
165}
166
167impl Default for QueryIoTReadingsDto {
168 fn default() -> Self {
169 let now = Utc::now();
170 Self {
171 building_id: Uuid::nil(), device_type: None,
173 metric_type: None,
174 start_date: now - chrono::Duration::days(30),
175 end_date: now,
176 limit: Some(1000),
177 }
178 }
179}
180
181#[derive(Debug, Clone, Serialize, Deserialize)]
183pub struct DailyAggregateDto {
184 pub building_id: Uuid,
185 pub device_type: DeviceType,
186 pub metric_type: MetricType,
187 pub day: DateTime<Utc>,
188 pub avg_value: f64,
189 pub min_value: f64,
190 pub max_value: f64,
191 pub total_value: f64,
192 pub reading_count: i64,
193 pub source: String,
194}
195
196#[derive(Debug, Clone, Serialize, Deserialize)]
198pub struct MonthlyAggregateDto {
199 pub building_id: Uuid,
200 pub device_type: DeviceType,
201 pub metric_type: MetricType,
202 pub month: DateTime<Utc>,
203 pub avg_value: f64,
204 pub min_value: f64,
205 pub max_value: f64,
206 pub total_value: f64,
207 pub reading_count: i64,
208 pub source: String,
209}
210
211#[cfg(test)]
212mod tests {
213 use super::*;
214
215 #[test]
216 fn test_iot_reading_response_dto_from_entity() {
217 let reading = IoTReading::new(
218 Uuid::new_v4(),
219 DeviceType::ElectricityMeter,
220 MetricType::ElectricityConsumption,
221 150.5,
222 "kWh".to_string(),
223 Utc::now(),
224 "linky_ores".to_string(),
225 )
226 .unwrap();
227
228 let dto: IoTReadingResponseDto = reading.clone().into();
229
230 assert_eq!(dto.id, reading.id);
231 assert_eq!(dto.value, 150.5);
232 assert_eq!(dto.normalized_value, 150.5);
233 assert_eq!(dto.metric_type, MetricType::ElectricityConsumption);
234 }
235
236 #[test]
237 fn test_linky_device_response_dto_from_entity() {
238 let device = LinkyDevice::new(
239 Uuid::new_v4(),
240 "12345678901234".to_string(),
241 LinkyProvider::Enedis,
242 "encrypted_token".to_string(),
243 )
244 .unwrap();
245
246 let dto: LinkyDeviceResponseDto = device.clone().into();
247
248 assert_eq!(dto.id, device.id);
249 assert_eq!(dto.prm, "12345678901234");
250 assert_eq!(dto.provider, LinkyProvider::Enedis);
251 assert_eq!(dto.api_endpoint, "https://ext.hml.myelectricaldata.fr/v1");
252 assert!(dto.needs_sync); }
254
255 #[test]
256 fn test_query_iot_readings_dto_default() {
257 let query = QueryIoTReadingsDto::default();
258
259 assert_eq!(query.limit, Some(1000));
260 assert!(query.device_type.is_none());
261 assert!(query.metric_type.is_none());
262
263 let days_diff = (query.end_date - query.start_date).num_days();
264 assert_eq!(days_diff, 30);
265 }
266}