Implement FDA guideline-based rounding

Rounding is left out of Food nutrients for now to prevent rounding up before
summing in certain places.
This commit is contained in:
Christopher C. Wells 2021-04-08 16:05:27 -07:00
parent 0f2a506add
commit 17e640303d
4 changed files with 26 additions and 20 deletions

View File

@ -103,7 +103,7 @@ final class IngredientAmount extends Model
public function getNutrientsSummaryAttribute(): string {
$summary = [];
foreach (Nutrients::all() as $nutrient) {
$amount = round($this->{$nutrient['value']}(), 2);
$amount = Nutrients::round($this->{$nutrient['value']}(), $nutrient['value']);
$summary[] = "{$nutrient['label']}: {$amount}{$nutrient['unit']}";
}
return implode(', ', $summary);

View File

@ -7,6 +7,7 @@ use App\Models\Traits\Ingredient;
use App\Models\Traits\Journalable;
use App\Models\Traits\Sluggable;
use App\Models\Traits\Taggable;
use App\Support\Nutrients;
use ElasticScoutDriverPlus\QueryDsl;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
@ -165,7 +166,7 @@ final class Recipe extends Model implements HasMedia
if (empty($this->weight)) {
return null;
}
return round($this->weight / $this->servings, 2);
return round($this->weight / $this->servings);
}
/**
@ -209,13 +210,6 @@ final class Recipe extends Model implements HasMedia
/**
* Add nutrient calculations handling to overloading.
*
* @param string $method
* @param array $parameters
*
* @return mixed
*
* @noinspection PhpMissingParamTypeInspection
*/
public function __call($method, $parameters): mixed {
if (in_array($method, $this->nutrientTotalMethods)) {
@ -223,15 +217,7 @@ final class Recipe extends Model implements HasMedia
}
elseif (in_array($method, $this->nutrientPerServingMethods)) {
$sum = $this->sumNutrient(substr($method, 0, -10)) / $this->servings;
// Per-serving calculations are rounded, though actual food label
// rounding standards are more complex.
if ($sum > 1) {
return round($sum);
}
else {
return round($sum, 2);
}
return Nutrients::round($sum, substr($method, 0, -10));
}
else {
return parent::__call($method, $parameters);

View File

@ -188,4 +188,24 @@ class Nutrients
throw new \DomainException("Unsupported recipe unit: {$fromUnit}");
}
}
/**
* Round a nutrient amount according to FDA guidelines.
*
* Note: this stays mostly true to the guidelines except that carbohydrates
* and protein are meant to state "less than 1 gram" when the amount is less
* than 1 gram. Instead, this method treats anything less than 1 gram as
* zero.
*
* @url https://labelcalc.com/food-labeling/a-guide-to-using-fda-rounding-rules-for-your-food-label/
*/
public static function round(float $amount, string $nutrient): float {
return match ($nutrient) {
'calories' => ($amount < 5 ? 0 : ($amount <= 50 ? round( $amount / 5 ) * 5 : round( $amount / 10 ) * 10)),
'carbohydrates', 'protein' => ($amount < 1 ? 0 : round( $amount)),
'cholesterol', 'fat' => ($amount < 0.5 ? 0 : ($amount <= 5 ? round( $amount / 5, 1 ) * 5 : round($amount))),
'sodium' => ($amount < 5 ? 0 : ($amount <= 140 ? round( $amount / 5 ) * 5 : round( $amount / 10 ) * 10)),
default => throw new \UnexpectedValueException()
};
}
}

View File

@ -104,7 +104,7 @@
</div>
<span class="text-sm text-gray-500">
@foreach(\App\Support\Nutrients::all()->sortBy('weight') as $nutrient)
{{ round($entries->where('meal', $meal)->sum($nutrient['value']), 2) }}{{ $nutrient['unit'] }}
{{ \App\Support\Nutrients::round($entries->where('meal', $meal)->sum($nutrient['value']), $nutrient['value']) }}{{ $nutrient['unit'] }}
{{ $nutrient['value'] }}@if(!$loop->last), @endif
@endforeach
</span>
@ -124,7 +124,7 @@
<div>
<span class="font-bold">nutrients:</span>
@foreach(\App\Support\Nutrients::all()->sortBy('weight') as $nutrient)
{{ round($entry->{$nutrient['value']}, 2) }}{{ $nutrient['unit'] }}
{{ \App\Support\Nutrients::round($entry->{$nutrient['value']}, $nutrient['value']) }}{{ $nutrient['unit'] }}
{{ $nutrient['value'] }}@if(!$loop->last), @endif
@endforeach
</div>