Skip to content

IDP Ground

Type: idp-ground  ·  Package: IDP Activities v1.18.0  ·  Output: object

Takes what AI Extract returned and finds each value back in the document. Three things come out of that: the box where it sits, a measured confidence (not one the model claims), and the decision about which fields a person has to look at.

The schema built by IDP Build Schema asked the model, for each field, for the value and the literal text it comes from. This activity searches for that literal among the document’s words:

  • Found → it has a box, and the confidence is the OCR’s for those words.
  • Not found → the value cannot be verified. To review.
ParameterEditorDescription
documentexpressionThe document from IDP Load Document.
extractionexpressionThe object from AI Extract.
taxonomyJSONThe same taxonomy the schema was built from.
FieldTypeWhat it is
documentTypestringThe taxonomy’s document type.
needsReviewbooleantrue if any field needs review.
reviewFieldsListNames of the fields that need it.
valuesDictionaryName → value. For using the data directly.
fieldsListOne object per field, with the full detail.

Each entry in fields carries: name, type, value, valueRaw, grounded, confidence, needsReview, reason, page, x, y, width, height, rows and rowCount.

A field of type table carries its rows in rows, and each cell has exactly the same shape as a scalar field — its value, its confidence, its box and its reason. In values, the table comes out as a list of clean rows:

values["maintenance"] = [
{ "date": "2024-01-15", "kind": "Preventive", "technician": "Juan Perez" },
{ "date": "2024-06-20", "kind": "Corrective", "technician": "Ana Gomez" }
]

A table needs review if any of its cells does.

Walking a table needs a nested Foreach:

Foreach items = = res["fields"] itemVariable = field
If condition = = field["type"] == "table"
Foreach items = = field["rows"] itemVariable = row
Set Variable name = cell value = = row["date"]
Log message = = toString(cell["value"]) + " conf=" + toString(cell["confidence"])

Why the confidence isn’t asked of the model

Section titled “Why the confidence isn’t asked of the model”

Because the confidence a LLM assigns itself is not calibrated: it says “0.95” just as readily when it is right as when it made the value up.

What is measured here is checkable instead: the literal is written in the document or it is not. A value the model invented appears nowhere, so it isn’t found, so it goes to review. And the box comes free with it.

That is why the schema asks for valueRaw alongside value: value may be normalised (2026-07-15) while the document says 15 July 2026. The literal anchors; the value is what you use.

  • Compared ignoring whitespace and case: OCR splits and joins words unpredictably, and where the spaces fall shouldn’t invalidate a real match. A literal can span several words, and the box merges them.
  • Punctuation is kept: in a serial number, a hyphen more or less is a real difference that deserves review.
  • The confidence of a multi-word box is the weakest link: if one word of the serial number read poorly, the serial number read poorly.

Finding the literal proves it exists, not that that occurrence is the one the value came from. A manufacturing date, an installation date and a last-review date can be identical: searching for the bare value would always match the first, and the box would point at the wrong data.

The fix is not to recover the position afterwards but to have the model cite it as it extracts. The document reaches the model with every line numbered:

[11] Manufacturing date: | 15/01/2024
[12] Installation date: | 15/01/2024
[13] Last review: | 15/01/2024

And for each field, alongside the value, the model returns the line number it read it from. Grounding then doesn’t search: it goes straight to that line’s box and narrows to the value inside it. Line 12’s date and line 13’s stop colliding because the model said “12”, not “15/01/2024”. This is what document-understanding systems do under the hood when they label — get the position at read time instead of guessing it afterwards — but without training a model per format.

  • The model cites a valid line → the box comes from there, directly and unambiguously.
  • The value doesn’t match inside the cited line (the model cited it loosely, or the value was normalised) → the whole-line box is used: the right line, less precise.
  • The model doesn’t cite → it falls back to searching the literal in the text; a repeated value that can’t be told apart that way goes to review, rather than pointing at one at random.

In a table, each cell cites its own line, so repeated rows each land in their place without depending on order.

The full pipeline:

IDP Load Document path = = docPath → output: doc
IDP Build Schema taxonomy = = asset("taxonomies/device-record.json") → output: schema
AI Extract credential = = credential("anthropic-key")
prompt = = doc["text"]
schema = = schema
→ output: extraction
IDP Ground document = = doc
extraction = = extraction
taxonomy = = asset("taxonomies/device-record.json")
→ output: res
If condition = = res["needsReview"]
Queue Add Item ...to the human validation queue

Walk the fields to draw the boxes over the rendered page:

Foreach items = = res["fields"] itemVariable = field
Log message = = field["name"] + " = " + toString(field["value"]) + " (conf " + toString(field["confidence"]) + ")"

Use one value — this needs a hop, because the engine does not chain indexers:

Set Variable name = vals value = = res["values"]
Log message = = "Serial: " + toString(vals["serial"])