Mova0.6 pre-release⌕
☰
home / docs / tools / db — the annotation database and its tool (DuckDB)

db — the annotation database and its tool (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.

Commands

cargo build --release -p db
D=target/release/db
$D init    <db>                         # the whole schema file, migrations included
$D migrate <db>                         # apply schema migrations; idempotent
$D load    <db> <source> <dialect> [--kind K] [--docs newdoc|idprefix|file] [--match new|auto|existing] [--run name] [--layer ud] [--lang en] [--origin O] [--method text] [--license L] [--genre G] [--tool T] <files.conllu>…
$D corpus  <db> [corpus.tsv]            # the whole corpus per data/corpus.tsv
$D tales   <db> <manifest.jsonl> <raw> <run dir> <train.conllu>…
$D export  <db> <key=value>… > out.conllu   # keys: file run source tool layer dialect origin doc sent sid aid gate
$D verify  <db> [file]                  # lossless round trip: export = file
$D stats   <db>
$D sql     <db> <file.sql>              # markdown output
$D prag    <db> <labels.tsv> --run name --source silver|rust [--tool opus|rust] [--origin O] [--method M] [--params params.json] [--path P] [--note N]
$D qa      <db> <qa.tsv> --run name [--source opus] [--tool T] [--params params.json] [--path P] [--note N] [--replaces run]

The database is mova.duckdb. Rust writes TSV and the DuckDB CLI loads it with COPY; the CLI is ~/bin/duckdb unless DUCKDB names another.

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-run>
$D init $B                     # the current schema (v4, the whole schema file with its migrations); does not overwrite an existing database
$D corpus $B                   # treebanks, Mova corpus, calibration — per data/corpus.tsv
$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
$D verify $B                   # lossless: every file byte for byte
$D stats $B                    # overview (sql/stats.sql)
$D sql $B 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, the teacher 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 (Context, layers and memory). pragmatics (migration v2, loaded by db prag, with the prag_labels view) and qa (v3 and v4, loaded by db qa) are populated; entities, mentions, events, passport and summaries are 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 the teacher, 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 (data/calib-runs.tsv, otherwise from the name, otherwise "unknown") and the stage. Stages follow annot files: opus-a — the teacher'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.

The gates also run on the gold treebanks as a positive control. They show red where a treebank follows other conventions: XPOS that is not PTB (LinES, ParTUT, CHILDES) or the old feature PronType=Int,Rel (ATIS).

Loading (db load, db corpus)

optionvalues
--kindtreebank — the treebank's row in treebanks/cards.tsv 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 data/corpus.tsv. Order matters: texts first (treebanks, corpus), then annotations on existing sentences.

Lossless

Fairy tales (db tales)

The five-year-old test (Context, layers and 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.

  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 named after the run directory.

Sentences that failed the gates stay in the database with the reason in gate. Only gate = 'ok' counts as bronze for training. Typical reasons are Rust errors:

Example queries

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

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

Pairs of ud sets on the same sentence: Rust (rust-b, bronze) versus the teacher's final annotation from the same run, or silver. Agreement is the share of words with the same head and relation, reported per teacher run with the count of disagreeing pairs and the agreement of tags and lemmas.

Next, the query gives the sentences with the largest tree disagreement, with the differing 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 · the teacher's first view
101409opusopuscalib-ud-patch-all300-2026-09-26/finalok2026-09-26… · the teacher 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

Counts of sets, sentences, documents, rows and words per layer and source, with the share of sets that pass the gates.

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

Limits

Questions and answers (qa, migrations v3 and v4)

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

Rules:

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/tools/db/