Mova0.5 pre-release⌕
☰
home / docs / Mova annotation database (DuckDB)

Mova annotation database (DuckDB)

A DuckDB database for the annotation of Mova's sentences: treebank gold, silver, bronze, model runs, pragmatics and the tales, all over the same sentences. It lives in the data directory, and the whole Mova corpus is loaded into it.

Where things are:

How data flows. We do not use the duckdb Rust crate: it is a long C++ build. The duckdb CLI does the work (variable DUCKDB):

TSV has no quotes or escapes, NULL is \N. A value containing TAB, LF, CR or equal to \N cannot be written: this is an error, not silent corruption. CoNLL-U lines have none of these by definition, and JSON is written by serde_json with escapes.

Rebuild from scratch

cd tools/db && cargo build --release
D=../../target/release/db; B=$MOVA_DATA/db/mova.duckdb; U=$MOVA_DATA/en/ud/ud-treebanks-v2.18; R=$MOVA_DATA/runs/tales-2026-09-26
$D init $B                     # schema v1; does not overwrite an existing database
$D corpus $B                   # treebanks, Mova corpus, calibration — per db/corpus.tsv (~40 s)
$D tales $B $R/tales.jsonl $MOVA_DATA/raw $R $U/UD_English-EWT/en_ewt-ud-train.conllu $U/UD_English-ESLSpok/en_eslspok-ud-train.conllu   # ~1 min
$D verify $B                   # lossless: every file byte for byte
$D stats $B                    # overview (db/sql/stats.sql)
$D sql $B ../../db/queries/rust-vs-opus.sql
$D export $B doc=gutenberg:2591:hansel-and-gretel > hansel.conllu

Other commands:

Temporary TSVs go to $TMPDIR; DB_KEEP=1 keeps them for inspection.

Schema

Idea: different annotations from different sources and different layers lie on the same sentences. Text goes docs → sentences. Each annotation is a separate set in annsets with its own provenance.

tablewhat it holdswhy
runsa run or load: name, kind (treebank, corpus, calib, tales), tool, directory, params JSON, whenwho annotated, with what and when. Treebank card, run mode and effort, Opus calls, training files
filesa loaded file: path, sha1, size, leading blank lines, trailing LF, countersso that db export file=… returns the same file byte for byte, and verify can check it
docsa document: key, source, title, author, language, license, genre, meta JSONwhere the text comes from and on what terms
sentencesdocument, paragraph, order, sent_id, text, comments JSONone sentence — many annotations
annsetsan annotation set: layer, source, who, run or treebank (origin), dialect, when, how, gate, position in the file, block comments verbatimprovenance of every view of a sentence
tokensCoNLL-U rows of a set: ten raw columns, including multiword tokens (3-4) and empty nodes (8.1)lossless, any dialect

Document:

meta::* of the first sentence of a document (GUM, fairy tales) give the title, author, genre and license. All of them also go into docs.meta.

Document-level set (annsets.sid = NULL) — for layers that belong to a document: entities, passport, summaries.

Views:

Upper layers (Smart annotator — layers, context, memory) — placeholder tables, empty for now. Anchor to sentences and words: sid + base (the ud set whose word IDs are meant) + w_from..w_to. A layer set is an annsets row with its own layer, source, run, method.

tablelayermain fields
entities4, entitieseid ("e3", as in the md graph), type as in GUM, name, Number/Gender/Person, animacy, attention weight, facts with anchors
mentions4, coreferencemention: eid, word span, head, information status, bridging relation (Bridge=)
events10, narrativeevent: predicate, frame, roles (who, what, when, where, why) JSON, event relations (cause, sequence)
pragmatics6, pragmaticss_type, the act and its form, what was meant, presuppositions, stance, speaker and addressee
passport7, document passportdomain, genre, style, narrator, purpose, mode, addressee, time and place, anchors
summaries10, summaries by levellevel (paragraph, chapter, document, book, series), bounds, text, anchors to sentences — a claim without an anchor is not accepted

Value dictionary

fieldvalues
annsets.sourcegold (treebank) · silver (checked by Opus, gates) · bronze (Rust only, gates) · opus · rust · udpipe (raw run output) — CHECK in the schema
annsets.toolwho annotated: human · opus · rust · udpipe. Default — from the source: silver → opus, bronze → rust
annsets.dialectud-2.18 (everything for now); later mova and other dialects from The Mova dialect: a UD metadialect, treebanks, and converters
annsets.layerud (everything for now); later entities, events, pragmatics, passport, summaries
annsets.origintreebank (UD_English-EWT), corpus file (mova-silver-en), run and stage (calib-ud-patch300-2026-09-26/final), run and book (tales-2026-09-26/en-tales-pg2591-grimm)
annsets.methodthe block's # annotation, if present. Otherwise — the run: for calibration, the mode from calls.jsonl, effort (db/calib-runs.tsv, otherwise from the name, otherwise "unknown") and the stage. Stages follow annot files: opus-a — Opus's first view (A), final — final, rust-b — Rust
annsets.createdrun date from # annotation (…/runs/silver-ud-2026-09-25), otherwise the file modification time
annsets.gateen gates: ok or the first reason

Gates — the same for all sources:

"" means the layer is absent and is not counted as a violation. Lemma "" skips lemma checks.

Positive control on gold — 0 violations in EWT, GENTLE, PUD, ESLSpok, GUMReddit, LittlePrince, CTeTex, Pronouns and 3 of 14,353 in GUM (Gender=Fem,Masc). Red where a treebank has other conventions:

Loading (db load, db corpus)

optionvalues
--kindtreebank — the card treebanks/<language>/<code>/README.md gives license, genres, authors and part sizes; calib — an annot run directory (calls.jsonl, report.txt); corpus; tales; file (default)
--docsnewdoc (default) · idprefix · file
--matchnew — every block is a new sentence (treebanks, fairy tales); auto — a block with the same sent_id and text lands on the existing sentence, otherwise a new one (silver onto bronze); existing — only onto existing ones, otherwise an error (calibration on EWT test)
--run, --layer, --lang, --origin, --method, --license, --genre, --tooloverride what was inferred

Rules:

db corpus reads db/corpus.tsv. Order matters: texts first (treebanks, corpus), then annotations on existing sentences.

Lossless

Fairy tales (db tales)

The five-year-old test (Smart annotator — layers, context, memory). Fairy tales in the public domain in UA, EU and the US, Project Gutenberg, plain text:

#booktranslation
2591Grimms' Fairy TalesEdgar Taylor and Marian Edwardes
1597Andersen's Fairy Talesthe text does not name the translator
21Three Hundred Aesop's FablesGeorge Fyler Townsend
2781Just So Stories (Kipling)—
14838The Tale of Peter Rabbit (Potter)—

Where from:

The license of each book is detailed in the manifest and in docs.meta.

How we split. The manifest field tales says how to split a book:

A heading or marker not found is an error.

Pipeline:

  1. The PG header and footer and [Illustration] are cut off.

  2. Paragraphs — from blank lines, lines joined with a space. The paragraph type goes into # newpar_block, as in GUM: p, head (subheading), lg (indented verse).

  3. Tokens come from en::tok. Sentence boundaries — after . ! ? … and closing quotes or brackets, unless a lowercase letter follows. Tokens in each paragraph reproduce the text verbatim.

  4. UD comes from the Rust annotator en::annotate — a shared en function, the same path as rust_annotate in annot. Training — EWT train + ESLSpok train.

    Cross-check: 496 random fairy-tale sentences through annot bronze and through db tales gave 12,786 words with identical form, lemma, UPOS, XPOS, FEATS, head and relation — 0 differences.

  5. Comments:

    • document — # newdoc id, # meta::title/author/translator/book/genre/year/license/source/sourceURL/sha1;
    • paragraph — # newpar id, # newpar_block;
    • sentence — # sent_id, # text, # source, # annotation. MISC — SpaceAfter=No.
  6. Files — <book>.conllu. In the database: set ud / bronze / rust, dialect ud-2.18, run tales-2026-09-26.

Sentences that failed the gates stay in the database with the reason in gate: 122 of 10,255, 1.2%. Only gate = 'ok' counts as bronze for training. The most frequent reasons are Rust errors:

booktalesparagraphssentenceswords
Grimms' Fairy Tales629074 263121 850
Andersen's Fairy Tales189762 78667 774
Just So Stories126871 58837 523
Three Hundred Aesop's Fables3134271 56039 927
The Tale of Peter Rabbit136581 148
total4063 03310 255268 222

What is in the database (26.09.2026)

28 runs, 72 files, 2,680 documents, 109,589 sentences, 113,973 sets, 1,473,155 CoNLL-U rows.

sourcewhatsetssentenceswords
gold13 English UD 2.18 treebanks, 29 files; licenses from the cards (NC — GENTLE, GUM, LinES, ParTUT)98 98698 9861 126 448
bronzecorpus/mova-bronze-en.conllu (326) + fairy tales (10 255)10 58110 581269 894
silvercorpus/mova-silver-en.conllu: 326 of 348 landed on bronze sentences3483481 982
opus12 calibration runs: opus-a and final2 69857333 956
rustthe same runs: rust-b1 36057317 183

Example queries

db sql <db> <file> (markdown output) or duckdb -readonly $MOVA_DATA/db/mova.duckdb < db/queries/<file>.

Where Rust and Opus disagree — queries/rust-vs-opus.sql

Pairs of ud sets on the same sentence: Rust (rust-b, bronze) versus Opus's final annotation from the same run, or silver. Agreement is the share of words with the same head and relation.

Opus (final)pairsdisagreeLAS agreement, %tags and lemmas, %
mova-silver-en (versus bronze)32613886.495.7
calib-ud-patch-all300-2026-09-26/final30018682.192.6
calib-ud-patch-hints300-2026-09-26/final30022382.392.9
calib-ud-patch300-2026-09-26/final30022282.493.0
calib-ud-high300-2026-09-25/final29222977.891.4

Next, the query gives the sentences with the largest tree disagreement: "Police in the Indian capital Delhi say…" — 13 of 26 words, in 6:case→5:case; the 6:det→5:det; ….

All sets of one sentence — queries/sentence-sets.sql

The variable sent is the sentence's sent_id. Example — EWT weblog-blogspot.com_tacitusproject_…-0007:

aidsourcewhorun or treebankgatewhenhow annotated
58278goldhumanUD_English-EWTok2026-05-06UD 2.18: annotation of treebank UD_English-EWT
100060opusopuscalib-ud-patch-all300-2026-09-26/opus-aok2026-09-26patch mode · effort high · Opus's first view
101409opusopuscalib-ud-patch-all300-2026-09-26/finalok2026-09-26… · Opus final
102769rustrustcalib-ud-patch-all300-2026-09-26/rust-bok2026-09-26… · Rust annotator en
iformEWTfinalopus-arust-b
1Two2:nummod NUM2:nummod NUM2:nummod NUM2:nummod NUM
3later0:root ADV0:root ADV0:root ADV8:advmod ADV
8continues3:conj VERB3:conj VERB3:conj VERB0:root VERB
9.3:punct PUNCT3:punct PUNCT3:punct PUNCT8:punct PUNCT

(rows 2, 4–7 are identical in all)

Layer statistics by source — queries/layers-by-source.sql

layersourcewhosetssentencesdocumentsrowswordsgates ok, %
udgoldhuman98 98698 9862 1171 150 1401 126 44880.6
udbronzerust10 58110 581556269 894269 89498.8
udopusopus2 69857322133 95633 956100.0
udrustrust1 36057322117 18317 18399.6
udsilveropus3483481571 9821 982100.0

db stats also gives tables, runs, document licenses and fairy tales.

Next

Questions and answers (qa, migration v3, 26.09.2026)

Training "question — answer" pairs from the LLM per fairy-tale paragraph (qagen/, command db qa). Fields:

Pilot qagen-2026-09-26: 1,626 pairs over 300 paragraphs. View — qa_pairs.

v2 (migration v4, 26.09.2026):

Current pairs — 7,843 over all 1,440 eligible paragraphs: 5,208 explicit, 2,635 inferred.

Licence

Elamur Cosmic License (Elamur Cosmic License). Third-party data keeps its own terms, as noted where it is used.

This page in Markdown → https://md.mova.elamur.ai/free/en/docs/db/