5.3 KiB
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 installdans ce dossier (deux dépendances :@anthropic-ai/sdkpour l'extraction,fflatepour lire le zip d'un.odt). - Une clé d'API :
ANTHROPIC_API_KEY, ou un profil créé une fois avecant auth login. Sans clé, le script s'arrête tout de suite avec un message clair — il ne lit aucun fichier pour rien. --dry-runne 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/etout/sont ignorés par git.