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 = 0Three Authoring Paths#
1. Natural Language (NL)#
The simplest path, describe your curve in plain English. The StoryParser extracts anchors and modifiers automatically:
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:
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:
| Method | Description |
|---|---|
.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():
| Format | Example | Meaning |
|---|---|---|
"YYYY-MM" | "2024-01" | January 2024 |
"YYYY-Q1" | "2024-Q4" | Q4 2024 (months 10–12) |
| integer | 6 | Month 6 |
3. YAML Schema#
Declare outcome curves in misata.yaml and generate from the CLI:
# 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}misata generate # → data/orders.csv with exact monthly sumsRate 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#
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#
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#
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:
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:
| Field | Description |
|---|---|
ame_achievable | True if all curves can reach AME = 0 |
curves | List of CurvePreview per outcome curve |
warnings | Merged list of all warnings |
.summary() | Human-readable multi-line string |
.to_dict() | JSON-serialisable dict for UI rendering |
CurvePreview fields:
| Field | Description |
|---|---|
periods | List of PeriodPreview (period, target, est_rows) |
total_target | Sum of all period targets |
total_rows | Estimated total rows across all periods |
ame_achievable | True for this specific curve |
warnings | Prop. 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# 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_pandn_prows, sample proportionswfrom a symmetric Dirichlet(α). Thenx_i = ⌊w_i · T_p · 10^d⌋ / 10^dwith remainder correction givesΣ x_i = T_pexactly.
Key parameters:
concentration(Dirichlet α), higher = tighter per-row distributionavg_transaction_value(μ), setsn_p = round(T_p / μ)(Prop. 3)min/max_transactions_per_period, guards against distortion whenn_pis 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.

