<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Explainability on Boris Dupoux</title><link>https://borisdupoux.com/tags/explainability/</link><description>Recent content in Explainability 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/explainability/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;namespace&amp;gt;.&amp;lt;step&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: 16/16 synthetic alterations were
rejected by the verbatim check (true by construction).&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, run separately, 99 end-to-end tests with real engines and 10 live-web tests;
these counts are of collected tests, as pass status is not logged. All 17 recipes have
at least one end-to-end test, and 34 of 35 Atlas elements (all but &lt;code&gt;anchor&lt;/code&gt;) are touched
end to end, but only about 41 % of modules.&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 (2 synthetic dossiers, 1 run); all 5 expected signals of the one injected discrepancy (a short delivery billed in full), 1 synthetic dossier; 0 error-level discrepancies on 1 compliant synthetic dossier (warnings not counted)&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 (4 synthetic invoices × 7 fields); 16/16 synthetic alterations rejected by the verbatim check (true by construction)&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;OCR on a label&lt;/td&gt;
					&lt;td&gt;PaddleOCR&lt;/td&gt;
					&lt;td&gt;synthetic label with simulated photo degradation (17 words, 2 scenes): recall 0.94 raw; an over-saturation defect in enhancement (0.65–0.71) was fixed (0.88–0.94), with no net gain over the raw image&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;Table reconstruction from OCR (one synthetic 10×4 table)&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;First runs on real documents (about 10–25 cases per recipe, partly unreviewed) show
strong text extraction but weak duplicate detection and document splitting; reviewed
scores will be published.&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></channel></rss>