Skip to main content

macro_factor_api/
models.rs

1use std::collections::HashMap;
2
3use chrono::NaiveDate;
4use serde::{Deserialize, Serialize};
5
6/// A weight/scale measurement entry.
7#[derive(Debug, Clone, Serialize, Deserialize)]
8pub struct ScaleEntry {
9    pub date: NaiveDate,
10    /// Weight in kg
11    pub weight: f64,
12    /// Body fat percentage
13    pub body_fat: Option<f64>,
14    /// Source (e.g. "m" = manual, "a" = Apple Health)
15    pub source: Option<String>,
16}
17
18/// A daily nutrition summary.
19#[derive(Debug, Clone, Serialize, Deserialize)]
20pub struct NutritionSummary {
21    pub date: NaiveDate,
22    /// Calories (kcal)
23    pub calories: Option<f64>,
24    /// Protein (g)
25    pub protein: Option<f64>,
26    /// Carbs (g)
27    pub carbs: Option<f64>,
28    /// Fat (g)
29    pub fat: Option<f64>,
30    /// Sugar (g) — nutrient code 269
31    pub sugar: Option<f64>,
32    /// Fiber (g) — nutrient code 291
33    pub fiber: Option<f64>,
34    /// Source
35    pub source: Option<String>,
36}
37
38/// A serving option for a food item (from Typesense search results).
39#[derive(Debug, Clone, Serialize, Deserialize)]
40pub struct FoodServing {
41    /// Serving description (e.g. "oz", "cup", "bar")
42    pub description: String,
43    /// Amount in display units (e.g. 3.0)
44    pub amount: f64,
45    /// Weight in grams for this serving (e.g. 85.0)
46    pub gram_weight: f64,
47}
48
49/// A food search result from Typesense.
50#[derive(Debug, Clone, Serialize, Deserialize)]
51pub struct SearchFoodResult {
52    /// Food ID (e.g. "uc_5749" or "m_19306281")
53    pub food_id: String,
54    /// Food description/name
55    pub name: String,
56    /// Brand name (None if generic/common food)
57    pub brand: Option<String>,
58    /// Calories per 100g
59    pub calories_per_100g: f64,
60    /// Protein per 100g
61    pub protein_per_100g: f64,
62    /// Fat per 100g
63    pub fat_per_100g: f64,
64    /// Carbs per 100g
65    pub carbs_per_100g: f64,
66    /// Default serving option
67    pub default_serving: Option<FoodServing>,
68    /// All available serving options
69    pub servings: Vec<FoodServing>,
70    /// Image ID for the bundled SVG icon (maps to assets/foods/i{id}.svg in the app)
71    pub image_id: Option<String>,
72    /// All nutrient values per 100g, keyed by USDA nutrient code (e.g. "269"=sugar, "291"=fiber)
73    pub nutrients_per_100g: HashMap<String, f64>,
74    /// Data source (e.g. "USDAC", "MND")
75    pub source: Option<String>,
76    /// Whether this is a branded food (vs common)
77    pub branded: bool,
78}
79
80/// An individual food log entry.
81///
82/// Raw values (`calories_raw`, `protein_raw`, etc.) are per serving size (`serving_grams`).
83/// Use the accessor methods (`.calories()`, `.protein()`, etc.) to get actual consumed amounts,
84/// which apply the quantity multiplier: `raw * (user_qty * unit_weight) / serving_grams`.
85#[derive(Debug, Clone, Serialize, Deserialize)]
86pub struct FoodEntry {
87    pub date: NaiveDate,
88    /// Entry timestamp ID
89    pub entry_id: String,
90    /// Food name
91    pub name: Option<String>,
92    /// Brand
93    pub brand: Option<String>,
94    /// Calories per serving size (kcal)
95    pub calories_raw: Option<f64>,
96    /// Protein per serving size (g)
97    pub protein_raw: Option<f64>,
98    /// Carbs per serving size (g)
99    pub carbs_raw: Option<f64>,
100    /// Fat per serving size (g)
101    pub fat_raw: Option<f64>,
102    /// Grams per serving size ("g" field)
103    pub serving_grams: Option<f64>,
104    /// User quantity in display units ("y" field)
105    pub user_qty: Option<f64>,
106    /// Grams per display unit ("w" field)
107    pub unit_weight: Option<f64>,
108    /// Quantity in serving units ("q" field)
109    pub quantity: Option<f64>,
110    /// Serving unit
111    pub serving_unit: Option<String>,
112    /// Hour logged
113    pub hour: Option<String>,
114    /// Minute logged
115    pub minute: Option<String>,
116    /// Source type: "t" = typesense, "n" = custom
117    pub source_type: Option<String>,
118    /// Food ID
119    pub food_id: Option<String>,
120    /// Whether this entry has been deleted
121    pub deleted: Option<bool>,
122}
123
124impl FoodEntry {
125    /// Multiplier to convert per-serving values to actual consumed amounts.
126    pub fn multiplier(&self) -> Option<f64> {
127        match (self.serving_grams, self.user_qty, self.unit_weight) {
128            (Some(g), Some(y), Some(w)) if g > 0.0 => Some((y * w) / g),
129            _ => None,
130        }
131    }
132
133    /// Actual calories consumed.
134    pub fn calories(&self) -> Option<f64> {
135        match (self.calories_raw, self.multiplier()) {
136            (Some(v), Some(m)) => Some(v * m),
137            _ => self.calories_raw,
138        }
139    }
140
141    /// Actual protein consumed (g).
142    pub fn protein(&self) -> Option<f64> {
143        match (self.protein_raw, self.multiplier()) {
144            (Some(v), Some(m)) => Some(v * m),
145            _ => self.protein_raw,
146        }
147    }
148
149    /// Actual carbs consumed (g).
150    pub fn carbs(&self) -> Option<f64> {
151        match (self.carbs_raw, self.multiplier()) {
152            (Some(v), Some(m)) => Some(v * m),
153            _ => self.carbs_raw,
154        }
155    }
156
157    /// Actual fat consumed (g).
158    pub fn fat(&self) -> Option<f64> {
159        match (self.fat_raw, self.multiplier()) {
160            (Some(v), Some(m)) => Some(v * m),
161            _ => self.fat_raw,
162        }
163    }
164
165    /// Actual weight consumed (g).
166    pub fn weight_grams(&self) -> Option<f64> {
167        match (self.user_qty, self.unit_weight) {
168            (Some(y), Some(w)) => Some(y * w),
169            _ => None,
170        }
171    }
172}
173
174/// Daily step count entry.
175#[derive(Debug, Clone, Serialize, Deserialize)]
176pub struct StepEntry {
177    pub date: NaiveDate,
178    /// Step count
179    pub steps: u64,
180    /// Source
181    pub source: Option<String>,
182}
183
184/// Daily macro/calorie goals from the planner.
185#[derive(Debug, Clone, Serialize, Deserialize)]
186pub struct Goals {
187    /// Daily calorie targets per day of week (Mon=0..Sun=6)
188    pub calories: Vec<f64>,
189    /// Daily protein targets (g) per day of week
190    pub protein: Vec<f64>,
191    /// Daily carbs targets (g) per day of week
192    pub carbs: Vec<f64>,
193    /// Daily fat targets (g) per day of week
194    pub fat: Vec<f64>,
195    /// Current TDEE estimate
196    pub tdee: Option<f64>,
197    /// Program style (e.g. "coached")
198    pub program_style: Option<String>,
199    /// Program type (e.g. "performance")
200    pub program_type: Option<String>,
201}
202
203/// User profile from the top-level user document.
204#[derive(Debug, Clone, Serialize, Deserialize)]
205pub struct UserProfile {
206    pub id: String,
207    pub name: Option<String>,
208    pub email: Option<String>,
209    pub sex: Option<String>,
210    pub dob: Option<String>,
211    pub height: Option<f64>,
212    pub height_units: Option<String>,
213    pub weight_units: Option<String>,
214    pub calorie_units: Option<String>,
215}