From addedc76ae9d823b7ac0411bf354c61c6734321e Mon Sep 17 00:00:00 2001 From: Shane Jaroch Date: Sun, 25 Feb 2024 15:13:18 -0500 Subject: [PATCH] wip --- ntclient/core/nutprogbar.py | 156 +++++++++++++++++++++++++++++++---- ntclient/services/analyze.py | 114 +++---------------------- tests/test_cli.py | 4 +- 3 files changed, 151 insertions(+), 123 deletions(-) diff --git a/ntclient/core/nutprogbar.py b/ntclient/core/nutprogbar.py index f656886..12deac9 100644 --- a/ntclient/core/nutprogbar.py +++ b/ntclient/core/nutprogbar.py @@ -1,24 +1,37 @@ """Temporary [wip] module for more visual (& colorful) RDA output""" +from typing import Mapping -def nutprogbar( - food_amts: dict, - food_analyses: list, - nutrients: dict, +from ntclient.utils import CLI_CONFIG + + +def nutrient_progress_bars( + _food_amts: Mapping[int, float], + _food_analyses: list, + _nutrients: Mapping[int, list], # grams: float = 100, width: int = 50, ) -> dict: """Returns progress bars, colorized, for foods analyses""" - def tally() -> None: - for nut in nut_percs: - # TODO: get RDA values from nt DB, tree node nested organization - print(nut) + def tally() -> int: + """Tally the progress bars, return n_skipped""" + n_skipped = 0 + for nut in nut_percs.items(): + nutr_id, nutr_val = nut + # Skip if nutr_val == 0.0 + if not nutr_val: + n_skipped += 1 + continue + # Print bars + print_nutrient_bar(nutr_id, nutr_val, _nutrients) + return n_skipped - # for _food_analysis in food_analyses: - # print(_food_analysis) + for _food_analysis in _food_analyses: + print(_food_analysis) food_analyses_dict = { - x[0]: {y[1]: y[2] for y in food_analyses if y[0] == x[0]} for x in food_analyses + x[0]: {y[1]: y[2] for y in _food_analyses if y[0] == x[0]} + for x in _food_analyses } # print(food_ids) @@ -26,21 +39,130 @@ def nutprogbar( nut_amts = {} - for food_id, grams in food_amts.items(): - # r = grams / 100.0 + for food_id, grams in _food_amts.items(): + ratio = grams / 100.0 analysis = food_analyses_dict[food_id] for nutrient_id, amt in analysis.items(): if nutrient_id not in nut_amts: - nut_amts[nutrient_id] = amt + nut_amts[nutrient_id] = amt * ratio else: - nut_amts[nutrient_id] += amt + nut_amts[nutrient_id] += amt * ratio nut_percs = {} for nutrient_id, amt in nut_amts.items(): # TODO: if not rda, show raw amounts? - if isinstance(nutrients[nutrient_id][1], float): - nut_percs[nutrient_id] = round(amt / nutrients[nutrient_id][1], 3) + if isinstance(_nutrients[nutrient_id][1], float): + # print(type(_nutrients[nutrient_id][1]), _nutrients[nutrient_id]) + nut_percs[nutrient_id] = round(amt / _nutrients[nutrient_id][1], 3) + else: + print(type(_nutrients[nutrient_id][1]), _nutrients[nutrient_id]) + continue tally() return nut_percs + + +def print_nutrient_bar( + _n_id: int, _amount: float, _nutrients: Mapping[int, list] +) -> tuple: + """Print a single color-coded nutrient bar""" + nutrient = _nutrients[_n_id] + rda = nutrient[1] + tag = nutrient[3] + unit = nutrient[2] + # anti = nutrient[5] + # hidden = nutrient[...?] + + # TODO: get RDA values from nt DB, tree node nested organization + if not rda: + return False, nutrient + attain = _amount / rda + perc = round(100 * attain, 1) + + if attain >= CLI_CONFIG.thresh_over: + color = CLI_CONFIG.color_over + elif attain <= CLI_CONFIG.thresh_crit: + color = CLI_CONFIG.color_crit + elif attain <= CLI_CONFIG.thresh_warn: + color = CLI_CONFIG.color_warn + else: + color = CLI_CONFIG.color_default + + # Print + detail_amount = "{0}/{1} {2}".format(round(_amount, 1), rda, unit).ljust(18) + detail_amount = "{0} -- {1}".format(detail_amount, tag) + left_index = 20 + left_pos = round(left_index * attain) if attain < 1 else left_index + print(" {0}<".format(color), end="") + print("=" * left_pos + " " * (left_index - left_pos) + ">", end="") + print(" {0}%\t[{1}]".format(perc, detail_amount), end="") + print(CLI_CONFIG.style_reset_all) + + return True, perc + + +def print_macro_bar( + _fat: float, _net_carb: float, _pro: float, _kcals_max: float, _buffer: int = 0 +) -> None: + """Print macro-nutrients bar with details.""" + _kcals = _fat * 9 + _net_carb * 4 + _pro * 4 + + p_fat = (_fat * 9) / _kcals + p_car = (_net_carb * 4) / _kcals + p_pro = (_pro * 4) / _kcals + + # TODO: handle rounding cases, tack on to, or trim off FROM LONGEST ? + mult = _kcals / _kcals_max + n_fat = round(p_fat * _buffer * mult) + n_car = round(p_car * _buffer * mult) + n_pro = round(p_pro * _buffer * mult) + + # Headers + f_buf = " " * (n_fat // 2) + "Fat" + " " * (n_fat - n_fat // 2 - 3) + c_buf = " " * (n_car // 2) + "Carbs" + " " * (n_car - n_car // 2 - 5) + p_buf = " " * (n_pro // 2) + "Pro" + " " * (n_pro - n_pro // 2 - 3) + print( + " " + + CLI_CONFIG.color_yellow + + f_buf + + CLI_CONFIG.color_blue + + c_buf + + CLI_CONFIG.color_red + + p_buf + + CLI_CONFIG.style_reset_all + ) + + # Bars + print(" <", end="") + print(CLI_CONFIG.color_yellow + "=" * n_fat, end="") + print(CLI_CONFIG.color_blue + "=" * n_car, end="") + print(CLI_CONFIG.color_red + "=" * n_pro, end="") + print(CLI_CONFIG.style_reset_all + ">") + + # Calorie footers + k_fat = str(round(_fat * 9)) + k_car = str(round(_net_carb * 4)) + k_pro = str(round(_pro * 4)) + f_buf = " " * (n_fat // 2) + k_fat + " " * (n_fat - n_fat // 2 - len(k_fat)) + c_buf = " " * (n_car // 2) + k_car + " " * (n_car - n_car // 2 - len(k_car)) + p_buf = " " * (n_pro // 2) + k_pro + " " * (n_pro - n_pro // 2 - len(k_pro)) + print( + " " + + CLI_CONFIG.color_yellow + + f_buf + + CLI_CONFIG.color_blue + + c_buf + + CLI_CONFIG.color_red + + p_buf + + CLI_CONFIG.style_reset_all + ) + + +def print_header(_header: str) -> None: + """Print a colorized header""" + print(CLI_CONFIG.color_default, end="") + print("=" * (len(_header) + 2 * 5)) + print("--> %s <--" % _header) + print("=" * (len(_header) + 2 * 5)) + print(CLI_CONFIG.style_reset_all) diff --git a/ntclient/services/analyze.py b/ntclient/services/analyze.py index bdb4aff..e1ea96d 100644 --- a/ntclient/services/analyze.py +++ b/ntclient/services/analyze.py @@ -19,7 +19,12 @@ from ntclient import ( NUTR_ID_KCAL, NUTR_ID_PROTEIN, ) -from ntclient.core.nutprogbar import nutprogbar +from ntclient.core.nutprogbar import ( + nutrient_progress_bars, + print_header, + print_macro_bar, + print_nutrient_bar, +) from ntclient.persistence.sql.usda.funcs import ( sql_analyze_foods, sql_food_details, @@ -132,14 +137,14 @@ def foods_analyze(food_ids: set, grams: float = 100) -> tuple: # TODO: nested, color-coded tree view # TODO: either make this function singular, or handle plural logic here _food_id = list(food_ids)[0] - nutprogbar( + nutrient_progress_bars( {_food_id: grams}, [(_food_id, x[0], x[1]) for x in analyses[_food_id]], nutrients, ) # BEGIN: deprecated code - table = tabulate(nutrient_rows, headers=headers, tablefmt="presto") - print(table) + # table = tabulate(nutrient_rows, headers=headers, tablefmt="presto") + # print(table) # END: deprecated code nutrients_rows.append(nutrient_rows) @@ -230,110 +235,11 @@ def day_analyze(day_csv_paths: Sequence[str], rda_csv_path: str = str()) -> tupl return 0, nutrients_totals -def print_macro_bar( - _fat: float, _net_carb: float, _pro: float, _kcals_max: float, _buffer: int = 0 -) -> None: - """Print macro-nutrients bar with details.""" - _kcals = _fat * 9 + _net_carb * 4 + _pro * 4 - - p_fat = (_fat * 9) / _kcals - p_car = (_net_carb * 4) / _kcals - p_pro = (_pro * 4) / _kcals - - # TODO: handle rounding cases, tack on to, or trim off FROM LONGEST ? - mult = _kcals / _kcals_max - n_fat = round(p_fat * _buffer * mult) - n_car = round(p_car * _buffer * mult) - n_pro = round(p_pro * _buffer * mult) - - # Headers - f_buf = " " * (n_fat // 2) + "Fat" + " " * (n_fat - n_fat // 2 - 3) - c_buf = " " * (n_car // 2) + "Carbs" + " " * (n_car - n_car // 2 - 5) - p_buf = " " * (n_pro // 2) + "Pro" + " " * (n_pro - n_pro // 2 - 3) - print( - " " - + CLI_CONFIG.color_yellow - + f_buf - + CLI_CONFIG.color_blue - + c_buf - + CLI_CONFIG.color_red - + p_buf - + CLI_CONFIG.style_reset_all - ) - - # Bars - print(" <", end="") - print(CLI_CONFIG.color_yellow + "=" * n_fat, end="") - print(CLI_CONFIG.color_blue + "=" * n_car, end="") - print(CLI_CONFIG.color_red + "=" * n_pro, end="") - print(CLI_CONFIG.style_reset_all + ">") - - # Calorie footers - k_fat = str(round(_fat * 9)) - k_car = str(round(_net_carb * 4)) - k_pro = str(round(_pro * 4)) - f_buf = " " * (n_fat // 2) + k_fat + " " * (n_fat - n_fat // 2 - len(k_fat)) - c_buf = " " * (n_car // 2) + k_car + " " * (n_car - n_car // 2 - len(k_car)) - p_buf = " " * (n_pro // 2) + k_pro + " " * (n_pro - n_pro // 2 - len(k_pro)) - print( - " " - + CLI_CONFIG.color_yellow - + f_buf - + CLI_CONFIG.color_blue - + c_buf - + CLI_CONFIG.color_red - + p_buf - + CLI_CONFIG.style_reset_all - ) - - def day_format( analysis: Mapping[int, float], nutrients: Mapping[int, list], buffer: int = 0 ) -> None: """Formats day analysis for printing to console""" - def print_header(header: str) -> None: - print(CLI_CONFIG.color_default, end="") - print("=" * (len(header) + 2 * 5)) - print("--> %s <--" % header) - print("=" * (len(header) + 2 * 5)) - print(CLI_CONFIG.style_reset_all) - - def print_nute_bar( - _n_id: int, amount: float, _nutrients: Mapping[int, list] - ) -> tuple: - nutrient = _nutrients[_n_id] - rda = nutrient[1] - tag = nutrient[3] - unit = nutrient[2] - # anti = nutrient[5] - - if not rda: - return False, nutrient - attain = amount / rda - perc = round(100 * attain, 1) - - if attain >= CLI_CONFIG.thresh_over: - color = CLI_CONFIG.color_over - elif attain <= CLI_CONFIG.thresh_crit: - color = CLI_CONFIG.color_crit - elif attain <= CLI_CONFIG.thresh_warn: - color = CLI_CONFIG.color_warn - else: - color = CLI_CONFIG.color_default - - # Print - detail_amount = "{0}/{1} {2}".format(round(amount, 1), rda, unit).ljust(18) - detail_amount = "{0} -- {1}".format(detail_amount, tag) - left_index = 20 - left_pos = round(left_index * attain) if attain < 1 else left_index - print(" {0}<".format(color), end="") - print("=" * left_pos + " " * (left_index - left_pos) + ">", end="") - print(" {0}%\t[{1}]".format(perc, detail_amount), end="") - print(CLI_CONFIG.style_reset_all) - - return True, perc - # Actual values kcals = round(analysis[NUTR_ID_KCAL]) pro = analysis[NUTR_ID_PROTEIN] @@ -373,7 +279,7 @@ def day_format( # Nutrition detail report print_header("Nutrition detail report") for n_id in analysis: - print_nute_bar(n_id, analysis[n_id], nutrients) + print_nutrient_bar(n_id, analysis[n_id], nutrients) # TODO: actually filter and show the number of filtered fields print( "work in progress...", diff --git a/tests/test_cli.py b/tests/test_cli.py index ab85c4c..9a0dd97 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -431,7 +431,7 @@ class TestCli(unittest.TestCase): """Verifies colored/visual output is successfully generated""" analysis = usda_funcs.sql_analyze_foods(food_ids={1001}) nutrients = usda_funcs.sql_nutrients_overview() - output = nutprogbar.nutprogbar( - food_amts={1001: 100}, food_analyses=analysis, nutrients=nutrients + output = nutprogbar.nutrient_progress_bars( + _food_amts={1001: 100}, _food_analyses=analysis, _nutrients=nutrients ) assert output -- 2.52.0