# Substance-Aware Default Serving Units ## Context GitHub issue #65: When a user logs a bare count like `1 whiteclaw`, the annotation currently shows `whiteclaw 1 (355ml 5%)`. The feature request is to infer the default serving type from the substance's category, producing `whiteclaw 1can (355ml 5%)` instead. Additionally, `whiteclaw` is not yet in `alcohol.json`. ## Changes ### 1. Add whiteclaw and truly to alcohol.json **File:** `josie-health-lut/data/alcohol.json` Add under `brands`: ```json "whiteclaw": { "abv": 5.0, "category": "seltzer", "style": "hard seltzer", "default_ml": 355 }, "truly": { "abv": 5.0, "category": "seltzer", "style": "hard seltzer", "default_ml": 355 } ``` Add `"seltzer"` to `CATEGORY_DEFAULTS` in the Crystal code as well. ### 2. Add category default serving constants to AlcoholLUT **File:** `josie-health-lut/src/alcohol_lut.cr` Add constant: ```crystal CATEGORY_DEFAULT_SERVING = { "beer" => "can", "seltzer" => "can", "cider" => "can", "wine" => "glass", "spirits" => "shot", "liqueur" => "shot", "cocktail" => nil, } ``` Add method: ```crystal def self.default_serving_for(name : String) : String? brand = lookup_brand(name) return nil unless brand CATEGORY_DEFAULT_SERVING[brand[:category]]? end ``` Expose via `josie-health-lut.cr`: ```crystal def self.alcohol_default_serving(name : String) : String? AlcoholLUT.default_serving_for(name) end ``` Update `CATEGORY_DEFAULTS` to include seltzer: ```crystal "seltzer" => {abv: 5.0, default_ml: 355}, ``` ### 3. Add category default serving constants to CaffeineLUT **File:** `josie-health-lut/src/caffeine_lut.cr` Add constant: ```crystal CATEGORY_DEFAULT_SERVING = { "energy_drink" => "can", "energy_shot" => "shot", "coffee" => "cup", "tea" => "cup", "soda" => "can", } ``` Add method: ```crystal def self.default_serving_for(name : String) : String? brand = lookup_brand(name) return nil unless brand CATEGORY_DEFAULT_SERVING[brand[:category]]? end ``` Expose via `josie-health-lut.cr`: ```crystal def self.caffeine_default_serving(name : String) : String? CaffeineLUT.default_serving_for(name) end ``` ### 4. Use default serving in log_handler annotation strings **File:** `services/core/src/handlers/log_handler.cr` In `process_alcohol_dose` (line ~1106), change: ```crystal # Before: serving_str = parsed[:serving] ? "#{parsed[:count]}#{parsed[:serving]}" : "#{parsed[:count]}" # After: serving_str = if parsed[:serving] "#{parsed[:count]}#{parsed[:serving]}" else default = JosieHealth::LUT.alcohol_default_serving(substance_name) default ? "#{parsed[:count]}#{default}" : "#{parsed[:count]}" end ``` In `process_caffeine_dose` (line ~1159), same pattern: ```crystal # Before: serving_str = parsed[:serving] ? "#{parsed[:count]}#{parsed[:serving]}" : "#{parsed[:count]}" # After: serving_str = if parsed[:serving] "#{parsed[:count]}#{parsed[:serving]}" else default = JosieHealth::LUT.caffeine_default_serving(substance_name) default ? "#{parsed[:count]}#{default}" : "#{parsed[:count]}" end ``` ### 5. Update LUT calculate_dose methods for consistency **File:** `josie-health-lut/src/alcohol_lut.cr` (line 152) Same pattern in `AlcoholLUT.calculate_dose`: ```crystal serving_str = if parsed[:serving] "#{parsed[:count]}#{parsed[:serving]}" else default = CATEGORY_DEFAULT_SERVING[brand_info.try(&.[:category])]? default ? "#{parsed[:count]}#{default}" : parsed[:count].to_s end ``` **File:** `josie-health-lut/src/caffeine_lut.cr` (line 180) Same for `CaffeineLUT.calculate_dose`. ## Files Modified 1. `josie-health-lut/data/alcohol.json` -- add whiteclaw, truly brands + seltzer category 2. `josie-health-lut/src/alcohol_lut.cr` -- CATEGORY_DEFAULT_SERVING, default_serving_for, update CATEGORY_DEFAULTS 3. `josie-health-lut/src/caffeine_lut.cr` -- CATEGORY_DEFAULT_SERVING, default_serving_for 4. `josie-health-lut/src/josie-health-lut.cr` -- expose alcohol_default_serving, caffeine_default_serving 5. `services/core/src/handlers/log_handler.cr` -- use default serving in process_alcohol_dose, process_caffeine_dose ## Verification 1. `cd services && make check` -- type check passes 2. `cd services && make test` -- all tests pass 3. Manual API test: ```bash curl -u admin:zEx7eNN4LfvpJuXdb1QPbLiDfya0ur https://api.josie.health/v1/metrics/substances ``` 4. Verify annotation output by logging a test dose via the API and checking the annotation contains "1can" for whiteclaw, "1shot" for vodka, "1cup" for coffee, etc.