Provenance representation
- Lesson 10
- 50 min
- Module 3: Recover structure and tables
Lesson 10 of 19 · Document and Multimodal AIProvenance representation
1. Understand document inputs
2. Prepare and extract content
3. Recover structure and tables
4. Use multimodal models deliberately
5. Validate and review results
6. Evaluate and deliver the pipeline
7. Case studies
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 ready
What you need to know
- Lines with boxes, points (pt) on PDF pages and pixels (px) on pictures, from The idea: three paths, one record in Text and OCR paths.
- Boxes mapped back onto the original picture, from Keep the original in Image preparation.
- Table cells with a page and a box, and spreadsheet cells with an address, from The idea: one table, one record per row, one place per cell and Spreadsheets: read the cells, never a picture in Tables and spreadsheets.
What you need on your computer
- Your project at the end of the previous lesson:
python -m pytest -qprints74 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.
Get the lesson’s file
Section titled “Get the lesson’s file”-
In the project folder, copy the module and its tests from the snapshot:
Terminal window S=../nextia-learning-labs/C13/snapshots/end-of-m03cp $S/doc_extract/provenance.py doc_extract/cp $S/tests/test_provenance.py tests/Terminal window $S = "..\nextia-learning-labs\C13\snapshots\end-of-m03"Copy-Item "$S\doc_extract\provenance.py" doc_extract\Copy-Item "$S\tests\test_provenance.py" tests\ -
Run the tests:
Terminal window python -m pytest -qThe last line is
77 passed. This is the test count of the snapshotend-of-m03.
A highlighter on an expense report
Section titled “A highlighter on an expense report”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.
The problem: a value without its place
Section titled “The problem: a value without its place”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.
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 dpilines: [('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.
Show where every value came from
Section titled “Show where every value came from”-
Show the evidence of every field of D011, a digital receipt:
Terminal window python -m doc_extract.provenance D011vendor Larkfield found page 1, box [76, 23, 151, 37] ptorder_number LK-103119 found page 1, box [14, 102, 101, 111] ptdate 2026-08-22 found page 1, box [14, 89, 213, 98] ptcurrency CAD found page 1, box [14, 227, 213, 236] ptsubtotal 189.00 found page 1, box [14, 189, 213, 198] ptshipping absent (not_found) absent -tax 22.68 found page 1, box [14, 202, 213, 223] pttotal 211.68 found page 1, box [14, 227, 213, 236] ptpayment_method Cash found page 1, box [14, 252, 213, 261] ptboxes drawn on out/evidence/D011-text-p1.pngEvery 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 isnot_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. -
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. -
See the record that the pipeline stores:
Terminal window python -m doc_extract.provenance D011 --jsonHere 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
boxon its own line; it is the same list. -
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 D036vendor Larkfield found page 1, box [492, 162, 791, 238] pxorder_number LK-706142 found page 1, box [252, 428, 660, 502] pxdate 2026-08-09 found page 1, box [255, 381, 991, 491] pxcurrency CAD inferred page 1, box [216, 1110, 915, 1210] pxsubtotal 880.00 found page 1, box [225, 988, 926, 1089] pxshipping absent (not_found) absent -tax 131.78 found page 1, box [220, 1029, 922, 1171] pxtotal 1011.78 found page 1, box [216, 1110, 915, 1210] pxpayment_method Cash found page 1, box [212, 1190, 905, 1289] pxboxes drawn on out/evidence/D036-ocr-p1.pngThe boxes are in pixels of the photo
IMG_4817.jpgitself (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:
python -m doc_extract.provenance D056python -m doc_extract.provenance D057SP-MIST: price each 36.00 CAD <- aquaflow-price-list.pdf, page 1, box [440, 708, 505, 727] ptSP-TRV: price each 89.00 CAD <- aquaflow-price-list.pdf, page 2, box [440, 108, 505, 127] ptSP-HOSE: price each 16.00 CAD <- aquaflow-price-list.pdf, page 2, box [440, 127, 505, 146] ptSP-BASE: price each 12.50 CAD <- aquaflow-price-list.pdf, page 2, box [440, 146, 505, 165] ptHR-30: price each 59 CAD <- aquaflow-price-list.xlsx, sheet Prices, cell E5HR-50: price each 119 CAD <- aquaflow-price-list.xlsx, sheet Prices, cell E6HR-15C: price each 34 CAD <- aquaflow-price-list.xlsx, sheet Prices, cell E7HR-30P: price each 79 CAD <- aquaflow-price-list.xlsx, sheet Prices, cell E8HR-50: category Hose reels <- sheet Prices, cell A6, merged range A5:A8The 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:

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] pxdrawn at 100 dpi, unit forgotten: [14, 227, 213, 236] px = in pt [10, 163, 153, 170]lines there: ['1 x 189.00', '189.00']
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:
python -m doc_extract.provenance D019Show 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] ptboxes drawn on out/evidence/D019-ocr-p1.pngThe 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.
Knowledge check
Section titled “Knowledge check”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.
Does your answer have the main points of ours?
Sign in and enroll in the course to save your answers. Every check counts toward the certificate.
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.
Does your answer have the main points of ours?
Sign in and enroll in the course to save your answers. Every check counts toward the certificate.
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.
Does your answer have the main points of ours?
Sign in and enroll in the course to save your answers. Every check counts toward the certificate.
Module recap
Section titled “Module recap”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”-
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 (CADfor both prices). -
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.
-
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. -
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 D056and 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}]} -
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.
Independent: one product, two files
Section titled “Independent: one product, two files”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 Truepdf Price (CAD) / Each = 16.00 | page 2 | box [440, 127, 505, 146] | filled Falsepdf Price (CAD) / Case of 6 = 86.40 | page 2 | box [505, 127, 572, 146] | filled Falsesheet Category = Sprinklers | Prices!A37 | merged A31:A38sheet 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.
- D036:
total 1011.78. - D056: SP-TRV,
price each 89.00. - D057: HR-50,
category Hose reels. - D054: the footnote “² Prices in Canadian dollars, tax included.”: which block does it belong to, and where is that block?
Show a worked solution
-
D036 total.
IMG_4817.jpg, page 1, box [216, 1110, 915, 1210] px, seenTOTAL 1 011,78 $. Openout/evidence/D036-ocr-p1.pngor the photo itself: the box is around the TOTAL line, not the cash line. -
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 pdfplumberfrom doc_extract import documentswith pdfplumber.open(documents.get("D056").path) as pdf:print(repr(pdf.pages[1].crop((440, 108, 505, 127)).extract_text()))'89.00' -
HR-50 category.
aquaflow-price-list.xlsx, sheet Prices, cell A6, merged range A5:A8. Open the cells with openpyxl:from openpyxl import load_workbookfrom doc_extract import documentsws = 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 = NoneA6 is empty in the file and A5 holds the value: exactly what the evidence says. E6 holds HR-50’s price, 119.
-
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