Skip to content

Business Rules

A business rule is a Boolean condition over the model's dimensions, measures and metrics, declared once in OBML and compiled by OBSL into the query that reports its findings. No SQL, no code: a rule is data, which is what lets it be listed, explained, exported to the RDF graph and tested.

Two kinds of rule

Row-level Aggregate
Reads dimensions only at least one measure or metric
grain not allowed required: the dimensions the rule is evaluated at
Compiles to a WHERE predicate over the dimensions it reads a query selecting the grain plus the measures it reads, with the condition as HAVING; the only dimensions it may compare are grain dimensions
Example "this sale is an Electronics sale" "this category returns more than a tenth of what it sells"

The level is derived from the condition, not declared. Referencing another rule pulls in what that rule reads.

What a rule is for

type Says An evaluation returns
classification members of a class the rows or groups the condition holds for
eligibility who qualifies the rows or groups the condition holds for
validation an invariant that should hold the violations: where the condition does not hold
constraint an invariant that must hold the violations

validation and constraint rules may carry a severity (info, warning, error). On the other two types a severity is rejected with INVALID_RULE_SEVERITY, since there is nothing to be severe about.

Syntax

rules:
  Electronics Sale:                         # row-level: dimensions only
    type: classification
    description: Sales rows in the Electronics category
    condition: {field: Product Category, op: "=", value: Electronics}

  High Return Rate:                         # aggregate: reads a metric
    type: classification
    grain: [Product Category]
    condition: {field: Return Rate, op: ">", value: 0.1}

  Healthy Category:
    type: validation
    severity: warning
    grain: [Product Category]
    condition:
      all:
        - {field: Total Sales, op: ">", value: 0}
        - {not: {rule: High Return Rate}}   # a reference: the other rule's condition is inlined
Property Type Required Description
type enum No classification (default), validation, constraint, eligibility
condition object Yes The condition tree, see below
grain list Aggregate rules Dimensions the rule is evaluated at
severity enum No info, warning, error; validation and constraint rules only
description string No Business description
owner string No Responsible team or person
synonyms list No Alternative names (LLM hints)
customExtensions list No Vendor-keyed metadata
externalConceptMappings list No Links to concepts in an external ontology, see External Concept Mappings

The condition tree

Every node is exactly one of:

Node Shape Meaning
comparison {field, op, value} the same shape as a query filter: field names a dimension, measure or metric; op is any query filter operator except exists / nonexists; value is a scalar, a list, or a relative-date object
all {all: [node, ...]} every child holds
any {any: [node, ...]} at least one child holds
not {not: node} the child does not hold
rule {rule: Name} another rule's condition, inlined

A referenced rule must be of the same level and, for aggregate rules, declare the same grain; anything else is RULE_REFERENCE_MISMATCH. References form a DAG: a cycle is CYCLIC_RULE_REFERENCE.

Validation

Error code Cause
RULE_PARSE_ERROR rules is not a mapping, a rule is not a mapping, or a property has the wrong type or an unknown enum value
INVALID_RULE_CONDITION a condition node is not exactly one form, lacks op, uses an unknown or disallowed operator, or has an empty all / any
UNKNOWN_RULE_FIELD a comparison names something that is not a dimension, measure or metric
UNKNOWN_RULE a reference names a rule that does not exist, or the rule itself
UNKNOWN_RULE_GRAIN grain names an unknown dimension
RULE_GRAIN_REQUIRED an aggregate rule has no grain
RULE_GRAIN_NOT_ALLOWED a row-level rule has a grain
RULE_DIMENSION_OUTSIDE_GRAIN an aggregate rule compares a dimension that is not in its grain (it would be neither grouped nor aggregated)
RULE_REFERENCE_MISMATCH referenced rule has a different level or grain
CYCLIC_RULE_REFERENCE rules reference each other in a cycle
INVALID_RULE_SEVERITY severity on a classification or eligibility rule

All carry the YAML source span. A rule with a problem is reported and dropped; the others still load, so every problem surfaces at once.

What a rule compiles to

The compiler turns a rule into an ordinary query, so resolution, fan-out detection, multi-fact planning, every dialect and the result cache are reused unchanged:

# High Return Rate -> the groups the condition holds for
select: {dimensions: [Product Category], measures: [Return Rate]}
having:
  - {field: Return Rate, op: ">", value: 0.1}

# Healthy Category (validation) -> the violations: the condition negated
select: {dimensions: [Product Category], measures: [Total Sales, Return Rate]}
having:
  - negated: true
    filters:
      - logic: and
        filters:
          - {field: Total Sales, op: ">", value: 0}
          - {negated: true, filters: [{field: Return Rate, op: ">", value: 0.1}]}

API

Session-scoped under /v1/sessions/{sid}/models/{mid}/, each with a top-level shortcut:

Endpoint Returns
GET rules every rule with level, findings, grain, what it reads, dependencies, whether it compiles (and why not), plus statistics by type, level, severity, executable
GET rules/{name} one rule with its authored condition and the query behind it
POST rules/{name}/compile the SQL whose rows are the rule's findings ({"dialect": ...} optional; defaults like query/sql)
POST rules/compile every rule's SQL or the reason it failed, never hiding a failure
POST rules/{name}/evaluate runs one rule and returns its findings ({"dialect", "limit", "format_values"} optional); requires QUERY_EXECUTE=true
POST rules/evaluate runs every rule (or a filtered subset) into a report: per rule its status (executed, compiled on a dry run, skipped, failed), finding count, a sample of findings and the reason when it failed; filters types, severities, executable_only, max_rules; controls limit, dry_run, stop_on_first_failure, include_sql, include_rows

See the endpoint reference.

In the graph

Each rule is an obsl:Rule in the OBSL RDF graph: its type, severity, derived level, grain dimensions, serialized condition, what it reads (obsl:ruleReads) and the rules it depends on (obsl:dependsOnRule). Definitions only; evaluation results are never part of the model graph. Through OSI, rules ride whole in the ORIONBELT vendor extension.

In the UI

The Gradio UI has a Business Rules tab: the model's rules with statistics, a click on a row to select a rule, a Show rule definition toggle that displays the selected rule's OBML as YAML, a Test Rule button that lists the selected rule's findings, and Test All Rules for the report.

What is next

Rule references in query filters, and provenance and certification metadata on rules.