Misata

by rasinmuhammed

Not rated
GitHub

Description

Generate realistic multi-table test data with foreign keys that resolve and aggregates that reconcile, returned with an integrity check. Seeds Postgres, MySQL and SQLite from your own schema, or exports CSV/JSON/SQL/Parquet. Deterministic, so the same schema and seed give the…

About

Generate realistic multi-table test data with foreign keys that resolve and aggregates that reconcile, returned with an integrity check. Seeds Postgres, MySQL and SQLite from your own schema, or exports CSV/JSON/SQL/Parquet. Deterministic, so the same schema and seed give the same rows.

Details

Author
rasinmuhammed
Categories
Database, Other, Developer Tools

Setup

Install Misata in your MCP client (Claude Desktop, Cursor, Windsurf, and others).

Repository: https://github.com/rasinmuhammed/misata

Follow the installation instructions in the repository README, then restart your MCP client.

Use Misata from Claude / Cursor / Windsurf (MCP)

Misata ships a built-in](https://github.com/rasinmuhammed/misata/blob/HEAD/mcpb/)Model Context Protocolserver with a clear division of labour:the AI agent designs the schema, Misata guarantees the math.Agents are good at knowing that a veterinary clinic needs aspeciescolumn; Misata is good at making 50 000 rows where every foreign key resolves, every roll-up reconciles to the cent, and the same seed reproduces byte-identical output. The primary tool,generate_from_schema, accepts the agent's schema dict and returns the dataplus an integrity proof: per-relationship orphan counts the agent can show you.

2. Add to Claude Desktop(~/Library/Application Support/Claude/claude_desktop_config.json):

{ "mcpServers": { "misata": { "command": "misata-mcp" } } }

"Generate a fintech dataset with 1 000 customers, payments, and a 2% fraud rate."

"Design a clinical-trials database (sites, patients, visits, adverse events) and generate 100k rows."

"I need SaaS data: MRR from $50k in January, doubled by December, with a Q3 slump."

The agent designs whatever tables the request needs (any domain; it isn't limited to Misata's built-ins), calls Misata, writes CSVs to disk, and reports back with previews and the verified integrity summary. See theMCP guidefor Cursor/Windsurf/Zed setup and all six available tools.

mcp-name: io.github.rasinmuhammed/misata

misata generate \ --story "Brazilian fintech with R$ payments, CPF verification, and 3% fraud" \ --rows 1000 \ --output-dir ./demo_data # Writes CSVs plus: # ./demo_data/oracle_report.json
import misata # One sentence → multi-table DataFrame dict tables = misata.generate("A SaaS company with 5k users, monthly subscriptions, and 20% churn") print(tables["users"].head()) print(tables["subscriptions"].head())
# Or from the CLI misata generate --story "A SaaS company with 5k users and 20% churn" --rows 5000

The Oracle report is Misata's proof layer. It separates hard guarantees from advisory realism checks so generated data can be trusted in CI, demos, notebooks, and research comparisons.

- referential integrity across configured relationships
- requested row-count fulfillment
- schema validation and configured constraints
- deterministic reproducibility when a seed is set

- quality score and plausibility warnings
- privacy heuristics
- schema-vs-output fidelity score
- locale/domain fit for countries, cities, phone prefixes, and national IDs
- data-card metadata

import misata schema = misata.parse("Brazilian fintech with CPF verification", rows=1000) tables = misata.generate_from_schema(schema) oracle = misata.build_oracle_report(tables, schema, seed=schema.seed) print(oracle["passed"]) print(oracle["advisory"]["locale_domain_fit"]["locale"])

Pointmisata.mimic()at a real dataset and get a synthetic twin that matches every column's distributions but contains none of the original rows. No schema authoring, no config.

import pandas as pd import misata real = pd.read_csv("titanic.csv") twin = misata.mimic(real, rows=2000, seed=42, table_name="passengers")["passengers"]

The profiler handles the columns that break other tools:

- Alphanumeric code columns(Ticket"A/5 21171", Cabin"C85", SKUs, reference numbers) are detected by their character-class shape and reproduced structurally, same shapes in the right proportions, entirely new values, zero verbatim leak from the source. They no longer fall through to prose text generation.
- Floats keep their cents.A Fare of7.25generates as7.25-shaped values. The profiler infers decimal places from the data; semantic quantization (charm pricing) never fires on mimicked columns.
- Distributions are fit from the data.Skewed-positive columns get lognormal; constant columns get a uniform stub; everything else gets normal. Categorical columns with fewer than 50 values carry their real frequencies.

# Verify: no verbatim rows can leak through shared = [c for c in real.columns if c in twin.columns] overlap = pd.merge(real[shared].astype(str), twin[shared].astype(str), how="inner") assert len(overlap) == 0
tables = misata.generate("A fintech startup with 10k customers, fraud rate 3%, and IBAN accounts")

Misata reads the story, infers domain (fintech), scale (10 000 rows), and column semantics (fraud flag, IBAN format), no schema authoring needed.

2. YAML schema-as-code, commit it to git

misata init # scaffolds misata.yaml in the current directory misata generate # reads misata.yaml automatically
# misata.yaml name: my-app seed: 42 tables: users: rows: 1000 columns: user_id: { type: int, unique: true } email: { type: text, text_type: email } plan: { type: categorical, choices: [free, pro, enterprise] } orders: rows: 5000 columns: order_id: { type: int, unique: true } user_id: { type: foreign_key } amount: { type: float, min: 5.0, max: 500.0 } relationships: - "users.user_id → orders.user_id" constraints: - name: amount_above_cost table: orders type: inequality column_a: amount operator: ">" column_b: cost
schema = misata.load_yaml_schema("misata.yaml") tables = misata.generate_from_schema(schema)
from misata import schema_from_db, generate_from_schema, seed_database # Introspect the live schema: no manual column definitions schema = schema_from_db("postgresql://user:pass@localhost/myapp") tables = generate_from_schema(schema) # Seed it back: insert order respects FK dependencies automatically report = seed_database(tables, "postgresql://user:pass@localhost/myapp_dev") # SeedReport: seeded 6 tables, 47,300 rows in 1.2s
# One-command workflow misata init --db postgresql://user:pass@localhost/myapp # writes misata.yaml misata generate --db-url postgresql://user:pass@localhost/myapp_dev --db-create
from misata import seed_from_sqlalchemy_models from myapp.models import Base report = seed_from_sqlalchemy_models(Base, db_url="sqlite:///test.db", row_count=500, create_tables=True)

4. From a dbt project's own schema.yml

cd my-dbt-project && misata dbt-seed

No story, no config. Misata reads the properties YAML your project already has and generates seed CSVs that satisfy it:relationshipstests become foreign keys with guaranteed integrity,accepted_valuesbecome the exact category pools,uniqueandnot_nullbecome hard constraints, anddata_typeplus column-name semantics decide the rest. Then:

dbt build # seed + run + test — the tests you already wrote, passing on day zero

Both the legacy inline test syntax and the dbt 1.9+arguments:nesting are understood. Tests Misata can't translate (dbt_utils.*, custom generics) are listed in the output rather than silently guessed at.

Reads the schema.prisma your app already maintains:@relationbecomes foreign keys with zero orphans, enums become the exact value pools,@idand@uniqueare honoured,@@id/@@uniquebecome composite uniqueness, and optional fields may be null. CSVs land inseed-data/ready for your seed script.

schema = misata.from_dict_schema({ "customers": { "id": {"type": "integer", "primary_key": True}, "email": {"type": "email"}, "plan": {"type": "string", "enum": ["free", "pro", "enterprise"]}, }, "orders": { "id": {"type": "integer", "primary_key": True}, "customer_id": {"type": "integer", "foreign_key": {"table": "customers", "column": "id"}}, "amount": {"type": "float", "min": 1.0, "max": 999.0}, "order_date": {"type": "date"}, }, }, row_count=5_000) tables = misata.generate_from_schema(schema)

Declared outcome curves: add__outcome_curves__as a top-level key alongside the table definitions. Generated rows sum to every declared target exactly, to the cent:

import pandas as pd schema = misata.from_dict_schema({ "__outcome_curves__": [{ "table": "orders", "column": "amount", "time_column": "order_date", "time_unit": "month", "value_mode": "absolute", "start_date": "2024-01-01", "avg_transaction_value": 120.0, "curve_points": [ {"month": 1, "target_value": 50_000.0}, {"month": 6, "target_value": 110_000.0}, {"month": 12, "target_value": 200_000.0}, ], }], "orders": { "__rows__": 5000, "order_id": {"type": "integer", "primary_key": True}, "amount": {"type": "float", "min": 5, "max": 500}, "order_date": {"type": "date"}, }, }, seed=42) tables = misata.generate_from_schema(schema) monthly = ( tables["orders"] .assign(m=pd.to_datetime(tables["orders"]["order_date"]).dt.month) .groupby("m")["amount"].sum() ) assert abs(monthly[1] - 50_000) < 0.01 # exact assert abs(monthly[12] - 200_000) < 0.01 # exact

Exact group shares: declare how a measure divides across a categorical column ("Electronics is 40% of revenue, Home 25%") with__group_shares__. Paired with an outcome curve on the same table and measure, the shares hold to the cent inside every declared period, and the period totals still hold; without a curve, the shares hold over the table total:

schema = misata.from_dict_schema({ "__group_shares__": [{ "table": "orders", "measure": "amount", "group_column": "category", "shares": {"Electronics": 0.4, "Home": 0.25, "Toys": 0.2, "Grocery": 0.15}, }], # ... same orders table and __outcome_curves__ as above, # plus a "category" enum column }, seed=42)

A period with fewer rows than positive-share groups is skipped with a warning rather than silently mangled; see LIMITATIONS.md.story_auditverifies the shares in the output, and evalpacks turn each period-group pair into a verified filtered-aggregation question.

Constraints and correlations: enforce business rules and inter-column relationships directly in the dict schema:

schema = misata.from_dict_schema({ "patients": { "__rows__": 1000, "__constraints__": [ # visit must be on or after enrollment: enforced at generation, not post-processing {"type": "inequality", "column_a": "visit_date", "operator": ">=", "column_b": "enroll_date", "action": "cap"}, ], "__correlations__": [ # heavier patients tend to have higher blood pressure (r = 0.41) {"col_a": "bmi", "col_b": "systolic_bp", "r": 0.41}, ], "patient_id": {"type": "integer", "primary_key": True}, "enroll_date": {"type": "date"}, "visit_date": {"type": "date"}, "bmi": {"type": "float", "min": 16, "max": 55}, "systolic_bp": {"type": "float", "min": 90, "max": 200}, }, })

__rate_curves__works the same way for per-period rate targets on boolean or categorical columns (fraud rates, churn flags, plan distributions).

7. LLM-assisted generation, richer semantics, optional

from misata import LLMSchemaGenerator gen = LLMSchemaGenerator(provider="groq", model="llama-3.3-70b-versatile") # free tier, fast & reliable # gen = LLMSchemaGenerator(provider="anthropic") # Claude # gen = LLMSchemaGenerator(provider="ollama", model="llama3") # fully local, no API key schema = gen.generate_from_story( "A fraud detection dataset, 2% positive rate, FICO scores, transaction velocity features" ) tables = misata.generate_from_schema(schema)

Requirespip install "misata[llm]"plus one ofGROQ_API_KEY,OPENAI_API_KEY,ANTHROPIC_API_KEY,GOOGLE_API_KEY.

Groq model tip:llama-3.3-70b-versatileis the reliable free-tier default. Larger models (e.g.openai/gpt-oss-120b) can return413 Request too largeon Groq's free tier, so use them only on a paid tier. Whatever the model returns, generation never crashes on an imperfect schema: missing relationships, malformed probabilities, and out-of-rangetime_units are repaired automatically.

8. Incremental generation, grow a dataset without re-seeding

tables = misata.generate("A fintech company with 1000 customers", seed=1) # Add 1 000 more rows: IDs auto-offset, FK integrity maintained across both batches tables = misata.generate_more(tables, schema, n=1000, seed=2) print(len(tables["customers"])) # 2000

Synthetic data rarely fails on the big numbers; it fails on the small tells a reviewer spots in five seconds. Misata kills each tell with a specific, deterministic mechanism. No LLM is involved; everything is seeded and reproducible.

tables = misata.generate("A hospital with 300 patients, doctors and appointments", seed=7) # patients: Tae-yang Ahn (Male) · Valentina Esposito (Female) · pooja.kapoor@icloud.com # appointments: 2023-03-08 14:00:00 · 2022-07-21 09:15:00: 15-min grid, business hours, 2% weekends

Every coherence class above is also a detector.story_auditchecks a generated dataset against the full invariant catalog: FK orphans, cross-table temporal causality, roll-up agreement, status gating, count and percent bounds, rare-flag base rates, age against birth date, and more. Nothing incoherent ships silently.

tables = misata.generate_from_schema(schema, verify=True) # warns on any finding report = misata.story_audit(tables, schema) # or audit explicitly print(report.summary()) # "Coherence: clean" or a scored list of findings

Every evalpack manifest embeds this verdict alongside its DuckDB answer certificate, so a pack asserts both that its answers are right and that the data telling the story is internally coherent.

- Within a version, generation is deterministic.The same schema, seed, and misata version produce byte-identical tables. Evalpack manifests record the version, seed, and a SHA-256 of the spec for exactly this reason.
- Across versions, RNG streams may changewhen generation improves (they did in 0.8.1.29 and 0.8.2). Declared outcomes still hold: aggregates, rates, identities, and integrity survive any upgrade; the individual rows may differ. Pin the version when you need bit-identical regeneration.
- The public API is the documented top-level surface(misata.generate,generate_from_schema,story_audit,coherence_audit,build_evalpack, the schema classes, and the builders). Underscore-prefixed modules and functions may change without notice.

Where the library is expected to fail is documented honestly, boundary by boundary, in[LIMITATIONS.md. Every entry there started as a reproduced defect or a deliberate design refusal.

No reviews yet — be the first

Sign in to leave a review

Use Google, GitHub, or an email account so ratings stay tied to real people.

Email sign in

No reviews posted yet.