llm-ingestion-okf/docs/plan/office-intake.md
Kjell Tore Guttormsen 169dfdf550 docs(plan): record the three intake decisions and what measured them
The converter binary is vendored as a pinned wheel in the [extract] extra
rather than resolved from PATH. Three measurements retired the alternative:
the xlsx and pptx readers exist only from pandoc 3.8.3 while Debian 12 ships
2.17.1.1 and Ubuntu 24.04 ships 3.1.3; the docx reader changed its output in
at least 22 of the 3.x releases; and Quarto and pypandoc-binary already made
this call. Two consequences are recorded because they are easy to get wrong:
the single-runtime-dependency rule is untouched, since the extra sits outside
it by construction, and the model-free gate keeps subprocess in its
alternation, since src/ imports a package instead of spawning a process.

One trap is recorded because it is invisible when it fires: pypandoc searches
PATH before its own bundled binary and takes the highest version it finds, so
on a host with a newer system pandoc the vendored wheel is bypassed without a
word and the determinism guarantee goes with it.

Judgement lives in tools/, outside the run path, as versioned data. The
verdict carries its dwell time, because a ratified flag with no per-item time
cannot be falsified. Rejecting is one keystroke and correcting optional,
because requiring a typed correction was measured to reduce flagging. The
model leg is off by default so its value can be measured.

A sixth profile carries both okf_version and bundle_id rather than extending
SEGMENTED_V1, which would have rested on an unverifiable claim about whether
another repository had adopted it.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-01 22:32:24 +02:00

94 lines
4.9 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Office intake: the three decisions, and what measured them
Decision record for the intake work planned 2026-09-01. The plan itself is
local-only; these three choices are not, because each of them changes something
a consumer can observe.
## 1. The converter binary is vendored, not resolved from PATH
**Decision: operator, 2026-09-01, after research.**
`pandoc` converts `docx`, `xlsx`, `pptx`, `odt` and `rtf` to text, and it enters
as a **pinned wheel inside the existing `[extract]` extra** rather than as a
system binary the library shells out to.
The first design considered was the opposite — spawn a system `pandoc` from one
allowlisted module — and three independent measurements retired it:
- The `xlsx` and `pptx` readers exist only from pandoc **3.8.3**. Debian 12
ships 2.17.1.1 and Ubuntu 24.04 ships 3.1.3, so a `PATH` binary cannot deliver
two of the five formats on current stable distributions. Those table rows
would fail by environment rather than by design.
- The `docx` reader changed its emitted output in **at least 22 of the 3.x
releases**, including one change shipped in 3.6.2 and reverted in 3.6.4. A
byte-exact golden over an unpinned binary is a promise about someone else's
machine.
- The ecosystem already made this decision: Quarto bundles pandoc to eliminate
version-mismatch risk, and `pypandoc-binary` ships it as a platform wheel.
Two consequences follow, and both are load-bearing:
**The single-runtime-dependency rule is unchanged.** The wheel lives in the
`[extract]` extra, which `pyproject.toml` already places outside that rule by
construction. The default install still declares exactly one runtime dependency.
**The model-free gate keeps `subprocess` in its alternation.** Because the
conversion goes through an imported package rather than a process this library
spawns, `src/llm_ingestion_okf/` needs no `subprocess` of its own and the gate
stays exactly as it was. What the gate proves has narrowed and should be stated
honestly: it proves no model vendor is reachable from `src/`, not that no
process is spawned anywhere in the dependency tree.
**One measured trap, recorded because it is invisible when it fires.**
`pypandoc` searches `PATH` **before** its own bundled binary and selects the
highest version it finds. On a machine with a newer system pandoc, the vendored
wheel is silently bypassed and every determinism guarantee with it. The binary
must therefore be resolved explicitly and its version asserted against a frozen
literal — a check that costs nothing and is the only thing standing between the
pin and a bundle built by a converter nobody chose.
## 2. Judgement lives outside the run path, as versioned data
**Decision: operator, 2026-09-01.**
Segment boundaries are proposed by a stdlib, model-free tool; a `tools/`-side
command raises those proposals into reasoned candidates; a human ratifies or
corrects; the ratification is written to a versioned plan file, and the run path
only ever reads that file. `src/` remains model-free.
The measured shape of the loop matters more than the shape of the diagram:
- **The verdict carries its dwell time.** A ratified flag with no per-item time
is unfalsifiable. The published case is 300 000+ insurance decisions averaging
1.2 seconds each, ~80 % of the appealed ones later overturned. Recording the
time is what makes "adjudicated" a fact rather than a claim, and it is the
same field that measures what adjudication costs.
- **Rejecting is one keystroke; correcting is optional.** Measured over 2 784
annotations: people flagged *fewer* errors when flagging required typing the
correction. An interface that charges for disagreement manufactures the
rubber-stamping its output then claims to have avoided.
- **The model leg is off by default,** so its value can be measured rather than
assumed. Pre-annotation has been measured lowering a good annotator's accuracy
from 98.1 % to 95.8 %.
## 3. A new profile, never a change to an existing one
**Decision: operator, 2026-09-01.**
A segmented bundle could not declare which upstream spec it targets:
`SEGMENTED_V1` names `bundle_id`, `OKF_V0_2` names `okf_version`, and the two
never intersect. Rather than add the key to `SEGMENTED_V1`, a sixth profile —
`SEGMENTED_OKF_V0_2` — carries both.
This follows the standing rule that upstream support is additive, expressed as a
new profile and never as a migration. It also avoids resting on an unverified
premise: extending `SEGMENTED_V1` would have been safe only if nobody had
adopted it, and "nobody has adopted it yet" is a claim about other repositories
that this one cannot check.
## What these three do not settle
The acceptance rubric these decisions are measured against (K1K7) is owned at
programme level, spans this library and its consumers, and is not closed here.
This work produces numbers on K1K5 with their denominators; the thresholds that
turn a number into a verdict belong to whoever owns the rubric.