Add Business Rules

English · Русский

Use structured YAML or JSON when generated rows must satisfy domain rules. The deterministic rule engine applies and validates the rules; an LLM is not the enforcement mechanism.

For a complete annotated format, see the runnable business-rules reference, including all rule families, condition operators, formula modes, aggregates, scenarios and a matching DatasetSpec. The standalone JSON Schema is generated from the runtime model.

Create A Rule File

The checked-in example at examples/orders_rules.yaml contains:

field_rules:
  - table: orders
    field: status
    required: true
    allowed_values: [paid, cancelled]
  - table: orders
    field: amount
    required: true
    min_value: 0

row_rules:
  - type: temporal_ordering
    table: orders
    start_field: created_at
    end_field: fulfilled_at
    allow_equal: true

  - type: formula
    table: orders
    field: amount
    expression: quantity * unit_price

Rule references must match fields in the effective DatasetSpec. Unknown keys, tables, fields, rule types, and formula syntax are rejected.

Prepare A Reviewed Spec

test-data-agent profile-example tests/fixtures/example_dataset \
  --output out/rules/profile.json

test-data-agent infer-spec out/rules/profile.json \
  --count 25 \
  --output out/rules/dataset_spec.yaml

Review out/rules/dataset_spec.yaml, then generate with rules:

test-data-agent generate out/rules/dataset_spec.yaml \
  --seed 12345 \
  --format csv \
  --business-rules examples/orders_rules.yaml \
  --output out/rules/generated

Review:

out/rules/generated/
  business_validation_report.json
  generation_manifest.json
  validation_report.json

The generation manifest records a normalized rule fingerprint, rule counts, pass/fail counts, truncation status, overall business validity, and bounded expected-versus-observed violation counts.

Supported Rule Categories

Category Use
Field rule Required values, allowed values, numeric bounds
Conditional required Require fields when a condition matches
Conditional allowed values Restrict a field under a condition
Temporal ordering Enforce start/end ordering
Formula Calculate or validate bounded arithmetic
Foreign key Preserve cross-table references
Aggregate formula Validate an aggregate expectation
Scenario Control weighted combinations of field values

Generate Controlled Invalid Cases

Use an explicit generation mode:

test-data-agent generate out/rules/dataset_spec.yaml \
  --count 100 \
  --seed 12345 \
  --format csv \
  --mode mixed \
  --invalid-ratio 0.02 \
  --business-rules examples/orders_rules.yaml \
  --output out/rules/mixed_cases

With business rules, mixed uses seeded random row selection at the requested invalid ratio and attempts an eligible rule violation for each selected row. The ratio is not an exact count of failed cells or rules. negative attempts eligible violations for every row; it does not make every field invalid, and tables without an applicable mutation can remain valid. Keep these datasets clearly separated from valid fixtures and review the validation report rather than expecting it to pass.

For tables with several field and row rules, selected rows are distributed deterministically across required, allowed-value, numeric-bound, conditional, temporal, and formula violations. Reusing the same seed, mode, ratio, and rule file reproduces the same negative cases. Foreign-key violations use synthetic missing parent keys without changing the parent table. Aggregate-formula violations change only the configured concrete numeric field. Count-style aggregate rules with field: "*" are validation-only because changing row counts would also break the dataset shape.

One selected invalid mutation can break several overlapping rules. The engine audits the affected row before and after mutation and includes newly broken local rules in the expected counts. Cross-table rules are checked before and after the injection pass; newly broken rules with a valid baseline are also counted. Injection requires a valid business-rule baseline and refuses an already invalid baseline before introducing negative cases. Standalone validation still reports unplanned failures as unexpected. An injection that does not break its selected rule still reports a missing expected failure. These audits share the normal work and deadline budgets.

The business validation report compares expected and observed violations per rule. unexpected_violation_count identifies failures that were not selected by controlled invalid generation; missing_expected_violation_count identifies selected cases that validation did not observe. expectations_met is true only when both counts are zero. These fields contain counts only, never generated row values.

Reproduce The Same Cases Through CLI And MCP

The checked-in examples/negative_cases/ bundle fixes the spec, rule file, seed (1300), mode (mixed), and invalid ratio (0.5). Run it through CLI:

test-data-agent generate examples/negative_cases/dataset_spec.yaml \
  --seed 1300 \
  --mode mixed \
  --invalid-ratio 0.5 \
  --format json \
  --business-rules examples/negative_cases/business_rules.yaml \
  --output out/negative-cli

The MCP equivalent uses the same files after copying the example bundle below the configured generator workspace. The mode and invalid ratio are already recorded in dataset_spec.yaml:

{
  "name": "generate_dataset",
  "arguments": {
    "spec_path": "negative_cases/dataset_spec.yaml",
    "output_folder": "negative-mcp",
    "output_format": "json",
    "seed": 1300,
    "business_rules_path": "negative_cases/business_rules.yaml"
  }
}

Both paths produce identical synthetic row files and matching expected versus observed violation counts. Use separate empty output folders when repeating the example.

Safety Restrictions

Do not put real identifiers, emails, phone numbers, addresses, credentials, tokens, or other production literals into rule files. Sensitive-looking literals are rejected by CLI and MCP entry points.

Formula syntax is a bounded arithmetic subset, not Python or SQL. Rule payload size, expression complexity, and estimated row/rule evaluations are limited.

See Configuration for adjustable limits.

Generation ranges and business invariants

A field's distribution describes sampling. A formula in assign mode computes and replaces its target; its result may leave that sampling range. The report records assignment-domain checks and violations separately from business-rule validity. Declare a field rule with min_value/max_value when the result must obey an invariant. Formula check mode validates existing values without assigning them; use it when replacement would hide the condition you intend to test. The complete runnable reference linked above includes both modes and conditional predicates, including null and inclusive range checks.

Scenario weights are relative probabilities, not exact row quotas or predicates that automatically bind a scenario to source rows. A passing rule report checks declared rules; it does not establish source fidelity or discover missing rules. Entity-attribute-value inputs are not automatically pivoted into business fields. Model the desired entities and declare relationships/rules explicitly before using the generation command above.

Conditional formulas and synthetic EAV models

A formula accepts an optional typed when condition in either assign or check mode. A false condition skips the expression entirely, so an unselected branch cannot divide by zero or overwrite a sampled value. Reports distinguish eligible_row_count, evaluated_row_count and skipped_row_count; skipped rows are not passed checks. Rules without when retain their old serialization, fingerprints and behavior. An explicit null condition value uses the existing condition semantics: equals: null selects null/missing, numeric ranges do not. A predicate field absent from the DatasetSpec rejects before generation.

row_rules:
  - type: formula
    table: observations
    field: value
    when: {field: attribute, equals: ratio}
    expression: numerator / divisor

Predicate fields cannot be row-rule assignment/repair targets. Multiple assigning formulas for one target require provably disjoint equals/in_values predicates on the same field; overlapping or unprovable branches reject before generation. Legacy unconditional assignments retain their previous order. Expressions keep the existing bounded arithmetic and exact DECIMAL rules; conditions share the same privacy checks, work allowance and deadline.

The runnable synthetic EAV example uses a reviewed ratio/total vocabulary, generated identifier domains and two disjoint exact-decimal formulas. Vocabulary and formulas are authored independently of source rows. Each observation has one attribute/value; no source pivot, source-key retention or duplicate merge occurs. Missing observations differ from nullable values. Declare entity/attribute uniqueness separately if required; the example intentionally permits repeated generated pairs.