forked from bjorkegeek/brittamat
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathalg.py
More file actions
169 lines (142 loc) · 5.37 KB
/
alg.py
File metadata and controls
169 lines (142 loc) · 5.37 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
from copy import copy
from typing import TypeVar, Iterable, Iterator, Optional, cast
import classes
import operator
import itertools
import pint
import re
import data
T = TypeVar("T")
def powerset(iterable: Iterable[T]) -> Iterator[tuple[T, ...]]:
"powerset([1,2,3]) --> () (1,) (2,) (3,) (1,2) (1,3) (2,3) (1,2,3)"
s = list(iterable)
return itertools.chain.from_iterable(
itertools.combinations(s, r) for r in range(len(s) + 1)
)
def to_purchase_unit(
ingredient: classes.Ingredient, ingredient_types: dict[str, classes.IngredientType]
) -> Optional[pint.Quantity]:
it = ingredient_types[ingredient.name]
if ingredient.quantity is None and it.purchase_unit is None:
return None
try:
conversions = it.conversions
except AttributeError:
conversions = []
for subconversions, op in itertools.product(
powerset(conversions), (operator.mul, operator.truediv)
):
try:
v = ingredient.quantity
for conversion in subconversions:
v = op(v, conversion)
if v is None:
return None
else:
return cast(pint.Quantity, v.to(it.purchase_unit))
except pint.DimensionalityError:
pass
raise ValueError(
"Could not convert %s from %s to %s"
% (it.name, ingredient.quantity, it.purchase_unit)
)
_translate_re = re.compile("|".join([re.escape(fr) for fr, to in data.translations]))
_translate_dict = dict(data.translations)
def translate_unit(unit_string: str) -> str:
return _translate_re.sub(lambda x: _translate_dict[x.group(0)], unit_string)
def find_undefined_ingredients(
menu: Iterable[classes.MenuItem],
ingredient_types: dict[str, classes.IngredientType],
) -> set[classes.Ingredient]:
not_found = set()
for menuitem in menu:
ingredients = list(menuitem.dish.ingredients)
for variant in menuitem.variants.keys():
try:
ingredients.extend(menuitem.dish.variants[variant])
except KeyError:
print(variant, menuitem.dish.name)
raise ValueError(
"Missing variant '%s' for dish '%s'" % (variant, menuitem.dish.name)
)
for ingredient in ingredients:
if ingredient.name not in ingredient_types:
not_found.add(ingredient)
return not_found
def make_shopping_list(
menu: Iterable[classes.MenuItem],
ingredient_types: dict[str, classes.IngredientType],
) -> list[classes.ShoppingListEntry]:
shopping_list = classes.NameDict[classes.ShoppingListEntry]()
for menuitem in menu:
ingredients = list(zip(menuitem.dish.ingredients, itertools.repeat(1)))
for variant, count in menuitem.variants.items():
ingredients.extend(
list(zip(menuitem.dish.variants[variant], itertools.repeat(count)))
)
for ingredient, count in ingredients:
pu = to_purchase_unit(ingredient, ingredient_types)
if pu is not None:
pu *= count
entry = shopping_list.get(ingredient.name)
if entry:
if pu is not None:
entry.quantity = entry.quantity + pu
entry.sources.append(menuitem)
else:
ingredient_type = ingredient_types[ingredient.name]
entry = classes.ShoppingListEntry(
name=ingredient.name,
category=ingredient_type.category,
quantity=pu,
sources=[menuitem],
)
shopping_list.append(entry)
return list(shopping_list.values())
def subtract_from_shopping_list(
shopping_list: Iterable[classes.ShoppingListEntry],
sub: Iterable[classes.Ingredient],
ingredient_types: dict[str, classes.IngredientType],
):
subd = classes.NameDict(sub)
for ingredient in shopping_list:
sub_ingredient = subd.get(ingredient.name, None)
if sub_ingredient is None:
yield ingredient
else:
# Copy
ing: classes.ShoppingListEntry = copy(ingredient)
pu = to_purchase_unit(sub_ingredient, ingredient_types)
ing.quantity -= pu # type: ignore
if ing.quantity and ing.quantity.magnitude > 0:
yield ing
def order_by_category(
shopping_list: Iterable[classes.ShoppingListEntry],
) -> Iterator[tuple[str, list[classes.ShoppingListEntry]]]:
cats: dict[str, list[classes.ShoppingListEntry]] = {}
for item in shopping_list:
cats.setdefault(item.category, []).append(item)
for x in sorted(cats.items()):
yield x
def scaled_dish(dish: classes.Dish, factor: float) -> classes.Dish:
return classes.Dish(
name=dish.name,
variants=dish.variants,
ingredients=[
(
classes.Ingredient(
name=ingredient.name, quantity=ingredient.quantity * factor
)
if ingredient.quantity is not None
else ingredient
)
for ingredient in dish.ingredients
],
)
def scaled_menu(
menu: list[classes.MenuItem], factor: float
) -> Iterator[classes.MenuItem]:
for item in menu:
d = item.__dict__.copy()
d["dish"] = scaled_dish(d["dish"], factor)
yield classes.MenuItem(**d)