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 sleep_note: Option<String>,
84 pub avg_stress: Option<i32>,
85 pub max_stress: Option<i32>,
86 pub body_battery_start: Option<i32>,
87 pub body_battery_end: Option<i32>,
88 pub hrv_weekly_avg: Option<i32>,
89 pub hrv_last_night: Option<i32>,
90 pub hrv_status: Option<String>,
91 pub avg_respiration: Option<f64>,
92 pub avg_spo2: Option<i32>,
93 pub lowest_spo2: Option<i32>,
94 pub hydration_ml: Option<i32>,
95 pub moderate_intensity_min: Option<i32>,
96 pub vigorous_intensity_min: Option<i32>,
97 pub raw_json: Option<serde_json::Value>,
98}
99
100#[derive(Debug, Clone, Serialize, Deserialize)]
102pub struct PerformanceMetrics {
103 pub id: Option<i64>,
104 pub profile_id: i32,
105 pub date: NaiveDate,
106 pub vo2max: Option<f64>,
107 pub fitness_age: Option<i32>,
108 pub training_readiness: Option<i32>,
109 pub training_status: Option<String>,
110 pub lactate_threshold_hr: Option<i32>,
111 pub lactate_threshold_pace: Option<f64>,
112 pub race_5k_sec: Option<i32>,
113 pub race_10k_sec: Option<i32>,
114 pub race_half_sec: Option<i32>,
115 pub race_marathon_sec: Option<i32>,
116 pub endurance_score: Option<i32>,
117 pub hill_score: Option<i32>,
118 pub raw_json: Option<serde_json::Value>,
119}
120
121#[derive(Debug, Clone, Serialize, Deserialize)]
123pub struct WeightEntry {
124 pub id: Option<i64>,
125 pub profile_id: i32,
126 pub date: NaiveDate,
127 pub weight_kg: Option<f64>,
128 pub bmi: Option<f64>,
129 pub body_fat_pct: Option<f64>,
130 pub muscle_mass_kg: Option<f64>,
131}
132
133#[derive(Debug, Clone, Serialize, Deserialize)]
135pub struct SyncState {
136 pub profile_id: i32,
137 pub data_type: String,
138 pub last_sync_date: Option<NaiveDate>,
139 pub last_activity_id: Option<i64>,
140}
141
142#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
144#[serde(rename_all = "lowercase")]
145pub enum TaskStatus {
146 Pending,
147 InProgress,
148 Completed,
149 Failed,
150}
151
152impl std::fmt::Display for TaskStatus {
153 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
154 match self {
155 TaskStatus::Pending => write!(f, "pending"),
156 TaskStatus::InProgress => write!(f, "in_progress"),
157 TaskStatus::Completed => write!(f, "completed"),
158 TaskStatus::Failed => write!(f, "failed"),
159 }
160 }
161}
162
163#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
165#[serde(rename_all = "snake_case")]
166pub enum SyncPipeline {
167 #[default]
168 Frontier,
169 Backfill,
170}
171
172impl std::fmt::Display for SyncPipeline {
173 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
174 match self {
175 SyncPipeline::Frontier => write!(f, "frontier"),
176 SyncPipeline::Backfill => write!(f, "backfill"),
177 }
178 }
179}
180
181#[derive(Debug, Clone, Serialize, Deserialize)]
183#[serde(tag = "type", rename_all = "snake_case")]
184pub enum SyncTaskType {
185 Activities {
186 start: u32,
187 limit: u32,
188 #[serde(default)]
189 min_date: Option<NaiveDate>,
190 #[serde(default)]
191 max_date: Option<NaiveDate>,
192 },
193 ActivityDetail {
194 activity_id: i64,
195 },
196 DownloadGpx {
197 activity_id: i64,
198 #[serde(default)]
199 activity_name: Option<String>,
200 #[serde(default)]
201 activity_date: Option<String>,
202 },
203 DailyHealth {
204 date: NaiveDate,
205 },
206 Performance {
207 date: NaiveDate,
208 },
209 Weight {
210 from: NaiveDate,
211 to: NaiveDate,
212 },
213 GenerateEmbeddings {
214 activity_ids: Vec<i64>,
215 },
216}
217
218#[derive(Debug, Clone, Serialize, Deserialize)]
220pub struct SyncTask {
221 pub id: Option<i64>,
222 pub profile_id: i32,
223 pub task_type: SyncTaskType,
224 #[serde(default)]
225 pub pipeline: SyncPipeline,
226 pub status: TaskStatus,
227 pub attempts: i32,
228 pub last_error: Option<String>,
229 pub created_at: Option<DateTime<Utc>>,
230 pub next_retry_at: Option<DateTime<Utc>>,
231 pub completed_at: Option<DateTime<Utc>>,
232}
233
234impl SyncTask {
235 pub fn new(profile_id: i32, pipeline: SyncPipeline, task_type: SyncTaskType) -> Self {
236 Self {
237 id: None,
238 profile_id,
239 task_type,
240 pipeline,
241 status: TaskStatus::Pending,
242 attempts: 0,
243 last_error: None,
244 created_at: None,
245 next_retry_at: None,
246 completed_at: None,
247 }
248 }
249}