1use chrono::{DateTime, NaiveDate, Utc};
4use serde::{Deserialize, Serialize};
5
6#[derive(Debug, Clone, Serialize, Deserialize)]
8pub struct Profile {
9 pub profile_id: i32,
10 pub display_name: String,
11 pub user_id: Option<i64>,
12 pub created_at: Option<DateTime<Utc>>,
13 pub last_sync_at: Option<DateTime<Utc>>,
14}
15
16#[derive(Debug, Clone, Serialize, Deserialize)]
18pub struct Activity {
19 pub activity_id: i64,
20 pub profile_id: i32,
21 pub activity_name: Option<String>,
22 pub activity_type: Option<String>,
23 pub start_time_local: Option<DateTime<Utc>>,
24 pub start_time_gmt: Option<DateTime<Utc>>,
25 pub duration_sec: Option<f64>,
26 pub distance_m: Option<f64>,
27 pub calories: Option<i32>,
28 pub avg_hr: Option<i32>,
29 pub max_hr: Option<i32>,
30 pub avg_speed: Option<f64>,
31 pub max_speed: Option<f64>,
32 pub elevation_gain: Option<f64>,
33 pub elevation_loss: Option<f64>,
34 pub avg_cadence: Option<f64>,
35 pub avg_power: Option<i32>,
36 pub normalized_power: Option<i32>,
37 pub training_effect: Option<f64>,
38 pub training_load: Option<f64>,
39 pub start_lat: Option<f64>,
40 pub start_lon: Option<f64>,
41 pub end_lat: Option<f64>,
42 pub end_lon: Option<f64>,
43 pub ground_contact_time: Option<f64>,
44 pub vertical_oscillation: Option<f64>,
45 pub stride_length: Option<f64>,
46 pub location_name: Option<String>,
47 pub raw_json: Option<serde_json::Value>,
48}
49
50#[derive(Debug, Clone, Serialize, Deserialize)]
52pub struct TrackPoint {
53 pub id: Option<i64>,
54 pub activity_id: i64,
55 pub timestamp: DateTime<Utc>,
56 pub lat: Option<f64>,
57 pub lon: Option<f64>,
58 pub elevation: Option<f64>,
59 pub heart_rate: Option<i32>,
60 pub cadence: Option<i32>,
61 pub power: Option<i32>,
62 pub speed: Option<f64>,
63}
64
65#[derive(Debug, Clone, Serialize, Deserialize)]
67pub struct DailyHealth {
68 pub id: Option<i64>,
69 pub profile_id: i32,
70 pub date: NaiveDate,
71 pub steps: Option<i32>,
72 pub step_goal: Option<i32>,
73 pub total_calories: Option<i32>,
74 pub active_calories: Option<i32>,
75 pub bmr_calories: Option<i32>,
76 pub resting_hr: Option<i32>,
77 pub sleep_seconds: Option<i32>,
78 pub deep_sleep_seconds: Option<i32>,
79 pub light_sleep_seconds: Option<i32>,
80 pub rem_sleep_seconds: Option<i32>,
81 pub sleep_score: Option<i32>,
82 pub avg_stress: Option<i32>,
83 pub max_stress: Option<i32>,
84 pub body_battery_start: Option<i32>,
85 pub body_battery_end: Option<i32>,
86 pub hrv_weekly_avg: Option<i32>,
87 pub hrv_last_night: Option<i32>,
88 pub hrv_status: Option<String>,
89 pub avg_respiration: Option<f64>,
90 pub avg_spo2: Option<i32>,
91 pub lowest_spo2: Option<i32>,
92 pub hydration_ml: Option<i32>,
93 pub moderate_intensity_min: Option<i32>,
94 pub vigorous_intensity_min: Option<i32>,
95 pub raw_json: Option<serde_json::Value>,
96}
97
98#[derive(Debug, Clone, Serialize, Deserialize)]
100pub struct PerformanceMetrics {
101 pub id: Option<i64>,
102 pub profile_id: i32,
103 pub date: NaiveDate,
104 pub vo2max: Option<f64>,
105 pub fitness_age: Option<i32>,
106 pub training_readiness: Option<i32>,
107 pub training_status: Option<String>,
108 pub lactate_threshold_hr: Option<i32>,
109 pub lactate_threshold_pace: Option<f64>,
110 pub race_5k_sec: Option<i32>,
111 pub race_10k_sec: Option<i32>,
112 pub race_half_sec: Option<i32>,
113 pub race_marathon_sec: Option<i32>,
114 pub endurance_score: Option<i32>,
115 pub hill_score: Option<i32>,
116 pub raw_json: Option<serde_json::Value>,
117}
118
119#[derive(Debug, Clone, Serialize, Deserialize)]
121pub struct WeightEntry {
122 pub id: Option<i64>,
123 pub profile_id: i32,
124 pub date: NaiveDate,
125 pub weight_kg: Option<f64>,
126 pub bmi: Option<f64>,
127 pub body_fat_pct: Option<f64>,
128 pub muscle_mass_kg: Option<f64>,
129}
130
131#[derive(Debug, Clone, Serialize, Deserialize)]
133pub struct SyncState {
134 pub profile_id: i32,
135 pub data_type: String,
136 pub last_sync_date: Option<NaiveDate>,
137 pub last_activity_id: Option<i64>,
138}
139
140#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
142#[serde(rename_all = "lowercase")]
143pub enum TaskStatus {
144 Pending,
145 InProgress,
146 Completed,
147 Failed,
148}
149
150impl std::fmt::Display for TaskStatus {
151 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
152 match self {
153 TaskStatus::Pending => write!(f, "pending"),
154 TaskStatus::InProgress => write!(f, "in_progress"),
155 TaskStatus::Completed => write!(f, "completed"),
156 TaskStatus::Failed => write!(f, "failed"),
157 }
158 }
159}
160
161#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
163#[serde(rename_all = "snake_case")]
164pub enum SyncPipeline {
165 #[default]
166 Frontier,
167 Backfill,
168}
169
170impl std::fmt::Display for SyncPipeline {
171 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
172 match self {
173 SyncPipeline::Frontier => write!(f, "frontier"),
174 SyncPipeline::Backfill => write!(f, "backfill"),
175 }
176 }
177}
178
179#[derive(Debug, Clone, Serialize, Deserialize)]
181#[serde(tag = "type", rename_all = "snake_case")]
182pub enum SyncTaskType {
183 Activities {
184 start: u32,
185 limit: u32,
186 #[serde(default)]
187 min_date: Option<NaiveDate>,
188 #[serde(default)]
189 max_date: Option<NaiveDate>,
190 },
191 ActivityDetail {
192 activity_id: i64,
193 },
194 DownloadGpx {
195 activity_id: i64,
196 #[serde(default)]
197 activity_name: Option<String>,
198 #[serde(default)]
199 activity_date: Option<String>,
200 },
201 DailyHealth {
202 date: NaiveDate,
203 },
204 Performance {
205 date: NaiveDate,
206 },
207 Weight {
208 from: NaiveDate,
209 to: NaiveDate,
210 },
211 GenerateEmbeddings {
212 activity_ids: Vec<i64>,
213 },
214}
215
216#[derive(Debug, Clone, Serialize, Deserialize)]
218pub struct SyncTask {
219 pub id: Option<i64>,
220 pub profile_id: i32,
221 pub task_type: SyncTaskType,
222 #[serde(default)]
223 pub pipeline: SyncPipeline,
224 pub status: TaskStatus,
225 pub attempts: i32,
226 pub last_error: Option<String>,
227 pub created_at: Option<DateTime<Utc>>,
228 pub next_retry_at: Option<DateTime<Utc>>,
229 pub completed_at: Option<DateTime<Utc>>,
230}
231
232impl SyncTask {
233 pub fn new(profile_id: i32, pipeline: SyncPipeline, task_type: SyncTaskType) -> Self {
234 Self {
235 id: None,
236 profile_id,
237 task_type,
238 pipeline,
239 status: TaskStatus::Pending,
240 attempts: 0,
241 last_error: None,
242 created_at: None,
243 next_retry_at: None,
244 completed_at: None,
245 }
246 }
247}