<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Krit</title>
    <description>The latest articles on DEV Community by Krit (@kritj2005).</description>
    <link>https://dev.to/kritj2005</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4167330%2Ffb6c1ef0-29b5-4ce3-b7e0-628fc895dcda.jpg</url>
      <title>DEV Community: Krit</title>
      <link>https://dev.to/kritj2005</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/kritj2005"/>
    <language>en</language>
    <item>
      <title>Your invoice reader is confidently wrong 4 times in 5. I built one that tells you which fields to check.</title>
      <dc:creator>Krit</dc:creator>
      <pubDate>Mon, 19 Oct 2026 04:21:30 +0000</pubDate>
      <link>https://dev.to/kritj2005/your-invoice-reader-is-confidently-wrong-4-times-in-5-i-built-one-that-tells-you-which-fields-to-2de0</link>
      <guid>https://dev.to/kritj2005/your-invoice-reader-is-confidently-wrong-4-times-in-5-i-built-one-that-tells-you-which-fields-to-2de0</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for the &lt;a href="https://dev.to/challenges/hacktoberfest-week2-2026-10-12"&gt;Hacktoberfest Open-Source AI Challenge Week 2: Side Hustle&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;I extracted 972 CORD receipt totals with a local 2B vision-language model. It got 59% of them right. When it was wrong, it was wrong &lt;em&gt;confidently&lt;/em&gt;: on the held-out test split, &lt;strong&gt;92% of the 111 wrong totals carried a mean token log-probability above −0.05&lt;/strong&gt; — an average per-token probability above 0.95.&lt;/p&gt;

&lt;p&gt;One receipt's total was &lt;code&gt;8,800,000.00&lt;/code&gt;. The model returned &lt;code&gt;88000.0&lt;/code&gt;, with a mean log-probability of &lt;code&gt;−0.000002&lt;/code&gt;. It was certain, and it was off by a hundredfold.&lt;/p&gt;

&lt;p&gt;That number is the whole project. A bookkeeper cannot hand a client "about 96% accurate". They need to know &lt;em&gt;which&lt;/em&gt; values to double-check. So I built Underwrite: it reads receipts and invoices into structured fields, then issues a &lt;strong&gt;certificate&lt;/strong&gt; per batch —&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Error rate on documents like these: ≤ 10%, with 90% confidence. 48% of totals auto-processed, the rest routed to a human."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;— and it earns that sentence with arithmetic, not marketing.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I Built
&lt;/h2&gt;

&lt;p&gt;Underwrite is a local pipeline that turns invoices into structured fields and decides, per field, whether to &lt;strong&gt;auto-accept&lt;/strong&gt; or &lt;strong&gt;send for review&lt;/strong&gt; — with the accept threshold chosen so the long-run error rate on documents like the calibration set stays under a target you pick.&lt;/p&gt;

&lt;p&gt;The customer is the one-person bookkeeping shop: a freelancer, a small accountant, someone digitising a shop's paper bills. Two things matter to them and neither is "accuracy":&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;What can I safely stop looking at?&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;What did the machine get wrong that I didn't catch?&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The second question is the one nobody answers. Every parser tells you &lt;em&gt;what&lt;/em&gt; it read. Almost none tell you &lt;em&gt;which values to trust&lt;/em&gt;, and the ones that do sell it as a closed feature. Underwrite answers it locally, on your own laptop, and shows its work.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The certificate is the product.&lt;/strong&gt; The extraction is table stakes.&lt;/p&gt;

&lt;h2&gt;
  
  
  Demo
&lt;/h2&gt;

&lt;p&gt;The review UI runs in &lt;strong&gt;demo mode&lt;/strong&gt; off a precomputed batch, so it needs no GPU and no network:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="s2"&gt;".[dev]"&lt;/span&gt;
python scripts/build_demo_batch.py     &lt;span class="c"&gt;# freezes one batch from the public CORD corpus&lt;/span&gt;
uvicorn underwrite.app.server:app
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Left: the document. Right: five fields, colour-coded — green auto-accepted, amber flagged for review, red parse failure. Flagged fields are the only editable ones. The right-hand panel is the certificate: the error budget, the certified threshold, what was auto-processed, how many auto-accepted fields were sampled for audit, and the assumption the whole thing rests on.&lt;/p&gt;

&lt;p&gt;In the shipped demo batch: &lt;strong&gt;8 of 20 totals auto-accepted, 12 routed to review&lt;/strong&gt;, at a certified 10% error budget.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;(Figures, screenshots and the screen recording live in &lt;code&gt;reports/figures/&lt;/code&gt; and &lt;code&gt;reports/demo_video/&lt;/code&gt;: the risk-coverage curves, the ablation, the validity resplits, the repair curve, the audit alarm, and the review UI.)&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;demo video link-&amp;gt;&lt;a href="https://www.youtube.com/watch?v=k6hZj1PyeVc" rel="noopener noreferrer"&gt;https://www.youtube.com/watch?v=k6hZj1PyeVc&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Code
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://github.com/Krit-Jain/Underwrite" rel="noopener noreferrer"&gt;github.com/Krit-Jain/Underwrite&lt;/a&gt;&lt;/strong&gt; — 85 files, 293 tests, and every number in this post is produced by a script in the repo. &lt;code&gt;reports/tables/*.csv&lt;/code&gt; and &lt;code&gt;reports/figures/*.png&lt;/code&gt; are committed outputs of those scripts.&lt;/p&gt;

&lt;h2&gt;
  
  
  How I Built It
&lt;/h2&gt;

&lt;h3&gt;
  
  
  The model, measured rather than assumed
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;google/gemma-4-E2B-it&lt;/code&gt;, loaded in 4-bit NF4 with bf16 compute on a &lt;strong&gt;6 GB laptop GPU&lt;/strong&gt; (RTX 4050).&lt;/p&gt;

&lt;p&gt;Two things I had to discover rather than guess:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;E4B does not fit.&lt;/strong&gt; Even at 4-bit it needs more than the 6,140 MiB available, because of Gemma 4's per-layer input embeddings — &lt;code&gt;vocab_size_per_layer_input: 262144&lt;/code&gt; across 42 layers is ~5.6 GB on its own. Transformers quietly spills to CPU/disk and the 4-bit quantizer refuses to load. E2B (35 layers, hidden 1536) fits.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;device_map="auto"&lt;/code&gt; must not be used.&lt;/strong&gt; It splits the model onto CPU whenever free VRAM looks tight, which the quantizer rejects. Pinning &lt;code&gt;{"": 0}&lt;/code&gt; is the difference between working and not.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Measured on 983 documents: &lt;strong&gt;9.06 s/document, 2.47 hours wall clock, ~6.6 GB peak VRAM&lt;/strong&gt;, 47 output tokens per document (&lt;code&gt;reports/tables/extraction_cost.csv&lt;/code&gt;; VRAM from the smoke test). An image is compressed to 280 vision tokens regardless of receipt height, which is what makes a 6 GB card viable at all. The synthetic Indian invoices were much slower — &lt;strong&gt;40.3 s/document&lt;/strong&gt;, emitting 62 output tokens against 47 on CORD.&lt;/p&gt;

&lt;h3&gt;
  
  
  The two-pass trick
&lt;/h3&gt;

&lt;p&gt;Generation gives you the answer but throws away the model's uncertainty. So every document gets a &lt;strong&gt;second forward pass&lt;/strong&gt;: the same (image + prompt + generated JSON) is fed back with &lt;code&gt;output_hidden_states=True&lt;/code&gt;, and I record, per field:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;realised log-probability of each value token (mean, min), first-token entropy, top-1/top-2 margin;&lt;/li&gt;
&lt;li&gt;the &lt;strong&gt;hidden-state vector&lt;/strong&gt; at the value's last token, and the mean over its tokens, at four depths (roughly 25/50/75/90%).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Aligning a field to its tokens is the fiddly part — the chat template wraps the response in its own tokens, so the response is located by subsequence search rather than assumed to start at the end of the prompt. Verified on all 983 documents: &lt;strong&gt;983/983 aligned&lt;/strong&gt;, checked by reconstructing each value from its selected tokens' character offsets.&lt;/p&gt;

&lt;h3&gt;
  
  
  Five black-box signals, one white-box signal
&lt;/h3&gt;

&lt;p&gt;The experiment is a fair fight at the &lt;em&gt;same&lt;/em&gt; certified error rate:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;signal&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;S0&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;mean token log-probability&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;S1&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;verbalized confidence (ask the model to rate itself)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;S2&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;validators only (arithmetic, formats, GSTIN checksum, date plausibility)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;S2g&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;source grounding — does the value actually appear on the page?&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;S3&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;logprob + validators + grounding (&lt;strong&gt;the strongest black-box baseline&lt;/strong&gt;)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;S4&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;hidden-state probe only (&lt;strong&gt;white-box&lt;/strong&gt;)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;S5&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;S3 + probe (&lt;strong&gt;the system&lt;/strong&gt;)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;On CORD, per-field AUROC for the &lt;code&gt;total&lt;/code&gt; field (n=292 held-out documents):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;S0  logprob            0.632
S2  validators         0.525   ← near chance
S2g grounding          0.725
S3  black-box fusion   0.838
S4  probe only         0.914
S5  probe + fusion     0.952
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;The hidden states carry information the entire black-box stack does not.&lt;/strong&gt; S4 — nothing but a PCA + logistic regression on the model's internal vectors — beats S3, which combines three hand-built signals.&lt;/p&gt;

&lt;p&gt;One honest gap in that table: &lt;strong&gt;S1 is missing.&lt;/strong&gt; The final run skipped the self-rating call to halve the runtime. It was measured on the earlier run over the same test split at AUROC &lt;strong&gt;0.603&lt;/strong&gt; for &lt;code&gt;total&lt;/code&gt; — weak, and consistent with the published finding that verbalized confidence is close to useless on receipts.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Does any of that depend on perfect OCR?&lt;/strong&gt; Grounding is measured against CORD's ground-truth OCR words and the synthetic renderer's own text — oracle text, so absolute coverage is optimistic. Removing grounding entirely makes the comparison &lt;em&gt;stronger&lt;/em&gt;, not weaker: S3 falls to &lt;strong&gt;0.729&lt;/strong&gt; while S5 holds &lt;strong&gt;0.915&lt;/strong&gt;. Without grounding, S5 is essentially S4 (0.915 vs 0.914) — the black-box features add nothing, and the probe carries the signal alone.&lt;/p&gt;

&lt;p&gt;That is the answer to "why open weights": a closed API gives you token probabilities at best. It does not give you the residual stream. The probe cannot exist there.&lt;/p&gt;

&lt;h3&gt;
  
  
  The gate: Learn-Then-Test
&lt;/h3&gt;

&lt;p&gt;The accept threshold is chosen by fixed-sequence testing over a &lt;strong&gt;pre-specified grid&lt;/strong&gt;: walk from the strictest threshold to the most lenient, and at each one test &lt;code&gt;H0: risk among accepted &amp;gt; α&lt;/code&gt; with an exact binomial test at level δ. Stop at the first failure. Because the order is fixed and you stop at the first non-rejection, the family-wise error rate is controlled.&lt;/p&gt;

&lt;p&gt;One correction I had to make to the textbook recipe: &lt;strong&gt;the strictest threshold accepts a single document, where &lt;code&gt;P[Bin(1, α) ≤ 0] = 0.95&lt;/code&gt; and no test can ever reject.&lt;/strong&gt; Taken literally, the procedure never certifies anything — my first simulation certified 0/200 times. Thresholds that cannot reject even with zero errors are skipped.&lt;/p&gt;

&lt;h3&gt;
  
  
  Results
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Held-out test split (&lt;code&gt;total&lt;/code&gt;, n=292, base error 38.0%).&lt;/strong&gt; These coverage columns are &lt;em&gt;empirical&lt;/em&gt;: the threshold behind each one is selected on this same test set, so read them as the achievable frontier, not a promise. The certified numbers are in "Is the certificate real?" below.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;method                AUROC   coverage@2%  @5%   @10%
S3 black-box fusion   0.838      8.2%     14.7%  46.2%
S4 probe              0.914     17.5%     30.5%  51.7%
S5 probe + fusion     0.952     41.4%     49.7%  62.0%
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;At a &lt;strong&gt;5% error budget, S5 auto-processes half the totals&lt;/strong&gt; — 3.4× what the black-box baseline manages.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Is the certificate real?&lt;/strong&gt; 200 random resplits of the pooled calibration and test documents. "Certifies" is how often a threshold was found at all; the violation column is the share of &lt;em&gt;certified&lt;/em&gt; resplits whose realised risk then exceeded α — the rest issued no promise, so they cannot break one.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;α=0.10                   certifies   coverage   violates-if-certified
S3 black-box fusion       79/200      10.9%             20.3%
S4 probe                 200/200      18.0%              4.5%
S5 probe + fusion        200/200      48.1%             11.5%

α=0.05                   certifies   coverage   violates-if-certified
S3 black-box fusion        4/200      17.5%            100.0%
S4 probe                  20/200      14.8%             95.0%
S5 probe + fusion        200/200      29.4%              6.0%
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;S5 certifies on every single resplit&lt;/strong&gt; — 48% coverage for a 10% budget, 29% for 5%. S3 certifies rarely, and when it does it is usually lying: &lt;strong&gt;all four&lt;/strong&gt; of its α=0.05 certificates violate, and a fifth of its α=0.10 ones do. A score that cannot separate a hard document from an easy one cannot safely make this promise, and the resplits expose it.&lt;/p&gt;

&lt;p&gt;I want to be straight about the one number that isn't clean: &lt;strong&gt;at α=0.10 the violation rate is 11.5%, above δ=10%.&lt;/strong&gt; Sampling noise accounts for much of it (with 200 splits the standard error is ~2%), and my simulations show that exceedances &lt;em&gt;should&lt;/em&gt; run slightly above δ when you measure realised risk on a finite test part — the guarantee is about population risk, and realised risk fluctuates across it. But it is above the line, and it's in the table rather than in a footnote.&lt;/p&gt;

&lt;h3&gt;
  
  
  What happens when the documents change
&lt;/h3&gt;

&lt;p&gt;Calibrate on Indonesian receipts (CORD), deploy on synthetic Indian GST invoices, and the certificate does not &lt;em&gt;break&lt;/em&gt; — it becomes useless:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;CORD-calibrated, deployed on Indian invoices
  source coverage 42.9%   →   target coverage 2.5%   target risk 0.0%
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The scores shift even though the target set is &lt;em&gt;easier&lt;/em&gt; (90.5% of totals correct, versus 59% on CORD). The threshold calibrated on hard documents is simply never cleared by an easier distribution. No wrong values were auto-accepted — but it auto-accepted almost nothing. I re-ran this after fixing a money-convention bug in the grounding feature and the table came out identical, so the collapse is a property of the score distribution, not an artifact.&lt;/p&gt;

&lt;p&gt;So recalibrate. The business question is how many labelled documents a new client's template needs:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;α=0.10   k=25 → 76% certify at 93% coverage
         k=50 → 100% certify at 93% coverage
α=0.05   k=50 → 70% certify at 91% coverage
        k=100 → 100% certify at 92% coverage
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Roughly 50 labelled documents per new client template to certify a 10% budget; about 100 for 5%.&lt;/strong&gt; That is a number a bookkeeper can act on.&lt;/p&gt;

&lt;h3&gt;
  
  
  The audit lane
&lt;/h3&gt;

&lt;p&gt;Flagged-item labels are selection-biased — humans only look at what the system already distrusts, so they can never tell you the error rate among auto-accepted fields. Underwrite instead samples a random slice of &lt;em&gt;auto-accepted&lt;/em&gt; fields and checks those.&lt;/p&gt;

&lt;p&gt;The alarm asks one question: &lt;em&gt;is the true error rate above α?&lt;/em&gt; That needs the Clopper-Pearson &lt;strong&gt;lower&lt;/strong&gt; bound to exceed α. My first version alarmed on the &lt;em&gt;upper&lt;/em&gt; bound. That sounds similar and is wrong: with only a handful of audits the interval is so wide that the upper bound is always above α. A reviewer caught it — that rule fired on &lt;strong&gt;52–90% of clean streams&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;With the lower bound, and a Bonferroni correction across the repeated checks, over &lt;strong&gt;500 seeds&lt;/strong&gt;:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;true error rate&lt;/th&gt;
&lt;th&gt;false alarms&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;2%&lt;/td&gt;
&lt;td&gt;0.0%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4%&lt;/td&gt;
&lt;td&gt;0.0%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;6%&lt;/td&gt;
&lt;td&gt;0.2%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;8%&lt;/td&gt;
&lt;td&gt;0.4%&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;against a 5% budget. Detecting a jump to 40% error inside a 500-document window:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;audit rate&lt;/th&gt;
&lt;th&gt;median delay&lt;/th&gt;
&lt;th&gt;missed&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;5%&lt;/td&gt;
&lt;td&gt;410 documents&lt;/td&gt;
&lt;td&gt;73%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;10%&lt;/td&gt;
&lt;td&gt;407 documents&lt;/td&gt;
&lt;td&gt;46%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;20%&lt;/td&gt;
&lt;td&gt;357 documents&lt;/td&gt;
&lt;td&gt;12%&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;On the real pipeline — 616 accepted CORD receipts at 1.1% error, then a template change — the stream I committed happens to be &lt;strong&gt;one of the misses&lt;/strong&gt;: the lane stays silent through the clean segment, and the lower bound climbs to 0.08 after the switch without reaching α. I left that in rather than hunting for a seed that alarms.&lt;/p&gt;

&lt;p&gt;The honest summary: &lt;strong&gt;the alarm doesn't cry wolf, but at a 10% audit rate it misses about half of drifts within a short window.&lt;/strong&gt; A quiet, slow monitor is still useful; a noisy one gets switched off.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Does Open Innovation Matter?
&lt;/h2&gt;

&lt;p&gt;Three reasons, and the first is decisive.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. The best signal is invisible to closed APIs.&lt;/strong&gt; The probe reads the model's hidden states. No hosted API exposes them, at any price. On this corpus a PCA over those vectors alone (AUROC 0.914) beats every black-box signal combined (0.838). That gap is not a tuning difference; it's an information difference, and it exists only because the weights are open.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. A certificate is only valid for a frozen model.&lt;/strong&gt; My numbers are calibrated against one specific quantized checkpoint. If Google ships an update, a hosted endpoint might change silently and my calibration becomes a lie. Pinned open weights keep it honest — I pin the revision.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. The documents never leave the laptop.&lt;/strong&gt; These are someone's supplier invoices with their margins on them. Running locally isn't a nice-to-have here; it's the reason a bookkeeper can use this at all. The marginal cost is electricity measured in a 2.47-hour run on a laptop GPU, not a per-page bill from an API.&lt;/p&gt;

&lt;p&gt;There's a fourth, less flattering reason: &lt;strong&gt;I could measure it.&lt;/strong&gt; Every number above came from a script I can re-run, against a checkpoint I can inspect. With a closed model I'd be calibrating against something I can't see and can't freeze.&lt;/p&gt;

&lt;h2&gt;
  
  
  Prize Categories
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Best Use of Gemma&lt;/strong&gt; — Gemma 4 E2B is the extractor, and the hidden-state probe is only possible because the weights are open.&lt;/p&gt;

&lt;h2&gt;
  
  
  Limitations
&lt;/h2&gt;

&lt;p&gt;Being precise about what this does &lt;em&gt;not&lt;/em&gt; show:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;The guarantee is about the &lt;code&gt;total&lt;/code&gt; field only.&lt;/strong&gt; &lt;code&gt;total&lt;/code&gt; has one value per document, so calibration samples are independent. Per-field guarantees on subtotal/tax/date/vendor would be invalid — the same document's fields are correlated, and the natural per-field procedure silently violates its own promise (see arXiv:2608.14639). Vendor and date are shown in the UI but always routed to review.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;~290 calibration documents is thin.&lt;/strong&gt; That is why α=0.05 is at the edge of feasibility, and why the α=0.10 violation rate sits slightly above δ.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A 2B model on a 6 GB card sets the ceiling.&lt;/strong&gt; Base error is 38% on &lt;code&gt;total&lt;/code&gt;. A larger model would raise coverage; it wouldn't change the machinery.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Exchangeability is assumed.&lt;/strong&gt; The audit lane exists because that assumption fails, and the shift experiment shows exactly how (silently, and by collapsing coverage rather than by producing wrong auto-accepts).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The synthetic Indian invoices have no OCR pass&lt;/strong&gt; — grounding is measured against the text the renderer drew, so OCR error is not modelled there.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;CORD is Indonesian and CC-BY-4.0&lt;/strong&gt;, and the corpus has no vendor/date ground truth, so the five-field story can only be told as a three-field one on this data.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Prior work
&lt;/h2&gt;

&lt;p&gt;This builds directly on open work and claims no novelty for the machinery:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Learn-Then-Test&lt;/strong&gt; (Angelopoulos et al., &lt;a href="https://arxiv.org/abs/2110.01052" rel="noopener noreferrer"&gt;arXiv:2110.01052&lt;/a&gt;) — the fixed-sequence threshold calibration.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;VerifyDoc&lt;/strong&gt; (&lt;a href="https://github.com/bhaskargurram-ai/verifydoc" rel="noopener noreferrer"&gt;bhaskargurram-ai/verifydoc&lt;/a&gt;, Apache-2.0) — per-field calibrated confidence, source grounding, accept/review at a target selective risk. Its reported CORD AUROCs (grounding 0.82, token-probability 0.69, verbalized ~0.50) are why grounding is in my baseline and why my verbalized signal is reported as near-useless.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;"Valid Per-Field Selective Risk Control for Document Extraction"&lt;/strong&gt; (&lt;a href="https://arxiv.org/html/2608.14639v1" rel="noopener noreferrer"&gt;arXiv:2608.14639&lt;/a&gt;) — shows the naive per-field procedure violates its own guarantee. That paper is the reason my certified guarantee is restricted to &lt;code&gt;total&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;CORD&lt;/strong&gt; (Park et al.) — the receipt corpus.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The contribution here is narrow and I'll state it narrowly: at a matched certified error rate on a laptop-class open model, a hidden-state probe auto-processes substantially more documents than a strong black-box stack that includes grounding.&lt;/p&gt;

</description>
      <category>devchallenge</category>
      <category>hf26challenge</category>
    </item>
  </channel>
</rss>
