Plato on Github
Report Home
lib/brauhaus.js
Maintainability
58.46
Lines of code
1158
Difficulty
141.81
Estimated Errors
17.02
Function weight
By Complexity
By SLOC
// Generated by CoffeeScript 1.6.3 /* @preserve Brauhaus.js Beer Calculator Copyright 2013 Daniel G. Taylor
https://github.com/homebrewing/brauhausjs */ (function() { var Brauhaus, tanh, _ref, _ref1, _ref2, _ref3, __hasProp = {}.hasOwnProperty, __indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; }, __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }; tanh = function(number) { return (Math.exp(number) - Math.exp(-number)) / (Math.exp(number) + Math.exp(-number)); }; Brauhaus = (typeof exports !== "undefined" && exports !== null) && exports || (this.Brauhaus = {}); /* Global constants ------------------------------------------------------------- */ Brauhaus.ROOM_TEMP = 23; Brauhaus.BURNER_ENERGY = 9000; Brauhaus.MASH_HEAT_LOSS = 5.0; Brauhaus.COLOR_NAMES = [[2, 'pale straw'], [3, 'straw'], [4, 'yellow'], [6, 'gold'], [9, 'amber'], [14, 'deep amber'], [17, 'copper'], [18, 'deep copper'], [22, 'brown'], [30, 'dark brown'], [35, 'very dark brown'], [40, 'black']]; /* Simple parsing functions ----------------------------------------------------- */ Brauhaus.parseDuration = function(value) { var days, duration, factor, factors, hours, min, sec, unit, weeks, _i, _len, _ref; duration = 0; if (!isNaN(value)) { return value; } weeks = value.match(/(\d+)\s*w/i); days = value.match(/(\d+)\s*d/i); hours = value.match(/(\d+)\s*h/i); min = value.match(/(\d+)\s*m/i); sec = value.match(/(\d+)\s*s/i); factors = [[weeks, 7 * 60 * 24], [days, 60 * 24], [hours, 60], [min, 1], [sec, 1.0 / 60]]; for (_i = 0, _len = factors.length; _i < _len; _i++) { _ref = factors[_i], unit = _ref[0], factor = _ref[1]; if (unit) { duration += parseInt(unit[1]) * factor; } } return duration; }; Brauhaus.displayDuration = function(minutes, approximate) { var amount, count, durations, factor, factors, label, _i, _len, _ref, _ref1; durations = []; factors = [['month', 30 * 60 * 24], ['week', 7 * 60 * 24], ['day', 60 * 24], ['hour', 60], ['minute', 1]]; count = 0; for (_i = 0, _len = factors.length; _i < _len; _i++) { _ref = factors[_i], label = _ref[0], factor = _ref[1]; if (factor === 1 || ((approximate != null) && count === approximate - 1)) { amount = Math.round(minutes / factor); } else { amount = Math.floor(minutes / factor); } minutes = minutes % factor; if (amount > 0 || count > 0) { count++; } if ((approximate != null) && count > approximate) { break; } if (amount > 0) { durations.push("" + amount + " " + label + ((_ref1 = (amount !== 1 ? 's' : void 0)) != null ? _ref1 : '')); } } if (!durations.length) { durations = ['start']; } return durations.join(' '); }; /* Conversion functions --------------------------------------------------------- */ Brauhaus.kgToLb = function(kg) { return kg * 2.20462; }; Brauhaus.lbToKg = function(lb) { return lb / 2.20462; }; Brauhaus.kgToLbOz = function(kg) { var lb; lb = Brauhaus.kgToLb(kg); return { lb: Math.floor(lb), oz: (lb - Math.floor(lb)) * 16.0 }; }; Brauhaus.lbOzToKg = function(lb, oz) { return Brauhaus.lbToKg(lb + (oz / 16.0)); }; Brauhaus.litersToGallons = function(liters) { return liters * 0.264172; }; Brauhaus.gallonsToLiters = function(gallons) { return gallons / 0.264172; }; Brauhaus.litersPerKgToQuartsPerLb = function(litersPerKg) { return litersPerKg * 0.479305709; }; Brauhaus.quartsPerLbToLitersPerKg = function(quartsPerLb) { return quartsPerLb / 0.479305709; }; Brauhaus.cToF = function(celcius) { return celcius * 1.8 + 32; }; Brauhaus.fToC = function(fahrenheit) { return (fahrenheit - 32) / 1.8; }; /* Color functions -------------------------------------------------------------- */ Brauhaus.srmToEbc = function(srm) { return srm * 1.97; }; Brauhaus.ebcToSrm = function(ebc) { return ebc * 0.508; }; Brauhaus.srmToLovibond = function(srm) { return (srm + 0.76) / 1.3546; }; Brauhaus.lovibondToSrm = function(lovibond) { return 1.3546 * lovibond - 0.76; }; Brauhaus.srmToRgb = function(srm) { var b, g, r; r = Math.round(Math.min(255, Math.max(0, 255 * Math.pow(0.975, srm)))); g = Math.round(Math.min(255, Math.max(0, 245 * Math.pow(0.88, srm)))); b = Math.round(Math.min(255, Math.max(0, 220 * Math.pow(0.7, srm)))); return [r, g, b]; }; Brauhaus.srmToCss = function(srm) { var b, g, r, _ref; _ref = Brauhaus.srmToRgb(srm), r = _ref[0], g = _ref[1], b = _ref[2]; return "rgb(" + r + ", " + g + ", " + b + ")"; }; Brauhaus.srmToName = function(srm) { var color, item, _i, _len, _ref; color = Brauhaus.COLOR_NAMES[0][1]; _ref = Brauhaus.COLOR_NAMES; for (_i = 0, _len = _ref.length; _i < _len; _i++) { item = _ref[_i]; if (item[0] <= srm) { color = item[1]; } } return color; }; /* Other Utilities -------------------------------------------------------------- */ Brauhaus.timeToHeat = function(liters, degrees) { var kj, minutes; if (degrees == null) { degrees = 80; } kj = 4.19 * liters * degrees; return minutes = kj / Brauhaus.BURNER_ENERGY * 60; }; /* Base objects ----------------------------------------------------------------- */ Brauhaus.OptionConstructor = (function() { OptionConstructor.prototype._paramMap = {}; function OptionConstructor(options) { var item, keys, property; if (typeof options === 'string') { options = JSON.parse(options); } keys = Object.keys(this._paramMap); for (property in options) { if (!__hasProp.call(options, property)) continue; if (__indexOf.call(keys, property) >= 0) { if (options[property] instanceof Array) { this[property] = (function() { var _i, _len, _ref, _results; _ref = options[property]; _results = []; for (_i = 0, _len = _ref.length; _i < _len; _i++) { item = _ref[_i]; if (item instanceof this._paramMap[property]) { _results.push(item); } else { _results.push(new this._paramMap[property](item)); } } return _results; }).call(this); } else { if (options[property] instanceof this._paramMap[property]) { this[property] = options[property]; } else { this[property] = new this._paramMap[property](options[property]); } } } else { this[property] = options[property]; } } } return OptionConstructor; })(); /* Base class for new recipe ingredients. Each ingredient gets a name, which defaults to 'New ' + the class name. For classes that inherit Ingredient it will use their name, e.g: */ Brauhaus.Ingredient = (function(_super) { __extends(Ingredient, _super); function Ingredient(options) { this.name = 'New ' + this.constructor.name; Ingredient.__super__.constructor.call(this, options); } Ingredient.prototype.nameRegex = function(regex) { var item, result, _i, _len; result = false; if (typeof regex === 'string') { result = regex.exec(this.name); } else { for (_i = 0, _len = regex.length; _i < _len; _i++) { item = regex[_i]; if (Array.isArray(item) && item.length === 2) { if (item[0].exec(this.name)) { result = item[1]; break; } } else if (typeof item === 'string') { result = item.exec(this.name); } else { throw new Error('Invalid regex input!'); } } } return result; }; return Ingredient; })(Brauhaus.OptionConstructor); /* A fermentable ingredient, e.g. liquid malt extract. Each ingredient has a name, weight in kilograms, yield as a percentage, color in degrees SRM, and is marked as either late or normal. Late additions affect hop utilization. Each fermentable also provides methods for getting the type, addition, color name, and gravity units per volume of liquid. */ Brauhaus.Fermentable = (function(_super) { __extends(Fermentable, _super); function Fermentable() { _ref = Fermentable.__super__.constructor.apply(this, arguments); return _ref; } Fermentable.STEEP = /biscuit|black|cara|chocolate|crystal|munich|roast|special ?b|toast|victory|vienna/i; Fermentable.BOIL = /candi|candy|dme|dry|extract|honey|lme|liquid|sugar|syrup|turbinado/i; Fermentable.prototype.weight = 1.0; Fermentable.prototype["yield"] = 75.0; Fermentable.prototype.color = 2.0; Fermentable.prototype.late = false; Fermentable.prototype.toJSON = function() { var json; return json = { name: this.name, weight: this.weight, "yield": this["yield"], color: this.color, late: this.late }; }; Fermentable.prototype.type = function() { return this.nameRegex([[Brauhaus.Fermentable.BOIL, 'extract'], [/.*/, 'grain']]); }; Fermentable.prototype.addition = function() { return this.nameRegex([[/mash/i, 'mash'], [/steep/i, 'steep'], [/boil/i, 'boil'], [Brauhaus.Fermentable.BOIL, 'boil'], [Brauhaus.Fermentable.STEEP, 'steep'], [/.*/, 'mash']]); }; Fermentable.prototype.weightLb = function() { return Brauhaus.kgToLb(this.weight); }; Fermentable.prototype.weightLbOz = function() { return Brauhaus.kgToLbOz(this.weight); }; Fermentable.prototype.ppg = function() { return this["yield"] * 0.46214; }; Fermentable.prototype.plato = function(liters) { if (liters == null) { liters = 1.0; } return 259 - (259 / (1.0 + this.gu(liters) / 1000)); }; Fermentable.prototype.gu = function(liters) { if (liters == null) { liters = 1.0; } return this.ppg() * this.weightLb() / Brauhaus.litersToGallons(liters); }; Fermentable.prototype.colorRgb = function() { return Brauhaus.srmToRgb(this.color); }; Fermentable.prototype.colorCss = function() { return Brauhaus.srmToCss(this.color); }; Fermentable.prototype.colorName = function() { return Brauhaus.srmToName(this.color); }; Fermentable.prototype.price = function() { var pricePerKg; pricePerKg = this.nameRegex([[/dry|dme/i, 8.80], [/liquid|lme/i, 6.60], [/.*/i, 4.40]]); return this.weight * pricePerKg; }; return Fermentable; })(Brauhaus.Ingredient); /* A spice ingredient, e.g. cascade hops or crushed coriander. Each spice has a weight in kilograms, alpha acid (aa) percentage, use (mash, boil, primary, secondary, etc), time in minutes to add the spice, and the spice's form (whole, leaf, pellet, crushed, ground, etc). */ Brauhaus.Spice = (function(_super) { __extends(Spice, _super); function Spice() { _ref1 = Spice.__super__.constructor.apply(this, arguments); return _ref1; } Spice.DRY_SPICE = /primary|secondary|dry/i; Spice.prototype.weight = 0.025; Spice.prototype.aa = 0.0; Spice.prototype.use = 'boil'; Spice.prototype.time = 60; Spice.prototype.form = 'pellet'; Spice.prototype.toJSON = function() { var json; return json = { name: this.name, weight: this.weight, aa: this.aa, use: this.use, time: this.time, form: this.form }; }; Spice.prototype.dry = function() { return Brauhaus.Spice.DRY_SPICE.exec(this.use) || false; }; Spice.prototype.weightLb = function() { return Brauhaus.kgToLb(this.weight); }; Spice.prototype.weightLbOz = function() { return Brauhaus.kgToLbOz(this.weight); }; Spice.prototype.price = function() { var pricePerKg; pricePerKg = this.nameRegex([[/.*/i, 17.64]]); return this.weight * pricePerKg; }; return Spice; })(Brauhaus.Ingredient); /* A yeast ingredient, e.g. Safbrew T-58 or Brett B. Each yeast has a type (ale, lager, other), a form (liquid, dry), and an attenuation percentage that describes the maximum attenuation under ideal conditions. */ Brauhaus.Yeast = (function(_super) { __extends(Yeast, _super); function Yeast() { _ref2 = Yeast.__super__.constructor.apply(this, arguments); return _ref2; } Yeast.prototype.type = 'ale'; Yeast.prototype.form = 'liquid'; Yeast.prototype.attenuation = 75.0; Yeast.prototype.toJSON = function() { var json; return json = { name: this.name, type: this.type, form: this.form, attenuation: this.attenuation }; }; Yeast.prototype.price = function() { return this.nameRegex([[/wyeast|white labs|wlp/i, 7.00], [/.*/i, 3.50]]); }; return Yeast; })(Brauhaus.Ingredient); /* A mash step, which contains information about a specific step during the mash process, such as the amount of water to add, temperature to raise or lower the mash to, etc. */ Brauhaus.MashStep = (function(_super) { __extends(MashStep, _super); function MashStep() { _ref3 = MashStep.__super__.constructor.apply(this, arguments); return _ref3; } MashStep.types = ['Infusion', 'Temperature', 'Decoction']; MashStep.prototype.name = 'Saccharification'; MashStep.prototype.type = 'Infusion'; MashStep.prototype.waterRatio = 3.0; MashStep.prototype.temp = 68.0; MashStep.prototype.endTemp = null; MashStep.prototype.time = 60; MashStep.prototype.rampTime = null; MashStep.prototype.toJSON = function() { var json; return json = { name: this.name, type: this.type, waterRatio: this.waterRatio, temp: this.temp, endTemp: this.endTemp, time: this.time, rampTime: this.rampTime }; }; MashStep.prototype.description = function(siUnits, totalGrainWeight) { var absoluteUnits, desc, relativeUnits, temp, waterAmount, waterRatio; if (siUnits == null) { siUnits = true; } desc = ''; if (siUnits) { absoluteUnits = 'l'; relativeUnits = 'l per kg'; temp = "" + this.temp + "C"; waterRatio = this.waterRatio; } else { absoluteUnits = 'qt'; relativeUnits = 'qt per lb'; temp = "" + (this.tempF()) + "F"; waterRatio = this.waterRatioQtPerLb(); } if (totalGrainWeight != null) { if (!siUnits) { totalGrainWeight = Brauhaus.kgToLb(totalGrainWeight); } waterAmount = "" + ((waterRatio * totalGrainWeight).toFixed(1)) + absoluteUnits; } else { waterAmount = "" + (waterRatio.toFixed(1)) + relativeUnits + " of grain"; } switch (this.type) { case 'Infusion': desc = "Infuse " + waterAmount + " for " + this.time + " minutes at " + temp; break; case 'Temperature': desc = "Stop heating and hold for " + this.time + " minutes at " + temp; break; case 'Decoction': desc = "Add " + waterAmount + " boiled water to reach " + temp + " and hold for " + this.time + " minutes"; break; default: desc = "Unknown mash step type '" + this.type + "'!"; } return desc; }; MashStep.prototype.waterRatioQtPerLb = function() { return Brauhaus.litersPerKgToQuartsPerLb(this.waterRatio); }; MashStep.prototype.tempF = function() { return Brauhaus.cToF(this.temp); }; MashStep.prototype.endTempF = function() { return Brauhaus.cToF(this.endTemp); }; return MashStep; })(Brauhaus.OptionConstructor); /* A mash profile, which contains information about a mash along with a list of steps to be taken. */ Brauhaus.Mash = (function(_super) { __extends(Mash, _super); Mash.prototype._paramMap = { steps: Brauhaus.MashStep }; function Mash(options) { this.steps = []; Mash.__super__.constructor.call(this, options); } Mash.prototype.name = ''; Mash.prototype.grainTemp = Brauhaus.ROOM_TEMP; Mash.prototype.spargeTemp = 76; Mash.prototype.ph = null; Mash.prototype.notes = ''; Mash.prototype.steps = null; Mash.prototype.toJSON = function() { var json; return json = { name: this.name, grainTemp: this.grainTemp, spargeTemp: this.spargeTemp, ph: this.ph, notes: this.notes, steps: this.steps }; }; Mash.prototype.grainTempF = function() { return Brauhaus.cToF(this.grainTemp); }; Mash.prototype.spargeTempF = function() { return Brauhaus.cToF(this.spargeTemp); }; Mash.prototype.addStep = function(options) { return this.steps.push(new Brauhaus.MashStep(options)); }; return Mash; })(Brauhaus.OptionConstructor); /* A beer recipe, consisting of various ingredients and metadata which provides a calculate() method to calculate OG, FG, IBU, ABV, and a timeline of instructions for brewing the recipe. */ Brauhaus.Recipe = (function(_super) { __extends(Recipe, _super); Recipe.prototype.name = 'New Recipe'; Recipe.prototype.description = 'Recipe description'; Recipe.prototype.author = 'Anonymous Brewer'; Recipe.prototype.boilSize = 10.0; Recipe.prototype.batchSize = 20.0; Recipe.prototype.servingSize = 0.355; Recipe.prototype.steepEfficiency = 50; Recipe.prototype.steepTime = 20; Recipe.prototype.mashEfficiency = 75; Recipe.prototype.style = null; Recipe.prototype.ibuMethod = 'tinseth'; Recipe.prototype.fermentables = null; Recipe.prototype.spices = null; Recipe.prototype.yeast = null; Recipe.prototype.mash = null; Recipe.prototype.og = 0.0; Recipe.prototype.fg = 0.0; Recipe.prototype.color = 0.0; Recipe.prototype.ibu = 0.0; Recipe.prototype.abv = 0.0; Recipe.prototype.price = 0.0; Recipe.prototype.buToGu = 0.0; Recipe.prototype.bv = 0.0; Recipe.prototype.ogPlato = 0.0; Recipe.prototype.fgPlato = 0.0; Recipe.prototype.abw = 0.0; Recipe.prototype.realExtract = 0.0; Recipe.prototype.calories = 0.0; Recipe.prototype.bottlingTemp = 0.0; Recipe.prototype.bottlingPressure = 0.0; Recipe.prototype.primaryDays = 14.0; Recipe.prototype.primaryTemp = 20.0; Recipe.prototype.secondaryDays = 0.0; Recipe.prototype.secondaryTemp = 0.0; Recipe.prototype.tertiaryDays = 0.0; Recipe.prototype.tertiaryTemp = 0.0; Recipe.prototype.agingDays = 14; Recipe.prototype.agingTemp = 20.0; Recipe.prototype.brewDayDuration = null; Recipe.prototype.timelineMap = null; Recipe.prototype._paramMap = { fermentables: Brauhaus.Fermentable, spices: Brauhaus.Spice, yeast: Brauhaus.Yeast, mash: Brauhaus.Mash }; function Recipe(options) { this.fermentables = []; this.spices = []; this.yeast = []; Recipe.__super__.constructor.call(this, options); } Recipe.prototype.toJSON = function() { var json; return json = { name: this.name, description: this.description, author: this.author, boilSize: this.boilSize, batchSize: this.batchSize, servingSize: this.servingSize, steepEfficiency: this.steepEfficiency, steepTime: this.steepTime, mashEfficiency: this.mashEfficiency, style: this.style, ibuMethod: this.ibuMethod, fermentables: this.fermentables, spices: this.spices, yeast: this.yeast, mash: this.mash, bottlingTemp: this.bottlingTemp, bottlingPressure: this.bottlingPressure, primaryDays: this.primaryDays, primaryTemp: this.primaryTemp, secondaryDays: this.secondaryDays, secondaryTemp: this.secondaryTemp, tertiaryDays: this.tertiaryDays, tertiaryTemp: this.tertiaryTemp, agingDays: this.agingDays, agingTemp: this.agingTemp }; }; Recipe.prototype.batchSizeGallons = function() { return Brauhaus.litersToGallons(this.batchSize); }; Recipe.prototype.boilSizeGallons = function() { return Brauhaus.litersToGallons(this.boilSize); }; Recipe.prototype.add = function(type, values) { switch (type) { case 'fermentable': return this.fermentables.push(new Brauhaus.Fermentable(values)); case 'spice': case 'hop': return this.spices.push(new Brauhaus.Spice(values)); case 'yeast': return this.yeast.push(new Brauhaus.Yeast(values)); } }; Recipe.prototype.grainWeight = function() { var fermentable, weight, _i, _len, _ref4; weight = 0.0; _ref4 = this.fermentables; for (_i = 0, _len = _ref4.length; _i < _len; _i++) { fermentable = _ref4[_i]; if (fermentable.type() === 'grain') { weight += fermentable.weight; } } return weight; }; Recipe.prototype.bottleCount = function() { return Math.floor(this.batchSize / this.servingSize); }; Recipe.prototype.calculate = function() { var addition, adjustment, attenuation, bitterness, earlyOg, efficiency, fermentable, gravity, gu, mcu, rte, spice, time, utilization, utilizationFactor, yeast, _base, _base1, _i, _j, _k, _len, _len1, _len2, _ref4, _ref5, _ref6; this.og = 1.0; this.fg = 0.0; this.ibu = 0.0; this.price = 0.0; earlyOg = 1.0; mcu = 0.0; attenuation = 0.0; this.timelineMap = { fermentables: { mash: [], steep: [], boil: [], boilEnd: [] }, times: {}, drySpice: {}, yeast: [] }; _ref4 = this.fermentables; for (_i = 0, _len = _ref4.length; _i < _len; _i++) { fermentable = _ref4[_i]; efficiency = 1.0; addition = fermentable.addition(); if (addition === 'steep') { efficiency = this.steepEfficiency / 100.0; } else if (addition === 'mash') { efficiency = this.mashEfficiency / 100.0; } mcu += fermentable.color * fermentable.weightLb() / this.batchSizeGallons(); gu = fermentable.gu(this.batchSize) * efficiency; gravity = gu / 1000.0; this.og += gravity; if (!fermentable.late) { earlyOg += gravity; } this.price += fermentable.price(); switch (addition) { case 'boil': if (!fermentable.late) { this.timelineMap.fermentables.boil.push([fermentable, gu]); } else { this.timelineMap.fermentables.boilEnd.push([fermentable, gu]); } break; case 'steep': this.timelineMap.fermentables.steep.push([fermentable, gu]); break; case 'mash': this.timelineMap.fermentables.mash.push([fermentable, gu]); } } this.color = 1.4922 * Math.pow(mcu, 0.6859); _ref5 = this.yeast; for (_j = 0, _len1 = _ref5.length; _j < _len1; _j++) { yeast = _ref5[_j]; if (yeast.attenuation > attenuation) { attenuation = yeast.attenuation; } this.price += yeast.price(); this.timelineMap.yeast.push(yeast); } if (attenuation === 0) { attenuation = 75.0; } this.fg = this.og - ((this.og - 1.0) * attenuation / 100.0); this.abv = ((1.05 * (this.og - this.fg)) / this.fg) / 0.79 * 100.0; this.ogPlato = (-463.37) + (668.72 * this.og) - (205.35 * (this.og * this.og)); this.fgPlato = (-463.37) + (668.72 * this.fg) - (205.35 * (this.fg * this.fg)); this.realExtract = (0.1808 * this.ogPlato) + (0.8192 * this.fgPlato); this.abw = 0.79 * this.abv / this.fg; this.calories = Math.max(0, ((6.9 * this.abw) + 4.0 * (this.realExtract - 0.10)) * this.fg * this.servingSize * 10); _ref6 = this.spices; for (_k = 0, _len2 = _ref6.length; _k < _len2; _k++) { spice = _ref6[_k]; bitterness = 0.0; time = spice.time; if (spice.aa && spice.use.toLowerCase() === 'boil') { utilizationFactor = 1.0; if (spice.form === 'pellet') { utilizationFactor = 1.15; } if (this.ibuMethod === 'tinseth') { bitterness = 1.65 * Math.pow(0.000125, earlyOg - 1.0) * ((1 - Math.pow(Math.E, -0.04 * spice.time)) / 4.15) * ((spice.aa / 100.0 * spice.weight * 1000000) / this.batchSize) * utilizationFactor; this.ibu += bitterness; } else if (this.ibuMethod === 'rager') { utilization = 18.11 + 13.86 * tanh((spice.time - 31.32) / 18.27); adjustment = Math.max(0, (earlyOg - 1.050) / 0.2); bitterness = spice.weight * 100 * utilization * utilizationFactor * spice.aa / (this.batchSize * (1 + adjustment)); this.ibu += bitterness; } else { throw new Error("Unknown IBU method '" + this.ibuMethod + "'!"); } } this.price += spice.price(); if (spice.dry()) { if ((_base = this.timelineMap['drySpice'])[time] == null) { _base[time] = []; } this.timelineMap['drySpice'][time].push([spice, bitterness]); } else { if ((_base1 = this.timelineMap['times'])[time] == null) { _base1[time] = []; } this.timelineMap['times'][time].push([spice, bitterness]); } } this.buToGu = this.ibu / (this.og - 1.000) / 1000.0; rte = (0.82 * (this.fg - 1.000) + 0.18 * (this.og - 1.000)) * 1000.0; return this.bv = 0.8 * this.ibu / rte; }; Recipe.prototype.timeline = function(siUnits) { var action, ageTemp, boilName, boilTime, boilVolume, chillTemp, currentTemp, fermentable, fermentableList, gravity, heatTemp, i, ingredients, key, liquidVolume, mash, previousSpiceTime, primeMsg, spiceList, steepHeatTime, steepTemp, steepVolume, steepWeight, step, steps, strikeTemp, strikeTempDesc, strikeVolume, strikeVolumeDesc, time, timeToHeat, timeline, times, timesStart, totalTime, value, waterChangeRatio, yeast, yeasts, _i, _j, _len, _len1, _ref4, _ref5; if (siUnits == null) { siUnits = true; } timeline = []; boilName = 'water'; totalTime = 0; currentTemp = Brauhaus.ROOM_TEMP; liquidVolume = 0; fermentableList = function(items) { var fermentable, gravity, ingredients, lboz, weight, _i, _len, _ref4, _ref5; ingredients = []; _ref4 = items || []; for (_i = 0, _len = _ref4.length; _i < _len; _i++) { _ref5 = _ref4[_i], fermentable = _ref5[0], gravity = _ref5[1]; if (siUnits) { weight = "" + (fermentable.weight.toFixed(1)) + "kg"; } else { lboz = fermentable.weightLbOz(); weight = "" + (parseInt(lboz.lb)) + "lb " + (parseInt(lboz.oz)) + "oz"; } ingredients.push("" + weight + " of " + fermentable.name + " (" + (gravity.toFixed(1)) + " GU)"); } return ingredients; }; spiceList = function(items) { var extra, ibu, ingredients, spice, weight, _i, _len, _ref4, _ref5; ingredients = []; _ref4 = items || []; for (_i = 0, _len = _ref4.length; _i < _len; _i++) { _ref5 = _ref4[_i], spice = _ref5[0], ibu = _ref5[1]; if (siUnits) { weight = "" + (parseInt(spice.weight * 1000)) + "g"; } else { weight = "" + ((spice.weightLb() * 16.0).toFixed(2)) + "oz"; } extra = ''; if (ibu) { extra = " (" + (ibu.toFixed(1)) + " IBU)"; } ingredients.push("" + weight + " of " + spice.name + extra); } return ingredients; }; if (this.timelineMap.fermentables.mash.length) { boilName = 'wort'; mash = this.mash; if (mash == null) { mash = new Brauhaus.Mash(); } ingredients = fermentableList(this.timelineMap.fermentables.mash); timeline.push([totalTime, "Begin " + mash.name + " mash. Add " + (ingredients.join(', ')) + "."]); steps = ((_ref4 = this.mash) != null ? _ref4.steps : void 0) || [ new Brauhaus.MashStep({ name: 'Saccharification', type: 'Infusion', time: 60, rampTime: Brauhaus.timeToHeat(this.grainWeight(), 68 - currentTemp), temp: 68, waterRatio: 2.75 }) ]; for (_i = 0, _len = steps.length; _i < _len; _i++) { step = steps[_i]; strikeVolume = (step.waterRatio * this.grainWeight()) - liquidVolume; if (step.temp !== currentTemp && strikeVolume > 0) { strikeTemp = ((step.temp - currentTemp) * (0.4184 * this.grainWeight()) / strikeVolume) + step.temp; timeToHeat = Brauhaus.timeToHeat(strikeVolume, strikeTemp - currentTemp); if (siUnits) { strikeVolumeDesc = "" + (strikeVolume.toFixed(1)) + "l"; strikeTempDesc = "" + (Math.round(strikeTemp)) + "°C"; } else { strikeVolumeDesc = "" + ((Brauhaus.litersToGallons(strikeVolume) * 4).toFixed(1)) + "qts"; strikeTempDesc = "" + (Math.round(Brauhaus.cToF(strikeTemp))) + "°F"; } timeline.push([totalTime, "Heat " + strikeVolumeDesc + " to " + strikeTempDesc + " (about " + (Math.round(timeToHeat)) + " minutes)"]); liquidVolume += strikeVolume; totalTime += timeToHeat; } else if (step.temp !== currentTemp) { timeToHeat = Brauhaus.timeToHeat(liquidVolume, step.temp - currentTemp); if (siUnits) { heatTemp = "" + (Math.round(step.temp)) + "°C"; } else { heatTemp = "" + (Math.round(Brauhaus.cToF(step.temp))) + "°F"; } timeline.push([totalTime, "Heat the mash to " + heatTemp + " (about " + (Math.round(timeToHeat)) + " minutes)"]); totalTime += timeToHeat; } timeline.push([totalTime, "" + step.name + ": " + (step.description(siUnits, this.grainWeight())) + "."]); totalTime += step.time; currentTemp = step.temp - (step.time * Brauhaus.MASH_HEAT_LOSS / 60.0); } timeline.push([totalTime, 'Remove grains from mash. This is now your wort.']); totalTime += 5; } if (this.timelineMap.fermentables.steep.length) { boilName = 'wort'; steepWeight = ((function() { var _j, _len1, _ref5, _ref6, _results; _ref5 = this.timelineMap.fermentables.steep; _results = []; for (_j = 0, _len1 = _ref5.length; _j < _len1; _j++) { _ref6 = _ref5[_j], fermentable = _ref6[0], gravity = _ref6[1]; _results.push(fermentable.weight); } return _results; }).call(this)).reduce(function(x, y) { return x + y; }); steepHeatTime = Brauhaus.timeToHeat(steepWeight * 2.75, 68 - currentTemp); currentTemp = 68; liquidVolume += steepWeight * 2.75; if (siUnits) { steepVolume = "" + ((steepWeight * 2.75).toFixed(1)) + "l"; steepTemp = "" + 68 + "°C"; } else { steepVolume = "" + (Brauhaus.litersToGallons(steepWeight * 2.75).toFixed(1)) + "gal"; steepTemp = "" + (Brauhaus.cToF(68).toFixed(1)) + "°F"; } timeline.push([totalTime, "Heat " + steepVolume + " to " + steepTemp + " (about " + (Math.round(steepHeatTime)) + " minutes)"]); totalTime += steepHeatTime; ingredients = fermentableList(this.timelineMap.fermentables.steep); timeline.push([totalTime, "Add " + (ingredients.join(', ')) + " and steep for " + this.steepTime + " minutes."]); totalTime += 20; } waterChangeRatio = Math.min(1, liquidVolume / this.boilSize); currentTemp = (currentTemp * waterChangeRatio) + (Brauhaus.ROOM_TEMP * (1.0 - waterChangeRatio)); if (siUnits) { boilVolume = "" + (this.boilSize.toFixed(1)) + "l"; } else { boilVolume = "" + (this.boilSizeGallons().toFixed(1)) + "gal"; } if (this.boilSize - liquidVolume < this.boilSize) { action = "Top up the " + boilName + " to " + boilVolume + " and heat to a rolling boil"; } else { action = "Bring " + boilVolume + " to a rolling boil"; } boilTime = parseInt(Brauhaus.timeToHeat(this.boilSize, 100 - currentTemp)); timeline.push([totalTime, "" + action + " (about " + boilTime + " minutes)."]); totalTime += boilTime; timesStart = totalTime; times = (function() { var _ref5, _results; _ref5 = this.timelineMap.times; _results = []; for (key in _ref5) { if (!__hasProp.call(_ref5, key)) continue; value = _ref5[key]; _results.push(parseInt(key)); } return _results; }).call(this); if (this.timelineMap.fermentables.boilEnd.length && __indexOf.call(times, 5) < 0) { this.timelineMap.times[5] = []; times.push(5); } previousSpiceTime = 0; _ref5 = times.sort(function(x, y) { return y - x; }); for (i = _j = 0, _len1 = _ref5.length; _j < _len1; i = ++_j) { time = _ref5[i]; ingredients = spiceList(this.timelineMap.times[time]); if (i === 0) { ingredients = fermentableList(this.timelineMap.fermentables.boil).concat(ingredients); previousSpiceTime = time; } totalTime += previousSpiceTime - time; previousSpiceTime = time; if (time === 5 && this.timelineMap.fermentables.boilEnd.length) { ingredients = fermentableList(this.timelineMap.fermentables.boilEnd).concat(ingredients); } timeline.push([totalTime, "Add " + (ingredients.join(', '))]); } totalTime += previousSpiceTime; if (siUnits) { chillTemp = "" + this.primaryTemp + "°C"; } else { chillTemp = "" + (Brauhaus.cToF(this.primaryTemp)) + "°F"; } timeline.push([totalTime, "Flame out. Begin chilling to " + chillTemp + " and aerate the cooled wort (about 20 minutes)."]); totalTime += 20; yeasts = (function() { var _k, _len2, _ref6, _results; _ref6 = this.yeast; _results = []; for (_k = 0, _len2 = _ref6.length; _k < _len2; _k++) { yeast = _ref6[_k]; _results.push(yeast.name); } return _results; }).call(this); if (!yeasts.length && this.primaryDays) { yeasts = ['yeast']; } if (yeasts.length) { timeline.push([totalTime, "Pitch " + (yeasts.join(', ')) + " and seal the fermenter. You should see bubbles in the airlock within 24 hours."]); } this.brewDayDuration = totalTime; if (!this.primaryDays && !this.secondaryDays && !this.tertiaryDays) { timeline.push([totalTime, "Drink immediately (about " + (this.bottleCount()) + " bottles)."]); return timeline; } totalTime += this.primaryDays * 1440; if (this.secondaryDays) { timeline.push([totalTime, "Move to secondary fermenter for " + (Brauhaus.displayDuration(this.secondaryDays * 1440, 2)) + "."]); totalTime += this.secondaryDays * 1440; } else if (this.tertiaryDays) { timeline.push([totalTime, "Move to tertiary fermenter for " + (Brauhaus.displayDuration(this.tertiaryDays * 1440, 2)) + "."]); totalTime += this.tertiaryDays * 1440; } primeMsg = "Prime and bottle about " + (this.bottleCount()) + " bottles."; if (this.agingDays) { if (siUnits) { ageTemp = "" + this.agingTemp + "C"; } else { ageTemp = "" + (Brauhaus.cToF(this.agingTemp)) + "F"; } primeMsg += " Age at " + ageTemp + " for " + this.agingDays + " days."; } timeline.push([totalTime, primeMsg]); totalTime += this.agingDays * 1440; timeline.push([totalTime, 'Relax, don\'t worry and have a homebrew!']); return timeline; }; return Recipe; })(Brauhaus.OptionConstructor); }).call(this);