maribakulj commited on
Commit
0532e96
·
unverified ·
1 Parent(s): 3c726a6

Update CLAUDE.md with formatting and content changes

Browse files
Files changed (1) hide show
  1. CLAUDE.md +227 -81
CLAUDE.md CHANGED
@@ -1,20 +1,43 @@
1
- Scriptorium AI — Instructions permanentes pour Claude Code
2
- Version 2.0 — mise à jour Sprint 2
3
 
4
- 1. Contexte du projet
5
- Scriptorium AI est une plateforme générique de génération d'éditions savantes augmentées
 
 
 
6
  pour documents patrimoniaux numérisés : manuscrits médiévaux, incunables, cartulaires,
7
  archives, chartes, papyri — tout type de document, toute époque, toute langue.
 
8
  Pipeline général :
9
- images sources → ingestion → normalisation → analyse Google AI → JSON maître
10
- → passes dérivées → ALTO / METS / Manifest IIIF → interface web → validation humaine
11
- Premier démonstrateur : Beatus de Saint-Sever (BnF Latin 8878, manuscrit enluminé,
 
12
  latin carolingien, XIe siècle). Le Beatus est un profil parmi d'autres — pas un cas spécial.
13
 
14
- 2. Stack technique
15
- ComposantTechnologieBackendPython 3.11+, FastAPI, UvicornValidationPydantic v2 (JAMAIS v1)Base de donnéesSQLite via SQLAlchemy 2.0 async + aiosqliteIAGoogle AI — provider sélectionnable (section 9)SDK Googlegoogle-genai (PAS google-generativeai — paquet différent)XMLlxmlImagesPillow (PIL)HTTP clienthttpx (téléchargement images IIIF)Testspytest, pytest-cov, pytest-asyncioFrontendReact + Vite, TypeScript, Tailwind CSS (sprint 4+)HébergementHuggingFace Spaces (Docker) + HF Datasets
16
- pyproject.toml dépendances exactes
17
- toml[project]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
18
  name = "scriptorium-ai"
19
  version = "0.1.0"
20
  requires-python = ">=3.11"
@@ -43,8 +66,13 @@ dev = [
43
  [tool.pytest.ini_options]
44
  testpaths = ["tests"]
45
  asyncio_mode = "auto"
 
 
 
46
 
47
- 3. Arborescence du repo — structure canonique
 
 
48
  scriptorium-ai/
49
 
50
  ├── CLAUDE.md ← CE FICHIER — ne pas modifier sans instruction
@@ -144,10 +172,16 @@ scriptorium-ai/
144
 
145
  └── infra/
146
  └── Dockerfile
 
 
 
 
 
147
 
148
- 4. Modèles de données — schémas Pydantic canoniques
149
- 4.1 CorpusProfile (corpus_profile.py)
150
- pythonfrom enum import Enum
 
151
  from pydantic import BaseModel, ConfigDict, Field
152
 
153
  class LayerType(str, Enum):
@@ -189,8 +223,12 @@ class CorpusProfile(BaseModel):
189
  prompt_templates: dict[str, str] # {"primary": "prompts/.../v1.txt"}
190
  uncertainty_config: UncertaintyConfig
191
  export_config: ExportConfig
192
- 4.2 PageMaster (page_master.py)
193
- pythonfrom datetime import datetime
 
 
 
 
194
  from typing import Any, Literal
195
  from pydantic import BaseModel, ConfigDict, Field, field_validator
196
 
@@ -292,8 +330,12 @@ class PageMaster(BaseModel):
292
  extensions: dict[str, Any] = {} # données spécifiques au profil
293
  processing: ProcessingInfo | None = None
294
  editorial: EditorialInfo = EditorialInfo()
295
- 4.3 AnnotationLayer (annotation.py)
296
- pythonclass LayerStatus(str, Enum):
 
 
 
 
297
  PENDING = "pending"
298
  RUNNING = "running"
299
  DONE = "done"
@@ -319,10 +361,16 @@ class ModelConfig(BaseModel):
319
  supports_vision: bool
320
  last_fetched_at: datetime
321
  available_models: list[dict] = []
 
 
 
 
 
322
 
323
- 5. Exemple complet d'un master.json valide
324
  Cet exemple est la référence. Tout master.json produit doit avoir cette forme.
325
- json{
 
 
326
  "schema_version": "1.0",
327
  "page_id": "beatus-lat8878-0013r",
328
  "corpus_profile": "medieval-illuminated",
@@ -416,28 +464,40 @@ json{
416
  "notes": []
417
  }
418
  }
 
 
 
 
 
419
 
420
- 6. Règles absolues NE JAMAIS ENFREINDRE
421
- R01 — Zéro logique hardcodée par corpus
422
- python# ❌ INTERDIT
423
  if profile_id == "medieval-illuminated":
424
  process_iconography()
425
 
426
  # ✅ CORRECT
427
  if "iconography_detection" in corpus_profile.active_layers:
428
  process_iconography()
429
- R02 — Le JSON maître est la source canonique
 
 
430
  Toutes les sorties (IIIF, ALTO, METS) sont générées depuis PageMaster.
431
  Jamais depuis ai_raw.json directement.
432
- R03 — Convention bbox [x, y, width, height] UNIQUEMENT
433
- python# INTERDIT coordonnées de coins opposés
 
 
434
  bbox = [x1, y1, x2, y2]
435
 
436
  # ✅ CORRECT — origine + dimensions
437
  bbox = [x, y, x2 - x1, y2 - y1]
 
438
  Pixels entiers absolus dans l'image. Width et height > 0. Toujours validé par Pydantic.
439
- R04 — Prompts dans des fichiers versionnés, jamais dans le code
440
- python# INTERDIT
 
 
441
  prompt = f"Tu analyses un {profile.label}. Retourne ce JSON..."
442
 
443
  # ✅ CORRECT
@@ -445,8 +505,11 @@ prompt = load_and_render_prompt(
445
  corpus_profile.prompt_templates["primary"],
446
  {"profile_label": profile.label, ...}
447
  )
448
- R05 — Double stockage obligatoire des réponses IA
449
- python# ❌ INTERDIT — un seul fichier
 
 
 
450
  master = parse(response)
451
  save(master, "master.json")
452
 
@@ -454,32 +517,49 @@ save(master, "master.json")
454
  save_raw(response.text, page_dir / "ai_raw.json") # brut, jamais effacé
455
  master = parse_and_validate(response.text)
456
  save_json(master.model_dump(), page_dir / "master.json")
457
- R06 — Secrets uniquement dans les variables d'environnement
 
 
458
  Jamais dans le code, les logs, les fichiers versionnés, les exports JSON.
459
- R07 — Pydantic v2 exclusivement
460
- python# INTERDIT syntaxe v1
 
 
461
  class Config:
462
  frozen = True
463
 
464
  # ✅ CORRECT — syntaxe v2
465
  model_config = ConfigDict(frozen=True)
466
- R08 — Tests pour tout nouveau modèle
 
 
467
  Aucun schéma Pydantic sans test de validation et de rejet.
468
- R09 — schema_version dans tout PageMaster
469
- schema_version: str = "1.0" obligatoire, valeur par défaut suffit.
470
- R10Endpoints préfixés /api/v1/
471
- R11 — SDK google-genai, pas google-generativeai
472
- python# INTERDIT
 
 
 
 
473
  import google.generativeai as genai
474
 
475
  # ✅ CORRECT
476
  from google import genai
477
- R12 — Jamais le master TIFF/JP2 brut envoyé à l'IA
 
 
478
  Toujours passer par le dérivé JPEG 1500px max.
479
 
480
- 7. Patterns de code attendus
481
- Config depuis variables d'environnement (config.py)
482
- pythonfrom pydantic_settings import BaseSettings
 
 
 
 
 
483
 
484
  class Settings(BaseSettings):
485
  ai_provider: str = "vertex_api_key"
@@ -493,8 +573,12 @@ class Settings(BaseSettings):
493
  model_config = ConfigDict(env_file=".env", extra="ignore")
494
 
495
  settings = Settings()
496
- Pattern SQLAlchemy (models/)
497
- pythonfrom sqlalchemy import String, Integer, Float, DateTime, JSON
 
 
 
 
498
  from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column
499
  from datetime import datetime
500
 
@@ -512,8 +596,12 @@ class PageModel(Base):
512
  confidence_summary: Mapped[float | None] = mapped_column(Float, nullable=True)
513
  created_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow)
514
  updated_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow)
515
- Pattern FastAPI endpoint (api/v1/)
516
- pythonfrom fastapi import APIRouter, HTTPException, Depends
 
 
 
 
517
  from app.schemas.page_master import PageMaster
518
 
519
  router = APIRouter(prefix="/api/v1")
@@ -534,8 +622,12 @@ async def update_master_json(page_id: str, master: PageMaster) -> PageMaster:
534
  }})
535
  save_json(master.model_dump(), get_page_dir(page_id) / "master.json")
536
  return master
537
- Pattern gestion d'erreur IA
538
- pythonimport json
 
 
 
 
539
  import logging
540
  from pydantic import ValidationError
541
 
@@ -561,16 +653,26 @@ def parse_ai_response(raw_text: str, page_id: str) -> PageMaster:
561
  except ValidationError as e:
562
  logger.error("Validation Pydantic échouée", extra={"page_id": page_id, "errors": e.errors()})
563
  raise ValueError(f"JSON IA invalide pour {page_id}: {e}")
 
 
 
 
 
564
 
565
- 8. Rendu des prompts conventions
566
- Variables disponibles dans tous les templates
 
567
  {{profile_label}} → CorpusProfile.label
568
  {{language_hints}} → ", ".join(CorpusProfile.language_hints)
569
  {{script_type}} → CorpusProfile.script_type.value
570
  {{folio_label}} → Page.folio_label
571
  {{manuscript_title}} → Manuscript.title (si disponible)
572
- Implémentation attendue (prompt_loader.py)
573
- pythonfrom pathlib import Path
 
 
 
 
574
 
575
  def load_and_render_prompt(template_path: str, context: dict[str, str]) -> str:
576
  """Charge un template de prompt et injecte les variables."""
@@ -590,9 +692,15 @@ def load_and_render_prompt(template_path: str, context: dict[str, str]) -> str:
590
  raise ValueError(f"Variables non résolues dans le prompt : {unresolved}")
591
 
592
  return content
 
 
 
 
 
593
 
594
- 9. Providers Google AI — architecture à 3 options
595
- Variables d'environnement (GitHub Secrets)
 
596
  # Option A — Google AI Studio (développement, gratuit)
597
  GOOGLE_AI_STUDIO_API_KEY = AIza...
598
 
@@ -608,8 +716,12 @@ VERTEX_LOCATION = (même)
608
 
609
  # Sélecteur actif — changer pour switcher de provider
610
  AI_PROVIDER = vertex_api_key
611
- Factory client (client.py)
612
- pythonfrom google import genai
 
 
 
 
613
  import os, json, logging
614
 
615
  logger = logging.getLogger(__name__)
@@ -650,8 +762,12 @@ def get_ai_client() -> genai.Client:
650
 
651
  raise ValueError(f"AI_PROVIDER inconnu : {provider!r}. "
652
  "Valeurs acceptées : google_ai_studio, vertex_api_key, vertex_service_account")
653
- Listage des modèles disponibles (models.py)
654
- pythondef list_available_models(client: genai.Client) -> list[dict]:
 
 
 
 
655
  """
656
  Retourne les modèles disponibles supportant vision + generateContent.
657
  Format : [{"id": str, "display_name": str, "supports_vision": bool}]
@@ -671,11 +787,17 @@ pythondef list_available_models(client: genai.Client) -> list[dict]:
671
  "supports_vision": supports_vision,
672
  })
673
  return models
 
 
 
 
 
 
 
674
 
675
- 10. Structure des exports documentaires
676
- ALTO (par page)
677
  ALTO contient la géométrie textuelle uniquement.
678
- xml<alto>
 
679
  <Layout>
680
  <Page WIDTH="{width}" HEIGHT="{height}" ID="{page_id}">
681
  <PrintSpace>
@@ -695,10 +817,15 @@ xml<alto>
695
  </Page>
696
  </Layout>
697
  </alto>
 
 
698
  ALTO ne porte PAS : commentaires savants, iconographie, couches éditoriales.
699
- IIIF Manifest (par manuscrit)
 
 
700
  Structure minimale V1 :
701
- json{
 
702
  "@context": "http://iiif.io/api/presentation/3/context.json",
703
  "id": "https://{base_url}/api/v1/manuscripts/{id}/iiif-manifest",
704
  "type": "Manifest",
@@ -720,14 +847,24 @@ json{
720
  }
721
  ]
722
  }
 
 
 
723
 
724
- 11. Statuts métier
 
 
725
  Corpus : CREATED → INGESTING → INGESTED → PROCESSING → READY → ERROR
726
  Page : INGESTED → PREPARED → ANALYZED → LAYERED → EXPORTED → VALIDATED → ERROR
727
  Layer : PENDING → RUNNING → DONE → FAILED → NEEDS_REVIEW → VALIDATED
728
  Éditorial: machine_draft → needs_review → reviewed → validated → published
 
 
 
729
 
730
- 12. Endpoints API — liste complète
 
 
731
  # Configuration & modèles IA
732
  POST /api/v1/settings/api-key
733
  GET /api/v1/models
@@ -777,8 +914,13 @@ GET /api/v1/pages/{id}/history
777
  # Recherche
778
  GET /api/v1/search?q=
779
  GET /api/v1/manuscripts/{id}/search?q=
 
 
 
780
 
781
- 13. État du projet par sprint
 
 
782
  Sprint 1 — Fondations du modèle de données [ TERMINÉ ✓ ]
783
  54 tests passants. Schémas Pydantic. 4 profils. 9 templates prompts.
784
 
@@ -796,18 +938,22 @@ Sprint 5 — Traitement en lot + HuggingFace [ À FAIRE ]
796
 
797
  Sprint 6 — Validation humaine + V1 complète [ À FAIRE ]
798
  Éditeur + versionnement + recherche
799
- Règle stricte : ne jamais implémenter du code d'un sprint futur.
 
 
800
  Si une idée émerge, la noter dans STATUS.md section "Backlog" et ne pas la coder.
801
 
802
- 14. Ce que tu NE dois PAS faire sans demande explicite
803
-
804
- Modifier le schéma PageMaster (champs, types, noms, structure)
805
- Modifier la convention bbox
806
- Ajouter des dépendances non listées dans pyproject.toml section 2
807
- Refactoriser du code existant si la session n'a pas ce but
808
- Créer des fichiers hors de l'arborescence section 3
809
- Implémenter du code d'un sprint futur (section 13)
810
- "Simplifier" un schéma pour "faire plus propre" les schémas sont figés
811
- Créer une logique spécifique à un corpus (règle R01)
812
- Utiliser google-generativeai au lieu de google-genai (règle R11)
813
- Laisser une variable d'environnement dans le code (règle R06)
 
 
 
1
+ # Scriptorium AI — Instructions permanentes pour Claude Code
2
+ ## Version 2.0 — mise à jour Sprint 2
3
 
4
+ ---
5
+
6
+ ## 1. Contexte du projet
7
+
8
+ Scriptorium AI est une **plateforme générique** de génération d'éditions savantes augmentées
9
  pour documents patrimoniaux numérisés : manuscrits médiévaux, incunables, cartulaires,
10
  archives, chartes, papyri — tout type de document, toute époque, toute langue.
11
+
12
  Pipeline général :
13
+ images sources → ingestion → normalisation → analyse Google AI → JSON maître
14
+ → passes dérivées → ALTO / METS / Manifest IIIF → interface web → validation humaine
15
+
16
+ Premier démonstrateur : **Beatus de Saint-Sever** (BnF Latin 8878, manuscrit enluminé,
17
  latin carolingien, XIe siècle). Le Beatus est un profil parmi d'autres — pas un cas spécial.
18
 
19
+ ---
20
+
21
+ ## 2. Stack technique
22
+
23
+ | Composant | Technologie |
24
+ |-----------------|--------------------------------------------------------|
25
+ | Backend | Python 3.11+, FastAPI, Uvicorn |
26
+ | Validation | Pydantic v2 (JAMAIS v1) |
27
+ | Base de données | SQLite via SQLAlchemy 2.0 async + aiosqlite |
28
+ | IA | Google AI — provider sélectionnable (section 9) |
29
+ | SDK Google | google-genai (PAS google-generativeai — paquet différent)|
30
+ | XML | lxml |
31
+ | Images | Pillow (PIL) |
32
+ | HTTP client | httpx (téléchargement images IIIF) |
33
+ | Tests | pytest, pytest-cov, pytest-asyncio |
34
+ | Frontend | React + Vite, TypeScript, Tailwind CSS (sprint 4+) |
35
+ | Hébergement | HuggingFace Spaces (Docker) + HF Datasets |
36
+
37
+ ### pyproject.toml — dépendances exactes
38
+
39
+ ```toml
40
+ [project]
41
  name = "scriptorium-ai"
42
  version = "0.1.0"
43
  requires-python = ">=3.11"
 
66
  [tool.pytest.ini_options]
67
  testpaths = ["tests"]
68
  asyncio_mode = "auto"
69
+ ```
70
+
71
+ ---
72
 
73
+ ## 3. Arborescence du repo — structure canonique
74
+
75
+ ```
76
  scriptorium-ai/
77
 
78
  ├── CLAUDE.md ← CE FICHIER — ne pas modifier sans instruction
 
172
 
173
  └── infra/
174
  └── Dockerfile
175
+ ```
176
+
177
+ ---
178
+
179
+ ## 4. Modèles de données — schémas Pydantic canoniques
180
 
181
+ ### 4.1 CorpusProfile (corpus_profile.py)
182
+
183
+ ```python
184
+ from enum import Enum
185
  from pydantic import BaseModel, ConfigDict, Field
186
 
187
  class LayerType(str, Enum):
 
223
  prompt_templates: dict[str, str] # {"primary": "prompts/.../v1.txt"}
224
  uncertainty_config: UncertaintyConfig
225
  export_config: ExportConfig
226
+ ```
227
+
228
+ ### 4.2 PageMaster (page_master.py)
229
+
230
+ ```python
231
+ from datetime import datetime
232
  from typing import Any, Literal
233
  from pydantic import BaseModel, ConfigDict, Field, field_validator
234
 
 
330
  extensions: dict[str, Any] = {} # données spécifiques au profil
331
  processing: ProcessingInfo | None = None
332
  editorial: EditorialInfo = EditorialInfo()
333
+ ```
334
+
335
+ ### 4.3 AnnotationLayer (annotation.py)
336
+
337
+ ```python
338
+ class LayerStatus(str, Enum):
339
  PENDING = "pending"
340
  RUNNING = "running"
341
  DONE = "done"
 
361
  supports_vision: bool
362
  last_fetched_at: datetime
363
  available_models: list[dict] = []
364
+ ```
365
+
366
+ ---
367
+
368
+ ## 5. Exemple complet d'un master.json valide
369
 
 
370
  Cet exemple est la référence. Tout master.json produit doit avoir cette forme.
371
+
372
+ ```json
373
+ {
374
  "schema_version": "1.0",
375
  "page_id": "beatus-lat8878-0013r",
376
  "corpus_profile": "medieval-illuminated",
 
464
  "notes": []
465
  }
466
  }
467
+ ```
468
+
469
+ ---
470
+
471
+ ## 6. Règles absolues — NE JAMAIS ENFREINDRE
472
 
473
+ ### R01Zéro logique hardcodée par corpus
474
+ ```python
475
+ # ❌ INTERDIT
476
  if profile_id == "medieval-illuminated":
477
  process_iconography()
478
 
479
  # ✅ CORRECT
480
  if "iconography_detection" in corpus_profile.active_layers:
481
  process_iconography()
482
+ ```
483
+
484
+ ### R02 — Le JSON maître est la source canonique
485
  Toutes les sorties (IIIF, ALTO, METS) sont générées depuis PageMaster.
486
  Jamais depuis ai_raw.json directement.
487
+
488
+ ### R03Convention bbox [x, y, width, height] UNIQUEMENT
489
+ ```python
490
+ # ❌ INTERDIT — coordonnées de coins opposés
491
  bbox = [x1, y1, x2, y2]
492
 
493
  # ✅ CORRECT — origine + dimensions
494
  bbox = [x, y, x2 - x1, y2 - y1]
495
+ ```
496
  Pixels entiers absolus dans l'image. Width et height > 0. Toujours validé par Pydantic.
497
+
498
+ ### R04 — Prompts dans des fichiers versionnés, jamais dans le code
499
+ ```python
500
+ # ❌ INTERDIT
501
  prompt = f"Tu analyses un {profile.label}. Retourne ce JSON..."
502
 
503
  # ✅ CORRECT
 
505
  corpus_profile.prompt_templates["primary"],
506
  {"profile_label": profile.label, ...}
507
  )
508
+ ```
509
+
510
+ ### R05 — Double stockage obligatoire des réponses IA
511
+ ```python
512
+ # ❌ INTERDIT — un seul fichier
513
  master = parse(response)
514
  save(master, "master.json")
515
 
 
517
  save_raw(response.text, page_dir / "ai_raw.json") # brut, jamais effacé
518
  master = parse_and_validate(response.text)
519
  save_json(master.model_dump(), page_dir / "master.json")
520
+ ```
521
+
522
+ ### R06 — Secrets uniquement dans les variables d'environnement
523
  Jamais dans le code, les logs, les fichiers versionnés, les exports JSON.
524
+
525
+ ### R07Pydantic v2 exclusivement
526
+ ```python
527
+ # ❌ INTERDIT — syntaxe v1
528
  class Config:
529
  frozen = True
530
 
531
  # ✅ CORRECT — syntaxe v2
532
  model_config = ConfigDict(frozen=True)
533
+ ```
534
+
535
+ ### R08 — Tests pour tout nouveau modèle
536
  Aucun schéma Pydantic sans test de validation et de rejet.
537
+
538
+ ### R09schema_version dans tout PageMaster
539
+ `schema_version: str = "1.0"` obligatoire, valeur par défaut suffit.
540
+
541
+ ### R10 — Endpoints préfixés /api/v1/
542
+
543
+ ### R11 — SDK google-genai, pas google-generativeai
544
+ ```python
545
+ # ❌ INTERDIT
546
  import google.generativeai as genai
547
 
548
  # ✅ CORRECT
549
  from google import genai
550
+ ```
551
+
552
+ ### R12 — Jamais le master TIFF/JP2 brut envoyé à l'IA
553
  Toujours passer par le dérivé JPEG 1500px max.
554
 
555
+ ---
556
+
557
+ ## 7. Patterns de code attendus
558
+
559
+ ### Config depuis variables d'environnement (config.py)
560
+
561
+ ```python
562
+ from pydantic_settings import BaseSettings
563
 
564
  class Settings(BaseSettings):
565
  ai_provider: str = "vertex_api_key"
 
573
  model_config = ConfigDict(env_file=".env", extra="ignore")
574
 
575
  settings = Settings()
576
+ ```
577
+
578
+ ### Pattern SQLAlchemy (models/)
579
+
580
+ ```python
581
+ from sqlalchemy import String, Integer, Float, DateTime, JSON
582
  from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column
583
  from datetime import datetime
584
 
 
596
  confidence_summary: Mapped[float | None] = mapped_column(Float, nullable=True)
597
  created_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow)
598
  updated_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow)
599
+ ```
600
+
601
+ ### Pattern FastAPI endpoint (api/v1/)
602
+
603
+ ```python
604
+ from fastapi import APIRouter, HTTPException, Depends
605
  from app.schemas.page_master import PageMaster
606
 
607
  router = APIRouter(prefix="/api/v1")
 
622
  }})
623
  save_json(master.model_dump(), get_page_dir(page_id) / "master.json")
624
  return master
625
+ ```
626
+
627
+ ### Pattern gestion d'erreur IA
628
+
629
+ ```python
630
+ import json
631
  import logging
632
  from pydantic import ValidationError
633
 
 
653
  except ValidationError as e:
654
  logger.error("Validation Pydantic échouée", extra={"page_id": page_id, "errors": e.errors()})
655
  raise ValueError(f"JSON IA invalide pour {page_id}: {e}")
656
+ ```
657
+
658
+ ---
659
+
660
+ ## 8. Rendu des prompts — conventions
661
 
662
+ ### Variables disponibles dans tous les templates
663
+
664
+ ```
665
  {{profile_label}} → CorpusProfile.label
666
  {{language_hints}} → ", ".join(CorpusProfile.language_hints)
667
  {{script_type}} → CorpusProfile.script_type.value
668
  {{folio_label}} → Page.folio_label
669
  {{manuscript_title}} → Manuscript.title (si disponible)
670
+ ```
671
+
672
+ ### Implémentation attendue (prompt_loader.py)
673
+
674
+ ```python
675
+ from pathlib import Path
676
 
677
  def load_and_render_prompt(template_path: str, context: dict[str, str]) -> str:
678
  """Charge un template de prompt et injecte les variables."""
 
692
  raise ValueError(f"Variables non résolues dans le prompt : {unresolved}")
693
 
694
  return content
695
+ ```
696
+
697
+ ---
698
+
699
+ ## 9. Providers Google AI — architecture à 3 options
700
 
701
+ ### Variables d'environnement (GitHub Secrets)
702
+
703
+ ```
704
  # Option A — Google AI Studio (développement, gratuit)
705
  GOOGLE_AI_STUDIO_API_KEY = AIza...
706
 
 
716
 
717
  # Sélecteur actif — changer pour switcher de provider
718
  AI_PROVIDER = vertex_api_key
719
+ ```
720
+
721
+ ### Factory client (client.py)
722
+
723
+ ```python
724
+ from google import genai
725
  import os, json, logging
726
 
727
  logger = logging.getLogger(__name__)
 
762
 
763
  raise ValueError(f"AI_PROVIDER inconnu : {provider!r}. "
764
  "Valeurs acceptées : google_ai_studio, vertex_api_key, vertex_service_account")
765
+ ```
766
+
767
+ ### Listage des modèles disponibles (models.py)
768
+
769
+ ```python
770
+ def list_available_models(client: genai.Client) -> list[dict]:
771
  """
772
  Retourne les modèles disponibles supportant vision + generateContent.
773
  Format : [{"id": str, "display_name": str, "supports_vision": bool}]
 
787
  "supports_vision": supports_vision,
788
  })
789
  return models
790
+ ```
791
+
792
+ ---
793
+
794
+ ## 10. Structure des exports documentaires
795
+
796
+ ### ALTO (par page)
797
 
 
 
798
  ALTO contient la géométrie textuelle uniquement.
799
+ ```xml
800
+ <alto>
801
  <Layout>
802
  <Page WIDTH="{width}" HEIGHT="{height}" ID="{page_id}">
803
  <PrintSpace>
 
817
  </Page>
818
  </Layout>
819
  </alto>
820
+ ```
821
+
822
  ALTO ne porte PAS : commentaires savants, iconographie, couches éditoriales.
823
+
824
+ ### IIIF Manifest (par manuscrit)
825
+
826
  Structure minimale V1 :
827
+ ```json
828
+ {
829
  "@context": "http://iiif.io/api/presentation/3/context.json",
830
  "id": "https://{base_url}/api/v1/manuscripts/{id}/iiif-manifest",
831
  "type": "Manifest",
 
847
  }
848
  ]
849
  }
850
+ ```
851
+
852
+ ---
853
 
854
+ ## 11. Statuts métier
855
+
856
+ ```
857
  Corpus : CREATED → INGESTING → INGESTED → PROCESSING → READY → ERROR
858
  Page : INGESTED → PREPARED → ANALYZED → LAYERED → EXPORTED → VALIDATED → ERROR
859
  Layer : PENDING → RUNNING → DONE → FAILED → NEEDS_REVIEW → VALIDATED
860
  Éditorial: machine_draft → needs_review → reviewed → validated → published
861
+ ```
862
+
863
+ ---
864
 
865
+ ## 12. Endpoints API — liste complète
866
+
867
+ ```
868
  # Configuration & modèles IA
869
  POST /api/v1/settings/api-key
870
  GET /api/v1/models
 
914
  # Recherche
915
  GET /api/v1/search?q=
916
  GET /api/v1/manuscripts/{id}/search?q=
917
+ ```
918
+
919
+ ---
920
 
921
+ ## 13. État du projet par sprint
922
+
923
+ ```
924
  Sprint 1 — Fondations du modèle de données [ TERMINÉ ✓ ]
925
  54 tests passants. Schémas Pydantic. 4 profils. 9 templates prompts.
926
 
 
938
 
939
  Sprint 6 — Validation humaine + V1 complète [ À FAIRE ]
940
  Éditeur + versionnement + recherche
941
+ ```
942
+
943
+ **Règle stricte** : ne jamais implémenter du code d'un sprint futur.
944
  Si une idée émerge, la noter dans STATUS.md section "Backlog" et ne pas la coder.
945
 
946
+ ---
947
+
948
+ ## 14. Ce que tu NE dois PAS faire sans demande explicite
949
+
950
+ - Modifier le schéma PageMaster (champs, types, noms, structure)
951
+ - Modifier la convention bbox
952
+ - Ajouter des dépendances non listées dans pyproject.toml section 2
953
+ - Refactoriser du code existant si la session n'a pas ce but
954
+ - Créer des fichiers hors de l'arborescence section 3
955
+ - Implémenter du code d'un sprint futur (section 13)
956
+ - "Simplifier" un schéma pour "faire plus propre" — les schémas sont figés
957
+ - Créer une logique spécifique à un corpus (règle R01)
958
+ - Utiliser google-generativeai au lieu de google-genai (règle R11)
959
+ - Laisser une variable d'environnement dans le code (règle R06)