from __future__ import annotations from dataclasses import dataclass import re from typing import Iterable, Mapping, Optional from publication_meta import clean, parse_meta class MarkerError(ValueError): pass @dataclass(frozen=True) class PageMarker: block_id: str raw: str meta: dict[str, str] def find_marker(blocks: Iterable[Mapping[str, object]]) -> Optional[PageMarker]: valid: list[PageMarker] = [] near_misses: list[str] = [] for block in blocks: block_id = str(block.get("id", "unknown")) raw_value = block.get("text", "") raw = raw_value if isinstance(raw_value, str) else "" normalized = clean(raw) if re.search(r"anwarpy", normalized, re.IGNORECASE) is None: continue if not normalized.startswith("anwarpy:"): near_misses.append(block_id) continue try: meta = parse_meta(normalized) except ValueError: near_misses.append(block_id) continue valid.append(PageMarker(block_id=block_id, raw=raw, meta=meta)) if len(valid) > 1: ids = ", ".join(marker.block_id for marker in valid) raise MarkerError(f"multiple anwarpy markers found in blocks: {ids}") if valid and near_misses: ids = ", ".join(near_misses) raise MarkerError(f"ambiguous anwarpy markers; near-miss blocks: {ids}") if near_misses: ids = ", ".join(near_misses) raise MarkerError(f"near-miss anwarpy marker in blocks: {ids}") return valid[0] if valid else None