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.