# Constraint Generation

Automatically discovering constraints from existing data, then enforcing them on future data.

**Rests on:** Data Validation Constraints

*Data Engineering and Analytics · Data Quality*


## Why learn this

Constraint generation is the engine that makes data quality checks practical. Once you master it, you can automatically discover the rules that your data must obey — without writing hundreds of rules by hand — and then enforce those rules on every new batch of data. This unlocks three neighbouring topics in your library: **Test-Driven Development** for data (where constraints become your tests), **Data Governance** (where the discovered constraints are your documented standards), and **Model Monitoring** (where the same constraints catch drift in production). You'll also be able to build a **Data Dictionary** that writes itself, because the constraints you generate describe your fields' valid values and ranges. Every AI system, from a small phone app to a large language model, depends on data that obeys constraints — this is how you ensure that.


## The intuition

Imagine you are a new quality inspector at a factory that makes metal bolts. On your first day, you are handed a box of a thousand bolts and told: 'Figure out what a good bolt looks like.' You don't have a spec sheet. So you measure every bolt — its length, its diameter, its weight — and you record the numbers. After measuring all thousand, you notice that the lengths are all between 10.0 mm and 10.2 mm, the diameters are all between 5.0 mm and 5.1 mm, and the weights never go below 3.5 g or above 3.8 g. From those observations, you write a rule: 'A bolt is good if its length is in [10.0, 10.2], its diameter is in [5.0, 5.1], and its weight is in [3.5, 3.8].' From now on, any new bolt that falls outside those ranges is rejected. You didn't invent the rules out of thin air — you discovered them from the data. That is constraint generation: looking at existing data, noticing the patterns and limits, and turning them into rules that future data must satisfy. The key insight is that you don't need a human to write the rules; the data itself tells you what is 'normal'. But — and this is the crucial part — you need to review the rules before you enforce them, because the data you observed might be a narrow sample that doesn't show the full range of what is acceptable.


## An analogy

Think of a teacher grading a new batch of essays. The teacher has never seen this class before, so they read the first ten essays to get a feel for the level — what kind of vocabulary, sentence length, and structure is typical. From those ten essays, the teacher extracts a mental rubric: 'Essays are between 500 and 800 words, use formal language, and have at least three paragraphs.' Now the teacher can grade the remaining essays quickly by checking them against this rubric. If an essay comes in at 300 words, it gets flagged. But is it a bad essay? Maybe the assignment explicitly asked for a short essay. This is where the analogy breaks down: the teacher's rubric is based on a small sample, and the sample might not represent the full range of acceptable work. In constraint generation, the same danger exists — you might discover constraints that are too tight because your data only covered a slice of the real world. So the process is never fully automatic: you must review the discovered constraints with a human in the loop before you enforce them. The teacher can adjust the rubric after seeing the rest of the essays. Constraint generation works the same way: you discover, you review, you adjust, and then you enforce.


## Definition

Constraint generation is the automatic discovery of rules (constraints) that existing data obeys, followed by human review and then enforcement of those rules on future data.


## How it connects

You have no prior topics in your library yet, so I'm building from zero. But this concept belongs to the **Data Quality** family. It is the mechanism that makes **Data Validation Constraints** practical — you don't write them by hand; you generate them. It also connects to **Data Dictionaries** because the constraints you generate effectively document the field definitions. In your library, you have a note: 'Constraints can be automatically discovered from data and then enforced as tests — 'constraint generation' is the mechanism that makes data TDD practical.' That means constraint generation is what turns data testing from a tedious manual chore into an automated, continuous process.


## The mathematics

The mathematics here is simple — it's mostly about ranges and counts, which are pure arithmetic. You don't need algebra or calculus; you need to count how many times values appear, and you need to find the smallest and largest values. The branch of mathematics involved is statistics, specifically descriptive statistics: you are summarising a set of numbers. The key operations are: finding the minimum and maximum (the bounds of a range), counting how many times a value occurs (for uniqueness constraints), and calculating simple proportions (like '95% of values are within this range'). Let me teach you each operation from nothing. **Minimum and maximum**: given a list of numbers, the minimum is the smallest one, the maximum is the largest. For example, with [3, 7, 2, 9, 4], the minimum is 2 and the maximum is 9. **Counting**: you go through the list and tally how many times each distinct value appears. For [3, 7, 3, 9, 3], the value 3 appears three times. **Proportion**: you divide a count by the total number of items. If 9 out of 10 values are between 0 and 5, the proportion is 9/10 = 0.9, or 90%. These are the only mathematical tools you need to generate constraints. The constraints themselves are simple comparisons: 'this column must be between X and Y' or 'this column must have at least Z distinct values' or 'this column must have no duplicates'. All of them are built from min/max, counts, and proportions.

Let D = {x₁, x₂, ..., xₙ} be a set of n values in a column. Let min(D) be the smallest value, max(D) be the largest value, and distinct(D) be the number of unique values. The generated range constraint is: min(D) ≤ x ≤ max(D) for all future x. The distinct-count constraint is: distinct(D) ≥ k, where k is a threshold (often set to the observed distinct count, or a human-adjusted value). A uniqueness constraint is: distinct(D) = n (i.e., no duplicates).


## A worked example

```
You have a dataset of 10 customer ages (in years): 25, 32, 25, 40, 32, 25, 61, 34, 25, 40. You want to generate constraints: a range for age, a uniqueness check (are duplicates allowed?), and a distinct-count check.

Step 1: List: [25, 32, 25, 40, 32, 25, 61, 34, 25, 40]   # The raw data — ten numbers.
Step 2: Minimum: 25, Maximum: 61   # Scan the list: the smallest is 25, the largest is 61. The range constraint would be 'age between 25 and 61'.
Step 3: Count of each value: 25 appears 4 times, 32 appears 2 times, 40 appears 2 times, 61 appears 1 time, 34 appears 1 time   # Go through the list and tally each distinct value. This gives you the frequency distribution.
Step 4: Total distinct values: 5 (25, 32, 40, 61, 34)   # How many different ages exist in the data. A constraint could be 'at least 5 distinct ages'.
Step 5: Duplicates: 10 - 5 = 5 records are duplicates   # Since the total is 10 and there are 5 distinct values, five records are repeated. A uniqueness constraint would fail — so the constraint 'age is unique' is NOT valid for this data.

Result: From this data, you might generate three constraints: 1) age ∈ [25, 61], 2) age has at least 5 distinct values, 3) age is not unique (allows duplicates). After human review, you might adjust the range to [18, 80] if you know the data should span adult ages, because the observed sample is limited.
```


## From scratch, in Python

```python
import csv
from collections import Counter

# Read data from a CSV file. This is a list of lists.
# Each inner list is a row, and each element is a cell value.
rows = []
with open('customers.csv') as f:
    reader = csv.reader(f)
    header = next(reader)  # e.g. ['age', 'city', 'signup_date']
    for row in reader:
        rows.append(row)

# We'll generate constraints for the 'age' column (index 0)
ages = []
for row in rows:
    value = int(row[0])  # convert the string to an integer
    ages.append(value)

# Find min and max
min_age = min(ages)
max_age = max(ages)

# Count distinct values
counts = Counter(ages)  # a dictionary: value -> number of times it appears
distinct_count = len(counts)

# Detect if there are duplicates
has_duplicates = len(ages) != distinct_count

# Print the discovered constraints
print(f'Range constraint: age between {min_age} and {max_age}')
print(f'Distinct count constraint: at least {distinct_count} distinct ages')
print(f'Uniqueness constraint allowed? {not has_duplicates}')
```


## What the code does

Let's go through the code line by line. First, we import `csv` (to read CSV files) and `Counter` from `collections` (to count things easily). The `csv` module gives us a way to read a file where each row is separated by commas. We open the file 'customers.csv' and create a `reader` object. The first line is the header (the column names), which we store in `header`. Then we loop over the remaining rows, and each row is added to a list called `rows`. So `rows` is a list of lists: each inner list is one row, and each element is a string (since CSV reads everything as text). Next, we create an empty list called `ages`. We loop through each row in `rows` and convert the first element (at index 0) to an integer using `int()`. This assumes the age is in the first column. We append it to `ages`. Now we have a list of integers. The `min(ages)` function finds the smallest value in the list, and `max(ages)` finds the largest. These become the bounds for a range constraint. The `Counter(ages)` function counts how many times each value appears. For example, if `ages` is [25, 32, 25], `Counter` gives {25: 2, 32: 1}. The length of `Counter` (using `len(counts)`) gives the number of distinct values. To check for duplicates, we compare the total number of ages with the number of distinct ages. If they are not equal, there are duplicates. Finally, we print the discovered constraints. A developer would run this on a sample of historical data to discover the constraints, then manually review them before turning them into automated tests for future data.


## Common misconceptions

**Constraints are always discovered automatically, no human needed** — The computer can look at my data and write all the rules for me. I can just enforce them without thinking.

The data may be biased, incomplete, or contain errors. If you enforce rules learned from a sample that is not representative of the real world, you will reject good data or accept bad data. In fact: Discovery is automatic, but review is mandatory. The generated constraints are a starting point; a human must examine them, adjust them if necessary, and only then enforce them.

**Constraints are static rules that never change** — Once I write a constraint, it's true forever.

Data distributions drift over time. A range that was valid last year may be obsolete next year. The constraints must be periodically regenerated from new data. In fact: Constraints are living rules. You should re-run the generation process on a regular basis (e.g., every new batch of data) and update the constraints after review.

**The more constraints the better** — I should generate as many rules as possible to catch every possible error.

Too many constraints, especially tight ones, will cause many false positives. This makes the system unusable and erodes trust in the validation. In fact: Generate constraints that capture the essential properties of your data, and review them to ensure they are reasonable. Start simple and add constraints only when they catch real errors.


## In short

Constraint generation is the automatic discovery of rules from existing data, followed by human review and then enforcement. You use simple statistics — min/max, counts, distinct counts — to write rules like 'age is between 25 and 61' or 'ID is unique.' These rules become tests that future data must pass. The process makes data quality checks practical without writing hundreds of rules by hand. Remember: discover, review, enforce, and periodically repeat.


## Where this is used

Constraint generation is used throughout the AI industry, but it is most visible in data quality pipelines, which underpin every other model. **In large language models (LLMs):** LLMs are trained on enormous text corpora. Before training, engineers must ensure the data is clean — no duplicate documents, no unnatural tokens, no format errors. Constraint generation helps automate the discovery of such rules. For example, an engineer can run an automated scan over a sample of documents and discover that every document starts with a header, or that the average sentence length is between 10 and 20 words. These become constraints that the preprocessing pipeline enforces on the full dataset. If a new batch of scraped web text violates these constraints, it is flagged for review or removed. The mechanism is: you sample a small portion of the data, generate constraints (e.g., min/max length, expected character set, pattern for timestamps), then apply them to the rest of the data to filter out anomalies. A developer might use a library like `great_expectations` to define expectations, but underneath it works like the plain Python code we wrote: compute min/max, count distinct, and apply comparisons. **In small models running on devices (edge AI):** On a phone, a model might process sensor data (e.g., heart rate, accelerometer readings). Before inference, the mobile app can check if the incoming data point falls within the range observed during training. If a heart rate reading is 500 bpm, that's clearly a sensor error. Such constraints are often generated from a sample of real sensor data during development and then hardcoded into the app as simple if-checks. This prevents the model from processing garbage input and producing nonsense outputs. **In retrieval-augmented generation (RAG):** RAG systems pull relevant documents from a database to help an LLM answer questions. The quality of answers depends on the quality of the retrieved chunks. Constraint generation can help maintain the document index: for example, an index might have a constraint that every document ID is unique, or that every embedding vector has a magnitude within a certain range. These constraints are discovered from the initial index build and then enforced whenever new documents are added. If a new document violates the constraint (e.g., duplicate ID), it is rejected. The mechanism: before adding a new document, run a check like 'is the ID already in the set of existing IDs?' — which is exactly the uniqueness constraint we generated. **In agentic systems that plan, call tools, and act in loops:** An agent might call a weather API, a calendar API, or a database query. The responses come back as data. Constraint generation can be used to validate the responses before the agent acts on them. For instance, an engineer might generate constraints on the response format (JSON schema with required fields, value ranges) from a sample of API responses. Then, at runtime, every response is validated against those constraints. If the API starts returning malformed data (e.g., a temperature of -999), the agent can detect it and retry or fail gracefully. The developer would write a validation function that checks the required fields and ranges, much like the min/max checks from our code. **In everyday applied machine learning (recommendation, vision, forecasting, fraud detection, speech):** Every one of these uses data pipelines. Constraint generation is used to create data validation tests. For example, a fraud detection system processes financial transactions. An engineer can sample historical transactions and discover that transaction amounts are between ₹10 and ₹10,00,000, that timestamps follow a certain format, and that each transaction has a unique ID. These become constraints. In production, every new transaction is checked against them; if a transaction has a negative amount, it is flagged as an error before the model even sees it. This is called 'data validation' and is a core part of MLOps. The library most often used for this is **Great Expectations**. A developer writes an expectation suite (a set of constraints), and the library checks the data against it. Under the hood, Great Expectations computes statistics like min, max, distinct counts, and compares them to the thresholds. If you used the plain Python we wrote, you would do the same thing manually. **What breaks when done badly?** If the constraints are too tight (discovered from a narrow sample), you get false alarms: legitimate data gets rejected. If they are too loose, you let bad data through. If you never review the generated constraints, you might enforce a bogus rule — for example, 'age is never greater than 61' in a dataset that was collected from a retirement home. This is why human review is non-negotiable. The `Test-Driven Data Analysis` book (in your library) says: 'Constraints can be automatically discovered from data and then enforced as tests — ‘constraint generation’ is the mechanism that makes data TDD practical.' A developer using this would write a script that generates a set of assertions (e.g., `assert data['age'].between(18, 80)`) and run them in CI/CD.


---

Read this on the web: https://basavapurushottam.com/learn-ai/constraint-generation
