3737##############################################################################
3838# Foods
3939##############################################################################
40- def foods_analyze (food_ids : set , grams : float = 100 ) -> tuple :
40+ def foods_analyze (
41+ food_ids : set , grams : float = 100 , scale : float = 0 , scale_mode : str = "kcal"
42+ ) -> tuple :
4143 """
4244 Analyze a list of food_ids against stock RDA values
4345 (NOTE: only supports a single food for now... add compare foods support later)
44- TODO: support flag -t (tabular/non-visual output)
45- TODO: support flag -s (scale to 2000 kcal)
4646 """
4747
4848 ##########################################################################
@@ -101,58 +101,47 @@ def foods_analyze(food_ids: set, grams: float = 100) -> tuple:
101101 print (refuse [0 ])
102102 print (" ({0}%, by mass)" .format (refuse [1 ]))
103103
104- ######################################################################
105- # Nutrient colored RDA tree-view
106- ######################################################################
107- print_header ( "NUTRITION" )
104+ # Prepare analysis dict for day_format
105+ analysis_dict = { x [ 0 ]: x [ 1 ] for x in nut_val_tuples }
106+
107+ # Reconstruct nutrient_rows to satisfy legacy return contract (and tests )
108108 nutrient_rows = []
109- # TODO: skip small values (<1% RDA), report as color bar if RDA is available
110109 for nutrient_id , amount in nut_val_tuples :
111- # Skip zero values
112110 if not amount :
113111 continue
114-
115- # Get name and unit
116112 nutr_desc = nutrients [nutrient_id ][4 ] or nutrients [nutrient_id ][3 ]
117113 unit = nutrients [nutrient_id ][2 ]
118-
119- # Insert RDA % into row
120114 if rdas [nutrient_id ]:
121115 rda_perc = float (round (amount / rdas [nutrient_id ] * 100 , 1 ))
122116 else :
123117 rda_perc = None
124118 row = [nutrient_id , nutr_desc , rda_perc , round (amount , 2 ), unit ]
125-
126- # Add to list
127119 nutrient_rows .append (row )
128-
129- # Add to list of lists
130120 nutrients_rows .append (nutrient_rows )
131121
132- # Calculate stuff
133- _kcal = next ((x [1 ] for x in nut_val_tuples if x [0 ] == NUTR_ID_KCAL ), 0 )
134-
135- # Print view
136- # TODO: either make this function singular, or handle plural logic here
137- # TODO: support flag --absolute (use 2000 kcal; dont' scale to food kcal)
138- _food_id = list (food_ids )[0 ]
139- nutrient_progress_bars (
140- {_food_id : grams },
141- [(_food_id , x [0 ], x [1 ] * (grams / 100 )) for x in analyses [_food_id ]],
122+ # Print view using consistent format
123+ buffer = BUFFER_WD - 4 if BUFFER_WD > 4 else BUFFER_WD
124+ day_format (
125+ analysis_dict ,
142126 nutrients ,
127+ buffer = buffer ,
128+ scale = scale ,
129+ scale_mode = scale_mode ,
130+ total_weight = grams ,
143131 )
144- # TODO: make this into the `-t` or `--tabular` branch of the function
145- # headers = ["id", "nutrient", "rda %", "amount", "units"]
146- # table = tabulate(nutrient_rows, headers=headers, tablefmt="presto")
147- # print(table)
148132
149133 return 0 , nutrients_rows , servings_rows
150134
151135
152136##############################################################################
153137# Day
154138##############################################################################
155- def day_analyze (day_csv_paths : Sequence [str ], rda_csv_path : str = str ()) -> tuple :
139+ def day_analyze (
140+ day_csv_paths : Sequence [str ],
141+ rda_csv_path : str = str (),
142+ scale : float = 0 ,
143+ scale_mode : str = "kcal" ,
144+ ) -> tuple :
156145 """Analyze a day optionally with custom RDAs, examples:
157146
158147 ./nutra day tests/resources/day/human-test.csv
@@ -211,12 +200,15 @@ def day_analyze(day_csv_paths: Sequence[str], rda_csv_path: str = str()) -> tupl
211200
212201 # Compute totals
213202 nutrients_totals = []
203+ total_grams_list = []
214204 for log in logs :
215205 nutrient_totals = OrderedDict () # NOTE: dict()/{} is NOT ORDERED before 3.6/3.7
206+ daily_grams = 0.0
216207 for entry in log :
217208 if entry ["id" ]:
218209 food_id = int (entry ["id" ])
219210 grams = float (entry ["grams" ])
211+ daily_grams += grams
220212 for _nutrient2 in foods_analysis [food_id ]:
221213 nutr_id = _nutrient2 [0 ]
222214 nutr_per_100g = _nutrient2 [1 ]
@@ -226,26 +218,72 @@ def day_analyze(day_csv_paths: Sequence[str], rda_csv_path: str = str()) -> tupl
226218 else :
227219 nutrient_totals [nutr_id ] += nutr_val
228220 nutrients_totals .append (nutrient_totals )
221+ total_grams_list .append (daily_grams )
229222
230223 # Print results
231224 buffer = BUFFER_WD - 4 if BUFFER_WD > 4 else BUFFER_WD
232- for analysis in nutrients_totals :
233- day_format (analysis , nutrients , buffer = buffer )
225+ for i , analysis in enumerate (nutrients_totals ):
226+ day_format (
227+ analysis ,
228+ nutrients ,
229+ buffer = buffer ,
230+ scale = scale ,
231+ scale_mode = scale_mode ,
232+ total_weight = total_grams_list [i ],
233+ )
234234 return 0 , nutrients_totals
235235
236236
237237def day_format (
238238 analysis : Mapping [int , float ],
239239 nutrients : Mapping [int , tuple ],
240240 buffer : int = 0 ,
241+ scale : float = 0 ,
242+ scale_mode : str = "kcal" ,
243+ total_weight : float = 0 ,
241244) -> None :
242245 """Formats day analysis for printing to console"""
243246
247+ multiplier = 1.0
248+ if scale :
249+ if scale_mode == "kcal" :
250+ current_val = analysis .get (NUTR_ID_KCAL , 0 )
251+ multiplier = scale / current_val if current_val else 0
252+ elif scale_mode == "weight" :
253+ multiplier = scale / total_weight if total_weight else 0
254+ else :
255+ # Try to interpret scale_mode as nutrient ID or Name
256+ target_id = None
257+ # 1. Check if int
258+ try :
259+ target_id = int (scale_mode )
260+ except ValueError :
261+ # 2. Check names
262+ for n_id , n_data in nutrients .items ():
263+ # n_data usually: (id, rda, unit, tag, name, ...)
264+ # Check tag or desc
265+ if scale_mode .lower () in str (n_data [3 ]).lower ():
266+ target_id = n_id
267+ break
268+ if scale_mode .lower () in str (n_data [4 ]).lower ():
269+ target_id = n_id
270+ break
271+
272+ if target_id and target_id in analysis :
273+ current_val = analysis [target_id ]
274+ multiplier = scale / current_val if current_val else 0
275+ else :
276+ print (f"WARN: Could not scale by '{ scale_mode } ', nutrient not found." )
277+
278+ # Apply multiplier
279+ if multiplier != 1.0 :
280+ analysis = {k : v * multiplier for k , v in analysis .items ()}
281+
244282 # Actual values
245- kcals = round (analysis [ NUTR_ID_KCAL ] )
246- pro = analysis [ NUTR_ID_PROTEIN ]
247- net_carb = analysis [ NUTR_ID_CARBS ] - analysis [ NUTR_ID_FIBER ]
248- fat = analysis [ NUTR_ID_FAT_TOT ]
283+ kcals = round (analysis . get ( NUTR_ID_KCAL , 0 ) )
284+ pro = analysis . get ( NUTR_ID_PROTEIN , 0 )
285+ net_carb = analysis . get ( NUTR_ID_CARBS , 0 ) - analysis . get ( NUTR_ID_FIBER , 0 )
286+ fat = analysis . get ( NUTR_ID_FAT_TOT , 0 )
249287 kcals_449 = round (4 * pro + 4 * net_carb + 9 * fat )
250288
251289 # Desired values
@@ -257,12 +295,15 @@ def day_format(
257295 # Print calories and macronutrient bars
258296 print_header ("Macro-nutrients" )
259297 kcals_max = max (kcals , kcals_rda )
260- rda_perc = round (kcals * 100 / kcals_rda , 1 )
298+ rda_perc = round (kcals * 100 / kcals_rda , 1 ) if kcals_rda else 0
261299 print (
262300 "Actual: {0} kcal ({1}% RDA), {2} by 4-4-9" .format (
263301 kcals , rda_perc , kcals_449
264302 )
265303 )
304+ if scale :
305+ print (" (Scaled to %s %s)" % (scale , scale_mode ))
306+
266307 print_macro_bar (fat , net_carb , pro , kcals_max , _buffer = buffer )
267308 print (
268309 "\n Desired: {0} kcal ({1} kcal)" .format (
@@ -278,7 +319,7 @@ def day_format(
278319 )
279320
280321 # Nutrition detail report
281- print_header ("Nutrition detail report" )
322+ print_header ("Nutrition detail report%s" % ( " (SCALED)" if scale else "" ) )
282323 for nutr_id , nutr_val in analysis .items ():
283324 print_nutrient_bar (nutr_id , nutr_val , nutrients )
284325 # TODO: actually filter and show the number of filtered fields
0 commit comments