Spaces:
Sleeping
Sleeping
maribakulj
Merge branch 'main' into codex/fix-deployment-issue-with-hf_token-18yajz
ca901f3 unverified | title: Clafoutis MVP | |
| emoji: 📚 | |
| sdk: docker | |
| app_port: 7860 | |
| short_description: Prototype federated portal for cultural heritage resources. | |
| # Clafoutis | |
| Portail universel de recherche IIIF avec lecture dans Mirador et connecteurs extensibles. | |
| ## Objectif | |
| Cette application permet de : | |
| - rechercher des objets patrimoniaux dans plusieurs institutions ; | |
| - agréger et normaliser les résultats dans un format commun ; | |
| - afficher une galerie homogène de résultats ; | |
| - ouvrir directement les ressources dans Mirador à partir de leurs manifests IIIF ; | |
| - comparer plusieurs objets dans le même viewer ; | |
| - exposer les fonctions principales via une API REST ; | |
| - prévoir une couche MCP sans duplication de logique métier. | |
| ## Vision produit | |
| Le produit est structuré en trois couches : | |
| 1. **Découverte** : recherche fédérée multi-sources ; | |
| 2. **Lecture** : visualisation et comparaison dans Mirador ; | |
| 3. **Interopérabilité** : API interne normalisée et couche MCP optionnelle. | |
| Principe fondamental : | |
| > Mirador est la couche de lecture, jamais la couche de recherche. | |
| ## Périmètre MVP | |
| Le MVP doit inclure : | |
| - recherche fédérée sur un petit nombre de sources ; | |
| - schéma de données normalisé ; | |
| - galerie de résultats ; | |
| - ouverture simple et multiple dans Mirador ; | |
| - import manuel d’un manifest IIIF ou d’une URL de notice ; | |
| - description des sources disponibles et de leurs capacités ; | |
| - robustesse aux échecs partiels. | |
| Le MVP n’inclut pas : | |
| - compte utilisateur ; | |
| - annotation persistante ; | |
| - index mondial exhaustif ; | |
| - moissonnage global du web patrimonial ; | |
| - recherche OCR universelle. | |
| ## Stack technique | |
| ### Frontend | |
| - React | |
| - TypeScript | |
| - Tailwind CSS | |
| - TanStack Query | |
| - Zustand ou Redux Toolkit | |
| - Mirador | |
| ### Backend | |
| - Python 3.11+ | |
| - FastAPI | |
| - Pydantic | |
| - httpx async | |
| ### Déploiement | |
| - Docker | |
| - Hugging Face Spaces | |
| ## Architecture du projet | |
| ```text | |
| app/ | |
| frontend/ | |
| src/ | |
| components/ | |
| pages/ | |
| hooks/ | |
| store/ | |
| lib/ | |
| types/ | |
| backend/ | |
| app/ | |
| api/ | |
| services/ | |
| connectors/ | |
| models/ | |
| mcp/ | |
| utils/ | |
| config/ | |
| tests/ | |
| unit/ | |
| integration/ | |
| docs/ | |
| Dockerfile | |
| docker-compose.yml | |
| README.md | |
| ``` | |
| ## Concepts importants | |
| ### Schéma normalisé | |
| Toutes les sources externes sont transformées dans un schéma commun de type `NormalizedItem`. | |
| Le schéma normalisé est le cœur du projet : l’interface ne doit pas dépendre directement des payloads bruts des institutions. | |
| ### Connecteurs | |
| Chaque source est branchée via un connecteur indépendant. | |
| Chaque connecteur doit implémenter une interface commune, par exemple : | |
| - `search(query, filters, page, page_size)` | |
| - `get_item(source_id)` | |
| - `resolve_manifest(item_or_url)` | |
| - `healthcheck()` | |
| - `capabilities()` | |
| ### Succès partiel | |
| Si une source échoue, les autres doivent quand même répondre. | |
| Le moteur fédéré ne doit jamais échouer globalement à cause d’un seul connecteur. | |
| ## Fonctionnalités prévues | |
| - recherche simple multi-sources ; | |
| - filtres par institution, type, langue, période, disponibilité IIIF ; | |
| - galerie homogène ; | |
| - ouverture d’un résultat dans Mirador ; | |
| - comparaison de plusieurs manifests ; | |
| - import manuel d’URL ; | |
| - page Sources avec capacités déclaratives ; | |
| - couche MCP optionnelle. | |
| ## Endpoints backend prévus | |
| - `GET /api/health` | |
| - `GET /api/sources` | |
| - `POST /api/search` | |
| - `GET /api/item/{id}` | |
| - `POST /api/resolve-manifest` | |
| - `POST /api/import` | |
| ### Heuristiques MVP de `/api/import` | |
| Le connecteur générique `manifest_by_url` applique des heuristiques minimales et explicites : | |
| 1. **Manifest direct** : l’URL est considérée comme manifest si son chemin contient `manifest` | |
| (ou se termine par `manifest.json`). | |
| 2. **Notice -> manifest** : si l’URL ne ressemble pas à un manifest, le backend tente des suffixes | |
| courants, dans cet ordre : | |
| - `/manifest` | |
| - `/manifest.json` | |
| - `/iiif/manifest` | |
| - `/iiif/manifest.json` | |
| Ces heuristiques sont volontairement simples au MVP et seront enrichies par source aux lots | |
| connecteurs réels. | |
| ### Sécurité MVP import URL (validation + SSRF basique) | |
| `/api/import` applique une validation stricte avant résolution : | |
| - schémas autorisés : `http`, `https` uniquement ; | |
| - rejet explicite de `localhost`/hôtes locaux ; | |
| - rejet des IP privées/loopback/link-local/réservées/unspecified ; | |
| - rejet des hôtes DNS qui résolvent vers ces plages privées/locales. | |
| Limite connue MVP : cette protection SSRF reste basique et devra être durcie (allowlist, | |
| résolution DNS contrôlée, protections réseau infra) avant production. | |
| ## Outils MCP prévus | |
| - `search_items` | |
| - `get_item` | |
| - `resolve_manifest` | |
| - `open_in_mirador` | |
| - `list_sources` | |
| ## Installation locale | |
| ### Prérequis | |
| - Python 3.11+ | |
| - Node.js 20+ ou version définie dans le projet | |
| - npm, pnpm ou yarn | |
| - Docker (optionnel mais recommandé) | |
| ### Backend | |
| ```bash | |
| python -m venv .venv | |
| source .venv/bin/activate | |
| pip install -e '.[dev]' | |
| uvicorn app.main:app --app-dir app/backend --reload | |
| ``` | |
| ### Frontend | |
| ```bash | |
| cd app/frontend | |
| npm install --legacy-peer-deps | |
| npm run dev | |
| ``` | |
| Par défaut, le frontend appelle `http://localhost:8000`. | |
| Optionnel : | |
| ```bash | |
| VITE_API_BASE_URL=http://localhost:8000 npm run dev | |
| ``` | |
| ## Variables d’environnement | |
| Créer un fichier `.env` à partir de `.env.example`. | |
| Variables backend principales (préfixe `CLAFOUTIS_`) : | |
| ```env | |
| CLAFOUTIS_DEBUG=false | |
| CLAFOUTIS_APP_HOST=0.0.0.0 | |
| CLAFOUTIS_APP_PORT=7860 | |
| CLAFOUTIS_REQUEST_TIMEOUT_SECONDS=8 | |
| CLAFOUTIS_CORS_ALLOW_ORIGINS=["http://localhost:5173"] | |
| CLAFOUTIS_SERVE_FRONTEND=true | |
| CLAFOUTIS_FRONTEND_DIST_DIR=app/frontend/dist | |
| CLAFOUTIS_GALLICA_USE_FIXTURES=true | |
| CLAFOUTIS_BODLEIAN_USE_FIXTURES=true | |
| CLAFOUTIS_EUROPEANA_USE_FIXTURES=true | |
| CLAFOUTIS_EUROPEANA_API_KEY= | |
| # Hugging Face Spaces: HF_TOKEN est aussi accepté en fallback pour Europeana | |
| # (utile si votre secret Space est nommé HF_TOKEN). | |
| CLAFOUTIS_ENABLE_CAPABILITY_PROBING=true | |
| CLAFOUTIS_CAPABILITY_PROBE_USE_FIXTURES=true | |
| CLAFOUTIS_CAPABILITY_PROBE_TIMEOUT_SECONDS=2 | |
| CLAFOUTIS_CAPABILITY_PROBE_CACHE_TTL_SECONDS=300 | |
| ``` | |
| Pour le frontend en mode dev local (Vite séparé) : | |
| ```env | |
| VITE_API_BASE_URL=http://localhost:8000 | |
| ``` | |
| ## Packaging Docker (lot démo Hugging Face Spaces) | |
| Stratégie MVP : | |
| - image **unique** ; | |
| - build frontend React dans une étape Node ; | |
| - copie des assets `dist` dans l’image runtime Python ; | |
| - backend FastAPI sert API + assets frontend (SPA fallback) sur un **port unique** ; | |
| - mode fixtures activable par variables d’environnement (par défaut recommandé pour démo). | |
| ### Build image | |
| ```bash | |
| docker build -t clafoutis-mvp . | |
| ``` | |
| ### Run local (démo) | |
| ```bash | |
| docker run --rm -p 7860:7860 \ | |
| -e PORT=7860 \ | |
| -e CLAFOUTIS_GALLICA_USE_FIXTURES=true \ | |
| -e CLAFOUTIS_BODLEIAN_USE_FIXTURES=true \ | |
| -e CLAFOUTIS_EUROPEANA_USE_FIXTURES=true \ | |
| clafoutis-mvp | |
| ``` | |
| Puis ouvrir : | |
| - UI : `http://localhost:7860` | |
| - API health : `http://localhost:7860/api/health` | |
| ### Hugging Face Spaces (Docker) | |
| 1. Créer un Space de type **Docker**. | |
| 2. Pousser ce dépôt (avec `Dockerfile`) dans le Space. | |
| 3. Définir les variables du Space (Settings -> Variables), au minimum : | |
| - `PORT=7860` | |
| - `CLAFOUTIS_GALLICA_USE_FIXTURES=true` | |
| - `CLAFOUTIS_BODLEIAN_USE_FIXTURES=true` | |
| - `CLAFOUTIS_EUROPEANA_USE_FIXTURES=true` | |
| 4. Optionnel : ajouter `CLAFOUTIS_EUROPEANA_API_KEY` pour le mode live Europeana. | |
| Le backend accepte aussi `HF_TOKEN` en fallback si votre secret Space porte ce nom. | |
| Le point d’entrée est `scripts/start.sh`, qui démarre Uvicorn sur `HOST/PORT` compatibles Space Docker. | |
| ## Sources prévues pour le MVP | |
| - Gallica / BnF | |
| - Bodleian Digital | |
| - Europeana | |
| - connecteur générique `manifest-by-url` | |
| ## Connecteur Gallica (lot 5) | |
| ### Hypothèses de mapping `NormalizedItem` | |
| - `source_item_id` : ARK extrait des identifiants Gallica (`ark:/...`) ; | |
| - `id` global : `gallica:{source_item_id}` ; | |
| - `title` : premier champ `dc:title` disponible ; | |
| - `creators` : liste des `dc:creator` ; | |
| - `date_display` : premier `dc:date` ; | |
| - `object_type` : dérivé de `dc:type` via mapping simple (`manuscript`, `book`, `map`, `image`, `newspaper`, `other`) ; | |
| - `record_url` : premier `dc:identifier` ; | |
| - `manifest_url` : construit depuis l’ARK (`https://gallica.bnf.fr/iiif/{ark}/manifest.json`) ; | |
| - `institution` : `Bibliothèque nationale de France`. | |
| ### Stratégie de résolution de manifest | |
| 1. si `item.manifest_url` est déjà présent, il est renvoyé ; | |
| 2. sinon, extraction d’un ARK depuis `record_url` (ou URL fournie) ; | |
| 3. construction déterministe de l’URL IIIF manifest Gallica. | |
| ### Robustesse / mode fallback | |
| - Le connecteur tente un mode live SRU Gallica ; | |
| - pour éviter de casser la suite en environnement instable, un mode fixtures est disponible (`CLAFOUTIS_GALLICA_USE_FIXTURES=true` au MVP, valeur par défaut) ; | |
| - en cas d’échec live, le connecteur renvoie un succès dégradé avec données fixtures et `partial_failures` explicite. | |
| ### Limites connues (MVP) | |
| - le parsing SRU est volontairement minimal et basé sur un sous-ensemble Dublin Core ; | |
| - certains champs Gallica restent absents/incertains selon les notices ; | |
| - la détection fine des types documentaires sera améliorée aux lots suivants. | |
| ## Connecteurs Bodleian et Europeana (lot 6) | |
| ### Bodleian — mapping `NormalizedItem` | |
| - `source_item_id` : identifiant objet Bodleian ; | |
| - `id` global : `bodleian:{source_item_id}` ; | |
| - `title`, `creators`, `date_display` : extraits du payload source ou fixtures ; | |
| - `record_url` : URL notice Bodleian (`/objects/{id}/`) ; | |
| - `manifest_url` : prioritairement fourni, sinon construit via pattern | |
| `https://iiif.bodleian.ox.ac.uk/iiif/manifest/{id}.json` ; | |
| - `institution` : `Bodleian Libraries`. | |
| ### Europeana — mapping `NormalizedItem` | |
| - `source_item_id` : `id` Europeana ; | |
| - `id` global : `europeana:{source_item_id}` ; | |
| - `title`, `creators`, `date_display` : extraits du payload source ou fixtures ; | |
| - `record_url` : `guid` Europeana (ou URL item fixture) ; | |
| - `manifest_url` : | |
| - `edmIsShownBy` si manifest explicite ; | |
| - sinon fallback pattern `https://iiif.europeana.eu/presentation/{item_path}/manifest` ; | |
| - `institution` : `Europeana` ou institution partenaire fixture. | |
| ### Capacités et mode robustesse | |
| - les deux connecteurs implémentent `search`, `get_item`, `resolve_manifest`, `capabilities`, `healthcheck` ; | |
| - stratégie **fixture-first** activée par défaut au MVP (`CLAFOUTIS_BODLEIAN_USE_FIXTURES=true`, `CLAFOUTIS_EUROPEANA_USE_FIXTURES=true`) pour préserver la stabilité ; | |
| - en mode live, les erreurs réseau/API retombent en mode dégradé avec `partial_failures` explicites ; | |
| - Europeana live nécessite une clé API (`CLAFOUTIS_EUROPEANA_API_KEY`). | |
| ### Limites connues (MVP) | |
| - endpoints live Bodleian/Europeana restent best-effort et peuvent évoluer ; | |
| - mapping conservateur pour limiter les faux positifs ; | |
| - enrichissement sémantique (types, droits, OCR, langue fine) prévu après lot 6. | |
| ## Principes de développement | |
| - code modulaire ; | |
| - typage strict ; | |
| - séparation claire frontend / backend / connecteurs ; | |
| - aucune duplication de logique métier entre REST et MCP ; | |
| - Mirador utilisé uniquement comme couche de lecture ; | |
| - tests unitaires minimum sur les connecteurs ; | |
| - tests d’intégration minimum sur les endpoints critiques. | |
| ## Ordre de développement recommandé | |
| 1. socle backend ; | |
| 2. socle frontend ; | |
| 3. intégration Mirador minimale ; | |
| 4. import manuel ; | |
| 5. connecteurs réels ; | |
| 6. ranking, cache et robustesse ; | |
| 7. couche MCP ; | |
| 8. déploiement Docker / Hugging Face Spaces. | |
| ## État du projet | |
| Statut actuel : en cours de développement. | |
| ## Priorité actuelle | |
| - [ ] Backend socle | |
| - [ ] Frontend socle | |
| - [ ] Intégration Mirador | |
| - [ ] Import manuel | |
| - [ ] Connecteur Gallica | |
| - [ ] Connecteurs Bodleian et Europeana | |
| - [ ] MCP | |
| - [ ] Docker / déploiement HF Spaces | |
| ## Documentation | |
| Voir aussi : | |
| - `AGENTS.md` | |
| - `PLANS.md` | |
| - `docs/specs.md` | |
| ## Contribution | |
| Avant toute contribution : | |
| - lire `AGENTS.md` ; | |
| - respecter l’architecture du projet ; | |
| - ne pas dupliquer la logique métier ; | |
| - garder les connecteurs isolés ; | |
| - documenter clairement tout nouveau module. | |