Skip to content

Provenance representation

  • Lesson 10
  • 50 min
  • Module 3: Recover structure and tables

What you will be able to do

Store, with every value that the pipeline extracts, where it came from: the file, the page and the box, or the sheet and the cell. Then draw that place back on the original page. An agent then checks a value in a few seconds, without a search of the document.

Before you startWhat to know and to have readyAbout 50 minutes

What you need to know

What you need on your computer

  • Your project at the end of the previous lesson: python -m pytest -q prints 74 passed.
  • The labs repository in ~/projects/nextia-learning-labs, up to date.

Tested with Python 3.12.13, RapidOCR 3.10.0 and Pillow 12.3.0 on macOS (Apple silicon). The Windows and Linux commands are not tested.

  1. In the project folder, copy the module and its tests from the snapshot:

    Terminal window
    S=../nextia-learning-labs/C13/snapshots/end-of-m03
    cp $S/doc_extract/provenance.py doc_extract/
    cp $S/tests/test_provenance.py tests/
  2. Run the tests:

    Terminal window
    python -m pytest -q

    The last line is 77 passed. This is the test count of the snapshot end-of-m03.

Think of an expense report at the end of a business trip. You type “Hotel: 412.50” in a form, and the accountant asks: “Where does this number come from?” A careful employee does not answer “from the receipt”. They staple the receipt to the form and mark the amount with a highlighter. Now the accountant checks in two seconds: the right receipt, the right line, the right number. Without the highlight, the accountant must read the whole receipt, and may stop at the wrong 412.50: the deposit, not the total.

The highlight costs almost nothing when you copy the number. Finding the place again later costs much more.

Tomás says it in a meeting: “We don’t need the page number, we have the value.” Look at what “we have the value” means on D036, the French phone photo from Montréal. The pipeline says total 1011.78. But the receipt prints 1 011,78 twice: on the TOTAL line and on the payment line, “Cash 1 011,78”. A value alone cannot say which line was read. If the rule had read the cash line, the value would be the same here. But on a receipt with change given back, it would be wrong, and nobody could see why.

The tax shows the same problem from another side. The pipeline says tax 131.78, and that number is not printed anywhere on the receipt. It is the sum of two printed lines, TPS 44,00 and TVQ 87,78. An agent who searches the photo for 131.78 finds nothing, and starts to doubt a value that is right.

And you saw a third case in Text and OCR paths: the vision model wrote LARKEFIELD on the clean PDF D009. Its answer had values only, with no place on the page. To check it, a person must open the file and look for the vendor name.

Grace’s question to the project was: “Can the system show us exactly where each value came from?” Without a place, every check starts with a search.

The idea: an evidence record for every value

Section titled “The idea: an evidence record for every value”

Provenance means “where something comes from”. The word is old: in a museum, the provenance of a painting is the list of its owners, back to the artist. For an extracted value, provenance is a small record stored next to the value: the evidence. In this project, the evidence of a value has these parts:

  • the file, as the customer sent it (the original, never a prepared copy);
  • for a page: the page number and the bounding box, the smallest rectangle around the text, as four numbers [x0, top, x1, bottom];
  • the unit of the box: pt (points, 1/72 of an inch) on a PDF page, px (pixels) on a picture file;
  • for a spreadsheet: the sheet, the cell address, and the merged range if the value comes from one;
  • the path that read it (text layer, OCR, table, sheet) and the text seen at that place.

Why two units? A PDF page has a fixed size in points; a picture of it is only one drawing, at some resolution. A box in points is the same whatever resolution you draw the page at. A JPEG photo has no points: its pixels are the original. So each file keeps its own unit, and the record says which one.

There is one more case: OCR on a scanned PDF. The project draws the page at 200 dpi (dots per inch) and OCR returns boxes in those pixels. One point is 1/72 of an inch, so one pixel at 200 dpi is 72/200 = 0.36 point. The project multiplies each pixel value by 0.36 and stores the box in points, the unit of a PDF page.

A flow from left to right. Five sources lead to one Evidence record. Text layer, D011, a digital PDF: box in pt. OCR on a picture file, D036, a JPEG photo: box in px of the file. OCR on a scanned PDF page, D019, drawn at 200 dpi: px times 72 over 200 equals pt. A table cell, D056, a PDF price list: page and cell box in pt. A spreadsheet cell, D057, an xlsx file: sheet and address. The Evidence record holds file, page, box, unit, or file, sheet, cell, merged range, plus the path and the text seen. It leads to two outputs: stored with the value by provenance.record, and drawn on the original in out/evidence.
Every path gives the same kind of evidence record. Boxes on PDF pages are stored in points, boxes on picture files in pixels, and spreadsheet values as a sheet and a cell.
Text description of the diagram

Five ways to read a value lead to one record. The text layer of a digital PDF gives a box in points. OCR on a photo gives a box in the pixels of the photo. OCR on a scanned PDF page reads a drawing at 200 dpi, so its pixel box is multiplied by 72/200 and stored in points. A table cell has its page and its cell box in points. A spreadsheet cell has its sheet and its address, plus the merged range when the value was filled from one. The evidence record holds these parts, with the path that read the value and the text seen there. It is stored next to the value, and it can be drawn back on the original file.

A tiny example: one box, from pixels to points

Section titled “A tiny example: one box, from pixels to points”

D019 is a scanned receipt in a PDF. Its page is 612 × 792 points (a US letter page); drawn at 200 dpi for OCR, it is 1,700 × 2,200 pixels. The rules found the total on two OCR lines:

page 1700 x 2200 px at 200 dpi
lines: [('TOTAL', [793, 1612, 881, 1643]), ('CAD 278.88', [1196, 1609, 1354, 1638])]
stored: Evidence(file='scan0001.pdf', page=1, box=[285.6, 579.2, 487.4, 591.5], unit='pt', sheet=None, cell=None, path='ocr', seen='TOTAL CAD 278.88')

Follow it by hand. The evidence covers both lines, the label and the amount, because a person checks them together. So the box is the rectangle around both. In pixels: left 793, top 1,609 (the higher top), right 1,354, bottom 1,643. Now multiply by 0.36. Top: 1,609 × 0.36 = 579.2. Right: 1,354 × 0.36 = 487.4. Bottom: 1,643 × 0.36 = 591.5. (The left edge, 285.6, comes from 793.3 pixels before rounding.) The stored box is in points, unit='pt', and seen keeps the text that OCR read there.

  1. Show the evidence of every field of D011, a digital receipt:

    Terminal window
    python -m doc_extract.provenance D011
    vendor Larkfield found page 1, box [76, 23, 151, 37] pt
    order_number LK-103119 found page 1, box [14, 102, 101, 111] pt
    date 2026-08-22 found page 1, box [14, 89, 213, 98] pt
    currency CAD found page 1, box [14, 227, 213, 236] pt
    subtotal 189.00 found page 1, box [14, 189, 213, 198] pt
    shipping absent (not_found) absent -
    tax 22.68 found page 1, box [14, 202, 213, 223] pt
    total 211.68 found page 1, box [14, 227, 213, 236] pt
    payment_method Cash found page 1, box [14, 252, 213, 261] pt
    boxes drawn on out/evidence/D011-text-p1.png

    Every found value has a page and a box. The absent shipping has no box, -: there is nothing to point at, and the record does not invent a place. Its reason, not_found, is not one of the contract’s three reasons. It is what the rules write when no line matches: they know that the value is missing, but not why. Here the true reason is not_printed, because a till receipt has no shipping line. When a required field is missing, Combine methods asks the model, which also gives a reason. The tax box is two lines high (202 to 223 points): GST and PST. The currency shares the total’s box, because “CAD” is printed on the TOTAL line.

  2. Open out/evidence/D011-text-p1.png. The project drew a red box and the field’s name for each value on a picture of the page. The names at the right edge are cut off on this narrow receipt; the boxes are what you check.

  3. See the record that the pipeline stores:

    Terminal window
    python -m doc_extract.provenance D011 --json

    Here is the total, from the real output:

    "total": {
    "value": "211.68",
    "status": "found",
    "reason": null,
    "evidence": {
    "file": "receipt-lk-103119.pdf",
    "page": 1,
    "box": [14.2, 226.8, 212.6, 235.8],
    "unit": "pt",
    "sheet": null,
    "cell": null,
    "path": "text",
    "seen": "TOTAL CAD 211.68"
    }
    }

    The terminal prints each number of box on its own line; it is the same list.

  4. Now the photo D036. Its first run reads the page with OCR, so it takes about a second:

    Terminal window
    python -m doc_extract.provenance D036
    vendor Larkfield found page 1, box [492, 162, 791, 238] px
    order_number LK-706142 found page 1, box [252, 428, 660, 502] px
    date 2026-08-09 found page 1, box [255, 381, 991, 491] px
    currency CAD inferred page 1, box [216, 1110, 915, 1210] px
    subtotal 880.00 found page 1, box [225, 988, 926, 1089] px
    shipping absent (not_found) absent -
    tax 131.78 found page 1, box [220, 1029, 922, 1171] px
    total 1011.78 found page 1, box [216, 1110, 915, 1210] px
    payment_method Cash found page 1, box [212, 1190, 905, 1289] px
    boxes drawn on out/evidence/D036-ocr-p1.png

    The boxes are in pixels of the photo IMG_4817.jpg itself (1,200 × 1,600 pixels), not of a prepared copy. The boxes are tall because the photo is tilted: a row that slopes by about 5 degrees needs a taller rectangle.

The --json record answers the questions of the problem section. Here is the text seen for four fields of D036:

total 1011.78 found seen: 'TOTAL 1 011,78 $'
payment_method Cash found seen: 'Cash 1 011,78'
tax 131.78 found seen: 'TPS 5 % 44,00 + TVQ 9,975 % 87,78'
currency CAD inferred seen: 'TOTAL 1 011,78 $'

The total was read on the TOTAL line, not on the cash line. The tax shows the two lines that it adds up. And the currency is marked inferred: “CAD” is not printed, and the rules concluded it from the “$” on a Montréal receipt. The evidence shows the line that the conclusion is based on.

The same record for table cells and spreadsheet cells

Section titled “The same record for table cells and spreadsheet cells”

A value from a table or a spreadsheet gets the same treatment. The price list in the PDF gives a page and a cell box; the spreadsheet gives a sheet and a cell:

Terminal window
python -m doc_extract.provenance D056
python -m doc_extract.provenance D057
SP-MIST: price each 36.00 CAD <- aquaflow-price-list.pdf, page 1, box [440, 708, 505, 727] pt
SP-TRV: price each 89.00 CAD <- aquaflow-price-list.pdf, page 2, box [440, 108, 505, 127] pt
SP-HOSE: price each 16.00 CAD <- aquaflow-price-list.pdf, page 2, box [440, 127, 505, 146] pt
SP-BASE: price each 12.50 CAD <- aquaflow-price-list.pdf, page 2, box [440, 146, 505, 165] pt
HR-30: price each 59 CAD <- aquaflow-price-list.xlsx, sheet Prices, cell E5
HR-50: price each 119 CAD <- aquaflow-price-list.xlsx, sheet Prices, cell E6
HR-15C: price each 34 CAD <- aquaflow-price-list.xlsx, sheet Prices, cell E7
HR-30P: price each 79 CAD <- aquaflow-price-list.xlsx, sheet Prices, cell E8
HR-50: category Hose reels <- sheet Prices, cell A6, merged range A5:A8

The PDF output crosses the page break: SP-MIST is on page 1, SP-TRV on page 2, and each value says which. The unit CAD comes from the column header, as in the previous lesson. The last line is the careful one. HR-50’s category is in cell A6, but A6 is empty in the file: the value lives in A5, the top of the merged range A5:A8. The evidence says both. A reviewer who opens A6 and finds nothing then understands why.

This is the module’s map from each value to its place, from three kinds of source:

Three panels. Left, A value on a photo: part of the D036 photo with a green box around the TOTAL line and 1 011,78 $; below it the record total = 1011.78, file IMG_4817.jpg, page 1, box 216, 1110, 915, 1210, unit px, path ocr. Middle, A cell of a table on a PDF page: the SKU and price columns of page 2 of the price list, with a green box around 89.00 in the Each column of SP-TRV; the record price each = 89.00 CAD, file aquaflow-price-list.pdf, page 2, box 440, 108, 505, 127, unit pt, row SKU SP-TRV. Right, A cell of a spreadsheet: rows 4 to 8 of the sheet, an orange frame around A5 to A8 with Hose reels in A5, and a green frame around the empty cell A6; the record category = Hose reels, file aquaflow-price-list.xlsx, sheet Prices, cell A6, from merged range A5:A8, row SKU HR-50.
Field-to-page and field-to-cell provenance: a value on a photo (box in pixels), a cell of a table on a PDF page (page and box in points), and a cell of a spreadsheet (sheet, cell and merged range). Each green frame is the place that the record names.
Text description of the diagram

Each panel shows a value, the place it came from, and its record. On the left, the total of the French photo D036 is 1011.78: page 1 of IMG_4817.jpg, box [216, 1110, 915, 1210] in pixels of the photo, read by OCR. The green box covers the TOTAL label and the amount “1 011,78 $”. In the middle, the price each of SP-TRV is 89.00 CAD: page 2 of aquaflow-price-list.pdf, box [440, 108, 505, 127] in points. The green box is the “Each” cell of the first row of page 2. On the right, the category of HR-50 is “Hose reels”: sheet Prices, cell A6 of aquaflow-price-list.xlsx. Cell A6 itself is empty, framed in green; the value comes from the merged range A5:A8, framed in orange, whose value is stored in A5.

Amira, who connects the pipeline to the ticket system, asks the practical question: “What do I store?” Mei answers: “The record, as --json prints it, next to the value. The ticket screen can then draw the box when an agent clicks a value. It costs a few hundred bytes per value; finding the place again costs a person’s minute.”

When to store provenance, and what it costs

Section titled “When to store provenance, and what it costs”

Store it always, for every value, from the first version of the pipeline. It is cheap at extraction time: the text layer, OCR, the table and the spreadsheet readers already know where each value is. provenance D011 takes about 0.2 seconds, and most of that is drawing the picture. Adding it later is expensive, because the places are lost once only the values are saved.

Provenance has limits, and the record should not hide them:

  • A box shows where the text was read, not that the value is right. An OCR box around a misread digit points at the right place with a wrong value. That is its use: a person sees the mistake at once.
  • The rules’ boxes cover whole rows. The evidence of a total covers the label and the amount. That is useful to a person, and less precise than a box around the digits only.
  • The vision model gives no box. Its values need a place found for them on the page. In Combine methods, the project looks for the model’s value in the OCR lines. It marks a value that it cannot find there as unverified.
  • An inferred value has evidence for its reason, not for itself. D036’s “CAD” is not printed. Mark it inferred, as the project does, so that nobody looks for it on the page.

Common mistake

The box is drawn in the wrong place

Symptom: The ticket screen shows D011 with the box of the total around the item line “1 x 189.00”, higher up the receipt. An agent sees “total 211.68” next to a box around 189.00 and starts to doubt a right value.

Cause: The box is in points, [14.2, 226.8, 212.6, 235.8]. The screen drew the page at 100 dpi and used the numbers as pixels. One point is 100/72 pixels at that resolution, so every coordinate is too small by a factor of 1.39. The box landed at 163 to 170 points, on the line “1 x 189.00”:

total box in pt: [14.2, 226.8, 212.6, 235.8]
drawn at 100 dpi, right: [20, 315, 295, 328] px
drawn at 100 dpi, unit forgotten: [14, 227, 213, 236] px = in pt [10, 163, 153, 170]
lines there: ['1 x 189.00', '189.00']
Two copies of the lower part of receipt D011. Left, titled Right: points times 100 over 72: a green box surrounds the line TOTAL CAD 211.68. Right, titled Unit forgotten: points used as pixels: a red box surrounds part of the item line 1 x 189.00, higher up and to the left.
The total's box of D011 drawn on a 100 dpi picture. Left: the points scaled by 100/72. Right: the points used as pixels, so the box lands on an item line.
Text description of the diagram

Both pictures show the same receipt drawn at 100 dots per inch. On the left, the box of the total was scaled from points to pixels, and it surrounds the TOTAL line with CAD 211.68. On the right, the same four numbers were used as pixels. Every coordinate is about 28% too small, so the box is higher and narrower, and it surrounds most of the item line “1 x 189.00”.

Diagnose: Read the unit of the evidence and the size of the picture. A box that is too small and too close to the top-left corner is a unit problem. A box in the right place on one page and wrong on another is a problem of page numbers.

Fix: Never store a box without its unit, and convert at drawing time. Use points × dpi / 72 for a PDF page, and pixels as they are for a picture file. provenance.draw() does exactly this (k = DRAW_DPI / 72 for pt). Test the drawing on a known value, as test_absent_values_have_no_evidence_and_boxes_are_drawn does.

Consequence in AI work: wrong evidence is worse than no evidence. It looks like proof, so a reviewer trusts it, or stops trusting every box.

Your turn: a scanned PDF instead of a photo

Section titled “Your turn: a scanned PDF instead of a photo”

Change one condition: run the same command on D019, a scanned receipt in a PDF, instead of the photo D036. Both are read by OCR. Predict the unit of the boxes, and the page size you would need to draw them. Then run:

Terminal window
python -m doc_extract.provenance D019
Show the answer
vendor Larkfield found page 1, box [346, 377, 424, 390] pt
order_number LK-853270 found page 1, box [285, 454, 374, 466] pt
date 2026-07-04 found page 1, box [285, 441, 487, 454] pt
currency CAD found page 1, box [286, 579, 487, 592] pt
subtotal 249.00 found page 1, box [286, 541, 487, 554] pt
shipping absent (not_found) absent -
tax 29.88 found page 1, box [286, 552, 488, 578] pt
total 278.88 found page 1, box [286, 579, 487, 592] pt
payment_method Mastercard found page 1, box [285, 603, 487, 617] pt
boxes drawn on out/evidence/D019-ocr-p1.png

The unit is pt, although OCR read pixels. The file is a PDF, so its unit is the point: OCR’s pixel boxes at 200 dpi were multiplied by 0.36. To draw them, use the PDF page (612 × 792 points) at any resolution, and scale by dpi / 72. All boxes start near x = 285 points, almost half-way across the page. The scanned receipt is a small strip of paper in the middle of a letter-size scanner page.

Check your understanding

Explain to Tomás, with D036, why 'we have the value' is not enough, and what the evidence record adds.

Show the answer

On D036, the amount 1 011,78 appears twice, on the TOTAL line and on the cash line. So the value alone does not say which line the rule read. The tax 131.78 is not printed at all: it is the sum of TPS 44,00 and TVQ 87,78. And the currency CAD is inferred from the “$”. The evidence record names the file, the page, the box in pixels of the photo and the text seen. So an agent sees in seconds where the total came from and which two lines make the tax. The agent also sees that the currency is a conclusion. Without the record, every check starts with a search of the document.

Check your understanding

Predict: the project starts to draw PDF pages at 300 dpi for OCR instead of 200 dpi. Do the stored boxes of D019 change?

Show the answer

Only very little. OCR would return boxes in pixels at 300 dpi, with numbers about 1.5 times larger. But the project multiplies them by 72/300 instead of 72/200. The stored boxes stay in points, so they stay at the same places on the page. They can move by a fraction of a point, because OCR draws its boxes a little differently on a sharper picture. This is why the project stores points for PDF pages: the unit does not depend on how the page was drawn.

Check your understanding

Practical check: run python -m doc_extract.provenance D011 --json and find the evidence of the tax. What does 'seen' hold, and why is the box two lines high?

Show the answer

seen holds GST 5% 9.45 + PST 7% 13.23. The tax of D011 is the sum of two printed tax lines, GST 9.45 and PST 13.23. So 22.68 is not printed anywhere. The evidence covers both lines: the box is [14.2, 201.8, 212.6, 223.3] points, from the top of the GST line to the bottom of the PST line. A reviewer sees the two amounts that make the value.

This module kept the structure and the source of everything that the pipeline reads. You can now turn a policy into blocks with section paths and linked footnotes, without the page furniture. You can also split a file into its documents (Sections and hierarchy). You can recover a table across a page break, with its header rows, units and merged cells. And you can read a spreadsheet by its cells (Tables and spreadsheets). And you can store the evidence of every value and draw it on the original (this lesson). Your project matches the snapshot end-of-m03: python -m pytest -q prints 77 passed. Only README.md differs: the snapshot’s version lists this module’s commands. Copy it from the snapshot if you want it.

Module practice: a schema for a table over two pages

Section titled “Module practice: a schema for a table over two pages”

Design a schema for a table that spans two pages. A schema is the shape of the record that the pipeline stores for each cell. With it, a person can check every value on the page.

Guided: the AquaFlow price list in the PDF

Section titled “Guided: the AquaFlow price list in the PDF”
  1. List what the whole table needs, from tables D056. It needs the file, the pages ([1, 2]), the header rows (2), the column names and the units (CAD for both prices).

  2. Choose a key for each row. A row number changes when the supplier adds a product; the SKU does not. So the key is the SKU.

  3. List what each cell needs, from the previous lesson: the value as printed, the page, the box in points, and filled. Add the unit from its column.

  4. Write the schema, and fill it for two real cells. Take SP-TRV’s price, the first row of page 2, and HR-50’s category, filled from a merged cell. The values come from provenance D056 and from the tiny example of the previous lesson:

    {
    "table": {"file": "aquaflow-price-list.pdf", "pages": [1, 2], "header_rows": 2,
    "columns": ["Category", "SKU", "Description", "Unit", "Price (CAD) / Each", "Price (CAD) / Case of 6"],
    "units": {"Price (CAD) / Each": "CAD", "Price (CAD) / Case of 6": "CAD"}},
    "cells": [
    {"row": "SP-TRV", "column": "Price (CAD) / Each", "value": "89.00", "unit": "CAD",
    "page": 2, "box": [440, 108, 505, 127], "box_unit": "pt", "filled": false},
    {"row": "HR-50", "column": "Category", "value": "Hose reels", "unit": null,
    "page": 1, "box": [40, 138, 150, 214], "box_unit": "pt", "filled": true}
    ]
    }
  5. Check the schema against the four problems of the table. The header rows are described once, not stored as rows. The units sit on the columns. A filled cell says so and points at the merged box. And each cell has its own page, so a table over two, three or ten pages needs no other field.

Purchasing receives the same list as a PDF and as a spreadsheet. Extend the schema so that one product record can hold values from either file, with the right evidence for each. Fill it for SP-HOSE from both files. You can print what you need in Python with tables.recover and sheets.read, as in the previous lesson. Which values differ between the two files, and how should the record show it?

Show a worked solution

The real values of SP-HOSE, from the two readers:

pdf Category = Sprinklers | page 2 | box [40, 108, 150, 165] | filled True
pdf Price (CAD) / Each = 16.00 | page 2 | box [440, 127, 505, 146] | filled False
pdf Price (CAD) / Case of 6 = 86.40 | page 2 | box [505, 127, 572, 146] | filled False
sheet Category = Sprinklers | Prices!A37 | merged A31:A38
sheet Price each (CAD) = 16 | Prices!E37 | merged -
sheet Price per case (CAD) = 86.4 | Prices!F37 | merged -
sheet Pack weight (kg) = 0.8 | Prices!G37 | merged -

One record per product, one entry per field, and a list of sources per field. Each source has its own evidence shape: a page and a box, or a sheet and a cell.

{
"sku": "SP-HOSE",
"fields": {
"price_each": {"value": "16.00", "unit": "CAD", "sources": [
{"file": "aquaflow-price-list.pdf", "page": 2, "box": [440, 127, 505, 146], "box_unit": "pt", "seen": "16.00"},
{"file": "aquaflow-price-list.xlsx", "sheet": "Prices", "cell": "E37", "seen": 16}]},
"category": {"value": "Sprinklers", "unit": null, "sources": [
{"file": "aquaflow-price-list.pdf", "page": 2, "box": [40, 108, 150, 165], "box_unit": "pt", "filled": true, "seen": "Sprinklers (cont.)"},
{"file": "aquaflow-price-list.xlsx", "sheet": "Prices", "cell": "A37", "merged": "A31:A38"}]},
"weight": {"value": "0.8", "unit": "kg", "sources": [
{"file": "aquaflow-price-list.xlsx", "sheet": "Prices", "cell": "G37", "seen": 0.8}]}
}
}

What differs between the files: the PDF prints text (“16.00”), the spreadsheet stores a number (16). So the record stores one normalised value, and each source keeps what it saw. The weight is only in the spreadsheet, so its list has one source. The category comes from a merged cell in both files, and each source says so in its own way. The PDF has filled and the merged box on page 2; the spreadsheet has the merged range A31:A38. When two sources disagree, the record keeps both. That is the start of a conflict, which Combine methods sends to a person.

Completion check: trace every value to its place

Section titled “Completion check: trace every value to its place”

Trace each structured value to an inspectable source location. For each value below, give its evidence, then open that place and check the value there, without searching the document.

  1. D036: total 1011.78.
  2. D056: SP-TRV, price each 89.00.
  3. D057: HR-50, category Hose reels.
  4. D054: the footnote “² Prices in Canadian dollars, tax included.”: which block does it belong to, and where is that block?
Show a worked solution
  1. D036 total. IMG_4817.jpg, page 1, box [216, 1110, 915, 1210] px, seen TOTAL 1 011,78 $. Open out/evidence/D036-ocr-p1.png or the photo itself: the box is around the TOTAL line, not the cash line.

  2. SP-TRV price. aquaflow-price-list.pdf, page 2, box [440, 108, 505, 127] pt. Check it without a picture: cut that box out of page 2 with pdfplumber and read its text.

    import pdfplumber
    from doc_extract import documents
    with pdfplumber.open(documents.get("D056").path) as pdf:
    print(repr(pdf.pages[1].crop((440, 108, 505, 127)).extract_text()))
    '89.00'
  3. HR-50 category. aquaflow-price-list.xlsx, sheet Prices, cell A6, merged range A5:A8. Open the cells with openpyxl:

    from openpyxl import load_workbook
    from doc_extract import documents
    ws = load_workbook(documents.get("D057").path)["Prices"]
    print("E6 =", ws["E6"].value, "| A5 =", ws["A5"].value, "| A6 =", ws["A6"].value)
    E6 = 119 | A5 = Hose reels | A6 = None

    A6 is empty in the file and A5 holds the value: exactly what the evidence says. E6 holds HR-50’s price, 119.

  4. Footnote ². It belongs to the paragraph “You pay return shipping: 12.95 dollars² is taken from the refund.”, path Returns policy > Return shipping costs, page 1, box [318, 329, 548, 356] pt, from the “Your turn” of Sections and hierarchy. The footnote itself is printed at the bottom of the left column, but its evidence follows its mark.

Each value led to one place, and each place showed the value without a search. A value whose place you cannot open is not traced: for example a model’s value without a box, which the next module handles.

Recap

  • Provenance is where a value comes from. The project stores an evidence record with every value. It holds the file, the page and the bounding box with its unit, or the sheet, the cell and the merged range. It also holds the path and the text seen.
  • Boxes on PDF pages are stored in points, also after OCR on a scanned page (pixels × 72 / 200). Boxes on picture files stay in pixels of the original file.
  • The evidence answers what the value cannot: which of two printed amounts was read, which lines make a tax, and which values are inferred. An absent value has no box.
  • Draw the evidence back on the original (out/evidence/), and always convert with the unit. A box in the wrong unit looks like proof and points at the wrong line.
  • Store provenance from the first version: it is cheap when the value is read and expensive to recover later.

Next, Module 4 uses the vision model deliberately. You decide what to send it and when to call it, and you find a place on the page for its answers.

Next: Input and prompt design