Acter la liste d'envies dans CLAUDE.md et nettoyer IDEES.md

Retire d'IDEES.md la section « Pour plus tard — livres souhaités », désormais
implémentée, et acte dans CLAUDE.md les trois décisions structurantes : table
dédiée pour la liste d'envies, SRU BnF par bib.author pour la bibliographie,
export en .txt et .csv.

Consigne dans CLAUDE.md ce que la source renvoie réellement (Werber 203 notices
-> 51 œuvres, Zola 2692 annoncées -> 87 œuvres sur 200 lues) et ce que le
rapprochement par titre rate, l'interface énonçant la même limite à l'écran.

Deux points relevés en passant et laissés à IDEES.md : la ligature « œ » que
NormalisationTexte ne réduit pas à « oe » (L'Œuvre de Zola y échappe, et le
défaut touche aussi la recherche du catalogue), et le plafond de 200 notices.

Corrige au passage une ponctuation bancale du bandeau de bibliographie, et
restreint le BOM UTF-8 au seul CSV.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
mathieu
2026-08-18 14:03:29 +02:00
co-authored by Claude Opus 5
parent 8b8fe3be6e
commit 231b0b2a11
2 changed files with 186 additions and 9 deletions
+161
View File
@@ -34,6 +34,9 @@ Application self-hosted de gestion de bibliothèque personnelle, à héberger su
| Statut de lecture | **Par utilisateur**, pas commun (décidé le 2026-08-17, après la phase 3) | Le livre est commun, sa lecture est personnelle : deux membres du foyer lisent le même exemplaire à des rythmes différents. Sort `Statut` de `Livre` vers une table dédiée |
| Prêts | **Communs au foyer**, jamais filtrés par utilisateur ; un seul prêt ouvert par livre, garanti par index unique partiel | Un livre absent l'est pour tout le monde, et n'importe qui doit pouvoir noter son retour. Voir « Prêts » |
| Auteurs | **Table dédiée** avec nom normalisé, remplaçant le champ texte libre | Nécessaire au regroupement par auteur et à la recherche insensible aux accents. Regroupement **automatique seulement quand c'est sûr**, sinon proposé à l'utilisateur |
| Liste d'envies | **Table dédiée `LivreSouhaite`**, personnelle (par `YNH_USER`), jamais dans `Livres` | Un livre souhaité n'est pas possédé. Dans `Livres`, il entrerait dans le catalogue, les compteurs et les prêts, et il faudrait répéter « et qui n'est pas souhaité » à chaque lecture. Voir « Liste d'envies » |
| Bibliographie par auteur | **SRU BnF, index `bib.author`**, avec post-filtre obligatoire sur l'auteur réel de la notice | Vérifié le 2026-08-18. `all` rapproche les mots sur l'ensemble des auteurs d'une notice : sans post-filtre, « Émile Zola » remonte l'œuvre de sa fille. Voir « Bibliographie par auteur » |
| Export de la liste d'envies | **`.txt` et `.csv`**, produits côté serveur, sans dépendance | Les deux usages d'IDEES.md diffèrent : le texte s'emporte en librairie, le CSV s'ouvre dans un tableur. Voir « Export » |
| Ebooks | **Fiches uniquement**, pas de stockage de fichiers | Inventaire, pas hébergement. Évite l'espace disque YunoHost, les sauvegardes lourdes, et garde le cache hors-ligne léger |
| Architecture serveur | **x86_64** → publish `linux-x64` | Serveur PC/VPS confirmé par l'utilisateur |
| Production des binaires | **Compilation locale + release manuelle**, via un script réutilisable en CI plus tard | Ne pas se bloquer sur l'outillage ; Gitea Actions nécessiterait un runner, non vérifié |
@@ -367,6 +370,18 @@ Pret
├── DateRetour (UTC, nullable — NULL tant que non rendu)
└── UNIQUE (LivreId) WHERE DateRetour IS NULL
un seul prêt ouvert par livre ; les prêts clos se répètent librement
LivreSouhaite (la liste d'envies est PERSONNELLE — voir sa section)
├── Id
├── Utilisateur (YNH_USER — une FRONTIÈRE, contrairement à AjoutePar)
├── Titre
├── TitreNormalise (clé d'ŒUVRE : « / » et « : » coupés — CleOeuvre)
├── Auteur (texte libre — PAS de FK vers Auteur, volontairement)
├── AuteurNormalise (jamais NULL, sinon l'unicité ci-dessous ne tient pas)
├── Editeur / Annee / Isbn / CoverUrl / Note
├── DateAjout
└── UNIQUE (Utilisateur, TitreNormalise, AuteurNormalise)
une œuvre par personne ; deux personnes peuvent souhaiter le même livre
```
Garder `Pret` comme table séparée (pas un champ sur `Livre`) pour conserver l'historique complet des prêts passés, pas juste l'état actuel.
@@ -465,6 +480,152 @@ livre sorti depuis six mois qu'on avait oublié, pas celui prêté hier.
(`ServiceLivresApi.EnUtc`). Sans cela le prêt se décalerait d'un jour pour la moitié du globe.
Vérifié en exécution : saisie du 12/08 → `2026-08-11 22:00` en base (CEST).
## Liste d'envies — implémentée le 2026-08-18
### Une table à part, et non un statut de plus
`LivreSouhaite` ne dépend pas de `Livre`. Le choix se joue sur un invariant, pas sur le confort :
un souhait logé dans `Livres` serait entré **mécaniquement** dans le catalogue, dans les
compteurs, dans les prêts et dans le cache hors-ligne, et il aurait fallu ajouter « et qui n'est
pas souhaité » à **chaque** lecture. Un invariant qu'on réécrit à chaque requête finit par être
oublié quelque part. Ici les deux tables ne se croisent pas — vérifié en exécution : une envie
n'apparaît ni au catalogue, ni dans la recherche, ni dans le compteur de livres d'un auteur.
C'est aussi la seule forme qui accepte une envie **sans édition arrêtée** : on souhaite une
œuvre, on possède un exemplaire. Un souhait n'a donc ni `Format`, ni prêt, et son ISBN est
facultatif.
```
LivreSouhaite (la liste d'envies est PERSONNELLE)
├── Id
├── Utilisateur (YNH_USER — ⚠️ une vraie FRONTIÈRE, voir ci-dessous)
├── Titre
├── TitreNormalise (clé d'ŒUVRE : sous-titre coupé — voir CleOeuvre)
├── Auteur (texte libre, PAS une FK vers Auteur)
├── AuteurNormalise (jamais NULL : SQLite tient deux NULL pour distincts)
├── Editeur / Annee / Isbn / CoverUrl / Note
├── DateAjout
├── INDEX (Utilisateur)
└── UNIQUE (Utilisateur, TitreNormalise, AuteurNormalise)
```
### `Utilisateur` est une frontière, contrairement à `Livre.AjoutePar`
C'est le point à ne pas confondre, les trois portées du projet coexistant désormais :
| Donnée | Portée | Filtre-t-on dessus ? |
|---|---|---|
| Catalogue (`AjoutePar`) | **commune** au foyer | **jamais** — ce serait un bug de conception |
| Prêt | **commun** au foyer | jamais |
| Statut de lecture | **personnel** | oui, sur l'appelant |
| **Liste d'envies** | **personnelle** | **oui, toujours, sans exception** |
Aucun point d'entrée n'accepte de nom d'utilisateur. Supprimer l'envie d'un autre renvoie **404**,
pas 403 : l'inexistence et l'appartenance à autrui sont volontairement indiscernables. Le sens
même de la fonctionnalité est de préparer un cadeau sans que l'autre le voie venir.
**L'auteur reste du texte libre**, pas une FK vers `Auteur` : cette table décrit qui est *dans la
bibliothèque*. Y insérer les auteurs souhaités les ferait apparaître sur `/auteurs` avec
« 0 livre » — et trahirait la liste d'envies de son propriétaire à tout le foyer.
### Export — `.txt` et `.csv`, aucun des deux ne demande de dépendance
IDEES.md décrit deux usages qui n'appellent pas le même fichier :
| Format | Usage | Forme |
|---|---|---|
| `.txt` | l'emporter en librairie | groupé **par auteur**, lisible tel quel sur un téléphone |
| `.csv` | le partager avant un anniversaire | tableau, pour se répartir les achats dans un tableur |
Deux points à ne pas « corriger » :
- **Séparateur point-virgule**, pas la virgule. Le CSV n'a qu'un travail — s'ouvrir dans un
tableur — et Excel en locale française attend `;`. Un fichier à virgules atterrit entièrement
dans la colonne A. La collection est francophone : c'est ce cas-là qu'il faut servir.
- **BOM UTF-8 en tête du CSV uniquement.** Sans lui Excel affiche « Émile Zola », soit à peu
près chaque ligne d'un fonds français. Le `.txt` s'en passe : certains lecteurs simples
l'affichent comme un caractère parasite.
Le fichier est produit **côté serveur** (`Results.File` pose un `Content-Disposition:
attachment`). ⚠️ Le lien de téléchargement **doit** porter l'attribut `download` : sans lui, le
routeur Blazor intercepte le clic comme une navigation interne et affiche sa page « introuvable »
au lieu de télécharger. Vérifié dans le navigateur.
## Bibliographie par auteur — SRU BnF, validé le 2026-08-18
Le déclencheur décrit dans IDEES.md : depuis un auteur déjà présent, voir tout ce qu'il a écrit,
les livres possédés grisés, et marquer les autres comme souhaités.
```
https://catalogue.bnf.fr/api/SRU?version=1.2&operation=searchRetrieve
&query=bib.author all "{nom}"&recordSchema=dublincore
&maximumRecords=100&startRecord={1|101}
```
L'index **`bib.author`** fonctionne, sans clé, en ~0,85 s par page. Deux pages de 100 sont
lancées **en parallèle** (200 notices au plus).
### ⚠️ Deux filtres sont indispensables, et le second est critique
Sans eux, l'écran ment à l'utilisateur.
1. **Type de document.** L'index auteur ne distingue pas un livre d'un livre audio, d'un jeu ou
d'un spectacle. Sur 200 notices de Werber : 180 imprimés, 13 enregistrements sonores, 3 images
animées, 2 jeux, 1 spectacle. Liste **positive** (`texte imprimé`, `ressource électronique`) :
un type inconnu est écarté.
2. **Auteur réel de la notice.** `all` exige que tous les mots soient présents — mais sur
l'**ensemble** des auteurs d'une notice. Un livre signé d'un « Émile » quelconque et d'un
« Zola » quelconque remonte donc aussi. Mesuré sur 200 notices d'« Émile Zola » : **67 écartées**,
dont toute l'œuvre de sa fille **Denise Le Blond-Zola** (*Émile Zola raconté par sa fille*),
qui parle de lui sans qu'il l'ait écrite. On exige donc qu'un `dc:creator` nettoyé corresponde
au nom demandé au sens de `RapprochementAuteurs`.
**Coût assumé du second filtre** : les adaptations dont l'auteur n'est pas le scénariste
disparaissent (les BD tirées de Werber sont signées Corbeyran — 17 notices sur 200). C'est le bon
compromis : afficher l'œuvre d'un tiers sous le nom de l'auteur est une erreur visible et gênante,
en omettre une adaptation ne l'est pas.
### Ce que ça donne réellement
| Auteur | Notices annoncées | Lues | Retenues | **Œuvres distinctes** |
|---|---|---|---|---|
| Bernard Werber | 203 | 200 | 166 | **51** |
| Émile Zola | 2692 | 200 | 118 | **87** |
| Amélie Nothomb | 277 | 200 | 169 | **63** |
Le regroupement des rééditions est ce qui rend l'écran utilisable : sans lui, « Les fourmis »
occupe onze lignes. Le représentant retenu est la notice **la plus ancienne** (l'édition
originale situe l'œuvre dans la carrière), mais l'ISBN est pris sur n'importe quelle notice qui
en porte un — les éditions anciennes n'en ont souvent pas.
⚠️ **Le plafond de 200 notices est un extrait pour les auteurs très réédités** (Zola en annonce
2692). L'interface **doit** le dire (`BibliographieDto.Tronquee`) : une bibliographie partielle
présentée comme complète ferait croire qu'un livre n'existe pas.
### Le rapprochement se fait par TITRE, pas par ISBN — et il rate des choses
**Pourquoi pas l'ISBN** : il désigne une *édition*, pas une œuvre. Le poche, le grand format et
la réédition portent trois ISBN pour le même roman — mesuré : 183 notices de livres de Werber
pour 51 œuvres, trois éditions par œuvre en moyenne. Comparer les ISBN répondrait « vous ne
l'avez pas » à propos d'un livre posé sur l'étagère. L'ISBN ne sert donc qu'en **confirmation**.
`CleOeuvre` coupe la mention de responsabilité (` / `) puis le sous-titre (` : `) et met à plat.
C'est ce qui réunit « Les thanatonautes », « Les thanatonautes : roman » et
« Les thanatonautes / Bernard Werber ». **Ce qu'elle rate**, et qu'il ne faut pas prétendre
autrement :
- un titre **retraduit ou changé** n'est pas rapproché du tout ;
- une œuvre possédée **à l'intérieur d'une intégrale** n'est pas repérée ;
- un **tome** n'est pas rapproché de son recueil (`Troisième humanité``Troisième humanité. Tome 1`) ;
- deux œuvres partageant leur **titre principal** sont confondues (risque borné : la comparaison
n'a lieu qu'**à auteur donné**, jamais sur tout le catalogue) ;
- la **ligature `œ`** n'est pas réduite à `oe`*L'Œuvre* de Zola échappe au rapprochement
(limite héritée de `NormalisationTexte`, voir IDEES.md).
Conséquence de conception : l'écran **grise** ce qu'il reconnaît et ne **masque jamais** de ligne.
Un faux négatif se voit et se corrige d'un coup d'œil ; une ligne masquée à tort serait invisible.
L'interface énonce cette limite en bas de liste plutôt que de laisser l'utilisateur la découvrir.
## Interface — décisions actées le 2026-08-18
Retours d'usage d'`IDEES.md`, appliqués et donc retirés de ce fichier-là.
+25 -9
View File
@@ -31,18 +31,34 @@ Correction envisagée : une fois `dc:creator` nettoyé, si le titre **se termine
---
## Pour plus tard — livres souhaités
## Recherche : la ligature « œ » n'est pas réduite à « oe »
Idée de fonctionnalité, **hors périmètre v1**, à ne pas implémenter sans décision explicite.
Repéré le 2026-08-18 en implémentant la liste d'envies. `NormalisationTexte.Normaliser`
décompose en **NFD**, qui sépare les accents mais laisse les ligatures intactes — seul NFKD les
défait. Conséquence : `L'Œuvre` et `L'oeuvre` ne se rencontrent jamais.
**La liste d'envies est personnelle**, pas commune : chacun la sienne, comme le statut de lecture. C'est le pendant naturel de la décision actée sur les statuts.
Ce n'est pas propre à la liste d'envies : la même fonction alimente la **recherche du catalogue**
et la **clé unique des auteurs**. Zola a écrit *L'Œuvre*, que la BnF orthographie avec la
ligature ; un utilisateur qui tape « oeuvre » ne le trouvera pas, et ne verra pas non plus le
livre grisé dans la bibliographie.
**Export de la liste d'envies** souhaité — pour l'emporter en librairie ou la partager avant un anniversaire. Format à choisir le moment venu ; du texte simple ou du CSV suffira probablement, et reste lisible partout sans dépendance.
Correction envisagée : passer en NFKD, ou traiter explicitement `œ`/`Œ` et `æ`/`Æ`. **Non fait
volontairement** — changer cette fonction déplace deux invariants en base (les colonnes
normalisées et l'index unique `CleRegroupement`). `ServiceRenormalisation` sait recalculer les
colonnes au démarrage, mais la fusion d'auteurs que la nouvelle règle provoquerait mérite d'être
regardée avant. Documenté par un test (`CleOeuvreTests.Ne_reduit_pas_la_ligature_oe`) pour que
la limite reste visible.
Pouvoir suivre les livres qu'on aimerait acquérir. Le déclencheur serait le regroupement par auteur : en cliquant sur un auteur déjà présent dans la bibliothèque (exemple donné : Bernard Werber), afficher **sa bibliographie complète**, avec les ouvrages déjà possédés grisés — et la possibilité de marquer les autres comme souhaités.
---
Ce que ça impliquerait, à évaluer le moment venu :
## Bibliographie : ce qui reste à creuser
- Une source pour les bibliographies par auteur. La BnF sait interroger par auteur, mais l'ordre et l'exhaustivité restent à vérifier ; OpenLibrary expose les œuvres liées à un auteur.
- Un moyen d'identifier un auteur de façon stable, donc probablement une **table `Auteur`** plutôt que le champ texte actuel — c'est le changement de modèle le plus lourd de cette idée.
- Un statut « souhaité » distinct des statuts de lecture existants, ou une entité séparée pour ne pas mélanger inventaire et liste d'envies.
La bibliographie par auteur est implémentée (voir `CLAUDE.md`). Deux pistes non traitées :
- **Plafond de 200 notices.** Suffisant pour un auteur contemporain (Werber : 203, Nothomb : 277),
très insuffisant pour un classique (Zola : 2692). L'écran le dit, mais on pourrait charger les
pages suivantes à la demande plutôt que d'annoncer un extrait.
- **OpenLibrary en second rideau.** La BnF ne connaît pas les auteurs étrangers non traduits ;
l'écran affiche alors une liste vide et l'explique. OpenLibrary expose les œuvres d'un auteur
(`/authors/{id}/works.json`) et pourrait prendre le relais — à ne faire que si le cas se
présente réellement en usage.