Tofu/scripts/import-recipes
Repository files (latest commit first)
Filename Latest commit message Latest commit date
2026-09-18 01:41:02 +02:00
..
cli Start menu screen 2026-09-18 01:41:02 +02:00
extraction Start menu screen 2026-09-18 01:41:02 +02:00
odt Start menu screen 2026-09-18 01:41:02 +02:00
recipes Start menu screen 2026-09-18 01:41:02 +02:00
tests Start menu screen 2026-09-18 01:41:02 +02:00
webdav Start menu screen 2026-09-18 01:41:02 +02:00
fetchOdtFromNextcloud.js Start menu screen 2026-09-18 01:41:02 +02:00
importRecipes.js Start menu screen 2026-09-18 01:41:02 +02:00
package-lock.json Start menu screen 2026-09-18 01:41:02 +02:00
package.json Start menu screen 2026-09-18 01:41:02 +02:00
README.md Start menu screen 2026-09-18 01:41:02 +02:00

import-recipes — amorçage de la bibliothèque de recettes

Outil jetable, lancé une fois à la main : il lit un dossier de fichiers .odt et en sort un JSON par recette, plus l'index que l'appli consomme.

C'est le seul endroit du projet où Node et npm sont autorisés. L'appli n'en dépend jamais : elle lit les fichiers produits, pas ce script. Une fois la bibliothèque importée, ce dossier peut rester à dormir.

Prérequis

  • Node 22 ou plus récent, et npm install dans ce dossier (deux dépendances : @anthropic-ai/sdk pour l'extraction, fflate pour lire le zip d'un .odt).
  • Une clé d'API : ANTHROPIC_API_KEY, ou un profil créé une fois avec ant auth login. Sans clé, le script s'arrête tout de suite avec un message clair — il ne lit aucun fichier pour rien.
  • --dry-run ne demande aucune clé.

Usage

npm install
node importRecipes.js --source ~/Documents/Recettes --dry-run   # lecture seule, gratuit
node importRecipes.js --source ~/Documents/Recettes --limit 2   # deux recettes, pour voir
node importRecipes.js --source ~/Documents/Recettes             # tout
Option Effet
--source <dossier> dossier plat contenant les .odt (obligatoire)
--out <dossier> où écrire (défaut : out)
--dry-run lit les .odt et s'arrête là : aucun appel au modèle, rien d'écrit
--limit N n'importe que les N premiers fichiers

Une ligne par fichier s'affiche pendant le passage, puis un récapitulatif et la liste des échecs. Un fichier illisible n'interrompt jamais le passage : il est consigné et on continue. Le code de sortie vaut 1 si au moins un fichier a échoué, 0 sinon.

Le script écrit au fur et à mesure : un passage interrompu garde ce qui a déjà été payé.

Ce que ça produit

out/
  recipes/<slug>.json     une recette, vocabulaire schema.org
  index.json              { generatedAt, recipes: [{ id, name, ingredientNames }] }

out/index.json a exactement la forme que l'appli attend : il se dépose tel quel dans le dossier Tofu/ du Nextcloud, à côté de out/recipes/. Sans Nextcloud, son contenu peut aussi remplacer celui de app/const/RECIPE_INDEX_FIXTURE.js.

La règle qui compte

recipeIngredient[i] est la ligne telle qu'elle est écrite dans l'ODT : ni reformulée, ni corrigée, ni convertie. C'est elle qui sera affichée et imprimée, donc elle garde les mots du foyer.

Le découpage structuré vit à côté, dans tofu:parsedIngredients, et n'existe que pour cumuler une liste de courses et pour la recherche. parsedIngredients[i].raw est identique à recipeIngredient[i] — même ordre, même longueur, toujours (alignParsedIngredients le garantit après coup, quoi que le modèle ait répondu).

Quand la ligne est imprécise, le découpage reste imprécis : on garde l'unité telle qu'écrite (poignée, filet, pincée) ou on met quantity: null, avec imprecise: true. Jamais de conversion en grammes, jamais de nombre inventé — un nombre inventé se retrouverait additionné dans une liste de courses comme si quelqu'un l'avait mesuré. La règle est écrite noir sur blanc dans le prompt (extraction/EXTRACTION_RULES.js), avec des exemples, et un test vérifie qu'elle y est toujours.

Si le modèle rend un découpage décalé, réécrit ou incomplet, la ligne brute gagne et l'entrée fautive est remplacée par une entrée ouvertement imprécise — le récapitulatif signale alors [n ingredient lines realigned].

Pourquoi des clés tofu:

recipeIngredient, recipeInstructions, recipeYield viennent du vocabulaire schema.org. Le découpage et la trace d'import n'en font pas partie : ils sont préfixés tofu:parsedIngredients et tofu:source. Le fichier dit ainsi lui-même ce qui est standard et ce qui est à nous.

Ce que ça coûte

Modèle claude-opus-5, tarif public 5 par million de tokens en entrée, 25 en sortie. Une recette d'une page, c'est de l'ordre de 2 000 tokens en entrée (les règles d'extraction comptent pour l'essentiel) et 1 000 en sortie, réflexion comprise : grossièrement 3 à 6 centimes par recette, soit quelques euros pour une bibliothèque entière. Le passage affiche les tokens consommés à la fin.

Commencer par --dry-run, puis --limit 2 : de quoi vérifier la mise en forme avant de payer le reste.

Tests

npm test        # node --test, aucune dépendance de test

Chaque fonction pure a son test. extractOdtText est testée sur un vrai fichier .odt (tests/sample-recipe.odt), pas sur une chaîne inventée ; tests/buildSampleOdt.py le régénère si le document d'exemple doit changer.

En option : récupérer les .odt depuis le Nextcloud

NEXTCLOUD_APP_PASSWORD=... node fetchOdtFromNextcloud.js \
  --server https://cloud.exemple.fr --user marie --folder Recettes --out odt

Télécharge les .odt d'un dossier (PROPFIND puis GET) pour que importRecipes.js --source odt les lise ensuite. Copier les fichiers à la main fait aussi bien l'affaire. Le mot de passe attendu est un mot de passe d'application, jamais celui du compte.

Notes

  • Les messages de la console sont en anglais, comme le reste du code ; seuls les textes des recettes — ceux qui s'affichent et s'impriment — sont en français.
  • node_modules/ et out/ sont ignorés par git.