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)
|
||||
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):
|
||||
return self.name
|
||||
|
||||
@ -97,6 +188,7 @@ class Fermentable(CustomIngredient):
|
||||
types = {
|
||||
1: 'Grain',
|
||||
2: 'Adjunct',
|
||||
3: 'Sugar'
|
||||
}
|
||||
|
||||
grain_category = models.IntegerField(choices=categories, default=1)
|
||||
@ -119,6 +211,10 @@ class RecipeFermentable(CustomModel):
|
||||
fermentable = models.ForeignKey(Fermentable, on_delete=models.CASCADE)
|
||||
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):
|
||||
uses = {
|
||||
1: 'Bittering',
|
||||
@ -155,6 +251,25 @@ class RecipeHop(CustomModel):
|
||||
time = models.IntegerField(default=60, validators=[MinValueValidator(0)])
|
||||
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):
|
||||
uses = {
|
||||
1: 'Mash',
|
||||
|
Loading…
Reference in New Issue
Block a user