ek.base#

Foundational data structures and strategy protocols for ek.

This module is the single source of truth (SSOT) for ek’s two-layer data model and for the small set of typing.Protocol strategy interfaces that every swappable behaviour in the framework implements. It contains **data structures and contracts only** – no business logic, no heavy imports. Importing it is cheap and pulls in nothing beyond the standard library.

The two layers#

Evaluating an information-extraction (IE) output splits into two halves – scoring against a gold reference (offline) and estimating quality with no reference (online) – and both operate on the same object:

  • Layer A – the schema (GraphGrammar and friends): a frozen description of the typed graph you extract, carrying both the types and the importance/cost weights per field/node/edge. It is the SSOT consulted by cost-sensitive metrics, by validators, and (eventually) by constrained decoders.

  • Layer B – the extraction metadata (AnnotatedExtraction and friends): per-extraction runtime metadata – provenance, raw signals, (calibrated) confidence, validator findings, and the accept/flag/block decision – keyed by a NodePath into the Layer-A schema, riding alongside the grammar without ever mutating it.

The strategy protocols#

Metric, Validator, Calibrator, DecisionPolicy and Signal are structural (duck-typed) contracts. Concrete implementations are resolved from ek.registry by name and injected as keyword-only arguments with smart defaults, so the simple path Just Works while every layer stays replaceable (open-closed).

Example

>>> g = GraphGrammar(
...     node_types={"donation": NodeType(
...         "donation",
...         fields={
...             "amount": FieldSpec("amount", "number", importance=10.0),
...             "city": FieldSpec("city", "string", importance=1.0),
...         },
...     )},
... )
>>> g.field_cost("donation", "amount")
10.0
>>> g.field_cost("donation", "city")
1.0

Module Attributes

Normalizer

Canonicalize a string before comparison (lowercase, fold, normalize, ...).

ConfidenceSource

Read named raw signals off a FieldEstimate (e.g. for calibration).

CostWeight

Cost of an edit on a typed element; defaults to reading *.importance.

OcrBackend

Anything that turns an image (path/bytes/array) into an OcrResult-shaped object (with .text and .blocks).

Functions

default_cost_weight(grammar, ref)

The default CostWeight: read the importance weight off the schema.

Classes

AnnotatedExtraction(grammar[, estimates])

Layer B: the verification metadata, referencing a frozen Layer-A grammar.

Calibrator(*args, **kwargs)

Maps a raw scalar signal to a probability after being fit on labels.

Decision(*values)

Terminal action a selective-prediction gate emits for an extracted value.

DecisionPolicy(*args, **kwargs)

Turns a (calibrated) confidence into an accept/flag/block Decision.

EdgeType(name, src, dst[, importance])

A directed relation kind between two NodeType names.

FieldEstimate(value[, raw_signals, ...])

One extracted value plus all of its reference-free verification metadata.

FieldSpec(name[, type, importance, domain, ...])

A leaf attribute of a node: its type, validation domain and cost weight.

Finding(field, layer[, severity, message, ...])

One validator observation about a value: a flag, optionally a correction.

GraphGrammar([node_types, edge_types])

The frozen schema SSOT: typed nodes/edges plus their importance weights.

Metric(*args, **kwargs)

Compares one prediction to one gold reference, returning a Score.

NodePath(node_id, node_type[, field])

Addresses a node (or one of its fields) in an extracted graph; keys Layer B.

NodeType(name[, fields, importance])

A node kind in the typed graph (e.g. an "invoice" or a "line item").

Provenance([engine, source_span, bbox, ...])

Where an extracted value came from, for audit and click-to-source review.

QualityReport([calibrated_confidence, ...])

The result of a reference-free quality estimate for one extraction.

Report([metric, aggregate, n, scores, ...])

Aggregate of many Score s over a corpus, with optional per-slice cuts.

Score(value[, precision, recall, f1, ...])

The result of comparing one prediction against one reference.

Severity(*values)

Whether a Finding merely flags or can also correct a value.

Signal(*args, **kwargs)

A reference-free quality signal: extractor output -> raw scalar score.

TypeRef(kind, name[, field])

A reference into the schema, so an injected cost function can read weights.

Validator(*args, **kwargs)

Reference-free check on a value, yielding zero or more Finding s.

class ek.base.AnnotatedExtraction(grammar, estimates=<factory>)[source]#

Bases: object

Layer B: the verification metadata, referencing a frozen Layer-A grammar.

The grammar is held by reference and never mutated; estimates maps a NodePath to the FieldEstimate for that node/field.

class ek.base.Calibrator(*args, **kwargs)[source]#

Bases: Protocol

Maps a raw scalar signal to a probability after being fit on labels.

ek.base.ConfidenceSource#

Read named raw signals off a FieldEstimate (e.g. for calibration).

alias of Callable[[FieldEstimate], Mapping[str, float]]

ek.base.CostWeight#

Cost of an edit on a typed element; defaults to reading *.importance.

alias of Callable[[GraphGrammar, TypeRef], float]

class ek.base.Decision(*values)[source]#

Bases: str, Enum

Terminal action a selective-prediction gate emits for an extracted value.

class ek.base.DecisionPolicy(*args, **kwargs)[source]#

Bases: Protocol

Turns a (calibrated) confidence into an accept/flag/block Decision.

class ek.base.EdgeType(name, src, dst, importance=1.0)[source]#

Bases: object

A directed relation kind between two NodeType names.

class ek.base.FieldEstimate(value, raw_signals=<factory>, confidence=None, findings=(), provenance=None, decision=None)[source]#

Bases: object

One extracted value plus all of its reference-free verification metadata.

class ek.base.FieldSpec(name, type='string', importance=1.0, domain=(), normalizer=None)[source]#

Bases: object

A leaf attribute of a node: its type, validation domain and cost weight.

Parameters:
  • name (str) – Attribute name.

  • type (str) – A free-form type tag (e.g. "string", "number", "date"); ek does not impose a type system – the tag drives metric/validator dispatch and is yours to define.

  • importance (float) – Per-field cost weight; the lever that makes “two extra digits on a donation amount” outweigh “a misspelled city”. Defaults to 1.0.

  • domain (tuple) – Optional allowed values: an enum tuple, a (lo, hi) range, or a regex string – consumed by validators, never by metrics.

  • normalizer (Optional[str]) – Optional key naming a canonicalizer (resolved from the registry) to apply to this field before comparison.

class ek.base.Finding(field, layer, severity=Severity.FLAG, message='', suggestion=None)[source]#

Bases: object

One validator observation about a value: a flag, optionally a correction.

A suggestion is present iff the producing layer can correct (only the deterministic canonicalizer and the gated LLM corrector freely do so). The original value is always retained elsewhere; a Finding never mutates.

class ek.base.GraphGrammar(node_types=<factory>, edge_types=<factory>)[source]#

Bases: object

The frozen schema SSOT: typed nodes/edges plus their importance weights.

Methods return the cost weight for a referenced type, defaulting to 1.0 for anything not declared, so a partial grammar is always usable.

cost(ref)[source]#

Importance weight for any TypeRef (the default CostWeight).

Return type:

float

edge_cost(name)[source]#

Importance weight of an edge type (1.0 if undeclared).

Return type:

float

field_cost(node, field_name)[source]#

Importance weight of a field on a node type (1.0 if undeclared).

Return type:

float

node_cost(name)[source]#

Importance weight of a node type (1.0 if undeclared).

Return type:

float

class ek.base.Metric(*args, **kwargs)[source]#

Bases: Protocol

Compares one prediction to one gold reference, returning a Score.

class ek.base.NodePath(node_id, node_type, field=None)[source]#

Bases: object

Addresses a node (or one of its fields) in an extracted graph; keys Layer B.

class ek.base.NodeType(name, fields=<factory>, importance=1.0)[source]#

Bases: object

A node kind in the typed graph (e.g. an “invoice” or a “line item”).

ek.base.Normalizer#

Canonicalize a string before comparison (lowercase, fold, normalize, …).

alias of Callable[[str], str]

ek.base.OcrBackend#

Anything that turns an image (path/bytes/array) into an OcrResult-shaped object (with .text and .blocks). Typed loosely so ek core never imports an OCR package.

alias of Callable[[Any], Any]

class ek.base.Provenance(engine='', source_span=None, bbox=None, raw_transcripts=())[source]#

Bases: object

Where an extracted value came from, for audit and click-to-source review.

bbox is intentionally typed Any so ek core need not import any OCR package; the OCR instance stores normalized [0, 1] top-left-origin geometry.

class ek.base.QualityReport(calibrated_confidence=None, decision=None, findings=(), raw_signals=<factory>, provenance=<factory>, per_field=None)[source]#

Bases: object

The result of a reference-free quality estimate for one extraction.

For a single value the scalar fields carry the verdict. For a whole AnnotatedExtraction, per_field maps each NodePath to its updated FieldEstimate (confidence/findings/decision filled in), and the scalar fields summarize: calibrated_confidence is the weakest field, and decision the most severe (block > flag > accept).

class ek.base.Report(metric='', aggregate=None, n=0, scores=<factory>, per_slice=<factory>, detail=<factory>)[source]#

Bases: object

Aggregate of many Score s over a corpus, with optional per-slice cuts.

aggregate is the corpus-level headline (computed by the metric’s own aggregator – e.g. globally accumulated WER – not a naive mean of per-item values). per_slice maps a slice label to its own aggregate.

class ek.base.Score(value, precision=None, recall=None, f1=None, metric='', detail=<factory>)[source]#

Bases: object

The result of comparing one prediction against one reference.

value is the headline number (higher is better unless a metric documents otherwise). The optional decomposition fields let structured metrics carry precision/recall/F1, an alignment, and raw counts (e.g. edit operations) so that corpus aggregation can be done correctly (e.g. global WER accumulation) rather than by averaging per-item scores. float(score) returns value.

class ek.base.Severity(*values)[source]#

Bases: str, Enum

Whether a Finding merely flags or can also correct a value.

class ek.base.Signal(*args, **kwargs)[source]#

Bases: Protocol

A reference-free quality signal: extractor output -> raw scalar score.

The cost_tier attribute (lower is cheaper) lets an escalation policy run cheap signals first and pay for expensive ones only on residual uncertainty.

class ek.base.TypeRef(kind, name, field=None)[source]#

Bases: object

A reference into the schema, so an injected cost function can read weights.

kind is one of "node", "edge" or "field"; for a field, name is the node-type name and field the field name.

class ek.base.Validator(*args, **kwargs)[source]#

Bases: Protocol

Reference-free check on a value, yielding zero or more Finding s.

ek.base.default_cost_weight(grammar, ref)[source]#

The default CostWeight: read the importance weight off the schema.

Return type:

float