Skip to content

dpmxl.evaluator

Evaluation of rules and preconditions, per-record outcomes and the caches shared by the rules of one report. The module docstring below includes the thread-safety guarantees.

dpmxl.evaluator

Evaluate DPM-XL expressions against a report's data provider.

evaluate_rule evaluates a rule against a DataProvider and returns a RuleResult; it never raises for a bad rule or bad data.

The evaluator implements every construct of the DPM-XL grammar; constructs that need data beyond one report (time_shift, operation and table-group references) use optional provider methods. It keeps every intermediate record set so that a failing record can be explained operand by operand (see dpmxl.explain).

Semantics in brief:

  • A selection yields one record per selected data point, keyed by varying closed axes and open key components. Missing facts take the selection's default (null when none is written); a written default also stands in where another operand has a key the selection lacks.
  • interval: true keeps each fact's decimals tolerance, so = holds when the intervals overlap; otherwise comparisons are exact.
  • Null propagates through arithmetic and comparisons; and/or use three-valued (Kleene) logic. A null rule result is "not evaluated", never a failure.
  • Aggregates over no records are null, except count, which is 0.

Threads

evaluate_rule may run concurrently on one report from several threads. parse_cached is thread-safe. A shared SelectionCache is safe too, though two threads that miss the same selection at once may both build it. Providers shared across threads must be safe to read concurrently; MemoryProvider is. An Evaluator instance is not meant to be shared: create one per evaluation.

Examples:

>>> from dpmxl import MemoryProvider, Number, TableLayout, evaluate_rule
>>> provider = MemoryProvider(
...     [TableLayout("T", rows=("0010", "0020"), columns=("0010",))],
...     {
...         ("T", "0010", "0010", None, ()): Number.of(5, 0),
...         ("T", "0020", "0010", None, ()): Number.of(-2, 0),
...     },
... )
>>> result = evaluate_rule("{tT, r*, c0010} >= 0", provider)
>>> result.status, result.counts
('failed', {'passed': 1, 'failed': 1, 'null': 0})
>>> [outcome.key for outcome in result.failures]
[{'r': '0020'}]

AGGREGATES module-attribute

AGGREGATES = frozenset(
    {"sum", "count", "avg", "max", "min", "median"}
)

Canonical names of the aggregate functions (Call.name values).

rank and aggregates with over (...) are Analytic nodes and are not listed.

Evaluation dataclass

Evaluation(
    root: Node,
    result: Recordset,
    trace: dict[int, Recordset] = dict(),
    notes: list[str] = list(),
    assignments: dict[str, Recordset] = dict(),
    shapes: dict[tuple[str, int], Any] = dict(),
)

Raw evaluation result with intermediate record sets for explanation.

Pass it to explain (it is RuleResult.evaluation).

Attributes:

  • root (Node) –

    The evaluated tree.

  • result (Recordset) –

    The record set of the last statement.

  • trace (dict[int, Recordset]) –

    Each node's record set by id(node); empty when evaluated with trace=False.

  • notes (list[str]) –

    Remarks gathered during evaluation.

root instance-attribute

root: Node

result instance-attribute

result: Recordset

trace class-attribute instance-attribute

trace: dict[int, Recordset] = field(default_factory=dict)

notes class-attribute instance-attribute

notes: list[str] = field(default_factory=list)

assignments class-attribute instance-attribute

assignments: dict[str, Recordset] = field(
    default_factory=dict
)

Results of persistent assignments ({target} <- value) by target, written as normalized DPM-XL such as {tT, r0010, c0010} or {vX}.

shapes class-attribute instance-attribute

shapes: dict[tuple[str, int], Any] = field(
    default_factory=dict, repr=False, compare=False
)

What explanations derive from the tree alone (plain formulas, term counts), by kind and node id; shared by every failure explained.

value_at

value_at(
    node: Node, binding: Mapping[str, Hashable]
) -> list[Record]

Return the records a node produced for a key binding.

Returns:

  • list[Record] –

    list[Record]: Matching records; empty when the node was not

  • list[Record] –

    evaluated for that binding.

SelectionCache

SelectionCache()

Selection results shared by every rule evaluated against one report.

Rules over the same report select the same cells again and again (every sum rule on a table reads its columns); building each record set once makes large open tables affordable. Record sets are never mutated by the evaluator, so sharing them is safe. Use one cache per report. Rules may share it across threads: a selection two threads miss at once is built by both, and either result is kept.

Create an empty cache.

recordsets instance-attribute

recordsets: dict[SelectionSpec, Recordset] = {}

earlier instance-attribute

earlier: dict[tuple[str, int], SelectionCache] = {}

Caches of earlier reports for time_shift, by period and count.

Evaluator

Evaluator(
    provider: DataProvider,
    *,
    trace: bool = True,
    cache: SelectionCache | None = None,
    only_keys: Mapping[str, Collection[str]] | None = None,
)

Evaluates one expression tree against one provider.

The engine behind evaluate_rule, which most callers want instead: Evaluator.run raises EvaluationError where evaluate_rule returns status error. Create one per evaluation; share a SelectionCache between them.

Bind the provider.

Parameters:

  • provider (DataProvider) –

    Report data access.

  • trace (bool, default: True ) –

    Keep intermediate record sets for explanations.

  • cache (SelectionCache | None, default: None ) –

    Selection results shared across rules of one report.

  • only_keys (Mapping[str, Collection[str]] | None, default: None ) –

    Closed-axis key (r, c, s) to the normalized codes whose records selections keep, applied once a selection's keys are decided; used to explain a few known failing records of a large selection.

provider instance-attribute

provider = provider

cache instance-attribute

cache = cache

only_keys instance-attribute

only_keys = only_keys

trace_enabled instance-attribute

trace_enabled = trace

run

run(node: Node) -> Evaluation

Evaluate a complete expression or program.

Returns:

  • Evaluation ( Evaluation ) –

    The result and its trace.

Raises:

evaluate

evaluate(
    node: Node,
    context: SelectionSpec,
    bindings: Mapping[str, Value] | None,
) -> Recordset

Evaluate a node under a with context and optional key bindings.

Returns:

  • Recordset ( Recordset ) –

    The node's records.

Outcome dataclass

Outcome(
    key: Mapping[str, Hashable],
    passed: bool | None,
    raw_points: tuple[Any, ...] = (),
)

One evaluated record of a rule.

points are flattened from the evaluation on first access: most outcomes pass, and nobody reads their points.

Attributes:

  • key (Mapping[str, Hashable]) –

    The record key, such as {'c': '0020'} or {'qCPI': 'LEI-B'}; empty for a rule without keys. Pass it to explain.

  • passed (bool | None) –

    True, False, or None when the record's result is null (not evaluated).

  • raw_points (tuple[Any, ...]) –

    The points as the evaluation keeps them (possibly nested); read points instead.

key instance-attribute

key: Mapping[str, Hashable]

passed instance-attribute

passed: bool | None

raw_points class-attribute instance-attribute

raw_points: tuple[Any, ...] = field(default=(), repr=False)

points property

points: tuple[DataPoint, ...]

Distinct data points the record was computed from.

RuleResult dataclass

RuleResult(
    status: Status,
    outcomes: list[Outcome] = list(),
    message: str | None = None,
    evaluation: Evaluation | None = None,
    notes: list[str] = list(),
    assignments: dict[str, Recordset] = dict(),
)

The outcome of evaluating a rule and its optional precondition.

Attributes:

  • status (Status) –

    passed, failed, not_applicable (precondition not met, or no record could be evaluated), error (syntax error, unknown table, type mismatch, division by zero that reaches the result) or unsupported (a time_shift, {o_...} or {g_...} the provider cannot resolve, or an unknown function).

  • outcomes (list[Outcome]) –

    One Outcome per record.

  • message (str | None) –

    Why the status is error, unsupported or not_applicable.

  • evaluation (Evaluation | None) –

    The traced Evaluation for explain; None when evaluation did not complete.

  • notes (list[str]) –

    Remarks gathered during evaluation.

status instance-attribute

status: Status

outcomes class-attribute instance-attribute

outcomes: list[Outcome] = field(default_factory=list)

message class-attribute instance-attribute

message: str | None = None

evaluation class-attribute instance-attribute

evaluation: Evaluation | None = None

notes class-attribute instance-attribute

notes: list[str] = field(default_factory=list)

assignments class-attribute instance-attribute

assignments: dict[str, Recordset] = field(
    default_factory=dict
)

Results of persistent assignments by target (see Evaluation.assignments); the verdict comes from the last statement.

failures property

failures: list[Outcome]

Records whose result is false.

counts property

counts: dict[str, int]

Counts of passed, failed and not-evaluated records.

parse_cached cached

parse_cached(expression: str) -> Node

Parse an expression once and reuse the tree.

Trees are immutable and traces key nodes per evaluation, so one parsed tree can serve every report a rule is evaluated against. The cache is thread-safe and holds up to 32,768 expressions.

Returns:

  • Node ( Node ) –

    The expression tree.

Raises:

Examples:

>>> parse_cached("{tT, r0010} > 0") is parse_cached("{tT, r0010} > 0")
True

evaluate_rule

evaluate_rule(
    expression: str | Node,
    provider: DataProvider,
    *,
    precondition: str | Node | None = None,
    trace: bool = True,
    cache: SelectionCache | None = None,
    only_keys: Mapping[str, Collection[str]] | None = None,
) -> RuleResult

Evaluate a validation rule, honouring its precondition.

Parameters:

  • expression (str | Node) –

    Rule expression text or a parsed tree.

  • provider (DataProvider) –

    Report data.

  • precondition (str | Node | None, default: None ) –

    Optional precondition; the rule is not applicable unless it yields at least one true record.

  • trace (bool, default: True ) –

    Keep intermediate values for explain.

  • cache (SelectionCache | None, default: None ) –

    Selections shared by the rules of one report.

  • only_keys (Mapping[str, Collection[str]] | None, default: None ) –

    Keep only records with these closed-axis key codes in every selection (see Evaluator); the precondition is evaluated on all records. Not meaningful with aggregates, whose totals would cover only the kept records.

Returns:

  • RuleResult ( RuleResult ) –

    Status, per-record outcomes and the evaluation trace.

  • RuleResult –

    Syntax errors are reported as status error, not raised.

Examples:

>>> from dpmxl import MemoryProvider, Number, TableLayout, explain
>>> provider = MemoryProvider(
...     [TableLayout("T", rows=("0010", "0020"), columns=("0010",))],
...     {
...         ("T", "0010", "0010", None, ()): Number.of(10, 0),
...         ("T", "0020", "0010", None, ()): Number.of(12, 0),
...     },
... )
>>> expression = "with {tT, c0010}: {r0010} >= {r0020}"
>>> result = evaluate_rule(expression, provider, precondition="{v_T}")
>>> result.status
'failed'
>>> explain(result.evaluation, expression, result.failures[0].key).summary
'10 should be at least 12: difference 2'
>>> evaluate_rule("{tT, r0010} >", provider).status
'error'