Evaluation and explanation#
The fg.eval namespace, its result envelope (EvaluateResult / EvaluateRow), and the explanation surface (Explanation and the EvidenceGraph it carries).
fg.eval is a read-only namespace on a FactGraph / SDKStore instance. Assigning to it raises FrozenSnapshotError. It never writes to the ledger.
fg.eval.evaluate(rule_or_expr, *, head, engine=None, config=None) -> EvaluateResult#
Evaluates an application Rule or a RuleExpr against the current visible view and returns an EvaluateResult envelope. (The same method also accepts an Inference object or a structured derivation dict; those legacy derivation inputs are not covered here.)
Parameters:
rule_or_expr(positional, required): a single applicationRuleorRuleExpr. Exactly one positional input is accepted. Astrfirst argument raisesSDKStoreError(“string derivation DSL is not supported in SDK v1”). For aRule/RuleExprinput,head=is required.head(keyword, required forRule/RuleExprinput): the closing applicationRule. Must be an instance of the application protocolRule(for exampleRule.projection("user")). If absent, raisesSDKStoreError(“evaluate(rule_expr, …) requires head= Rule”). If not aRule, raisesSDKStoreError(“evaluate(rule_expr, …) head= must be Rule; …”).engine(keyword, defaultNone): one of"native","problog","pyreason","souffle".Noneresolves to"native". Any other value raisesSDKStoreError(“engine= must be one of: native, problog, pyreason, souffle”).config(keyword, defaultNone): aSemanticsProfile, or an SDK public semantics object (ProbLogConfig,PyReasonConfig). Only the"problog"and"pyreason"engines consume semantics; pairing a profile with another engine raisesSDKStoreError(“engine=’…’ does not consume SemanticsProfile”). ASemanticsProfilewhoseenginedoes not match the resolvedengineraisesSDKStoreError.
Returns: EvaluateResult.
Raises: SDKStoreError for unsupported keywords. view=, policy=, semantics_profile=, mode=, temporal_view=, registry=, and engine_options= are each rejected with a specific message (for example view= directs you to FactGraph.attach(db, view=view); see persistence.md). Any other unexpected keyword raises SDKStoreError (“unknown evaluate(rule_expr, …) keyword(s): …”). RuleExprError is raised when the head foundation is invalid.
Example:
result = fg.eval.evaluate(rule, head=rule, engine="native")
row = result[0]fg.eval.explain(rule_or_expr, *, head, engine=None, config=None) -> Explanation#
Replays a closed-head evaluation and returns an Explanation. The first matching result row is explained; if no row matches the closed head, a "failed" explanation is returned.
Parameters:
rule_or_expr(positional, required): a singleRuleExpror applicationRule. Exactly one positional input; otherwise raisesSDKStoreError(“eval.explain(expr, …) accepts exactly one RuleExpr or Rule input”).head(keyword, required): a closed applicationRule. Required (SDKStoreError“eval.explain(expr, …) requires head= closed Rule” when absent). Must be aRule(SDKStoreError“eval.explain(…) head= must be Rule”). If the head has unbound ports, raisesRuleExprError(“manual explain head must be closed; unbound ports: …”).engine(keyword, defaultNone): same accepted values asevaluate.config(keyword, defaultNone): same accepted values asevaluate.
Returns: Explanation. When a matching row is found, the explanation mirrors the row’s own explain() result. When no row matches, Explanation(status="failed", failure_class="closed_head_false", ...) is returned with row=None.
Raises: SDKStoreError for view= (directs to FactGraph.attach) and for any other unknown keyword (“unknown eval.explain(…) keyword(s): …”). RuleExprError when head is not closed.
Example:
explanation = fg.eval.explain(rule, head=closed_head, engine="native")
print("\n".join(explanation.narrate()))For the cell-level fg.audit.explain (chosen-policy state for a stored assertion), see audit.md. Choosing between engines is covered in engines-and-semantics.md.
EvaluateResult#
Frozen dataclass envelope returned by fg.eval.evaluate. It holds the result rows and a deterministic fingerprint of the evaluation.
Fields:
result_id: str: identifier prefixedevalr_v1:.rows: tuple[EvaluateRow, ...]: the result rows. Row ids are unique within a result; duplicates raiseProtocolShapeError.head: Rule: the closing application protocolRule.engine: str: the engine that produced the result.evaluated_at: object: evaluation timestamp.fingerprint: ResultFingerprint: the digests identifying this evaluation (see below).engine_meta: Mapping[str, Any]: engine and adapter metadata.
Methods and operators:
first() -> EvaluateRow | None: the first row, orNoneif there are no rows.exists() -> bool:Trueif there is at least one row.count() -> int: number of rows.__iter__() -> Iterator[EvaluateRow]: iterate the rows (for row in result).__len__() -> int:len(result), equal tocount().__getitem__(index: int) -> EvaluateRow: index access (result[0]).
Deprecated properties (each emits a DeprecationWarning and forwards to fingerprint or engine_meta): run_id, engine_version, adapter_version, expr_digest, rule_set_digest, view_snapshot_digest, config_digest, result_digest. Read these from result.fingerprint.* and result.engine_meta[...] instead.
Example:
result = fg.eval.evaluate(expr, head=head, engine="native")
if result.exists():
print(result.count(), "rows")
for row in result:
...ResultFingerprint#
Frozen dataclass of the digests that identify an evaluation. Carried by EvaluateResult.fingerprint. All digest fields are sha256:-prefixed tokens; run_id is run_v1:-prefixed.
Fields:
expr_digest: strrule_set_digest: strview_snapshot_digest: strconfig_digest: str | None:Nonewhen no semantics profile was applied.result_digest: strrun_id: str
EvaluateRow#
Frozen dataclass for one row of an EvaluateResult. Rows are bound to their owning result; calling a live-only method on a detached row raises DetachedRowError.
Fields:
row_id: str: non-empty row identifier.bindings: Mapping[str, Any]: the variable bindings for this row (frozen mapping).kind: ClaimKind: one of"fact_triple","rule_head","aggregate_result","projection".digest: str:sha256:-prefixed row digest.closed_head_digest: str:sha256:-prefixed digest of the closed head.certainty: Certainty | None: the row’s certainty, orNone.
Methods:
explain() -> Explanation: explain this row against its owning result. ReturnsExplanation(status="passed", evidence=..., row=self, ...)on success. If the row is not present in the current result, returnsstatus="unsupported"with error codeROW_NOT_IN_RESULT; if the row no longer matches the result anchor,status="unsupported"with codeSTALE_ROW; if the evidence graph fails validation,status="unsupported"with codeGRAPH_VALIDATION_FAILED. RaisesDetachedRowErrorif the row has no owning result.close() -> Rule: build a closed applicationRulefrom this row’s bindings. RaisesDetachedRowErrorif the row is detached, stale, or does not belong to its result.
Example:
row = result.first()
if row is not None:
print(row.bindings)
explanation = row.explain()Certainty#
Frozen dataclass attached to a row or evidence node, expressing a confidence interval.
Fields:
lo: float: lower bound, in[0.0, 1.0].hi: float: upper bound, in[0.0, 1.0].kind: CertaintyKind: one of"boolean","probabilistic","possibilistic". Default"boolean".
Raises: ProtocolShapeError if kind is not one of the three kinds, if a bound is not a finite number in [0.0, 1.0], or if lo > hi.
The module constant BOOLEAN_CERTAINTY is Certainty(1.0, 1.0, "boolean").
Explanation#
Frozen dataclass returned by fg.eval.explain and EvaluateRow.explain. It carries the outcome status, the evidence graph (when present), and the rendered output accessors.
Fields:
status: ExplanationStatus: one of"passed","failed","unsupported","invalid_request".evidenceis non-Noneif and only ifstatusis"passed"or"failed".evidence: EvidenceGraph | None: the evidence graph (see below).Nonefor"unsupported"and"invalid_request".row: EvaluateRow | None: the explained row, orNone. Required whenstatusis"passed".result_id: str | None:evalr_v1:-prefixed result id; required (non-empty) whenstatusis"passed"or"failed".failure_class: ExplanationFailureClass | None: one of"no_matching_row","closed_head_false","stale_row","row_not_in_result","insufficient_closed_bindings". Required whenstatusis"failed"; must beNoneotherwise.checked_scope: Mapping[str, Any] | None: the digests checked during the replay (frozen mapping when provided).suggested_next_steps: tuple[str, ...]: default().errors: tuple[ErrorDTO, ...]: default(). Non-empty whenstatusis"unsupported"or"invalid_request".warnings: tuple[WarningDTO, ...]: default().
Rendered output:
repr -> tuple[str, ...] | None(property): deterministic structured lines walked from the evidence graph. ReturnsNonewhenstatusis"unsupported"or"invalid_request". Cached after first access.narrate() -> tuple[str, ...] | None: a human-readable narrative of the evidence graph. ReturnsNonewhenstatusis"unsupported"or"invalid_request". Cached after first access.
Raises: ProtocolShapeError on construction when the field invariants above are violated (for example a "passed" status without a row, a "failed" status without a failure_class, or an "unsupported" / "invalid_request" status with an empty errors tuple).
Example:
explanation = row.explain()
lines = explanation.narrate()
if lines is not None:
print("\n".join(lines))EvidenceGraph#
Frozen dataclass carried by Explanation.evidence. It is a paths-model graph: each path is an EvidenceTree (or EvidenceTimeline) and each tree holds rules, which hold atoms with per-atom verdicts.
Fields:
graph_id: strengine: str: the engine that produced the evidence.layout_hint: LayoutHint: one of"tree"(constantLAYOUT_TREE) or"timeline"(constantLAYOUT_TIMELINE).subject_binding: Mapping[str, Any]: frozen mapping of the subject’s bindings.paths: tuple[EvidenceTree | EvidenceTimeline, ...]certainty: Certainty | None: defaultBOOLEAN_CERTAINTY.metadata: Mapping[str, Any]: frozen mapping; default empty.
Example:
evidence = explanation.evidence
for path in evidence.paths:
for rule in path.rules:
for atom in rule.atoms:
print(rule.rule_id, atom.repr_text, atom.verdict)EvidenceTree#
Frozen dataclass for one path in EvidenceGraph.paths.
Fields:
tree_id: strstatus: TreeStatus: one of"holds","fails","not_reached".rules: tuple[EvidenceRule, ...]joins: tuple[EvidenceJoin, ...]: default().certainty: Certainty | None: defaultBOOLEAN_CERTAINTY.metadata: Mapping[str, Any]: frozen mapping; default empty.
EvidenceRule#
Frozen dataclass for a rule occurrence within an EvidenceTree.
Fields:
occurrence_alias: strrule_id: strrole: RuleRole: one of"head","body".status: TreeStatus: one of"holds","fails","not_reached".repr_text: str | None: defaultNone.ports: Mapping[str, Any]: frozen mapping; default empty.atoms: tuple[EvidenceAtom, ...]: default().
EvidenceAtom#
Frozen dataclass for a single atom within an EvidenceRule, with its verdict.
Fields:
form: AtomForm: one ofFact,Compare,Builtin,Aggregate.verdict: Verdict: one ofHolds,Fails,NotReached(see below).atom_id: strrepr_text: str | None: defaultNone.negated: bool: defaultFalse.timestep: int | None: defaultNone.
Verdict types (Verdict = Holds | Fails | NotReached):
Holds: fieldscertainty: Certainty(defaultBOOLEAN_CERTAINTY),support: tuple[Source, ...](default()).Fails: fieldcertainty: Certainty(defaultBOOLEAN_CERTAINTY).NotReached: fieldblocked_by: str | None(defaultNone).
Each Source (in Holds.support) has fields ref: str, field: str | None, value: Any (default None), and meta: Mapping[str, Any] (frozen mapping; default empty), tracing the supporting fact back to the ledger. For ledger-level provenance and audit, see audit.md and the explanation in reasoning-evaluation-and-evidence.md.
Legacy surface#
fg.eval.evaluate_candidates (raw CandidateSet output) and CandidateSet are part of the legacy candidate/derivation flow and are not covered here.