The framework to calculate yeast starters added.

Thu, 05 May 2022 17:20:06 +0200

author
Michiel Broek <mbroek@mbse.eu>
date
Thu, 05 May 2022 17:20:06 +0200
changeset 195
9887278c4fbe
parent 194
ea8cce5e7eb9
child 196
f7954f2d4451

The framework to calculate yeast starters added.

src/EditProduct.cpp file | annotate | diff | comparison | revisions
src/EditProduct.h file | annotate | diff | comparison | revisions
src/EditProductTab6.cpp file | annotate | diff | comparison | revisions
src/Utils.cpp file | annotate | diff | comparison | revisions
src/global.h file | annotate | diff | comparison | revisions
ui/EditProduct.ui file | annotate | diff | comparison | revisions
--- a/src/EditProduct.cpp	Wed May 04 13:49:37 2022 +0200
+++ b/src/EditProduct.cpp	Thu May 05 17:20:06 2022 +0200
@@ -807,9 +807,7 @@
     ui->est_ibu2Edit->setValue(product->est_ibu);
 
     // Tab yeasts.
-    ui->est_og4Edit->setValue(product->est_og);
-    ui->est_fg3Edit->setValue(product->est_fg);
-    ui->est_abv2Edit->setValue(product->est_abv);
+    initYeast();
 
     // Tab mashs.
     ui->mash_nameEdit->setText(product->mash_name);
@@ -1045,8 +1043,9 @@
     connect(ui->addMisc, SIGNAL(clicked()), this, SLOT(addMiscRow_clicked()));
 
     // All signals from tab "Yeasts"
-    ui->yeastsTable->setEditTriggers(QAbstractItemView::NoEditTriggers);
     connect(ui->addYeast, SIGNAL(clicked()), this, SLOT(addYeastRow_clicked()));
+    connect(ui->stmethodEdit, QOverload<int>::of(&QComboBox::currentIndexChanged), this, &EditProduct::yeast_method_changed);
+    connect(ui->startersgEdit, QOverload<double>::of(&QDoubleSpinBox::valueChanged), this, &EditProduct::yeast_starter_sg_changed);
 
     // All signals from tab "Mash"
     ui->mashsTable->setEditTriggers(QAbstractItemView::NoEditTriggers);
--- a/src/EditProduct.h	Wed May 04 13:49:37 2022 +0200
+++ b/src/EditProduct.h	Thu May 05 17:20:06 2022 +0200
@@ -15,6 +15,14 @@
 
 #include "global.h"
 
+struct StepResult {
+    double	svol;
+    double	irate;
+    double	ncells;
+    double	totcells;
+    double	growf;
+};
+
 namespace Ui {
 class EditProduct;
 }
@@ -72,6 +80,11 @@
     void misc_select_changed(int val);
     void misc_instock_changed(bool val);
     void misc_useat_changed(int val);
+    void yeast_prod_date_clicked();
+    void yeast_method_changed(int val);
+    void yeast_starter_sg_changed(double val);
+    void yeast_starter_edit_clicked();
+    void yeast_pitchrate_button_clicked();
     void yeast_amount_changed(double val);
     void yeast_select_changed(int val);
     void yeast_instock_changed(bool val);
@@ -181,6 +194,10 @@
     void calcSparge();
     double GetBUGU();
     double GetOptSO4Clratio();
+    double getGrowthRate(int stype, double totcells, double egrams);
+    StepResult calcStep(double svol, int stype, double start);
+    void calcSteps(int stype, double start, double needed);
+    void initYeast();
     void calcYeast();
     void adjustYeasts(double factor);
     double infusionVol(double step_infused, double step_mashkg, double infuse_temp, double step_temp, double last_temp);
--- a/src/EditProductTab6.cpp	Wed May 04 13:49:37 2022 +0200
+++ b/src/EditProductTab6.cpp	Thu May 05 17:20:06 2022 +0200
@@ -89,22 +89,24 @@
         item->setTextAlignment(Qt::AlignRight|Qt::AlignVCenter);
         ui->yeastsTable->setItem(i, 8, item);
 
-	if (product->yeasts.at(i).y_form == 0)
+	if (product->yeasts.at(i).y_form == YEAST_FORMS_LIQUID)
             item = new QTableWidgetItem(QString("%1 pack").arg(product->yeasts.at(i).y_amount, 1, 'f', 0, '0'));
-	else if (product->yeasts.at(i).y_form == 1)
+	else if (product->yeasts.at(i).y_form == YEAST_FORMS_DRY || product->yeasts.at(i).y_form == YEAST_FORMS_DRIED)
 	    item = new QTableWidgetItem(QString("%1 gr").arg(product->yeasts.at(i).y_amount * 1000.0, 3, 'f', 2, '0'));
 	else
 	    item = new QTableWidgetItem(QString("%1 ml").arg(product->yeasts.at(i).y_amount * 1000.0, 3, 'f', 2, '0'));
         item->setTextAlignment(Qt::AlignRight|Qt::AlignVCenter);
         ui->yeastsTable->setItem(i, 9, item);
 
-	if (product->yeasts.at(i).y_form == 0)
+	if (product->yeasts.at(i).y_form == YEAST_FORMS_LIQUID)
             item = new QTableWidgetItem(QString("%1 pack").arg(product->yeasts.at(i).y_inventory, 1, 'f', 0, '0'));
-        else if (product->yeasts.at(i).y_form == 1)
+        else if (product->yeasts.at(i).y_form == YEAST_FORMS_DRY || product->yeasts.at(i).y_form == YEAST_FORMS_DRIED)
             item = new QTableWidgetItem(QString("%1 gr").arg(product->yeasts.at(i).y_inventory * 1000.0, 3, 'f', 2, '0'));
         else
             item = new QTableWidgetItem(QString("%1 ml").arg(product->yeasts.at(i).y_inventory * 1000.0, 3, 'f', 2, '0'));
         item->setTextAlignment(Qt::AlignRight|Qt::AlignVCenter);
+	if (product->yeasts.at(i).y_inventory < product->yeasts.at(i).y_amount)
+	    item->setForeground(QBrush(QColor(Qt::red)));
         ui->yeastsTable->setItem(i, 10, item);
 
 	pWidget = new QWidget();
@@ -132,27 +134,62 @@
 }
 
 
+void EditProduct::initYeast()
+{
+    ui->est_og4Edit->setValue(product->est_og);
+    ui->est_fg3Edit->setValue(product->est_fg);
+    ui->est_abv2Edit->setValue(product->est_abv);
+    ui->productionEdit->setText(product->yeast_prod_date.toString("dd MMM yyyy"));
+    ui->conditionShow->setValue(product->starter_viability);
+    ui->startersgEdit->setValue(product->starter_sg);
+    ui->pitchrateEdit->setValue(product->yeast_pitchrate);
+
+    ui->yeastsTable->setEditTriggers(QAbstractItemView::NoEditTriggers);
+
+    for (int i = 0; i < starters.size(); i++) {
+	ui->stmethodEdit->addItem(starters[i]);
+    }
+    ui->stmethodEdit->setCurrentIndex(product->starter_type);
+}
+
+
 /*
- * The results are not stored line in EditProduct. This is just a hint.
+ * Calculate the needed yeast for this batch.
  */
 void EditProduct::calcYeast()
 {
-    double sg = product->est_og;
-    double plato = Utils::sg_to_plato(sg);
-    double volume = product->batch_size * 0.9;	// Volume min trub chiller loss.
+    double sg = product->brew_fermenter_sg;
+    double use_cells;
+    double needed = 0;
+    double initcells = 0;
     bool maybe_starter = false;
-    double pitchrate = 0.75;
-    double initcells = 0;
 
     qDebug() << "calcYeast()";
     ui->yeastProcedure->setCurrentIndex(0);
 
+    if (sg <= 1.0001 && product->fg > 1.000)
+	sg = product->fg;
+    else if (sg <= 1.0001)
+	sg = product->est_og;
+    double plato = Utils::sg_to_plato(sg);
+
+    double volume = product->brew_fermenter_volume;
+    if (volume > 0) {
+	if (product->brew_fermenter_extrawater > 0)
+	    volume += product->brew_fermenter_extrawater;
+    } else {
+	volume = product->batch_size - product->eq_trub_chiller_loss;
+    }
+
+    // Also in calcFermentables()
+   //$('#yeast_cells').val(initcells);
+
     if (product->yeasts.size() == 0)
 	return;		// No yeast in product.
 
     for (int i = 0; i < product->yeasts.size(); i++) {
-	if (product->yeasts.at(i).y_use == 0) {		// Primary
-	    if (product->yeasts.at(i).y_form == 1) {
+	if (product->yeasts.at(i).y_use == YEAST_USE_PRIMARY) {		// Primary
+	    if (product->yeasts.at(i).y_form == YEAST_FORMS_DRY) {
 		/*
 		 * Dry yeast, build the formule with the yeast parameters.
 		 * Based on https://www.lallemandbrewing.com/en/canada/brewers-corner/brewing-tools/pitching-rate-calculator/
@@ -180,39 +217,446 @@
 		 * and http://braukaiser.com/blog/blog/2012/11/03/estimating-yeast-growth/
 		 */
 		ui->yeastProcedure->setCurrentIndex(1);
-		if (product->yeasts.at(i).y_type == 0) {		// Lager yeast
-		    pitchrate = 1.5;
-		    if (sg > 1.060)
-			pitchrate = 2.0;
-		} else if (product->yeasts.at(i).y_type == 6) {	// Real Kveik
-		    pitchrate = 0.075;
-		} else {
-		    pitchrate = 0.75;
-		    if (sg > 1.060)
-			pitchrate = 1.0;
+		if (product->yeast_pitchrate == 0) {
+		    /*
+		     * No pitchrate yet, do a educated guess ..
+		     */
+		    if (product->yeasts.at(i).y_type == YEAST_TYPES_LAGER) {
+		    	product->yeast_pitchrate = 1.5;
+		    	if (sg > 1.060)
+			    product->yeast_pitchrate = 2.0;
+		    } else if (product->yeasts.at(i).y_type == YEAST_TYPES_KVEIK) {	// Real Kveik
+		    	product->yeast_pitchrate = 0.075;
+		    } else {
+		    	product->yeast_pitchrate = 0.75;
+		    	if (sg > 1.060)
+			    product->yeast_pitchrate = 1.0;
+		    }
+		    is_changed();
+		    ui->pitchrateEdit->setValue(product->yeast_pitchrate);
 		}
-		if (product->yeasts.at(i).y_form == 0)
+
+		initcells = (product->yeasts.at(i).y_cells / 1000000) * product->yeasts.at(i).y_amount * 0.97;	// cells / ml.
+		if (product->yeasts.at(i).y_form == YEAST_FORMS_LIQUID)
 		    initcells = (product->yeasts.at(i).y_cells / 1000000000) * product->yeasts.at(i).y_amount * 0.97;	// 97% viability assumed.
-		else
-		    initcells = (product->yeasts.at(i).y_cells / 1000000) * product->yeasts.at(i).y_amount * 0.97;
 
-		double needed = round(pitchrate * volume * plato * 10.0) / 10.0;
-		double starter = 0;
+		needed = round(product->yeast_pitchrate * volume * plato * 10.0) / 10.0;
 		if (needed > initcells) {
 		    maybe_starter = true;
-		    starter = round(needed / 2.0) / 100.0;	// A very rough starter size estimate.
 		}
 
-		ui->pitchrateEdit->setValue(pitchrate);
-		ui->initcellsEdit->setValue(initcells);
-		ui->targetcellsEdit->setValue(needed);
-		ui->starterEdit->setValue(starter);
-
-		qDebug() << "  pitchrate:" << pitchrate << "needed:" << needed << "initcells:" << initcells << "starter" << maybe_starter << "size" << starter;
+		qDebug() << "  pitchrate:" << product->yeast_pitchrate << "needed:" << needed << "initcells:" << initcells << "starter" << maybe_starter;
 	    }
 	    break;
 	}
     }
+
+    if (maybe_starter != product->starter_enable) {
+	product->starter_enable = maybe_starter;
+	qDebug() << "  Set starter enable" << maybe_starter;
+	is_changed();
+    }
+
+    if (product->starter_enable) {
+	qDebug() << "  Starter calculate..";
+
+	const QStringList labels({tr("Method"), tr("Volume"), tr("Inj. factor"), tr("New cells"), tr("Total cells"), tr("Grow factor"), "" });
+	ui->starterTable->show();
+	ui->starterTable->setEditTriggers(QAbstractItemView::NoEditTriggers);
+	ui->starterTable->clear();
+	ui->starterTable->setColumnCount(7);
+	ui->starterTable->setRowCount(0);
+	ui->starterTable->setColumnWidth(0, 130);	/* Method	*/
+	ui->starterTable->setColumnWidth(1,  90);	/* Volume	*/
+	ui->starterTable->setColumnWidth(2,  90);	/* Inj. factor	*/
+	ui->starterTable->setColumnWidth(3,  90);	/* New cells	*/
+	ui->starterTable->setColumnWidth(4,  90);	/* Total cells	*/
+	ui->starterTable->setColumnWidth(5,  90);	/* Grow factor	*/
+	ui->starterTable->setColumnWidth(6,  30);	/* Edit button	*/
+	ui->starterTable->setHorizontalHeaderLabels(labels);
+	calcSteps(product->starter_type, initcells, needed);
+    } else {
+	ui->starterTable->hide();
+    }
+}
+
+
+/*
+ * http://braukaiser.com/blog/blog/2012/11/03/estimating-yeast-growth/
+ *
+ * stype: 0=stirred, 1=shaken, 2=simple
+ * totcells: initial cells
+ * egrams: gram extract
+ */
+double EditProduct::getGrowthRate(int stype, double totcells, double egrams)
+{
+    /* Cells per grams extract (B/g) */
+    double cpe = totcells / egrams;
+
+    if (cpe > 3.5)
+        return 0;       // no growth
+    if (stype == STARTERS_SIMPLE)
+        return 0.4;     // simple starter
+    if (stype == STARTERS_SHAKEN)
+        return 0.62;    // shaken starter
+    if (cpe <= 1.4)     // stirred starter
+        return 1.4;
+    return 2.33 - (.67 * cpe);
+};
+
+
+StepResult EditProduct::calcStep(double svol, int stype, double start)
+{
+    StepResult res;
+    double gperpoint = 2.72715;	//number of grams of extract per point of starter gravity per liter
+    double irate = round(start / svol * 10000.0) / 10.0;
+    double egrams = (product->starter_sg - 1) * svol * gperpoint;
+    double grate = getGrowthRate(stype, start, egrams);
+    double ncells = round(egrams * grate * 10.0) / 10.0;
+    double totcells = ncells + start;
+
+    res.svol = svol;
+    res.irate = irate;
+    res.ncells = ncells;
+    res.totcells = totcells;
+    res.growf = round((ncells / start) * 100.0) / 100.0;
+    qDebug() << "  calcStep(" << svol << "," << stype << "," << start << ") irate" << irate
+	     << "ncells" << res.ncells << "totcells" << res.totcells << "growf" << res.growf;
+    return res;
+}
+
+
+/*
+ * 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.
+ */
+void EditProduct::calcSteps(int stype, double start, double needed)
+{
+    int	i, step, svol;
+    int	lasti = 0;
+    /* Erlenmeyer sizes */
+    const int uvols[] { 20, 40, 60, 80, 100, 150, 200, 250, 375, 500, 625, 750, 875, 1000, 1250, 1500, 2000, 2500, 3000, 4000, 5000 };
+    int mvols = sizeof(uvols);
+    StepResult result;
+    QTableWidgetItem *item;
+    QWidget* pWidget;
+    QHBoxLayout* pLayout;
+    double tcells = start;
+    QIcon iconT;
+
+    iconT.addFile(QString::fromUtf8(":/icons/silk/pencil.png"), QSize(), QIcon::Normal, QIcon::Off);
+
+    if ((product->prop1_volume + product->prop2_volume + product->prop3_volume + product->prop4_volume) == 0) {
+	/*
+	 * Auto calculate the starter.
+	 */
+	qDebug() << "  calcSteps() auto";
+
+	if (start > needed)
+	    return;
+
+	for (step = 1; step < 5; step++) {
+	    qDebug() << "  step" << step;
+
+	    for (i = lasti; i <= mvols; i++) {
+		lasti = i;
+		svol = uvols[lasti];
+		result = calcStep(svol, stype, tcells);
+		if (result.irate < 25) {
+		    // inocculation rate too low, backup one step and break out.
+		    lasti = i - 1;
+		    svol = uvols[lasti];
+		    result = calcStep(svol, stype, tcells);
+		    break;
+		}
+		if (result.totcells > needed || i == mvols) {	// hit the target or loops done
+		    break;
+		}
+	    }
+	    ui->starterTable->setRowCount(step);
+	    item = new QTableWidgetItem(starters[stype]);
+	    item->setTextAlignment(Qt::AlignCenter|Qt::AlignVCenter);
+	    ui->starterTable->setItem(step -1, 0, item);
+
+	    item = new QTableWidgetItem(QString("%1").arg(result.svol / 1000.0, 4, 'f', 3, '0'));	// To liters
+	    item->setTextAlignment(Qt::AlignRight|Qt::AlignVCenter);
+	    ui->starterTable->setItem(step -1, 1, item);
+
+	    item = new QTableWidgetItem(QString("%1").arg(result.irate, 2, 'f', 1, '0'));
+	    item->setTextAlignment(Qt::AlignRight|Qt::AlignVCenter);
+            ui->starterTable->setItem(step -1, 2, item);
+
+	    item = new QTableWidgetItem(QString("%1").arg(result.ncells, 2, 'f', 1, '0'));
+            item->setTextAlignment(Qt::AlignRight|Qt::AlignVCenter);
+            ui->starterTable->setItem(step -1, 3, item);
+
+	    item = new QTableWidgetItem(QString("%1").arg(result.totcells, 2, 'f', 1, '0'));
+            item->setTextAlignment(Qt::AlignRight|Qt::AlignVCenter);
+            ui->starterTable->setItem(step -1, 4, item);
+
+	    item = new QTableWidgetItem(QString("%1").arg(result.growf, 3, 'f', 2, '0'));
+            item->setTextAlignment(Qt::AlignRight|Qt::AlignVCenter);
+            ui->starterTable->setItem(step -1, 5, item);
+
+	    pWidget = new QWidget();
+            QToolButton* btn_edit = new QToolButton();
+            btn_edit->setObjectName(QString("%1").arg(step - 1));  /* Send row with the button */
+	    btn_edit->setIcon(iconT);
+            connect(btn_edit, SIGNAL(clicked()), this, SLOT(yeast_starter_edit_clicked()));
+            pLayout = new QHBoxLayout(pWidget);
+            pLayout->addWidget(btn_edit);
+            pLayout->setContentsMargins(5, 0, 5, 0);
+            pWidget->setLayout(pLayout);
+            ui->starterTable->setCellWidget(step -1, 6, pWidget);
+
+	    if (step == 1) {
+		product->prop1_type = product->starter_type;
+		product->prop1_volume = result.svol / 1000.0;
+	    } else if (step == 2) {
+		product->prop2_type = product->starter_type;
+                product->prop2_volume = result.svol / 1000.0;
+	    } else if (step == 3) {
+                product->prop3_type = product->starter_type;
+                product->prop3_volume = result.svol / 1000.0;
+            } else if (step == 4) {
+                product->prop4_type = product->starter_type;
+                product->prop4_volume = result.svol / 1000.0;
+            }
+
+	    tcells = result.totcells;
+	    if (result.totcells > needed)	// Hit the target
+		return;
+	}
+
+    } else {
+	/*
+	 * Recalculate the starter.
+	 */
+	qDebug() << "  calcSteps() recalculate";
+
+	if (product->prop1_volume > 0) {
+	    result = calcStep(product->prop1_volume * 1000, product->prop1_type, tcells);
+	    ui->starterTable->setRowCount(1);
+	    item = new QTableWidgetItem(starters[product->prop1_type]);
+	    item->setTextAlignment(Qt::AlignCenter|Qt::AlignVCenter);
+	    ui->starterTable->setItem(0, 0, item);
+
+            item = new QTableWidgetItem(QString("%1").arg(result.svol / 1000.0, 4, 'f', 3, '0'));       // To liters
+            item->setTextAlignment(Qt::AlignRight|Qt::AlignVCenter);
+            ui->starterTable->setItem(0, 1, item);
+
+            item = new QTableWidgetItem(QString("%1").arg(result.irate, 2, 'f', 1, '0'));
+            item->setTextAlignment(Qt::AlignRight|Qt::AlignVCenter);
+            ui->starterTable->setItem(0, 2, item);
+
+            item = new QTableWidgetItem(QString("%1").arg(result.ncells, 2, 'f', 1, '0'));
+            item->setTextAlignment(Qt::AlignRight|Qt::AlignVCenter);
+            ui->starterTable->setItem(0, 3, item);
+
+            item = new QTableWidgetItem(QString("%1").arg(result.totcells, 2, 'f', 1, '0'));
+            item->setTextAlignment(Qt::AlignRight|Qt::AlignVCenter);
+            ui->starterTable->setItem(0, 4, item);
+
+            item = new QTableWidgetItem(QString("%1").arg(result.growf, 3, 'f', 2, '0'));
+            item->setTextAlignment(Qt::AlignRight|Qt::AlignVCenter);
+            ui->starterTable->setItem(0, 5, item);
+
+	    pWidget = new QWidget();
+            QToolButton* btn_edit = new QToolButton();
+            btn_edit->setObjectName(QString("0"));  /* Send row with the button */
+            btn_edit->setIcon(iconT);
+            connect(btn_edit, SIGNAL(clicked()), this, SLOT(yeast_starter_edit_clicked()));
+            pLayout = new QHBoxLayout(pWidget);
+            pLayout->addWidget(btn_edit);
+            pLayout->setContentsMargins(5, 0, 5, 0);
+            pWidget->setLayout(pLayout);
+            ui->starterTable->setCellWidget(0, 6, pWidget);
+
+	    tcells = result.totcells;
+	    if (result.totcells > needed) {	// Hit the target
+		product->prop2_volume = product->prop3_volume = product->prop4_volume = 0;
+		product->prop2_type = product->prop3_type = product->prop4_type = 0;
+                return;
+	    } else if (product->prop2_volume == 0) {	// Extra step needed, start with the same size.
+		product->prop2_volume = product->prop1_volume;
+		product->prop2_type = product->prop1_type;
+	    }
+	}
+	if (product->prop2_volume > 0) {
+            result = calcStep(product->prop2_volume * 1000, product->prop2_type, tcells);
+            ui->starterTable->setRowCount(2);
+	    item = new QTableWidgetItem(starters[product->prop2_type]);
+	    item->setTextAlignment(Qt::AlignCenter|Qt::AlignVCenter);
+	    ui->starterTable->setItem(1, 0, item);
+
+            item = new QTableWidgetItem(QString("%1").arg(result.svol / 1000.0, 4, 'f', 3, '0'));       // To liters
+            item->setTextAlignment(Qt::AlignRight|Qt::AlignVCenter);
+            ui->starterTable->setItem(1, 1, item);
+
+            item = new QTableWidgetItem(QString("%1").arg(result.irate, 2, 'f', 1, '0'));
+            item->setTextAlignment(Qt::AlignRight|Qt::AlignVCenter);
+            ui->starterTable->setItem(1, 2, item);
+
+            item = new QTableWidgetItem(QString("%1").arg(result.ncells, 2, 'f', 1, '0'));
+            item->setTextAlignment(Qt::AlignRight|Qt::AlignVCenter);
+            ui->starterTable->setItem(1, 3, item);
+
+            item = new QTableWidgetItem(QString("%1").arg(result.totcells, 2, 'f', 1, '0'));
+            item->setTextAlignment(Qt::AlignRight|Qt::AlignVCenter);
+            ui->starterTable->setItem(1, 4, item);
+
+            item = new QTableWidgetItem(QString("%1").arg(result.growf, 3, 'f', 2, '0'));
+            item->setTextAlignment(Qt::AlignRight|Qt::AlignVCenter);
+            ui->starterTable->setItem(1, 5, item);
+
+	    pWidget = new QWidget();
+            QToolButton* btn_edit = new QToolButton();
+            btn_edit->setObjectName(QString("1"));  /* Send row with the button */
+            btn_edit->setIcon(iconT);
+            connect(btn_edit, SIGNAL(clicked()), this, SLOT(yeast_starter_edit_clicked()));
+            pLayout = new QHBoxLayout(pWidget);
+            pLayout->addWidget(btn_edit);
+            pLayout->setContentsMargins(5, 0, 5, 0);
+            pWidget->setLayout(pLayout);
+            ui->starterTable->setCellWidget(1, 6, pWidget);
+
+            tcells = result.totcells;
+            if (result.totcells > needed) {     // Hit the target
+                product->prop3_volume = product->prop4_volume = 0;
+                product->prop3_type = product->prop4_type = 0;
+                return;
+            } else if (product->prop3_volume == 0) {    // Extra step needed, start with the same size.
+                product->prop3_volume = product->prop2_volume;
+                product->prop3_type = product->prop2_type;
+            }
+        }
+	if (product->prop3_volume > 0) {
+            result = calcStep(product->prop3_volume * 1000, product->prop3_type, tcells);
+            ui->starterTable->setRowCount(3);
+	    item = new QTableWidgetItem(starters[product->prop3_type]);
+	    item->setTextAlignment(Qt::AlignCenter|Qt::AlignVCenter);
+	    ui->starterTable->setItem(2, 0, item);
+
+            item = new QTableWidgetItem(QString("%1").arg(result.svol / 1000.0, 4, 'f', 3, '0'));       // To liters
+            item->setTextAlignment(Qt::AlignRight|Qt::AlignVCenter);
+            ui->starterTable->setItem(2, 1, item);
+
+            item = new QTableWidgetItem(QString("%1").arg(result.irate, 2, 'f', 1, '0'));
+            item->setTextAlignment(Qt::AlignRight|Qt::AlignVCenter);
+            ui->starterTable->setItem(2, 2, item);
+
+            item = new QTableWidgetItem(QString("%1").arg(result.ncells, 2, 'f', 1, '0'));
+            item->setTextAlignment(Qt::AlignRight|Qt::AlignVCenter);
+            ui->starterTable->setItem(2, 3, item);
+
+            item = new QTableWidgetItem(QString("%1").arg(result.totcells, 2, 'f', 1, '0'));
+            item->setTextAlignment(Qt::AlignRight|Qt::AlignVCenter);
+            ui->starterTable->setItem(2, 4, item);
+
+            item = new QTableWidgetItem(QString("%1").arg(result.growf, 3, 'f', 2, '0'));
+            item->setTextAlignment(Qt::AlignRight|Qt::AlignVCenter);
+            ui->starterTable->setItem(2, 5, item);
+
+	    pWidget = new QWidget();
+            QToolButton* btn_edit = new QToolButton();
+            btn_edit->setObjectName(QString("2"));  /* Send row with the button */
+            btn_edit->setIcon(iconT);
+            connect(btn_edit, SIGNAL(clicked()), this, SLOT(yeast_starter_edit_clicked()));
+            pLayout = new QHBoxLayout(pWidget);
+            pLayout->addWidget(btn_edit);
+            pLayout->setContentsMargins(5, 0, 5, 0);
+            pWidget->setLayout(pLayout);
+            ui->starterTable->setCellWidget(2, 6, pWidget);
+
+            tcells = result.totcells;
+            if (result.totcells > needed) {     // Hit the target
+                product->prop4_volume = 0;
+                product->prop4_type = 0;
+                return;
+            } else if (product->prop4_volume == 0) {    // Extra step needed, start with the same size.
+                product->prop4_volume = product->prop3_volume;
+                product->prop4_type = product->prop3_type;
+            }
+        }
+	if (product->prop4_volume > 0) {
+            result = calcStep(product->prop4_volume * 1000, product->prop4_type, tcells);
+            ui->starterTable->setRowCount(4);
+	    item = new QTableWidgetItem(starters[product->prop4_type]);
+	    item->setTextAlignment(Qt::AlignCenter|Qt::AlignVCenter);
+	    ui->starterTable->setItem(3, 0, item);
+
+            item = new QTableWidgetItem(QString("%1").arg(result.svol / 1000.0, 4, 'f', 3, '0'));       // To liters
+            item->setTextAlignment(Qt::AlignRight|Qt::AlignVCenter);
+            ui->starterTable->setItem(3, 1, item);
+
+            item = new QTableWidgetItem(QString("%1").arg(result.irate, 2, 'f', 1, '0'));
+            item->setTextAlignment(Qt::AlignRight|Qt::AlignVCenter);
+            ui->starterTable->setItem(3, 2, item);
+
+            item = new QTableWidgetItem(QString("%1").arg(result.ncells, 2, 'f', 1, '0'));
+            item->setTextAlignment(Qt::AlignRight|Qt::AlignVCenter);
+            ui->starterTable->setItem(3, 3, item);
+
+            item = new QTableWidgetItem(QString("%1").arg(result.totcells, 2, 'f', 1, '0'));
+            item->setTextAlignment(Qt::AlignRight|Qt::AlignVCenter);
+            ui->starterTable->setItem(3, 4, item);
+
+            item = new QTableWidgetItem(QString("%1").arg(result.growf, 3, 'f', 2, '0'));
+            item->setTextAlignment(Qt::AlignRight|Qt::AlignVCenter);
+            ui->starterTable->setItem(3, 5, item);
+
+	    pWidget = new QWidget();
+            QToolButton* btn_edit = new QToolButton();
+            btn_edit->setObjectName(QString("3"));  /* Send row with the button */
+            btn_edit->setIcon(iconT);
+            connect(btn_edit, SIGNAL(clicked()), this, SLOT(yeast_starter_edit_clicked()));
+            pLayout = new QHBoxLayout(pWidget);
+            pLayout->addWidget(btn_edit);
+            pLayout->setContentsMargins(5, 0, 5, 0);
+            pWidget->setLayout(pLayout);
+            ui->starterTable->setCellWidget(3, 6, pWidget);
+        }
+    }
+}
+
+
+void EditProduct::yeast_prod_date_clicked()
+{
+}
+
+
+void EditProduct::yeast_method_changed(int val)
+{
+    qDebug() << "yeast_method_changed" << val;
+    product->starter_type = val;
+    calcYeast();
+    is_changed();
+}
+
+
+void EditProduct::yeast_starter_sg_changed(double val)
+{
+    qDebug() << "yeast_starter_sg_changed" << val;
+    product->starter_sg = val;
+    calcYeast();
+    is_changed();
+}
+
+
+void EditProduct::yeast_pitchrate_button_clicked()
+{
+}
+
+
+void EditProduct::yeast_starter_edit_clicked()
+{
+    QToolButton *pb = qobject_cast<QToolButton *>(QObject::sender());
+    int row = pb->objectName().toInt();
+    qDebug() << "yeast_starter_edit_clicked" << row;
 }
 
 
@@ -594,7 +1038,7 @@
 	return;
 
     for (int i = 0; i < product->yeasts.size(); i++) {
-	if (product->yeasts.at(i).y_form == 1) { // Only adjust dry yeast
+	if (product->yeasts.at(i).y_form == YEAST_FORMS_DRY) { // Only adjust dry yeast
 	    amount = product->yeasts.at(i).y_amount * factor;
 	    product->yeasts[i].y_amount = amount;
 	}
--- a/src/Utils.cpp	Wed May 04 13:49:37 2022 +0200
+++ b/src/Utils.cpp	Thu May 05 17:20:06 2022 +0200
@@ -214,6 +214,7 @@
 
 double Utils::sg_to_plato(double sg)
 {
+    // On stChiellus: return ((135.997 * sg - 630.272) * sg + 1111.14) * sg - 616.868;
     return -668.962 + (1262.45 * sg) - (776.43 * sg * sg) + (182.94 * sg * sg * sg);
 }
 
--- a/src/global.h	Wed May 04 13:49:37 2022 +0200
+++ b/src/global.h	Thu May 05 17:20:06 2022 +0200
@@ -662,9 +662,47 @@
 };
 
 extern const QStringList misc_uses;
+
+enum YeastTypes {
+	YEAST_TYPES_LAGER,
+	YEAST_TYPES_ALE,
+	YEAST_TYPES_WHEAT,
+	YEAST_TYPES_WINE,
+	YEAST_TYPES_CHAMPAGNE,
+	YEAST_TYPES_BRETT,
+	YEAST_TYPES_KVEIK,
+	YEAST_TYPES_HYBRID
+};
+
 extern const QStringList yeast_types;
+
+enum YeastForms {
+	YEAST_FORMS_LIQUID,
+	YEAST_FORMS_DRY,
+	YEAST_FORMS_SLANT,
+	YEAST_FORMS_CULTURE,
+	YEAST_FORMS_FROZEN,
+	YEAST_FORMS_BOTTLE,
+	YEAST_FORMS_DRIED
+};
+
 extern const QStringList yeast_forms;
+
+enum YeastUse {
+	YEAST_USE_PRIMARY,
+	YEAST_USE_SECONDARY,
+	YEAST_USE_TERTIARY,
+	YEAST_USE_BOTTLE
+};
+
 extern const QStringList yeast_use;
+
+enum Starters {
+	STARTERS_STIRRED,
+	STARTERS_SHAKEN,
+	STARTERS_SIMPLE
+};
+
 extern const QStringList starters;
 extern const QStringList step_types;
 extern const QStringList tun_materials;
--- a/ui/EditProduct.ui	Wed May 04 13:49:37 2022 +0200
+++ b/ui/EditProduct.ui	Thu May 05 17:20:06 2022 +0200
@@ -95,7 +95,7 @@
        <enum>QTabWidget::Rounded</enum>
       </property>
       <property name="currentIndex">
-       <number>0</number>
+       <number>5</number>
       </property>
       <property name="elideMode">
        <enum>Qt::ElideNone</enum>
@@ -2723,7 +2723,7 @@
        <widget class="QLabel" name="est_og4Label">
         <property name="geometry">
          <rect>
-          <x>50</x>
+          <x>30</x>
           <y>10</y>
           <width>131</width>
           <height>20</height>
@@ -2739,7 +2739,7 @@
        <widget class="QDoubleSpinBox" name="est_og4Edit">
         <property name="geometry">
          <rect>
-          <x>190</x>
+          <x>170</x>
           <y>10</y>
           <width>71</width>
           <height>24</height>
@@ -2770,7 +2770,7 @@
        <widget class="QLabel" name="est_fg3Label">
         <property name="geometry">
          <rect>
-          <x>50</x>
+          <x>30</x>
           <y>40</y>
           <width>131</width>
           <height>20</height>
@@ -2786,7 +2786,7 @@
        <widget class="QDoubleSpinBox" name="est_fg3Edit">
         <property name="geometry">
          <rect>
-          <x>190</x>
+          <x>170</x>
           <y>40</y>
           <width>71</width>
           <height>24</height>
@@ -2817,7 +2817,7 @@
        <widget class="QLabel" name="est_abv2Label">
         <property name="geometry">
          <rect>
-          <x>50</x>
+          <x>30</x>
           <y>70</y>
           <width>131</width>
           <height>20</height>
@@ -2833,7 +2833,7 @@
        <widget class="QDoubleSpinBox" name="est_abv2Edit">
         <property name="geometry">
          <rect>
-          <x>190</x>
+          <x>170</x>
           <y>70</y>
           <width>71</width>
           <height>24</height>
@@ -2870,7 +2870,7 @@
        <widget class="QLabel" name="est_svgLabel">
         <property name="geometry">
          <rect>
-          <x>30</x>
+          <x>10</x>
           <y>100</y>
           <width>151</width>
           <height>20</height>
@@ -2886,7 +2886,7 @@
        <widget class="QDoubleSpinBox" name="est_svgEdit">
         <property name="geometry">
          <rect>
-          <x>190</x>
+          <x>170</x>
           <y>100</y>
           <width>71</width>
           <height>24</height>
@@ -2924,19 +2924,19 @@
         <property name="geometry">
          <rect>
           <x>10</x>
-          <y>250</y>
+          <y>280</y>
           <width>1101</width>
-          <height>211</height>
+          <height>181</height>
          </rect>
         </property>
        </widget>
        <widget class="QStackedWidget" name="yeastProcedure">
         <property name="geometry">
          <rect>
-          <x>290</x>
+          <x>320</x>
           <y>10</y>
-          <width>821</width>
-          <height>231</height>
+          <width>791</width>
+          <height>261</height>
          </rect>
         </property>
         <property name="currentIndex">
@@ -2944,31 +2944,11 @@
         </property>
         <widget class="QWidget" name="yeastNot"/>
         <widget class="QWidget" name="yeastLiquid">
-         <widget class="QLabel" name="label">
-          <property name="geometry">
-           <rect>
-            <x>200</x>
-            <y>20</y>
-            <width>341</width>
-            <height>20</height>
-           </rect>
-          </property>
-          <property name="font">
-           <font>
-            <pointsize>11</pointsize>
-            <weight>75</weight>
-            <bold>true</bold>
-           </font>
-          </property>
-          <property name="text">
-           <string>Liquid yeast advice</string>
-          </property>
-         </widget>
          <widget class="QDoubleSpinBox" name="pitchrateEdit">
           <property name="geometry">
            <rect>
-            <x>200</x>
-            <y>60</y>
+            <x>610</x>
+            <y>10</y>
             <width>91</width>
             <height>24</height>
            </rect>
@@ -2995,8 +2975,8 @@
          <widget class="QLabel" name="pitchrateLabel">
           <property name="geometry">
            <rect>
-            <x>10</x>
-            <y>60</y>
+            <x>420</x>
+            <y>10</y>
             <width>181</width>
             <height>20</height>
            </rect>
@@ -3008,139 +2988,119 @@
            <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
           </property>
          </widget>
-         <widget class="QLabel" name="initcellsLabel">
+         <widget class="QComboBox" name="stmethodEdit">
           <property name="geometry">
            <rect>
-            <x>10</x>
-            <y>90</y>
+            <x>230</x>
+            <y>10</y>
+            <width>101</width>
+            <height>23</height>
+           </rect>
+          </property>
+         </widget>
+         <widget class="QLabel" name="stmethodLabel">
+          <property name="geometry">
+           <rect>
+            <x>40</x>
+            <y>10</y>
             <width>181</width>
             <height>20</height>
            </rect>
           </property>
           <property name="text">
-           <string>Initial billion cells:</string>
+           <string>Starter method:</string>
           </property>
           <property name="alignment">
            <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
           </property>
          </widget>
-         <widget class="QLabel" name="targetcellsLabel">
+         <widget class="QLabel" name="startersgLabel">
           <property name="geometry">
            <rect>
-            <x>10</x>
-            <y>120</y>
+            <x>40</x>
+            <y>40</y>
             <width>181</width>
             <height>20</height>
            </rect>
           </property>
           <property name="text">
-           <string>Target billion cells:</string>
+           <string>Starter SG:</string>
           </property>
           <property name="alignment">
            <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
           </property>
          </widget>
-         <widget class="QDoubleSpinBox" name="initcellsEdit">
+         <widget class="QDoubleSpinBox" name="startersgEdit">
           <property name="geometry">
            <rect>
-            <x>200</x>
-            <y>90</y>
-            <width>91</width>
+            <x>230</x>
+            <y>40</y>
+            <width>71</width>
             <height>24</height>
            </rect>
           </property>
           <property name="alignment">
            <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
           </property>
-          <property name="readOnly">
-           <bool>true</bool>
-          </property>
           <property name="buttonSymbols">
-           <enum>QAbstractSpinBox::NoButtons</enum>
-          </property>
-          <property name="accelerated">
-           <bool>false</bool>
-          </property>
-          <property name="decimals">
-           <number>0</number>
-          </property>
-          <property name="maximum">
-           <double>100000.000000000000000</double>
-          </property>
-         </widget>
-         <widget class="QDoubleSpinBox" name="targetcellsEdit">
-          <property name="geometry">
-           <rect>
-            <x>200</x>
-            <y>120</y>
-            <width>91</width>
-            <height>24</height>
-           </rect>
-          </property>
-          <property name="alignment">
-           <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
-          </property>
-          <property name="readOnly">
-           <bool>true</bool>
-          </property>
-          <property name="buttonSymbols">
-           <enum>QAbstractSpinBox::NoButtons</enum>
+           <enum>QAbstractSpinBox::UpDownArrows</enum>
           </property>
           <property name="accelerated">
-           <bool>false</bool>
-          </property>
-          <property name="decimals">
-           <number>0</number>
-          </property>
-          <property name="maximum">
-           <double>100000.000000000000000</double>
-          </property>
-         </widget>
-         <widget class="QLabel" name="starterLabel">
-          <property name="geometry">
-           <rect>
-            <x>10</x>
-            <y>150</y>
-            <width>181</width>
-            <height>20</height>
-           </rect>
-          </property>
-          <property name="text">
-           <string>Starter volume L:</string>
-          </property>
-          <property name="alignment">
-           <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
-          </property>
-         </widget>
-         <widget class="QDoubleSpinBox" name="starterEdit">
-          <property name="geometry">
-           <rect>
-            <x>200</x>
-            <y>150</y>
-            <width>91</width>
-            <height>24</height>
-           </rect>
-          </property>
-          <property name="toolTip">
-           <string>A very rough starter volume estimate.</string>
-          </property>
-          <property name="alignment">
-           <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
-          </property>
-          <property name="readOnly">
            <bool>true</bool>
           </property>
-          <property name="buttonSymbols">
-           <enum>QAbstractSpinBox::NoButtons</enum>
-          </property>
-          <property name="accelerated">
-           <bool>false</bool>
-          </property>
           <property name="decimals">
            <number>3</number>
           </property>
+          <property name="minimum">
+           <double>0.980000000000000</double>
+          </property>
           <property name="maximum">
-           <double>100000.000000000000000</double>
+           <double>2.000000000000000</double>
+          </property>
+          <property name="singleStep">
+           <double>0.001000000000000</double>
+          </property>
+         </widget>
+         <widget class="QToolButton" name="pitchrateButton">
+          <property name="geometry">
+           <rect>
+            <x>710</x>
+            <y>10</y>
+            <width>28</width>
+            <height>22</height>
+           </rect>
+          </property>
+          <property name="toolTip">
+           <string>Set or clear date</string>
+          </property>
+          <property name="text">
+           <string>...</string>
+          </property>
+          <property name="icon">
+           <iconset resource="../../../../../../home/mbroek/MyProjects/bmsapp/resources/icons.qrc">
+            <normaloff>:/icons/bms/erlenmeyer.png</normaloff>:/icons/bms/erlenmeyer.png</iconset>
+          </property>
+         </widget>
+         <widget class="QTableWidget" name="starterTable">
+          <property name="geometry">
+           <rect>
+            <x>80</x>
+            <y>100</y>
+            <width>636</width>
+            <height>146</height>
+           </rect>
+          </property>
+          <property name="sizePolicy">
+           <sizepolicy hsizetype="Expanding" vsizetype="Expanding">
+            <horstretch>0</horstretch>
+            <verstretch>0</verstretch>
+           </sizepolicy>
+          </property>
+          <property name="minimumSize">
+           <size>
+            <width>0</width>
+            <height>0</height>
+           </size>
           </property>
          </widget>
         </widget>
@@ -3425,8 +3385,8 @@
        <widget class="QPushButton" name="addYeast">
         <property name="geometry">
          <rect>
-          <x>180</x>
-          <y>210</y>
+          <x>90</x>
+          <y>240</y>
           <width>80</width>
           <height>23</height>
          </rect>
@@ -3439,6 +3399,99 @@
           <normaloff>:/icons/bms/erlenmeyer.png</normaloff>:/icons/bms/erlenmeyer.png</iconset>
         </property>
        </widget>
+       <widget class="QLabel" name="productionLabel">
+        <property name="geometry">
+         <rect>
+          <x>10</x>
+          <y>130</y>
+          <width>151</width>
+          <height>20</height>
+         </rect>
+        </property>
+        <property name="text">
+         <string>Production date:</string>
+        </property>
+        <property name="alignment">
+         <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
+        </property>
+       </widget>
+       <widget class="QLabel" name="conditionLabel">
+        <property name="geometry">
+         <rect>
+          <x>10</x>
+          <y>160</y>
+          <width>151</width>
+          <height>20</height>
+         </rect>
+        </property>
+        <property name="text">
+         <string>Yeast condition:</string>
+        </property>
+        <property name="alignment">
+         <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
+        </property>
+       </widget>
+       <widget class="QToolButton" name="productionButton">
+        <property name="geometry">
+         <rect>
+          <x>280</x>
+          <y>130</y>
+          <width>28</width>
+          <height>22</height>
+         </rect>
+        </property>
+        <property name="toolTip">
+         <string>Set or clear date</string>
+        </property>
+        <property name="text">
+         <string>...</string>
+        </property>
+        <property name="icon">
+         <iconset resource="../../../../../../home/mbroek/MyProjects/bmsapp/resources/icons.qrc">
+          <normaloff>:/icons/silk/date.png</normaloff>:/icons/silk/date.png</iconset>
+        </property>
+       </widget>
+       <widget class="QLineEdit" name="productionEdit">
+        <property name="geometry">
+         <rect>
+          <x>170</x>
+          <y>130</y>
+          <width>101</width>
+          <height>23</height>
+         </rect>
+        </property>
+        <property name="toolTip">
+         <string>End of primary fermentation, start secondary.</string>
+        </property>
+        <property name="readOnly">
+         <bool>true</bool>
+        </property>
+       </widget>
+       <widget class="QSpinBox" name="conditionShow">
+        <property name="geometry">
+         <rect>
+          <x>170</x>
+          <y>160</y>
+          <width>71</width>
+          <height>24</height>
+         </rect>
+        </property>
+        <property name="alignment">
+         <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
+        </property>
+        <property name="readOnly">
+         <bool>true</bool>
+        </property>
+        <property name="buttonSymbols">
+         <enum>QAbstractSpinBox::NoButtons</enum>
+        </property>
+        <property name="accelerated">
+         <bool>false</bool>
+        </property>
+        <property name="suffix">
+         <string> %</string>
+        </property>
+       </widget>
       </widget>
       <widget class="QWidget" name="mash">
        <attribute name="icon">

mercurial