Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refine nutri game: limit nutriments to known list and add color #1099

Merged
merged 3 commits into from
Dec 12, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
99 changes: 99 additions & 0 deletions src/pages/nutrition/Instructions.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
/* eslint-disable react/no-unescaped-entities */
import * as React from "react";
import Button from "@mui/material/Button";
import Dialog from "@mui/material/Dialog";
import DialogActions from "@mui/material/DialogActions";
import DialogContent from "@mui/material/DialogContent";
import DialogContentText from "@mui/material/DialogContentText";
import DialogTitle from "@mui/material/DialogTitle";
import Typography from "@mui/material/Typography";

export default function Instructions() {
const [open, setOpen] = React.useState(false);

const handleClickOpen = () => {
setOpen(true);
};

const handleClose = () => {
setOpen(false);
};

return (
<React.Fragment>
<Button
size="small"
variant="outlined"
onClick={handleClickOpen}
sx={{ margin: 2 }}
>
Instructions
</Button>
<Dialog
open={open}
onClose={handleClose}
aria-labelledby="alert-dialog-title"
aria-describedby="alert-dialog-description"
>
<DialogTitle id="alert-dialog-title">
Comment utiliser cette interface
</DialogTitle>
<DialogContent sx={{ "& p": { marginBottom: 2 } }}>
<DialogContentText component="div" id="alert-dialog-description">
<Typography>
Avec les checkbox du haut, vous pouvez choisir entre traiter des
tableux de produit sans tableau nutritionel. Ou traiter des
produits pour lequels robotoof à trouver des nutriments en plus.
</Typography>
<Typography>
La date de la photo est affichée au dessus de la photo.
</Typography>
<Typography>
Les nutriments déjà associé au produit sont affiché en petit sous
les inputs.
<ol>
<li>
En <span style={{ color: "green" }}>vert</span> pour ceux qui
matchent avec la valeur du champ
</li>
<li>
En <span style={{ color: "orange" }}>orange</span> si le champ
est vide
</li>
<li>
En <span style={{ color: "red" }}>rouge</span> si le champ est
différent de la valeur connue.
</li>
</ol>
La valeur "-" indique une valeur abscente du tableau nutritionel.
Particulierement utile pour les fibres qui sont souvent abscente.
</Typography>
<Typography>
Quand une colone a été vérifiée (celle pour 100g ou celle par
portion) il ne vous reste plus qu'à la valider pour passer à la
suite. Innutil de remplir les deux colones. Une seul des deux peut
être enregistrée par OFF.
</Typography>

<ul>
<li>
Le bouton "skip" passe à la suite. quelqu'un d'autre s'en
chargera.
</li>
<li>
Le bouton "invalid image" indique que la photo ne correspond pas
à un tableau nutritionel, et supprime la question pour tout le
monde.
</li>
</ul>
</DialogContentText>
</DialogContent>
<DialogActions>
<Button onClick={handleClose} autoFocus>
Fermer
</Button>
</DialogActions>
</Dialog>
</React.Fragment>
);
}
53 changes: 49 additions & 4 deletions src/pages/nutrition/NutrimentCell.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,35 @@ interface NutrimentCellProps {
setValues: (object) => void;
}

/**
* Returns the string value of the input without any space.
*/
function clean(input: undefined | string | null | number): string {
if (input == undefined) {
return "";
}
return `${input}`.replaceAll(" ", "");
}
function getLegendColor(product, prediction, nutrimentId) {
const cleanProduct = clean(product);
const cleanPrediction = clean(prediction);

console.log({ nutrimentId, cleanProduct, cleanPrediction });

if (cleanProduct === cleanPrediction) {
return "green";
}

if (cleanProduct === "" || cleanPrediction === "") {
return "orange";
}

if (cleanProduct !== cleanPrediction) {
return "red";
}
return undefined;
}

export const NutrimentCell = (props: NutrimentCellProps) => {
const {
nutrimentId,
Expand Down Expand Up @@ -64,13 +93,29 @@ export const NutrimentCell = (props: NutrimentCellProps) => {
/>
<br />
{displayOFFValue && (
<legend style={{ fontSize: 13, textAlign: "end" }}>
{productValue}
{productUnit}
<legend
style={{
fontSize: 13,
textAlign: "end",
}}
>
<span
style={{
color: getLegendColor(productValue, value, nutrimentId),
}}
>
{productValue}
</span>
<span
style={{
color: getLegendColor(productUnit, unit, nutrimentId),
}}
>
{productUnit}
</span>
</legend>
)}
</div>

{isValidUnit(unit) ? (
<select
style={{ width: 55 }}
Expand Down
18 changes: 5 additions & 13 deletions src/pages/nutrition/config.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,6 @@
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 = [
export const KNOWN_NUTRIMENTS = [
// Energy
"energy-kj",
"energy-kcal",
Expand Down Expand Up @@ -104,10 +96,10 @@ export const NUTRIMENTS_ORDER = [
"pantothenic-acid",
"silica",
"bicarbonate",
"Sulphate",
"Nitrate",
"Hydrogencarbonate",
"Nitrite",
"sulphate",
"nitrate",
"nydrogencarbonate",
"nitrite",
"potassium",
"chloride",
"calcium",
Expand Down
12 changes: 7 additions & 5 deletions src/pages/nutrition/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@ import { ErrorBoundary } from "../taxonomyWalk/Error";
import LinksToProduct from "./LinksToProduct";
import { NutrimentCell } from "./NutrimentCell";
import PictureSection from "./PictureSection";
import { NUTRIMENTS_ORDER } from "./config";
import { KNOWN_NUTRIMENTS } from "./config";
import Instructions from "./Instructions";

export default function Nutrition() {
const [partiallyFilled, setPartiallyFilled] = React.useState(false);
Expand Down Expand Up @@ -68,18 +69,19 @@ export default function Nutrition() {
}));
}, [insight]);

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

const notUsedNutriments = React.useMemo(
() => NUTRIMENTS_ORDER.filter((id) => !nutrimentsDetected.includes(id)),
[nutrimentsDetected],
() => KNOWN_NUTRIMENTS.filter((id) => !nutrimentsDisplayed.includes(id)),
[nutrimentsDisplayed],
);
return (
<React.Suspense>
<ErrorBoundary>
<Instructions />
<Stack direction="row">
<Box sx={{ width: "50%" }}>
<PictureSection
Expand Down Expand Up @@ -160,7 +162,7 @@ export default function Nutrition() {
</tr>
</thead>
<tbody>
{nutrimentsDetected.map((nutrimentId) => {
{nutrimentsDisplayed.map((nutrimentId) => {
const key100g = `${nutrimentId}_100g`;
const { value: value100g, unit: unit100g } =
values[key100g] ?? {};
Expand Down
6 changes: 3 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 { OFF_NUTRIMENTS_TO_IGNORE, UNITS } from "./config";
import { KNOWN_NUTRIMENTS, UNITS } from "./config";
import { NutrimentPrediction } from "./insight.types";
import { ROBOTOFF_API_URL } from "../../const";

Expand Down Expand Up @@ -32,7 +32,7 @@ export function structurePredictions(
Object.keys(predictions).forEach((key) => {
const id = key.split("_")[0]; // split 'energy-kj_100g' to only get 'energy-kj'

if (OFF_NUTRIMENTS_TO_IGNORE.includes(id)) {
if (!KNOWN_NUTRIMENTS.includes(id)) {
return;
}
if (!nurimentsIds.includes(id)) {
Expand All @@ -43,7 +43,7 @@ export function structurePredictions(
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)) {
if (!KNOWN_NUTRIMENTS.includes(id)) {
return;
}
if (!nurimentsIds.includes(id)) {
Expand Down
Loading