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 { public function getNutrientsSummaryAttribute(): string {
$summary = []; $summary = [];
foreach (Nutrients::all() as $nutrient) { 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']}"; $summary[] = "{$nutrient['label']}: {$amount}{$nutrient['unit']}";
} }
return implode(', ', $summary); return implode(', ', $summary);

View File

@ -7,6 +7,7 @@ use App\Models\Traits\Ingredient;
use App\Models\Traits\Journalable; use App\Models\Traits\Journalable;
use App\Models\Traits\Sluggable; use App\Models\Traits\Sluggable;
use App\Models\Traits\Taggable; use App\Models\Traits\Taggable;
use App\Support\Nutrients;
use ElasticScoutDriverPlus\QueryDsl; use ElasticScoutDriverPlus\QueryDsl;
use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Model;
@ -165,7 +166,7 @@ final class Recipe extends Model implements HasMedia
if (empty($this->weight)) { if (empty($this->weight)) {
return null; 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. * Add nutrient calculations handling to overloading.
*
* @param string $method
* @param array $parameters
*
* @return mixed
*
* @noinspection PhpMissingParamTypeInspection
*/ */
public function __call($method, $parameters): mixed { public function __call($method, $parameters): mixed {
if (in_array($method, $this->nutrientTotalMethods)) { if (in_array($method, $this->nutrientTotalMethods)) {
@ -223,15 +217,7 @@ final class Recipe extends Model implements HasMedia
} }
elseif (in_array($method, $this->nutrientPerServingMethods)) { elseif (in_array($method, $this->nutrientPerServingMethods)) {
$sum = $this->sumNutrient(substr($method, 0, -10)) / $this->servings; $sum = $this->sumNutrient(substr($method, 0, -10)) / $this->servings;
return Nutrients::round($sum, substr($method, 0, -10));
// 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);
}
} }
else { else {
return parent::__call($method, $parameters); return parent::__call($method, $parameters);

View File

@ -188,4 +188,24 @@ class Nutrients
throw new \DomainException("Unsupported recipe unit: {$fromUnit}"); 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> </div>
<span class="text-sm text-gray-500"> <span class="text-sm text-gray-500">
@foreach(\App\Support\Nutrients::all()->sortBy('weight') as $nutrient) @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 {{ $nutrient['value'] }}@if(!$loop->last), @endif
@endforeach @endforeach
</span> </span>
@ -124,7 +124,7 @@
<div> <div>
<span class="font-bold">nutrients:</span> <span class="font-bold">nutrients:</span>
@foreach(\App\Support\Nutrients::all()->sortBy('weight') as $nutrient) @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 {{ $nutrient['value'] }}@if(!$loop->last), @endif
@endforeach @endforeach
</div> </div>