# Tour the quote-template-unification corpus A procurement team rarely receives quotes in a convenient form. Suppliers answer the same request for quote with spreadsheets, PDFs, scans, JSON exports, and ERP dumps, each with its own units, part codes, and quirks, and someone has to turn that pile into a defensible award decision. This tutorial builds a Hankweave pipeline that does exactly that for a fictional RFQ: it normalizes eight supplier responses into shared records, isolates the hazards it cannot resolve, and produces a cited award brief. This opening tour shows the destination before the mechanism. We will inspect the finished artifacts, meet the suppliers and their dialects, catalog the planted hazards, and confirm the fixture corpus is deterministic, so that every later chapter has a concrete target to build toward. Everything on this page is synthetic training material, marked as follows: ```text FICTIONAL TRAINING FIXTURE — NOT CUSTOMER DATA Every document in this corpus is fictional. Buyer (Northstar Components), RFQ NC-RFQ-0042, all suppliers, part numbers, prices, and case details are invented for testing/tutorial purposes. No real customer, vendor, or transaction data is represented. data_class: synthetic. License: CC0-1.0 (pending project-counsel confirmation -- see forge-report.md OQ). "Datalab" and "Reducto" appear only as digitizer-output-shape tags on fixture filenames/adapters (matching corpus-spec.md's own file inventory), not as case-study attribution. ``` ## See the destination before the mechanism Before we build `quote-template-unification`, let's inspect the four files we want to finish with: unified records, an exception ledger, a notes channel, and a cited award brief. These are captured results, not a transcript of a run, and each one answers a different question a buyer would ask. The unified records answer "what did every supplier actually bid, in comparable terms?" The file holds one row for each of eight quoting suppliers and five BOM parts: 40 data rows, or 41 CSV lines when the header is counted. Granite is quarantined during survey, so it contributes no row. Each row records the supplier and part identity, RFQ revision, quantity, minor-unit price and basis, line extension, currency, status, ranking eligibility, and four source references. In the excerpt below, notice how a supplier's native pricing has been converted into normalized `unit_price_minor` cents, and how every value traces back to a `source_line_id` and `source_block_id` in the original document. ```csv supplier_code,buyer_part_id,rfq_rev,qty,unit_price_minor,price_basis,line_extension_minor,currency,status,ranking_eligible,source_document_id,source_quote_id,source_line_id,source_block_id AST,NC-1001-A,B,500,410,PER_EACH,205000,USD,QUOTED,true,doc-aster-qb-1047-rev2,q-aster-qb-1047-rev2,doc-aster-qb-1047-rev2-L1,doc-aster-qb-1047-rev2-b1 AST,NC-1002-A,B,2000,79,PER_EACH,158000,USD,QUOTED,true,doc-aster-qb-1047-rev2,q-aster-qb-1047-rev2,doc-aster-qb-1047-rev2-L2,doc-aster-qb-1047-rev2-b2 AST,NC-1003-B,B,1000,115,PER_EACH,115000,USD,QUOTED,true,doc-aster-qb-1047-rev2,q-aster-qb-1047-rev2,doc-aster-qb-1047-rev2-L3,doc-aster-qb-1047-rev2-b3 ``` The exception ledger answers "what could not be resolved mechanically?" Hazards stay visible here instead of being dropped or silently coerced: Cedar's expired validity, its `NO BID` row, and Aster's deduplication conflict each get a typed row with a resolution note. The golden ledger has eight rows covering eight exception types; the anchor's current `exceptions.csv` is serialized from its structured ledger, and the row IDs need not match the golden file's ordering. Compare the type and supplier rather than the row ID. ```csv exception_id,type,supplier_code,buyer_part_id,detail,resolution,source_ref EX-01,EXPIRED_VALIDITY,CDR,NC-1001-A|NC-1002-A|NC-1003-B|NC-1004-A|NC-1005-A,CDR valid_until 2026-01-20 < award decision date 2026-02-01,"JC-1: planted irresolvable judgment call — exclude-vs-show-flagged is a human decision; quote stays visible, cannot win ranking",doc-cedar-cw-77 EX-02,NO_BID,CDR,NC-1004-A,CDR rendered NO BID for NC-1004-A,"normalized to status:NO_BID, not $0.00; excluded from ranking, visible",doc-cedar-cw-77-b4 EX-03,DEDUP_CONFLICT,AST,NC-1004-A,same supplier+RFQ rev submitted twice: doc-aster-qb-1047 stated_total 604000 vs doc-aster-qb-1047-rev2 stated_total 604001 (1-cent drift on NC-1004-A printed extension),"dedup rule: latest job (doc-aster-qb-1047-rev2, issued 2026-01-14) wins; doc-aster-qb-1047 (issued 2026-01-10) superseded, conflict logged",doc-aster-qb-1047-rev2 ``` The notes channel answers "what did suppliers say that is not a price?" Annotations such as Cedar's lead-time remark and Iris's conditional discount offer live here, outside the line items. Neither is applied to pricing; the `applied_to_pricing` column says so explicitly. ```csv note_id,supplier_code,buyer_part_id,type,text,applied_to_pricing,source_ref N-01,CDR,NC-1002-A,LEAD_TIME,lead time 45 days on pins,false,doc-cedar-cw-77-b2 N-02,IRS,NC-1005-A,DISCOUNT_OFFER,can do 5% if you take all 5000,false,doc-iris-quote-b5 ``` Finally, the award brief answers "who should win each part, and on what evidence?" It selects the lowest eligible bidder per part and cites the source block for each amount, so every number in the recommendation can be traced back to a supplier's own document. A setup rig (a setup program) serializes the exception ledger's seven declared fields in exception-ID order and derives the financial projection from the unified rows; the agent supplies only the qualitative assessment. ```markdown ## Financial recommendation Projection of unified-records.csv only. Rank QUOTED, ranking_eligible=true rows by line_extension_minor; break equal-price ties by supplier_code ascending. Unit prices are already normalized per each upstream. No eligible bid means no recommendation; unsupported data stops export rather than inventing a price or citation. | Buyer part | Selected supplier | Unit price (USD per each) | Line extension (USD) | Tie / no-bid decision | Ineligible QUOTED suppliers | | --- | --- | --- | --- | --- | --- | | NC-1001-A | BCN | $3.95 [doc-beacon-8821:doc-beacon-8821-b1] | $1,975.00 [doc-beacon-8821:doc-beacon-8821-b1] | Unique lowest eligible bid | CDR [doc-cedar-cw-77:doc-cedar-cw-77-b1] | | NC-1002-A | AST | $0.79 [doc-aster-qb-1047-rev2:doc-aster-qb-1047-rev2-b2] | $1,580.00 [doc-aster-qb-1047-rev2:doc-aster-qb-1047-rev2-b2] | Unique lowest eligible bid | CDR [doc-cedar-cw-77:doc-cedar-cw-77-b2] | | NC-1003-B | EMB | $1.05 [doc-embar-quote:doc-embar-quote-b3] | $1,050.00 [doc-embar-quote:doc-embar-quote-b3] | Unique lowest eligible bid | CDR [doc-cedar-cw-77:doc-cedar-cw-77-b3] | | NC-1004-A | AST | $6.40 [doc-aster-qb-1047-rev2:doc-aster-qb-1047-rev2-b4] | $160.00 [doc-aster-qb-1047-rev2:doc-aster-qb-1047-rev2-b4] | Unique lowest eligible bid | None | | NC-1005-A | BCN | $0.19 [doc-beacon-8821:doc-beacon-8821-b5] | $950.00 [doc-beacon-8821:doc-beacon-8821-b5] | Unique lowest eligible bid | CDR [doc-cedar-cw-77:doc-cedar-cw-77-b5] | ``` These captures come from a real run, and the numbers behind them are worth reading carefully. The fixture tree was generated on 2026-09-01; the anchor's expected capture was re-captured on 2026-09-06. The current anchor capture is a complete 7/7-codon run. It matched all 40 unified rows to the oracle and routed all eight planted exception types. Its tracked codon cost was `$0.88303367`; provider health checks and sentinel calls are outside that tracked-codon total. The configured per-codon caps add to `$9.00`, but those are ceilings in `hank.json`, not a measured total or a price promise across retries. For historical context only, the original golden run used sonnet at \~$2.41. One component is deliberately narrower than the artifacts suggest. The quality observer watches the `validate-and-repair` completion event and reports the status, cost, and duration fields present there. It cannot read output files, so row coverage and correctness come from the structured outputs and the deterministic checker, not from the observer log. **Check-it:** In the extracted release bundle, open `quote-template-unification/truth/expected.csv` and confirm that the header is followed by 40 data rows. Then inspect `truth/exceptions.csv` and `truth/notes.csv`; these are the target contracts before a hank runs. ## Name the procurement we will unify Having seen where we are going, we can look at what we start from. We will work with Northstar Components' fictional request for quote (RFQ), `NC-RFQ-0042`. Revision A was issued on 2026-01-03, revision B on 2026-01-05, and the award decision date is 2026-02-01. The bill of materials below is the shared reference every supplier quoted against. | Part | Description | Rev-B quantity | Revision-B change | | ----------- | ----------------- | -------------: | ------------------------------------------ | | `NC-1001-A` | mounting bracket | 500 EA | – | | `NC-1002-A` | locking pin | 2000 EA | rev-A quantity was 1000 | | `NC-1003-B` | fastener kit | 1000 EA | pack and box variants appear | | `NC-1004-A` | gasket sheet | 25 EA | – | | `NC-1005-A` | rating label roll | 5000 EA | new in rev B; Cedar may render `NC-1OO5-A` | The rev-B delta is exactly two changes: the locking-pin quantity doubles from 1000 to 2000, and the rating-label roll is new. Revision therefore matters to the pipeline: a rev-A quote is one of the 19 standalone failure fixtures, and accepting stale quantities would corrupt the award math. Two conventions apply to every document in the corpus. Each carries the watermark `FICTIONAL TRAINING FIXTURE — NOT CUSTOMER DATA` and the data class `synthetic`. And the seed prices are USD cents per EA; fields ending in `_minor` hold minor currency units, while each row carries `USD` separately. The three hand-authored digitizer contract fixtures mark `actual_service_output: false`. They establish a tutorial convention, not the response shape of a real Datalab, Reducto, or generic-OCR service. **Check-it:** Find `NC-1002-A` and `NC-1005-A` in the BOM table, then confirm the watermark in the notice at the top of this page. ## Survey the suppliers before normalizing Eight suppliers answered the RFQ, and no two answers look alike. Real procurement intake spans clean JSON, raw TSV, formatted spreadsheets, and degraded scans, so before we normalize any price into the shared record shape, we inspect its dialect. Aster, Beacon, and Cedar cross a digitizer boundary through hand-authored contract fixtures. Dover, Embar, Fjord, Aster rev2, Harbor, and Iris are already-digital extracts. Granite is surveyed and quarantined rather than forced into a template. The inventory below lists each dialect and the exact quirks it renders; every quirk is a deliberate test of a normalization or quarantine boundary. ```markdown ## 5. Dialect specs (per supplier; every quirk is a rendered artifact, not a comment) | id | supplier / file | format | exact quirks to render | cite | |---|---|---|---|---| | D1 | Aster Metals `aster-quote-qb-1047.xlsx` | XLSX, 2 sheets | merged heading cells; quantity-break price table (tiers 92/85/79¢ on NC-1002-A); blank cell = "same as above" (continuation rows); sheet 2 = terms | S§story, G§4-1 | | D2 | Beacon Industrial `beacon-quote-8821.pdf` | 2-page digital PDF | per-PACK pricing (NC-1003-B `$115.00/pack of 100`); freight-excluded note (EXW); one approved substitute (BI-2002-X for NC-1002-A); one-time tooling line `$250.00` on p2 — stated total correct only after adding it | S§story, G§4-2, S§contract | | D3 | Cedar Works `cedar-quote-cw-77-scan.png` | skewed scan PNG | OCR-confusable `NC-1OO5-A`; one `NO BID` row (NC-1004-A); handwritten "lead time 45 days on pins"; expired validity 2026-01-20 (JC-1); NC-1003-B priced per BOX(10) | S§story, G§4-3 | | D4 | Dover `dover-quote.json` | Datalab-contract-shaped JSON | `fields[]` each with `{page,bbox}` citation; one value string mixes part+name (`"NC-1004-A gasket sheet"`); clean 5/5 bid | G§4-4 | | D5 | Embar `embar-quote.md` | markdown (parse-not-extract) | per-page tables; NC-1004-A row SPLIT across page break (qty p1, price+ext p2) | G§4-5 | | D6 | Fjord `fjord-erp.tsv` | TSV | internal part codes FJ-3341..3345; some rows price `TBD` → UNRESOLVED | G§4-6 | | D7 | Aster rev2 `aster-quote-qb-1047_rev2.json` | JSON re-run | same RFQ+supplier as D1; 1¢ total drift (16000→16001 on NC-1004-A extension) | G§4-7 | | D8 | Granite `granite-bid.xlsx` | XLSX | first-time supplier, never-seen template (arbitrary layout, no recognizable line-item grid) → survey quarantines + onboarding note | G§4-8 | | D9 | Harbor `harbor-quote.json` | JSON | `ocr_confidence:0.81`; fixed-width text lines; `warnings:["low_confidence: page 2"]`; p2 rows quarantined | G§4-9 | | D10 | Iris `iris-quote.json` | JSON | `annotations:[{type:"handwriting", text:"can do 5% if you take all 5000"}]` → notes channel only | G§4-10 | ``` Reading the table column by column pays off later. D2's per-pack pricing and D3's per-box pricing both have to land on the same per-each basis as D1's tiered spreadsheet prices. D5's page-split row tests whether the pipeline can rejoin a line without losing its source pages. D8 is the control case: a template nobody has seen before should be quarantined, not guessed at. The native-input rig parses the five structured extracts without retyping them: it preserves their explicit source line or block identifiers, TSV currency even on `TBD` rows, native annotations, OCR warnings, and Embar's source pages when it joins the split line. Validation byte-checks those generated envelopes before using them. The survey begins incomplete and the agent records the quarantine decisions before validation can require complete intake coverage. This division of labor is worth stating plainly. An adapter transforms input shape; it does not decide commercial meaning. The contract fixtures share a `lines[]` convention, but their `actual_service_output:false` marker matters: these are hand-authored tutorial fixtures. The already-digital extracts enter through their native formats. Granite's zero-row quarantine is the correct result for an unknown template. ```json { "adapter": "datalab", "actual_service_output": false, "data_class": "synthetic", "watermark": "FICTIONAL TRAINING FIXTURE — NOT CUSTOMER DATA", "document_id": "doc-aster-qb-1047", "quote_id": "q-aster-qb-1047", "source_file": "source-quotes/aster-quote-qb-1047.xlsx", "media_type": "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", "issued_at": "2026-01-10T09:00:00", "dedup_key": "AST|NC-RFQ-0042|B", ``` **Check-it:** In the dialect inventory, find D3's `NO BID` and D10's notes-only annotation. In the JSON excerpt, find `actual_service_output: false`. ## Find the hazards before they surprise us The dialect quirks are not decoration; each one plants a hazard the pipeline must handle correctly. The corpus plants nine hazard classes: expired validity, NO BID, a deduplication conflict, a page-split line, a low-confidence page, handwriting, an OCR-confusable part number, internal part codes with `TBD` prices, and an unknown template. Each has a correct treatment, and most of those treatments share a theme: preserve the source meaning and its citation rather than forcing a number. * **Expired validity (JC-1):** Cedar expires on 2026-01-20, before the 2026-02-01 award date. Keep its rows visible and ineligible; the decision to exclude or show-flagged is a human call. * **NO BID:** Cedar's `NC-1004-A` has null pricing, not `$0.00`. * **Deduplication:** Aster rev2 wins the same supplier/RFQ-revision key; the one-cent drift is logged. * **Page split:** Embar's `NC-1004-A` fragments are joined while their source pages remain available. * **Low confidence:** Harbor page-2 rows remain visible but quarantined and unranked. * **Handwriting:** Iris's conditional discount is routed to notes and never changes the line price. * **OCR alias:** Cedar's `NC-1OO5-A` can resolve to the BOM part when its context supports that alias. * **Unresolved price:** Fjord's `TBD` rows remain null and ineligible. * **Unknown template:** Granite is quarantined at survey with an onboarding note. The 19 failure fixtures F01–F19 are separate mutations of golden inputs. They cover failures such as manifest mismatch, broken citations, wrong pack conversion, an expired quote, an unlabeled synthetic document, and an unsupported award number; later validation chapters exercise their expected diagnostics. > **Pitfall:** Do not turn `NO BID` into `$0.00`, apply Iris's 5% annotation, or guess a Fjord `TBD` value. Preserve the source meaning and the citation instead. **Check-it:** Compare the expired-validity and NO-BID rows in the captured exception excerpt. The first stays visible but cannot rank; the second carries no price – `NO BID`, not `$0.00`. ## Verify the corpus before we build A tutorial corpus is only useful if its ground truth is trustworthy, so the fixture tree is checked before any hank runs. The corpus itself is deterministic. A typed Bun generator builds the fixture tree from the BOM and supplier seed, and a separate checker re-parses the emitted files against an independently transcribed oracle. The checker completed 76/76 checks, including the 19 failure-fixture shape checks. A second generator/checker pass produced no directory differences. That result belongs to the fixture generator; it does not promise byte-identical model envelopes, judgments, or prose. The model-written normalizers should be repeated with `--start-new`, a fresh execution, and a distinct output location when semantic repeatability is being tested. The comparison projection checks identity, raw parts, quantity, prices and basis, status, and citations; it reports full-JSON and byte variation separately rather than rejecting every difference. Five under-specified choices are recorded in the corpus report rather than hidden: line extensions are computed as quantity × unit price; the contract fixtures use a shared `lines[]` convention; F19 uses a structural proxy because `comparison.json` is absent; the Cedar PNG is structurally valid while its dialect quirks live in the digitized fixture; and `generator_commit` is `n/a` because generation ran outside a git repository. **Check-it:** Read the corpus report's invariant result and determinism sections. Confirm that the 76/76 result belongs to the corpus checker, not to a claim that a model will reproduce every byte. ## Protect the data boundary Because the corpus ships with known answers, we need a rule about what the agent may see. The **data boundary** separates task inputs from the held-out answer. Prepare the task-only input from the extracted corpus before a chapter run: it contains `digitized/`, `digitized-extracts/`, `lookups/`, `rfq/`, `source-quotes/`, and a filtered manifest. The `truth/`, `generator/`, and planted failure fixtures stay outside the agent input. This is evaluation-data separation, not a security sandbox. The task inputs are exposed read-only at the mount seen by the codons. Upstream payloads remain immutable. The digitizer boundary is inside this rule: native files cross once through the three hand-authored contract fixtures or the deterministic `native-inputs.ts` rig, and later codons consume the shared envelope shape rather than the raw file. Within that boundary, the pipeline works through four stages, and the diagram below is the roadmap for everything the later chapters build. Normalization reshapes dialects and resolves Fjord's internal aliases without judging prices. Validation and repair catch the planted hazards. Reconciliation produces one row per quoting supplier and BOM part, with the latest Aster job winning the dedup conflict. The brief names the lowest eligible bidder with unit price, line extension, and a source citation. The diagram is a teaching view of the corpus, not a transcript of a runtime event graph. ![Protect the data boundary — the tour's four-stage working shape](/content-assets/cf45dff5691c48c0/diagrams/tutorial-0-tour/1.png) Protect the data boundary — the tour's four-stage working shape
Diagram as text ```text normalize ──> validate/repair ──> reconcile ──> brief shared catches the planted one row per lowest eligible lines[] hazards and repairs supplier + bidder with a convention what is repairable BOM part cited amount without dropping it latest job and source_ref Fjord aliases wins the resolve here one-cent drift ```
The held-out oracle is useful to us as readers, but it is not supplied to the codons. The later chapters use the same prepared task-input layout and then verify outputs against the separately held-out truth. **Check-it:** Look at the prepared-input layout in the fixture documentation and verify that its top-level directories are the five task-input directories above, not a `data/` prefix and not the oracle. ## Start chapter 1 with a clean fixture We now know the target files, the source dialects, the hazards, and the boundary between task and answer. Next, in [chapter 1](/0.10.0/files/tutorial/1-baseline), we will write and run the hank: its syntax, codons, model selection, and CLI commands. We have not run a hank yet. Start with the versioned fixtures: [download the 0.10.0 bundle](/content-assets/cf45dff5691c48c0/fixtures/0.10.0/hankweave-fixtures-0.10.0.tar.gz) or browse an [individual fixture artifact](/content-assets/cf45dff5691c48c0/fixtures/0.10.0/files/chapters/PROGRESSION.md). The archive already contains the top-level directory `hankweave-fixtures-0.10.0/`; after extraction, the relevant entries are `minimal-single-provider/hank.json` with `minimal-single-provider/data/`, and `chapters/ch1` through `chapters/ch5`. These are directories, not checkpoint tags. The fixture command recipes require Node `>=22.19.0`; we use Bun's `bunx` package launcher (Node/npm users can use the equivalent `npx` spelling), and the recipes also assume Git and a non-root account. The supplied minimal capture ran on Node v23.8.0. Follow the commands in each chapter. The credentials ladder comes before a paid run: chapters 1–2 use `ANTHROPIC_API_KEY`; chapters 3–5 and the full anchor also need `BASETEN_API_KEY` for their `pi/baseten/deepseek-ai/DeepSeek-V4-Pro` judgment codons. The anchor's quality observer uses the full `anthropic/claude-haiku-4-5` registry ID. A passing preflight does not establish that the Baseten key is valid. Each chapter's own run page names its required keys before its command. The chapter progression is deliberately cumulative: ch1 has one codon for Aster, ch2 has three codons for Aster/Beacon/Cedar, ch3 adds validation and exceptions, ch4 emits 15 Aster/Beacon/Cedar rows, and ch5 expands to eight quoting suppliers and 40 rows before producing the cited award brief. Fresh 2026-09-06 captures measured tracked codon costs of `$0.10441000`, `$0.16203200`, `$0.28754050`, `$0.40847623`, and `$0.70680328` for ch1 through ch5. Use a fresh execution and output location for an independent repeat; input checksums do not make model extraction byte-deterministic. The data-boundary and outcome-before-mechanism ideas begin here and are formalized on their concepts pages. Before any provider call, Hankweave can check a hank's configuration statically. The capture below shows that check passing for the full anchor: `7 codons • 0 loops`, with no codons executed and no provider health checks run. ```text ✓ Configuration is valid! ╭──────────────────────────────────────────────────────────────────────────────╮ │ quote-template-unification v1.0.0 │ │ 7 codons • 0 loops │ ╰──────────────────────────────────────────────────────────────────────────────╯ ``` Read the capture as a pre-codon configuration check only. It writes only a temporary log. The registry setup disables provider health checks for validation; runtime startup is a separate concern. Validation may add `$schema` to the hank. The capture is not a live-run success or a credential proof. **Check-it:** Before moving to chapter 1, confirm the bundle-relative entry `chapters/ch1`, the two-key ladder for ch3 onward, and the `7 codons • 0 loops` validation result. Then follow the chapter's own run and verification instructions.