Misata
Genera datos de prueba realistas para múltiples tablas con claves foráneas que se resuelven y agregados que cuadran, devueltos con una verificación de integridad. Siembra Postgres, MySQL y SQLite desde tu propio esquema, o exporta CSV/JSON/SQL/Parquet. Determinista, de modo que el mismo esquema y la misma semilla generan las mismas filas.
Documentación
Misata
Tú declaras el resultado. Misata genera los datos que demuestran que coinciden.
Filas relacionales y realistas que alcanzan curvas de ingresos exactas, tasas de fraude, integridad referencial y estructura estadística. A partir de una frase, YAML o tu base de datos. Sin datos reales, sin modelo de ML.
¿Prefieres no escribir código? Prueba Misata Studio, el generador de datos sintéticos sin código: diseña un esquema en un lienzo o describe tu conjunto de datos en inglés sencillo, y genéralo en tu navegador. Mismo motor, misma prueba de integridad.
La mayoría de las herramientas de datos sintéticos aprenden de un conjunto de datos real y lo imitan. Misata funciona al revés: tú declaras el resultado que deseas: "los ingresos mensuales suben de $50k a $200k", "el fraude es del 3% en el Q1 y sube al 8% para el Q4", "el total_spent de cada cliente es igual a la suma de sus pedidos", y Misata genera filas individuales cuyos agregados alcanzan esos objetivos exactamente, con integridad referencial completa, sin ningún dato de origen.
Esto es generación conforme al resultado. El mecanismo está formalizado en un preprint de arXiv (2606.08736): un método de forma cerrada que satisface agregados declarados con error de $0.00, donde los sintetizadores de imitación estándar entrenados con los mismos datos fallan entre un 74% y un 86%. Cada ejecución también puede emitir un informe Oracle, un paquete de prueba que cubre integridad referencial, restricciones, consistencia temporal y reproducibilidad.
Genera a partir de una descripción en inglés sencillo, un esquema YAML o un esquema de base de datos existente. No se requiere ningún modelo de aprendizaje automático. No se necesitan datos reales.
Diseñado para:
- Pruebas de respuesta conocida: declara el KPI, genera los datos y luego verifica que tu transformación en dbt, Spark o SQL devuelva exactamente ese número. Una prueba de pipeline con una verdad de referencia, antes de que existan datos reales.
- Relleno de bases de datos: llena entornos de desarrollo y preparación con datos similares a los de producción.
- Pruebas de integración: fixtures relacionales con integridad de claves foráneas en cada tabla.
- Demostraciones y prototipos: números, nombres y distribuciones realistas, sin PII.
- Desarrollo de BI y paneles: datos con la forma de tu dominio real antes del lanzamiento.
- Validación de métodos estadísticos: conjuntos de datos longitudinales, agrupados y de múltiples sitios que superan modelos de efectos mixtos, pruebas ICC y comprobaciones de autocorrelación.
Declarar o imitar: dos formas de entrar
Misata funciona en dos modos, y la diferencia es el punto clave:
- Declarar (el predeterminado, sin datos requeridos). Tú indicas el esquema y los resultados que deseas: curvas de ingresos exactas, tasas de fraude, resúmenes, restricciones, y Misata genera filas desde cero que se ajustan a ellos. Úsalo cuando no tengas datos reales o cuando necesites una respuesta conocida para probar un pipeline, panel o demostración.
- Imitar (cuando ya tienes datos). Apunta
misata.mimic()a un CSV real y obtén un gemelo sintético que coincida con sus distribuciones y correlaciones, pero que no contenga ninguna de las filas originales, confidelity_reportyprivacy_reportpara medir el resultado. Úsalo para copias seguras de privacidad de los datos que ya posees.
La mayoría de las herramientas de datos sintéticos solo hacen lo segundo: aprenden de un conjunto de datos real y lo imitan. Misata lidera con lo primero: tú declaras la respuesta y luego generas los datos a su alrededor.
Investigación
El motor de agregados exactos de Misata está respaldado por un preprint de arXiv:
Síntesis Declarativa Conforme al Resultado: Satisfacción Exacta de Especificaciones de Forma Cerrada y un Punto de Referencia de Conformidad
Muhammed Rasin, arXiv:2606.08736 (2026)
https://arxiv.org/abs/2606.08736v1
El artículo formaliza la afirmación central: cuando declaras "SaaS MRR from $50k in January to $200k in December", Misata genera transacciones individuales cuyos totales mensuales coinciden con la curva declarada con un error exacto de $0.00, no de forma aproximada, sino demostrable, mediante un mecanismo de suma condicional Gamma de forma cerrada (caracterización de Lukacs). Los sintetizadores de imitación estándar entrenados con los mismos datos fallan en el agregado mensual declarado entre un 74% y un 86%; Misata alcanza exactamente 0.
El artículo también presenta SpecBench: el primer punto de referencia que mide la conformidad con resultados analíticos para la síntesis relacional de arranque en frío. Misata es la implementación de referencia.
@article{rasin2026declarative,
title = {Declarative Outcome-Conformant Synthesis: Exact, Closed-Form
Specification Satisfaction and a Conformance Benchmark},
author = {Rasin, Muhammed},
year = {2026},
url = {https://arxiv.org/abs/2606.08736v1}
}
Instalación
pip install misata
Extras opcionales:
pip install "misata[llm]" # multi-provider LLM schema generation
pip install "misata[documents]" # PDF output via weasyprint
pip install "misata[advanced]" # SDV/CTGAN statistical synthesis
pip install "misata[mcp]" # MCP server, expose Misata to Claude, Cursor, and other AI agents
pip install "misata[evalpack]" # evalpacks: verified eval databases for data agents (DuckDB)
Úsalo desde un agente de codificación
Misata incluye una habilidad de agente, por lo que Claude Code y cualquier otra cosa que lea
SKILL.md sabe qué punto de entrada se adapta a cada solicitud y qué vale la pena
declarar:
/plugin marketplace add rasinmuhammed/misata
/plugin install misata@misata
La habilidad impulsa la CLI, por lo que pip install misata sigue siendo necesario. También
hay un servidor MCP (pip install "misata[mcp]") y una extensión de Claude Desktop
en mcpb/.
Usa Misata desde Claude / Cursor / Windsurf (MCP)
Misata incluye un servidor Model Context Protocol integrado con una división clara del trabajo: el agente de IA diseña el esquema, Misata garantiza las matemáticas. Los agentes son buenos sabiendo que una clínica veterinaria necesita una columna species; Misata es bueno creando 50 000 filas donde cada clave foránea se resuelve, cada resumen cuadra al céntimo y la misma semilla reproduce una salida idéntica byte a byte. La herramienta principal, generate_from_schema, acepta el dict de esquema del agente y devuelve los datos más una prueba de integridad: recuentos de huérfanos por relación que el agente puede mostrarte.
1. Instala:
pip install "misata[mcp]"
2. Añádelo a Claude Desktop (~/Library/Application Support/Claude/claude_desktop_config.json):
{
"mcpServers": {
"misata": {
"command": "misata-mcp"
}
}
}
Reinicia Claude Desktop. Luego solo pregunta:
"Genera un conjunto de datos fintech con 1 000 clientes, pagos y una tasa de fraude del 2%."
"Diseña una base de datos de ensayos clínicos (sitios, pacientes, visitas, eventos adversos) y genera 100k filas."
"Necesito datos SaaS: MRR de $50k en enero, duplicado para diciembre, con una caída en el Q3."
El agente diseña las tablas que la solicitud necesite (cualquier dominio; no está limitado a los integrados de Misata), llama a Misata, escribe los CSV en disco e informa con vistas previas y el resumen de integridad verificado. Consulta la guía MCP para la configuración de Cursor/Windsurf/Zed y las seis herramientas disponibles.
mcp-name: io.github.rasinmuhammed/misata
Inicio rápido
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
Misata Oracle
El informe Oracle es la capa de prueba de Misata. Separa las garantías sólidas de las comprobaciones de realismo consultivas para que los datos generados se puedan confiar en CI, demostraciones, notebooks y comparaciones de investigación.
Comprobaciones garantizadas:
- integridad referencial en las relaciones configuradas
- cumplimiento del recuento de filas solicitado
- validación de esquema y restricciones configuradas
- reproducibilidad determinista cuando se establece una semilla
Comprobaciones consultivas:
- puntuación de calidad y advertencias de plausibilidad
- heurísticas de privacidad
- puntuación de fidelidad esquema-vs-salida
- ajuste de localización/dominio para países, ciudades, prefijos telefónicos e identificaciones nacionales
- metadatos de tarjeta de datos
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"])
Modo imitar: clona cualquier CSV en una sola llamada
Apunta misata.mimic() a un conjunto de datos real y obtén un gemelo sintético que coincida con las distribuciones de cada columna pero que no contenga ninguna de las filas originales. Sin redactar esquemas, sin configuración.
import pandas as pd
import misata
real = pd.read_csv("titanic.csv")
twin = misata.mimic(real, rows=2000, seed=42, table_name="passengers")["passengers"]
El perfilador maneja las columnas que rompen otras herramientas:
- Columnas de códigos alfanuméricos (Ticket
"A/5 21171", Cabin"C85", SKUs, números de referencia) se detectan por su forma de clase de caracteres y se reproducen estructuralmente, mismas formas en las proporciones correctas, valores completamente nuevos, cero fuga textual de la fuente. Ya no caen en la generación de texto en prosa. - Los flotantes conservan sus céntimos. Un Fare de
7.25genera valores con forma de7.25. El perfilador infiere los decimales de los datos; la cuantización semántica (precios de encanto) nunca se activa en columnas imitadas. - Las distribuciones se ajustan a partir de los datos. Las columnas con sesgo positivo obtienen lognormal; las columnas constantes obtienen un stub uniforme; todo lo demás obtiene normal. Las columnas categóricas con menos de 50 valores llevan sus frecuencias reales.
# 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
Ocho formas de generar datos
1. Inglés sencillo, sin configuración requerida
tables = misata.generate("A fintech startup with 10k customers, fraud rate 3%, and IBAN accounts")
Misata lee la historia, infiere el dominio (fintech), la escala (10 000 filas) y la semántica de las columnas (marca de fraude, formato IBAN), sin necesidad de redactar esquemas.
Una frase es leída por un reconocedor que maneja un conjunto fijo de formulaciones. Cualquier cosa que no pueda convertir en una declaración se nombra en una advertencia en lugar de descartarse, para que siempre sepas qué tuvo efecto. Cuando un prompt necesita ser exacto, escríbelo como una especificación en su lugar.
1b. Una especificación estructurada, analizada de forma determinista
La prosa que declara tablas, recuentos de filas, columnas y reglas no es una historia, y adivinarla es la herramienta equivocada. Misata detecta esa forma y la analiza directamente, por lo que "exactamente 4000" significa exactamente 4000. Sin modelo, sin inferencia, sin reescritura.
Table 1: accounts
Rows: exactly 600
Columns:
account_id
company_name
plan
seats
signed_up_on
plan must only be:
Starter
Professional
Enterprise
seats must be 1 to 120
signed_up_on must be 2023-01-01 to 2023-12-31
Table 2: invoices
Rows: exactly 3200
Columns:
invoice_id
account_id
amount
issued_on
account_id must match values from accounts table
amount must be 120 to 8500
issued_on must be 2024-01-01 to 2024-12-31
Revenue curve on invoices.amount by issued_on:
Jan 180000
Feb 195000
Mar 210000
tables = misata.generate_from_schema(misata.parse(open("spec.txt").read()))
Lo que garantiza la especificación:
| Escribes | Obtienes |
|---|---|
Rows: exactly 3200 | 3200 filas, no aproximadamente 3200 |
x must match values from y table | una clave foránea con cero huérfanos |
plan must only be: + una lista | esos valores y ningún otro |
seats must be 1 to 120 | cada fila dentro del límite |
signed_up_on must be 2023-01-01 to 2023-12-31 | fechas dentro de esa ventana |
Revenue curve on t.col by t.date: | cada mes aterriza en su cifra al céntimo |
Las reglas pueden estar dentro del bloque de una tabla o en una sección al final; de cualquier manera se adjuntan por nombre de columna. Una columna que termina en _on, _at, _date o _for se genera como fecha. Cualquier cosa que el analizador no pueda traducir se te lista de vuelta, nunca se adivina.
2. Esquema YAML como código, hazle commit a 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)
3. Rellena una base de datos existente directamente
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
Los modelos SQLAlchemy también son compatibles:
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. Desde el propio schema.yml de un proyecto dbt
cd my-dbt-project && misata dbt-seed
Sin historia, sin configuración. Misata lee el YAML de propiedades que tu proyecto ya
tiene y genera CSV de semilla que lo satisfacen: las pruebas relationships se convierten en
claves foráneas con integridad garantizada, accepted_values se convierten en los grupos
de categorías exactos, unique y not_null se convierten en restricciones duras, y
data_type más la semántica de nombres de columna deciden el resto. Luego:
dbt build # seed + run + test — the tests you already wrote, passing on day zero
Tanto la sintaxis de prueba en línea heredada como el anidamiento arguments: de dbt 1.9+ se
entienden. Las pruebas que Misata no puede traducir (dbt_utils.*, genéricos personalizados) se
listan en la salida en lugar de adivinarse silenciosamente.
5. Desde un esquema Prisma
cd my-app && misata prisma-seed
Lee el schema.prisma que tu aplicación ya mantiene: @relation se convierte en claves
foráneas con cero huérfanos, los enums se convierten en los grupos de valores exactos, @id y @unique
se respetan, @@id/@@unique se convierten en unicidad compuesta, y los campos opcionales
pueden ser nulos. Los CSV aterrizan en seed-data/ listos para tu script de semilla.
6. Esquema de dict de Python
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)
Curvas de resultado declaradas: añade __outcome_curves__ como clave de nivel superior junto a las definiciones de tabla. Las filas generadas suman exactamente cada objetivo declarado, al céntimo:
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
Participaciones de grupo exactas: declara cómo se divide una medida en una columna categórica ("Electrónica es el 40% de los ingresos, Hogar el 25%") con __group_shares__. Combinado con una curva de resultado en la misma tabla y medida, las participaciones se mantienen al céntimo dentro de cada período declarado, y los totales del período siguen manteniéndose; sin una curva, las participaciones se mantienen sobre el total de la tabla:
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)
Un período con menos filas que grupos de participación positiva se omite con una advertencia en lugar de alterarse silenciosamente; consulta LIMITATIONS.md. story_audit verifica las participaciones en la salida, y los evalpacks convierten cada par período-grupo en una pregunta verificada de agregación filtrada.
Restricciones y correlaciones: aplica reglas de negocio y relaciones entre columnas directamente en el esquema del diccionario:
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__ funciona de la misma manera para objetivos de tasa por período en columnas booleanas o categóricas (tasas de fraude, indicadores de abandono, distribuciones de planes).
7. Generación asistida por LLM, semántica más rica, opcional
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)
Requiere pip install "misata[llm]" más uno de GROQ_API_KEY, OPENAI_API_KEY, ANTHROPIC_API_KEY, GOOGLE_API_KEY.
Consejo sobre modelos Groq:
llama-3.3-70b-versatilees el valor predeterminado confiable del nivel gratuito. Los modelos más grandes (p. ej.,openai/gpt-oss-120b) pueden devolver413 Request too largeen el nivel gratuito de Groq, así que úsalos solo en un nivel de pago. Sea cual sea el modelo que devuelva, la generación nunca falla con un esquema imperfecto: las relaciones faltantes, las probabilidades mal formadas y lostime_unitfuera de rango se reparan automáticamente.
8. Generación incremental, amplía un conjunto de datos sin volver a sembrar
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
Realismo que sobrevive a la inspección
Los datos sintéticos rara vez fallan en los números grandes; fallan en los pequeños detalles que un revisor detecta en cinco segundos. Misata elimina cada detalle con un mecanismo específico y determinista. No interviene ningún LLM; todo está sembrado y es reproducible.
| El detalle | El mecanismo |
|---|---|
Pablo Müller, Female: nombres, géneros y culturas extraídos de forma independiente | Muestreo de identidad conjunta: (culture, gender, first, last) es una extracción de grupos clave por cultura, con una mezcla intercultural medida del 6% (las poblaciones reales no son endogámicas). Los correos electrónicos se derivan del nombre final. |
appointment_date: 2022-08-29 06:36:12.995319155: precisión de nanosegundos, 6 a. m., un domingo | Perfiles temporales: los eventos programados se ajustan a cuadrículas de 15 minutos en horario comercial con atenuación de fines de semana; los registros siguen ritmos de horas de vigilia; solo los eventos de máquina (registros, clics) mantienen precisión de subsegundos; las fechas de nacimiento son fechas. |
| Cada categoría igualmente probable | Marginales Zipf–Mandelbrot: las categóricas sin ponderar siguen la ley de potencia de rango-frecuencia que siguen los estados, países y categorías reales, con el valor dominante variando por columna. Las probabilidades declaradas siempre ganan. |
Chicago → San Diego, 145.6 km | Hechos geográficos: las distancias entre ciudades nombradas se calculan (haversine × factor de sinuosidad vial) a partir de 289 coordenadas de ciudades integradas, y los tiempos de viaje se derivan de las distancias. Hechos, no distribuciones: así el Oráculo puede verificarlos. |
| Una reseña de cinco estrellas que dice "decepcionante", o lorem ipsum | Microtexto gramatical: el texto de la reseña se genera a partir de la calificación de la fila mediante una gramática sembrada (1★ suena enojado, 5★ suena encantado), un invariante verificable. Las notas de texto libre provienen de una gramática de notas de negocio. Lorem ipsum no puede llegar a la salida. |
| Una cita de 19 minutos, un precio de $43.27 | Cuantificación numérica: las duraciones programadas se ajustan a las cuadrículas de franjas que los calendarios realmente ofrecen (15/30/45/60), los precios minoristas terminan en .99/.95/.00, las edades son enteros. Las cantidades medidas se dejan intactas. |
| Un pedido enviado antes de realizarse, por un cliente que aún no se había registrado | Orden de ciclo de vida y causalidad: las marcas de tiempo de una fila se ordenan a lo largo del ciclo de vida real de comercio electrónico/SaaS/logística, y una fila hija se desplaza para que nunca preceda a su padre de clave foránea, a través de cadenas de varios niveles, preservando las brechas propias de la fila. |
state: cancelled junto a city: Los Angeles, una fila de Tokio con un código postal de EE. UU., teléfonos +1 en todas partes | Coherencia de la cadena de direcciones: ciudad, estado, formato postal y código de llamada telefónica coinciden con el país de la fila (y una ciudad conocida lleva su estado exacto), en 14 países y 8 formatos postales. |
is_fraud verdadero en la mitad de las filas, salarios en una campana simétrica, cada cantidad 1-5 uniforme | Base de conocimientos de prioridades estadísticas: los nombres de columnas reconocidos dibujan su forma del mundo real automáticamente: calificaciones en forma de J, cantidades de pedido Zipf (60% de unos), salarios lognormales, terminaciones de precio .99, indicadores de eventos raros ~3%. Las declaraciones explícitas siempre ganan. |
Un order_total que no es igual a la suma de sus partidas | Coherencia de valores entre tablas: el unit_price de una partida se copia del producto al que hace referencia, y una columna de total de entidad se acumula desde su partida hija, sin contar dos veces una tabla hermana. |
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
El conjunto de datos se califica a sí mismo
Cada clase de coherencia anterior también es un detector. story_audit verifica un conjunto de datos generado contra el catálogo completo de invariantes: huérfanos de clave foránea, causalidad temporal entre tablas, acuerdo de acumulación, control de estados, límites de conteo y porcentaje, tasas base de indicadores raros, edad contra fecha de nacimiento y más. Nada incoherente se envía en silencio.
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
Cada manifiesto de evalpack incorpora este veredicto junto con su certificado de respuesta DuckDB, de modo que un paquete afirma tanto que sus respuestas son correctas como que los datos que cuentan la historia son internamente coherentes.
Reproducibilidad y estabilidad
- Dentro de una versión, la generación es determinista. El mismo esquema, semilla y versión de misata producen tablas idénticas byte a byte. Los manifiestos de evalpack registran la versión, la semilla y un SHA-256 de la especificación exactamente por esta razón.
- Entre versiones, los flujos RNG pueden cambiar cuando la generación mejora (cambiaron en 0.8.1.29 y 0.8.2). Los resultados declarados siguen siendo válidos: agregados, tasas, identidades e integridad sobreviven a cualquier actualización; las filas individuales pueden diferir. Fija la versión cuando necesites regeneración idéntica bit a bit.
- La API pública es la superficie de nivel superior documentada (
misata.generate,generate_from_schema,story_audit,coherence_audit,build_evalpack, las clases de esquema y los constructores). Los módulos y funciones con prefijo de guion bajo pueden cambiar sin previo aviso.
Donde se espera que la biblioteca falle se documenta honestamente, límite por límite, en LIMITATIONS.md. Cada entrada allí comenzó como un defecto reproducido o una negativa de diseño deliberada.
Dominios desconocidos: compuestos, no confabulados
Los 18 dominios integrados son plantillas. Para todo lo demás, Misata se niega a fingir comprensión y se niega a rendirse. Un sintetizador composicional deriva la estructura de tu oración: frases nominales en plural se convierten en tablas, "80 apicultores" vincula un recuento de filas, y un pequeño entramado de arquetipos (persona / activo / lugar / evento / documento) proporciona columnas estructurales honestas y cableado de claves foráneas.
tables = misata.generate(
"A beekeeping cooperative with 12 apiaries, 80 beekeepers, hives, inspections and honey harvests"
)
# beekeepers: beekeeper_id, first_name, last_name, email, joined_at, status
# inspections: inspection_id, beekeeper_id, apiary_id, hive_id, inspection_date, status
# → full FK integrity, profiled timestamps, Zipfian statuses: from one sentence, no LLM
Lo que no hará es inventar semántica de dominio: las entidades desconocidas obtienen columnas estructurales (códigos de referencia, estados, fechas) y el informe de detección dice exactamente eso, señalando las dos rutas de actualización: un diccionario de esquema o un LLM. La misma puerta también previene la confabulación: una historia que solo coincide débilmente con una plantilla integrada (una palabra clave incidental) se compone a partir de sus propias entidades en lugar de forzarse en la plantilla incorrecta.
Cápsulas: enseña a Misata un dominio una vez
Una cápsula es un archivo JSON compartible de vocabularios de dominio (las especies, tratamientos y nombres de modelos que un dominio llama a las cosas) con procedencia para cada lista. La inteligencia se gasta una vez, en la creación; la generación permanece determinista, sin conexión y gratuita.
# Mine a capsule from example data you already have: no LLM, no key
misata capsule create --domain veterinary --from-csv ./samples/ -o vet.capsule.json
misata capsule show vet.capsule.json
# Vocabularies override built-in pools for matching columns
tables = misata.generate("a veterinary clinic with patients and visits",
capsule="vet.capsule.json")
Las cápsulas también pueden ser escritas por un LLM una vez y revisadas antes de su uso (capsule_from_llm, clave BYO; el nivel gratuito de Groq funciona), o escritas a mano: es JSON. Como una cápsula es un archivo, es un artefacto comunitario. Compártela mediante git, un gist o conjuntos de datos de HF.
Localización
Misata detecta automáticamente el contexto de país de tu historia y genera datos estadísticamente precisos para esa localidad: los nombres correctos, distribuciones salariales, formatos de identificación nacional, monedas, códigos postales y convenciones de nombres de empresas.
# Locale is detected automatically: no extra flag needed
tables = misata.generate("German SaaS company in Berlin with 2k enterprise customers")
# → names from de_DE Faker pool, salary ~ lognormal(μ=10.71, σ=0.5) ≈ €45k median,
# postcodes are 5-digit, company names end in GmbH/AG/UG
tables = misata.generate("Brazilian fintech with R$ payments and CPF verification, 50k users")
# → pt_BR names, salary median ~BRL 33.6k, national IDs match CPF format ###.###.###-##
tables = misata.generate("Indian startup in Bangalore with ₹ salary bands and Aadhaar KYC")
# → hi_IN names, salary median ~₹350k/yr, national IDs match Aadhaar 12-digit format
Fuerza o anula una localidad explícitamente:
schema = misata.parse("An ecommerce store with 10k orders")
tables = misata.generate_from_schema(schema) # defaults to en_US
# CLI
misata generate --story "Ecommerce store" --locale ja_JP
15 localidades integradas
| Localidad | País | Moneda | Salario mediano | Identificación nacional |
|---|---|---|---|---|
en_US | Estados Unidos | USD / $ | $62 000 | SSN ###-##-#### |
en_GB | Reino Unido | GBP / £ | £34 000 | NIN AA######A |
de_DE | Alemania | EUR / € | €45 000 | Steuer-IdNr |
fr_FR | Francia | EUR / € | €38 000 | NIR |
pt_BR | Brasil | BRL / R$ | R$33 600 | CPF ###.###.###-## |
es_ES | España | EUR / € | €27 000 | NIE |
hi_IN | India | INR / ₹ | ₹350 000 | Aadhaar ####-####-#### |
ja_JP | Japón | JPY / ¥ | ¥4 400 000 | My Number |
zh_CN | China | CNY / ¥ | ¥90 000 | Resident ID |
ar_SA | Arabia Saudita | SAR | SAR 96 000 | National ID |
ko_KR | Corea del Sur | KRW / ₩ | ₩42 000 000 | RRN |
nl_NL | Países Bajos | EUR / € | €42 000 | BSN |
it_IT | Italia | EUR / € | €29 000 | Codice Fiscale |
pl_PL | Polonia | PLN | PLN 72 000 | PESEL |
tr_TR | Turquía | TRY | TRY 720 000 | TC Kimlik |
Cada paquete lleva distribuciones salariales reales (medianas y prioridades lognormales), distribuciones de edad, ciudades mejor clasificadas, prefijos de números de teléfono, patrones de códigos postales, sufijos de empresas y tasas de IVA, con fuentes de la OCDE, el Banco Mundial, la OIT y oficinas nacionales de estadística (datos 2023–24).
# Inspect a locale pack directly
pack = misata.get_locale_pack("de_DE")
print(pack.salary_median) # 45000
print(pack.currency_symbol) # €
print(pack.top_cities[:3]) # ['Berlin', 'Hamburg', 'Munich']
print(pack.company_suffixes) # ['GmbH', 'AG', 'UG', 'KG', 'e.K.']
# Auto-detect from a story
locale = misata.detect_locale("South Korean company in Seoul with KRW salaries")
# → "ko_KR"
Restricciones
Aplica reglas de negocio que sobreviven a cada fila de generación:
from misata.constraints import (
InequalityConstraint, # price > cost on every row
ColumnRangeConstraint, # min_price <= price <= max_price
RatioConstraint, # 70% free / 30% pro
UniqueConstraint, # no duplicate (user_id, date) pairs
SumConstraint, # total_hours per employee per day <= 8
NotNullConstraint, # no nulls in required columns
)
c = InequalityConstraint("price", ">", "cost")
df = c.apply(df)
Las restricciones también se pueden declarar en misata.yaml; se ejecutan en el momento de la generación, no como un paso de posprocesamiento.
Acumulaciones entre tablas
Haz que las columnas de resumen de padres se reconcilien con las filas hijas, para que los datos sobrevivan a un GROUP BY ... JOIN. Una columna customers.total_spent generada independientemente de los pedidos reales de ese cliente es una señal de que los datos son falsos; una acumulación la calcula a partir de las filas hijas reales.
schema = misata.from_dict_schema({
"name": "shop",
"tables": {
"customers": {
"rows": 500,
"columns": {
"customer_id": {"type": "int", "unique": True},
# total_spent = sum(orders.amount) per customer
"total_spent": {"type": "float", "rollup": {
"from_table": "orders", "fk": "customer_id",
"agg": "sum", "column": "amount"}},
# completed_spend = sum(amount) where status == "completed"
"completed_spend": {"type": "float", "rollup": {
"from_table": "orders", "fk": "customer_id", "agg": "sum",
"column": "amount", "where": {"status": "completed"}}},
},
},
"orders": {
"rows": 3000,
"columns": {
"order_id": {"type": "int", "unique": True},
"customer_id": {"type": "foreign_key", "references": "customers.customer_id"},
"amount": {"type": "float", "distribution": "lognormal", "mu": 4, "sigma": 0.5, "min": 1},
"status": {"type": "categorical", "choices": ["completed", "cancelled", "pending"]},
},
},
},
})
tables = misata.generate_from_schema(schema)
# tables["customers"]["total_spent"] reconciles exactly with the orders table.
Agregaciones: sum, count, mean, max, min. Cuando un nombre de columna de padre nombra explícitamente una tabla hija (num_orders, total_orders), la acumulación se infiere automáticamente sin declaración. Las acumulaciones sobreviven al viaje de ida y vuelta de misata.yaml y se ejecutan en el momento de la generación.
Realismo estadístico: datos que pasan la validación de métodos
La mayoría de las herramientas de datos sintéticos generan filas de forma independiente. Eso funciona para la siembra de bases de datos y pruebas de canalizaciones. Se rompe en el momento en que los datos necesitan pasar un método estadístico: una prueba de autocorrelación en mediciones repetidas, un modelo de efectos mixtos que verifica si los grupos difieren, o una auditoría que detecta valores fuera de límites plausibles.
Misata 0.8.1.0 agrega un conjunto de funciones que cierran esta brecha. Todas se declaran en el mismo esquema de diccionario simple y son accesibles desde agentes MCP, Studio y llamadas directas de Python.
Perfiles de distribución estratificados: diferentes distribuciones por subgrupo
Un conjunto de datos de prueba A/B realista no extrae a todos los usuarios de una distribución de conversión. El grupo de control se ve diferente del grupo de tratamiento. Usa profiles para declarar esto con precisión en cualquier columna:
schema = misata.from_dict_schema({
"users": {
"__rows__": 5000,
"user_id": {"type": "integer", "primary_key": True},
"cohort": {
"type": "string",
"enum": ["control", "variant_a", "variant_b"],
"probabilities": [0.50, 0.25, 0.25],
},
"session_duration": {
"type": "float",
"distribution": "lognormal",
"mean": 180.0, "std": 90.0, # fallback for unmatched rows
"profiles": [
{"when": "cohort == 'control'", "distribution": "lognormal", "mean": 180.0, "std": 90.0},
{"when": "cohort == 'variant_a'", "distribution": "lognormal", "mean": 240.0, "std": 100.0},
{"when": "cohort == 'variant_b'", "distribution": "lognormal", "mean": 310.0, "std": 120.0},
],
},
}
})
La expresión when se evalúa como una consulta de pandas contra columnas ya generadas en el mismo lote. Las filas que no coinciden con ningún perfil obtienen la distribución de nivel superior de la columna. Los perfiles pueden hacer referencia a cualquier columna generada antes de la actual en orden de declaración.
Falta informativa: MAR y MNAR
Los conjuntos de datos del mundo real tienen valores faltantes no aleatorios. Misata modela ambos mecanismos:
Falta al azar (MAR): La probabilidad de que un valor falte depende de una columna observada. Es más probable que los usuarios de alto gasto omitan el campo de ingresos opcional.
"annual_income": {
"type": "float",
"nullable": True,
"missing_if": {
"predictor": "total_spend",
"relationship": "higher_increases_probability",
"base_rate": 0.05,
"max_rate": 0.40,
"mechanism": "MAR",
},
}
Falta no al azar (MNAR): La probabilidad de que un valor falte depende del valor en sí. Las puntuaciones de satisfacción muy bajas son las más propensas a no informarse.
"satisfaction_score": {
"type": "float",
"distribution": "normal", "mean": 7.5, "std": 1.8,
"nullable": True,
"missing_if": {
"predictor": "satisfaction_score", # references its own column
"mechanism": "MNAR",
"relationship": "lower_increases_probability",
"base_rate": 0.02,
"max_rate": 0.50,
},
}
Nulos condicionales (null_when): Anula una columna siempre que una expresión booleana sea verdadera.
"cancellation_reason": {
"type": "string",
"enum": ["price", "competitor", "unused", "other"],
"nullable": True,
"null_when": "churned == False",
}
Control de incidencia exacto: tasas precisas, no aproximaciones estadísticas
Una columna boolean con probability: 0.03 da aproximadamente un 3% de valores True en muchas ejecuciones. Si necesitas que el conjunto de datos contenga exactamente un 3% (auditable contra su propia especificación), usa exact_incidence:
"is_fraud": {
"type": "boolean",
"exact_incidence": {
"mode": "exact",
"rate": 0.03, # exactly floor(n * 0.03) rows are True
},
}
Las tarifas exactas por segmento funcionan de la misma manera:
"converted": {
"type": "boolean",
"exact_incidence": {
"mode": "exact",
"group_by": "cohort",
"rates": {"control": 0.12, "variant_a": 0.18, "variant_b": 0.24},
},
}
La diferencia entre «aproximadamente un 3 % de fraude» y «exactamente un 3 % de fraude» es la diferencia entre un conjunto de datos que supera una auditoría y uno que no.
Autocorrelación de series temporales dentro de la entidad: datos longitudinales que superan pruebas estadísticas
Sin autocorrelación, un conjunto de datos longitudinal (sesiones de usuario, lecturas de IoT, series temporales financieras) es estadísticamente idéntico a uno transversal. Cada prueba de series temporales (Ljung-Box, Durbin-Watson, gráfico de autocorrelación) detectará inmediatamente que las filas son independientes y que los datos son sintéticos.
La especificación time_series reescribe una columna para que tenga autocorrelación real dentro de la entidad:
"daily_revenue": {
"type": "float",
"distribution": "lognormal", "mean": 8500.0, "std": 3000.0,
"time_series": {
"entity_id": "store_id", # one process per store
"order_by": "day_number",
"model": "AR1", # AR1 | LINEAR_TREND | RANDOM_WALK | MEAN_REVERSION
"phi": 0.72, # autocorrelation coefficient (0 = independent, 1 = random walk)
"noise_std": 800.0,
"trend": {
"slope_mean": 45.0, # average daily growth per store
"slope_std": 12.0, # per-store growth variability
},
},
}
Hay cuatro modelos disponibles:
| Modelo | Caso de uso |
|---|---|
AR1 | Mediciones que persisten entre períodos: ingresos, usuarios activos, inventario |
LINEAR_TREND | KPIs con una dirección declarada: crecimiento, decaimiento, pérdida de peso, mejora de habilidades |
RANDOM_WALK | Precios de activos, tipos de cambio, cualquier proceso browniano sin media |
MEAN_REVERSION | Métricas acotadas que vuelven hacia el promedio: NPS, tasa de llenado de inventario |
Distribuciones ancladas por entidad: separando la variación dentro de la entidad y entre entidades
Cuando la columna de una tabla hija debe estar anclada al valor de su entidad padre, usa una fórmula en distribution.mean:
"stores": {
"__rows__": 50,
"store_id": {"type": "integer", "primary_key": True},
"baseline_daily_revenue": {"type": "float", "distribution": "lognormal", "mean": 8500.0, "std": 3000.0},
},
"daily_sales": {
"__rows__": 18250, # 50 stores × 365 days
"record_id": {"type": "integer", "primary_key": True},
"store_id": {"type": "integer", "foreign_key": {"table": "stores", "column": "store_id"}},
"revenue": {
"type": "float",
"distribution": "normal",
"mean": {"formula": "@stores.baseline_daily_revenue"}, # anchored to each store's baseline
"std": 800.0, # day-to-day noise
},
}
El motor resuelve la FK para cada fila y extrae de la distribución personalizada de esa entidad. La variación entre tiendas proviene de la dispersión de baseline_daily_revenue; el ruido diario dentro de la tienda es std: 800. Generar todas las filas a partir de una única distribución compartida (como hace todo generador independiente de columnas) colapsa la varianza entre entidades y dentro de la entidad en un solo número y falla en toda prueba de efectos aleatorios.
Efectos de clúster ICC jerárquicos: estructura de grupo que sobrevive a pruebas estadísticas
Cuando las filas se agrupan bajo entidades padre (tiendas, regiones, sucursales), las observaciones dentro del mismo grupo tienden a parecerse más que las observaciones entre grupos. Esta homogeneidad dentro del grupo (el coeficiente de correlación intraclase (ICC)) es una característica definitoria de los datos agrupados. Sin ella, todos los grupos parecen estadísticamente idénticos.
__cluster_effect__ se declara en la tabla padre y aplica interceptos aleatorios por entidad a las columnas de la tabla hija:
"regions": {
"__rows__": 8,
"__cluster_effect__": {
"affects_table": "stores",
"affects_columns": {
"avg_order_value": {
"icc": 0.22, # target intraclass correlation
"sd_total": 45.0, # sd_between = sqrt(0.22) * 45 ≈ 21
},
"conversion_rate": {
"sd_between": 0.04, # supply sd_between directly
},
},
},
"region_id": {"type": "integer", "primary_key": True},
"name": {"type": "string", "enum": ["North", "South", "East", "West", "Central", "NW", "NE", "SE"]},
}
Se extrae un intercepto aleatorio por entidad padre de N(0, sd_between) y se añade a cada fila hija en ese grupo. La distribución marginal en todas las filas se conserva. Valores típicos de ICC: 0.05–0.20 para métricas minoristas a nivel de tienda, 0.10–0.30 para resultados educativos entre escuelas, 0.15–0.40 para métricas bancarias a nivel de sucursal.
Matriz de correlación completa: declara la estructura de covarianza completa de una vez
Para tablas con muchas columnas correlacionadas, la sintaxis de matriz es más limpia que una lista de pares:
"__correlations__": {
"matrix": {
"columns": ["session_duration", "pages_viewed", "revenue", "satisfaction"],
"values": {
"session_duration": [1.00, 0.71, 0.55, 0.32],
"pages_viewed": [0.71, 1.00, 0.48, 0.28],
"revenue": [0.55, 0.48, 1.00, 0.41],
"satisfaction": [0.32, 0.28, 0.41, 1.00],
}
}
}
La matriz se expande en pares y se aplica mediante el reordenamiento de rangos de Iman-Conover, que alcanza los valores de r de Pearson declarados mientras conserva exactamente la distribución marginal de cada columna. La sintaxis de lista de pares sigue funcionando sin cambios.
Estados terminales de máquina de estados: columnas categóricas correctas para procesos
Cualquier columna que represente la posición de una entidad en un proceso (etapa del ciclo de vida del cliente, estado de cumplimiento de pedidos, estado de suscripción) debe seguir una cadena de Markov, no una probabilidad plana. __state_machine__ genera la distribución terminal correcta:
"orders": {
"__state_machine__": {
"state_column": "status",
"initial_state": "placed",
"transitions": {
"placed": {"confirmed": 0.95, "cancelled": 0.05},
"confirmed": {"shipped": 0.92, "cancelled": 0.08},
"shipped": {"delivered": 0.97, "returned": 0.03},
},
},
...
}
Los estados sin transiciones salientes son terminales. El motor recorre la cadena por fila hasta alcanzar un estado terminal. Las probabilidades de transición declaradas se conservan en expectativa. Funciona junto con incidencia exacta, perfiles, correlaciones y series temporales en la misma tabla.
Validación de datos: detecta valores fuera de rango antes de que lleguen a tu pipeline
Después de la generación, valida contra los límites de dominio declarados antes de que los datos lleguen a un modelo o un panel:
tables = misata.generate_from_schema(schema)
report = misata.validate_domain(tables, domain="financial")
print(report.summary())
# Domain validation (financial): 0 errors, 0 warnings.
assert report.passed
Rangos integrados para financial / fintech: precio ≥ 0, descuento 0–1, tasa –1 a 100, salario ≥ 0. La coincidencia de columnas es por subcadena en el nombre de columna en minúsculas, "unit_price" coincide con la regla price.
Añade rangos personalizados mediante el dict custom_ranges para cualquier tipo de columna. Declara "__domain__": "financial" en el esquema del dict para adjuntar el dominio al SchemaConfig para herramientas posteriores.
Exportación
# Columnar / analytical
misata.to_parquet(tables, "data/")
misata.to_arrow(tables, "data/") # Apache Arrow IPC; requires pip install pyarrow
misata.to_duckdb(tables, "data/dataset.duckdb")
# Row-oriented
misata.to_jsonl(tables, "data/")
misata.to_sql(tables, "data/", dialect="postgresql") # CREATE TABLE + INSERT statements
# dialects: ansi, postgresql, mysql
Filas incrementales reproducibles
Genera filas adicionales que se añaden limpiamente a un conjunto de datos existente sin colisiones de ID:
# Day 1: generate the base dataset
schema = misata.from_dict_schema({...}, seed=1)
base = misata.generate_from_schema(schema)
for name, df in base.items():
df.to_csv(f"./data/{name}.csv", index=False)
# Day 2: generate only new rows, PKs offset above existing max
new_rows = misata.generate_diff(
schema,
existing_dir="./data/",
new_rows={"customers": 200, "orders": 1500},
output_dir="./data/delta/", # optional: write delta CSVs
)
generate_diff lee los CSV existentes para encontrar el PK máximo por tabla y genera nuevas filas con PKs desplazados por encima de ese máximo. Úsalo para pipelines de streaming, fixtures de prueba día a día y cualquier flujo de trabajo donde necesites extender un conjunto de datos sin regenerarlo desde cero.
Databricks y Apache Spark
Genera datos de prueba realistas y referencialmente correctos directamente en Delta Lake: no se requieren datos de producción. El módulo misata.spark conecta la salida pandas de Misata a Spark/Delta en Databricks (Edición Gratuita o completa), AWS Glue, EMR o cualquier clúster PySpark 3.3+.
import misata
from misata import spark as mspark
schema = misata.from_dict_schema({
"customers": {"__rows__": 500, "id": {"type": "integer", "primary_key": True},
"email": {"type": "email"}, "country": {"type": "string", "text_type": "country"}},
"orders": {"__rows__": 2000, "id": {"type": "integer", "primary_key": True},
"customer_id": {"type": "integer",
"foreign_key": {"table": "customers", "column": "id"}},
"total": {"type": "float", "distribution": "lognormal", "mu": 4.5, "sigma": 0.9}},
})
# One call: generate all tables (FK integrity guaranteed) and write to Delta
result = mspark.generate_to_delta(schema, spark, catalog="dev", database="bronze", mode="overwrite")
print(result.summary())
# ✅ customers (500 rows) → dev.bronze.customers
# ✅ orders (2,000 rows) → dev.bronze.orders
Lo que hace que dbldatagen no puede: múltiples tablas relacionadas en una sola llamada, integridad referencial garantizada, distribuciones realistas y conformidad de resultados: declara un agregado o tasa exacto (por ejemplo, «el fraude es 1.8 % en enero aumentando a 4.1 % para junio») y los datos se conforman, dando a las pruebas de pipeline posteriores una verdad fundamental conocida para afirmar.
| Función | Propósito |
|---|---|
generate_to_delta(schema, spark, …) | Una línea: genera + escribe todas las tablas a Delta |
to_spark(tables, spark, schema_config=…) | Convierte DataFrames de Misata a Spark con un esquema explícito y de tipo correcto |
write_delta(tables, spark, …) | Escribe a Delta con particionado, clustering líquido, propiedades de tabla o upsert MERGE |
verify_delta_integrity(spark, relationships, …) | Verifica la integridad de FK de tablas Delta mediante anti-joins de Spark SQL |
from_catalog_schema(spark, database, …) | Importa un esquema existente de Unity Catalog (solo estructura) → genera datos coincidentes, FKs inferidos automáticamente |
append_to_delta(schema, spark, n_rows=…) | Añade filas incrementales con PKs sin colisiones |
write_delta_stream(schema, spark, …) | Escribe en streaming conjuntos de datos de 100M+ filas sin almacenamiento en búfer |
En Databricks serverless / Edición Gratuita, instala misata simple (PySpark ya está en el clúster, instalar misata[spark] detendría una sesión serverless). En otros entornos: pip install misata[spark].
Tutorial de extremo a extremo: un pipeline completo de medallón de detección de fraude (Bronce → Plata → Oro) probado enteramente con datos sintéticos, con una afirmación de verdad fundamental de grado CI, examples/databricks/. Referencia completa de la API: docs/spark.md.
Generación de documentos
Renderiza un documento por fila de cualquier tabla, útil para conjuntos de datos de demostración que necesitan verse reales de extremo a extremo:
# Built-in templates: invoice, patient_report, transaction_receipt, user_profile
paths = misata.generate_documents(
tables, "invoice", table="orders", output_dir="/tmp/invoices", format="html"
)
# format="pdf" requires: pip install "misata[documents]"
# Custom Jinja2 template
tmpl = "<h1>Order #{{ order_id }}</h1><p>Amount: ${{ amount }}</p>"
paths = misata.generate_documents(tables, tmpl, table="orders", output_dir="/tmp/custom")
Análisis de calidad y privacidad
bundle = misata.analyze_generation(tables, schema) # runs privacy, fidelity, data_card
print(bundle.fidelity.overall_score) # 0–100 statistical fidelity score vs. schema intent
print(bundle.fidelity.grade) # letter grade for the same score
print(bundle.privacy.overall_risk_score) # heuristic PII / re-identification risk
print(bundle.data_card.tables) # per-table row counts and metadata
Evalpacks: bases de datos de evaluación donde la clave de respuestas no puede estar equivocada
Los benchmarks publicados de texto a SQL se construyen anotando pares de pregunta/respuesta sobre una base de datos existente, y ese paso de anotación es donde se cuelan errores generalizados en la clave de respuestas. Un evalpack invierte el orden: la verdad fundamental es la especificación declarada en sí (curvas de resultados, curvas de tasas, relaciones de FK), Misata genera una base de datos que la satisface, y cada pregunta incluida en el paquete se verifica luego ejecutando su SQL dorado contra los archivos CSV escritos con DuckDB, un motor que no comparte código con el generador. Las preguntas cuya respuesta observada no coincide exactamente con la respuesta declarada se descartan y se registran en el manifiesto. Una clave de respuestas incorrecta es imposible por construcción y se verifica dos veces mediante ejecución independiente.
pip install "misata[evalpack]"
misata evalpack --config misata.yaml -o ./my_pack --seed 42
from misata.evalpack import build_evalpack
result = build_evalpack(schema, "my_pack")
assert result.all_verified
Cada paquete incluye las tablas como CSV, questions.jsonl, un certificado de verificación por pregunta, un manifiesto con el hash de la especificación y la semilla, y un verify.py independiente que cualquiera puede re-ejecutar con solo duckdb instalado. Úsalo para evaluar agentes SQL, sistemas de RAG sobre bases de datos, o cualquier herramienta que afirme responder preguntas sobre datos, contra una base de datos cuyas respuestas correctas se conocen antes de que exista una sola fila.
Dominios admitidos
18 esquemas de dominio integrados, cada uno genera un conjunto de datos totalmente relacional y de múltiples tablas con distribuciones realistas, integridad de FK y semántica de columnas apropiada al dominio.
| Dominio | Palabras clave de activación | Tablas generadas |
|---|---|---|
| SaaS | saas, suscripción, mrr, churn | usuarios, suscripciones, facturas |
| Comercio electrónico | ecommerce, pedidos, tienda, retail | clientes, productos, pedidos, artículos_pedido |
| Fintech | fintech, pagos, banca, fraude | clientes, cuentas, transacciones |
| Salud | salud, pacientes, médicos, clínica | médicos, pacientes, citas |
| Marketplace | marketplace, vendedores, compradores, listados | vendedores, compradores, listados, pedidos |
| Logística | logística, envíos, conductores, rutas | conductores, vehículos, rutas, envíos |
| RRHH | rrhh, empleados, nómina, fuerza laboral | departamentos, empleados, nómina |
| Social | redes sociales, instagram, feed, seguidores | usuarios, publicaciones, seguimientos, reacciones |
| Bienes raíces | bienes raíces, vivienda, hipoteca | agentes, propiedades, transacciones |
| Farmacéutica | farma, clínico, ensayos | investigadores, proyectos, ensayos, hojas de tiempo |
| Entrega de comida | entrega de comida, restaurante, para llevar | restaurantes, clientes, repartidores, pedidos, artículos_pedido |
| EdTech | edtech, cursos, estudiantes, inscripciones | instructores, cursos, estudiantes, inscripciones, intentos_cuestionario |
| Gaming | gaming, jugadores, tabla de clasificación, esports | jugadores, partidas, sesiones, logros |
| CRM | crm, salesforce, tratos, pipeline | empresas, contactos, tratos, actividades |
| Crypto / Web3 | crypto, blockchain, ethereum, defi | billeteras, tokens, transacciones, precios_token |
| Seguros | seguros, póliza, reclamos, prima | clientes, pólizas, reclamos, pagos |
| Viajes | viajes, hotel, vuelos, reservas | usuarios, hoteles, vuelos, reservas, reseñas |
| Streaming | streaming, netflix, suscriptores, historial de visualización | suscriptores, contenido, historial_visualización, calificaciones |
Sin coincidencia de palabras clave → el sintetizador composicional construye un esquema estructural de múltiples tablas a partir de las propias entidades de tu oración (ver Dominios desconocidos arriba); historias sin entidades en absoluto recurren a una sola tabla genérica con inferencia inteligente de columnas.
Cómo funciona
story / YAML / dict / DB introspection / MCP tool call
↓
StoryParser · compositional synthesizer · locale detection · load_yaml_schema · schema_from_db
↓
DetectionReport (domain, confidence, near_misses, table_preview, warnings)
↓
SchemaConfig ← validate_schema() catches issues before any rows are generated
↓
DataSimulator
├─ topological sort (FK dependency order)
├─ domain priors → locale priors (salary, age, monetary)
├─ constraint engine (inequality, range, ratio, sum, unique)
├─ outcome curves (monthly targets from narrative control points)
├─ stratified profiles (per-subgroup distributions, pandas eval)
├─ AR1 / time-series autocorrelation (per entity, 4 models)
├─ state machine (Markov terminal states)
├─ ICC cluster effects (per-parent-entity random intercepts)
├─ Iman-Conover correlation engine (pairwise + full matrix)
├─ MAR / MNAR missingness (predictor-scaled and value-dependent)
├─ exact incidence (floor(n × rate), per-group rates)
├─ realism core (joint identities, temporal profiles, Zipf marginals,
│ geo facts, grammar microtext, numeric quantization)
└─ RealisticTextGenerator (capsules + Faker locale + vocabulary assets)
↓
{table_name: DataFrame}
↓
validate_domain · seed_database · to_parquet · to_arrow
to_duckdb · to_sql · to_jsonl · generate_documents · MCP CSV output
Prioridades de dominio: las columnas monetarias obtienen distribuciones log-normales. Las categóricas usan muestreo Zipf. Los tipos de sangre, las distribuciones de países y los rangos salariales reflejan estadísticas del mundo real.
Prioridades de localización: las distribuciones de salario y edad se sobrescriben con parámetros lognormales/normales específicos del país provenientes de estadísticas nacionales. "Brazilian fintech" en tu historia significa que los salarios se muestrean de la distribución BRL, no de la USD.
Curvas de resultados: la narrativa en lenguaje natural se analiza en puntos de control mensuales exactos. Eventos nombrados, trimestres y multiplicadores funcionan todos:
# All of these produce precise, shaped outcome curves:
misata.generate("SaaS mrr from $50k in Jan to $200k in Dec, with a Q3 slump")
misata.generate("Ecommerce orders, Black Friday spike, Christmas peak")
misata.generate("SaaS startup, MRR 10x growth over the year")
misata.generate("Fintech payments, strong Q4, dip in Q1")
Reglas de realismo: cost siempre es menor que price. delivered_at siempre es después de shipped_at. hire_date es después de date_of_birth + 18 años y nunca en el futuro. tenure_years se deriva en la misma fila de hire_date. Las direcciones de correo electrónico se derivan de las columnas de nombre y apellido, los nombres concuerdan con los géneros declarados, las distancias de ruta concuerdan con sus ciudades y el texto de reseña concuerda con su calificación de estrellas.
Qué hace diferente a Misata
La comparación refleja el comportamiento documentado y listo para usar de cada herramienta a finales de 2025; todas son bibliotecas capaces construidas para diferentes objetivos, y un «No» significa «no es una característica integrada», no «imposible».
| Faker | Synth | syda | SDV | Misata | |
|---|---|---|---|---|---|
| Sin configuración, una línea para datos multi-tabla | No | No | No | No | Sí |
| La historia detecta automáticamente la configuración regional + estadísticas del país | No | No | No | No | Sí |
| 18 esquemas de dominio integrados (SaaS → streaming) | No | No | No | No | Sí |
| Curvas narrativas (empuje del Q4, Black Friday, 10×) | No | No | No | No | Sí |
| Dominios desconocidos compuestos a partir de la propia frase | No | No | No | No | Sí |
| Identidades coherentes (el nombre ↔ el género ↔ el correo coinciden) | No | No | No | No | Sí |
| El texto de la reseña coincide demostrablemente con su calificación de estrellas | No | No | No | No | Sí |
| Distancias reales entre ciudades en tablas de rutas | No | No | No | No | Sí |
| Cápsulas de vocabulario de dominio compartibles | No | No | No | No | Sí |
| Modo imitación: clonar distribuciones desde un CSV | No | No | No | Sí | Sí |
| Correlación por pares + matriz completa (Iman-Conover) | No | No | No | Sí | Sí |
| Columnas geoespaciales (lat, lng, postal_code) | No | No | No | No | Sí |
| Inyección de anomalías (tasa de valores atípicos por columna) | No | No | No | No | Sí |
| Servidor MCP: utilizable desde Claude / Cursor | No | No | No | No | Sí |
| Esquema YAML confirmado en git | No | Sí | Sí | No | Sí |
| Validación de esquema JSON + autocompletado del editor | No | No | No | No | Sí |
| Introspección de BD → generar → re-sembrar | No | Sí | No | Limitado | Sí |
| Siembra directa de BD (Postgres / MySQL / SQLite) | No | No | No | No | Sí |
| Siembra de modelos SQLAlchemy | No | No | No | No | Sí |
| Integridad referencial en todas las tablas FK | No | Sí | Sí | Sí | Sí |
Restricciones de desigualdad / rango (price > cost) | No | Limitado | No | Sí | Sí |
| Curvas objetivo agregadas (forma del MRR mensual) | No | No | No | No | Sí |
| Distribuciones estratificadas por subgrupo (perfiles) | No | No | No | No | Sí |
| Falta informativa MAR y MNAR | No | No | No | No | Sí |
| Control exacto de incidencia (valores verdaderos floor(n × rate)) | No | No | No | No | Sí |
| Autocorrelación AR(1) / series temporales por entidad | No | No | No | No | Sí |
| Efectos de clúster ICC jerárquicos (multi-sitio) | No | No | No | No | Sí |
| Fórmula @parent en media/desviación estándar de distribución | No | No | No | No | Sí |
| Estados terminales de máquina de estados de Markov | No | No | No | No | Sí |
| Validación consciente del dominio (rangos clínicos/financieros) | No | No | No | No | Sí |
| Exportación SQL INSERT (ansi / postgresql / mysql) | No | No | No | No | Sí |
| Exportación Apache Arrow IPC | No | No | No | No | Sí |
| Filas incrementales reproducibles (generate_diff) | No | No | No | No | Sí |
| Distribuciones realistas de dominio | No | No | No | Limitado | Sí |
| LLM multi-proveedor (Groq / OpenAI / Claude / Gemini / Ollama) | No | No | Sí | No | Sí |
| Totalmente sin conexión, sin LLM requerido | Sí | Sí | No | Sí | Sí |
| Generación de documentos (HTML / PDF por fila) | No | No | No | No | Sí |
| Informes de calidad + privacidad | No | No | No | Limitado | Sí |
| Python puro, sin servicios externos | Sí | No | No | Sí | Sí |
Faker genera valores falsos individuales, no relacionales, sin esquema, sin precisión estadística.
Synth sobresale en flujos de trabajo de esquema-como-código con git; control de distribución limitado.
syda usa un LLM para cada fila, semánticamente rico pero costoso, lento y requiere una clave API.
SDV aprende de datos reales, un problema diferente (necesitas datos reales primero).
Gretel es un servicio en la nube que necesita una clave API y envía datos fuera de las instalaciones; Misata se ejecuta localmente.
Misata genera a partir de la intención, sin conexión por defecto, siembra bases de datos directamente y ahora trae estadísticas precisas por país a cada columna automáticamente.
Comparaciones completas cara a cara: Misata vs Faker, Misata vs SDV, Misata vs Gretel.
Rendimiento
Medido en Apple serie M (un solo núcleo, sin GPU):
| Carga de trabajo | Filas | Tiempo | Rendimiento |
|---|---|---|---|
| Tabla única, lognormal | 1 000 000 | 0.06 s | ~16M filas/s |
| Esquema de estrella (5 tablas, 4 FK) | 1 055 030 | 1.54 s | ~687k filas/s |
Contribuciones
git clone https://github.com/rasinmuhammed/misata
cd misata
pip install -e ".[dev]"
pytest tests/
1,088 pruebas, 0 fallos. Problemas y PRs bienvenidos, github.com/rasinmuhammed/misata/issues