<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Muhammed Rasin O M</title>
    <description>The latest articles on DEV Community by Muhammed Rasin O M (@rasinmuhammed).</description>
    <link>https://dev.to/rasinmuhammed</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3649283%2F280874f9-48bd-4283-838c-ab37ca897bda.jpeg</url>
      <title>DEV Community: Muhammed Rasin O M</title>
      <link>https://dev.to/rasinmuhammed</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/rasinmuhammed"/>
    <language>en</language>
    <item>
      <title>Half the answer keys in text-to-SQL benchmarks are wrong. So I generated the database from the answer key.</title>
      <dc:creator>Muhammed Rasin O M</dc:creator>
      <pubDate>Fri, 10 Jul 2026 13:02:47 +0000</pubDate>
      <link>https://dev.to/rasinmuhammed/half-the-answer-keys-in-text-to-sql-benchmarks-are-wrong-so-i-generated-the-database-from-the-3431</link>
      <guid>https://dev.to/rasinmuhammed/half-the-answer-keys-in-text-to-sql-benchmarks-are-wrong-so-i-generated-the-database-from-the-3431</guid>
      <description>&lt;p&gt;Earlier this year a team at UIUC audited the benchmarks the text-to-SQL field ranks itself on. The numbers in the paper (&lt;a href="https://arxiv.org/abs/2601.08778" rel="noopener noreferrer"&gt;Jin et al., VLDB 2026&lt;/a&gt;) stopped me cold: 52.8% of BIRD Mini-Dev annotations and 62.8% of Spider 2.0-Snow annotations are wrong. Not ambiguous. Wrong, as adjudicated by human SQL experts after an agent flagged them. When the authors corrected just 100 examples and re-evaluated 16 open-source agents, relative performance swung by as much as 31% and leaderboard positions moved by up to 9 places.&lt;/p&gt;

&lt;p&gt;Every team that picked a data agent off those leaderboards was, to a measurable degree, choosing based on noise.&lt;/p&gt;

&lt;p&gt;I build synthetic data tooling as a side quest, and my whole library is organised around one guarantee: declared numbers hold exactly in generated data. Reading that paper, it was hard to miss that the two problems are mirror images. Benchmarks struggle to attach correct answers to existing databases. My engine attaches a database to declared answers. So I spent this week building the inversion, and this post walks through the mechanism, the verification, and the parts that did not survive their own verification, because those are the most instructive.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why annotation-after-the-fact fails structurally
&lt;/h2&gt;

&lt;p&gt;The error rates are not annotator laziness. Look at how every benchmark in this lineage gets built. A database exists first, scraped or donated or assembled. Then question writers read schemas they did not design and write natural-language questions. Then annotators produce the gold SQL and expected answers by interrogating data full of null conventions, encoding quirks, and domain definitions they can only infer.&lt;/p&gt;

&lt;p&gt;The UIUC audit's taxonomy shows exactly where that leaks. Semantic mismatches between question and SQL, like a strict inequality in the question compiled as an inclusive BETWEEN. Schema misunderstandings: missed aggregations, wrong joins, a filter on the wrong status column. Domain knowledge errors, their example being an annotator treating K-12 as equivalent to grades 1 through 12. And genuinely ambiguous questions with several defensible readings.&lt;/p&gt;

&lt;p&gt;Strip away the details and all of these share one structural property: they can only exist because the answer was derived &lt;em&gt;after&lt;/em&gt; the data, by a human reading it. The answer key is a claim about the database, and claims can be wrong.&lt;/p&gt;

&lt;h2&gt;
  
  
  Inverting the construction
&lt;/h2&gt;

&lt;p&gt;So make the answer key not a claim but a constraint.&lt;/p&gt;

&lt;p&gt;Declare the ground truth first, as a specification: exact monthly revenue for calendar 2025, a fraud rate rising from 2% to 3.5%, two foreign-key relationships that must have zero orphans. Then generate a database that satisfies the specification. Questions are derived from the declaration. The expected answer for "total order amount in November 2025" is not something anyone computed by reading rows. It is $750,000.00 because the data was constructed so that it would be.&lt;/p&gt;

&lt;p&gt;The generation math matters here, so briefly: for each declared period the engine allocates a row count from the target and the average transaction value, draws per-row amounts from a lognormal around that average, then rescales the period so it sums to the target exactly. Two stages, allocation then scaling, no learned model anywhere in the path, fully deterministic given a seed. The method is written up in &lt;a href="https://arxiv.org/abs/2606.08736" rel="noopener noreferrer"&gt;the Misata paper&lt;/a&gt;; the engine is open source. The property that makes it usable for evaluation is that the aggregate guarantee survives all the way to files on disk: modern float serialization round-trips exactly, so the CSV a grader reads carries the same cent-exact sums the generator produced.&lt;/p&gt;

&lt;h2&gt;
  
  
  Generators don't get to grade their own homework
&lt;/h2&gt;

&lt;p&gt;Here is the part I consider non-negotiable, because "the generator says the generator is right" should convince nobody.&lt;/p&gt;

&lt;p&gt;After the CSVs are written, every candidate question's gold SQL is executed by DuckDB against those files, the ones that actually ship, and the observed answer is compared with the declared one. DuckDB shares no code with my engine. The two systems agree on nothing except the files. Each shipped question looks like this, from the actual pack:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"q011"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"question"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"In the orders table, what is the total of amount during November 2025 (rows whose timestamp is on or after 2025-11-01 and strictly before 2025-12-01)? Give a number rounded to 2 decimal places."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"gold_sql"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"SELECT ROUND(SUM(&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;amount&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;), 2) FROM &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;orders&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt; WHERE &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;order_date&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt; &amp;gt;= TIMESTAMP '2025-11-01 00:00:00' AND &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;order_date&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt; &amp;lt; TIMESTAMP '2025-12-01 00:00:00'"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"expected_answer"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;750000.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"answer_type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"number"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"round_decimals"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notice the question states its window as half-open and names its rounding. That is deliberate. Most of what the audit classified as ambiguity comes from questions that leave boundaries and output format to the reader's imagination. When you author the question from the declaration instead of from the data, you can simply say what you mean.&lt;/p&gt;

&lt;p&gt;The demonstration pack is public: &lt;a href="https://github.com/rasinmuhammed/evalpacks" rel="noopener noreferrer"&gt;github.com/rasinmuhammed/evalpacks&lt;/a&gt;. A 73,629-row orders table plus customers and products, 35 shipped questions, and a certificate recording the DuckDB version, the seed, the spec hash, and every per-question verification result. Re-checking it needs one dependency and about thirty seconds:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip &lt;span class="nb"&gt;install &lt;/span&gt;duckdb
python pack/verify.py
&lt;span class="c"&gt;# 35/35 verified exactly&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Edit one amount in one CSV and the script exits nonzero. That is the entire trust model. There is no leaderboard to take on faith, just a check anyone can run against the artifact itself.&lt;/p&gt;

&lt;h2&gt;
  
  
  The questions that did not survive
&lt;/h2&gt;

&lt;p&gt;The pack shipped 35 questions. The spec produced 40 candidates. The other five are my favorite part of the artifact.&lt;/p&gt;

&lt;p&gt;Declared rates collide with integer arithmetic. January was declared at a 2% fraud rate, and the January allocation came out to 4,824 orders. Two percent of 4,824 is 96.48 orders, and you cannot flag half an order. The achievable neighbors are 96 flagged rows, which is 1.99%, or 97, which is 2.01%. The verification gate measured 1.99% against a declared 2.00% and refused to ship the question. Same story for four other months:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;period&lt;/th&gt;
&lt;th&gt;declared rate&lt;/th&gt;
&lt;th&gt;achieved rate&lt;/th&gt;
&lt;th&gt;shipped?&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;2025-01&lt;/td&gt;
&lt;td&gt;2.00%&lt;/td&gt;
&lt;td&gt;1.99%&lt;/td&gt;
&lt;td&gt;dropped&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2025-06&lt;/td&gt;
&lt;td&gt;2.68%&lt;/td&gt;
&lt;td&gt;2.69%&lt;/td&gt;
&lt;td&gt;dropped&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2025-08&lt;/td&gt;
&lt;td&gt;2.95%&lt;/td&gt;
&lt;td&gt;2.96%&lt;/td&gt;
&lt;td&gt;dropped&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2025-10&lt;/td&gt;
&lt;td&gt;3.23%&lt;/td&gt;
&lt;td&gt;3.22%&lt;/td&gt;
&lt;td&gt;dropped&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2025-11&lt;/td&gt;
&lt;td&gt;3.36%&lt;/td&gt;
&lt;td&gt;3.37%&lt;/td&gt;
&lt;td&gt;dropped&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Seven other monthly rate anchors happened to be exactly achievable at four decimal places, and those shipped. All five failures are recorded in the manifest with their observed values, next to the full generation spec.&lt;/p&gt;

&lt;p&gt;I want to be precise about what this section is, because a reader could mistake it for an admission. A benchmark that publishes what it refused to include, with the measured reason, is making a stronger claim than one that ships everything: it is telling you the shipping bar is exact verification and showing you the bar rejecting things. The alternative, quietly shipping a question whose true answer is 1.99% with an answer key that says 2%, is precisely the failure mode the UIUC paper documented at scale.&lt;/p&gt;

&lt;h2&gt;
  
  
  Regeneration, or why contamination stops mattering
&lt;/h2&gt;

&lt;p&gt;BIRD and Spider are in every frontier model's training corpus by now. That is not an accusation, it is arithmetic: benchmark questions get indexed, discussed, and absorbed. Static benchmarks depreciate.&lt;/p&gt;

&lt;p&gt;An evalpack is a spec plus a seed, so it regenerates. Change the seed and you get a different database that satisfies the same declared answers. From my own run while writing this post:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;seed 20260710: November total = 750,000.00 over 73,629 orders
seed 99:       November total = 750,000.00 over 73,629 orders
shared order_ids between the two databases: 544
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two databases, essentially disjoint rows (the 544 shared IDs are random collisions in a ten-million-value ID space), identical declared answers, and the seed-99 pack re-verifies 35/35 on its own certificate. If you suspect a model has seen your eval data, you rotate the seed and the answer key survives. No annotation cycle, no re-adjudication.&lt;/p&gt;

&lt;p&gt;There is a second contamination defense hiding in plain sight: every entity in the data is plausible fiction. No model can answer "which month had the highest revenue" from memorized world knowledge about a retailer that does not exist. It has to query. For evaluation data, fictional values are not a compromise. They are the point.&lt;/p&gt;

&lt;h2&gt;
  
  
  Rewards for RL, where wrong labels hurt twice
&lt;/h2&gt;

&lt;p&gt;If you train data agents with reinforcement learning, ground-truth noise is worse than in evaluation, because the policy optimizes toward the noise. The large synthetic SQL corpora used for training measure around 86% full correctness by their own published human evals, and RL on a 14%-wrong reward signal is a strange thing to spend GPUs on.&lt;/p&gt;

&lt;p&gt;A verified evalpack is, from an RL perspective, a reward function with a certificate: exact-match rewards whose correctness was checked by an independent engine before training started, generable at whatever scale and difficulty the spec dials in. Join depth, aggregation complexity, and temporal structure are parameters, not accidents of whichever database was available. I think this is where answer-key-first construction ends up mattering most, and it is the part I would most like to compare notes on with people building agent environments.&lt;/p&gt;

&lt;h2&gt;
  
  
  Design choices a skeptic should ask about
&lt;/h2&gt;

&lt;p&gt;Three decisions define the current scope, and each was made with eyes open.&lt;/p&gt;

&lt;p&gt;Question families cover declared aggregates, per-period counts, rates, extrema, and referential integrity, not arbitrary SQL. That is what the spec language can declare today. The right way to extend coverage is to extend the declaration language, keeping the property that every shipped answer traces to a declared constraint, rather than annotating free-form questions and reintroducing the original disease.&lt;/p&gt;

&lt;p&gt;Natural-language ambiguity is reduced, not eliminated. Authoring questions from the declaration lets every question state its window, its rounding, and its tie-breaking rule explicitly, which removes the largest documented ambiguity sources. Wording is still wording, and I will not claim a theorem where I only have a discipline.&lt;/p&gt;

&lt;p&gt;And this complements human-authored benchmarks rather than replacing them. Real-world schema messiness, dialect coverage, the long tail of enterprise weirdness: existing benchmarks carry that, and correcting them, as the UIUC team is doing, stays essential. Answer-key-first construction owns the complementary territory: the error classes where annotation-after-the-fact structurally fails, at whatever scale you need, contamination-free by rotation.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try it
&lt;/h2&gt;

&lt;p&gt;Everything is MIT licensed. The pack, certificate, and verifier are at &lt;a href="https://github.com/rasinmuhammed/evalpacks" rel="noopener noreferrer"&gt;github.com/rasinmuhammed/evalpacks&lt;/a&gt;. Building your own takes one install and one command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="s1"&gt;'misata[evalpack]'&lt;/span&gt;
misata evalpack &lt;span class="nt"&gt;--config&lt;/span&gt; your_schema.yaml &lt;span class="nt"&gt;-o&lt;/span&gt; your_pack &lt;span class="nt"&gt;--seed&lt;/span&gt; 42
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The declaration language, integrity proofs, and the rest of the engine are documented at &lt;a href="https://misata.studio/docs" rel="noopener noreferrer"&gt;misata.studio/docs&lt;/a&gt;, and there is a no-code version at &lt;a href="https://misata.studio" rel="noopener noreferrer"&gt;misata.studio&lt;/a&gt; if you want to go from a plain-English description to a generated database without writing the schema by hand.&lt;/p&gt;

&lt;p&gt;If you build evaluations for data agents, or RL environments that contain databases, and this construction is either useful to you or broken in a way I have not noticed, I genuinely want to hear it. Open an issue on the repo. The fastest way to make the answer key trustworthy is to keep trying to break it.&lt;/p&gt;

</description>
      <category>evaluation</category>
      <category>dataagents</category>
      <category>benchmarks</category>
      <category>syntheticdata</category>
    </item>
    <item>
      <title>Synthetic Data for Data Engineering: How to test a Pipeline before the real data arrives</title>
      <dc:creator>Muhammed Rasin O M</dc:creator>
      <pubDate>Sat, 20 Jun 2026 15:17:34 +0000</pubDate>
      <link>https://dev.to/rasinmuhammed/synthetic-data-for-data-engineering-how-to-test-a-pipeline-before-the-real-data-arrives-4ikm</link>
      <guid>https://dev.to/rasinmuhammed/synthetic-data-for-data-engineering-how-to-test-a-pipeline-before-the-real-data-arrives-4ikm</guid>
      <description>

&lt;p&gt;There is a quiet absurdity at the center of most data work, and once you notice it you cannot stop seeing it.&lt;/p&gt;

&lt;p&gt;You are asked to make sure a pipeline is correct. To do that, you need data to run it on. The only data that would truly prove anything is the real data, the production tables with their odd shapes and their long tails and their one customer in Belgium who somehow has forty thousand orders. And that is exactly the data you are not allowed to have. It sits behind an access request, a compliance review, a privacy policy, and a Slack thread that goes quiet after someone says "let me check with legal."&lt;/p&gt;

&lt;p&gt;So you build the thing in the dark. You write twenty rows by hand and tell yourself they represent a million. You run a loop of fake names and random numbers and watch your joins quietly drop half the rows because none of the foreign keys point anywhere. You ship, and you wait, and you find out whether you were right when the real data finally lands and something downstream catches fire.&lt;/p&gt;

&lt;p&gt;This is the part nobody puts in the job description. Most of data engineering is reasoning about data you cannot look at.&lt;/p&gt;

&lt;p&gt;Synthetic data is usually sold as a machine learning trick. Train a model without touching real records. Get around the privacy rules. Pad out a dataset that is too small. All of that is real. But it hides the use that shows up far more often, in the ordinary Tuesday-afternoon work of building and testing pipelines. And the reframe that makes it click is small and a little beautiful.&lt;/p&gt;

&lt;h2&gt;
  
  
  The data whose answers you already know
&lt;/h2&gt;

&lt;p&gt;Here is the inversion. Normally data comes first and you spend your life trying to discover its truths. You profile it, you chart it, you write tests that hope to catch it lying. The data is the authority and you are the supplicant.&lt;/p&gt;

&lt;p&gt;Generated data flips the direction of that relationship. You state a truth, and the data assembles itself to honor it.&lt;/p&gt;

&lt;p&gt;Say you are testing a fraud aggregation. You run your pipeline against some test data and it reports three point one percent fraud last month. Is that correct? You have no idea. You do not know the true fraud rate of your test data, so the number is unfalsifiable. The test can confirm the code ran. It cannot confirm the code is right.&lt;/p&gt;

&lt;p&gt;Now turn it around. Generate test data where you decided the fraud rate in advance. One point eight percent in January, climbing to four point one by June. Run the pipeline. If the gold table comes back at one point seven nine and four point oh nine, then your joins held, your filters were honest, your aggregation counted what it was supposed to count. If it comes back at two and a half, something in the middle is dropping or double counting fraud, and you caught it on a laptop, on a branch, before a single real record was at risk.&lt;/p&gt;

&lt;p&gt;That is a real test for a data pipeline. Not "did it run" but "is it correct." And you cannot write it with a fake-data loop, because a fake-data loop cannot hand you a target to check against. The value here is not that the data looks real. It is that you wrote the answer key first.&lt;/p&gt;

&lt;h2&gt;
  
  
  What this actually does on an ordinary day
&lt;/h2&gt;

&lt;p&gt;A few jobs where this earns its place.&lt;/p&gt;

&lt;h3&gt;
  
  
  Proving a pipeline is correct, on every commit
&lt;/h3&gt;

&lt;p&gt;Declare the tables, the relationships between them, and the outcome you care about. Generate. Assert that your transformation reproduces the outcome. Here is the shape of it, using &lt;a href="https://github.com/rasinmuhammed/misata" rel="noopener noreferrer"&gt;Misata&lt;/a&gt;, an open source library I build (so take the pitch with the appropriate pinch of salt, though the idea holds whatever you reach for):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;misata&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;misata&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;from_dict_schema&lt;/span&gt;

&lt;span class="n"&gt;schema&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;from_dict_schema&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;__rate_curves__&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;table&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;transactions&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;column&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;is_fraud&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;time_column&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;txn_ts&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;time_unit&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;month&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;true_value&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;rate_points&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;period&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;2025-01&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;rate&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;0.018&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;period&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;2025-06&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;rate&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;0.041&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="p"&gt;}],&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;customers&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;type&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;integer&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;primary_key&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;email&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;type&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;email&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;transactions&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;type&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;integer&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;primary_key&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;customer_id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;type&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;integer&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;foreign_key&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;table&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;customers&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;column&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;}},&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;amount&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;type&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;float&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;distribution&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;lognormal&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;mu&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;3.4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;sigma&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;1.1&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;txn_ts&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;type&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;datetime&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;start&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;2025-01-01&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;end&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;2025-06-30&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;is_fraud&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;type&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;boolean&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;

&lt;span class="n"&gt;tables&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;misata&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;generate_from_schema&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;schema&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Every transaction points at a customer who exists. The monthly fraud rate follows the curve you declared. Run your silver and gold transforms on top, then assert the gold output lands on that curve within a tolerance. Wire it into CI and it runs forever, with no access to production and nothing to leak.&lt;/p&gt;

&lt;h3&gt;
  
  
  Making a copy of data you have but cannot share
&lt;/h3&gt;

&lt;p&gt;Sometimes the real table is right there on your screen and the problem is the opposite. You need to send it somewhere it should not go. To a vendor, into a public demo, onto the laptop of someone who started on Monday. The job now is a copy that keeps the statistical shape and the relationships between columns while carrying none of the actual rows.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;pandas&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;pd&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;misata&lt;/span&gt;

&lt;span class="n"&gt;real&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;read_csv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;customers.csv&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;synthetic&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;misata&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;mimic&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;real&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;rows&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;real&lt;/span&gt;&lt;span class="p"&gt;))[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;table&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;misata&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fidelity_report&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;synthetic&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;real&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;target_column&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;churned&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;summary&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;misata&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;privacy_report&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;synthetic&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;real&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;summary&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The two report lines are the point. A copy is only worth using if you can measure that it matches the real distributions and the relationships inside them. It is only worth sharing if you can measure that it is not quietly memorizing real people. Numbers you can put in a pull request, not a feeling you defend in a meeting.&lt;/p&gt;

&lt;p&gt;Honesty belongs here too, because this is the hard part of the whole field. Simple tabular structure reproduces beautifully: distributions, correlations between columns, values that shift by category. Complicated nonlinear or geographic structure is genuinely harder, and the right move is to measure the gap rather than pretend it closed. If your need is deep generative fidelity on messy real data, the heavier synthesizers like &lt;a href="https://github.com/sdv-dev/SDV" rel="noopener noreferrer"&gt;SDV&lt;/a&gt; have spent years on exactly that.&lt;/p&gt;

&lt;h3&gt;
  
  
  Filling the time before the data exists
&lt;/h3&gt;

&lt;p&gt;Two more, quickly. Load testing wants volume with a believable shape, so you can watch a pipeline behave at a hundred million rows before the business actually sends them. And teams before launch have no data at all, yet still have to build the dashboard and train the first model. Declared synthetic data covers both, and since you set the dials, you know what every downstream number is supposed to say.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where it is the wrong tool, plainly
&lt;/h2&gt;

&lt;p&gt;It will not find a bug you did not think to model. If an upstream system encodes dates in a way nobody documented, synthetic data built from a clean schema will be innocent of that sin, and your pipeline will meet it for the first time in production. Use generated data to prove your logic against known inputs, not to discover the unknown.&lt;/p&gt;

&lt;p&gt;It does not replace an integration test against the real source systems. It exercises your transformations, not your connectors.&lt;/p&gt;

&lt;p&gt;And a copy learned from real data is still a descendant of real data. Measure the privacy distance before you hand it over. Assume nothing.&lt;/p&gt;

&lt;h2&gt;
  
  
  The small wonder of it
&lt;/h2&gt;

&lt;p&gt;Come back to that two in the morning feeling, the green pipeline you did not trust. The reason you did not trust it was that nothing in the test could have told you it was wrong. The data had no opinion about the right answer.&lt;/p&gt;

&lt;p&gt;What changes, when you generate data on purpose, is that the data starts to carry an opinion. You write down what should be true, the rows arrange themselves around it, and then your pipeline has to earn that truth back out the other end. The test can finally fail for the right reasons. That is a strange and slightly wonderful thing to be able to do, to know the answer before the data exists, and it turns the loneliest part of data engineering, reasoning in the dark about tables you cannot see, into something you can actually check.&lt;/p&gt;

&lt;p&gt;If you want to try the examples, Misata is &lt;code&gt;pip install misata&lt;/code&gt;, and there are runnable Databricks notebooks in the &lt;a href="https://github.com/rasinmuhammed/misata" rel="noopener noreferrer"&gt;repository&lt;/a&gt;. Whatever you use, give your pipelines a test that knows the answer.&lt;/p&gt;

</description>
      <category>dataengineering</category>
      <category>testing</category>
      <category>python</category>
      <category>databricks</category>
    </item>
    <item>
      <title>The Best Python Library for Generating Quick Synthetic Data in 2026</title>
      <dc:creator>Muhammed Rasin O M</dc:creator>
      <pubDate>Sat, 11 Apr 2026 14:22:09 +0000</pubDate>
      <link>https://dev.to/rasinmuhammed/the-best-python-library-for-generating-quick-synthetic-data-in-2026-5681</link>
      <guid>https://dev.to/rasinmuhammed/the-best-python-library-for-generating-quick-synthetic-data-in-2026-5681</guid>
      <description>&lt;h3&gt;
  
  
  Misata: Generate Realistic Synthetic Datasets From Plain English Descriptions
&lt;/h3&gt;

&lt;p&gt;Generating synthetic data in Python used to mean one of three things: write &lt;code&gt;random.uniform()&lt;/code&gt; loops by hand, use Faker for fake names and emails, or spend a week configuring SDV on top of real data you might not even have. But we have got LLMs now. Still maintaining the logics and the referential integrity is a nightmare.&lt;/p&gt;

&lt;p&gt;Misata is none of those things.&lt;/p&gt;

&lt;p&gt;One sentence in. Multiple related tables out. Distributions calibrated to real-world statistics. Foreign key integrity guaranteed. Monthly revenue targets hit to the cent.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip &lt;span class="nb"&gt;install &lt;/span&gt;misata
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;misata&lt;/span&gt;

&lt;span class="n"&gt;tables&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;misata&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;generate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;A SaaS company with 2000 users. &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;MRR rises from 80k in January to 320k in June, &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;drops to 180k in August due to churn, &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;then recovers to 400k in December.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;seed&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;42&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That generates two linked tables with 21,000+ rows. Here is what the monthly MRR looks like when you sum the rows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Jan    $80,000   ✓
Feb   $128,000   ✓
Mar   $176,000   ✓
Apr   $224,000   ✓
May   $272,000   ✓
Jun   $320,000   ✓
Jul   $250,000   ✓
Aug   $180,000   ✓   &amp;lt;- churn dip, as described
Sep   $235,000   ✓
Oct   $290,000   ✓
Nov   $345,000   ✓
Dec   $400,000   ✓
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Every target exact. Not approximate. The individual rows still follow a log-normal distribution (median MRR $126, mean $150, p90 $291) because that is what real SaaS revenue looks like. But the monthly totals are pinned to whatever story you gave it.&lt;/p&gt;




&lt;h2&gt;
  
  
  The core problem: why most synthetic data is useless
&lt;/h2&gt;

&lt;p&gt;There's a gap between what synthetic data generators produce and what you actually need to build, test, or demo a data system.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Uniform distributions lie.&lt;/strong&gt; Real revenue data is log-normal. Real fraud rates hover around 2%, not 50%. Real product category distributions follow Zipf's law - one category dominates, the others trail off. When your fake data looks nothing like the real thing, your model trains on lies, your dashboards tell wrong stories, and your tests pass cases that would fail in production.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Referential integrity breaks things.&lt;/strong&gt; If you're testing a JOIN across customers and transactions, orphan foreign keys will silently ruin your results. Most data generators either skip relational structure entirely or produce it inconsistently.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Business targets get ignored.&lt;/strong&gt; You don't just want data that looks roughly right. You want a dataset where Q3 revenue dips 22% due to a simulated product recall, or where churn spikes in August because your description says so. No general-purpose generator can do this.&lt;/p&gt;

&lt;p&gt;Misata was built specifically to close this gap.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why distributions matter more than people think
&lt;/h2&gt;

&lt;p&gt;Most fake data generators produce values that are uniformly distributed. When you plot them, everything looks flat. Real business data is never flat.&lt;/p&gt;

&lt;p&gt;Misata ships calibrated distribution priors for seven domains. Here is what that means in practice.&lt;/p&gt;

&lt;h3&gt;
  
  
  Fintech: fraud rates, credit scores, and account balances
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;tables&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;misata&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;generate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;A fintech company with 2000 customers and banking transactions.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;seed&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;42&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;transactions&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;tables&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;transactions&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Fraud rate: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;transactions&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;is_fraud&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;mean&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;%&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Fraud rate: 2.00%
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;400 fraudulent transactions out of 20,000. The calibrated real-world baseline for card fraud is around 2%. That is what you get. Not a random number. A calibrated one.&lt;/p&gt;

&lt;p&gt;Credit scores follow the actual US distribution:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;mean:   679   (real US average: 680-720)
std:     80   (real range: 70-90)
min:    328
max:    850
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Account balances follow log-normal because real bank balances do:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;median     $1,976
mean       $6,128
p90       $14,260
p99       $62,565
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Most customers have under two thousand dollars. A few have tens of thousands. The tail is real. This matters enormously if you're building fraud detection models, credit scoring pipelines, or stress-testing payment infrastructure — a flat distribution would make every one of those models overfit to a distribution that doesn't exist in production.&lt;/p&gt;

&lt;h3&gt;
  
  
  Healthcare: blood type frequencies, age distributions, and appointment patterns
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;tables&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;misata&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;generate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;A hospital with 500 patients and doctors.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;seed&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;42&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;patients&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;tables&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;patients&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Blood type    Generated    Real-world
O+               37.9%        38.0%   ✓
A+               33.9%        34.0%   ✓
B+                9.6%         9.0%   ✓
AB+               3.0%         3.0%   ✓
O-                6.5%         7.0%   ✓
A-                6.1%         6.0%   ✓
B-                2.0%         2.0%   ✓
AB-               0.9%         1.0%   ✓
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;All eight blood types within 0.6% of the actual ABO/Rh frequency distribution. Patient ages center on 45 with a standard deviation of 18, matching a chronic-care hospital population. Nobody configured any of this. It is what the healthcare domain prior knows.&lt;/p&gt;

&lt;p&gt;This level of epidemiological accuracy is essential when you're training triage models, testing EHR systems, or building health analytics pipelines that will eventually run on real patient data.&lt;/p&gt;

&lt;h3&gt;
  
  
  Ecommerce: Zipf categories, seasonal peaks, and return rates
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;schema&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;misata&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;An ecommerce store with 5000 customers and orders. &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Revenue grows from 100k in January to 300k in November &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;then 350k in December.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;rows&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;5000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;tables&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;misata&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;generate_from_schema&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;schema&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Product categories follow Zipf's law because that is how real shopping behavior works:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;electronics      47.1%
clothing         20.0%
home &amp;amp; garden    12.3%
sports            8.7%
books             6.5%
beauty            5.5%
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;One category dominates. The rest trail off. Uniform would give you ~17% each. Real shopping does not look like that.&lt;/p&gt;

&lt;p&gt;Order statuses come with realistic rates:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;completed    71.5%
shipped      12.4%
pending       8.2%
returned      5.0%
cancelled     3.0%
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Real e-commerce return rates are 8–10%. That is what gets generated. If you're building a returns processing pipeline, this means your test data will actually stress the right code paths.&lt;/p&gt;




&lt;h2&gt;
  
  
  Referential integrity across all tables
&lt;/h2&gt;

&lt;p&gt;Every child table samples foreign key values from the actual parent pool. This means zero orphan rows by construction, not by luck.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;tables&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;misata&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;generate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;A fintech company with 2000 customers and banking transactions.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;seed&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;42&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;customers&lt;/span&gt;    &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;tables&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;customers&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;     &lt;span class="c1"&gt;# 2,000 rows
&lt;/span&gt;&lt;span class="n"&gt;accounts&lt;/span&gt;     &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;tables&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;accounts&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;      &lt;span class="c1"&gt;# 2,600 rows
&lt;/span&gt;&lt;span class="n"&gt;transactions&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;tables&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;transactions&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;  &lt;span class="c1"&gt;# 20,000 rows
&lt;/span&gt;
&lt;span class="c1"&gt;# Both FK edges hold
&lt;/span&gt;&lt;span class="n"&gt;orphan_accounts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;~&lt;/span&gt;&lt;span class="n"&gt;accounts&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;customer_id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;isin&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;customers&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;customer_id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])).&lt;/span&gt;&lt;span class="nf"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;orphan_txns&lt;/span&gt;     &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;~&lt;/span&gt;&lt;span class="n"&gt;transactions&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;account_id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;isin&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;accounts&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;account_id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])).&lt;/span&gt;&lt;span class="nf"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;orphan_accounts&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# 0
&lt;/span&gt;&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;orphan_txns&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;      &lt;span class="c1"&gt;# 0
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Tables are generated in topological dependency order. Parents first. Children sample from the completed parent pool. It cannot produce orphans.&lt;/p&gt;

&lt;p&gt;This matters for any workflow that involves JOINs. Referential integrity errors in test data produce false negatives — your pipeline looks like it works until it meets real data.&lt;/p&gt;




&lt;h2&gt;
  
  
  The two-step flow for more control
&lt;/h2&gt;

&lt;p&gt;When you want to inspect or modify the schema before committing to generation:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;schema&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;misata&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;A hospital with 500 patients and doctors.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;schema&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;summary&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Schema: Healthcare Dataset
Domain: healthcare
Tables (3)
  doctors         25 rows    [doctor_id, first_name, last_name, specialty, years_experience]
  patients       500 rows    [patient_id, name, age, gender, blood_type, registered_at]
  appointments  1500 rows    [appointment_id, patient_id, doctor_id, type, duration_minutes]

Relationships (2)
  patients.patient_id  -&amp;gt; appointments.patient_id
  doctors.doctor_id    -&amp;gt; appointments.doctor_id
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Adjust the seed, add columns, change row counts. Then generate. The two-step flow is useful for teams where a data engineer defines the schema and a developer generates data against it — the schema becomes a shared artifact you can version control.&lt;/p&gt;




&lt;h2&gt;
  
  
  Real-world use cases
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Use case 1: Training ML models without access to production data
&lt;/h3&gt;

&lt;p&gt;Privacy regulations — GDPR, HIPAA, CCPA — make it difficult or impossible to use real user data for model training in many industries. The usual workaround is anonymization, but anonymized data often loses the statistical properties that make it useful for training.&lt;/p&gt;

&lt;p&gt;Misata generates statistically calibrated data with no PII at all. A fraud detection team can produce 500,000 transactions with a realistic 2% fraud rate, a plausible credit score distribution, and calibrated account balance tails — without touching a single real customer record.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;tables&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;misata&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;generate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;A fintech company with 50000 customers and banking transactions. &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Fraud rate is 2%. High-value accounts above 50k balance are 3x more likely to be targeted.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;seed&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;42&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The model trains on data that behaves like production data. The privacy risk is zero.&lt;/p&gt;

&lt;h3&gt;
  
  
  Use case 2: Seeding development and staging databases
&lt;/h3&gt;

&lt;p&gt;Every new developer joining a product team hits the same wall: the development database is empty or has three test rows from 2019. You can't build features that depend on realistic data patterns without realistic data.&lt;/p&gt;

&lt;p&gt;Misata can seed a full development database in seconds:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;misata&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;seed_database&lt;/span&gt;

&lt;span class="n"&gt;tables&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;misata&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;generate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;A SaaS company with 1000 users.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;seed&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;42&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;report&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;seed_database&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tables&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;postgresql://user:pass@localhost/mydb&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;create&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;report&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;total_rows&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# 12,400
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or from the CLI, which makes it easy to add to a &lt;code&gt;Makefile&lt;/code&gt; or &lt;code&gt;docker-compose&lt;/code&gt; setup:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;misata generate &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--story&lt;/span&gt; &lt;span class="s2"&gt;"A SaaS company with 1000 users"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--db-url&lt;/span&gt; postgresql://user:pass@localhost/mydb &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--db-create&lt;/span&gt; &lt;span class="nt"&gt;--db-truncate&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;SQLite works too for local-only development:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;misata generate &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--story&lt;/span&gt; &lt;span class="s2"&gt;"A SaaS company with 1000 users"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--db-url&lt;/span&gt; sqlite:///./dev.db &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--db-create&lt;/span&gt; &lt;span class="nt"&gt;--db-truncate&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A new developer can run &lt;code&gt;make seed-db&lt;/code&gt; and have a working dataset in their environment in under 10 seconds.&lt;/p&gt;

&lt;h3&gt;
  
  
  Use case 3: Building product demos without real customer data
&lt;/h3&gt;

&lt;p&gt;Sales engineering teams routinely need to demo analytics dashboards, CRM systems, and data products to prospects. Using real customer data for demos is a legal and ethical non-starter. Using hand-crafted fake data means someone spends two days building a CSV in Excel.&lt;/p&gt;

&lt;p&gt;Misata lets you generate a compelling, internally consistent demo dataset for any domain:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;tables&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;misata&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;generate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;A B2B SaaS company with 800 enterprise customers. &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ARR grows from 2M in Q1 to 5M in Q4. &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Average contract value is 6000. Churn rate is 8%.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;seed&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;42&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The result is a dataset where every KPI in the demo dashboard reflects a plausible business trajectory — not a random scatter of numbers.&lt;/p&gt;

&lt;h3&gt;
  
  
  Use case 4: Testing data pipelines and ETL systems
&lt;/h3&gt;

&lt;p&gt;Data pipeline tests are only as good as the data they run on. Edge cases like NULL foreign keys, skewed distributions, and outlier values are exactly what break pipelines in production — and exactly what hand-crafted test data tends to miss.&lt;/p&gt;

&lt;p&gt;Misata's calibrated distributions naturally produce the tail values that stress-test pipelines:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;tables&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;misata&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;generate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;A logistics company with 10000 shipments. &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Include delayed deliveries at a 12% rate. &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;International shipments are 30% of total volume.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;seed&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;42&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The p99 values in account balances, the occasional NULL in optional fields, the rare blood type AB- at 1% frequency — these are the values that reveal pipeline brittleness.&lt;/p&gt;

&lt;h3&gt;
  
  
  Use case 5: Generating benchmark datasets for academic and research use
&lt;/h3&gt;

&lt;p&gt;Researchers publishing papers on data systems, query optimizers, or ML benchmarks need datasets that are reproducible, realistic, and free of privacy concerns. Misata's &lt;code&gt;seed&lt;/code&gt; parameter makes generation fully deterministic:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;tables&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;misata&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;generate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;A marketplace with 5000 buyers and sellers, orders, and product listings.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;seed&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;42&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="c1"&gt;# Anyone running this gets the exact same dataset
&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Share the &lt;code&gt;seed&lt;/code&gt; and description in your paper. Readers can reproduce your exact dataset with a single Python call.&lt;/p&gt;

&lt;h3&gt;
  
  
  Use case 6: Prototyping data products and BI dashboards
&lt;/h3&gt;

&lt;p&gt;Before you connect a BI tool to production data, you need something to build against. Misata gives you a structurally correct, statistically plausible dataset to prototype on — so you can validate your data model, build your first dashboard, and demo your schema to stakeholders before a single production row exists.&lt;/p&gt;




&lt;h2&gt;
  
  
  LLM-powered generation for custom domains
&lt;/h2&gt;

&lt;p&gt;The rule-based parser covers SaaS, ecommerce, fintech, healthcare, marketplace, logistics, and pharma. For anything outside those domains, the LLM backend handles arbitrary schema generation:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;misata&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;LLMSchemaGenerator&lt;/span&gt;

&lt;span class="n"&gt;gen&lt;/span&gt;    &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;LLMSchemaGenerator&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;provider&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;groq&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;   &lt;span class="c1"&gt;# or openai, ollama
&lt;/span&gt;&lt;span class="n"&gt;schema&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;gen&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;generate_from_story&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;A B2B marketplace with vendor tiers, SLA contracts, and quarterly invoices&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;tables&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;misata&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;generate_from_schema&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;schema&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This works with any LLM provider that supports the OpenAI-compatible API format. Requires &lt;code&gt;GROQ_API_KEY&lt;/code&gt; or &lt;code&gt;OPENAI_API_KEY&lt;/code&gt;. Retries automatically on rate limits.&lt;/p&gt;

&lt;p&gt;The LLM path is useful for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Industry-specific schemas with unusual entities (clinical trials, commodity trading, fleet management)&lt;/li&gt;
&lt;li&gt;Multi-tenant SaaS with complex permission hierarchies&lt;/li&gt;
&lt;li&gt;Any domain where the rule-based parser doesn't have calibrated priors&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The LLM infers a reasonable schema, column types, and row count ratios from your description. You get back the same DataFrames as the rule-based path — just with the schema derived from a language model instead of hard-coded priors.&lt;/p&gt;




&lt;h2&gt;
  
  
  How it compares to Faker and SDV
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Faker&lt;/strong&gt; generates individual fake values. One row at a time. It has no concept of tables that reference each other and no domain-specific distributions. Wiring foreign keys and getting log-normal amounts is your job.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;SDV (Synthetic Data Vault)&lt;/strong&gt; learns patterns from real data and generates synthetic copies. It requires actual training data, pulls in heavy ML dependencies, and cannot pin specific business targets like "fraud rate must be 2%." If you don't have real data to train on, SDV is a dead end.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Misata&lt;/strong&gt; generates from a description. No real data required. No ML training. Distributions are calibrated to domain knowledge. Business targets are exact.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Faker&lt;/th&gt;
&lt;th&gt;SDV&lt;/th&gt;
&lt;th&gt;Misata&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Multi-table FK integrity&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Partial&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;No real data needed&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Calibrated domain distributions&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Learned&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Exact monthly aggregate targets&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Plain-English story input&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Database seeding&lt;/td&gt;
&lt;td&gt;Manual&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;LLM-powered custom domains&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Reproducible with seed&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The key distinction: SDV is a synthetic data &lt;em&gt;replication&lt;/em&gt; tool. Misata is a synthetic data &lt;em&gt;generation&lt;/em&gt; tool. They solve different problems. SDV needs real data to learn from. Misata generates from scratch.&lt;/p&gt;




&lt;h2&gt;
  
  
  Installation and quick start
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip &lt;span class="nb"&gt;install &lt;/span&gt;misata pandas numpy
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;All of these produce full verified output in under 3 seconds:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;python examples/saas_revenue_curve.py
python examples/fintech_fraud_detection.py
python examples/healthcare_multi_table.py
python examples/ecommerce_seasonal.py
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or open the &lt;a href="https://colab.research.google.com/github/rasinmuhammed/misata/blob/main/notebooks/quickstart.ipynb" rel="noopener noreferrer"&gt;Colab notebook&lt;/a&gt; and run it without installing anything. No signup, no API key, no configuration.&lt;/p&gt;




&lt;h2&gt;
  
  
  Design principles
&lt;/h2&gt;

&lt;p&gt;A few constraints Misata holds to that are worth understanding:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Determinism over randomness.&lt;/strong&gt; Given the same description and seed, you always get the same dataset. This is non-negotiable for reproducible research and CI pipelines where test data needs to be stable across runs.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Statistical realism over convenience.&lt;/strong&gt; It would be simpler to generate uniformly distributed values. Misata does not do this because uniform distributions produce data that behaves nothing like real data. The extra calibration work is the point.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Aggregate targets are constraints, not approximations.&lt;/strong&gt; When you say MRR should be $320k in June, the generated data will sum to exactly $320k in June. Not $318k. Not $322k. The individual rows remain statistically realistic while the aggregates are treated as hard constraints.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Referential integrity is structural, not checked.&lt;/strong&gt; Misata does not generate data and then validate foreign keys. It generates in dependency order so invalid keys cannot occur. This is a stronger guarantee than post-hoc validation.&lt;/p&gt;




&lt;h2&gt;
  
  
  Frequently asked questions
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Can I add custom columns to a generated schema?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Yes. The two-step &lt;code&gt;parse&lt;/code&gt; → &lt;code&gt;generate_from_schema&lt;/code&gt; flow lets you inspect and modify the schema object before generating. You can add columns, change data types, adjust row counts, and modify relationship cardinality.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How large can generated datasets be?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Misata is DataFrame-based, so the practical limit is your available RAM. For datasets larger than a few million rows, you can generate in chunks and write directly to a database using &lt;code&gt;seed_database&lt;/code&gt;. Benchmarks on a standard laptop show ~500k rows/second for most schemas.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Does it support databases other than PostgreSQL and SQLite?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;seed_database&lt;/code&gt; accepts any SQLAlchemy connection string, which covers PostgreSQL, MySQL, SQLite, MS SQL Server, Oracle, and others. If SQLAlchemy can connect to it, Misata can seed it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Is there a way to generate time-series data?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Temporal columns are supported. The &lt;code&gt;registered_at&lt;/code&gt;, &lt;code&gt;transaction_date&lt;/code&gt;, and similar timestamp fields follow realistic distributions relative to one another — a customer's first transaction always comes after their account creation date, for example. You can specify date ranges in your description: "transactions between January 2023 and December 2024."&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What if I need data that follows my company's specific distribution?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The LLM path lets you describe distribution constraints in natural language: "30% of accounts are enterprise tier with balances above $50k." For highly specific requirements, the schema object exposes column-level distribution parameters you can override directly.&lt;/p&gt;




&lt;p&gt;Misata is open source, MIT licensed, and available now.&lt;/p&gt;

&lt;p&gt;GitHub: &lt;a href="https://github.com/rasinmuhammed/misata" rel="noopener noreferrer"&gt;github.com/rasinmuhammed/misata&lt;/a&gt;&lt;br&gt;
PyPI: &lt;a href="https://pypi.org/project/misata/" rel="noopener noreferrer"&gt;pypi.org/project/misata&lt;/a&gt;&lt;br&gt;
Docs: &lt;a href="https://github.com/rasinmuhammed/misata/blob/main/QUICKSTART.md" rel="noopener noreferrer"&gt;QUICKSTART.md&lt;/a&gt;&lt;br&gt;
Colab: &lt;a href="https://colab.research.google.com/github/rasinmuhammed/misata/blob/main/notebooks/quickstart.ipynb" rel="noopener noreferrer"&gt;Run the quickstart notebook&lt;/a&gt;&lt;/p&gt;

</description>
      <category>python</category>
      <category>datascience</category>
      <category>testing</category>
      <category>opensource</category>
    </item>
    <item>
      <title>How I Built a "Story-to-Data" Engine in Python (Because Faker Wasn't Enough)</title>
      <dc:creator>Muhammed Rasin O M</dc:creator>
      <pubDate>Tue, 16 Dec 2025 16:01:49 +0000</pubDate>
      <link>https://dev.to/rasinmuhammed/how-i-built-a-story-to-data-engine-in-python-because-faker-wasnt-enough-3oi0</link>
      <guid>https://dev.to/rasinmuhammed/how-i-built-a-story-to-data-engine-in-python-because-faker-wasnt-enough-3oi0</guid>
      <description>&lt;p&gt;&lt;strong&gt;The "2 Months of Pain" Origin Story&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;An year ago, I was working as a Data Science Engineer at a consultancy firm. We needed to build a Tableau dashboard to demonstrate a new business model. The consultants didn't want "random" data; they wanted a specific story:&lt;/p&gt;

&lt;p&gt;"Show a _____ failing in Phase 2, causing a 40% revenue dip in Q3, followed by a recovery in Q4 due to a new ____ launch."&lt;/p&gt;

&lt;p&gt;I tried at first using standard libraries like Faker and Mimesis. They are fantastic for generating random names and emails, but they failed hard on Business Logic. Then I used python scripting to generate the data, using for loops and all kind of loops.&lt;/p&gt;

&lt;p&gt;I ended up with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Time Travel Bugs: Timesheets dated before an employee's hire date.&lt;/li&gt;
&lt;li&gt;Orphaned Rows: Orders linked to non-existent Users.&lt;/li&gt;
&lt;li&gt;Flat Curves: Revenue that looked like static noise, not a "Q3 Dip."&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I spent 2 months manually hacking Python scripts, hard-coding probabilities, and stitching CSVs together to make the demo look real. It was a nightmare.&lt;/p&gt;

&lt;p&gt;I realized: We don't need more random data generators. We need Narrative Data Engines.&lt;/p&gt;

&lt;p&gt;So, I built &lt;strong&gt;Misata&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What is Misata?&lt;/strong&gt;&lt;br&gt;
Misata is an open-source Python engine that turns a natural language story into a multi-table, referentially intact dataset.&lt;/p&gt;

&lt;p&gt;Instead of writing 500 lines of schema config, you just type:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;misata generate --story "A SaaS platform with 50k users, 20% churn in Q3, and usage-based billing" --use-llm
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And it generates SQL-ready CSVs where the math actually works.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Under the Hood: The Architecture&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Misata isn't just a wrapper around Faker. It uses a Neuro-Symbolic approach to solve the consistency problem.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The Brain (LLM Parser)&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;First, it uses an LLM (I optimized it for Llama 3.3 via Groq) to parse your story into a strict JSON schema. It extracts:&lt;/p&gt;

&lt;p&gt;Entities: Users, Subscriptions, Invoices.&lt;/p&gt;

&lt;p&gt;Distributions: "20% churn" becomes a probability weight.&lt;/p&gt;

&lt;p&gt;Relationships: "Invoices belong to Subscriptions."&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The Logic (Topological Sort)&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;To prevent "Orphaned Rows," Misata builds a Directed Acyclic Graph (DAG) of your tables. It uses Topological Sorting to ensure parent tables (e.g., Users) are generated before child tables (e.g., Orders).&lt;/p&gt;

&lt;p&gt;Python&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# Simplified logic from misata/simulator.py
def topological_sort(self):
    graph = defaultdict(list)
    in_degree = {table.name: 0 for table in self.config.tables}

    for rel in self.config.relationships:
        graph[rel.parent_table].append(rel.child_table)
        in_degree[rel.child_table] += 1

    # Standard Kahn's Algorithm...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;The Muscle (Vectorized NumPy)&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The biggest bottleneck with Python data generation is looping. Generating 10 million rows in a loop is too slow.&lt;/p&gt;

&lt;p&gt;Misata uses Vectorized Operations (via NumPy and Pandas) to generate data in blocks. This allows it to hit speeds of ~250,000 rows/second on a standard laptop.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Features for Data Engineers&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I built this to solve the specific pains I faced in consulting:&lt;/p&gt;

&lt;p&gt;Relational Integrity: It automatically maps Primary Keys to Foreign Keys. No more broken joins in SQL/Tableau.&lt;/p&gt;

&lt;p&gt;No "Time Travel": Child tables (like Timesheets) automatically look up their parent's Start Date to ensure events happen chronologically.&lt;/p&gt;

&lt;p&gt;Business Constraints: You can define rules like "Employees cannot log &amp;gt; 8 hours/day."&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Try it out&lt;/strong&gt;&lt;br&gt;
It's &lt;strong&gt;open source&lt;/strong&gt; and available on PyPI.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;pip install misata
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Generate a dataset:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# Needs GROQ_API_KEY (free tier works great)
misata generate --story "E-commerce store with seasonal spikes in December" --use-llm
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Why I Open Sourced It&lt;/strong&gt;&lt;br&gt;
I know there are enterprise tools out there that cost $10k+/year. But for individual consultants, students, and indie hackers, there was no good "middle ground" between Faker and Enterprise Privacy tools.&lt;/p&gt;

&lt;p&gt;I want Misata to be that middle ground.&lt;/p&gt;

&lt;p&gt;I'm currently working on adding Curve Fitting (so you can draw a chart and get data that matches it). If you're into Data Engineering or Python optimization, I'd love your feedback on the architecture!&lt;/p&gt;

&lt;p&gt;Repo: github.com/rasinmuhammed/misata&lt;/p&gt;

&lt;p&gt;P.S. If you are a consultant stuck in "Demo Data Hell" right now and need a specific scenario generated, drop a comment or DM me. I'm looking for complex edge cases to stress-test the engine.&lt;/p&gt;

</description>
      <category>python</category>
      <category>datascience</category>
      <category>opensource</category>
      <category>database</category>
    </item>
    <item>
      <title>I Built a TUI to Visualize RAG Chunking because chunk_size=1000 is a Lie 📉</title>
      <dc:creator>Muhammed Rasin O M</dc:creator>
      <pubDate>Wed, 10 Dec 2025 15:38:26 +0000</pubDate>
      <link>https://dev.to/rasinmuhammed/i-built-a-tui-to-visualize-rag-chunking-because-chunksize1000-is-a-lie-1dg0</link>
      <guid>https://dev.to/rasinmuhammed/i-built-a-tui-to-visualize-rag-chunking-because-chunksize1000-is-a-lie-1dg0</guid>
      <description>&lt;p&gt;Let’s be honest for a second. When you are building a RAG (Retrieval-Augmented Generation) pipeline, how do you pick your &lt;code&gt;chunk_size&lt;/code&gt; and &lt;code&gt;overlap&lt;/code&gt;?&lt;/p&gt;

&lt;p&gt;If you are like 90% of us, you copy-paste &lt;code&gt;1000&lt;/code&gt; and &lt;code&gt;200&lt;/code&gt; from a tutorial, run it, and hope the LLM doesn't hallucinate.&lt;/p&gt;

&lt;p&gt;I realized I was doing "vibes-based engineering". I had no idea if my chunks were cutting sentences in half, if my overlap was actually preserving context, or if my retrieval was failing because of the embedding model or the chunking strategy.&lt;/p&gt;

&lt;p&gt;So, I spent my nights and weekends building a tool to fix it.&lt;/p&gt;

&lt;p&gt;Meet &lt;strong&gt;RAG-TUI&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;It is an open-source, terminal-based visual debugger for RAG pipelines. It helps you &lt;strong&gt;see&lt;/strong&gt; what your splitters are doing before you index millions of documents.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Problem: "The Black Box"
&lt;/h2&gt;

&lt;p&gt;We treat text splitters like black boxes. You feed in a PDF, and out comes a list of strings. But what do those strings look like?&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Did you just cut a critical definition in half?&lt;/li&gt;
&lt;li&gt;Is your 10% overlap actually capturing the previous sentence?&lt;/li&gt;
&lt;li&gt;Are you feeding your embedding model garbage?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I got tired of printing chunks to the console to debug this. I wanted a UI, but I didn't want to leave my terminal.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Solution: RAG-TUI
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;RAG-TUI&lt;/strong&gt; is a lightweight CLI tool built in Python. You point it at a file, and it gives you an interactive dashboard to tune your indexing strategy in real-time.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fykfvfxujdmv3u2uk80gz.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fykfvfxujdmv3u2uk80gz.gif" alt=" " width="" height=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Key Features (Why you might want this)
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;1. Real-time Visualization&lt;/strong&gt; 🎨&lt;br&gt;
Drag a slider to change the &lt;code&gt;chunk_size&lt;/code&gt;. Watch the text re-chunk instantly.&lt;br&gt;
The UI uses color-coded cards to show you exactly where one chunk ends and the next begins.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Quality Indicators&lt;/strong&gt; 🚦&lt;br&gt;
I added visual "linters" for your chunks:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;🟢 &lt;strong&gt;Green:&lt;/strong&gt; Clean break (ends with &lt;code&gt;.&lt;/code&gt;, &lt;code&gt;!&lt;/code&gt;, &lt;code&gt;?&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;🟡 &lt;strong&gt;Yellow:&lt;/strong&gt; Mid-phrase break (ends with &lt;code&gt;,&lt;/code&gt;, &lt;code&gt;:&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;🔴 &lt;strong&gt;Red:&lt;/strong&gt; Hard cut (ends with a character).&lt;/li&gt;
&lt;li&gt;⚠️ &lt;strong&gt;Warning:&lt;/strong&gt; Chunk is too small (&amp;lt;50 tokens) or too large.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;3. "Scientific" Batch Testing&lt;/strong&gt; &lt;br&gt;
Stop guessing. Enter 20 test queries ("What is the refund policy?", "How do I reset my password?"). RAG-TUI runs them against your current settings using local vector search and calculates a &lt;strong&gt;Hit Rate&lt;/strong&gt;.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;em&gt;Hit Rate &amp;gt; 80%?&lt;/em&gt; Ship it.&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;Hit Rate &amp;lt; 60%?&lt;/em&gt; Your chunks are wrong. Change the strategy.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;4. Privacy First (Ollama Support)&lt;/strong&gt; 🔒&lt;br&gt;
You don't need to send your private docs to OpenAI just to debug a splitter. RAG-TUI has native support for &lt;strong&gt;Ollama&lt;/strong&gt;. You can run the entire debugging loop offline on your laptop.&lt;/p&gt;
&lt;h2&gt;
  
  
  💻 Under the Hood
&lt;/h2&gt;

&lt;p&gt;For the Python nerds (like me), here is the stack that makes this possible:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;UI:&lt;/strong&gt; &lt;a href="https://github.com/Textualize/textual" rel="noopener noreferrer"&gt;Textual&lt;/a&gt; (The best TUI framework, period).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Chunking:&lt;/strong&gt; &lt;a href="https://github.com/chonkie-ai/chonkie" rel="noopener noreferrer"&gt;Chonkie&lt;/a&gt; (Blazing fast token splitting).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Vector DB:&lt;/strong&gt; &lt;a href="https://github.com/unum-cloud/usearch" rel="noopener noreferrer"&gt;Usearch&lt;/a&gt; (Lightweight, in-memory vector search).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;LLM:&lt;/strong&gt; Async wrapper for Ollama/OpenAI.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  🚀 How to Try It
&lt;/h2&gt;

&lt;p&gt;I tried to make the onboarding as painless as possible.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip &lt;span class="nb"&gt;install &lt;/span&gt;rag-tui
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then, just run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;rag-tui
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;(Make sure you have &lt;code&gt;ollama serve&lt;/code&gt; running if you want to test embeddings!)&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  🤝 I Need Your Feedback!
&lt;/h2&gt;

&lt;p&gt;This is currently in &lt;strong&gt;v0.0.2 Beta&lt;/strong&gt;. It works, but I know there are edge cases I haven't found yet.&lt;/p&gt;

&lt;p&gt;I am building this in public because I believe RAG tooling needs to get better. If you are learning RAG or building a production pipeline, please give this a spin.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Does it support your weird PDF format?&lt;/li&gt;
&lt;li&gt;Do you need a specific splitter I haven't added?&lt;/li&gt;
&lt;li&gt;Is the TUI crashing on Windows? (It shouldn't, but you know... Windows).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Star the repo if you think this is useful. It motivates me to keep shipping updates!&lt;/strong&gt; ⭐️&lt;/p&gt;

&lt;p&gt;🔗 &lt;strong&gt;GitHub Repo:&lt;/strong&gt; &lt;a href="https://github.com/rasinmuhammed/rag-tui" rel="noopener noreferrer"&gt;https://github.com/rasinmuhammed/rag-tui&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Happy Chunking! ✂️&lt;/p&gt;

</description>
      <category>rag</category>
      <category>python</category>
      <category>machinelearning</category>
      <category>opensource</category>
    </item>
    <item>
      <title>I built a FastAPI admin panel that doesn't suck (and here's why it's different)</title>
      <dc:creator>Muhammed Rasin O M</dc:creator>
      <pubDate>Sat, 06 Dec 2025 13:56:15 +0000</pubDate>
      <link>https://dev.to/rasinmuhammed/i-built-a-fastapi-admin-panel-that-doesnt-suck-and-heres-why-its-different-57ej</link>
      <guid>https://dev.to/rasinmuhammed/i-built-a-fastapi-admin-panel-that-doesnt-suck-and-heres-why-its-different-57ej</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;TL;DR:&lt;/strong&gt; FastAPI Matrix Admin combines one-line auto-discovery, async-first architecture, and production-grade security in a package that requires &lt;strong&gt;zero Node.js&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;🟢 &lt;strong&gt;&lt;a href="https://fastapi-matrix-admin-demo.onrender.com/admin/" rel="noopener noreferrer"&gt;Live Demo&lt;/a&gt;&lt;/strong&gt; (Read-only)&lt;br&gt;
💻 &lt;strong&gt;&lt;a href="https://github.com/rasinmuhammed/fastapi-matrix-admin" rel="noopener noreferrer"&gt;Github Repo&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  The admin panel problem nobody talks about
&lt;/h2&gt;

&lt;p&gt;Every FastAPI project follows the same arc:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; You build a great API.&lt;/li&gt;
&lt;li&gt; Product wants to "just update a few records manually."&lt;/li&gt;
&lt;li&gt; You reluctantly install Django admin (now you have two frameworks).&lt;/li&gt;
&lt;li&gt; Or you build a custom React dashboard (6 weeks later...).&lt;/li&gt;
&lt;li&gt; Or you use a generic admin and spend days configuring it.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;I've done all three. They all sucked for different reasons.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's wrong with existing FastAPI admin solutions?
&lt;/h2&gt;

&lt;p&gt;I evaluated every major option before building this. Here's what I found:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Library&lt;/th&gt;
&lt;th&gt;Issue&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;FastAPI-Admin&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Requires Tortoise ORM (can't use SQLAlchemy).&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;SQLAdmin&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Good, but sync-only. No async support in 2024?&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Starlette-Admin&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Heavy Starlette dependency, limited FastAPI integration.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Admin-One&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Requires Vue.js build step, defeats FastAPI's simplicity.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;The pattern:&lt;/strong&gt; They either force you into specific ORMs, ignore async, or add frontend build complexity.&lt;/p&gt;




&lt;h2&gt;
  
  
  What I Built Instead
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;FastAPI Matrix Admin&lt;/strong&gt; focuses on three non-negotiables:&lt;/p&gt;

&lt;h3&gt;
  
  
  1. One-Line Auto-Discovery (Because Your Time Matters)
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;fastapi_matrix_admin&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;MatrixAdmin&lt;/span&gt;

&lt;span class="n"&gt;admin&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;MatrixAdmin&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;engine&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;engine&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;secret_key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;...&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;admin&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;auto_discover&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Base&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# Done. All models registered.
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Under the hood:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Introspects SQLAlchemy models using &lt;code&gt;inspect()&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Analyzes column types to generate appropriate form fields.&lt;/li&gt;
&lt;li&gt;Detects text columns for search.&lt;/li&gt;
&lt;li&gt;Finds timestamp columns for default ordering.&lt;/li&gt;
&lt;li&gt;Creates a sensible list display based on column types.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Customization when you need it:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;admin&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;register&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;User&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;list_display&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;email&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;created_at&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="n"&gt;searchable_fields&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;email&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;name&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="n"&gt;ordering&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;-created_at&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="n"&gt;exclude&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;password_hash&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;  &lt;span class="c1"&gt;# Obviously
&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  2. Zero Node.js (Seriously)
&lt;/h3&gt;

&lt;p&gt;No npm. No webpack. No &lt;code&gt;package.json&lt;/code&gt;.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Stack:&lt;/strong&gt; Tailwind CSS via CDN, HTMX for dynamic updates, Alpine.js (3KB), and Jinja2 templates.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Why this matters:&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;pip install fastapi-matrix-admin&lt;/code&gt; → you're done.&lt;/li&gt;
&lt;li&gt;No build step in CI/CD.&lt;/li&gt;
&lt;li&gt;No frontend/backend version mismatches.&lt;/li&gt;
&lt;li&gt;Deploys anywhere Python runs.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;h3&gt;
  
  
  3. Production-Grade Security (Not an Afterthought)
&lt;/h3&gt;

&lt;p&gt;Most admin libraries treat security as optional. Here is what is built-in:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Content Security Policy (CSP):&lt;/strong&gt; Prevents XSS by strictly controlling script sources.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;CSRF Protection:&lt;/strong&gt; Every form gets a signed token automatically.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;URL Signing:&lt;/strong&gt; All admin URLs are cryptographically signed to prevent ID enumeration/tampering.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Type Safety with Pydantic v2:&lt;/strong&gt; Input validation happens automatically.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Security Comparison:&lt;/strong&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Feature&lt;/th&gt;
&lt;th&gt;FastAPI Matrix Admin&lt;/th&gt;
&lt;th&gt;SQLAdmin&lt;/th&gt;
&lt;th&gt;FastAPI-Admin&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;CSP Headers&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;✅ Built-in&lt;/td&gt;
&lt;td&gt;❌ Manual&lt;/td&gt;
&lt;td&gt;❌ Manual&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;CSRF Protection&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;✅ Automatic&lt;/td&gt;
&lt;td&gt;⚠️ Optional&lt;/td&gt;
&lt;td&gt;❌ None&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;URL Signing&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;✅ Yes&lt;/td&gt;
&lt;td&gt;❌ No&lt;/td&gt;
&lt;td&gt;❌ No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Pydantic v2&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;✅ Yes&lt;/td&gt;
&lt;td&gt;⚠️ v1&lt;/td&gt;
&lt;td&gt;❌ No validation&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  Performance: Async All The Way Down
&lt;/h2&gt;

&lt;p&gt;Full async support isn't optional in 2024.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Async SQLAlchemy 2.0
&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;sqlalchemy.ext.asyncio&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;create_async_engine&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;AsyncSession&lt;/span&gt;

&lt;span class="n"&gt;engine&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;create_async_engine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;postgresql+asyncpg://...&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;admin&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;MatrixAdmin&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;engine&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;engine&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Simple benchmark (100 concurrent list view requests):&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;FastAPI Matrix Admin (async):&lt;/strong&gt; ~50ms avg&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;SQLAdmin (sync):&lt;/strong&gt; ~180ms avg (blocks other requests)&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The Matrix UI (Yeah, It's Different)
&lt;/h2&gt;

&lt;p&gt;I'm not going to pretend the cyberpunk aesthetic is for everyone. But here's why it exists:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Problem:&lt;/strong&gt; Every admin panel looks the same. Generic Bootstrap tables. Boring gray sidebars. No personality.&lt;br&gt;
&lt;strong&gt;Solution:&lt;/strong&gt; Terminal-style green/black theme with neon accents.&lt;/p&gt;

&lt;p&gt;It makes internal tools feel less corporate, and stakeholders actually remember seeing "that Matrix admin thing." (If you hate it, the CSS variables are customizable).&lt;/p&gt;


&lt;h2&gt;
  
  
  Quick Start
&lt;/h2&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;pip install fastapi-matrix-admin
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;fastapi&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;FastAPI&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;sqlalchemy&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Column&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Integer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Boolean&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;sqlalchemy.ext.asyncio&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;create_async_engine&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;sqlalchemy.orm&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;declarative_base&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;fastapi_matrix_admin&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;MatrixAdmin&lt;/span&gt;

&lt;span class="n"&gt;Base&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;declarative_base&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;User&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Base&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;__tablename__&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;users&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="nb"&gt;id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Column&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Integer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;primary_key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;email&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Column&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;unique&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;is_active&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Column&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Boolean&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;default&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;FastAPI&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;engine&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;create_async_engine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;sqlite+aiosqlite:///./database.db&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# That's it
&lt;/span&gt;&lt;span class="n"&gt;admin&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;MatrixAdmin&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;engine&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;engine&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;secret_key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;your-secret-key-min-32-chars&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;admin&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;auto_discover&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Base&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Run it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;uvicorn app:app
# Visit http://localhost:8000/admin
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  What's Next
&lt;/h2&gt;

&lt;p&gt;Current roadmap:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;[ ] File/image upload support&lt;/li&gt;
&lt;li&gt;[ ] Advanced filters (date ranges, multi-select)&lt;/li&gt;
&lt;li&gt;[ ] Export to CSV/Excel&lt;/li&gt;
&lt;li&gt;[ ] Custom dashboard widgets&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://dev.toMaybe"&gt; &lt;/a&gt; light theme for the Matrix-haters&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Final Thoughts
&lt;/h2&gt;

&lt;p&gt;I built this because I kept rebuilding the same admin panel over and over. Auto-discovery saves me hours per project. Zero Node.js means simple deploys. The security features mean I can actually use this in production.&lt;/p&gt;

&lt;p&gt;If you try it, let me know what breaks. Or what you wish it did differently.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Questions I'd love feedback on:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; Is auto-discovery too magical, or genuinely useful?&lt;/li&gt;
&lt;li&gt; What security features am I missing?&lt;/li&gt;
&lt;li&gt; Would you actually use this in production?&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Drop a comment or open an issue. First-time contributors welcome!&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://github.com/rasinmuhammed/fastapi-matrix-admin" rel="noopener noreferrer"&gt;Github: fastapi-matrix-admin&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>python</category>
      <category>fastapi</category>
      <category>opensource</category>
    </item>
  </channel>
</rss>
