Spaces:
Running
Running
| """Marqueurs typographiques et abréviations des archives modernes | |
| (XIXᵉ-XXᵉ siècles) — Sprint 59. | |
| Sprint 59 — Étape 3 / extension philologique du plan d'évolution | |
| 2026. | |
| Pourquoi ce module | |
| ------------------ | |
| Les Sprints 56-57 sont orientés **médiéval scribal** (Capelli, MUFI), | |
| le Sprint 58 cible l'**imprimé ancien** XVIᵉ-XVIIIᵉ. Ce sprint étend | |
| la couverture aux **archives modernes** (XIXᵉ-XXᵉ), période où la | |
| typographie historique a disparu mais où subsistent des conventions | |
| d'abréviation propres aux corpus institutionnels (état civil, | |
| recensements, presse, monographies, archives militaires). | |
| Distinction avec les modules précédents | |
| --------------------------------------- | |
| - ``mufi.py`` (Sprint 57) : caractères médiévaux scribaux. | |
| - ``abbreviations.py`` (Sprint 56) : signes scribaux médiévaux. | |
| - ``early_modern_typography.py`` (Sprint 58) : marqueurs | |
| typographiques imprimé ancien (fi ſ ı &…). | |
| - ``modern_archives.py`` (ce module) : abréviations et conventions | |
| de l'archive moderne XIXᵉ-XXᵉ. | |
| Catégories | |
| ---------- | |
| 1. ``civility_titles`` : Mme, M., Mlle, Mgr, Dr, Pr, Me, R.P., S.M., | |
| S.A.R., S.E., S.S. | |
| 2. ``ordinals`` : 1ᵉʳ, 1ʳᵉ, 2ᵉ, 2ᵈ, Vᵉ (avec exposants Unicode) | |
| 3. ``currency`` : ₶ (livre tournois), ₣ ƒ (franc), £, l. s. d. | |
| (livre/sol/denier d'Ancien Régime) | |
| 4. ``administrative`` : arr., dép., cant., com., reg., prov. | |
| 5. ``civil_status`` : °, †, ✶, ⚭, ép., vve | |
| 6. ``typographic_punctuation`` : « », –, —, …, ’ | |
| 7. ``latin_abbr_modern`` : e.g., i.e., etc., cf., ibid., op. cit., | |
| ad lib. | |
| 8. ``bibliographic`` : vol., t., p., pp., n°, fasc., éd., ms., | |
| r°, v° | |
| 9. ``address`` : bd, av., r., pl., imp., fbg | |
| Sortie | |
| ------ | |
| ``compute_modern_archives_metrics(ref, hyp)`` retourne deux scores | |
| par catégorie (pattern Sprint 56) : | |
| - ``strict_score`` : forme abrégée préservée telle quelle ; | |
| - ``expansion_score`` : forme abrégée OU forme développée présente. | |
| Le **ratio strict/expansion** par catégorie permet au chercheur de | |
| juger lui-même la convention adoptée par chaque moteur, sans | |
| classification automatique imposée par le module. | |
| Stratégie de découpage | |
| ---------------------- | |
| Cohérente avec NER (38), Flesch (52), Reading order F1 (53), | |
| Layout F1 (54), Bloc Unicode (55), Abréviations (56), MUFI (57), | |
| Imprimé ancien (58) : couche de calcul pure d'abord ; câblage | |
| runner et HTML dans des sprints dédiés. | |
| """ | |
| from __future__ import annotations | |
| import logging | |
| import re | |
| from typing import Optional | |
| from picarones.core.metric_registry import register_metric | |
| from picarones.core.modules import ArtifactType | |
| logger = logging.getLogger(__name__) | |
| # ────────────────────────────────────────────────────────────────────────── | |
| # Tables d'abréviations par catégorie | |
| # ────────────────────────────────────────────────────────────────────────── | |
| # | |
| # Format : tuple ``(marker, expansions, regex_strict_pattern_or_None)`` | |
| # où : | |
| # - ``marker`` : forme abrégée canonique (str) | |
| # - ``expansions`` : tuple de formes développées | |
| # acceptées (insensible à la casse) | |
| # - ``regex_strict_pattern`` : pattern Python regex pour la | |
| # détection dans la GT. ``None`` | |
| # = on dérive automatiquement | |
| # ``\b<marker_escaped>\b`` (avec | |
| # garde-fou sur les abréviations | |
| # contenant un point). | |
| # | |
| # Détection : pour les abréviations contenant un ``.`` (« M. »), | |
| # on n'utilise pas ``\b`` standard car « M.\b » match dans | |
| # « M.A. » (le ``.`` étant non-mot, ``\b`` est satisfait). On | |
| # exige donc explicitement une frontière espace/début/fin/ | |
| # ponctuation après le point. | |
| CIVILITY_TITLES: tuple[tuple[str, tuple[str, ...]], ...] = ( | |
| ("Mme", ("Madame",)), | |
| ("Mlle", ("Mademoiselle",)), | |
| ("Mgr", ("Monseigneur",)), | |
| ("Dr", ("Docteur",)), | |
| ("Pr", ("Professeur",)), | |
| ("Me", ("Maître",)), | |
| ("M.", ("Monsieur",)), | |
| ("R.P.", ("Révérend Père",)), | |
| ("S.M.", ("Sa Majesté",)), | |
| ("S.A.R.", ("Son Altesse Royale",)), | |
| ("S.E.", ("Son Excellence",)), | |
| ("S.S.", ("Sa Sainteté",)), | |
| ) | |
| # Ordinaux : la forme **strict** porte l'exposant Unicode | |
| # (1ᵉʳ U+1D49 U+02B3, 1ʳᵉ, 2ᵈ, 2ᵉ, 3ᵉ…) ; la forme **expansion** | |
| # accepte la version plate (« 1er », « 1re », « 2nd ») ou la forme | |
| # textuelle (« premier », « première »). | |
| # | |
| # On définit chaque ordinal explicitement (1-12 + Vᵉ pour les | |
| # numéraux romains de siècle). Au-delà, l'exposant ᵉ seul couvre | |
| # les usages courants (3ᵉ, 4ᵉ, 5ᵉ, 6ᵉ, 7ᵉ, 8ᵉ, 9ᵉ, 10ᵉ). | |
| ORDINALS: tuple[tuple[str, tuple[str, ...]], ...] = ( | |
| ("1ᵉʳ", ("1er", "premier")), | |
| ("1ʳᵉ", ("1re", "première", "premiere")), | |
| ("2ᵈ", ("2d", "second")), | |
| ("2ᵈᵉ", ("2de", "seconde")), | |
| ("2ᵉ", ("2e", "deuxième", "deuxieme")), | |
| ("3ᵉ", ("3e", "troisième", "troisieme")), | |
| ("Iᵉʳ", ("Ier", "premier")), | |
| ("Vᵉ", ("Ve", "cinquième", "cinquieme")), | |
| ("XIᵉ", ("XIe", "onzième", "onzieme")), | |
| ("XIIᵉ", ("XIIe", "douzième", "douzieme")), | |
| ("XVIᵉ", ("XVIe", "seizième", "seizieme")), | |
| ("XVIIᵉ", ("XVIIe", "dix-septième", "dix-septieme")), | |
| ("XVIIIᵉ", ("XVIIIe", "dix-huitième", "dix-huitieme")), | |
| ("XIXᵉ", ("XIXe", "dix-neuvième", "dix-neuvieme")), | |
| ("XXᵉ", ("XXe", "vingtième", "vingtieme")), | |
| ) | |
| CURRENCY: tuple[tuple[str, tuple[str, ...]], ...] = ( | |
| ("₶", ("livre tournois", "livres tournois")), | |
| ("₣", ("franc", "francs")), | |
| ("ƒ", ("florin", "florins")), | |
| ("£", ("livre", "livres", "pound", "pounds")), | |
| ("l.", ("livre", "livres")), | |
| ("s.", ("sol", "sols", "sou", "sous")), | |
| ("d.", ("denier", "deniers")), | |
| ) | |
| ADMINISTRATIVE: tuple[tuple[str, tuple[str, ...]], ...] = ( | |
| ("arr.", ("arrondissement",)), | |
| ("dép.", ("département", "departement")), | |
| ("cant.", ("canton",)), | |
| ("com.", ("commune",)), | |
| ("reg.", ("régiment", "regiment")), | |
| ("prov.", ("province",)), | |
| ) | |
| # État civil : signes typographiques (° = né, † = mort, ⚭ = marié) | |
| # et abréviations textuelles (ép. = épouse/époux, vve = veuve). | |
| CIVIL_STATUS: tuple[tuple[str, tuple[str, ...]], ...] = ( | |
| ("°", ("né", "née")), | |
| ("†", ("mort", "morte", "décédé", "décédée")), | |
| ("✶", ("naissance",)), | |
| ("⚭", ("marié", "mariée", "épousa", "epousa")), | |
| ("ép.", ("épouse", "époux", "epouse", "epoux")), | |
| ("vve", ("veuve",)), | |
| ) | |
| # Ponctuation typographique : ces marqueurs sont préservés en | |
| # diplomatique et remplacés par leur équivalent ASCII en | |
| # modernisant. L'expansion n'est pas une « expansion » au sens | |
| # linguistique mais un substitut typographique. | |
| TYPOGRAPHIC_PUNCTUATION: tuple[tuple[str, tuple[str, ...]], ...] = ( | |
| ("«", ('"',)), | |
| ("»", ('"',)), | |
| ("—", ("-", "--")), | |
| ("–", ("-",)), | |
| ("…", ("...",)), | |
| ("’", ("'",)), | |
| ("‘", ("'",)), | |
| ) | |
| LATIN_ABBR_MODERN: tuple[tuple[str, tuple[str, ...]], ...] = ( | |
| ("e.g.", ("for example", "par exemple", "exempli gratia")), | |
| ("i.e.", ("c'est-à-dire", "id est", "that is")), | |
| ("etc.", ("et cetera", "et caetera")), | |
| ("cf.", ("confer", "voir")), | |
| ("ibid.", ("ibidem",)), | |
| ("op. cit.", ("opere citato", "opus citatum")), | |
| ("ad lib.", ("ad libitum",)), | |
| ("N.B.", ("nota bene",)), | |
| ) | |
| BIBLIOGRAPHIC: tuple[tuple[str, tuple[str, ...]], ...] = ( | |
| ("vol.", ("volume",)), | |
| ("t.", ("tome",)), | |
| ("p.", ("page",)), | |
| ("pp.", ("pages",)), | |
| ("n°", ("numéro", "numero", "no")), | |
| ("fasc.", ("fascicule",)), | |
| ("éd.", ("édition", "edition")), | |
| ("ms.", ("manuscrit",)), | |
| ("f.", ("folio",)), | |
| ("r°", ("recto",)), | |
| ("v°", ("verso",)), | |
| ) | |
| ADDRESS: tuple[tuple[str, tuple[str, ...]], ...] = ( | |
| ("bd", ("boulevard",)), | |
| ("av.", ("avenue",)), | |
| ("r.", ("rue",)), | |
| ("pl.", ("place",)), | |
| ("imp.", ("impasse",)), | |
| ("fbg", ("faubourg",)), | |
| ) | |
| # ────────────────────────────────────────────────────────────────────────── | |
| # Indexation par catégorie | |
| # ────────────────────────────────────────────────────────────────────────── | |
| _CATEGORIES: dict[str, tuple[tuple[str, tuple[str, ...]], ...]] = { | |
| "civility_titles": CIVILITY_TITLES, | |
| "ordinals": ORDINALS, | |
| "currency": CURRENCY, | |
| "administrative": ADMINISTRATIVE, | |
| "civil_status": CIVIL_STATUS, | |
| "typographic_punctuation": TYPOGRAPHIC_PUNCTUATION, | |
| "latin_abbr_modern": LATIN_ABBR_MODERN, | |
| "bibliographic": BIBLIOGRAPHIC, | |
| "address": ADDRESS, | |
| } | |
| # Liste plate de tous les marqueurs avec leur catégorie. Triée par | |
| # longueur décroissante pour que la détection préfère le marqueur | |
| # le plus long quand plusieurs préfixes matchent (ex. « S.A.R. » | |
| # avant « S.A. "). | |
| _ALL_MARKERS: list[tuple[str, tuple[str, ...], str]] = sorted( | |
| [ | |
| (marker, expansions, category) | |
| for category, entries in _CATEGORIES.items() | |
| for marker, expansions in entries | |
| ], | |
| key=lambda triple: -len(triple[0]), | |
| ) | |
| # ────────────────────────────────────────────────────────────────────────── | |
| # Compilation des patterns regex | |
| # ────────────────────────────────────────────────────────────────────────── | |
| # | |
| # Pour chaque marqueur, on compile un pattern qui exige une | |
| # frontière de mot adaptée : | |
| # | |
| # - Marqueur alphabétique seul (« Mme », « bd ») → ``\b<marker>\b`` | |
| # (le ``\b`` Python gère correctement les bords). | |
| # - Marqueur contenant un point (« M. », « S.A.R. », « arr. », | |
| # « r° », « n° ») → frontière espace/début/fin/ponctuation | |
| # explicite (le ``.`` final étant non-mot, ``\b`` standard | |
| # matcherait dans « arr.acher »). | |
| # - Marqueur contenant un caractère non ASCII (exposant, monnaie, | |
| # guillemet, croix d'état civil) → match littéral, pas de | |
| # frontière de mot car ``\b`` ne fonctionne pas sur les | |
| # caractères non-mot Unicode. | |
| # | |
| # La frontière de droite après un point exige soit la fin de | |
| # chaîne, soit un blanc, soit une ponctuation usuelle (« , ; : ! ? ) | |
| # … » »). | |
| _TRAILING_BOUNDARY = r"(?=$|[\s,;:!?\)\]\»\"\'\n\r\t…])" | |
| _LEADING_BOUNDARY = r"(?:^|(?<=[\s,;:!?\(\[\«\"\'\n\r\t]))" | |
| def _is_alphanumeric_only(text: str) -> bool: | |
| """Vrai si tous les caractères sont alphanumériques ASCII.""" | |
| return all(c.isascii() and c.isalnum() for c in text) | |
| def _compile_pattern(marker: str) -> re.Pattern[str]: | |
| """Compile le pattern regex pour la détection d'un marqueur | |
| dans la GT et l'hypothèse. | |
| La logique de frontière de mot dépend de la composition du | |
| marqueur (cf. commentaire principal). | |
| """ | |
| escaped = re.escape(marker) | |
| if "." in marker: | |
| # Frontière explicite après le point final. | |
| return re.compile(_LEADING_BOUNDARY + escaped + _TRAILING_BOUNDARY) | |
| if _is_alphanumeric_only(marker): | |
| return re.compile(r"\b" + escaped + r"\b") | |
| # Marqueurs Unicode (exposants, monnaies, guillemets, ponctuation | |
| # typographique, croix) : match littéral, pas de \b. | |
| return re.compile(escaped) | |
| # Cache des patterns compilés : (marker, category) → pattern. | |
| _PATTERNS: dict[tuple[str, str], re.Pattern[str]] = { | |
| (marker, category): _compile_pattern(marker) | |
| for marker, _expansions, category in _ALL_MARKERS | |
| } | |
| # Patterns d'expansion (insensibles à la casse, frontière de mot | |
| # si la forme développée est purement alphabétique). | |
| _EXPANSION_PATTERNS: dict[str, list[re.Pattern[str]]] = {} | |
| for marker, expansions, _category in _ALL_MARKERS: | |
| compiled: list[re.Pattern[str]] = [] | |
| for exp in expansions: | |
| escaped = re.escape(exp) | |
| if exp and _is_alphanumeric_only(exp): | |
| compiled.append(re.compile(r"\b" + escaped + r"\b", re.IGNORECASE)) | |
| else: | |
| compiled.append(re.compile(escaped, re.IGNORECASE)) | |
| _EXPANSION_PATTERNS[marker] = compiled | |
| # ────────────────────────────────────────────────────────────────────────── | |
| # API publique : catégorisation + détection | |
| # ────────────────────────────────────────────────────────────────────────── | |
| def get_category(marker: str) -> Optional[str]: | |
| """Retourne la catégorie d'un marqueur ou ``None`` si inconnu. | |
| La comparaison est exacte (sensible à la casse, aux exposants | |
| Unicode et aux points). | |
| """ | |
| if not marker: | |
| return None | |
| for category, entries in _CATEGORIES.items(): | |
| for known, _expansions in entries: | |
| if known == marker: | |
| return category | |
| return None | |
| def get_expansions(marker: str) -> tuple[str, ...]: | |
| """Retourne les formes développées connues pour un marqueur, | |
| ou un tuple vide si inconnu.""" | |
| if not marker: | |
| return () | |
| for _category, entries in _CATEGORIES.items(): | |
| for known, expansions in entries: | |
| if known == marker: | |
| return expansions | |
| return () | |
| def detect_modern_markers( | |
| text: Optional[str], | |
| ) -> list[tuple[int, str, str]]: | |
| """Retourne les marqueurs trouvés dans ``text``. | |
| Forme de sortie : ``[(index, marker, category), ...]`` triée | |
| par index croissant. Si plusieurs marqueurs se chevauchent, le | |
| plus long gagne (ex. « S.A.R. » plutôt que « S. " puis « A.R. »). | |
| Tolérance casse | |
| --------------- | |
| Les marqueurs alphabétiques courts (« Mme », « Dr », « bd ») | |
| sont matchés tels quels (sensibilité à la casse) — on n'élargit | |
| pas car « me » en minuscule n'est pas une abréviation de | |
| « Maître ». | |
| """ | |
| if not text: | |
| return [] | |
| # Collecte tous les matches de tous les marqueurs. | |
| candidates: list[tuple[int, int, str, str]] = [] # start, end, marker, cat | |
| for marker, _expansions, category in _ALL_MARKERS: | |
| pattern = _PATTERNS[(marker, category)] | |
| for match in pattern.finditer(text): | |
| candidates.append((match.start(), match.end(), marker, category)) | |
| # Tri par (start, -length) pour appliquer une stratégie greedy | |
| # « plus long gagne » à chaque position. | |
| candidates.sort(key=lambda c: (c[0], -(c[1] - c[0]))) | |
| chosen: list[tuple[int, str, str]] = [] | |
| last_end = -1 | |
| for start, end, marker, category in candidates: | |
| if start < last_end: | |
| continue | |
| chosen.append((start, marker, category)) | |
| last_end = end | |
| return chosen | |
| # ────────────────────────────────────────────────────────────────────────── | |
| # Calcul des scores strict / expansion | |
| # ────────────────────────────────────────────────────────────────────────── | |
| def _hyp_contains_marker( | |
| hypothesis: str, marker: str, category: str, | |
| ) -> bool: | |
| """Vrai si le marqueur est présent (au moins une occurrence) dans | |
| l'hypothèse, avec la même règle de frontière qu'en GT.""" | |
| pattern = _PATTERNS[(marker, category)] | |
| return pattern.search(hypothesis) is not None | |
| def _hyp_contains_expansion(hypothesis: str, marker: str) -> bool: | |
| """Vrai si une forme développée connue du marqueur est présente | |
| dans l'hypothèse (insensible à la casse).""" | |
| for pattern in _EXPANSION_PATTERNS.get(marker, ()): | |
| if pattern.search(hypothesis) is not None: | |
| return True | |
| return False | |
| def compute_modern_archives_metrics( | |
| reference: Optional[str], | |
| hypothesis: Optional[str], | |
| ) -> dict: | |
| """Calcule la préservation des marqueurs d'archives modernes. | |
| Pour chaque catégorie : retourne le ``strict_score`` (forme | |
| abrégée préservée) et l'``expansion_score`` (abrégée OU | |
| développée présente). Le ratio des deux donne au chercheur la | |
| convention adoptée (diplomatique / modernisante / mixte) sans | |
| qu'aucune classification ne soit imposée. | |
| Returns | |
| ------- | |
| dict | |
| ``{ | |
| "n_markers_reference": int, | |
| "n_strict_preserved": int, | |
| "n_expansion_preserved": int, | |
| "global_strict_score": float, | |
| "global_expansion_score": float, | |
| "per_category": { | |
| category: { | |
| "n_total": int, | |
| "n_strict_preserved": int, | |
| "n_expansion_preserved": int, | |
| "strict_score": float, | |
| "expansion_score": float, | |
| } | |
| }, | |
| "missed_markers": [ | |
| {"index": int, "marker": str, "category": str, | |
| "expansion_preserved": bool} | |
| ], | |
| }`` | |
| Cas dégénérés | |
| ------------- | |
| - GT vide ou sans marqueur → tous les compteurs à 0, scores à | |
| ``0.0``, ``per_category == {}``. | |
| - GT non vide avec marqueurs + hyp vide → tous les scores à | |
| ``0.0``, tous les marqueurs dans ``missed_markers``. | |
| """ | |
| ref = reference or "" | |
| hyp = hypothesis or "" | |
| detected = detect_modern_markers(ref) | |
| n_total = len(detected) | |
| if n_total == 0: | |
| return { | |
| "n_markers_reference": 0, | |
| "n_strict_preserved": 0, | |
| "n_expansion_preserved": 0, | |
| "global_strict_score": 0.0, | |
| "global_expansion_score": 0.0, | |
| "per_category": {}, | |
| "missed_markers": [], | |
| } | |
| per_cat_total: dict[str, int] = {} | |
| per_cat_strict: dict[str, int] = {} | |
| per_cat_expansion: dict[str, int] = {} | |
| n_strict = 0 | |
| n_expansion = 0 | |
| missed: list[dict] = [] | |
| for index, marker, category in detected: | |
| per_cat_total[category] = per_cat_total.get(category, 0) + 1 | |
| strict_ok = _hyp_contains_marker(hyp, marker, category) | |
| # Convention identique à Sprint 56 : si l'abrégé est | |
| # préservé, c'est aussi un succès pour expansion (l'OCR n'a | |
| # pas perdu l'information). | |
| expansion_ok = strict_ok or _hyp_contains_expansion(hyp, marker) | |
| if strict_ok: | |
| per_cat_strict[category] = per_cat_strict.get(category, 0) + 1 | |
| n_strict += 1 | |
| if expansion_ok: | |
| per_cat_expansion[category] = per_cat_expansion.get(category, 0) + 1 | |
| n_expansion += 1 | |
| if not strict_ok: | |
| missed.append({ | |
| "index": index, | |
| "marker": marker, | |
| "category": category, | |
| "expansion_preserved": expansion_ok, | |
| }) | |
| per_category = { | |
| cat: { | |
| "n_total": per_cat_total[cat], | |
| "n_strict_preserved": per_cat_strict.get(cat, 0), | |
| "n_expansion_preserved": per_cat_expansion.get(cat, 0), | |
| "strict_score": ( | |
| per_cat_strict.get(cat, 0) / per_cat_total[cat] | |
| if per_cat_total[cat] > 0 else 0.0 | |
| ), | |
| "expansion_score": ( | |
| per_cat_expansion.get(cat, 0) / per_cat_total[cat] | |
| if per_cat_total[cat] > 0 else 0.0 | |
| ), | |
| } | |
| for cat in sorted(per_cat_total) | |
| } | |
| return { | |
| "n_markers_reference": n_total, | |
| "n_strict_preserved": n_strict, | |
| "n_expansion_preserved": n_expansion, | |
| "global_strict_score": n_strict / n_total, | |
| "global_expansion_score": n_expansion / n_total, | |
| "per_category": per_category, | |
| "missed_markers": missed, | |
| } | |
| def modern_archives_strict_score( | |
| reference: Optional[str], hypothesis: Optional[str], | |
| ) -> float: | |
| """Raccourci : taux global de préservation **stricte** des | |
| marqueurs d'archives modernes ∈ [0, 1].""" | |
| return compute_modern_archives_metrics( | |
| reference, hypothesis, | |
| )["global_strict_score"] | |
| def modern_archives_expansion_score( | |
| reference: Optional[str], hypothesis: Optional[str], | |
| ) -> float: | |
| """Raccourci : taux global de préservation **étendue** (abrégée | |
| OU développée) des marqueurs d'archives modernes ∈ [0, 1].""" | |
| return compute_modern_archives_metrics( | |
| reference, hypothesis, | |
| )["global_expansion_score"] | |
| # ────────────────────────────────────────────────────────────────────────── | |
| # Enregistrement dans le registre typé (Sprint 34) | |
| # ────────────────────────────────────────────────────────────────────────── | |
| def _registered_strict(reference: str, hypothesis: str) -> float: | |
| return modern_archives_strict_score(reference, hypothesis) | |
| def _registered_expansion(reference: str, hypothesis: str) -> float: | |
| return modern_archives_expansion_score(reference, hypothesis) | |
| __all__ = [ | |
| "CIVILITY_TITLES", | |
| "ORDINALS", | |
| "CURRENCY", | |
| "ADMINISTRATIVE", | |
| "CIVIL_STATUS", | |
| "TYPOGRAPHIC_PUNCTUATION", | |
| "LATIN_ABBR_MODERN", | |
| "BIBLIOGRAPHIC", | |
| "ADDRESS", | |
| "compute_modern_archives_metrics", | |
| "detect_modern_markers", | |
| "get_category", | |
| "get_expansions", | |
| "modern_archives_strict_score", | |
| "modern_archives_expansion_score", | |
| ] | |