Skip to main content

macro_factor_api/
models.rs

1use chrono::NaiveDate;
2use serde::{Deserialize, Serialize};
3
4/// A weight/scale measurement entry.
5#[derive(Debug, Clone, Serialize, Deserialize)]
6pub struct ScaleEntry {
7    pub date: NaiveDate,
8    /// Weight in kg
9    pub weight: f64,
10    /// Body fat percentage
11    pub body_fat: Option<f64>,
12    /// Source (e.g. "m" = manual, "a" = Apple Health)
13    pub source: Option<String>,
14}
15
16/// A daily nutrition summary.
17#[derive(Debug, Clone, Serialize, Deserialize)]
18pub struct NutritionSummary {
19    pub date: NaiveDate,
20    /// Calories (kcal)
21    pub calories: Option<f64>,
22    /// Protein (g)
23    pub protein: Option<f64>,
24    /// Carbs (g)
25    pub carbs: Option<f64>,
26    /// Fat (g)
27    pub fat: Option<f64>,
28    /// Sugar (g) — nutrient code 269
29    pub sugar: Option<f64>,
30    /// Fiber (g) — nutrient code 291
31    pub fiber: Option<f64>,
32    /// Source
33    pub source: Option<String>,
34}
35
36/// An individual food log entry.
37///
38/// Raw values (`calories_raw`, `protein_raw`, etc.) are per serving size (`serving_grams`).
39/// Use the accessor methods (`.calories()`, `.protein()`, etc.) to get actual consumed amounts,
40/// which apply the quantity multiplier: `raw * (user_qty * unit_weight) / serving_grams`.
41#[derive(Debug, Clone, Serialize, Deserialize)]
42pub struct FoodEntry {
43    pub date: NaiveDate,
44    /// Entry timestamp ID
45    pub entry_id: String,
46    /// Food name
47    pub name: Option<String>,
48    /// Brand
49    pub brand: Option<String>,
50    /// Calories per serving size (kcal)
51    pub calories_raw: Option<f64>,
52    /// Protein per serving size (g)
53    pub protein_raw: Option<f64>,
54    /// Carbs per serving size (g)
55    pub carbs_raw: Option<f64>,
56    /// Fat per serving size (g)
57    pub fat_raw: Option<f64>,
58    /// Grams per serving size ("g" field)
59    pub serving_grams: Option<f64>,
60    /// User quantity in display units ("y" field)
61    pub user_qty: Option<f64>,
62    /// Grams per display unit ("w" field)
63    pub unit_weight: Option<f64>,
64    /// Quantity in serving units ("q" field)
65    pub quantity: Option<f64>,
66    /// Serving unit
67    pub serving_unit: Option<String>,
68    /// Hour logged
69    pub hour: Option<String>,
70    /// Minute logged
71    pub minute: Option<String>,
72    /// Source type: "t" = typesense, "n" = custom
73    pub source_type: Option<String>,
74    /// Food ID
75    pub food_id: Option<String>,
76}
77
78impl FoodEntry {
79    /// Multiplier to convert per-serving values to actual consumed amounts.
80    pub fn multiplier(&self) -> Option<f64> {
81        match (self.serving_grams, self.user_qty, self.unit_weight) {
82            (Some(g), Some(y), Some(w)) if g > 0.0 => Some((y * w) / g),
83            _ => None,
84        }
85    }
86
87    /// Actual calories consumed.
88    pub fn calories(&self) -> Option<f64> {
89        match (self.calories_raw, self.multiplier()) {
90            (Some(v), Some(m)) => Some(v * m),
91            _ => self.calories_raw,
92        }
93    }
94
95    /// Actual protein consumed (g).
96    pub fn protein(&self) -> Option<f64> {
97        match (self.protein_raw, self.multiplier()) {
98            (Some(v), Some(m)) => Some(v * m),
99            _ => self.protein_raw,
100        }
101    }
102
103    /// Actual carbs consumed (g).
104    pub fn carbs(&self) -> Option<f64> {
105        match (self.carbs_raw, self.multiplier()) {
106            (Some(v), Some(m)) => Some(v * m),
107            _ => self.carbs_raw,
108        }
109    }
110
111    /// Actual fat consumed (g).
112    pub fn fat(&self) -> Option<f64> {
113        match (self.fat_raw, self.multiplier()) {
114            (Some(v), Some(m)) => Some(v * m),
115            _ => self.fat_raw,
116        }
117    }
118
119    /// Actual weight consumed (g).
120    pub fn weight_grams(&self) -> Option<f64> {
121        match (self.user_qty, self.unit_weight) {
122            (Some(y), Some(w)) => Some(y * w),
123            _ => None,
124        }
125    }
126}
127
128/// Daily step count entry.
129#[derive(Debug, Clone, Serialize, Deserialize)]
130pub struct StepEntry {
131    pub date: NaiveDate,
132    /// Step count
133    pub steps: u64,
134    /// Source
135    pub source: Option<String>,
136}
137
138/// Daily macro/calorie goals from the planner.
139#[derive(Debug, Clone, Serialize, Deserialize)]
140pub struct Goals {
141    /// Daily calorie targets per day of week (Mon=0..Sun=6)
142    pub calories: Vec<f64>,
143    /// Daily protein targets (g) per day of week
144    pub protein: Vec<f64>,
145    /// Daily carbs targets (g) per day of week
146    pub carbs: Vec<f64>,
147    /// Daily fat targets (g) per day of week
148    pub fat: Vec<f64>,
149    /// Current TDEE estimate
150    pub tdee: Option<f64>,
151    /// Program style (e.g. "coached")
152    pub program_style: Option<String>,
153    /// Program type (e.g. "performance")
154    pub program_type: Option<String>,
155}
156
157/// User profile from the top-level user document.
158#[derive(Debug, Clone, Serialize, Deserialize)]
159pub struct UserProfile {
160    pub id: String,
161    pub name: Option<String>,
162    pub email: Option<String>,
163    pub sex: Option<String>,
164    pub dob: Option<String>,
165    pub height: Option<f64>,
166    pub height_units: Option<String>,
167    pub weight_units: Option<String>,
168    pub calorie_units: Option<String>,
169}