diff --git a/beer/migrations/0013_batch_first_runnings_equipmentprofile_mash_ratio.py b/beer/migrations/0013_batch_first_runnings_equipmentprofile_mash_ratio.py new file mode 100644 index 0000000..d1e0257 --- /dev/null +++ b/beer/migrations/0013_batch_first_runnings_equipmentprofile_mash_ratio.py @@ -0,0 +1,23 @@ +# Generated by Django 5.0.6 on 2024-06-24 18:56 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('beer', '0012_recipe_fermentables'), + ] + + operations = [ + migrations.AddField( + model_name='batch', + name='first_runnings', + field=models.DecimalField(blank=True, decimal_places=4, max_digits=8, null=True), + ), + migrations.AddField( + model_name='equipmentprofile', + name='mash_ratio', + field=models.DecimalField(decimal_places=2, default=2.6, max_digits=6), + ), + ] diff --git a/beer/models.py b/beer/models.py index 2221207..69f2236 100644 --- a/beer/models.py +++ b/beer/models.py @@ -36,6 +36,48 @@ class Batch(CustomModel): recipe = models.OneToOneField( 'Recipe', on_delete=models.CASCADE, default=1) + first_runnings = models.DecimalField( + max_digits=8, decimal_places=4, null=True, blank=True) + + # Batch measurements to add: + # - Mash pH + # - First Runnings gravity (include lookup table fo rmash thickness) + # - Boil Vol + # - Pre-Boil Gravity + # - Post-Boil Vol + # - Post-Boil Gravity + # - Original Gravity + # - Final Gravity + # - Fermenter Top-Up + # - Fermenter Vol + + # Properties Needed: (https://braukaiser.com/wiki/index.php/Troubleshooting_Brewhouse_Efficiency) + # - Conversion Efficiency + # - Mash Efficiency + # - Brewhouse Efficiency + # kettle extract weight in kg = volume in liter * SG * Plato / 100 + # brewhouse efficiency in % = 100% * kettle extract weight in kg / extract in grist in kg + # - ABV + # - Attenuation + # - Actual Boil-Off Rate + # - Actual Trub/Chiller Loss + + @property + def conversion_efficiency(self): + """ Calculate conversion efficiency of mash.""" + strike_volume = (self.recipe.equipment.mash_ratio + * self.recipe.fermentable_weight) + + pot_yield = 0 + for ferm in self.recipefermentable_set.all(): + pot_yield += ferm.extract_potential * ferm.quantity + + pot_yield = pot_yield / self.recipe.fermentable_weight + fw_max = pot_yield / (strike_volume+pot_yield) + + return (100 * (self.first_runnings/fw_max) + * (100-fw_max) / (100-self.first_runnings)) + @property def brewfather_url(self): return '{}/tabs/batches/batch/{}'.format( @@ -213,6 +255,10 @@ class Recipe(CustomModel): return 0 return float(self.equipment.kettle_boil_rate) + @property + def ibu(self): # TODO: Multiple IBU formulas + return self.ibu_tinseth + @property def ibu_tinseth(self): return sum(x.ibu_tinseth for x in self.recipehop_set.all()) @@ -298,6 +344,14 @@ class RecipeFermentable(CustomModel): fermentable = models.ForeignKey(Fermentable, on_delete=models.CASCADE) quantity = models.DecimalField(max_digits=6, decimal_places=4) + # Properties Needed: + # - Extract Potential (1-moisture percent as decimal) * potential + # - The weight of extract in the grist + # extract in grist in kg = weight of grist in kg * extract potential + @property + def extract_potential(self): + return .8 * .96 + @property def percent(self): return float(100 * self.quantity / self.recipe.fermentable_weight) @@ -486,6 +540,8 @@ class EquipmentProfile(CustomModel): pellet_hop_trub = models.DecimalField( max_digits=6, decimal_places=4, default=0.025) hops_remain_kettle = models.BooleanField(default=True) + mash_ratio = models.DecimalField( + max_digits=6, decimal_places=2, default=2.6) # 1.25 qt/lb # Thermal Properties mt_initial_hear = models.DecimalField( diff --git a/beer/templates/beer/batch.html b/beer/templates/beer/batch.html new file mode 100644 index 0000000..cb10610 --- /dev/null +++ b/beer/templates/beer/batch.html @@ -0,0 +1,65 @@ +{% extends "no_jumbo_base.html" %} +{% load mathfilters %} +{% block title %}Yeast Samples{% endblock %} + +{% block style %} +.table td.fit, +.table th.fit { + white-space: nowrap; + width: 1%; +} +{% endblock %} + + + +{% block content %} + +
{{ f.quantity|floatformat:2 }} lb | +{{ f.fermentable.name }} + {{ f.fermentable.get_fermentable_type_display }} {{ f.srm }} SRM + |
+ {{ f.percent|floatformat:1 }} % | +
{{ batch.recipe.name }} + OG: {{ batch.recipe.original_sg }} IBU: {{ batch.recipe.ibu|floatformat:0 }} + |
+
{{ y.yeast.manufacturer.name }} {{ y.yeast.name }} + {{ y.yeast.long_name }} + |
+