Add a bunch of properties for beer calculation.
This commit is contained in:
parent
8a3c80b517
commit
d6aa8e8d6a
115
beer/models.py
115
beer/models.py
@ -82,6 +82,97 @@ class BatchRecipe(CustomModel):
|
|||||||
efficiency = models.DecimalField(max_digits=6, decimal_places=2, default=75)
|
efficiency = models.DecimalField(max_digits=6, decimal_places=2, default=75)
|
||||||
batch_size = models.DecimalField(max_digits=6, decimal_places=2, default=11)
|
batch_size = models.DecimalField(max_digits=6, decimal_places=2, default=11)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def fermentables(self):
|
||||||
|
return [x for x in list(self.recipefermentable_set.all())]
|
||||||
|
|
||||||
|
@property
|
||||||
|
def hops(self):
|
||||||
|
return [x for x in list(self.recipehop_set.all())]
|
||||||
|
|
||||||
|
@property
|
||||||
|
def final_volume(self):
|
||||||
|
return float(self.batch_size) + self.hop_water_loss + self.net_kettle_deadspace + self.kettle_hose_loss
|
||||||
|
|
||||||
|
@property
|
||||||
|
def ferm_yield(self):
|
||||||
|
ferm_yield = 0
|
||||||
|
|
||||||
|
for f in self.fermentables:
|
||||||
|
if f.fermentable.fermentable_type == 3: # Is sugar
|
||||||
|
ferm_yield += f.quantity * (f.fermentable.potential - 1) * 1000
|
||||||
|
else:
|
||||||
|
ferm_yield += f.quantity * (self.efficiency / 100) * (f.fermentable.potential - 1) * 1000
|
||||||
|
|
||||||
|
return float(ferm_yield)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def mash_yield(self):
|
||||||
|
mash_yield = 0
|
||||||
|
|
||||||
|
for f in self.fermentables:
|
||||||
|
if f.fermentable.fermentable_type != 3: # Is not sugar
|
||||||
|
mash_yield += f.quantity * (self.efficiency / 100) * (f.fermentable.potential - 1) * 1000
|
||||||
|
|
||||||
|
return float(mash_yield)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def original_sg(self):
|
||||||
|
return round(1 + self.ferm_yield / self.final_volume / 1000, 3)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def pre_boil_sg(self):
|
||||||
|
return self.ferm_yield / (self.final_volume + self.boil_off_gph)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def hop_water_loss(self):
|
||||||
|
hop_absorption = .025 # gallons per ounce
|
||||||
|
return sum([float(x.quantity) * hop_absorption for x in self.hops])
|
||||||
|
|
||||||
|
@property
|
||||||
|
def net_kettle_deadspace(self):
|
||||||
|
# If hops in kettle deadspace
|
||||||
|
result = self.kettle_dead_space - self.hop_water_loss
|
||||||
|
return float(max(0, result)) # No deadspace if its all filled with hop trub)
|
||||||
|
|
||||||
|
# Else hops in bag or removed
|
||||||
|
return 0
|
||||||
|
|
||||||
|
@property
|
||||||
|
def kettle_hose_loss(self):
|
||||||
|
return .25 # TODO
|
||||||
|
|
||||||
|
@property
|
||||||
|
def kettle_dead_space(self):
|
||||||
|
return .25 # TODO
|
||||||
|
|
||||||
|
@property
|
||||||
|
def boil_off_gph(self):
|
||||||
|
return .8 # TODO
|
||||||
|
|
||||||
|
@property
|
||||||
|
def ibu_tinseth(self):
|
||||||
|
return sum([x.ibu_tinseth for x in self.hops])
|
||||||
|
|
||||||
|
@property
|
||||||
|
def srm(self):
|
||||||
|
color_total = sum([x.srm for x in self.fermentables])
|
||||||
|
return 1.4922*(color_total**0.6859)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def srm_hex(self):
|
||||||
|
SRM_HEX = {
|
||||||
|
1: 'F3F993',2: 'F5F75C',3: 'F6F513',4: 'EAE615',5: 'E0D01B',
|
||||||
|
6: 'D5BC26',7: 'CDAA37',8: 'C1963C',9: 'BE8C3A',10: 'BE823A',
|
||||||
|
11: 'C17A37',12: 'BF7138',13: 'BC6733',14: 'B26033',15: 'A85839',
|
||||||
|
16: '985336',17: '8D4C32',18: '7C452D',19: '6B3A1E',20: '5D341A',
|
||||||
|
21: '4E2A0C',22: '4A2727',23: '361F1B',24: '261716',25: '231716',
|
||||||
|
26: '19100F',27: '16100F',28: '120D0C',29: '100B0A',30: '050B0A'
|
||||||
|
}
|
||||||
|
|
||||||
|
return '#{}'.format(SRM_HEX[int(self.srm)])
|
||||||
|
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.name
|
return self.name
|
||||||
|
|
||||||
@ -97,6 +188,7 @@ class Fermentable(CustomIngredient):
|
|||||||
types = {
|
types = {
|
||||||
1: 'Grain',
|
1: 'Grain',
|
||||||
2: 'Adjunct',
|
2: 'Adjunct',
|
||||||
|
3: 'Sugar'
|
||||||
}
|
}
|
||||||
|
|
||||||
grain_category = models.IntegerField(choices=categories, default=1)
|
grain_category = models.IntegerField(choices=categories, default=1)
|
||||||
@ -119,6 +211,10 @@ class RecipeFermentable(CustomModel):
|
|||||||
fermentable = models.ForeignKey(Fermentable, on_delete=models.CASCADE)
|
fermentable = models.ForeignKey(Fermentable, on_delete=models.CASCADE)
|
||||||
quantity = models.DecimalField(max_digits=6, decimal_places=4)
|
quantity = models.DecimalField(max_digits=6, decimal_places=4)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def srm(self):
|
||||||
|
return round(float(self.fermentable.lovibond) * float(self.quantity) / self.recipe.final_volume, 1)
|
||||||
|
|
||||||
class Hop(CustomIngredient):
|
class Hop(CustomIngredient):
|
||||||
uses = {
|
uses = {
|
||||||
1: 'Bittering',
|
1: 'Bittering',
|
||||||
@ -155,6 +251,25 @@ class RecipeHop(CustomModel):
|
|||||||
time = models.IntegerField(default=60, validators=[MinValueValidator(0)])
|
time = models.IntegerField(default=60, validators=[MinValueValidator(0)])
|
||||||
use = models.IntegerField(choices=uses, default=1)
|
use = models.IntegerField(choices=uses, default=1)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def ibu_tinseth(self):
|
||||||
|
type_bonus = {
|
||||||
|
1: 1.1, # Pellet
|
||||||
|
2: 1.0, # Leaf
|
||||||
|
3: 1.1, # Cryo
|
||||||
|
4: 1.4, # CO2 Extract
|
||||||
|
}
|
||||||
|
|
||||||
|
ibu = 0
|
||||||
|
|
||||||
|
average_wort_sg = (self.recipe.pre_boil_sg/1000 + (self.recipe.original_sg-1)) / 2
|
||||||
|
if self.use == 1:
|
||||||
|
conc = ((float(self.hop.alpha) / 100) * float(self.quantity)) * 7490 / self.recipe.final_volume
|
||||||
|
util = (type_bonus[self.hop.hop_type] * 1.65 * (0.000125**average_wort_sg)) * ((1-2.71828182845904**(-0.04 * self.time))/4.15)
|
||||||
|
ibu = conc * util
|
||||||
|
|
||||||
|
return float(ibu)
|
||||||
|
|
||||||
class Misc(CustomIngredient):
|
class Misc(CustomIngredient):
|
||||||
uses = {
|
uses = {
|
||||||
1: 'Mash',
|
1: 'Mash',
|
||||||
|
Loading…
Reference in New Issue
Block a user