124 lines
5.3 KiB
Markdown
124 lines
5.3 KiB
Markdown
# 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
|
|
|
|
```sh
|
|
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
|
|
|
|
```sh
|
|
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
|
|
|
|
```sh
|
|
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.
|