Skip to content

Commit

Permalink
Merge pull request #14 from chrisgilmerproj/brewhouse_yield
Browse files Browse the repository at this point in the history
Add a brew house yield calculator
  • Loading branch information
chrisgilmerproj committed May 7, 2017
2 parents d48d6b9 + 9208fad commit 999855a
Show file tree
Hide file tree
Showing 9 changed files with 122 additions and 11 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@

## Version 0.0.8

- Add method to get grains and hops from recipe by type
- Make gravity units a property on grain additions
- Add brew house yield calculator utility
- Add custom exceptions
- Improve error messages for faster debugging

Expand Down
22 changes: 22 additions & 0 deletions brew/grains.py
Original file line number Diff line number Diff line change
Expand Up @@ -282,6 +282,28 @@ def get_weight_map(self):
u'dry_weight': round(self.get_dry_weight(), 2),
}

@property
def gu(self):
return self.get_gravity_units()

def get_gravity_units(self):
"""
Get the gravity units for the Grain Addition
:return: Gravity Units as PPG or HWE depending on units
:rtype: float
Gravity Units are really a property of whole grains, not of extracts.
Therefore the units will always be represented after the weight has
been converted from an extract weight to a cereal weight.
"""
# Pick the attribute based on units
if self.units == IMPERIAL_UNITS:
attr = u'ppg'
if self.units == SI_UNITS:
attr = u'hwe'

return getattr(self.grain, attr) * self.get_cereal_weight()

def to_dict(self):
grain_data = self.grain.to_dict()
return {u'name': grain_data.pop('name'),
Expand Down
30 changes: 23 additions & 7 deletions brew/recipes.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@
from .utilities.sugar import gu_to_sg
from .utilities.sugar import sg_to_gu
from .utilities.sugar import sg_to_plato
from .validators import validate_grain_type
from .validators import validate_hop_type
from .validators import validate_optional_fields
from .validators import validate_percentage
from .validators import validate_required_fields
Expand Down Expand Up @@ -195,20 +197,14 @@ def get_total_points(self):
:return: PPG or HWE depending on the units of the Recipe
:rtype: float
"""
# Pick the attribute based on units
if self.units == IMPERIAL_UNITS:
attr = u'ppg'
if self.units == SI_UNITS:
attr = u'hwe'

total_points = 0
for grain_add in self.grain_additions:
# DME and LME are 100% efficient in disolving in water
# Cereal extraction depends on brew house yield
efficiency = self.percent_brew_house_yield
if grain_add.grain_type in [GRAIN_TYPE_DME, GRAIN_TYPE_LME]:
efficiency = 1.0
total_points += getattr(grain_add.grain, attr) * grain_add.weight * efficiency # noqa
total_points += grain_add.gu * efficiency # noqa
return total_points

def get_original_gravity_units(self):
Expand Down Expand Up @@ -550,6 +546,26 @@ def get_total_wort_color_map(self):
},
}

def get_grain_additions_by_type(self, grain_type):
"""
Return grain additions by given grain_type
:param str grain_type: The type of grains to return
:return: list of GrainAddition objects
"""
validate_grain_type(grain_type)
return [grain_add for grain_add in self.grain_additions if grain_add.grain_type == grain_type] # noqa

def get_hop_additions_by_type(self, hop_type):
"""
Return hop additions by given hop_type
:param str hop_type: The type of hops to return
:return: list of HopAddition objects
"""
validate_hop_type(hop_type)
return [hop_add for hop_add in self.hop_additions if hop_add.hop_type == hop_type] # noqa

def to_dict(self):
og = self.og
bg = self.bg
Expand Down
27 changes: 27 additions & 0 deletions brew/utilities/efficiency.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# -*- coding: utf-8 -*-

from .sugar import sg_to_gu

__all__ = [
u'calculate_brew_house_yield',
]


def calculate_brew_house_yield(wort_volume, sg, grain_additions):
"""
Calculate Brew House Yield
:param float wort_volume: The volume of the wort
:param float sg: THe specific gravity of the wort
:param list grain_additions: A list of grain additions in the wort
:type grain_additions: list of GrainAddition objects
:return: The brew house yield as a percentage
Brew House Yield is a function of the wort volume, the measured specific
gravity, and the grain additions used to make the wort. This equation is
thrown off by use of LME or DME since both have 100% efficiency in a brew.
A better measure is to look at just the grains that needed to be steeped
seperately and measure the specific gravity of that process.
"""
total_gu = sum([grain_add.gu for grain_add in grain_additions])
return (sg_to_gu(sg) * wort_volume) / total_gu
10 changes: 10 additions & 0 deletions tests/test_grains.py
Original file line number Diff line number Diff line change
Expand Up @@ -246,6 +246,16 @@ def test_ne_units(self):
def test_ne_grain_class(self):
self.assertTrue(pale_add != pale)

def test_gu(self):
out = self.grain_add.gu
expected = 516.52
self.assertEquals(out, expected)

def test_get_gravity_units(self):
out = self.grain_add.get_gravity_units()
expected = 516.52
self.assertEquals(out, expected)

def test_to_dict(self):
out = self.grain_add.to_dict()
expected = {u'name': u'pale 2-row',
Expand Down
16 changes: 16 additions & 0 deletions tests/test_recipes.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@
import sys
import unittest

from brew.constants import GRAIN_TYPE_CEREAL
from brew.constants import GRAIN_TYPE_LME
from brew.constants import HOP_TYPE_PELLET
from brew.constants import HOP_TYPE_WHOLE
from brew.constants import IMPERIAL_UNITS
from brew.constants import SI_UNITS
from brew.exceptions import RecipeException
Expand Down Expand Up @@ -149,6 +153,18 @@ def test_hops_units_mismatch_raises(self):
self.assertEquals(str(ctx.exception),
u"pale ale: Hop addition units must be in 'imperial' not 'metric'") # noqa

def test_get_grain_additions_by_type(self):
grain_additions = self.recipe.get_grain_additions_by_type(GRAIN_TYPE_CEREAL) # noqa
self.assertEquals(grain_additions, self.recipe.grain_additions)
grain_additions = self.recipe.get_grain_additions_by_type(GRAIN_TYPE_LME) # noqa
self.assertEquals(grain_additions, [])

def test_get_hop_additions_by_type(self):
hop_additions = self.recipe.get_hop_additions_by_type(HOP_TYPE_PELLET) # noqa
self.assertEquals(hop_additions, self.recipe.hop_additions)
hop_additions = self.recipe.get_hop_additions_by_type(HOP_TYPE_WHOLE) # noqa
self.assertEquals(hop_additions, [])


class TestRecipeBuilder(unittest.TestCase):

Expand Down
4 changes: 2 additions & 2 deletions tests/test_recipes_imperial.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ def test_get_total_points_lme(self):
units=IMPERIAL_UNITS)

out = recipe.get_total_points()
self.assertEquals(round(out, 2), 387.39)
self.assertEquals(round(out, 2), 516.52)

def test_get_total_points_dme(self):
pale_dme = GrainAddition(pale,
Expand All @@ -74,7 +74,7 @@ def test_get_total_points_dme(self):
units=IMPERIAL_UNITS)

out = recipe.get_total_points()
self.assertEquals(round(out, 2), 310.06)
self.assertEquals(round(out, 2), 516.77)

def test_get_original_gravity_units(self):
out = self.recipe.get_original_gravity_units()
Expand Down
4 changes: 2 additions & 2 deletions tests/test_recipes_si.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ def test_get_total_points_lme(self):
units=SI_UNITS)

out = recipe.get_total_points()
self.assertEquals(round(out, 2), 1466.71)
self.assertEquals(round(out, 2), 1955.61)

def test_get_total_points_dme(self):
pale_dme = GrainAddition(pale,
Expand All @@ -85,7 +85,7 @@ def test_get_total_points_dme(self):
units=SI_UNITS)

out = recipe.get_total_points()
self.assertEquals(round(out, 2), 1173.36)
self.assertEquals(round(out, 2), 1955.61)

def test_get_original_gravity_units(self):
out = self.recipe.get_original_gravity_units()
Expand Down
17 changes: 17 additions & 0 deletions tests/test_utilities_efficiency.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# -*- coding: utf-8 -*-
import unittest

from brew.utilities.efficiency import calculate_brew_house_yield
from fixtures import recipe


class TestEfficiencyUtilities(unittest.TestCase):

def setUp(self):
self.recipe = recipe

def test_calculate_brew_house_yield(self):
out = calculate_brew_house_yield(recipe.final_volume,
recipe.og,
recipe.grain_additions)
self.assertEquals(round(out, 3), self.recipe.percent_brew_house_yield)

0 comments on commit 999855a

Please sign in to comment.