Skip to content

Commit

Permalink
feat: Allow to add nutriments (#1094)
Browse files Browse the repository at this point in the history
* Allow to add nutriments

* Allow to add nutriments from a list of ids

* add-sodium-as-default
  • Loading branch information
alexfauquette authored Dec 10, 2024
1 parent 4563306 commit 84c0bba
Show file tree
Hide file tree
Showing 4 changed files with 219 additions and 5 deletions.
15 changes: 15 additions & 0 deletions src/pages/nutrition/PictureSection.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
import * as React from "react";
import IconButton from "@mui/material/IconButton";
import RotateRightIcon from "@mui/icons-material/RotateRight";
import RotateLeftIcon from "@mui/icons-material/RotateLeft";
import { InsightType } from "./insight.types";
import { ProductType } from "./useRobotoffPredictions";
import { getImageId } from "./utils";
Expand All @@ -19,6 +22,11 @@ interface PictureSectionProps {
export default function PictureSection(props: PictureSectionProps) {
const { isLoading, insight, product, apiRef } = props;

const [rotationIndex, setRotationIndex] = React.useState(0);

React.useEffect(() => {
setRotationIndex(0);
}, [insight]);
if (isLoading) {
return <p>Loading ....</p>;
}
Expand All @@ -42,6 +50,12 @@ export default function PictureSection(props: PictureSectionProps) {
})
: "..."}
</p>
<IconButton onClick={() => setRotationIndex((p) => p - 1)}>
<RotateLeftIcon />
</IconButton>
<IconButton onClick={() => setRotationIndex((p) => p + 1)}>
<RotateRightIcon />
</IconButton>
<TransformWrapper limitToBounds={false} ref={apiRef}>
<TransformComponent>
<img
Expand All @@ -51,6 +65,7 @@ export default function PictureSection(props: PictureSectionProps) {
style={{
width: "100%",
maxHeight: "200vh",
rotate: `${90 * rotationIndex}deg`,
}}
/>
</TransformComponent>
Expand Down
152 changes: 152 additions & 0 deletions src/pages/nutrition/config.ts
Original file line number Diff line number Diff line change
@@ -1 +1,153 @@
export const UNITS = ["", "g", "mg", "µg"];

export const OFF_NUTRIMENTS_TO_IGNORE = [
"serving",
"energy", // Already available with energy kj and kcal.
"fruits-vegetables-legumes-estimate-from-ingredients",
"fruits-vegetables-nuts-estimate-from-ingredients",
"nova-group",
"nutrition-score-fr",
];
export const NUTRIMENTS_ORDER = [
// Energy
"energy-kj",
"energy-kcal",

// Fat
"fat",
"saturated-fat",
"unsaturated-fat",
"monounsaturated-fat",
"polyunsaturated-fat",
"trans-fat",
"butyric-acid",
"caproic-acid",
"caprylic-acid",
"capric-acid",
"lauric-acid",
"myristic-acid",
"palmitic-acid",
"stearic-acid",
"arachidic-acid",
"behenic-acid",
"lignoceric-acid",
"cerotic-acid",
"montanic-acid",
"melissic-acid",

// Sucres
"carbohydrates",
"sugars",
"added-sugars",
"sucrose",
"glucose",
"fructose",
"lactose",
"maltose",
"maltodextrins",

"fiber",
"soluble-fiber",
"insoluble-fiber",

"proteins",
"casein",
"serum-proteins",

"salt",
"added-salt",
"sodium",

"cholesterol",

"alpha-linolenic-acid",
"eicosapentaenoic-acid",
"docosahexaenoic-acid",
"omega-3-fat",
"omega-6-fat",
"linoleic-acid",
"arachidonic-acid",
"gamma-linolenic-acid",
"dihomo-gamma-linolenic-acid",
"omega-9-fat",
"oleic-acid",
"elaidic-acid",
"gondoic-acid",
"mead-acid",
"erucic-acid",
"nervonic-acid",

"oligosaccharide",

"galactose",
"starch",
"Polydextrose",
"polyols",
"erythritol",

"nucleotides",
"alcohol",
"vitamin-a",
"beta-carotene",
"vitamin-d",
"vitamin-e",
"vitamin-k",
"vitamin-c",
"vitamin-b1",
"vitamin-b2",
"vitamin-pp",
"vitamin-b6",
"vitamin-b9",
"folates",
"vitamin-b12",
"biotin",
"pantothenic-acid",
"silica",
"bicarbonate",
"Sulphate",
"Nitrate",
"Hydrogencarbonate",
"Nitrite",
"potassium",
"chloride",
"calcium",
"phosphorus",
"iron",
"magnesium",
"zinc",
"copper",
"manganese",
"fluoride",
"selenium",
"chromium",
"molybdenum",
"iodine",
"caffeine",
"taurine",
"ph",
"collagen-meat-protein-ratio",
"cocoa",
"chlorophyl",
"glycemic-index",
"water-hardness",
"choline",
"phylloquinone",
"beta-glucan",
"inositol",
"carnitine",
"spermidine",
"water",
"acidity",
"dry-residue",
"beta-alanine",
"creatine",
"l-citrulline",
"l-glutamine",
"bcaa",
"l-valine",
"l-leucine",
"l-isoleucine",
"l-arginine",
"l-cysteine",
"l-Glutathione",
];
37 changes: 35 additions & 2 deletions src/pages/nutrition/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,16 @@ import { ErrorBoundary } from "../taxonomyWalk/Error";
import LinksToProduct from "./LinksToProduct";
import { NutrimentCell } from "./NutrimentCell";
import PictureSection from "./PictureSection";
import { NUTRIMENTS_ORDER } from "./config";

export default function Nutrition() {
const [partiallyFilled, setPartiallyFilled] = React.useState(false);
const [displayOFFValue, setDisplayOFFValue] = React.useState(false);
const handlePartiallyFilled = (_, checked) => setPartiallyFilled(checked);
const handleDisplayOFFValue = (_, checked) => setDisplayOFFValue(checked);

console.log({ partiallyFilled });
const [additionalIds, setAdditionalIds] = React.useState([]);

const { isLoading, insight, nextItem, count, product } =
useRobotoffPredictions(partiallyFilled);

Expand All @@ -32,6 +34,7 @@ export default function Nutrition() {
const apiRef = React.useRef<ReactZoomPanPinchRef>();

React.useEffect(() => {
setAdditionalIds([]);
if (!insight || typeof insight === "string") {
setValues({});
return;
Expand Down Expand Up @@ -62,8 +65,15 @@ export default function Nutrition() {
}));
}, [insight]);

const nutrimentsDetected = structurePredictions(values);
const nutrimentsDetected = React.useMemo(
() => structurePredictions(values, product, additionalIds),
[values, product, additionalIds],
);

const notUsedNutriments = React.useMemo(
() => NUTRIMENTS_ORDER.filter((id) => !nutrimentsDetected.includes(id)),
[nutrimentsDetected],
);
return (
<React.Suspense>
<ErrorBoundary>
Expand Down Expand Up @@ -195,6 +205,29 @@ export default function Nutrition() {
</tr>
);
})}
<tr>
<td style={{ paddingLeft: 10, paddingRight: 4 }}>
<select
style={{ width: 155 }}
value=""
tabIndex={2}
onChange={(event) => {
setAdditionalIds((p) => [...p, event.target.value]);
}}
>
<option disabled selected value="">
-- add nutriment --
</option>
{notUsedNutriments.map((nutriId) => (
<option key={nutriId} value={nutriId}>
{nutriId}
</option>
))}
</select>
</td>
<td />
<td />
</tr>
</tbody>
<tfoot>
<tr>
Expand Down
20 changes: 17 additions & 3 deletions src/pages/nutrition/utils.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import axios from "axios";
import { UNITS } from "./config";
import { OFF_NUTRIMENTS_TO_IGNORE, UNITS } from "./config";
import { NutrimentPrediction } from "./insight.types";
import { ROBOTOFF_API_URL } from "../../const";

Expand All @@ -15,6 +15,7 @@ export const NUTRIMENTS = {
fiber: "fiber",
proteins: "proteins",
salt: "salt",
sodium: "sodium",
};

export function isValidUnit(unit: string | null) {
Expand All @@ -23,21 +24,34 @@ export function isValidUnit(unit: string | null) {

export function structurePredictions(
predictions: Record<string, Pick<NutrimentPrediction, "value" | "unit">>,
productValue?: { nutriments?: Record<string, string | number> },
additionalIds?: string[],
) {
const nurimentsIds = Object.keys(NUTRIMENTS);

Object.keys(predictions).forEach((key) => {
if (key === "serving_size") {
const id = key.split("_")[0]; // split 'energy-kj_100g' to only get 'energy-kj'

if (OFF_NUTRIMENTS_TO_IGNORE.includes(id)) {
return;
}
if (!nurimentsIds.includes(id)) {
nurimentsIds.push(id);
}
});

Object.keys(productValue?.nutriments ?? {}).forEach((key) => {
const id = key.split("_")[0]; // split 'energy-kj_100g' to only get 'energy-kj'

if (OFF_NUTRIMENTS_TO_IGNORE.includes(id)) {
return;
}
if (!nurimentsIds.includes(id)) {
nurimentsIds.push(id);
}
});

return nurimentsIds;
return [...nurimentsIds, ...additionalIds];
}

interface PostRobotoffParams {
Expand Down

0 comments on commit 84c0bba

Please sign in to comment.