Turn documents into trusted structured data.

Ovrin is a Go library that turns invoices, receipts, forms and contracts into typed, validated values — with the evidence for every one of them.

Typed output

A struct in. A struct out.

You declare the shape you want and get that shape back — not a map to assert your way through. The description in each tag is what the model reads; the rules after it are what gets checked.

type Invoice struct {
    Number   string  `ovrin:"invoice number,required"`
    Vendor   string  `ovrin:"vendor company name"`
    Currency string  `ovrin:"currency code,required,enum=UGX|USD"`
    Total    float64 `ovrin:"total amount,required,min=0"`
}

client := ovrin.New(ovrin.WithModel(model))

res, err := ovrin.Extract[Invoice](ctx, client, ovrin.File("in.pdf"))
if err != nil {
    return err
}

fmt.Println(res.Data.Vendor, res.Data.Total)

Staged extraction

Not a prompt with extra steps.

When a PDF carries its own text, reading it is exact and nearly free. Rendering those characters to pixels for a model to read back is a lossy round trip. OCR runs when there is no text layer — not before.

  1. 1Detect
  2. 2Acquire
  3. 3Normalise
  4. 4Schema
  5. 5Prompt
  6. 6Generate
  7. 7Validate
  8. 8Ground
  9. 9Score
client := ovrin.New(
    ovrin.WithModel(model),       // structured output
    ovrin.WithOCR(ocr),           // when there is no text layer
    ovrin.WithRenderer(renderer), // when OCR needs pixels
)

res, err := ovrin.Extract[Invoice](ctx, client, ovrin.File("scan.pdf"))
if err != nil {
    return err
}

fmt.Println(res.Valid, res.Confidence)

Explainability

Not 0.98. The reasons for it.

Confidence is composed from named signals that fail in uncorrelated ways, and every one is recorded on the field. It is a ranking signal rather than a probability — the documentation says so too, and will until it is calibrated.

res, err := ovrin.Extract[Invoice](ctx, client, src)
if err != nil {
    return err
}

if !res.Valid || res.NeedsReview {
    for _, reason := range res.Reasons {
        fmt.Printf("review %s: %s\n", reason.Field, reason.Why)
    }
    return nil
}

Provenance

Every value knows whether it was found.

A field that could not be read stays absent rather than quietly becoming zero. In a payments system, “the total is zero” and “we could not read the total” are different facts, and nothing is ever guessed to fill a struct.

total := res.Fields["total"]

if !total.Found {
    // Not the same fact as "the total is zero".
    return queueForReview(res)
}

fmt.Println(total.Value, total.Confidence)

Every result carries

Valid
whether every validation rule passed
Confidence
decomposed into named signals
Provenance
the page and region each value came from
NeedsReview
whether a person should look first