<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Provenance on Boris Dupoux</title><link>https://borisdupoux.com/tags/provenance/</link><description>Recent content in Provenance on Boris Dupoux</description><generator>Hugo -- gohugo.io</generator><language>en-us</language><lastBuildDate>Tue, 01 Sep 2026 00:00:00 +0000</lastBuildDate><atom:link href="https://borisdupoux.com/tags/provenance/index.xml" rel="self" type="application/rss+xml"/><item><title>Explainable Processing Atoms for Documents &amp; Data</title><link>https://borisdupoux.com/research/processing-atoms/</link><pubDate>Tue, 01 Sep 2026 00:00:00 +0000</pubDate><guid>https://borisdupoux.com/research/processing-atoms/</guid><description>&lt;h2 id="context"&gt;&lt;a href="#context" class="header-anchor"&gt;&lt;/a&gt;Context
&lt;/h2&gt;&lt;p&gt;Production systems for document reconciliation and OCR, built for clients such as a
wholesale distributor and an accounting firm, left several codebases that each solved
the same sub-problems (page routing, OCR tuning, LLM extraction, field comparison)
with their own bugs. The atoms library consolidates them into one package. Its central
question is the one behind the original systems: &lt;strong&gt;can a document check be automated so
that a human can audit and contest every verdict?&lt;/strong&gt; Extracting a field is not enough. The
system must state whether two documents agree, why, and from what evidence.&lt;/p&gt;
&lt;h2 id="approach"&gt;&lt;a href="#approach" class="header-anchor"&gt;&lt;/a&gt;Approach
&lt;/h2&gt;&lt;p&gt;Everything is built from &lt;strong&gt;atoms&lt;/strong&gt;. An atom has one responsibility, typed inputs and
outputs, no global state, and injected dependencies (LLM client, OCR engine,
storage). A pipeline is a composition of atoms, either written in code or declared
as a YAML recipe that is validated when it loads, before anything runs.&lt;/p&gt;
&lt;pre class="mermaid" style="visibility:hidden"&gt;flowchart TB
 A["Input documents (PDF, scans, CSV, mail)"] --&gt; B{"Page routing: native text or OCR?"}
 B -- "native" --&gt; C["Positioned native text"]
 B -- "scan" --&gt; D["PaddleOCR (production settings)"]
 C --&gt; E["Segmentation &amp; classification"]
 D --&gt; E
 E --&gt; F["LLM JSON extraction + verbatim grounding check"]
 F --&gt; G["Cross-document reconciliation (invoice / delivery note / order)"]
 G --&gt; H["Tri-state verdict + named reason + trace"]
 H --&gt; I["Report / human review"]&lt;/pre&gt;&lt;h2 id="the-catalogue"&gt;&lt;a href="#the-catalogue" class="header-anchor"&gt;&lt;/a&gt;The catalogue
&lt;/h2&gt;&lt;p&gt;The 459 atoms are grouped into 32 families, one Python package each, ordered along the
processing chain: &lt;strong&gt;capture&lt;/strong&gt; the world, &lt;strong&gt;understand&lt;/strong&gt; it, &lt;strong&gt;reconcile&lt;/strong&gt; and decide,
&lt;strong&gt;deliver&lt;/strong&gt;, then &lt;strong&gt;operate&lt;/strong&gt; in production. Two further packages assemble them: &lt;code&gt;steps&lt;/code&gt;
(77 named, parameterised steps) and &lt;code&gt;recipes&lt;/code&gt; (the YAML loader and validator).&lt;/p&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;Stage&lt;/th&gt;
					&lt;th&gt;Family&lt;/th&gt;
					&lt;th style="text-align: right"&gt;Atoms&lt;/th&gt;
					&lt;th&gt;Role&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;Base&lt;/td&gt;
					&lt;td&gt;&lt;code&gt;core&lt;/code&gt;&lt;/td&gt;
					&lt;td style="text-align: right"&gt;25&lt;/td&gt;
					&lt;td&gt;pivot types, pipeline, configuration, resilience (retry, rate limits), files, concurrency&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;Capture&lt;/td&gt;
					&lt;td&gt;&lt;code&gt;connect&lt;/code&gt;&lt;/td&gt;
					&lt;td style="text-align: right"&gt;40&lt;/td&gt;
					&lt;td&gt;resilient HTTP, mailboxes (Graph, IMAP), SFTP, OneDrive, read-only ERP source, open data, audit trail&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;/td&gt;
					&lt;td&gt;&lt;code&gt;google&lt;/code&gt;&lt;/td&gt;
					&lt;td style="text-align: right"&gt;24&lt;/td&gt;
					&lt;td&gt;Gmail, Sheets, Drive, Apps Script, with resilient execution&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;/td&gt;
					&lt;td&gt;&lt;code&gt;web&lt;/code&gt;&lt;/td&gt;
					&lt;td style="text-align: right"&gt;17&lt;/td&gt;
					&lt;td&gt;driver-agnostic web robots (Selenium, Playwright): waits, resumable skeleton, forms&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;/td&gt;
					&lt;td&gt;&lt;code&gt;pdf&lt;/code&gt;&lt;/td&gt;
					&lt;td style="text-align: right"&gt;9&lt;/td&gt;
					&lt;td&gt;positioned native text, OCR-or-not decision, rendering, page handling, duplicates&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;/td&gt;
					&lt;td&gt;&lt;code&gt;ocr&lt;/code&gt;&lt;/td&gt;
					&lt;td style="text-align: right"&gt;9&lt;/td&gt;
					&lt;td&gt;PaddleOCR with production settings, EasyOCR&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;/td&gt;
					&lt;td&gt;&lt;code&gt;layout&lt;/code&gt;&lt;/td&gt;
					&lt;td style="text-align: right"&gt;3&lt;/td&gt;
					&lt;td&gt;page geometry: lines, columns, deskew, monospace rendering&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;/td&gt;
					&lt;td&gt;&lt;code&gt;audio&lt;/code&gt;&lt;/td&gt;
					&lt;td style="text-align: right"&gt;16&lt;/td&gt;
					&lt;td&gt;chunked transcription, segment post-processing, microphone ↔ screen alignment&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;/td&gt;
					&lt;td&gt;&lt;code&gt;vision&lt;/code&gt;&lt;/td&gt;
					&lt;td style="text-align: right"&gt;32&lt;/td&gt;
					&lt;td&gt;video / RTSP streams, detection, tracking, depth, segmentation, calibration&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;/td&gt;
					&lt;td&gt;&lt;code&gt;vlm&lt;/code&gt;&lt;/td&gt;
					&lt;td style="text-align: right"&gt;10&lt;/td&gt;
					&lt;td&gt;screen reading with vision-language models: change detection, label location&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;/td&gt;
					&lt;td&gt;&lt;code&gt;geo&lt;/code&gt;&lt;/td&gt;
					&lt;td style="text-align: right"&gt;11&lt;/td&gt;
					&lt;td&gt;geodesic measures, combined GPS fixes, line-distance filtering, spatial SQL&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;/td&gt;
					&lt;td&gt;&lt;code&gt;calendar&lt;/code&gt;&lt;/td&gt;
					&lt;td style="text-align: right"&gt;4&lt;/td&gt;
					&lt;td&gt;public holidays, working days and opening hours&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;Understand&lt;/td&gt;
					&lt;td&gt;&lt;code&gt;text&lt;/code&gt;&lt;/td&gt;
					&lt;td style="text-align: right"&gt;32&lt;/td&gt;
					&lt;td&gt;numbers and amounts, dates, normalisation, pattern-based fields, identifiers&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;/td&gt;
					&lt;td&gt;&lt;code&gt;segment&lt;/code&gt;&lt;/td&gt;
					&lt;td style="text-align: right"&gt;7&lt;/td&gt;
					&lt;td&gt;pages → logical documents, typed document numbers, page nature&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;/td&gt;
					&lt;td&gt;&lt;code&gt;classify&lt;/code&gt;&lt;/td&gt;
					&lt;td style="text-align: right"&gt;12&lt;/td&gt;
					&lt;td&gt;file name, sender, weighted patterns, TF-IDF, visual and LLM classifiers, cascades&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;/td&gt;
					&lt;td&gt;&lt;code&gt;llm&lt;/code&gt;&lt;/td&gt;
					&lt;td style="text-align: right"&gt;24&lt;/td&gt;
					&lt;td&gt;provider-agnostic contract, providers, pool, prompts, structured extraction&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;/td&gt;
					&lt;td&gt;&lt;code&gt;anchor&lt;/code&gt;&lt;/td&gt;
					&lt;td style="text-align: right"&gt;5&lt;/td&gt;
					&lt;td&gt;fields and tables located relative to printed labels, native text or OCR&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;/td&gt;
					&lt;td&gt;&lt;code&gt;kb&lt;/code&gt;&lt;/td&gt;
					&lt;td style="text-align: right"&gt;8&lt;/td&gt;
					&lt;td&gt;traced knowledge base: validated event log folded into a SQLite index&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;Reconcile&lt;/td&gt;
					&lt;td&gt;&lt;code&gt;match&lt;/code&gt;&lt;/td&gt;
					&lt;td style="text-align: right"&gt;25&lt;/td&gt;
					&lt;td&gt;EAN/GTIN, indexes, multi-criteria scores, optimal assignment, entity resolution&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;/td&gt;
					&lt;td&gt;&lt;code&gt;compare&lt;/code&gt;&lt;/td&gt;
					&lt;td style="text-align: right"&gt;9&lt;/td&gt;
					&lt;td&gt;tri-state, explainable comparators for fields, records and sets&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;/td&gt;
					&lt;td&gt;&lt;code&gt;check&lt;/code&gt;&lt;/td&gt;
					&lt;td style="text-align: right"&gt;9&lt;/td&gt;
					&lt;td&gt;generic tri-state checks: totals, VAT, balance, completeness, plausibility&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;/td&gt;
					&lt;td&gt;&lt;code&gt;reconcile&lt;/code&gt;&lt;/td&gt;
					&lt;td style="text-align: right"&gt;12&lt;/td&gt;
					&lt;td&gt;set pairing, order → delivery → invoice chains, N-way, ledger and bank matching&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;/td&gt;
					&lt;td&gt;&lt;code&gt;validate&lt;/code&gt;&lt;/td&gt;
					&lt;td style="text-align: right"&gt;14&lt;/td&gt;
					&lt;td&gt;rules engine, identifiers, amount consistency, corrected LLM verdicts, final decision&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;/td&gt;
					&lt;td&gt;&lt;code&gt;rules&lt;/code&gt;&lt;/td&gt;
					&lt;td style="text-align: right"&gt;8&lt;/td&gt;
					&lt;td&gt;safe condition language, explainable first-match tables, escalation, state machines&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;/td&gt;
					&lt;td&gt;&lt;code&gt;events&lt;/code&gt;&lt;/td&gt;
					&lt;td style="text-align: right"&gt;11&lt;/td&gt;
					&lt;td&gt;timestamped events: windows, expected-event watch, sessions, status transitions&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;/td&gt;
					&lt;td&gt;&lt;code&gt;analytics&lt;/code&gt;&lt;/td&gt;
					&lt;td style="text-align: right"&gt;18&lt;/td&gt;
					&lt;td&gt;supply-chain analytics without pandas: demand, stock-outs, service level, safety stock&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;Deliver&lt;/td&gt;
					&lt;td&gt;&lt;code&gt;export&lt;/code&gt;&lt;/td&gt;
					&lt;td style="text-align: right"&gt;8&lt;/td&gt;
					&lt;td&gt;CSV for French Excel, shared Excel workbooks, HTML / Markdown / PDF reports&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;/td&gt;
					&lt;td&gt;&lt;code&gt;report&lt;/code&gt;&lt;/td&gt;
					&lt;td style="text-align: right"&gt;6&lt;/td&gt;
					&lt;td&gt;persistable discrepancies, summaries, review queue&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;/td&gt;
					&lt;td&gt;&lt;code&gt;render&lt;/code&gt;&lt;/td&gt;
					&lt;td style="text-align: right"&gt;12&lt;/td&gt;
					&lt;td&gt;HTML deliverables: pre-rendered Mermaid, ECharts charts, GeoJSON maps&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;/td&gt;
					&lt;td&gt;&lt;code&gt;diagram&lt;/code&gt;&lt;/td&gt;
					&lt;td style="text-align: right"&gt;6&lt;/td&gt;
					&lt;td&gt;process diagrams: draw.io / BPMN JSON in, Mermaid and BPMN XML out&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;Operate&lt;/td&gt;
					&lt;td&gt;&lt;code&gt;ops&lt;/code&gt;&lt;/td&gt;
					&lt;td style="text-align: right"&gt;26&lt;/td&gt;
					&lt;td&gt;high availability (heartbeats, leader election), scheduled daemon, monitoring, pre-flight checks&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;/td&gt;
					&lt;td&gt;&lt;code&gt;qa&lt;/code&gt;&lt;/td&gt;
					&lt;td style="text-align: right"&gt;7&lt;/td&gt;
					&lt;td&gt;editorial non-regression (golden files), simulation replay, inter-annotator agreement&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 id="named-recipes"&gt;&lt;a href="#named-recipes" class="header-anchor"&gt;&lt;/a&gt;Named recipes
&lt;/h3&gt;&lt;p&gt;Each of the 17 recipes is a YAML list of steps, named &lt;code&gt;&amp;lt;family&amp;gt;.&amp;lt;atom&amp;gt;&lt;/code&gt;. Three examples:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;invoice_multi_doc&lt;/code&gt;&lt;/strong&gt;: a batch of multi-document PDFs becomes checked logical
documents. &lt;code&gt;io.collect&lt;/code&gt; → &lt;code&gt;pdf.read&lt;/code&gt; → &lt;code&gt;segment.documents&lt;/code&gt; → &lt;code&gt;classify.documents&lt;/code&gt; →
&lt;code&gt;llm.extract&lt;/code&gt; → &lt;code&gt;validate.ids&lt;/code&gt; → &lt;code&gt;validate.amounts&lt;/code&gt; → &lt;code&gt;validate.decide&lt;/code&gt; → &lt;code&gt;export.csv&lt;/code&gt;
→ &lt;code&gt;google.sheets.upsert&lt;/code&gt; → &lt;code&gt;connect.notify&lt;/code&gt;. Each document ends as valid, to check, or invalid.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;doc_reconciliation&lt;/code&gt;&lt;/strong&gt;: a file of invoices, credit notes and delivery notes, checked
against orders. Guarded LLM extraction (&lt;code&gt;llm.extract_docs&lt;/code&gt;) is cross-checked with
pattern extraction (&lt;code&gt;extract.regex&lt;/code&gt;, &lt;code&gt;compare.extractions&lt;/code&gt;, &lt;code&gt;reconcile.fields&lt;/code&gt;), then
&lt;code&gt;check.document&lt;/code&gt; and &lt;code&gt;check.completeness&lt;/code&gt;, lines against the order reference
(&lt;code&gt;compare.reference&lt;/code&gt;), the order → delivery → invoice chain (&lt;code&gt;reconcile.nway&lt;/code&gt;), and a
discrepancy file plus review queue (&lt;code&gt;report.discrepancies&lt;/code&gt;, &lt;code&gt;report.review&lt;/code&gt;). 17 steps.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;meeting_transcript&lt;/code&gt;&lt;/strong&gt;: a meeting recording becomes a timestamped transcript with
speakers. &lt;code&gt;audio.decode&lt;/code&gt; → &lt;code&gt;audio.vad&lt;/code&gt; → &lt;code&gt;audio.transcribe&lt;/code&gt; → &lt;code&gt;audio.diarize&lt;/code&gt; →
&lt;code&gt;audio.speakers&lt;/code&gt; → &lt;code&gt;audio.segments&lt;/code&gt; → &lt;code&gt;export.csv&lt;/code&gt; → &lt;code&gt;export.transcript&lt;/code&gt; → &lt;code&gt;connect.notify&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The others include &lt;code&gt;delivery_vs_order&lt;/code&gt;, &lt;code&gt;ledger_reconciliation&lt;/code&gt;, &lt;code&gt;invoice_dedup&lt;/code&gt;,
&lt;code&gt;article_base_cleaning&lt;/code&gt;, &lt;code&gt;stock_policy&lt;/code&gt;, &lt;code&gt;mail_to_sheet&lt;/code&gt;, &lt;code&gt;draft_mails&lt;/code&gt;,
&lt;code&gt;pdf_text_extract&lt;/code&gt;, &lt;code&gt;rubric_eval&lt;/code&gt;, &lt;code&gt;screen_session&lt;/code&gt;, &lt;code&gt;camera_flow&lt;/code&gt; and
&lt;code&gt;process_from_transcript&lt;/code&gt;.&lt;/p&gt;
&lt;h2 id="the-atlas"&gt;&lt;a href="#the-atlas" class="header-anchor"&gt;&lt;/a&gt;The Atlas
&lt;/h2&gt;&lt;p&gt;The Atlas is a single offline HTML page generated from the code itself (syntax trees and
YAML), with no hand-written data. It draws the catalogue as a periodic table: each family
is an element with a symbol, each module an atom, each recipe a molecule. It also flags
atoms that lack documentation, tests or a usage example. The interface is in French.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Atlas, “Capture” series: one element per family (connect, google, web, pdf, ocr, layout, audio, vision, vlm, geo, calendar), with its atoms listed below" class="gallery-image" data-flex-basis="367px" data-flex-grow="153" height="920" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://borisdupoux.com/research/processing-atoms/atlas-capture-series.png" srcset="https://borisdupoux.com/research/processing-atoms/atlas-capture-series_hu_dfcfb778a533826c.png 800w, https://borisdupoux.com/research/processing-atoms/atlas-capture-series.png 1408w" width="1408"&gt;
&lt;em&gt;The &amp;ldquo;Capture&amp;rdquo; series of the periodic table. Each box is a family, with its symbol, number of modules and size in thousands of lines; each tile below it is an atom.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;img alt="Atlas, “Reconcile” series: match, compare, check, reconcile, validate, rules, events, analytics" class="gallery-image" data-flex-basis="536px" data-flex-grow="223" height="630" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://borisdupoux.com/research/processing-atoms/atlas-reconcile-series.png" srcset="https://borisdupoux.com/research/processing-atoms/atlas-reconcile-series_hu_872c082d4171f5de.png 800w, https://borisdupoux.com/research/processing-atoms/atlas-reconcile-series.png 1408w" width="1408"&gt;
&lt;em&gt;The &amp;ldquo;Reconcile&amp;rdquo; series: the families that pair, compare, check and decide.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;img alt="Atlas page for the compare.fields atom: purpose, dominance rules and full function signature" class="gallery-image" data-flex-basis="170px" data-flex-grow="71" height="900" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://borisdupoux.com/research/processing-atoms/atlas-atom-compare-fields.png" width="640"&gt;
&lt;em&gt;An atom&amp;rsquo;s page (&lt;code&gt;compare.fields&lt;/code&gt;): what it does, taken from its docstring, then each public name with its full signature.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;img alt="Atlas view of the invoice_multi_doc recipe as a molecule: 11 steps linked by the context keys they pass on" class="gallery-image" data-flex-basis="590px" data-flex-grow="246" height="572" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://borisdupoux.com/research/processing-atoms/atlas-recipe-invoice-multi-doc.png" srcset="https://borisdupoux.com/research/processing-atoms/atlas-recipe-invoice-multi-doc_hu_9f54a6aa2766961a.png 800w, https://borisdupoux.com/research/processing-atoms/atlas-recipe-invoice-multi-doc.png 1408w" width="1408"&gt;
&lt;em&gt;The &lt;code&gt;invoice_multi_doc&lt;/code&gt; recipe as a molecule: 11 steps, the context keys that link them, and the services they need (dashed when optional).&lt;/em&gt;&lt;/p&gt;
&lt;h2 id="methods"&gt;&lt;a href="#methods" class="header-anchor"&gt;&lt;/a&gt;Methods
&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Tri-state results.&lt;/strong&gt; Every comparison returns &lt;code&gt;ok ∈ {True, False, None}&lt;/code&gt;
(conforms / discrepancy / inconclusive) with a named reason code and a trace. A
missing or unreadable value counts as inconclusive, never as an anomaly. Inconclusive
results are still counted, not silently dropped.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Hybrid native-text/OCR routing.&lt;/strong&gt; Each PDF page is classified as native, OCR or
empty, based on how much native text it holds and how much of it is covered by
images. OCR runs only where it is needed.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Grounded LLM extraction.&lt;/strong&gt; Structured JSON extraction is validated against
Pydantic schemas. Each extracted value is then checked verbatim against the source
text, so altered or invented values are flagged.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Cross-document reconciliation.&lt;/strong&gt; Invoices, delivery notes and orders are compared
line by line using named tolerances and dominance rules. For example, a matching
line amount can override a price difference.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Provenance.&lt;/strong&gt; Every ported atom carries a &lt;code&gt;Provenance:&lt;/code&gt; header and an entry tracing
it to &lt;code&gt;repo@commit:path:lines&lt;/code&gt; in the original production code. Bugs found while
porting were fixed and each fix is covered by a non-regression test.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Enforced architecture.&lt;/strong&gt; Layering is checked by 30 import contracts: an atom
imports only the &lt;code&gt;core&lt;/code&gt; package. Strict typing, a ≥ 80 % coverage gate and a check that
no client names appear in source code are also enforced.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Device abstraction.&lt;/strong&gt; Vision and audio atoms target &lt;code&gt;cuda&lt;/code&gt;, &lt;code&gt;mps&lt;/code&gt; or &lt;code&gt;cpu&lt;/code&gt; via one setting.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;End-to-end evaluation.&lt;/strong&gt; Tests run with real engines (PaddleOCR, Ollama &lt;code&gt;qwen3:8b&lt;/code&gt;
and &lt;code&gt;qwen3-vl:8b&lt;/code&gt;) on data with known ground truth. Each metric is recorded against a
threshold in a JSON/HTML report. Metrics that miss their threshold are kept and shown
as failing.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="results"&gt;&lt;a href="#results" class="header-anchor"&gt;&lt;/a&gt;Results
&lt;/h2&gt;&lt;p&gt;Codebase (current tree): 459 atom modules in 32 families, 77 parameterised steps,
17 YAML recipes, 30 import contracts. The suite collects 2,959 unit and recipe tests,
plus 109 end-to-end tests that run separately. All 17 recipes have at least one
end-to-end test, and 34 of the 35 Atlas elements are exercised end to end.&lt;/p&gt;
&lt;p&gt;Selected end-to-end scores (synthetic documents with known ground truth, local 8B models):&lt;/p&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;Task&lt;/th&gt;
					&lt;th&gt;Engine&lt;/th&gt;
					&lt;th&gt;Measured&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;Invoice + delivery note vs. order reconciliation&lt;/td&gt;
					&lt;td&gt;&lt;code&gt;qwen3:8b&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;document types and fields 14/14; expected discrepancies 5/5; 0 false discrepancies on the compliant case&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;LLM JSON extraction (4 native invoices)&lt;/td&gt;
					&lt;td&gt;&lt;code&gt;qwen3:8b&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;valid JSON 4/4; exact fields 28/28; all deliberately altered values caught by grounding&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;OCR on a photographed label&lt;/td&gt;
					&lt;td&gt;PaddleOCR&lt;/td&gt;
					&lt;td&gt;word recall 0.94 on the raw photo; a contrast-enhancement defect (0.65–0.71) was found and fixed (0.88–0.94)&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;Table reconstruction from OCR&lt;/td&gt;
					&lt;td&gt;PaddleOCR&lt;/td&gt;
					&lt;td&gt;100 % exact cells (proportional font), 95 % (monospace)&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2 id="limitations--next-steps"&gt;&lt;a href="#limitations--next-steps" class="header-anchor"&gt;&lt;/a&gt;Limitations &amp;amp; next steps
&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;Verbatim grounding cannot catch a value that exists in the document but was put in
the wrong field: an absent field was wrongly filled in 1 of 8 cases.&lt;/li&gt;
&lt;li&gt;The 8B model judged a +50 % quantity gap a &amp;ldquo;minor&amp;rdquo; discrepancy, against the
instruction. LLM verdicts are therefore normalised and kept downstream of
deterministic rules.&lt;/li&gt;
&lt;li&gt;Scores come mostly from synthetic data. A real-document test set is being built
outside the repository, and only cases that have been reviewed are scored.&lt;/li&gt;
&lt;li&gt;Next steps: measure the automatic-reconciliation rate and the false-positive rate
on production data.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="stack"&gt;&lt;a href="#stack" class="header-anchor"&gt;&lt;/a&gt;Stack
&lt;/h2&gt;&lt;p&gt;&lt;code&gt;Python&lt;/code&gt; · &lt;code&gt;Pydantic&lt;/code&gt; · &lt;code&gt;PyYAML&lt;/code&gt; · &lt;code&gt;PaddleOCR&lt;/code&gt; · &lt;code&gt;Ollama&lt;/code&gt; (&lt;code&gt;qwen3:8b&lt;/code&gt;, &lt;code&gt;qwen3-vl:8b&lt;/code&gt;) · &lt;code&gt;Mistral / Anthropic / OpenAI APIs&lt;/code&gt; · &lt;code&gt;pytest&lt;/code&gt; · &lt;code&gt;mypy&lt;/code&gt; · &lt;code&gt;import-linter&lt;/code&gt; · &lt;code&gt;ruff&lt;/code&gt;&lt;/p&gt;</description></item><item><title>Mission Knowledge Graphs: From Consulting Evidence to an Ontology Queried Through MCP</title><link>https://borisdupoux.com/research/mission-knowledge-graph/</link><pubDate>Tue, 01 Sep 2026 00:00:00 +0000</pubDate><guid>https://borisdupoux.com/research/mission-knowledge-graph/</guid><description>&lt;h2 id="context"&gt;&lt;a href="#context" class="header-anchor"&gt;&lt;/a&gt;Context
&lt;/h2&gt;&lt;p&gt;Process-observation consulting produces flowcharts, interview notes, screen recordings and
recommendations. Once a mission is delivered, that material is effectively dead: the next
mission starts from a blank page, even when the answer was already found elsewhere.
The research question is how to turn deliverables from &lt;strong&gt;documents into typed, sourced,
queryable data&lt;/strong&gt;, so that an assistant can answer &amp;ldquo;where have we seen this before, and what
did we conclude?&amp;rdquo; while citing verifiable evidence.&lt;/p&gt;
&lt;p&gt;Two requirements structure the work: &lt;strong&gt;traceability&lt;/strong&gt; (every assertion points to its proof)
and a strict &lt;strong&gt;separation between evidence and knowledge&lt;/strong&gt; (what was observed is not what was
generalised). Retrieval-augmented generation is fast to build but cites passages, not
checkable assertions; a formal ontology is traceable but expensive. This project explores a
practical path between the two.&lt;/p&gt;
&lt;h2 id="approach"&gt;&lt;a href="#approach" class="header-anchor"&gt;&lt;/a&gt;Approach
&lt;/h2&gt;&lt;pre class="mermaid" style="visibility:hidden"&gt;flowchart TB
 A["`Raw mission sources
immutable, hashed`"] --&gt; B["`Append-only event log
source of truth`"]
 B --&gt; C["`Rebuildable SQLite base
disposable`"]
 C --&gt; D[Deterministic extraction]
 C --&gt; E["`LLM extraction
proposer only`"]
 D --&gt; F["`Proposed nodes and edges
with provenance`"]
 E --&gt; F
 F --&gt; G{"`Human review
outside MCP`"}
 G --&gt;|validated| H[Knowledge graph]
 H --&gt; I[MCP: evidence]
 H --&gt; J[MCP: knowledge]
 H --&gt; K[MCP: ontology]
 I --&gt; L[AI assistant]
 J --&gt; L
 K --&gt; L&lt;/pre&gt;&lt;p&gt;The core design choice is to split the interface into &lt;strong&gt;three MCP servers by epistemic
status&lt;/strong&gt;: &lt;em&gt;evidence&lt;/em&gt; (attested, sourced observations), &lt;em&gt;knowledge&lt;/em&gt; (generalisations), and
&lt;em&gt;ontology&lt;/em&gt; (the meta-model itself). The split prevents an assistant from presenting a
generalisation as an observation.&lt;/p&gt;
&lt;pre class="mermaid" style="visibility:hidden"&gt;flowchart TB
 subgraph SCHEMA["SCHEMA · naming"]
 ON["`ontology server
How do we name it?`"]
 end
 subgraph PROVEN["PROVEN · observed"]
 EV["`evidence server
What did we capture, and where?`"]
 end
 subgraph GENERAL["GENERALISED · concluded"]
 KN["`knowledge server
What do we conclude?
proposes, never validates`"]
 end
 ON -. "types" .-&gt; EV
 EV == "grounds every conclusion" ==&gt; KN
 EV --&gt; AI["`AI assistant
answers labelled by status`"]
 KN --&gt; AI
 ON --&gt; AI&lt;/pre&gt;&lt;p&gt;The graph is single; only the façades are split. The boundary that matters runs between
evidence and knowledge: the &lt;em&gt;evidence&lt;/em&gt; server only returns what was observed and where, the
&lt;em&gt;knowledge&lt;/em&gt; server returns conclusions that must chain back to that evidence, and no MCP
tool can mark anything as validated.&lt;/p&gt;
&lt;h2 id="methods"&gt;&lt;a href="#methods" class="header-anchor"&gt;&lt;/a&gt;Methods
&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Meta-model.&lt;/strong&gt; A fixed core plus mission profiles and a versioned ontology (v0). Each
node carries an origin (structural, deterministic, LLM-extracted) and a status
(proposed → validated).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Invariants (I1–I6).&lt;/strong&gt; Provenance is mandatory on every derived node and edge; a
recommendation must chain back to a sourced finding; LLM-extracted quotes must be literal
substrings of their fragment; &amp;ldquo;validated&amp;rdquo; requires a recorded human decision. Raw sources
are immutable; the database is disposable and rebuilt from the log.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Competency questions before implementation.&lt;/strong&gt; A v0 set of 33 questions, typed as
factual, aggregate, traceability, contradiction, temporal, cross-mission and &lt;strong&gt;negative&lt;/strong&gt;
(requests the system must refuse, e.g. cross-client leakage), in an executable format with
expected answers and references.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Evaluation protocol.&lt;/strong&gt; At least 30 natural-language tasks, scored automatically or by a
judge. Gate thresholds were fixed up front: accuracy ≥ 0.8, valid citations ≥ 0.98,
grounding ≥ 0.95, leakage = 0, median ≤ 6 tool calls per task; each question solvable in
≤ 3 calls; deterministic extraction precision ≥ 0.95 per type; inter-annotator κ ≥ 0.7.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Phases P0–P6 with human gates.&lt;/strong&gt; Implementation is delegated to an agent session that
must stop at each gate and log open decisions.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Black-box software mapping.&lt;/strong&gt; The same method was applied to a commercial
business-management software, without reading its source code. The target is a triplet of
user, software and database truths linked by proven edges. Eleven heuristics (H1–H11) cover value matching,
inverse and direct perturbation, calculated fields, conditional visibility, scenario
diffs, invariant mining, input fuzzing, semantic priors (never treated as proof),
documentation-to-test and symptom-to-rule. Inputs: the menu tree via AT-SPI, a screen
survey, column profiling, and an event typology from the application audit log. A
hypothesis is accepted only with independent counter-evidence; vendor-produced maps are
held in quarantine as a scoring oracle.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;An illustrative excerpt of the typed graph (labels are fictitious): the recommendation is
only admissible because the chain down to a literal fragment of a source is complete.&lt;/p&gt;
&lt;pre class="mermaid" style="visibility:hidden"&gt;flowchart TB
 R["`Recommendation
single entry point`"] -- responds to --&gt; C["`Finding
double data entry at goods receipt`"]
 O["`Observation
“stock is re-keyed by hand”`"] -- attests --&gt; C
 O -- extracted from --&gt; F["`Fragment
interview notes, lines 40–52`"]
 F -- cites --&gt; P["`Source file
hashed, immutable`"]
 C -- concerns --&gt; A["`Activity
goods receipt`"]
 A -- uses --&gt; S["`Screen
receipt form`"]
 S -- module of --&gt; Y["`System
business software`"]&lt;/pre&gt;&lt;h2 id="status--results"&gt;&lt;a href="#status--results" class="header-anchor"&gt;&lt;/a&gt;Status &amp;amp; results
&lt;/h2&gt;&lt;p&gt;This is &lt;strong&gt;mainly a design with partial implementation&lt;/strong&gt;, not a system in service.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Pilot (observation corpus, deterministic pass).&lt;/strong&gt; 6,195 files (6.92 GB) acquired
idempotently; 13,272 nodes and 6,022 edges extracted in 2.1 s at zero LLM cost; all six
invariants green over 57,969 checks.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Gold-set measurement.&lt;/strong&gt; 145 annotation units plus 100 entity-resolution pairs. Human
annotation was partial and the second annotator was an LLM. Human × LLM agreement:
κ = 0.72 (software), 0.41 (screen), 0.31 (category), 0.52 (segment boundaries).
Extractor precision against human reference: 0.74, 0.43, 0.04, 0.63. &lt;strong&gt;Gate G2 (≥ 0.95)
not passed&lt;/strong&gt;; the category result reflects a definitional mismatch, not a computation error.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Software mapping.&lt;/strong&gt; Schema of 427 tables with 0 declared foreign keys, triggers or views
(all logic is applicative); 1,048 menu entries (899 leaves) mapped; 154 of 899 screens
surveyed (17 %); 2,845 rule hypotheses reduced to 1,519 by a local LLM filter, with 1 decision error in a
50-item sample reviewed by the supervising agent. A read-only MCP prototype exists; &lt;strong&gt;no
rule has yet been proven&lt;/strong&gt; in the software. Work paused after the host VM was overloaded.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Gold-set measurement against the gates fixed in advance: bars are the measured values per
field, the orange line is the gate. Only one bar clears its gate.&lt;/p&gt;
&lt;pre class="mermaid" style="visibility:hidden"&gt;---
config:
 xyChart:
 width: 540
 height: 320
 themeVariables:
 xyChart:
 backgroundColor: "transparent"
 plotColorPalette: "#3F86B8, #C46A2B"
---
xychart-beta
 title "Human × LLM agreement κ (gate 0.7)"
 x-axis ["software", "screen", "category", "segments"]
 y-axis "κ" 0 --&gt; 1
 bar [0.72, 0.41, 0.31, 0.52]
 line [0.7, 0.7, 0.7, 0.7]&lt;/pre&gt;&lt;pre class="mermaid" style="visibility:hidden"&gt;---
config:
 xyChart:
 width: 540
 height: 320
 themeVariables:
 xyChart:
 backgroundColor: "transparent"
 plotColorPalette: "#3F86B8, #C46A2B"
---
xychart-beta
 title "Extractor precision vs human reference (gate 0.95)"
 x-axis ["software", "screen", "category", "segments"]
 y-axis "Precision" 0 --&gt; 1
 bar [0.74, 0.43, 0.04, 0.63]
 line [0.95, 0.95, 0.95, 0.95]&lt;/pre&gt;&lt;p&gt;The software mapping, as a funnel: the menu tree is fully mapped, but screen coverage and
proof lag far behind, and the hypothesis pipeline has not yet produced a single proven rule.&lt;/p&gt;
&lt;pre class="mermaid" style="visibility:hidden"&gt;---
config:
 xyChart:
 width: 540
 height: 360
 themeVariables:
 xyChart:
 backgroundColor: "transparent"
 plotColorPalette: "#3F86B8"
---
xychart-beta
 title "Black-box mapping: from inventory to proof"
 x-axis ["Menu entries", "Leaves", "Screens", "Hypotheses", "Filtered", "Proven"]
 y-axis "Count" 0 --&gt; 3000
 bar [1048, 899, 154, 2845, 1519, 0]&lt;/pre&gt;&lt;p&gt;The MCP servers and end-to-end evaluation (P4–P5) are &lt;strong&gt;not implemented&lt;/strong&gt;.&lt;/p&gt;
&lt;h2 id="limitations--next-steps"&gt;&lt;a href="#limitations--next-steps" class="header-anchor"&gt;&lt;/a&gt;Limitations &amp;amp; next steps
&lt;/h2&gt;&lt;p&gt;Pilot results are below threshold and rest on partial human annotation. Next steps:
redefine the category label, complete a fully human reference, re-measure G1/G2, then build
the three servers and run the pre-registered evaluation. For the software mapping, the
write-perturbation heuristics await written authorisation and a dedicated VM.&lt;/p&gt;
&lt;h2 id="stack"&gt;&lt;a href="#stack" class="header-anchor"&gt;&lt;/a&gt;Stack
&lt;/h2&gt;&lt;p&gt;&lt;code&gt;Python 3.11&lt;/code&gt; · &lt;code&gt;JSONL event log&lt;/code&gt; · &lt;code&gt;SQLite&lt;/code&gt; · &lt;code&gt;YAML ontology&lt;/code&gt; · &lt;code&gt;MCP&lt;/code&gt; · &lt;code&gt;AT-SPI&lt;/code&gt; · &lt;code&gt;Ollama&lt;/code&gt; · &lt;code&gt;pytest&lt;/code&gt;&lt;/p&gt;</description></item></channel></rss>