fix: normalize nutrition units and retain zero-valued nutrients
This commit is contained in:
1 parent
f037001339
commit
fa07ba8910
8 files changed
+253
-149
No files matched your search
@@ -0,0 +1,24 @@
|
||||
/** Normalização dos valores nutricionais recebidos do Open Food Facts. */
|
||||
export function getNutrientValue(value: unknown): number | null {
|
||||
if (typeof value !== "number" && typeof value !== "string") return null;
|
||||
if (typeof value === "string" && value.trim() === "") return null;
|
||||
const number = Number(value);
|
||||
return Number.isFinite(number) && number >= 0 ? number : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prefere o campo explícito em kcal. Os campos energy/energy-kj usam kJ;
|
||||
* 1 kcal = 4.184 kJ. Zero é um valor válido, distinto de dado ausente.
|
||||
* https://openfoodfacts.github.io/openfoodfacts-server/api/tutorial-off-api/
|
||||
*/
|
||||
export function getEnergyKcal(nutriments?: {
|
||||
"energy-kcal_100g"?: unknown;
|
||||
"energy-kj_100g"?: unknown;
|
||||
energy_100g?: unknown;
|
||||
}): number | null {
|
||||
const kcal = getNutrientValue(nutriments?.["energy-kcal_100g"]);
|
||||
if (kcal !== null) return kcal;
|
||||
const kj = getNutrientValue(nutriments?.["energy-kj_100g"])
|
||||
?? getNutrientValue(nutriments?.energy_100g);
|
||||
return kj === null ? null : kj / 4.184;
|
||||
}
|
||||
+21
-14
@@ -3,6 +3,8 @@
|
||||
* Melhorias: Memoização de Scores, Rate Limiting Robusto e Tipagem Estrita.
|
||||
*/
|
||||
|
||||
import { getEnergyKcal, getNutrientValue } from "./nutrition.ts";
|
||||
|
||||
export interface Product {
|
||||
code: string;
|
||||
product_name: string;
|
||||
@@ -11,6 +13,8 @@ export interface Product {
|
||||
ingredients_text?: string;
|
||||
nutriments?: {
|
||||
energy_100g?: number;
|
||||
"energy-kcal_100g"?: number;
|
||||
"energy-kj_100g"?: number;
|
||||
fat_100g?: number;
|
||||
carbohydrates_100g?: number;
|
||||
proteins_100g?: number;
|
||||
@@ -185,6 +189,7 @@ export function sortProducts(
|
||||
value: p,
|
||||
score: calculateCompletenessScore(p),
|
||||
nutritionValue: getGradeValue(p.nutrition_grades),
|
||||
energyKcal: getEnergyKcal(p.nutriments),
|
||||
}));
|
||||
|
||||
// 2. Sort: Ordenação sobre valores em memória (O(n log n))
|
||||
@@ -199,7 +204,9 @@ export function sortProducts(
|
||||
diff = a.nutritionValue - b.nutritionValue;
|
||||
break;
|
||||
case "energy":
|
||||
diff = (a.value.nutriments?.energy_100g || 9999) - (b.value.nutriments?.energy_100g || 9999);
|
||||
if (a.energyKcal === null) return b.energyKcal === null ? 0 : 1;
|
||||
if (b.energyKcal === null) return -1;
|
||||
diff = a.energyKcal - b.energyKcal;
|
||||
break;
|
||||
case "name":
|
||||
diff = (a.value.product_name || "").localeCompare(b.value.product_name || "");
|
||||
@@ -240,21 +247,21 @@ export function formatNutritionData(p: Product) {
|
||||
const n = p.nutriments;
|
||||
if (!n) return null;
|
||||
|
||||
const fmt = (v: unknown, d: number = 1) => {
|
||||
const num =
|
||||
typeof v === "number"
|
||||
? v
|
||||
: parseFloat(typeof v === "string" ? v : String(v));
|
||||
return isNaN(num) ? "N/A" : num.toFixed(d);
|
||||
const fmt = (v: unknown, unit: string, d: number = 1) => {
|
||||
const num = getNutrientValue(v);
|
||||
return num === null ? "N/A" : `${num.toFixed(d)}${unit}`;
|
||||
};
|
||||
const energy = getEnergyKcal(n);
|
||||
|
||||
return {
|
||||
energy: n.energy_100g ? `${Math.round(n.energy_100g)} kcal` : "N/A",
|
||||
fat: n.fat_100g ? `${fmt(n.fat_100g)}g` : "N/A",
|
||||
carbs: n.carbohydrates_100g ? `${fmt(n.carbohydrates_100g)}g` : "N/A",
|
||||
proteins: n.proteins_100g ? `${fmt(n.proteins_100g)}g` : "N/A",
|
||||
sugars: n.sugars_100g ? `${fmt(n.sugars_100g)}g` : "N/A",
|
||||
sodium: n.sodium_100g ? `${fmt(n.sodium_100g, 2)}g` : "N/A",
|
||||
energy: energy === null ? "N/A" : `${Math.round(energy)} kcal`,
|
||||
fat: fmt(n.fat_100g, "g"),
|
||||
carbs: fmt(n.carbohydrates_100g, "g"),
|
||||
carbohydrates: fmt(n.carbohydrates_100g, "g"),
|
||||
proteins: fmt(n.proteins_100g, "g"),
|
||||
sugars: fmt(n.sugars_100g, "g"),
|
||||
fiber: fmt(n.fiber_100g, "g"),
|
||||
sodium: fmt(n.sodium_100g, "g", 2),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -269,4 +276,4 @@ export function detectSearchType(input: string): "barcode" | "name" {
|
||||
export function isValidBarcode(barcode: string): boolean {
|
||||
const cleaned = barcode.replace(/\D/g, "");
|
||||
return cleaned.length >= 8 && cleaned.length <= 13 && /^\d+$/.test(cleaned);
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user