Cleaning, Structuring and Grounding Historical Data
One dataset, start to finish, with a coding agent
Jim Clifford, University of Saskatchewan
15 October 2026
Thousands of pages like this…
…became the moving atlas on the next slide. The Colonial Office List for 1913 (52nd publication), pp. 561–562. Scan digitised by Google, from the Internet Archive.
WODEHOUSE, SIR PHILIP E.—Entered the Ceylon civil service as a writer, May, 1828; … superintendent of Honduras, 1851; … governor of British Guiana; … in 1861 was appointed governor of the Cape of Good Hope.
Printed wording
Event
Historical place
Plotting seat
Year
writer, May, 1828
writer
British Ceylon
Colombo
1828
superintendent of Honduras
superintendent
British Honduras
Belize
1851
governor of British Guiana
governor
British Guiana
Georgetown
1854
governor of the Cape of Good Hope
governor
Cape Colony
Cape Town
1861
governor of Bombay (later editions; India Office List)
governor
Bombay Presidency
Bombay
1872–77
Printed clause“governor of the Cape of Good Hope”
→
Eventgovernor · 1861
→
Checked place identifierCape Colony, a Wikidata item
→
Plotting seatCape Town: a proxy, not a residence
→
ArcBritish Guiana → Cape
Two Lists, one career, mapped
Colonial Office List, 1878 · raw OCRWODEHOUSE, SIR PHILIP E., G.C.S.I. (created, 1876), K.C.B. (Creat. 1862).—Entered the Ceylon civil service as a writer, May, 1828; promoted to be assistant colonial secretary and clerk of the executive and legislative councils, Oct., 1833; district judge of Kandy, 1840; government agent for the western province, 1843; was appointed superintendent of Honduras, 1851; in Feb., 1854, governor of British Guiana; was employed in 1858 on a special mission to the government of Venezuela; and in 1861 was appointed governor of the Cape of Good Hope, and also high commissioner in South Africa. Governor of Bombay, 7th May, 1872, to 30th April, 1877.
India Office List, January 1889 · raw OCRWODEHOUSE, SIR PHILIP EDMUND, K.C.B., G.C.S.I.—Writer, Ceylon civil service, May, 1828; assistant colonial secretary, and clerk of the executive and legislative councils, Oct., 1833; district judge of Kandy, 1840; government agent for the Western Province, 1843; superintendent of Honduras, 1851; governor of British Guiana, 9th Feb., 1854; employed on a special mission to Venezuela, Feb.-May, 1858; C.B., May, 1860; governor and commander-in-chief of the Cape of Good Hope and its dependencies, and Her Majesty's high commissioner for the settling of the affairs of the territories adjacent to the eastern frontier of that colony, 29th Oct., 1861; K.C.B., July, 1862; governor of Bombay, 6th May, 1872, to 30th April, 1877; G.C.S.I., June, 1876.
One man in two Lists? The same name and a K.C.B. of 1862 in both entries link the records into one career.
The Lists disagree: Bombay from 7 May 1872 (Colonial Office) or 6 May (India Office)? Keep both readings, each with its source.
Two printed records → one linked career → arcs between dated postings, not journeys.
What the records make visible
46,926person records across two Lists
305,164career events
211,581events linked to a Wikidata place
36,431arcs for 16,287 roster records with moves (September build)
Administrative accounts of careers, offices and honours.
Person counts resolved within each List; their sum includes cross-service overlap.
Seats used for plotting; arcs connect postings, not observed journeys.
People are another matter: only 1,066 of 27,526 Colonial Office people (3.9%) link to a Wikidata person.
Whose careers does this source make easy to see? Whose does it leave out?
Three questions
The question
You decide
Structure
What is a record here?
What counts as an event
Clean
One person or two?
Merge, keep apart, review
Ground
Which place, which person?
Supported, unresolved, wrong
OCR text85 volumes
→
Structureentries → events
→
Cleaneditions → people
→
Groundnames → identifiers
→
Graph and atlasvisualise, then look
↺ See · Fix the rule · Rerun for every affected record
The source
BALMER, F. E.—Dep. chief acctnt., G.P.O., E. Africa Prot., Mar., 1919; ch. acctnt., G.P.O., Kenya, Oct., 1919; ch. acctnt., Kenya-Uganda-Tanganyika post and tel. service, 1933; senr. dep. P.M.G., do., 1935.
85 volumes OCR’d, 1867–1966, including later Dominions and Commonwealth Relations Office Lists. Services of principal officers from 1867; the full Record of Services from 1883. The same person returns in many editions.
What is OCR? Optical character recognition turns a page image into text. These volumes were read by olmOCR, an open model that returns text blocks with their position on the page. OCR errors and printers’ errors look alike in the text; the scan decides.
What is “do.”?Ditto: “the same as before”. The printer saved space by pointing back to an earlier place or office. A reader resolves it without thinking; a program has to be told.
Mark up Balmer
Choose entry 2. Tag the last two positions, their places and years.
Three minutes, then compare:
What does “do.” inherit? Where should that decision be recorded?
What is Claude Code? Anthropic’s coding agent. It runs on your own computer, reads the files you point it to, and writes and runs Python with your permission. You describe the task in plain language and judge the results; it reports what it did.
It is a commercial service, paid through a Claude subscription or per use. Here it built the pipeline; it did not read the 65,000 career records.
Real rules: mostly right, quietly wrong
Our real rule-based parser: 258 lines of Python, run on every printed biography in 66 editions. It splits a career at semicolons, knows months, abbreviations and ditto marks, and refuses an entry whole when a dated clause will not parse.
EntryPrinted clauseThe rules returnedVerdictBalmer, 1936ch. acctnt., G.P.O., Kenya, Oct., 1919place: Kenya · 1919✓ the regular shape worksBalmer, 1936senr. dep. P.M.G., do., 1935place: Dominions Office · 1935✗ two rules collideConnolly, 1940agt. gen. in London for Malta, 1930place: Western Australia · 1930✗ accepted, and wrongWodehouse, 1867and in 1861 was appointed governor of the Cape of Good Hopeentry refused → Qwen: 9 events, each placed✓ refused, not guessed
78% of 202,100 printed biographies parsed by rules
61% → 96% rules share, 1888 vs 1960 editions
3,755 places dropped mid-clause, recovered later
Rules are fast and exact on regular entries. Each new rule can collide with another, and the dangerous errors are the confident ones.
Too irregular for regular expressions alone
A familiar historical problem: semi-structured data.
The fields recur. The wording and relationships vary.
Source wording
What needs interpretation
“do., 1935”
Which preceding place does this posting inherit?
“B. Honduras, Apl., 1898”
An appointment date; “B.” belongs to the place name
Trimen’s posts, 1867–1875 and 1869–1879
Two concurrent jobs, not one sequence
Finding a year is easier than deciding what it dates.
What is a regular expression? A pattern that matches text by its shape. 1[89]\d\d finds every year from 1800 to 1999 in an entry. It cannot say which posting the year belongs to, or that “do.” points back three clauses.
Develop the hybrid with Claude Code
Agree the fields → try examples → decide which work belongs in each stage.
Stage 1 · reading every edition, about 200,000 printed biographies
Python rulesParse the regular entries; refuse the rest whole.
→
QwenReads the refused entries.
→
LinkEntries joined into people across editions.
Stage 2 · the final records, 65,239 careers
PythonExpands known abbreviations; keeps the rest as printed.
→
Qwen reads every careerOne entry in, one JSON record out: position, place and dates bound across ditto, elided and concurrent clauses.
→
Python checksDrops any place or year not in the source; applies logged fixes.
What is Qwen? A family of open-weight language models from Alibaba’s Qwen team. Open weight means the trained model can be downloaded and run on your own hardware, or reached through a hosted service. The structuring runs used Qwen3.6-35B-A3B: 35 billion parameters in all, about 3 billion active for each word-piece, so one GPU can run it quickly.
Claude Code helped build the pipeline. Qwen read the entries.
Three ways to run Qwen
The script sends each entry to a web address that speaks the OpenAI request format. Change one address to change where the model runs.
Hosted service
OpenRouter
A broker for many models, open and commercial. Pay per token; no hardware.
Used in June to compare several open models on the same 40 entries, scored against records Claude had structured in-session. Qwen3.6 came closest.
Desktop GPU
NVIDIA DGX Spark
A desktop machine with 128 GB of memory shared with its GPU. vLLM serves the model on the local network.
India Office List: 30,496 entries in 8.24 hours overnight. No per-request bill.
Research cluster
Alliance: Narval, Nibi
Canada’s national research computing. Submit a batch job; it waits in a queue, starts the model, works through a file.
Qwen3.8-27B judged 3,539 identity pairs in 35 minutes on one A100 GPU.
What is vLLM? Open-source software that loads a model onto a GPU and answers requests in the same format as the commercial services, so the same script works locally, on a cluster or through OpenRouter.
The prompt is the decisions
Extract ONLY what is printed; never invent or infer facts.
events: one object per distinct posting. EXPLODE compound clauses.
Place rule, summarised: keep printed wording; flag inheritance and stop at a stated transfer.
birth_year: only if printed. NEVER infer from the earliest career date.
What is a system prompt? The standing instructions sent with each of about 65,000 career records. The entry itself follows as the user message. Every rule here exists because an earlier run broke it.
Read records against the source
Examples from the saved extraction audit, before rule fixes
Source
Earlier output
Reading to preserve
“do., 1935”
place: “do.”
Inherit the preceding place
“Apl., 1898”
birth_year: 1898
Date of the appointment
“KT. BACH. (1920)”
two honours
One knighthood
Sampleevery tenth record, then spread across the corpus
→
Read against the sourcerecord beside the printed entry
→
Sort findingsone-off, or a recurring pattern?
→
Logged ruleor individual review
Batch run: 30,496 entries in 8.24 hours.
Replay a past correction
Balmer’s final event · saved extraction output
Beforeplace: “do.” place_inherited: true
→
RuleA literal ditto, and the previous event has a resolved place → inherit that place and log the reason
A reader resolves it at a glance. A program has to be told, and the change is logged.
Clean: forty editions, one person
A missed link leaves a fragmented career.
A false merge can silently corrupt later analysis.
Ties resolve to no merge.
199,099printed entries, Colonial Office List
→
Strong rulessame name and birth year, or a shared dated event
→
Clusterssame forename, same surname ending, so OCR variants meet
→
Dossiersa model argues each hard case; review queue for the rest
→
27,526person records, with a ledger of reasons
Dossier Every version of every biography in a cluster, laid out for one decision: births, schools, honours, the full dated career.
Ledger An append-only file of merge decisions, each with its reason, so any one can be revisited and reversed.
Decide before the answer
One edition
ABERY, Joseph
M.B.C.M.
1867 · Ceylon · assistant colonial surgeon
Six editions
CARBERY, Joseph
M.B.C.M.
1867 · Ceylon · assistant colonial surgeon
Merge, keep apart, or review? What evidence decides it?
The ledger merges them: consistent name and qualification, an identical dated appointment, and a plausible OCR loss. Preserve both entries and the reason.
Keep apart; keep the evidence
Born 1836
A’BECKETT, Thomas
Lincoln’s Inn
Victorian judge · knighted 1909
Born 1898
BECKETT, Thomas Rumbold
Judd School
Nigerian telegraph engineer
Different schools and careers, not only different birth years. Keep apart.
The merge rules, in brief Merge only on positive evidence: an overlapping dated event, a shared honour with its year, near-identical education, or an OCR variant with a consistent career. Birth year alone never splits: it is often misread. Different spelled forenames keep people apart.
Ground: retrieve, read, decide
Every plotted posting needs a place.
Every shared person identifier asserts an identity.
Mention“Victoria, 1912”
→
Retrievecandidate items from a search tool
→
Readtype, dates, location against the career
→
Decide and recordsupported · unresolved · wrong, with evidence and date
No supported match? Keep the local identifier.
What is Wikidata? A QID? A free, shared knowledge base. Each item has an identifier: Q plus a number. Q1800510 is Philip Wodehouse. Linking to it lets other projects join their data to ours.
What is MCP? Model Context Protocol: a standard way to hand a model a tool, such as a Wikidata search. The answer comes back as retrieved evidence, not from the model’s memory.
One entry, three kinds of link
LOGAN, Sir William Marston, K.B.E. (1944) … —b. 1889; ed. Bristol and Brasenose Coll., Oxford …; ch. sec., N. Rhod., 1937; ag. gov., Dec., 1937 …; gov. and c.-in-c., Seychelles, 1942; retd., 1947 …
Places · WikidataMCP search“N. Rhod.” → Northern Rhodesia“Seychelles” → SeychellesRead the candidate’s type, country and dates; prefer the colonial-era entity.131,343 of 152,132 placed events (86%)
Person · QLever queryname · b. 1889 · Northern Rhodesia → William Marston LoganQ108649219. Surname, given names, birth year and office all agree; died 1968, after his last listing.1,066 of 27,526 people (3.9%)
School · WikidataMCP search“Brasenose Coll., Oxford” → Brasenose CollegeQ899030. “Bristol” stays unlinked: a city, not a named school.11,056 of 15,775 education links (70%)
Places are many and easy to check. People need more evidence, so most stay with a local identifier.
WikidataMCP: search by meaning, then read
Printed string“Penang, S. Sttlnts.”
→
Normalised query“Penang Straits Settlements”
→
search_itemscandidates ranked by meaning, not spelling
→
get_statementsinstance of · country · located in · coordinates
→
Cache the decisionwith its evidence, so the graph rebuilds without new lookups
Rules the agent followed
Prefer the colonial-era entity: British Ceylon, not Sri Lanka.
Leave compounds and ambiguous initialisms (“Ken. and Uga.”, “N.S.”) ungrounded.
Never write an identifier from memory.
What the checks caught
A later check of every identifier caught five invented by parallel agents: Dehra, Ayrshire, Rohilkund, Neemuch, Narsinghpur.
Fuzzy reuse flipped N./S. and E./W.; only exact spelling variants were folded (166).
What is vector search? Each Wikidata item is stored as a list of numbers that captures what its description means, so a query finds items close in meaning, not only in spelling. It still returns candidates, not answers: on 11 October “Penang Straits Settlements” put the whole colony first, the modern Malaysian state eighth and Penang Island 24th. Reading the statements decides.
QLever: ask exactly when you can
Who held a position named “Governor … Northern Rhodesia”? Give their birth dates.
2 Same surname and given names: LOGAN, William Marston
3 Death check: died 1968; last listed 1948 ✓
Wikidata holders returnedborn
William Marston Logan1889
Sir John Maybin1889
John Waddington1890
Hubert Winthrop Young1885
Ronald Storrs1881
Herbert Stanley1872
James Crawford Maxwell1869
1,095 senior officials without a link
198 colony names queried
124 people linked this way
What are SPARQL and QLever? SPARQL is the query language for Wikidata: ask for every item matching a pattern of statements, such as “held this office and was born in…”. QLever, from the University of Freiburg, is a fast public engine that runs such queries over all of Wikidata.
What reading the corpus costs
Measured on a random sample of 2,000 India Office entries and 2,000 Qwen records:
~640tokens read per entry: 470 of prompt, about 150 of entry
~330tokens written per entry: the JSON record
30,496India Office List entries
Route
Price
30,496 entries
OpenRouter, Qwen3.8-27B, 8 Oct
$0.0239 read / $2.175 written per million tokens (50% promotion)
≈ $22
Same model, regular price
about twice the promotion
≈ $45
DGX Spark desktop, Qwen3.6 (as run)
the machine and its electricity
8.24 hours, no bill per request
Almost all of the hosted cost is the writing: the record the model returns, not the text it reads.
What is a token? The unit models read and write in, roughly three-quarters of a word. Services charge per million tokens, with separate prices for reading (input) and writing (output).
A past repair: which Victoria?
Brewster’s entry: “defeated by Sir Richard McBride, in Victoria, 1912.”
An earlier atlas placed a British Columbia politician in Australia.
Colony of Victoria, AustraliaVictoria, British ColumbiaVictoria, Hong KongVictoria, SeychellesVictoria, Labuan
One printed word; several imperial places with that name.
BeforeOne “Victoria” lookup, reused for every entry that printed the word → Australia
→
Completed fixResolve the place per person, from the surrounding career: McBride, the legislative assembly, British Columbia
→
AfterVictoria, British Columbia; every affected entry re-emitted
Which Victoria?
Brewster: “defeated by Sir Richard McBride, in Victoria, 1912.”
Resolve the place per person, from the surrounding career. Every affected entry is re-emitted.
A past repair: what counts as an event?
Source
Earlier graph, after the place fix
Defeated, Victoria, 1912
Legislative assembly member for Victoria, 1912–1916
Would you accept this office-held assertion?
A defeat does not establish an appointment. Repaired: preserve the defeat as an event; remove the office-held assertion. Check other defeat clauses.
A plausible map can conceal a misread source.
What a map cannot check
The map shows
What looks wrong
A politician in the wrong hemisphere. A jump no career could make. Two nodes with one name.
The map hides
What is missing
A missed link leaves two plausible short careers. An ungrounded posting simply vanishes. A false merge can look like an ordinary career.
Use a labelled sample, including the cases the pipeline rejected.
Check both the repaired cases and other records the rule changed.
Three decisions, with reasons
Work from the saved teaching cases.
R1 · Blakely: accept the record? R3 · Abery / Carbery: accept the merge? R8 · Harris: accept a link with a disputed birth year?
Decide. Give the evidence. Say what you would check next.