On this page

Outcome Curve-Driven Synthesis

Misata's outcome-curve engine lets you declare what your data should aggregate to rather than how individual rows are distributed. The engine then generates rows that hit your declared targets exactly: not approximately.

This capability is formalised in the published paper:

Declarative Outcome-Conformant Synthesis: Exact, Closed-Form Specification Satisfaction and a Conformance Benchmark
Muhammed Rasin, arXiv:2606.08736v1 (2026)

The paper proves the mechanism is exactly conditional-sum sampling of a Gamma population via Lukacs' characterisation. The Aggregate Mean Error (AME) is exactly 0 for the closed-form engine, versus 74–86% miss for off-the-shelf learned synthesisers.


Core Concept#

You declare targets; Misata generates individual rows whose aggregate (sum per period) matches:

Declared:   Jan=$10k   Jun=$45k   Dec=$120k
Generated:  36,200 individual order rows
Verified:   sum(Jan orders) = $10,000.00 ✓  AME = 0
            sum(Dec orders) = $120,000.00 ✓  AME = 0

Three Authoring Paths#

1. Natural Language (NL)#

The simplest path, describe your curve in plain English. The StoryParser extracts anchors and modifiers automatically:

Python
import misata

tables = misata.generate(
    "An ecommerce store with 10k orders — revenue from $10k in January "
    "to $120k in December, with a Black Friday spike and an August dip"
)

Supported NL patterns include:

  • Anchors: "$50k in January", "$200k in Q4"
  • Multipliers: "doubled", "10x growth", "grew 300%"
  • Named events: "Black Friday", "Christmas", "summer slump", "New Year"
  • Quarter modifiers: "Q4 spike", "dip in Q3", "strong Q4"

2. SDK Builder (Programmatic)#

Use OutcomeCurveBuilder when you need precise control, the primary target for the no-code UI backend:

Python
from misata import OutcomeCurveBuilder, parse, generate_from_schema

# Sparse anchors — the builder interpolates between them
curve = (
    OutcomeCurveBuilder("orders", column="amount", time_column="order_date")
    .start("2024-01-01")
    .anchor("2024-01", 10_000)    # January: $10k
    .anchor("2024-06", 45_000)    # June: $45k (mid-year push)
    .anchor("2024-12", 120_000)   # December: $120k
    .dip("2024-08", factor=0.7)   # August slowdown (-30%)
    .spike("2024-11", factor=1.3) # November push (+30%)
    .avg_value(75.0)              # drives row-count planning (paper §3.1)
    .concentration(2.0)           # Dirichlet α — per-row dispersion (paper §3.2)
    .build()
)

schema = parse("An ecommerce store with 10k orders")
schema = OutcomeCurveBuilder.attach(schema, curve)
tables = generate_from_schema(schema)

Builder API reference:

MethodDescription
.start("YYYY-MM-DD")Start date of the curve timeline
.anchor(period, value)Pin an exact aggregate target for one period
.dip(period, factor=0.7)Multiply a period downward (factor < 1)
.spike(period, factor=1.3)Multiply a period upward (factor > 1)
.quarter_pattern(q1, q2, q3, q4)Set relative multipliers for all four quarters
.seasonal(black_friday=True, ...)Apply named seasonal event multipliers
.avg_value(mu)Average transaction value: drives row count (§3.1)
.concentration(alpha)Dirichlet α: controls dispersion (§3.2)
.row_bounds(min_tx, max_tx)Row-count bounds per period (Prop. 3 guard)
.intra_period(pattern)Within-period timestamp distribution
.build()Returns an OutcomeCurve

OutcomeCurveBuilder.attach(schema, *curves), non-mutating attach (returns a deep copy).

Period formats accepted by .anchor(), .dip(), .spike():

FormatExampleMeaning
"YYYY-MM""2024-01"January 2024
"YYYY-Q1""2024-Q4"Q4 2024 (months 10–12)
integer6Month 6

3. YAML Schema#

Declare outcome curves in misata.yaml and generate from the CLI:

YAML
# misata.yaml
name: Ecommerce Dataset
tables:
  orders:
    rows: 10000
    columns:
      order_id:
        type: int
        unique: true
      amount:
        type: float
        min: 5.0
        decimals: 2
      order_date:
        type: date
        start: "2024-01-01"
        end: "2024-12-31"

outcome_curves:
  - table: orders
    column: amount
    time_column: order_date
    time_unit: month
    pattern_type: growth
    avg_transaction_value: 75        # drives row-count planning (paper §3.1)
    concentration: 2.0               # Dirichlet α (paper §3.2)
    start_date: "2024-01-01"
    intra_period_pattern: weekday_heavy
    curve_points:
      - {period: "2024-01", value: 10000}
      - {period: "2024-06", value: 45000}
      - {period: "2024-12", value: 120000}
Shell
misata generate   # → data/orders.csv with exact monthly sums

Rate Curves (RCE Axis)#

OutcomeCurve targets aggregate sums (AME axis). RateCurve targets the rate of a boolean or categorical column per period, the rate-conformance (RCE) axis introduced in SpecBench.

SDK#

Python
from misata import OutcomeCurveBuilder

# Fraud rate rising from 2% → 5% through the year
fraud_curve = (
    OutcomeCurveBuilder.rate(
        "transactions",
        column="is_fraud",
        time_column="transaction_date",
    )
    .anchor("2024-01", 0.02)
    .anchor("2024-06", 0.035)
    .anchor("2024-12", 0.05)
    .interpolate(True)              # rates between anchors are interpolated
    .build()                        # → RateCurve
)

schema = OutcomeCurveBuilder.attach(schema, fraud_curve)

YAML#

YAML
rate_curves:
  - table: transactions
    column: is_fraud
    time_column: transaction_date
    time_unit: month
    true_value: true         # value counted as the positive class
    interpolate: true
    rate_points:
      - {period: "2024-01", rate: 0.02}
      - {period: "2024-06", rate: 0.035}
      - {period: "2024-12", rate: 0.05}

Verification#

Python
monthly_fraud = (
    tables["transactions"]
    .groupby(tables["transactions"]["transaction_date"].dt.to_period("M"))["is_fraud"]
    .mean()
)
# January: ≈ 0.020  ✓
# December: ≈ 0.050  ✓

Conformance Preview#

Before generating rows, call conformance_preview() to see what the engine will produce, period targets, estimated row counts, and clamping warnings. This is what a no-code UI renders to the user before generation:

Python
from misata import conformance_preview, OutcomeCurveBuilder, parse

curve = (
    OutcomeCurveBuilder("orders", column="amount", time_column="order_date")
    .anchor("2024-01", 10_000)
    .anchor("2024-12", 120_000)
    .avg_value(75.0)
    .build()
)
schema = parse("An ecommerce store with 5k orders")
schema = OutcomeCurveBuilder.attach(schema, curve)

preview = conformance_preview(schema)
print(preview.summary())
# Conformance preview: E-commerce Dataset
# Outcome curves: 1
# AME achievable: Yes ✓
#   orders.amount over order_date (month)
#     Total target: 785,000.00  |  Est. rows: 10,467
#     month:1    target=   10,000.00  rows≈  133
#     month:2    target=   20,182.00  rows≈  269
#     ...

# Serialise for the no-code UI chart renderer
chart_data = preview.to_dict()

ConformancePreview fields:

FieldDescription
ame_achievableTrue if all curves can reach AME = 0
curvesList of CurvePreview per outcome curve
warningsMerged list of all warnings
.summary()Human-readable multi-line string
.to_dict()JSON-serialisable dict for UI rendering

CurvePreview fields:

FieldDescription
periodsList of PeriodPreview (period, target, est_rows)
total_targetSum of all period targets
total_rowsEstimated total rows across all periods
ame_achievableTrue for this specific curve
warningsProp. 3 clamping warnings for this curve

No-Code UI Integration#

The SDK is designed so the no-code UI backend never touches the NL parser. The expected flow:

User drags anchors on chart
        ↓
UI POSTs JSON { "table": ..., "anchors": {...}, "modifiers": {...} }
        ↓
Backend: OutcomeCurveBuilder.from_dict(payload).build()
        ↓
Backend: conformance_preview(schema) → render chart to user
        ↓
User clicks "Generate"
        ↓
Backend: generate_from_schema(schema) → download CSV / seed DB
Python
# Backend handler (FastAPI / Flask / etc.)
from misata import OutcomeCurveBuilder, conformance_preview, generate_from_schema

def handle_preview(payload: dict, schema):
    curve = OutcomeCurveBuilder.from_dict(payload["curve"]).build()
    schema = OutcomeCurveBuilder.attach(schema, curve)
    preview = conformance_preview(schema)
    return preview.to_dict()          # sent to UI chart

def handle_generate(payload: dict, schema):
    curve = OutcomeCurveBuilder.from_dict(payload["curve"]).build()
    schema = OutcomeCurveBuilder.attach(schema, curve)
    return generate_from_schema(schema)

Mathematical Background#

The engine implements Proposition 2 from the paper:

Given a total T_p and n_p rows, sample proportions w from a symmetric Dirichlet(α). Then x_i = ⌊w_i · T_p · 10^d⌋ / 10^d with remainder correction gives Σ x_i = T_p exactly.

Key parameters:

  • concentration (Dirichlet α), higher = tighter per-row distribution
  • avg_transaction_value (μ), sets n_p = round(T_p / μ) (Prop. 3)
  • min/max_transactions_per_period, guards against distortion when n_p is clamped

See the full paper at arXiv:2606.08736v1 for proofs and SpecBench benchmark results.

Prefer generating data visually?

Misata Studio lets you turn natural language prompts into relational schemas with exact outcome invariants, live preview tables, and one-click database seeding without writing code.

Last updated Edit this page.md