État au 29 avril 2026. Document vivant : ouvre une PR pour proposer un ajout, ou réagis dans une issue pour discuter d'une priorité.
v0.2 livrée le 26 avril 2026 — Sprint 1 quasi complet (1.1 à 1.6), plus 2.3 (investigué) et 3.1 (24 tests). Le seul item Sprint 1 restant est 1.7 (highlight diacritiques-aware, faible valeur).
v0.3 livrée le 26 avril 2026 — Cap 500→2 000/source (5 002 → 20 171 records, ×4), Polytechnique ajoutée, taxonomie 33 → 74 disciplines Érudit-aligned, classifieur bilingue, audit a11y FastAPI, SEO (sitemap + JSON-LD Dataset). 1,04 % records non classés (vs 28,6 % au prototype, 0,02 % avec LLM en v0.2).
v0.4 livrée le 27 avril 2026 — Cap totalement levé (×6.7 → 123 824
records) ; harvest incrémental + suppressions OAI honorées (25 993
tombstones traités) ; toutes les sources passent à des prefixes riches
(dim / oai_etdms / uketd_dc) qui exposent une discipline qualifiée
par l'institution ; 3-pass classifier avec attribution de provenance
explicite (auth 60 % / rule 18 % / rule_abstract 6 % / llm 17 %).
0,21 % records non classés. Couverture : 16 sources québécoises,
toutes répondent (UQAM/TÉLUQ inclus).
v0.5 livrée le 27 avril 2026 — Migration Pagefind : MiniSearch (26 MB initial) → Pagefind chunks à la demande (~50 KB initial + 100-300 KB par requête). Abstracts re-indexés (1 500 chars). Stemming français + anglais natif. Excerpts surlignés natifs.
v0.5.1 livrée le 27 avril 2026 — Refactor : extraction de toute la
logique UI partagée (~780 lignes) dans web/common.js + adaptateurs
web/backends/{fastapi,pagefind}.js. L'a11y de web/index.html est
désormais automatiquement disponible sur web/static.html (fini le
backport). Click sur titre ouvre le modal détail (et non la source).
Légende
- Effort :
S≤ 4 h ·M½–2 jours ·Lune semaine et plus - Valeur : ✶ marginal · ✶✶ utile · ✶✶✶ change l'utilité du produit
- Statut : ⏳ à faire · 🟡 en cours · ✅ fait · 🚫 abandonné
Ce qui ferait passer le projet de démo à outil de travail pour un·e chercheur·se.
| # | Amélioration | Effort | Valeur | Statut |
|---|---|---|---|---|
| 1.1 | Vue détail par thèse — modal au clic d'une carte, abstract complet, tous les champs DC, lien vers le PDF source. | M | ✶✶✶ | ✅ 738434a |
| 1.2 | Export citation — bouton « Copier la citation » dans le détail (BibTeX, APA, RIS). | S | ✶✶✶ | ✅ 738434a |
| 1.3 | Auteur cliquable — clic sur un nom → filtre par auteur. Stockage : facette dédiée. | S | ✶✶✶ | ✅ 208a4bf |
| 1.4 | Discipline cliquable sur les cartes — clic sur la pill → sélectionne la facette. | S | ✶✶ | ✅ 208a4bf |
| 1.5 | Mode sombre — dark: Tailwind + toggle dans le header. Respect prefers-color-scheme. |
S | ✶✶ | ✅ 229ff0c |
| 1.6 | Bouton « Copier le lien » — visible quand des filtres sont actifs. URL est déjà sync, juste rendre le geste explicite. | S | ✶ | ✅ 229ff0c |
| 1.7 | Surlignage diacritiques-aware — <mark> couvre maintenant les variantes accentuées (« éducation » highlighte sur query « education » et inversement). Position-mapping NFD-aware dans common.js::highlight(). |
S | ✶ | ✅ v0.5.2 |
Le projet vit ou meurt sur la profondeur du corpus.
| # | Amélioration | Effort | Valeur | Statut |
|---|---|---|---|---|
| 2.1 | Lever le cap 500/source — re-harvester sans --max-per-source. Estimation : ~50 000 records, DB ~250 MB. |
S | ✶✶✶ | ✅ v0.4 (cap totalement levé · 123 824 records) |
| 2.2 | Migrer vers Pagefind — à 50k records l'index MiniSearch dépasse 30 MB. Pagefind charge des chunks à la demande, ~100–500 KB par session. | M | ✶✶✶ | ✅ v0.5 |
| 2.3 | Ajouter Érudit — oai.erudit.org, couvre revues + thèses. Le déclencheur initial du projet. Investigué : OAI-PMH ne sert que des articles de revues (aucun set thèses, dc:type=text); les thèses Érudit sont fédérées depuis Papyrus/Savoirs UdeS/Archipel — déjà moissonnés directement. Voir notes dans sources.yaml. |
M | ✶✶✶ | ✅ investigué |
| 2.4 | Étendre la taxonomie disciplinaire — passer de 33 à 60+ catégories (sous-disciplines). Re-classifier en batch. | M | ✶✶ | ✅ v0.3 (74 disciplines, Érudit-aligned) |
| 2.5 | Re-classifier l'historique quand classify.py évolue — bouton CLI classify-existing. |
S | ✶✶ | ⏳ |
| 2.6 | Dépôts canadiens hors Québec — UofT, UBC, McMaster, Dalhousie. OAI-PMH partout. Renomme en theses-canada ? |
L | ✶✶ | ⏳ |
| 2.7 | Fallback PDF text extraction — pour les records sans abstract, extraire les 500 premiers mots du PDF (institutions qui le permettent). Améliore la classif LLM. | L | ✶✶ | éthique |
| # | Amélioration | Effort | Valeur | Statut |
|---|---|---|---|---|
| 3.1 | Tests Python — pytest sur normalize.py (extraction année, type), classify.py (mots-clés). Ajouter à ci.yml. |
S | ✶✶ | ✅ e00c3b3 (24 tests) |
| 3.2 | Audit accessibilité — ARIA labels sur facettes/pagination, traversée clavier des résultats, contraste. Test lighthouse. | M | ✶✶ | ✅ v0.3 (FastAPI) + v0.5.1 (auto-applique aux 2 versions via common.js partagé) |
| 3.3 | i18n EN — toggle FR/EN dans le header. Interface uniquement, pas de traduction des résumés. | M | ✶ | ⏳ |
| 3.4 | OGP / cartes sociales — <meta> pour partages X/Bluesky/LinkedIn. Capture statique du site. |
S | ✶ | ⏳ |
| 3.5 | Sitemap.xml + JSON-LD Dataset — visibilité Google Scholar / Datasets. |
S | ✶✶ | ✅ v0.3 |
| 3.6 | Documentation interne (docs/) — schéma DB, format JSONL Gemini, règles de classif. |
M | ✶ | ⏳ |
| 3.7 | CONTRIBUTORS.md auto-généré — gh contributor list. |
S | ✶ | ⏳ |
| # | Amélioration | Effort | Valeur | Statut |
|---|---|---|---|---|
| 4.1 | Endpoint OAI-PMH ré-exposant le corpus enrichi — autres agrégateurs peuvent moissonner notre classification. Ironie : on devient une source. | M | ✶✶ | ⏳ |
| 4.2 | API JSON publique stable — /api/v1/search, /api/v1/sources. Doc OpenAPI. Hébergée Fly.io ou Cloudflare Workers + D1. |
L | ✶✶ | dépend de 4.3 |
| 4.3 | Déploiement Cloudflare Workers + D1 — backend serverless qui sert l'API et le frontend. Coût quasi-nul. | M | ✶ | ⏳ |
| 4.4 | Webhook de re-publication — quand un dépôt source met à jour un record, on reflète sous 24 h. | L | ✶ | infrastructure |
| 4.5 | Dataset HuggingFace — uploader le corpus normalisé comme dataset libre. | S | ✶✶ | doc seulement |
| # | Amélioration | Effort | Valeur | Statut |
|---|---|---|---|---|
| 5.1 | Recherche sémantique — embeddings sentence-transformer multilingue (LaBSE / paraphrase-multilingual-MiniLM), index FAISS / WASM, recherche par similarité. ~50 MB. | L | ✶✶✶ | scale |
| 5.2 | « Did you mean? » — suggestions de correction sur 0 résultat. Trigramme + dist. Levenshtein. | S | ✶✶ | ✅ v0.6 |
| 5.3 | Recherche par auteur fuzzy — variantes nom/prénom (« Jodoin, M. » vs « Maude Jodoin »). | M | ✶✶ | ⏳ |
| 5.4 | Compare 2+ disciplines / décennies — vue côte-à-côte, courbes temporelles. | M | ✶ | ⏳ |
| 5.5 | Graphique temporel — distribution annuelle par discipline (chart.js / svg natif). | S | ✶✶ | ✅ v0.6 (par décennie) |
| 5.6 | Sauvegarder une recherche — localStorage, retour facile aux dernières requêtes + recherches épinglées (« Mes recherches »). | S | ✶ | ✅ v0.6.2 |
| 5.7 | Détection de doublons — même thèse archivée à plusieurs endroits (rare mais existe). | M | ✶ | ⏳ |
| # | Amélioration | Effort | Valeur | Statut |
|---|---|---|---|---|
| 6.1 | Dockerfile + fly.toml — pour ceux qui veulent l'auto-héberger avec backend FastAPI. |
S | ✶ | ⏳ |
| 6.2 | deploy/systemd/ + Caddyfile — recettes self-host VPS Linux. |
S | ✶ | ⏳ |
| 6.3 | Monitoring de santé OAI — page /status qui ping chaque endpoint chaque 6 h, affiche un status board. |
M | ✶ | ⏳ |
| 6.4 | Export régulier sur archive.org — preservation des snapshots de la DB. | S | ✶✶ | ⏳ |
| 6.5 | Distribution DB hors LFS — DB publiée comme asset GitHub Release (zstd). Le quota LFS gratuit (1 Go) ne tenait pas le rythme une fois la DB à 666 Mo. | S | ✶✶ | ✅ v0.6.3 |
- PWA / offline-first — installable, fonctionne sans connexion une fois chargé. Cache
search.jsonvia Service Worker. - Mode chercheur·se connecté·e — sauvegarder ses thèses pertinentes, annoter, exporter une bibliographie. Demande auth → augmente la complexité d'un cran.
- Notifications nouvelle thèse — RSS / Atom feed des nouveaux records par discipline.
- Visualisation des co-auteurs — graphe par université, par décennie.
- API LLM publique pour Q&A sur le corpus — RAG sur les abstracts. Coûts variables.
1.1 Vue détail · 1.2 Export citation · 1.3 Auteur cliquable · 1.4 Discipline cliquable · 1.5 Mode sombre · 1.6 Copier le lien · 2.3 Érudit (investigué) · 3.1 Tests Python (24)
2.1 partiel (cap 2000) · 2.4 Taxonomie 33→74 · 3.2 a11y FastAPI · 3.5 Sitemap+JSON-LD
- 2.1 Cap totalement levé : 123 824 records (×6.7)
- Métadonnées riches : 14/16 sources sur
dim/oai_etdms/uketd_dcau lieu d'oai_dc - Authoritative discipline : 100 775 records avec discipline curée par l'institution
- 3-pass classifier : Pass 0 (auth) / Pass 1 (primary) / Pass 2 (abstract) avec provenance trackée
- Harvest incrémental + tombstones honorés (25 993 suppressions)
- 1.7 highlight, encore ⏳
- 2.2 Migration Pagefind — bundle initial ÷500
- Abstracts re-indexés (perdus en v0.3-v0.4 pour cause de taille)
- Stemming français + anglais natif
- Refactor majeur :
web/common.js(UI partagée) + 2 backends (fastapi.js/pagefind.js) - 780+ lignes de duplication JS éliminées
- A11y backportée automatiquement (3.2 désormais complet)
- UX : click sur titre ouvre le modal au lieu de naviguer
- 1.7 Surlignage diacritiques-aware —
<mark>couvre les variantes accentuées dans les deux sens
- 5.2 « Did you mean? » — Levenshtein-tolérant (1 typo / 4 chars), vocabulaire construit à partir des labels de facettes (discipline + dépôt) repliés NFD-lowercase. Suggestion cliquable injectée dans l'état vide.
- 5.5 Graphique temporel — barres horizontales par décennie, largeur proportionnelle au max. Click toggle filtre
year_min/year_max. Trié chronologiquement,aria-pressed+aria-label.
- Bug majeur — auteurs DSpace :
parse_dimprojetaitdc.contributor.authorverscontributorau lieu decreator, donc 100 % des records UdeM, Laval, Sherbrooke et Bishop's avaient un champ auteur vide (40 % du corpus). Fix dansparse_dim+ re-harvest--fulldes 4 sources DSpace : 71 307 records → 5 records sans auteur (0,0 %). - Direction : nouveau champ
advisors. Parsé depuisdc.contributor.advisor(DIM) et<etdms:contributor role="advisor">(ETDMS). Surface dans la carte (Direction : …) et le modal (rangée labellisée), inclus dans l'index Pagefind, BibTeXnote = {Sous la direction de …}, RISA2 - …. Couverture : ~70 400 records (UdeM 99,2 %, Sherbrooke 99,9 %, Laval 97,1 %, Bishop's 82,1 %). Sources EPrints/UKETD n'exposent rien via OAI. - Modal restructuré : grille
<dl>avec rangéesAuteur·rice / Direction / Provenancequi se replient quand vide. - Bandeau de mise à jour :
startUpdateWatcherdanscommon.jspollmeta.jsonàvisibilitychange+ toutes les 15 min ; affiche un bandeau orange « Une nouvelle version est disponible — Actualiser » avec rechargement 1-clic, dismissable. - Préséance discipline corrigée :
manual > auth > llm > rule_abstract > rule. L'upsert utilise maintenant un rang numérique au lieu d'une simple liste « préserve ». 1 524 records mal classés par le LLM (avant queauthoritative_disciplineexiste) corrigés viaauth+ 4 473 raffinements de provenance. Cas type : « Morphologie et cycle annuel de l'Ancolie du Canada » —Linguistique (llm)→Biologie (auth). - Index Pagefind unifié :
forceLanguage: "fr"au build pour éviter le partitionnement par langue (le frontend ne chargeait que le sous-indexfrau splash, masquant 26 420 records anglais ; le compteur d'en-tête disait 177 413 mais la recherche vide en retournait 150 993). - Graphique décennies : filtre les barres avec
n=0pour qu'une décennie sélectionnée fasse disparaître les autres au lieu d'afficher 16 slivers à 2 %. - Recherche debounced — 200 ms → 250 ms, vidage instantané (pas de délai en effaçant), short-circuit si la requête trim-égalise l'état actuel.
- Polish : suppression du clear-X natif WebKit qui chevauchait notre badge
esc; lien footer pointe versdata/theses.db(LFS) au lieu du défuntsearch.json; footer crédite Pagefind, plus MiniSearch ; tag de version v0.1 → v0.6. - Workflow Pages élargi à
web/**(auparavantweb/static.htmlseul, donc les modifscommon.jsne re-déployaient pas).
- Bug majeur — UKETD empty-payload : UQAM (et à degré moindre UQAC) émettait
<uketd_dc:uketddc/>vide pour ~80 % de ses records pré-2014, alors queoai_dcretournait le DC complet pour les mêmes identifiants. Notreparse_uketd_dcretournait un dc vide →normalize_recordretournaitNone→ record silencieusement écarté. - Fix dans
ingest_record: fallback per-record versoai_dcquand le préfixe configuré rend un payload vide. Coût : un appel HTTP supplémentaire uniquement pour les records autrement perdus. Compteurfallbackajouté aux stats par-source. - Récupération : UQAM 3 433 → 13 452 (+10 019 records), UQAC 3 450 → 3 470 (+20). Le record d'exemple
oai:archipel.uqam.ca:4005(« Les pratiques des enseignants d'expérience en milieu socio-économiquement faible », Maude Jodoin, 2010) est désormais ingéré. - Audit complet des 16 sources : probé chaque source pour détecter le même bug. Résultat — seuls UQAM et UQAC affectés (uketd_dc); UQAR, UQAT, INRS sont propres (uketd_dc rempli partout); UQO false alarm (les premiers IDs sont des tombstones); UQTR re-harvesté pour confirmer (
fallback=0, +7 records). Sourcesdimetoai_etdmsutilisent un autre chemin de code et ne sont pas touchées. - Comparaison OpenAlex : confirmé que notre couverture des thèses québécoises est plus complète que OpenAlex sur tous les dépôts. UQAM Archipel n'est même pas indexé comme Source dans OpenAlex — les 13 452 records UQAM sont essentiellement uniques à notre projet dans l'écosystème open data.
- Corpus total : 177 413 → 187 459 (+10 046).
- Bug majeur — McGill 100 % mal classé : McGill exposait
oai_dcqui collapse le diplôme à<dc:type>Thesis</dc:type>sans distinction master/doctoral. 55 589 records → 98 % marquésthesisalors que la majorité sont des mémoires (les premiers 675 records échantillonnés : 672 master, 3 doctoral). McGill bascule versoai_etdmsqui expose<degree><name>Doctor of Philosophy / Master of Engineering>+<degree><discipline>Department of …>. Le provider Blacklight de McGill annule une pageListRecordsentière surcannotDisseminateFormatquand un seul record refuse la sérialisation ETDMS — workaround :_bump_token_offsetparse le curseur:OFFSETdu resumption token et incrémente d'1 jusqu'à passer la fenêtre fautive. Résultat : 963 mem / 54 626 thes → 34 756 / 20 835, et 81 % de McGill ont maintenant une discipline autoritative (vs 0 %). - Bug parser —
qualificationlevelignoré :parse_uketd_dclisaitdegreeleveletqualificationnamemais ratait<uketdterms:qualificationlevel>(la VRAIE source dudoctoral/mastersà ÉTS, INRS, UQO, UQAR, UQAT, UQAC). Combiné avecdc:type="Thèse"ou"Mémoire ou thèse"ambigu, ça produisait des classifications uniformément fausses : ÉTS = 100 %memoire, INRS = 100 %thesis, UQO = 100 %thesis. Fix dansparsers.py+ ajout des tokens nus"doctoral","masters","doctor of"dansDOCTORAL_TYPE_SIGNALS/MASTER_TYPE_SIGNALS(normalize.py). ÉTS basculéoai_dc→uketd_dcdanssources.yaml. - Splits redressés après re-harvest
--full:- ÉTS : 3 397 mem / 0 thes → 2 285 / 1 112
- INRS : 0 / 2 867 → 1 644 / 1 223
- UQO : 0 / 1 108 → 665 / 443
- UQAT : 844 / 218 → 680 / 382 (engd doctorats récupérés)
- UQAC : 2 917 / 553 → 2 768 / 704 (engd)
- 9 autres sources audités explicitement, classifications confirmées correctes
- DB → GitHub Releases : la DB committée atteignait 666 Mo après les re-harvests, ce qui faisait sauter le quota Git LFS gratuit (1 Go) en 1-2 commits. Migration :
npm run db:release→ strip FTS5 + VACUUM (-34 %) →zstd -19(-83 %) →gh release create db-YYYY-MM-DD --latest. Wire size : 666 Mo → 76 Mo (88 % de bande passante en moins par build Pages).npm run db:fetchtélécharge + vérifie SHA-256 + décompresse ;harvester/db.pyreconstruit FTS5 transparemment au premierconnect()(~8 s). Workflow Pages déclenche maintenant surrelease: published. Historique git réécrit (git filter-repo) pour purger les 4,3 Go de blobs LFS accumulés. - Modal de signalement : bouton "Signaler un problème" dans le modal détail → menu déroulant (mauvaise discipline / mauvais type / métadonnées / autre) → ouvre une issue GitHub pré-remplie avec l'identifiant OAI, le titre, le dépôt, le type/discipline actuels, et un template de saisie. Zéro backend (deep-link
github.com/issues/new), fonctionne sur les deux variantes FastAPI et statique. - Lock contention sqlite : harvester
db.pypassetimeout=60sur la connexion (défaut Python = 5 s) — évite les! record error: database is lockedquand un harvest concurrent attend l'écrivain. CLAUDE.mdajouté à la racine pour donner aux instances Claude Code futures la mappe des 4 mondes de préfixes OAI, du skip-window McGill, et de la précédence du classifieur 3-pass.- Corpus total : 187 459 → 187 463.
- 3.6 Documentation interne (
docs/schema DB, format JSONL Gemini) - 2.7 Fallback PDF text extraction pour records sans abstract
- 3.3 i18n EN (toggle FR/EN)
- 2.6 Dépôts canadiens hors-Québec (rebrand
theses-canada?) - 5.1 Recherche sémantique (embeddings)
- 4.5 Dataset sur HuggingFace
- 4.1 OAI-PMH ré-exposant le corpus enrichi