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(nullwhen none is written); a written default also stands in where another operand has a key the selection lacks. interval: truekeeps each fact's decimals tolerance, so=holds when the intervals overlap; otherwise comparisons are exact.- Null propagates through arithmetic and comparisons;
and/oruse 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
¶
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 withtrace=False. -
notes(list[str]) –Remarks gathered during evaluation.
trace
class-attribute
instance-attribute
¶
trace: dict[int, Recordset] = field(default_factory=dict)
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
¶
What explanations derive from the tree alone (plain formulas, term counts), by kind and node id; shared by every failure explained.
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.
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.
run
¶
run(node: Node) -> Evaluation
Evaluate a complete expression or program.
Returns:
-
Evaluation(Evaluation) –The result and its trace.
Raises:
-
EvaluationError–For a type error, an unknown table and the like;
UnsupportedErrorwhen the provider cannot resolve a reference.
Outcome
dataclass
¶
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 toexplain. -
passed(bool | None) –True,False, orNonewhen the record's result is null (not evaluated). -
raw_points(tuple[Any, ...]) –The points as the evaluation keeps them (possibly nested); read
pointsinstead.
raw_points
class-attribute
instance-attribute
¶
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) orunsupported(atime_shift,{o_...}or{g_...}the provider cannot resolve, or an unknown function). -
outcomes(list[Outcome]) –One
Outcomeper record. -
message(str | None) –Why the status is
error,unsupportedornot_applicable. -
evaluation(Evaluation | None) –The traced
Evaluationforexplain;Nonewhen evaluation did not complete. -
notes(list[str]) –Remarks gathered during evaluation.
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.
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:
-
DpmXlSyntaxError–If the text is not a valid expression.
Examples:
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'