Archival Data Model — Development Plan
2026-09-19 — read this first. The shared ideas in this file (what a segment is, its slots, the delivery rule) now have one home:
source-model.mdand its slices. Where this file and that set differ, that set wins. This file stays as the STAGED PLAN, updated below (“How the phases map to the source model”).Manual: TBD — the user manual needs a “How Fichero stores what it reads” section: what a segment is, how a page’s regions/lines/words relate, and why that shape lets a claim point back at the exact ink it came from. Written for a researcher, not a developer.
Status: DRAFT for discussion (Fichero creative director, 2026-09-08 night). This consolidates the segment/archival design captured across issues #4635–#4642 and #1755 into ONE staged plan to develop and then talk through. Design-led (Testing Constitution): nothing here is built until the creative director approves the slice. Epic index: #4639.
1. The two models and the one hinge
Everything below serves a dead-simple user model on top of a rich data model. They never leak into each other; the Profile is the only translator.
User model (what a person touches)
Document → Page → Profile → Transcription (in an Edition) → Entity/Claim → Source → Credit → Tags.
A small vocabulary. Depth is disclosed only when a scholar reaches for it (progressive
disclosure): a casual user does import → transcribe → read → search and never sees a
segment, an RDF triple, or a provenance chain.
Data model (what is stored)
One primitive — the Segment — and every record hangs off it. Every record (Segment, Transcription, Representation, Claim, Entity, Annotation, Zone, Tag) carries the same four invariant slots:
| Slot | Meaning |
|---|---|
| anchor | WHERE — geometry/time on which medium, + layer + reading order |
| language + script | WHAT system — coverage tier, direction |
| provenance + version + credit | HOW it got here — actor (model/human), reasoning, restorable history |
| export map | HOW it leaves — mapping to ALTO/PageXML/TEI/JSON-LD/Linked Art/… |
A new format or challenge is a new value in one slot, not new schema.
The hinge: Profile
medieval-manuscript · damaged-archive · papyrus · printed-book · modern-typescript · …
One choice loads the right segmenter + runtime/model + edition + transcription
guideline + condition-handling. The user picks (or Fichero detects) the profile; the
deep machinery configures itself. This is how the whole model reaches the user without
exposing its complexity.
2. The delivery contract (non-negotiable)
A capability is DONE only across the whole spine:
backend → inspector/front-end → AI (MCP) + CLI → tested → exportable
- Backend row alone = not done.
- UX without MCP/CLI = not done.
- Untested (esp. the cross-surface invariant) = not done.
- Not exportable = not done.
Every phase below is a vertical slice that crosses the whole spine for a small piece, never a horizontal layer that builds backend-only breadth. The acceptance test for each capability is the do-it / represent-it / save-it trio plus MCP/CLI + export.
3. The primitive: Segment (spatial AND temporal)
A Segment is any addressable slice of any medium — this is W3C Media Fragments:
- spatial (image / PDF): polygon /
#xywh=x,y,w,h - temporal (audio / video): time span /
#t=start,end - both (video): region + time
- ~~geographic (georeferenced map, #1755): lat/lon + warp transform~~ Superseded by
maps-and-georeference.md(#5120): a geographic place is NOT an anchor kind. A segment’s place in the world is worked out through a georeferencing pass of ground-control-point segments, with the input CRS explicit and WGS 84 stored (#5124).
Granularity ladder: stroke → glyph → character → word → line → region → page-part → page.
Surface for creating/editing a segment = the Preview (source) pane (#168): the polygon
is drawn and adjusted on the media. The inspector shows a segment’s metadata/versions.
Existing infra to build ON (not replace): _segment_image crop, the Kraken segmenter
that persists baseline geometry, recombine/contact-sheet.
4. The layers (all are versioned collections at the segment grain)
A segment holds many, each versioned append-only:
- Transcription[] —
{ text, language, script, edition, version, provenance }. Language/script live HERE, not only on the segment (bilingual gloss = 2 transcriptions; graphemic vs normalized = 2 transcriptions). Editions: diplomatic / normalized / graphemic / graphetic (#4638). - Representation[] —
{ kind, version, provenance }, kind ∈image-crop · text · tokens · image-vector · text-vector · glyph-rep(no-Unicode) · layout-features. The consumer chooses which it wants (an AI asks image+text; an index asks vectors; an exporter asks text→ALTO). - Provenance/Rationale — per version: the model’s reasoning AND the human’s rationale, not just the value. This makes plausible-readings-side-by-side possible and IS the training corpus (#4636, #4642).
- Linguistic annotation — token spans within a transcription: POS, lemma, morphology, dependency (CoNLL-U/UD). NER (entities) is the same mechanism (#4637).
- Zones — logical grouping ABOVE the page: a page typed as letter/response/deposition; a set of pages spanning multiple documents (a legal case); with relationships (reply-to, continues, part-of). Reading-order / correspondence layer (#4639).
- Tags — arbitrary labels (free or controlled-vocabulary) on any object at any grain (SKOS / W3C Web Annotation).
5. Staged development plan
Each phase is a vertical slice. P0 proves the spine cheaply on existing data before any new schema. Later phases add breadth only after the pattern holds and is approved.
P0 — Segment Representations read model (the proof slice)
Spec: segment-representations.md. Milestone spread: Inspector-Knowledge-Engine #55 (backend),
Inspector-Knowledge #151 (inspector), Client-MCP #52 (MCP/CLI), Testing #267, Exporter #14.
- backend: segment.read.representations → image-crop (via _segment_image) + text
(existing transcription); read-only, honest-absence, no new schema.
- inspector: a segment shows its crop + text, anchored (reuse F5 ClaimSourceRequest).
- MCP + CLI: segment.get(reps=image+text) — hand an AI segments + images in one call.
- tests: rep-roundtrip + cross-surface HARD-GATE (same segment → same anchor/text
from backend, MCP, CLI) + resource-safety.
- export: one segment’s transcription → ALTO (or PageXML) with geometry intact.
P1 — Segment editing on the Preview pane
- Draw/adjust the polygon on the image/PDF in the Preview pane; persist geometry as an audited, reversible action; changes update in place. (Builds on #168 bbox work.)
P2 — Transcriptions as versioned collection + editions
- Promote transcription to a first-class versioned record with language/script/edition; show the version stack; plausible-readings side-by-side; capture reasoning on each version (P4 dep). Editions picker (diplomatic/normalized/…). (#4638)
P3 — Representation vectors
image-vector/text-vectorrepresentations, versioned by embedding model; feed the search index. Resource-safety bounded (Search-Engine #186). (#4634 load-testing rides here.)
P4 — Provenance, versions, rationale (cross-cutting)
- The full restorable history + the reasoning field (model + human) on every version, shown on the page and easy to edit. Actor roles extensible (extractor/editor/reviewer/ end-user). This is #4636 — a candidate its-own-milestone; every layer rides it.
P5 — Zones + relationships
- Logical groupings above the page (letter→response; case across documents) with relationships; see/edit on the Preview pane; query via MCP/CLI; export TEI correspDesc / METS structMap / IIIF ranges. (#4639)
P6 — Linguistic annotation (POS) + Tags
- Token spans + labels within a transcription (CoNLL-U/UD); general tagging (SKOS) across objects. (#4637 + the tags thread.)
P7 — Language/script coverage + no-Unicode path
- loove coverage tiers; CLDR/Glottolog/UCD/UDHR; fonts-by-character; Unicode-space viz; the extreme case — a script with no encoding/no fonts works image-anchored with custom glyph representations and contributes toward encoding. (#4637)
P8 — Authority linking + Web of Data
sameAssets to VIAF/Wikidata/GeoNames/Pleiades/Getty; dereferenceable URIs; SPARQL endpoint (make rdflib visible); JSON-LD/Linked Art; PROV/CIDOC-CRM. (#4641)
P9 — Exporter Manager + folder sync + contribute
- One export path for all the slots/formats; index a folder / keep it in sync; package a repo; contribute data upstream; training-corpus export (feeds P10). (#4640)
P10 — Distilled paleography VLM
- The versioned (image-crop → text → reasoning) corpus + human review → distill a small local “thinking” VLM (MLX) that emits its reasoning. (#4642)
P11 — Georeference
- Old-map-on-new-map, specified in
maps-and-georeference.md(#5120): not a geographic anchor kind but control-point segments, a transform worked out from them with residuals, and an explicit CRS; export IIIF Georeference Annotation, GCP tables, world file/GeoTIFF, GeoJSON, GeoPackage and Linked Places. (#1755)
5a. How the phases map to the source model (2026-09-19)
The maintainer has since ruled that the whole model is specified properly first, and the order
of building is decided from the finished spec (source-model.md, open question on
order). The phases above stay as a record of intent; this table says where each now lives and
what changed. Words: the per-document Profile hinge of section 1 is retired (a project’s profile, plus
overrides lower in the cascade, does its job; “profile” now means only that); the granularity
ladder of section 3 is replaced by the source model’s (which reaches above the page and has
no separate “glyph” or “page-part” level); the open questions in section 7 on profile
detection and on the first export target were answered by the rulings of 2026-09-19
(onboarding starts from sample pages; PageXML, ALTO and TEI are built together); a “layer” of segments is now a pass; a “transcription” in an
“edition” is a reading with a kind and a level; “representations” are readings plus
worked-out things.
| Phase | Now specified in | What changed |
|---|---|---|
| P0 read model | segment-representations.md (first slice) + segments-and-geometry.md |
a segment has a lasting id and its own record; pictures are cut to the polygon |
| P1 editing on the Preview | segment-editor.md |
native SwiftUI, Mac + iPad + iPhone, Pencil; one overlay; merge, split, reorder, links |
| P2 transcriptions + editions | readings-and-apparatus.md |
readings as a set; written and read; only a person chooses; normalisation level recorded |
| P3 vectors | source-model.md (“What stands on segments”) |
vectors per segment and per reading; search lands on a segment |
| P4 provenance, versions, rationale | segments-and-geometry.md, readings-and-apparatus.md |
versions per segment; hand kept apart from provenance; three kinds of “sure” |
| P5 zones + relationships | segments-and-geometry.md |
physical and logical structure; named reading orders; typed links; flows |
| P6 linguistic annotation + tags | source-model.md (worked-out things), readings-and-apparatus.md (marks) |
word-level analysis hangs on a reading; marks on any segment, in authored sets |
| P7 language/script coverage, no-Unicode | languages-scripts-signs.md |
moved from late to foundational: three facts, cascade, direction, declared signs, sign lists, fonts |
| P8 authority linking | kg-enrichment.md |
unchanged; a segment gains a citable reference |
| P9 exporter, contribute | formats-and-training.md, export/exporter.md |
every format both ways; validation; loss reports; imports arrive as passes |
| P10 distilled VLM | formats-and-training.md |
the training loop, human-checked by default, split by manuscript, measured against ground truth |
| P11 georeference | maps-and-georeference.md (umbrellas in segments-and-geometry.md) |
control points as segments; the transform worked out; CRS; gazetteers; geo formats |
| new | rights-and-access.md |
rights, consent, community labels, redaction, removal (proposed) |
| new | segments-and-geometry.md |
recordings as stretches of time; several images lined up by alignment points; rescans |
Embeddings, the knowledge graph, content and workflows all stand on segments: see “What stands on segments” in the foundation.
6. Standards map (the export slot, by layer)
| Layer | Standards |
|---|---|
| Anchor (spatial/temporal) | W3C Media Fragments, W3C Web Annotation selectors |
| Segmentation / geometry | ALTO, PageXML, hOCR, abbyyXML (Kraken outputs) |
| Transcription / editions | TEI, CATMuS guidelines, Leiden (papyri), ZOLO, MEI (music) |
| Linguistics | CoNLL-U / Universal Dependencies |
| Claims (RDF) | RDF/SPARQL, JSON-LD, Linked Art, CIDOC-CRM |
| Provenance | W3C PROV |
| Zones / structure | TEI msDesc/correspDesc, METS structMap, IIIF ranges/collections |
| Tags | SKOS, W3C Web Annotation |
| Authority | VIAF, Wikidata, LoC, GeoNames, Pleiades, Getty (TGN/AAT) |
| Package / interchange | IIIF, METS, BagIt |
| Georeference | GDAL world-file, GeoTIFF, IIIF-georef |
Rulings
- 2026-10-04 (maintainer): profile detection. A page’s archival profile is detected from the page and its recipe, shown to the person, and editable; a change can cascade to the folder.
- Ruled 2026-10-04 (design lead, applying the spec’s own lean): a representation is both a
file payload and a metadata row. Transcription is already first-class: text lives as readings
on segments, in passes (
source-model.md, rulings of 2026-09-20), so no separate transcription table is owed here. - Answered by what is built (2026-10-04): ALTO and PAGE XML both export today, one page at a
time (
fichero_server/api/routes/document/page_export.py, “Export one page as PAGE XML, ALTO or TEI”; writers infichero_server/formats/alto.pyandpagexml.py), so neither is “first”.
7. Open questions to talk through
- Answered (maintainer 2026-10-04: archival profile detected + editable): Detected from the page and editable after. Profile detection — user-picked only, or auto-detected from the page? Editable after?
- Answered (design lead 2026-10-04, applying the spec’s own lean): Both, a file payload and a metadata row. Where representations physically live — derivative files (like
_segment_image), a table, or both? - Answered (source/source-model.md Rulings 2026-09-20 (readings on segments, slice 8)): Text lives as readings on segments; no separate transcription table. Is Transcription first-class now (P2) or does P0 read the existing transcription text and defer the multi-edition table?
- Answered (design lead 2026-10-04, applying the spec’s lean): DuckDB is the record; LanceDB and the graph are derived from it and rebuilt from it. Store responsibility — DuckDB (relational) vs LanceDB (vectors) vs rdflib/SPARQL (graph): which store owns which slot, and how they stay in sync (ties Exporter #4640)?
- Answered (source/source-model.md Rulings 2026-09-19 item 21): One milestone for the source-model set. New milestones? — “Segments & Anchors” and/or “Provenance, Versions & Credit”, or keep everything under the existing surface milestones? (Decomposition on #4639.)
- Answered (source/source-model.md Rulings 2026-09-19 item 19): PageXML, TEI and ALTO all built first on one mapping system. P0 export target — ALTO or PageXML first?
8. Milestone decomposition
Design lane lives in Historical Text (#265); each phase’s build issues land in the surface milestone that owns their spine segment — Inspector-Knowledge #151/#55, Client-MCP
52, Search-Engine #186, Importer #188, Preview-Image-Editing #168, Exporter #14,
Settings-Models #20, Testing #267. Full mapping + the two proposed new milestones are on #4639 (awaiting the creative director’s ack — no milestones created yet).