Picarones / picarones /domain /corpus.py
Claude
feat(domain): Sprint A14-S4 — modèle Artifact et types fondamentaux
52412a3 unverified
Raw
History Blame
3.44 kB
"""``CorpusSpec`` — Sprint A14-S4.
Description **immuable et déclarative** d'un corpus à benchmarker.
Construit par un adapter de corpus (``picarones.adapters.corpus.*``),
consommé par les services applicatifs et le pipeline executor.
Différence avec l'ancien ``picarones.core.corpus.Corpus`` :
``CorpusSpec`` est volontairement minimaliste — il décrit la
**structure** d'un corpus (liste de documents + métadonnées
contextuelles). La logique de chargement, parsing, détection des
patterns de nommage GT vit ailleurs (dans ``adapters/corpus/``,
puis ``app/services/corpus_service.py`` au S20).
Au Sprint S10, un convertisseur ``CorpusSpec ↔ Corpus`` permettra
au nouveau code d'utiliser les fixtures historiques sans
réimplémentation.
"""
from __future__ import annotations
from pydantic import BaseModel, ConfigDict, Field, field_validator
from picarones.domain.documents import DocumentRef
class CorpusSpec(BaseModel):
"""Description immuable d'un corpus à benchmarker.
Attributs
---------
name:
Nom court du corpus (utilisé dans les rapports, le cache,
les logs). Ex : ``"bnf_etat_civil_xviiie"``.
documents:
Liste ordonnée des ``DocumentRef``. L'ordre est respecté
par le runner (utile pour des comparaisons reproductibles).
Les ``id`` ne peuvent pas être dupliqués.
metadata:
Dictionnaire libre de contexte. Conventions actuelles :
- ``"language"`` : ``"fr"`` ou ``"en"`` (utilisé par le delta
Flesch et les profils de normalisation).
- ``"period"`` : étiquette éditoriale (``"medieval"``,
``"early_modern"``, ``"modern_archives"``).
- ``"source"`` : ``"local"``, ``"iiif"``, ``"htr_united"``, ...
Pas de validation stricte sur les clés — les conventions
évolueront (cf. ``BACKLOG_POST_LIVRAISON.md``).
Note méthodologique
-------------------
Un ``CorpusSpec`` ne contient **pas** la racine du filesystem
(les ``DocumentRef.image_uri`` doivent être absolus ou résoluble
sans contexte). C'est volontaire : ça permet à un service
applicatif de réécrire les chemins (sandbox utilisateur, cache,
etc.) sans muter le ``CorpusSpec`` lui-même.
"""
model_config = ConfigDict(frozen=True, extra="forbid")
name: str = Field(min_length=1, max_length=128)
documents: tuple[DocumentRef, ...] = Field(default_factory=tuple)
metadata: dict[str, str] = Field(default_factory=dict)
@field_validator("documents")
@classmethod
def _validate_unique_doc_ids(
cls, v: tuple[DocumentRef, ...],
) -> tuple[DocumentRef, ...]:
seen: set[str] = set()
for doc in v:
if doc.id in seen:
from picarones.domain.errors import CorpusSpecError
raise CorpusSpecError(
f"document id dupliqué : {doc.id!r}. "
"Les id de DocumentRef doivent être uniques au sein "
"d'un CorpusSpec."
)
seen.add(doc.id)
return v
def __len__(self) -> int:
return len(self.documents)
def doc_by_id(self, doc_id: str) -> DocumentRef | None:
"""Retourne le ``DocumentRef`` correspondant ou ``None``."""
for doc in self.documents:
if doc.id == doc_id:
return doc
return None
__all__ = ["CorpusSpec"]