From cf6c3a9d6f4cdf432e7b475efcfa35b121407d1d Mon Sep 17 00:00:00 2001 From: neingeist Date: Thu, 23 Apr 2026 19:26:12 +0200 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8=20table=20to=20list=20servings,=20gro?= =?UTF-8?q?ss=20and=20net=20weight?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- fruehstueck_bestellung+inventar.py | 138 +++++++++++++++++++++++++++++ fruehstueck_inventar.py | 78 ---------------- lib.py | 11 +++ pyproject.toml | 2 + uv.lock | 34 +++++++ 5 files changed, 185 insertions(+), 78 deletions(-) create mode 100644 fruehstueck_bestellung+inventar.py delete mode 100644 fruehstueck_inventar.py diff --git a/fruehstueck_bestellung+inventar.py b/fruehstueck_bestellung+inventar.py new file mode 100644 index 0000000..e0c41e2 --- /dev/null +++ b/fruehstueck_bestellung+inventar.py @@ -0,0 +1,138 @@ +import csv +import json +import os +import pprint +import re +import sys +from collections import defaultdict +from datetime import timedelta +from pathlib import Path + +from openpyxl import load_workbook +from tabulate import tabulate +from tqdm import tqdm + +from config import * +from lib import read_recipes, markdown_link, recipe_url, markdown_alert + + +def read_plan(): + wb = load_workbook("data/Planung + Einkauf Brotaufstriche.xlsx") + sheet = wb.active + + plan = {} + + for row in sheet.iter_rows(values_only=True): + + r_name = row[0] + # Ansprechpartner*in = row[1] + r_total_net_weight = row[2] + # gpn23 = row[3] + r_serving_net_weight = row[4] + # (Formula) = row[5] + r_recipe_url = row[6] + + # We simply skip over any rows that do not have the recipe url + if not r_recipe_url: + continue + if m := re.match(r"https://.*/(\d+)$", r_recipe_url): + r_recipe_id = int(m.group(1)) + else: + continue # skip + + r_wanted_servings = r_total_net_weight / r_serving_net_weight + + plan[r_name] = { + "total_net_weight": r_total_net_weight, + "serving_net_weight": r_serving_net_weight, + "recipe_id": r_recipe_id, + "wanted_servings": r_wanted_servings, + } + + return plan + +plan = read_plan() + + +# XXX move to lib +def grams(ingredient): + conversion = None + for c in ingredient["conversions"]: + if c["unit"] in ["g / Gramm", "g"]: + conversion = c + if conversion: + return conversion.get("amount") + + +def main(): + + recipes = read_recipes(keyword="Frühstück") + + inventory = {} + + for recipe in recipes: + scale_factor = plan[recipe["name"]]["wanted_servings"] / recipe["servings"] + r_name = recipe["name"] + for step in recipe["steps"]: + for ingredient in step["ingredients"]: + if not ingredient.get("food"): + raise ValueError("No food in ingredient") + continue + i_grams = scale_factor * (grams(ingredient) or 0) + i_name = ingredient["food"]["name"] + i_description = ingredient["food"][ + "description" + ] # XXX probably not what i wanted + + inventory.setdefault(i_name, []).append((i_grams, r_name)) + + # gross ingredient weight per serving + r_gross_weight = 0.0 + for step in recipe["steps"]: + for ingredient in step["ingredients"]: + if not ingredient.get("food"): + raise ValueError("No food in ingredient") + continue + r_gross_weight += (grams(ingredient) or 0) + r_gross_weight /= recipe["servings"] + + recipe["total_net_weight"] = plan[r_name]["total_net_weight"] + recipe["wanted_servings"] = plan[r_name]["wanted_servings"] + recipe["total_gross_weight"] = r_gross_weight * plan[r_name]["wanted_servings"] + + + print(markdown_alert("automatisch erstellt, nicht editieren...")) + print() + + + print("## Rezepte und Portionen") + + data = [] + for recipe in recipes: + row = [markdown_link(recipe["name"], recipe_url(recipe["id"])), + int(recipe["wanted_servings"]), + int(recipe["total_net_weight"]), + int(recipe["total_gross_weight"])] + data.append(row) + + headers = ["Rezept", "Portionen", "Gewicht Netto (Soll)", "Gewicht Brutto Zutaten"] + + print(tabulate(data, headers=headers, tablefmt="github")) + print() + print() + + + print("## Zutaten") + + for ingredient, entries in inventory.items(): + total = sum(amount for amount, _ in entries) + + print(f"### {ingredient}") + + print(f"- **{total:.1f}g** **Total**") + for i_grams, r_name in entries: + print(f"- {i_grams:.1f}g {r_name}") + + +if __name__ == "__main__": + main() diff --git a/fruehstueck_inventar.py b/fruehstueck_inventar.py deleted file mode 100644 index 48b634d..0000000 --- a/fruehstueck_inventar.py +++ /dev/null @@ -1,78 +0,0 @@ -import csv -import json -import os -import pprint -import re -import sys -from collections import defaultdict -from datetime import timedelta -from pathlib import Path - -from tqdm import tqdm - -from config import * -from lib import read_recipes - - -# TODO read from csv -wanted_servings = { - "Aufstrich Zwiebel & Kümmel": 100, - "Cashew-Streichkäse": 100, - "GPN-Tomatenbutter": 100, - "Granatapfelcreme": 100, - "Gulaschmarmelade": 100, - "Hummus": 100, - "Matelade Apfel": 100, - "Mungobohnenhummus mit Jalapenos und Zatar": 100, - "Rauchige Schwarze Bohnencreme": 100, -} - - -# XXX move to lib -def grams(ingredient): - conversion = None - for c in ingredient["conversions"]: - if c["unit"] in ["g / Gramm", "g"]: - conversion = c - if conversion: - return conversion.get("amount") - - -def main(): - - recipes = read_recipes(keyword="Frühstück") - - inventory = {} - - for recipe in recipes: - scale_factor = wanted_servings[recipe["name"]] / recipe["servings"] - r_name = recipe["name"] - for step in recipe["steps"]: - for ingredient in step["ingredients"]: - if not ingredient.get("food"): - raise ValueError("No food in ingredient") - continue - i_grams = scale_factor * (grams(ingredient) or 0) - i_name = ingredient["food"]["name"] - i_description = ingredient["food"][ - "description" - ] # XXX probably not what i wanted - - inventory.setdefault(i_name, []).append((i_grams, r_name)) - - print("!!! alert") - print("automatisch erstellt, nicht editieren...") - print("!!!") - - for ingredient, entries in inventory.items(): - total = sum(amount for amount, _ in entries) - - print(f"## {ingredient}") - - print(f"- **{total:.1f}g** **Total**") - for i_grams, r_name in entries: - print(f"- {i_grams:.1f}g {r_name}") - - -if __name__ == "__main__": - main() diff --git a/lib.py b/lib.py index 64d1bab..29e7350 100644 --- a/lib.py +++ b/lib.py @@ -19,3 +19,14 @@ def read_recipes(keyword=None): ] return recipes + + +def recipe_url(recipe_id: int) -> str: + return f"{TANDOOR_URL}/recipe/{recipe_id}" + + +def markdown_link(text: str, url: str) -> str: + return f"[{text}]({url})" + +def markdown_alert(text: str) -> str: + return f"::: danger\n{text}\n:::" diff --git a/pyproject.toml b/pyproject.toml index 74e75e0..e58cd85 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -7,8 +7,10 @@ requires-python = ">=3.14" dependencies = [ "click>=8.3.2", "mwclient>=0.11.0", + "openpyxl>=3.1.5", "pathvalidate>=3.3.1", "requests>=2.33.1", + "tabulate>=0.10.0", "tqdm>=4.67.3", ] diff --git a/uv.lock b/uv.lock index 09eeef2..322c7a4 100644 --- a/uv.lock +++ b/uv.lock @@ -73,6 +73,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" }, ] +[[package]] +name = "et-xmlfile" +version = "2.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d3/38/af70d7ab1ae9d4da450eeec1fa3918940a5fafb9055e934af8d6eb0c2313/et_xmlfile-2.0.0.tar.gz", hash = "sha256:dab3f4764309081ce75662649be815c4c9081e88f0837825f90fd28317d4da54", size = 17234, upload-time = "2024-10-25T17:25:40.039Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c1/8b/5fe2cc11fee489817272089c4203e679c63b570a5aaeb18d852ae3cbba6a/et_xmlfile-2.0.0-py3-none-any.whl", hash = "sha256:7a91720bc756843502c3b7504c77b8fe44217c85c537d85037f0f536151b2caa", size = 18059, upload-time = "2024-10-25T17:25:39.051Z" }, +] + [[package]] name = "gpn24-recipes" version = "0.0.0" @@ -80,8 +89,10 @@ source = { virtual = "." } dependencies = [ { name = "click" }, { name = "mwclient" }, + { name = "openpyxl" }, { name = "pathvalidate" }, { name = "requests" }, + { name = "tabulate" }, { name = "tqdm" }, ] @@ -94,8 +105,10 @@ dev = [ requires-dist = [ { name = "click", specifier = ">=8.3.2" }, { name = "mwclient", specifier = ">=0.11.0" }, + { name = "openpyxl", specifier = ">=3.1.5" }, { name = "pathvalidate", specifier = ">=3.3.1" }, { name = "requests", specifier = ">=2.33.1" }, + { name = "tabulate", specifier = ">=0.10.0" }, { name = "tqdm", specifier = ">=4.67.3" }, ] @@ -132,6 +145,18 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/be/9c/92789c596b8df838baa98fa71844d84283302f7604ed565dafe5a6b5041a/oauthlib-3.3.1-py3-none-any.whl", hash = "sha256:88119c938d2b8fb88561af5f6ee0eec8cc8d552b7bb1f712743136eb7523b7a1", size = 160065, upload-time = "2025-06-19T22:48:06.508Z" }, ] +[[package]] +name = "openpyxl" +version = "3.1.5" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "et-xmlfile" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/3d/f9/88d94a75de065ea32619465d2f77b29a0469500e99012523b91cc4141cd1/openpyxl-3.1.5.tar.gz", hash = "sha256:cf0e3cf56142039133628b5acffe8ef0c12bc902d2aadd3e0fe5878dc08d1050", size = 186464, upload-time = "2024-06-28T14:03:44.161Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c0/da/977ded879c29cbd04de313843e76868e6e13408a94ed6b987245dc7c8506/openpyxl-3.1.5-py2.py3-none-any.whl", hash = "sha256:5282c12b107bffeef825f4617dc029afaf41d0ea60823bbb665ef3079dc79de2", size = 250910, upload-time = "2024-06-28T14:03:41.161Z" }, +] + [[package]] name = "pathvalidate" version = "3.3.1" @@ -194,6 +219,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/03/36/76704c4f312257d6dbaae3c959add2a622f63fcca9d864659ce6d8d97d3d/ruff-0.15.9-py3-none-win_arm64.whl", hash = "sha256:0694e601c028fd97dc5c6ee244675bc241aeefced7ef80cd9c6935a871078f53", size = 11005870, upload-time = "2026-04-02T18:17:15.773Z" }, ] +[[package]] +name = "tabulate" +version = "0.10.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/46/58/8c37dea7bbf769b20d58e7ace7e5edfe65b849442b00ffcdd56be88697c6/tabulate-0.10.0.tar.gz", hash = "sha256:e2cfde8f79420f6deeffdeda9aaec3b6bc5abce947655d17ac662b126e48a60d", size = 91754, upload-time = "2026-03-04T18:55:34.402Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/99/55/db07de81b5c630da5cbf5c7df646580ca26dfaefa593667fc6f2fe016d2e/tabulate-0.10.0-py3-none-any.whl", hash = "sha256:f0b0622e567335c8fabaaa659f1b33bcb6ddfe2e496071b743aa113f8774f2d3", size = 39814, upload-time = "2026-03-04T18:55:31.284Z" }, +] + [[package]] name = "tqdm" version = "4.67.3"