diff --git a/code/kubes/nocodb/today.py b/code/kubes/nocodb/today.py index 2390163..f08c2a9 100755 --- a/code/kubes/nocodb/today.py +++ b/code/kubes/nocodb/today.py @@ -5,15 +5,23 @@ import os import urllib.parse +# Reference Daily Intake RDI_TABLE = "mamm9dpys1j1znw" +# Ingredients (per 100g) +INGREDIENTS_TABLE = "m0zj43gvvzeqxqz" + +# Dishes (ingredients, but in actual weight when put on a plate) DISHES_TABLE = "m5ddsw6b8q0jn55" +# Meals (dated collections of dishes) MEALS_TABLE = "mohjrlr1l7et77g" MEALS_VIEW_TODAY = "vwpckbejvg1mbifx" MEALS_LINK_DISHES = "cc9wqhoe8nfb18o" + def process_nutrients(rdi, ingredient): + """Compute all nutrients for an ingredient based on the amount.""" nutrients = {} ingredient_amount = ingredient["Amount (g)"] for k, v in ingredient.items(): @@ -26,6 +34,7 @@ def process_nutrients(rdi, ingredient): def sum_amounts(total, dishes): + """Sum up all the amounts and RDI values of all nutrients to a total.""" for dish in dishes: for k, v in dish.items(): if isinstance(v, dict): @@ -36,10 +45,10 @@ def sum_amounts(total, dishes): def compute_total(dishes): + """Create a new Total dish and add it to the end of the dishes.""" total = {"Name": "Total", "Amount (g)": None} sum_amounts(total, dishes) - dishes.append(total) - return dishes + return dishes + (total,) class HealthDb: @@ -48,40 +57,52 @@ def __init__(self): self.headers = {'xc-token': os.environ["NOCODB_TOKEN"]} def get(self, url: str): + """GET a URL and JSON-decode the success-response.""" self.conn.request("GET", url, headers=self.headers) res = self.conn.getresponse() return json.loads(res.read().decode("utf-8"))["list"] def list_table_records(self, table: str, view: str = "", fields: tuple[str] = tuple(), key: str = "Id"): + """Retrieve records from a specified table/view, create a dict keyed by a given column.""" + fields_encoded = ",".join(map(urllib.parse.quote_plus, fields)) return { rec[key]: rec - for rec in self.get(f"/api/v2/tables/{table}/records?offset=0&limit=1000&viewId={view}&fields={','.join(map(urllib.parse.quote_plus, fields))}") + for rec in self.get( + f"/api/v2/tables/{table}/records?offset=0&limit=1000&viewId={view}&fields={fields_encoded}") } def list_linked_records(self, table: str, link_field_id: str, record_id: int, key: str = "Id"): - return {rec[key]: rec for rec in self.get(f"/api/v2/tables/{table}/links/{link_field_id}/records/{record_id}")} + """Retrieve list of linked records for a specific Link field and Record ID.""" + return { + rec[key]: rec + for rec in self.get( + f"/api/v2/tables/{table}/links/{link_field_id}/records/{record_id}") + } def today(self): + """Compute nutrient intake for the current day.""" rdi = self.list_table_records(RDI_TABLE, fields=("Nutrient", "Amount"), key="Nutrient") - dishes = self.list_table_records(DISHES_TABLE, fields=("Id", "Amount (g)", "nc_cupq___nc_m2m_8u3b_5gyns")) + ingredients = self.list_table_records(INGREDIENTS_TABLE) + dishes = self.list_table_records(DISHES_TABLE, fields=("Id", "Amount (g)", "Ingredient")) meals = self.list_table_records(MEALS_TABLE, view=MEALS_VIEW_TODAY) - for mealId in [meal["Id"] for meal in meals.values()]: - meals[mealId]["Dishes"] = [process_nutrients(rdi, { + for mealId in tuple(meal["Id"] for meal in meals.values()): + meals[mealId]["Dishes"] = tuple(process_nutrients(rdi, { "Amount (g)": dishes[k]["Amount (g)"], - **dishes[k]["nc_cupq___nc_m2m_8u3b_5gyns"][0]["Ingredients"] - }) for k in self.list_linked_records(MEALS_TABLE, MEALS_LINK_DISHES, mealId).keys()] + **ingredients[dishes[k]["Ingredient"]["Id"]] + }) for k in self.list_linked_records(MEALS_TABLE, MEALS_LINK_DISHES, mealId).keys()) table = {} for meal in meals.values(): table[meal["Meal"]] = compute_total(meal["Dishes"]) - return table + return table, next(iter(meals.values()))["Date"] def format_amount(amount): + """Render an amount or amount with RDI to string.""" if isinstance(amount, dict): rdi = int(round(amount['RDI'] * 100, 0)) return f"{round(amount['Amount'], 2):7} ({rdi:3}%)" - elif isinstance(amount, int): + elif isinstance(amount, int) or isinstance(amount, float): return f"{round(amount, 2):7}" else: return "" @@ -91,19 +112,34 @@ def print_dishes(meal, dishes): print(f"\n## {meal}\n") keys = [k for k in dishes[0].keys() if k not in ("Name",)] padding = max(len(k) for k in keys) - title = "| " + " " * padding + " | " + " | ".join(f"{dish['Name']:18}" for dish in dishes) + min_col_width = 14 + title = ("| " + " " * padding + " | " + + " | ".join(f"{dish['Name']:{min_col_width}}" + for dish in dishes)) print(title) - print("| :" + "-" * (padding - 1) + " | " + " | ".join("-" * 17 + ":" for dish in dishes)) + print("| :" + "-" * (padding - 1) + " | " + + " | ".join("-" * (max(len(dish["Name"]), min_col_width) - 1) + ":" + for dish in dishes)) for k in keys: - print(f"| {k:{padding}} | " + " | ".join(f"{format_amount(dish.get(k, None)):18}" for dish in dishes)) + print(f"| {k:{padding}} | " + + " | ".join(f"{format_amount(dish.get(k, None)):{max(len(dish['Name']), min_col_width)}}" + for dish in dishes)) + + +def main(): + db = HealthDb() + + total = {"Name": "Total"} -db = HealthDb() + meals, date = db.today() + print(f"# {date}") + for meal, dishes in meals.items(): + print_dishes(meal, dishes) -total = {"Name": "Total"} + sum_amounts(total, (dish for dish in dishes if dish["Name"] == "Total")) -for meal, dishes in db.today().items(): - print_dishes(meal, dishes) + print_dishes("Total", [total]) - sum_amounts(total, (dish for dish in dishes if dish["Name"] == "Total")) -print_dishes("Total", [total]) +if __name__ == "__main__": + main()