Skip to content

Commit 240ea73

Browse files
committed
document all 29 declarations and ship copy-paste prompts, with every example executed first
Twelve of the twenty-nine schema-level declarations had zero documentation: bitemporal, closures, degradations, event_logs, generation_mode, late_arrivals, missingness, stock_flows, time_grids, typos, vocabularies and waterfalls. Eight more were mentioned three times or fewer. The docs covered roughly a third of the product, and the uncovered third included the accounting identities nobody else ships. docs/reference/declarations.md now covers every one, grouped by what it is for, each with a runnable example. Writing it meant discovering the real API rather than recalling it, and the discovery was worth having: lifecycles.states takes objects and not bare strings, dag_edges and closures require a name, stock_flows.periods is a list of period labels and not a count, retention.curve is a mapping of offset to fraction, event_logs.state_events maps a state to one event type and pairs with a lifecycle, and a waterfall point carries ending_value where I had written value. That last class matters more than the individual keys. The engine warns and drops an invalid declaration rather than raising, so a schema that generates nothing at all looks exactly like one that worked. Four of my first drafts "ran" while doing nothing. Every example here was checked by asserting the declaration reached the config AND that its guarantee held on the rows: stock flows reconcile with a maximum residual of 0.0000000000, a six-period waterfall recomputes to every declared balance with a delta of 0.0000, group shares land on 0.5/0.3/0.2 exactly, time grids put 0 of 400 timestamps off-grid or outside hours, conditional missingness lands on 0.90 and 0.05, and the closure table equals an independently recomputed closure triple for triple. docs/prompts.md carries the copy-paste prompts: one for driving Misata from Claude, Cursor or ChatGPT (pointing the agent at llms.txt and the .md twins rather than hoping the model remembers the API), four verified schema recipes, and an honest section on plain English. Honest because I measured it: "800 customers, 1200 subscriptions and 5000 invoices" silently drops the invoices table, a relative clause like "where 12% of admissions are readmissions" produces a table named are_readmissions, and a row count can drift when a curve shares its sentence. Those are documented as measured behaviour rather than smoothed over, and the page says to check what you got. Diagrams are box-drawing text, not SVG, because every page has a Markdown twin that agents read instead of the HTML and an <svg> there is a hole exactly where the shape is explained. The docs gate caught the last defect on its own: the YAML example declared shares over a category column the schema never defined, which misata lint rejects even though generation accepts it. 44 of 44 runnable examples pass.
1 parent 2f27a7e commit 240ea73

3 files changed

Lines changed: 805 additions & 0 deletions

File tree

docs/prompts.md

Lines changed: 297 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,297 @@
1+
---
2+
title: "Copy-paste prompts for Misata: agents, schemas and plain English"
3+
description: "Ready-to-paste prompts for generating synthetic data with Misata, whether you are driving it from Claude, Cursor or ChatGPT, writing a schema directly, or starting from a sentence. Every prompt here was run before publishing."
4+
---
5+
6+
# Prompts
7+
8+
Three ways to reach the same engine, in descending order of how much you can
9+
trust the result without checking it.
10+
11+
```diagram
12+
# Reliability, and where the checking happens
13+
14+
you write the schema an agent writes it plain English
15+
▏ ▏ ▏
16+
exact, every key exact once emitted, approximate, and
17+
under your control agent may guess silently so
18+
▏ ▏ ▏
19+
▼ ▼ ▼
20+
feasibility refuses a contradiction in all three; only the first two
21+
guarantee that what you asked for is what was asked of the engine
22+
```
23+
24+
Every prompt on this page was executed against the engine before publishing,
25+
and the plain-English section says exactly where that path breaks.
26+
27+
---
28+
29+
## 1. Driving Misata from an agent
30+
31+
The most reliable way to get a schema you did not want to write by hand. The
32+
agent produces a schema, the engine validates it, and feasibility refuses
33+
anything contradictory, so a bad guess fails loudly instead of quietly.
34+
35+
Paste this into Claude, Cursor, ChatGPT or any coding agent, then add your own
36+
last paragraph.
37+
38+
```text
39+
You are writing a schema for Misata, a declarative synthetic data engine
40+
(pip install misata). Read https://misata.studio/llms.txt for the full
41+
documentation index; each page is also available as raw Markdown by appending
42+
.md to its URL.
43+
44+
Rules:
45+
- Output one Python dict, built with
46+
schema = misata.from_dict_schema(<the dict>, seed=42)
47+
tables = misata.generate_from_schema(schema)
48+
Note generate_from_schema takes a SchemaConfig and takes no seed of its own;
49+
the seed belongs on from_dict_schema.
50+
- Top-level keys are table names. Each table takes "__rows__" plus its columns.
51+
- Column types: integer, float, string, text, date, datetime, boolean,
52+
foreign_key. A foreign key is
53+
{"type": "foreign_key", "foreign_key": {"table": "parent", "column": "id"}}.
54+
- Schema-level declarations are __dunder__ keys beside the tables:
55+
__outcome_curves__ (an aggregate over time, hit exactly)
56+
__rate_curves__ (a rate over time, hit exactly)
57+
__group_shares__ (exact shares of a measure across a category)
58+
__joint_distributions__ (several margins holding at once)
59+
__waterfalls__ (movements reconciling to declared balances)
60+
__stock_flows__ (closing = opening + received - shipped, per unit)
61+
__lifecycles__ (a state machine, with legal transitions)
62+
__missingness__ (why values are missing, conditionally)
63+
__constraints__, __dag_edges__, __closures__, __graph_motifs__
64+
- Prefer declaring the aggregate over inventing per-row values. If the user
65+
says "revenue grows to 200k", that is an __outcome_curves__ declaration,
66+
not a min/max on a column.
67+
- Do not invent declaration names. If you are unsure a key exists, fetch
68+
https://misata.studio/docs/reference/declarations.md and check.
69+
- Always pass a seed, so the run is reproducible.
70+
71+
Now write a schema for: <describe your dataset here>
72+
```
73+
74+
Why this prompt works: it points the agent at a machine-readable index rather
75+
than hoping the model remembers the API, it names the declarations explicitly so
76+
the model does not invent plausible ones, and it tells the model to reach for a
77+
declaration rather than fake the number per row, which is the single most common
78+
mistake.
79+
80+
### Shorter version, for a model that already has the docs
81+
82+
```text
83+
Write a Misata schema (misata.from_dict_schema(..., seed=42) then
84+
misata.generate_from_schema(schema)) for:
85+
<your dataset>.
86+
87+
Use __outcome_curves__ for any "grows to X" aggregate, __group_shares__ for any
88+
"A is 40% of B", and foreign_key columns for every relationship. Check key names
89+
against https://misata.studio/docs/reference/declarations.md before answering.
90+
```
91+
92+
### Giving an agent the tools directly
93+
94+
Misata ships an MCP server, so an agent can generate data itself rather than
95+
writing code you then run:
96+
97+
```bash
98+
pip install "misata[mcp]"
99+
```
100+
101+
See the [MCP guide](guides/mcp.md). Once connected, this is enough:
102+
103+
```text
104+
Use the Misata MCP server to generate a 5-table ecommerce dataset with 2,000
105+
buyers and 20,000 orders where GMV grows from $80k in January 2026 to $260k in
106+
December 2026, then show me the integrity verification.
107+
```
108+
109+
---
110+
111+
## 2. Schema recipes, ready to paste
112+
113+
These run as written.
114+
115+
### A SaaS book with revenue that hits its targets
116+
117+
```python
118+
import misata
119+
120+
schema = misata.from_dict_schema({
121+
"customers": {
122+
"__rows__": 800,
123+
"id": {"type": "integer", "primary_key": True},
124+
"company": {"type": "text", "semantic": "company_name"},
125+
"signed_up": {"type": "date"},
126+
},
127+
"invoices": {
128+
"__rows__": 6000,
129+
"id": {"type": "integer", "primary_key": True},
130+
"customer_id": {"type": "foreign_key",
131+
"foreign_key": {"table": "customers", "column": "id"}},
132+
"issued_at": {"type": "date"},
133+
"amount": {"type": "float", "min": 50, "max": 4000},
134+
"plan": {"type": "string", "enum": ["Starter", "Pro", "Enterprise"]},
135+
},
136+
"__outcome_curves__": [{
137+
"table": "invoices", "column": "amount",
138+
"time_column": "issued_at", "time_unit": "month",
139+
"value_mode": "absolute",
140+
"curve_points": [{"date": "2026-01-01", "target_value": 50000},
141+
{"date": "2026-12-01", "target_value": 200000}],
142+
}],
143+
"__group_shares__": [{
144+
"table": "invoices", "measure": "amount", "group_column": "plan",
145+
"shares": {"Starter": 0.2, "Pro": 0.5, "Enterprise": 0.3},
146+
}],
147+
}, seed=42)
148+
tables = misata.generate_from_schema(schema)
149+
```
150+
151+
Every month's invoice total equals its declared target, and within every total
152+
the three plans split 20/50/30, exactly.
153+
154+
### An MRR waterfall that reconciles
155+
156+
```python
157+
schema = misata.from_dict_schema({
158+
"mrr_movements": {
159+
"__rows__": 900,
160+
"period": {"type": "string"},
161+
"movement_type": {"type": "string"},
162+
"amount": {"type": "float"},
163+
},
164+
"__waterfalls__": [{
165+
"table": "mrr_movements", "starting_value": 100000,
166+
"points": [{"period": f"2026-{m:02d}", "ending_value": 100000 + m * 6000}
167+
for m in range(1, 7)],
168+
"inflow_shares": {"new": 0.7, "expansion": 0.3},
169+
"outflow_shares": {"churn": 1.0},
170+
}],
171+
}, seed=42)
172+
tables = misata.generate_from_schema(schema)
173+
```
174+
175+
### An order lifecycle nobody can contradict
176+
177+
```python
178+
schema = misata.from_dict_schema({
179+
"orders": {
180+
"__rows__": 5000,
181+
"id": {"type": "integer", "primary_key": True},
182+
"status": {"type": "string"},
183+
"placed_at": {"type": "date"},
184+
},
185+
"__lifecycles__": [{
186+
"name": "order_flow", "table": "orders",
187+
"state_column": "status", "start_column": "placed_at",
188+
"states": [{"name": "placed"}, {"name": "paid"}, {"name": "shipped"},
189+
{"name": "delivered"}, {"name": "refunded", "terminal": True}],
190+
"transitions": [["placed", "paid"], ["paid", "shipped"],
191+
["shipped", "delivered"], ["delivered", "refunded"]],
192+
"initial": "placed",
193+
}],
194+
}, seed=42)
195+
tables = misata.generate_from_schema(schema)
196+
```
197+
198+
### Dirty data on purpose, for testing a cleaning step
199+
200+
```python
201+
schema = misata.from_dict_schema({
202+
"contacts": {
203+
"__rows__": 5000,
204+
"id": {"type": "integer", "primary_key": True},
205+
"name": {"type": "text", "semantic": "person_name"},
206+
"city": {"type": "string", "enum": ["Berlin", "Lisbon", "Oslo", "Porto"]},
207+
"notes": {"type": "text"},
208+
"is_active": {"type": "boolean"},
209+
},
210+
"__typos__": [{"table": "contacts", "column": "city", "count": 120}],
211+
"__duplicates__": [{"table": "contacts", "count": 60}],
212+
"__missingness__": [{
213+
"table": "contacts", "column": "notes",
214+
"rate": 0.75, "else_rate": 0.05,
215+
"when_column": "is_active", "when_op": "==", "when_value": False,
216+
}],
217+
}, seed=42)
218+
tables = misata.generate_from_schema(schema)
219+
```
220+
221+
Exactly 120 corrupted city values and exactly 60 duplicate rows, so your
222+
cleaning step has a known number to find and your test can assert it.
223+
224+
### Seed a real database
225+
226+
```bash
227+
misata seed "postgresql://user:pass@localhost:5432/mydb" --rows 5000
228+
```
229+
230+
It reads the schema from the database, inserts parents before children, and
231+
verifies every foreign key afterwards. It plans by default; add `--apply` once
232+
the plan looks right. See [Database seeding in Python](guides/database-seeding-python.md).
233+
234+
---
235+
236+
## 3. Plain English
237+
238+
`misata.generate("...")` parses a sentence into a schema. It is the fastest way
239+
to something on screen and the least precise, so use it to start and then edit
240+
the schema it produced.
241+
242+
### What works
243+
244+
Nouns with counts, in one clause:
245+
246+
```python
247+
misata.generate("A SaaS company with 800 customers and 1200 subscriptions", seed=11)
248+
```
249+
250+
Put a rate in its own sentence rather than in a relative clause:
251+
252+
```python
253+
misata.generate(
254+
"A payments processor with 500 merchants and 20000 transactions. "
255+
"The dispute rate is 1.5%.",
256+
seed=11,
257+
)
258+
```
259+
260+
### What does not, measured
261+
262+
These are real results from the current parser, not cautions in principle.
263+
264+
- **An entity can be dropped silently.** `"800 customers, 1200 subscriptions and
265+
5000 invoices"` produces `customers` and `subscriptions` only. No warning, no
266+
invoices table.
267+
- **A relative clause can become a table.** `"...where 12% of admissions are
268+
readmissions within 30 days"` produces a table named `are_readmissions`.
269+
Splitting the rate into its own sentence avoids it.
270+
- **Row counts drift when a curve is in the same sentence.** Adding "where
271+
revenue grows from X to Y" to a sentence that also declares counts can leave a
272+
table at the default row count instead of the one you asked for.
273+
274+
So: always check what you got before you trust it.
275+
276+
```python
277+
tables = misata.generate("...", seed=11)
278+
print({name: (len(df), list(df.columns)) for name, df in tables.items()})
279+
```
280+
281+
If a table is missing or a count is wrong, move to a dict schema. The plain
282+
English path is a draft; the schema is the specification. Sections 1 and 2 above
283+
exist because that is the honest split.
284+
285+
The [canvas at misata.studio/try](https://misata.studio/try) shows the parsed
286+
schema visually before you generate, which is the fastest way to catch a
287+
misreading.
288+
289+
---
290+
291+
## Next
292+
293+
- [Declaration reference](reference/declarations.md), every property you can state
294+
- [Quick start](quickstart.md)
295+
- [Try it without installing](https://misata.studio/try)
296+
- [Ready-made datasets](https://misata.studio/datasets)
297+
- [What Misata does not do well yet](https://misata.studio/limitations)

0 commit comments

Comments
 (0)