Initial design of a yeast starter calculator based on the Braukaiser model. Changed the yeast inventory prompts to the correct Dutch amounts. The pitchrate calculator makes a difference between light and heavy beers, turning point is a SG 1.060. Some code is from Homebrew Dad's calculator.

Mon, 18 Feb 2019 22:07:12 +0100

author
Michiel Broek <mbroek@mbse.eu>
date
Mon, 18 Feb 2019 22:07:12 +0100
changeset 278
dc22dd5d77fd
parent 277
7776b3c68c46
child 279
60d56f39e63e

Initial design of a yeast starter calculator based on the Braukaiser model. Changed the yeast inventory prompts to the correct Dutch amounts. The pitchrate calculator makes a difference between light and heavy beers, turning point is a SG 1.060. Some code is from Homebrew Dad's calculator.

www/css/style.css file | annotate | diff | comparison | revisions
www/import/from_brouwhulp.php file | annotate | diff | comparison | revisions
www/includes/db_product.php file | annotate | diff | comparison | revisions
www/js/global.js file | annotate | diff | comparison | revisions
www/js/inv_yeasts.js file | annotate | diff | comparison | revisions
www/js/prod_edit.js file | annotate | diff | comparison | revisions
www/prod_edit.php file | annotate | diff | comparison | revisions
--- a/www/css/style.css	Thu Feb 14 16:28:37 2019 +0100
+++ b/www/css/style.css	Mon Feb 18 22:07:12 2019 +0100
@@ -30,6 +30,16 @@
 }
 
 
+#propagator{
+    width: 800px;
+    background: #353536;
+    margin-top: 15px;
+    border: 1px solid;
+    border-color: #59b4d4;
+    border-radius: 5px 5px 5px 5px;
+}
+
+
 #about_table {
     width: 960px;
     background: #353536;
--- a/www/import/from_brouwhulp.php	Thu Feb 14 16:28:37 2019 +0100
+++ b/www/import/from_brouwhulp.php	Mon Feb 18 22:07:12 2019 +0100
@@ -1025,13 +1025,13 @@
 		else
 			echo "Unknown FLOCCULATION " . $yeast->FLOCCULATION . PHP_EOL;
 
-		if ($yeast->ADD_TO_SECONDARY=="FALSE") {
-			$yeasts .= ',"y_use":0';	// Primary
+		if ($yeast->PRODUCT_ID=="F2" || $yeast->PRODUCT_ID=="CBC-1") {
+			$yeasts .= ',"y_use":3'; 	// Bottle
+		} else if ($yeast->ADD_TO_SECONDARY=="TRUE") {
+			$yeasts .= ',"y_use":1';	// Secondary
 			$svg = floatval($yeast->ATTENUATION);
-		} else if ($yeast->PRODUCT_ID=="F2" || $yeast->PRODUCT_ID=="CBC-1") {
-			$yeasts .= ',"y_use":3';	// Bottle
 		} else {
-			$yeasts .= ',"y_use":1';	// Secondary
+			$yeasts .= ',"y_use":0';	// Primary
 		}
 		$yeasts .= ',"y_min_temperature":' . floatval($yeast->MIN_TEMPERATURE);
 		$yeasts .= ',"y_max_temperature":' . floatval($yeast->MAX_TEMPERATURE);
--- a/www/includes/db_product.php	Thu Feb 14 16:28:37 2019 +0100
+++ b/www/includes/db_product.php	Mon Feb 18 22:07:12 2019 +0100
@@ -230,6 +230,17 @@
 	$sql .= "', wa_acid_name='" . $_POST['wa_acid_name'];
 	$sql .= "', wa_acid_perc='" . $_POST['wa_acid_perc'];
 	$sql .= "', wa_base_name='" . $_POST['wa_base_name'];
+	$sql .= "', starter_enable='" . $_POST['starter_enable'];
+	$sql .= "', starter_type='" . $_POST['starter_type'];
+	$sql .= "', starter_sg='" . $_POST['starter_sg'];
+	$sql .= "', prop1_type='" . $_POST['prop1_type'];
+	$sql .= "', prop1_volume='" . $_POST['prop1_volume'];
+	$sql .= "', prop2_type='" . $_POST['prop2_type'];
+	$sql .= "', prop2_volume='" . $_POST['prop2_volume'];
+	$sql .= "', prop3_type='" . $_POST['prop3_type'];
+	$sql .= "', prop3_volume='" . $_POST['prop3_volume'];
+	$sql .= "', prop4_type='" . $_POST['prop4_type'];
+	$sql .= "', prop4_volume='" . $_POST['prop4_volume'];
 	syslog(LOG_NOTICE, $sql);
 
 	$fermentables = '[';
@@ -631,6 +642,18 @@
 		$brew .= ',"wa_acid_name":' . $row['wa_acid_name'];
 		$brew .= ',"wa_acid_perc":' . $row['wa_acid_perc'];
 		$brew .= ',"wa_base_name":' . $row['wa_base_name'];
+		$brew .= ',"starter_enable":' . $row['starter_enable'];
+		$brew .= ',"starter_type":' . $row['starter_type'];
+		$brew .= ',"starter_sg":' . $row['starter_sg'];
+		$brew .= ',"prop1_type":' . $row['prop1_type'];
+		$brew .= ',"prop1_volume":' . $row['prop1_volume'];
+		$brew .= ',"prop2_type":' . $row['prop2_type'];
+		$brew .= ',"prop2_volume":' . $row['prop2_volume'];
+		$brew .= ',"prop3_type":' . $row['prop3_type'];
+		$brew .= ',"prop3_volume":' . $row['prop3_volume'];
+		$brew .= ',"prop4_type":' . $row['prop4_type'];
+		$brew .= ',"prop4_volume":' . $row['prop4_volume'];
+
 		if (isset($_GET['record'])) {
 			// Append stock information.
 			$fermentables = json_decode($row['json_fermentables'], true);
--- a/www/js/global.js	Thu Feb 14 16:28:37 2019 +0100
+++ b/www/js/global.js	Mon Feb 18 22:07:12 2019 +0100
@@ -196,9 +196,9 @@
 var FlocculationAdapter = new $.jqx.dataAdapter(FlocculationSource);
 
 var StarterTypeData = [
-	{ id: 0, en: 'Simple',  nl: 'simpel' },
-	{ id: 1, en: 'Aerated', nl: 'belucht' },
-	{ id: 2, en: 'Stirred', nl: 'geroerd' }
+	{ id: 0, en: 'Stirred', nl: 'Geroerd' },
+	{ id: 1, en: 'Shaken',  nl: 'Geschud' },
+	{ id: 2, en: 'Simple',  nl: 'Simpel' }
 ];
 var StarterTypeSource = {
 	localdata: StarterTypeData,
--- a/www/js/inv_yeasts.js	Thu Feb 14 16:28:37 2019 +0100
+++ b/www/js/inv_yeasts.js	Mon Feb 18 22:07:12 2019 +0100
@@ -52,18 +52,18 @@
 		if (dataRecord.form == 0) {	// Liquid
 			$("#pmpt_cost").html('Prijs per pak:');
 			$("#pmpt_inventory").html('Voorraad pak(ken):');
-			$("#pmpt_cells").html('Biljoen cellen per pak:');
-			$("#inventory").jqxNumberInput({ decimalDigits: 0, spinButtonsStep: 1 });
+			$("#pmpt_cells").html('Miljard cellen per pak:');
+			$("#inventory").jqxNumberInput({ decimalDigits: 0 });
 		} else if (dataRecord.form == 1) {	// Dry
 			$("#pmpt_cost").html('Prijs per kg:');
 			$("#pmpt_inventory").html('Voorraad gram:');
-			$("#pmpt_cells").html('Biljoen cellen per gram:');
-			$("#inventory").jqxNumberInput({ decimalDigits: 1, spinButtonsStep: 0.5 });
+			$("#pmpt_cells").html('Miljard cellen per gram:');
+			$("#inventory").jqxNumberInput({ decimalDigits: 1 });
 		} else {
 			$("#pmpt_cost").html('Prijs per liter:');
 			$("#pmpt_inventory").html('Voorraad ml:');
-			$("#pmpt_cells").html('Biljoen cellen per ml:');
-			$("#inventory").jqxNumberInput({ decimalDigits: 1, spinButtonsStep: 0.5 });
+			$("#pmpt_cells").html('Miljard cellen per ml:');
+			$("#inventory").jqxNumberInput({ decimalDigits: 1 });
 		}
 	}
 
--- a/www/js/prod_edit.js	Thu Feb 14 16:28:37 2019 +0100
+++ b/www/js/prod_edit.js	Mon Feb 18 22:07:12 2019 +0100
@@ -54,6 +54,8 @@
 	var     pcara = 0;		// Percentage cara/crystal malts
 	var     svg = 77;               // Default attenuation
         var     mashkg = 0;             // Malt in mash weight
+	var	pitchrate = 0.75;	// Yeast pitch rate default
+	var	initcells = 0;		// Initial yeast cell count
 
         var     hop_flavour = 0;
         var     hop_aroma = 0;
@@ -195,6 +197,10 @@
 		var aboil_volume = parseFloat(dataRecord.batch_size);
 		if (dataRecord.brew_aboil_volume > 0)
 			aboil_volume = dataRecord.brew_aboil_volume / 1.04;	// volume @ 20 degrees
+		if (dataRecord.brew_fermenter_tcloss == 0) {
+			dataRecord.brew_fermenter_tcloss = dataRecord.eq_trub_chiller_loss;
+			$("#brew_fermenter_tcloss").val(dataRecord.brew_fermenter_tcloss);
+		}
 		dataRecord.brew_fermenter_volume = aboil_volume - dataRecord.brew_fermenter_tcloss + dataRecord.brew_fermenter_extrawater;
 		$("#brew_fermenter_volume").val(dataRecord.brew_fermenter_volume);
 		// Estimated needed sparge water corrected for the temperature.
@@ -253,12 +259,17 @@
 
 		// Calculate estimated svg.
 		svg = 0; // default.
+		initcells = 0;
 		var rows = $('#yeastGrid').jqxGrid('getrows');
 		for (var i = 0; i < rows.length; i++) {
 			var row = rows[i];
 			if (row.y_use == 0) {	// Primary
 				if (parseFloat(row.y_attenuation) > svg)
 					svg = parseFloat(row.y_attenuation);	// Take the highest if multiple yeasts.
+				if (row.y_form == 0)
+					initcells += (parseFloat(row.y_cells) / 1000000000) * parseFloat(row.y_amount);
+				else
+					initcells += (parseFloat(row.y_cells) / 1000000) * parseFloat(row.y_amount);
 			}
 			// TODO: brett in secondary ??
 		}
@@ -284,6 +295,8 @@
 			//console.log("real svg:"+svg);
 		}
 
+		$("#yeast_cells").val(initcells);
+		$("#need_cells").val(getNeededYeastCells());
 	};
 
 	/*
@@ -317,6 +330,25 @@
 		}
 	};
 
+	function getNeededYeastCells() {
+
+		var sg = dataRecord.brew_fermenter_sg;
+		if (sg <= 1.0001 && dataRecord.fg > 1.000)
+			sg = dataRecord.fg;
+		else if (sg <= 1.0001)
+			sg = dataRecord.est_og;
+		var plato = sg_to_plato(sg);
+
+		var volume = dataRecord.brew_fermenter_volume;
+		if (volume <= 0)
+			volume = dataRecord.batch_size - dataRecord.eq_trub_chiller_loss;
+
+		//console.log("getNeededYeastCells f:"+f+" volume:"+volume+" plato:"+plato+" sg:"+sg);
+		var result = pitchrate * volume * plato;
+		//console.log("getNeededYeastCells("+pitchrate+"): "+result+" billion cells");
+		return result;
+	}
+
 	function hopFlavourContribution(bt, vol, use, amount) {
 		var result;
 
@@ -389,6 +421,363 @@
 		calcStage();
 	};
 
+	/*
+	 * http://braukaiser.com/blog/blog/2012/11/03/estimating-yeast-growth/
+	 *
+	 * stype: 0=stirred, 1=shaken, 2=simple
+	 * totcells: initial cells
+	 * egrams: gram extract
+	 */
+	function getGrowthRate(stype, totcells, egrams){
+
+		/* Cells per grams extract (B/g) */
+		var cpe = totcells / egrams;
+
+		if (cpe > 3.5)
+			return 0;	// no growth
+		if (stype == 2)
+			return 0.4;	// simple starter
+		if (stype == 1)
+			return 0.62;	// shaken starter
+		if (cpe <= 1.4)		// stirred starter
+			return 1.4;
+		return 2.33 - (.67 * cpe );
+	};
+
+	function calcStep(svol, stype, start) {
+
+		var gperpoint = 2.72715;  //number of grams of extract per point of starter gravity per liter
+		var prate = start/svol * 1000;
+		var irate = Math.round(prate * 10) / 10;
+		var egrams = (dataRecord.starter_sg - 1) * svol * gperpoint;
+		var grate = getGrowthRate(stype, start, egrams);
+		var ncells = Math.round(egrams * grate);
+		var totcells = parseFloat(ncells) + start;
+		console.log("svol:"+svol+" start:"+start+" irate:"+irate+" egrams:"+egrams+" grate:"+grate+" ncells:"+ncells);
+		return {
+			svol: svol,
+			irate: irate,
+			prate: Math.round(prate * 10)/10,
+			ncells: ncells,
+			totcells: totcells,
+			growf: Math.round(ncells/start*100)/100
+		};
+	}
+
+	/*
+	 * Calculate all starter steps.
+	 * stype: final starter type: 0 = stirred, 1 = shaked, 2 = simple.
+	 * start: initial cells in billions
+	 * needed: needed cells in billions
+	 *
+	 * result: all values updated.
+	 */
+	function calcSteps(stype, start, needed) {
+
+		var uvols  = [ 20, 40, 60,  80, 100, 150, 200, 250, 375, 500,  625, 750, 875, 1000, 1250, 1500, 2000, 2500, 3000, 4000, 5000 ];
+		var mvols  = uvols.length;
+		var svol = 0;
+		var lasti = 0;
+		var result = {};
+
+		/*
+		 * If no values are set, auto calculate the starter.
+		 */
+		if ((parseFloat($("#prop1_volume").jqxNumberInput('decimal')) + parseFloat($("#prop2_volume").jqxNumberInput('decimal')) +
+		     parseFloat($("#prop3_volume").jqxNumberInput('decimal')) + parseFloat($("#prop4_volume").jqxNumberInput('decimal'))) == 0) {
+			// clear by default
+			for (var i = 1; i < 5; i++) {
+				$("#prop"+i+"_type").hide();
+				$("#r"+i+"_pmpt").show();
+				$("#prop"+i+"_type").val(stype);
+				$("#prop"+i+"_volume").hide();
+				$("#prop"+i+"_volume").val(0);
+				$("#prop"+i+"_irate").hide();
+				$("#prop"+i+"_ncells").hide();
+				$("#prop"+i+"_tcells").hide();
+				$("#prop"+i+"_growf").hide();
+			}
+			if (start > needed) {
+				return;	// no starter needed
+			}
+			$("#prop1_type").show();
+			$("#r1_pmpt").hide();
+			$("#prop1_volume").show();
+			$("#prop1_irate").show();
+			$("#prop1_ncells").show();
+			$("#prop1_tcells").show();
+			$("#prop1_growf").show();
+			for (var i = lasti; i <= mvols; i++) {
+				lasti = i;
+				svol = uvols[lasti];
+				result = calcStep(svol, stype, start);
+				if (result.irate < 25) {
+					// inocculation rate too low, backup one step and break out.
+					lasti = i - 1;
+					svol = uvols[lasti];
+					result = calcStep(svol, stype, start);
+					break;
+				}
+				if (result.totcells > needed || i == mvols) { // hit the target or loops done
+					break;
+				}
+			}
+			$("#prop1_volume").val(result.svol / 1000); // to liters
+			$("#prop1_irate").val(result.prate);
+			$("#prop1_ncells").val(result.ncells);
+			$("#prop1_tcells").val(result.totcells);
+			$("#prop1_growf").val(result.growf);
+			if (result.totcells > needed)
+				return; // hit the target
+
+			// second stage
+			$("#r2_pmpt").hide();
+			$("#prop2_type").val(stype);
+			$("#prop2_type").show();
+			$("#prop2_volume").show();
+			$("#prop2_irate").show();
+			$("#prop2_ncells").show();
+			$("#prop2_tcells").show();
+			$("#prop2_growf").show();
+			for (var i = lasti; i <= mvols; i++) {
+				lasti = i;
+				svol = uvols[lasti];
+				result = calcStep(svol, stype, $("#prop1_tcells").val());
+				if (result.irate < 25) {
+					lasti = i - 1;
+					svol = uvols[lasti];
+					result = calcStep(svol, stype, $("#prop1_tcells").val());
+					break;
+				}
+				if (result.totcells > needed || i == mvols) { // hit the target or loops done
+					break;
+				}
+			}
+			$("#prop2_volume").val(result.svol / 1000); // to liters
+			$("#prop2_irate").val(result.prate);
+			$("#prop2_ncells").val(result.ncells);
+			$("#prop2_tcells").val(result.totcells);
+			$("#prop2_growf").val(result.growf);
+			if (result.totcells > needed)
+				return; // hit the target
+
+			// third stage
+			$("#r3_pmpt").hide();
+			$("#prop3_type").val(stype);
+			$("#prop3_type").show();
+			$("#prop3_volume").show();
+			$("#prop3_irate").show();
+			$("#prop3_ncells").show();
+			$("#prop3_tcells").show();
+			$("#prop3_growf").show();
+			for (var i = lasti; i <= mvols; i++) {
+				lasti = i;
+				svol = uvols[lasti];
+				result = calcStep(svol, stype, $("#prop2_tcells").val());
+				if (result.irate < 25) {
+					lasti = i - 1;
+					svol = uvols[lasti];
+					result = calcStep(svol, stype, $("#prop2_tcells").val());
+					break;
+				}
+				if (result.totcells > needed || i == mvols) { // hit the target or loops done
+					break;
+				}
+			}
+			$("#prop3_volume").val(result.svol / 1000); // to liters
+			$("#prop3_irate").val(result.prate);
+			$("#prop3_ncells").val(result.ncells);
+			$("#prop3_tcells").val(result.totcells);
+			$("#prop3_growf").val(result.growf);
+			if (result.totcells > needed)
+				return; // hit the target
+
+			// fourth stage
+			$("#r4_pmpt").hide();
+			$("#prop4_type").val(stype);
+			$("#prop4_type").show();
+			$("#prop4_volume").show();
+			$("#prop4_irate").show();
+			$("#prop4_ncells").show();
+			$("#prop4_tcells").show();
+			$("#prop4_growf").show();
+			for (var i = lasti; i <= mvols; i++) {
+				lasti = i;
+				svol = uvols[lasti];
+				result = calcStep(svol, stype, $("#prop3_tcells").val());
+				if (result.totcells > needed || i == mvols) { // hit the target or loops done
+					$("#prop4_volume").val(result.svol / 1000); // to liters
+					$("#prop4_irate").val(result.prate);
+					$("#prop4_ncells").val(result.ncells);
+					$("#prop4_tcells").val(result.totcells);
+					$("#prop4_growf").val(result.growf);
+					return;
+				}
+			}
+		} else {
+			// recalculate
+			if (dataRecord.prop1_volume > 0) {
+				$("#r1_pmpt").hide();
+				$("#prop1_type").show();
+				$("#prop1_volume").show();
+				$("#prop1_irate").show();
+				$("#prop1_ncells").show();
+				$("#prop1_tcells").show();
+				$("#prop1_growf").show();
+				result = calcStep($("#prop1_volume").val() * 1000, dataRecord.prop1_type, start);
+				$("#prop1_irate").val(result.prate);
+				$("#prop1_ncells").val(result.ncells);
+				$("#prop1_tcells").val(result.totcells);
+				$("#prop1_growf").val(result.growf);
+			}
+			if (dataRecord.prop2_volume > 0) {
+				$("#r2_pmpt").hide();
+				$("#prop2_type").show();
+				$("#prop2_volume").show();
+				$("#prop2_irate").show();
+				$("#prop2_ncells").show();
+				$("#prop2_tcells").show();
+				$("#prop2_growf").show();
+				result = calcStep($("#prop2_volume").val() * 1000, dataRecord.prop2_type, $("#prop1_tcells").val());
+				$("#prop2_irate").val(result.prate);
+				$("#prop2_ncells").val(result.ncells);
+				$("#prop2_tcells").val(result.totcells);
+				$("#prop2_growf").val(result.growf);
+			}
+			if (dataRecord.prop3_volume > 0) {
+				$("#r3_pmpt").hide();
+				$("#prop3_type").show();
+				$("#prop3_volume").show();
+				$("#prop3_irate").show();
+				$("#prop3_ncells").show();
+				$("#prop3_tcells").show();
+				$("#prop3_growf").show();
+				result = calcStep($("#prop3_volume").val() * 1000, dataRecord.prop3_type, $("#prop2_tcells").val());
+				$("#prop3_irate").val(result.prate);
+				$("#prop3_ncells").val(result.ncells);
+				$("#prop3_tcells").val(result.totcells);
+				$("#prop3_growf").val(result.growf);
+			}
+			if (dataRecord.prop4_volume > 0) {
+				$("#r4_pmpt").hide();
+				$("#prop4_type").show();
+				$("#prop4_volume").show();
+				$("#prop4_irate").show();
+				$("#prop4_ncells").show();
+				$("#prop4_tcells").show();
+				$("#prop4_growf").show();
+				result = calcStep($("#prop4_volume").val() * 1000, dataRecord.prop4_type, $("#prop3_tcells").val());
+				$("#prop4_irate").val(result.prate);
+				$("#prop4_ncells").val(result.ncells);
+				$("#prop4_tcells").val(result.totcells);
+				$("#prop4_growf").val(result.growf);
+			}
+
+		}
+	}
+
+	function calcYeast() {
+
+		// Also in calcFermentables()
+		$("#yeast_cells").val(initcells);
+
+		if (!(rows = $('#yeastGrid').jqxGrid('getrows'))) {
+			return; // grid not yet loaded.
+		}
+		var rowscount = $("#yeastGrid").jqxGrid('getdatainformation').rowscount;
+		if (rowscount == 0)
+			return;	// no yeast in recipe
+
+		for (var i = 0; i < rowscount; i++) {
+			var row = $("#yeastGrid").jqxGrid('getrowdata', i);
+			if (row.y_use == 0) { // primary
+				// pitchrate see https://www.brewersfriend.com/yeast-pitch-rate-and-starter-calculator/
+				// and http://braukaiser.com/blog/blog/2012/11/03/estimating-yeast-growth/
+				pitchrate = 0.75;
+				if (dataRecord.est_og > 1.060)
+					pitchrate = 1.0;
+				if (row.y_type == 0) // lager yeast
+					pitchrate *= 2;
+
+				if (row.y_form == 1) { // dry yeast
+				} else { // possible starter needed
+				}
+			}
+		}
+		var needed = getNeededYeastCells();
+		console.log("calcYeast() pitchrate:"+pitchrate+" start:"+initcells+" needed:"+needed);
+		calcSteps(dataRecord.starter_type, initcells, needed);
+		//console.log("calcYeast() pitchrate:"+pitchrate+" needed:"+needed);
+		$("#need_cells").val(needed);
+
+		$("#r1_irate").html("");
+		$("#r1_growf").html("");
+		$("#r1_tcells").html("");
+		if (parseFloat($("#prop1_volume").val()) > 0) {
+			if ((parseFloat($("#prop1_irate").val()) < 25) || (parseFloat($("#prop1_irate").val()) > 100)) {
+				$("#r1_irate").html("<img src='images/dialog-error.png'>");
+			} else {
+				$("#r1_irate").html("<img src='images/dialog-ok-apply.png'>");
+			}
+			if (parseFloat($("#prop1_growf").val()) < 1)
+				$("#r1_growf").html("<img src='images/dialog-error.png'>");
+			if (($("#prop1_type").val() > 0) && (parseFloat($("#prop1_growf").val()) > 3))
+				$("#r1_growf").html("<img src='images/dialog-error.png'>");
+			if (parseFloat($("#prop1_tcells").val()) > needed)
+				$("#r1_tcells").html("<img src='images/dialog-ok-apply.png'>");
+		}
+		$("#r2_irate").html("");
+		$("#r2_growf").html("");
+		$("#r2_tcells").html("");
+		if (parseFloat($("#prop2_volume").val()) > 0) {
+			if ((parseFloat($("#prop2_irate").val()) < 25) || (parseFloat($("#prop2_irate").val()) > 100)) {
+				$("#r2_irate").html("<img src='images/dialog-error.png'>");
+			} else {
+				$("#r2_irate").html("<img src='images/dialog-ok-apply.png'>");
+			}
+			if (parseFloat($("#prop2_growf").val()) < 1)
+				$("#r2_growf").html("<img src='images/dialog-error.png'>");
+			if (($("#prop2_type").val() > 0) && (parseFloat($("#prop2_growf").val()) > 3))
+				$("#r2_growf").html("<img src='images/dialog-error.png'>");
+			if (parseFloat($("#prop2_tcells").val()) > needed)
+				$("#r2_tcells").html("<img src='images/dialog-ok-apply.png'>");
+		}
+		$("#r3_irate").html("");
+		$("#r3_growf").html("");
+		$("#r3_tcells").html("");
+		if (parseFloat($("#prop3_volume").val()) > 0) {
+			if ((parseFloat($("#prop3_irate").val()) < 25) || (parseFloat($("#prop3_irate").val()) > 100)) {
+				$("#r3_irate").html("<img src='images/dialog-error.png'>");
+			} else {
+				$("#r3_irate").html("<img src='images/dialog-ok-apply.png'>");
+			}
+			if (parseFloat($("#prop3_growf").val()) < 1)
+				$("#r3_growf").html("<img src='images/dialog-error.png'>");
+			if (($("#prop3_type").val() > 0) && (parseFloat($("#prop3_growf").val()) > 3))
+				$("#r3_growf").html("<img src='images/dialog-error.png'>");
+			if (parseFloat($("#prop3_tcells").val()) > needed)
+				$("#r3_tcells").html("<img src='images/dialog-ok-apply.png'>");
+		}
+		$("#r4_irate").html("");
+		$("#r4_growf").html("");
+		$("#r4_tcells").html("");
+		if (parseFloat($("#prop4_volume").val()) > 0) {
+			if ((parseFloat($("#prop4_irate").val()) < 25) || (parseFloat($("#prop4_irate").val()) > 100)) {
+				$("#r4_irate").html("<img src='images/dialog-error.png'>");
+			} else {
+				$("#r4_irate").html("<img src='images/dialog-ok-apply.png'>");
+			}
+			if (parseFloat($("#prop4_growf").val()) < 1)
+				$("#r4_growf").html("<img src='images/dialog-error.png'>");
+			if (($("#prop4_type").val() > 0) && (parseFloat($("#prop4_growf").val()) > 3))
+				$("#r4_growf").html("<img src='images/dialog-error.png'>");
+			if (parseFloat($("#prop4_tcells").val()) > needed)
+				$("#r4_tcells").html("<img src='images/dialog-ok-apply.png'>");
+		} else {
+			$("#r4_irate").html("");
+		}
+	};
+
 	function adjustHops(factor) {
 
 		console.log("adjustHops("+factor+")");
@@ -454,7 +843,8 @@
 				$("#yeastGrid").jqxGrid('setcellvalue', i, "y_amount", amount);
 			}
 		}
-		// TODO: adjust starter size
+
+		calcYeast();
 	};
 
 	function adjustWaters(factor) {
@@ -1240,7 +1630,7 @@
 			return;
 		if ((dataRecord.primary_end_sg > 0.990) && (dataRecord.primary_end_sg < dataRecord.brew_fermenter_sg)) {
 			var primary_svg = 100 * (dataRecord.brew_fermenter_sg - dataRecord.primary_end_sg) / (dataRecord.brew_fermenter_sg - 1);
-			console.log("primary svg: "+primary_svg);
+			//console.log("primary svg: "+primary_svg);
 			$("#primary_svg").val(primary_svg);
 			if ((dataRecord.fg > 0.990) && (dataRecord.fg < dataRecord.brew_fermenter_sg)) {
 				var final_svg = 100 * (dataRecord.brew_fermenter_sg - dataRecord.fg) / (dataRecord.brew_fermenter_sg - 1);
@@ -1442,6 +1832,94 @@
 		calcEfficiencyBeforeBoil();
 		calcEfficiencyAfterBoil();
 
+		$("#starter_enable").on('checked', function (event) {
+			dataRecord.starter_enable = 1;
+			$("#propagator").show();
+			$("#starter_type").jqxDropDownList( {disabled: false });
+			$("#starter_try").jqxButton({ disabled: false });
+			$("#starter_sg").jqxNumberInput({ spinButtons: true, readOnly: false, width: 110 });
+			calcYeast();
+		});
+		$("#starter_enable").on('unchecked', function (event) {
+			dataRecord.starter_enable = 0;
+			$("#propagator").hide();
+			$("#starter_type").jqxDropDownList( {disabled: true });
+			$("#starter_try").jqxButton({ disabled: true });
+			$("#starter_sg").jqxNumberInput({ spinButtons: false, readOnly: true, width: 90 });
+		});
+		$("#starter_try").click(function () {
+			$("#prop1_volume").val(0);
+			$("#prop2_volume").val(0);
+			$("#prop3_volume").val(0);
+			$("#prop4_volume").val(0);
+			calcYeast();
+		});
+		$('#starter_type').on('change', function (event) {
+			if (event.args) {
+				var index = event.args.index;
+				dataRecord.starter_type = index;
+				calcYeast();
+			}
+		});
+		$('#starter_sg').on('change', function (event) {
+			if (event.args) {
+				dataRecord.starter_sg = event.args.value;
+				calcYeast();
+			}
+		});
+		$('#prop1_type').on('change', function (event) {
+			if (event.args) {
+				var index = event.args.index;
+				dataRecord.prop1_type = index;
+				calcYeast();
+			}
+		});
+		$('#prop1_volume').on('change', function (event) {
+			if (event.args) {
+				dataRecord.prop1_volume = event.args.value;
+				calcYeast();
+			}
+		});
+		$('#prop2_type').on('change', function (event) {
+			if (event.args) {
+				var index = event.args.index;
+				dataRecord.prop2_type = index;
+				calcYeast();
+			}
+		});
+		$('#prop2_volume').on('change', function (event) {
+			if (event.args) {
+				dataRecord.prop2_volume = event.args.value;
+				calcYeast();
+			}
+		});
+		$('#prop3_type').on('change', function (event) {
+			if (event.args) {
+				var index = event.args.index;
+				dataRecord.prop3_type = index;
+				calcYeast();
+			}
+		});
+		$('#prop3_volume').on('change', function (event) {
+			if (event.args) {
+				dataRecord.prop3_volume = event.args.value;
+				calcYeast();
+			}
+		});
+		$('#prop4_type').on('change', function (event) {
+			if (event.args) {
+				var index = event.args.index;
+				dataRecord.prop4_type = index;
+				calcYeast();
+			}
+		});
+		$('#prop4_volume').on('change', function (event) {
+			if (event.args) {
+				dataRecord.prop4_volume = event.args.value;
+				calcYeast();
+			}
+		});
+
 		$("#calc_acid").on('checked', function (event) {
 			dataRecord.calc_acid = 1;
 			calcWater();
@@ -1591,6 +2069,7 @@
 			calcFermentablesFromOG(event.args.value);       // Adjust fermentables amounts
 			calcFermentables();                             // Update the recipe details
 			calcIBUs();                                     // and the IBU's.
+			calcYeast();
 		});
 		$('#mash_ph').on('change', function (event) {
 			dataRecord.mash_ph = parseFloat(event.args.value);
@@ -1668,11 +2147,13 @@
 			dataRecord.brew_fermenter_extrawater = parseFloat(event.args.value);
 			calcFermentables();
 			calcIBUs();
+			calcYeast();
 		});
 		$("#brew_fermenter_tcloss").on('change', function (event) {
 			dataRecord.brew_fermenter_tcloss = parseFloat(event.args.value);
 			calcFermentables();
 			calcIBUs();
+			calcYeast();
 		});
 
 		$("#BLog").jqxButton({ disabled: (dataRecord.log_brew) ? false:true });
@@ -1962,6 +2443,17 @@
 			wa_acid_name: $("#wa_acid_name").val(),
 			wa_acid_perc: parseFloat($("#wa_acid_perc").jqxNumberInput('decimal')),
 			wa_base_name: $("#wa_base_name").val(),
+			starter_enable: dataRecord.starter_enable,
+			starter_type: $("#starter_type").val(),
+			starter_sg: parseFloat($("#starter_sg").jqxNumberInput('decimal')),
+			prop1_type: $("#prop1_type").val(),
+			prop1_volume: parseFloat($("#prop1_volume").jqxNumberInput('decimal')),
+			prop2_type: $("#prop2_type").val(),
+			prop2_volume: parseFloat($("#prop2_volume").jqxNumberInput('decimal')),
+			prop3_type: $("#prop3_type").val(),
+			prop3_volume: parseFloat($("#prop3_volume").jqxNumberInput('decimal')),
+			prop4_type: $("#prop4_type").val(),
+			prop4_volume: parseFloat($("#prop4_volume").jqxNumberInput('decimal')),
 			fermentables: fermentablerow,
 			hops: hoprow,
 			miscs: miscrow,
@@ -2153,6 +2645,17 @@
 			{ name: 'wa_acid_name', type: 'int' },
 			{ name: 'wa_acid_perc', type: 'int' },
 			{ name: 'wa_base_name', type: 'int' },
+			{ name: 'starter_enable', type: 'int' },
+			{ name: 'starter_type', type: 'int' },
+			{ name: 'starter_sg', type: 'float' },
+			{ name: 'prop1_type', type: 'int' },
+			{ name: 'prop1_volume', type: 'float' },
+			{ name: 'prop2_type', type: 'int' },
+			{ name: 'prop2_volume', type: 'float' },
+			{ name: 'prop3_type', type: 'int' },
+			{ name: 'prop3_volume', type: 'float' },
+			{ name: 'prop4_type', type: 'int' },
+			{ name: 'prop4_volume', type: 'float' },
 			{ name: 'fermentables', type: 'array' },
 			{ name: 'hops', type: 'array' },
 			{ name: 'miscs', type: 'array' },
@@ -2343,6 +2846,17 @@
 			$("#wa_acid_name").val(dataRecord.wa_acid_name);
 			$("#wa_acid_perc").val(dataRecord.wa_acid_perc);
 			$("#wa_base_name").val(dataRecord.wa_base_name);
+			$("#starter_enable").val(dataRecord.starter_enable);
+			$("#starter_type").val(dataRecord.starter_type);
+			$("#starter_sg").val(dataRecord.starter_sg);
+			$("#prop1_type").val(dataRecord.prop1_type);
+			$("#prop1_volume").val(dataRecord.prop1_volume);
+			$("#prop2_type").val(dataRecord.prop2_type);
+			$("#prop2_volume").val(dataRecord.prop2_volume);
+			$("#prop3_type").val(dataRecord.prop3_type);
+			$("#prop3_volume").val(dataRecord.prop3_volume);
+			$("#prop4_type").val(dataRecord.prop4_type);
+			$("#prop4_volume").val(dataRecord.prop4_volume);
 			editFermentable(dataRecord);
 			editHop(dataRecord);
                         editMisc(dataRecord);
@@ -3027,7 +3541,7 @@
                 var yeastAdapter = new $.jqx.dataAdapter(yeastSource);
                 $("#yeastGrid").jqxGrid({
                         width: 1240,
-                        height: 400,
+                        height: 350,
                         source: yeastAdapter,
                         theme: theme,
                         selectionmode: 'singlerow',
@@ -3079,6 +3593,7 @@
 						row["y_inventory"] = datarecord.inventory;
                                                 var commit = $("#yeastGrid").jqxGrid('addrow', null, row);
                                         }
+					calcYeast();
 					$("#yaddrowbutton").jqxDropDownList('clearSelection');
                                 });
                                 $("#yinstockbutton").jqxCheckBox({ theme: theme, height: 27, disabled: (dataRecord.stage > 3) });
@@ -3094,11 +3609,13 @@
                                         if (selectedrowindex >= 0 && selectedrowindex < rowscount) {
                                                 var id = $("#yeastGrid").jqxGrid('getrowid', selectedrowindex);
                                                 var commit = $("#yeastGrid").jqxGrid('deleterow', id);
+						calcYeast();
                                         }
                                 });
                         },
                         ready: function() {
 				calcFermentables();
+				calcYeast();
                                 $('#jqxTabs').jqxTabs('next');
                         },
                         columns: [
@@ -3972,6 +4489,9 @@
 	$("#est_fg2").jqxNumberInput( Show3dec );
 	$("#est_abv2").jqxTooltip({ content: 'Alcohol volume %. Dit wordt automatisch berekend.' });
 	$("#est_abv2").jqxNumberInput({ inputMode: 'simple', theme: theme, width: 50, height: 23, decimalDigits: 1, readOnly: true });
+	$("#yeast_cells").jqxNumberInput( Show1dec );
+	$("#need_cells").jqxNumberInput( Show1dec );
+	$("#plato_cells").jqxNumberInput( Show1dec );
 	$("#popupYeast").jqxWindow({
 		width: 800,
 		height: 300,
@@ -3986,6 +4506,7 @@
 	$("#YeastReady").jqxButton({ template: "success", width: '90px', theme: theme });
 	$("#YeastReady").click(function () {
 		calcFermentables();
+		calcYeast();
 		$("#yeastGrid").jqxGrid('sortby', 'y_use', 'asc');
 	});
 	$("#wy_name").jqxInput({ theme: theme, width: 320, height: 23 });
@@ -4038,6 +4559,7 @@
 				$("#wy_pmpt_amount").html("Volume ml:");
 			}
 			calcFermentables();
+			calcYeast();
 		}
 	});
 	$("#wy_amount").jqxNumberInput( Spin1dec );
@@ -4050,6 +4572,7 @@
 			var amount = parseFloat(event.args.value) / 1000;
 		rowdata.y_amount = amount;
 		calcFermentables();
+		calcYeast();
 	});
 	$("#wy_use").jqxDropDownList({
 		theme: theme,
@@ -4067,8 +4590,43 @@
 			var rowdata = $("#yeastGrid").jqxGrid('getrowdata', yeastRow);
 			rowdata.y_use = index;
 			calcFermentables();
+			calcYeast();
 		}
 	});
+	for (var i = 1; i < 5; i++) {
+		$("#prop"+i+"_type").jqxDropDownList({
+			theme: theme,
+			source: StarterTypeAdapter,
+			valueMember: 'id',
+			displayMember: 'nl',
+			width: 120,
+			height: 23,
+			autoDropDownHeight: true
+		});
+		$("#prop"+i+"_type").hide();
+		$("#prop"+i+"_volume").jqxNumberInput( Spin3dec );
+		$("#prop"+i+"_volume").hide();
+		$("#prop"+i+"_irate").jqxNumberInput( Show1dec );
+		$("#prop"+i+"_irate").hide();
+		$("#prop"+i+"_ncells").jqxNumberInput( Show1dec );
+		$("#prop"+i+"_ncells").hide();
+		$("#prop"+i+"_tcells").jqxNumberInput( Show1dec );
+		$("#prop"+i+"_tcells").hide();
+		$("#prop"+i+"_growf").jqxNumberInput( Show2dec );
+		$("#prop"+i+"_growf").hide();
+	}
+	$("#starter_enable").jqxCheckBox({ theme: theme, height: 23 });
+	$("#starter_type").jqxDropDownList({
+		theme: theme,
+		source: StarterTypeAdapter,
+		valueMember: 'id',
+		displayMember: 'nl',
+		width: 120,
+		height: 23,
+		autoDropDownHeight: true
+	});
+	$("#starter_sg").jqxNumberInput( SGopts );
+	$("#starter_try").jqxButton({ template: "primary", width: '100px', height: 23, theme: theme });
 
 	// Tab 7, Mashing
 	$("#mash_name").jqxTooltip({ content: 'De omschrijving van dit maisch profiel.' });
--- a/www/prod_edit.php	Thu Feb 14 16:28:37 2019 +0100
+++ b/www/prod_edit.php	Mon Feb 18 22:07:12 2019 +0100
@@ -247,12 +247,89 @@
       <table style="width: 100%;">
        <tr>
         <td style="vertical-align: top; float: right; padding: 3px;">Verwacht eind SG:</td>
-        <td style="padding: 3px;"><div id="est_fg2"></div></td>
+	<td style="padding: 3px;"><div id="est_fg2"></div></td>
+	<td align="center" colspan="5" rowspan="5">
+         <div id="propagator">
+          <table style="width: 100%;">
+	   <caption>Giststarter stappen</caption>
+	   <tr>
+            <td style="width: 40px; padding: 3px;">Stap</td>
+            <td align="left" style="vertical-align: top; padding: 3px;">Methode</td>
+            <td style="width: 120px; padding: 3px;">Volume</td>
+            <td style="width: 120px; padding: 3px;">Inj. factor</td>
+            <td style="width: 120px; padding: 3px;">Nieuwe cellen</td>
+            <td style="width: 120px; padding: 3px;">Totaal cellen</td>
+            <td style="width: 120px; padding: 3px;">Groei factor</td>
+	   </tr>
+           <tr>
+            <td align="center">1</td>
+            <td><div id="prop1_type"></div><div id="r1_pmpt">Niet nodig</div></td>
+            <td><div id="prop1_volume"></div></td>
+            <td><div style="float: left;" id="prop1_irate"></div><div style="float: left;" id="r1_irate"></div></td>
+            <td><div id="prop1_ncells"></div></td>
+            <td><div style="float: left;" id="prop1_tcells"></div><div style="float: left;" id="r1_tcells"></div></td>
+            <td><div style="float: left;" id="prop1_growf"></div><div style="float: left;" id="r1_growf"></div></td>
+	   </tr>
+           <tr>
+            <td align="center">2</td>
+            <td><div id="prop2_type"></div><div id="r2_pmpt">Niet nodig</div></td>
+            <td><div id="prop2_volume"></div></td>
+            <td><div style="float: left;" id="prop2_irate"></div><div style="float: left;" id="r2_irate"></div></td>
+            <td><div id="prop2_ncells"></div></td>
+            <td><div style="float: left;" id="prop2_tcells"></div><div style="float: left;" id="r2_tcells"></div></td>
+            <td><div style="float: left;" id="prop2_growf"></div><div style="float: left;" id="r2_growf"></div></td>
+           </tr>
+           <tr>
+            <td align="center">3</td>
+            <td><div id="prop3_type"></div><div id="r3_pmpt">Niet nodig</div></td>
+            <td><div id="prop3_volume"></div></td>
+            <td><div style="float: left;" id="prop3_irate"></div><div style="float: left;" id="r3_irate"></div></td>
+            <td><div id="prop3_ncells"></div></td>
+            <td><div style="float: left;" id="prop3_tcells"></div><div style="float: left;" id="r3_tcells"></div></td>
+            <td><div style="float: left;" id="prop3_growf"></div><div style="float: left;" id="r3_growf"></div></td>
+           </tr>
+           <tr>
+	    <td align="center">4</td>
+            <div id="prop4_row">
+            <td><div id="prop4_type"></div><div id="r4_pmpt">Niet nodig</div></td>
+            <td><div id="prop4_volume"></div></td>
+            <td><div style="float: left;" id="prop4_irate"></div><div style="float: left;" id="r4_irate"></div></td>
+            <td><div id="prop4_ncells"></div></td>
+            <td><div style="float: left;" id="prop4_tcells"></div><div style="float: left;" id="r4_tcells"></div></td>
+	    <td><div style="float: left;" id="prop4_growf"></div><div style="float: left;" id="r4_growf"></div></td>
+            </div>
+           </tr>
+ 	  </table>
+         </div>
+        </td>
+       </tr>
+       <tr>
         <td style="vertical-align: top; float: right; padding: 3px;">Verwacht ABV %:</td>
-        <td style="padding: 3px;"><div id="est_abv2"></div></td>
+	<td style="padding: 3px;"><div id="est_abv2"></div></td>
+       </tr>
+       <tr>
+        <td style="vertical-align: top; float: right; padding: 3px;">Aantal cellen miljard:</td>
+        <td style="padding: 3px;"><div id="yeast_cells"></div></td>
+       </tr>
+       <tr>
+        <td style="vertical-align: top; float: right; padding: 3px;">Cellen nodig miljard:</td>
+        <td style="padding: 3px;"><div id="need_cells"></div></td>
        </tr>
-       <tr><td colspan="4">&nbsp;</td></tr>
-       <tr><td align="center" colspan="4"><div id="yeastGrid"></div></td></tr>
+       <tr>
+        <td style="vertical-align: top; float: right; padding: 3px;">Cellen/ml &deg;P:</td>
+        <td style="padding: 3px;"><div id="plato_cells"></div></td>
+       </tr>
+       <tr>
+        <td style="vertical-align: top; float: right; padding: 3px;">Maak een starter:</td>
+	<td style="padding: 3px;"><div id="starter_enable"></div></td>
+        <td style="vertical-align: top; float: right; padding: 3px;">Starter type:</td>
+	<td style="padding: 3px;"><div id="starter_type"></div></td>
+	<td style="vertical-align: top; float: right; padding: 3px;">Starter SG:</td>
+        <td style="padding: 3px;"><div id="starter_sg"></div></td>
+        <td style="vertical-align: top; padding: 3px;"><input type="button" id="starter_try"  value="Probeer" /></td>
+       </tr>
+       <tr><td colspan="7">&nbsp;</td></tr>
+       <tr><td align="center" colspan="7"><div id="yeastGrid"></div></td></tr>
       </table>
      </div>
     </div> <!-- tab gisten -->

mercurial