Last active
May 29, 2020 06:37
-
-
Save hetsch/e44f79d57b8f3ea9d54de65a94ce0754 to your computer and use it in GitHub Desktop.
Pizza dough calculator
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import collections | |
from enum import IntEnum | |
from copy import copy | |
import click | |
class EnumChoice(click.Choice): | |
def __init__(self, enum, case_sensitive=False, use_value=False): | |
self.enum = enum | |
self.use_value = use_value | |
choices = [str(e.value) if use_value else e.name for e in self.enum] | |
super().__init__(choices, case_sensitive) | |
def convert(self, value, param, ctx): | |
if value in self.enum: | |
return value | |
result = super().convert(value, param, ctx) | |
# Find the original case in the enum | |
if not self.case_sensitive and result not in self.choices: | |
result = next(c for c in self.choices if result.lower() == c.lower()) | |
if self.use_value: | |
return next(e for e in self.enum if str(e.value) == result) | |
return self.enum[result] | |
class PIZZA_TYPE(IntEnum): | |
Napolitan = 0 | |
American = 1 | |
Sicilian = 2 | |
Sourdough = 3 | |
class PIZZA_SIZE(IntEnum): | |
Small = 0 | |
Medium = 1 | |
Large = 2 | |
# https://www.stadlermade.com/de/pizza-dough-calculator/ | |
# https://www.stadlermade.com/blog/how-to-make-neapolitan-style-pizza-dough/ | |
RATIOS = { | |
PIZZA_TYPE.Napolitan: {"flour": 1.0, "salt": 0.03, "yeast": 0.002, "water": 0.65}, | |
PIZZA_TYPE.American: { | |
"flour": 1.0, | |
"salt": 0.02, # not 1.5 like in the description | |
"yeast": 0.004, | |
"water": 0.65, | |
"oliveoil": 0.025, | |
"sugar": 0.02, | |
}, | |
PIZZA_TYPE.Sicilian: { | |
"flour": 1.0, | |
"salt": 0.02, | |
"yeast": 0.015, | |
"water": 0.75, | |
"oliveoil": 0.015, | |
}, | |
PIZZA_TYPE.Sourdough: { | |
"flour": 1.0, | |
"salt": 0.03, | |
"mother_dough": 0.16, | |
"water": 0.64, | |
}, | |
} | |
SIZES = { | |
PIZZA_TYPE.Napolitan: { | |
PIZZA_SIZE.Small: 160, | |
PIZZA_SIZE.Medium: 230, | |
PIZZA_SIZE.Large: 300, | |
}, | |
PIZZA_TYPE.American: { | |
PIZZA_SIZE.Small: 175, | |
PIZZA_SIZE.Medium: 240, | |
PIZZA_SIZE.Large: 315, | |
}, | |
PIZZA_TYPE.Sicilian: 650, | |
} | |
WATER_MIN_MAX = { | |
PIZZA_TYPE.Napolitan: (0.55, 0.75), | |
PIZZA_TYPE.American: (0.55, 0.65), | |
PIZZA_TYPE.Sicilian: (0.55, 0.85), | |
} | |
fields = ("flour", "water", "salt", "yeast", "mother_dough", "oliveoil", "sugar") | |
Result = collections.namedtuple("Result", fields, defaults=(None,) * len(fields)) | |
# https://www.stadlermade.com/blog/how-to-make-neapolitan-style-pizza-dough/ | |
def calculate(pieces, grams_per_piece, ratios): | |
if style == PIZZA_TYPE.Napolitan: | |
return Result( | |
flour=round(flour_weight), | |
water=round(flour_weight * ratios["water"]), | |
salt=round(flour_weight * ratios["salt"], 1), | |
yeast=round(flour_weight * ratios["yeast"], 1), | |
) | |
elif style == PIZZA_TYPE.American: | |
return Result( | |
flour=round(flour_weight), | |
water=round(flour_weight * ratios["water"]), | |
salt=round(flour_weight * ratios["salt"], 1), | |
yeast=round(flour_weight * ratios["yeast"], 1), | |
oliveoil=round(flour_weight * ratios["oliveoil"]), | |
sugar=round(flour_weight * ratios["sugar"], 1), | |
) | |
elif style == PIZZA_TYPE.Sicilian: | |
return Result( | |
flour=round(flour_weight), | |
water=round(flour_weight * ratios["water"]), | |
salt=round(flour_weight * ratios["salt"], 1), | |
yeast=round(flour_weight * ratios["yeast"], 1), | |
oliveoil=round(flour_weight * ratios["oliveoil"]), | |
) | |
elif style == PIZZA_TYPE.Sourdough: | |
return Result( | |
flour=round(flour_weight), | |
water=round(flour_weight * ratios["water"]), | |
salt=round(flour_weight * ratios["salt"], 1), | |
mother_dough=round(flour_weight * ratios["mother_dough"]), | |
) | |
def get_floor_weight(desired_total_weight, ratios): | |
# Backer's Percentage Formula: Calculate the amount of floor as basis | |
# https://www.wildyeastblog.com/bakers-percentage-3/ | |
# Total Flour Weight = (Desired Dough Weight / Total %) x 100 | |
return (desired_total_weight / (sum(ratios.values()) * 100)) * 100 | |
@click.group() | |
def cli(): | |
pass | |
def validate_water(value, pizza_type): | |
_min, _max = WATER_MIN_MAX[pizza_type] | |
if _min <= value <= _max: | |
raise click.BadParameter(f"Water ratio must be between {_min} and {_max}") | |
return value | |
@cli.command() | |
@click.option("-p", "--pieces", default=1, help="The number of pizzas") | |
@click.option( | |
"-s", | |
"--size", | |
type=EnumChoice(PIZZA_SIZE), | |
default=PIZZA_SIZE.Medium, | |
help="S Pizza = Ø 16 CM/6 INCH == 160 gram, M Pizza = Ø 28CM/ 11 INCH == 230 gramm, L Pizza = Ø 34CM/ 13 INCH == 300 gram", | |
) | |
@click.option( | |
"-g", | |
"--grams", | |
default=0, | |
help="The weight of each pizza piece. Overwrites size argument if given", | |
) | |
@click.option( | |
"-w", | |
"--water_ratio", | |
default=0.0, | |
help="The pizza dough calculator assumes that you’re using tipo 00 flour. If this is the case, you won’t need to make any changes to the water percentage indicated. In case you’re using regular bread flour, it would be wise to set the parameter between 50% and 55%, since regular bread flower can’t absorb large quantities of water. If you don’t use less water with bread flour, Your dough will get wet and soupy", | |
) | |
def neapolitan(pieces, size, grams, water_ratio): | |
ratios = copy(RATIOS[PIZZA_TYPE.Napolitan]) | |
if water_ratio: | |
ratios["water"] = water_ratio | |
if not grams and size: | |
click.echo(click.style(f"Pizza size: {PIZZA_SIZE(size).name}", fg="blue")) | |
grams = SIZES[PIZZA_TYPE.Napolitan][size] | |
else: | |
click.echo(click.style(f"Using {grams} g per piece", fg="blue")) | |
flour_weight = get_floor_weight(pieces * grams, ratios) | |
print( | |
Result( | |
flour=round(flour_weight), | |
water=round(flour_weight * ratios["water"]), | |
salt=round(flour_weight * ratios["salt"], 1), | |
yeast=round(flour_weight * ratios["yeast"], 1), | |
) | |
) | |
@cli.command() | |
@click.option("-p", "--pieces", default=1, help="The number of pizzas") | |
@click.option( | |
"-s", | |
"--size", | |
type=EnumChoice(PIZZA_SIZE), | |
default=PIZZA_SIZE.Medium, | |
help="S Pizza = Ø 16 CM/6 INCH == 175 gram, M Pizza = Ø 28CM/ 11 INCH == 240 gramm, L Pizza = Ø 34CM/ 13 INCH == 315 gram", | |
) | |
@click.option( | |
"-g", | |
"--grams", | |
default=0, | |
help="The weight of each pizza piece. Overwrites size argument if given", | |
) | |
@click.option( | |
"-w", | |
"--water_ratio", | |
callback=lambda _, __, x: validate_water(x, PIZZA_TYPE.American), | |
default=RATIOS[PIZZA_TYPE.Napolitan]["water"], | |
help="The pizza dough calculator assumes that you’re using tipo 00 flour. If this is the case, you won’t need to make any changes to the water percentage indicated. In case you’re using regular bread flour, it would be wise to set the parameter between 50% and 55%, since regular bread flower can’t absorb large quantities of water. If you don’t use less water with bread flour, Your dough will get wet and soupy", | |
) | |
def american(pieces, size, grams, water_ratio): | |
click.echo(click.style(f"Pizza style: {PIZZA_TYPE.American.name}", fg="green")) | |
ratios = copy(RATIOS[PIZZA_TYPE.American]) | |
if water_ratio: | |
ratios["water"] = water_ratio | |
if not grams and size: | |
click.echo(click.style(f"Pizza size: {PIZZA_SIZE(size).name}", fg="blue")) | |
grams = SIZES[PIZZA_TYPE.American][size] | |
else: | |
click.echo(click.style(f"Using {grams} g per piece", fg="blue")) | |
flour_weight = get_floor_weight(pieces * grams, ratios) | |
print( | |
Result( | |
flour=round(flour_weight), | |
water=round(flour_weight * ratios["water"]), | |
salt=round(flour_weight * ratios["salt"], 1), | |
yeast=round(flour_weight * ratios["yeast"], 1), | |
oliveoil=round(flour_weight * ratios["oliveoil"]), | |
sugar=round(flour_weight * ratios["sugar"], 1), | |
) | |
) | |
if __name__ == "__main__": | |
cli() | |
# print(calculate(1, 230, style=PIZZA_TYPE.Sourdoug |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment