PROMETHEUS OS
NATIVE AI OPERATING SYSTEM
[INIT] Prometheus OS loading...
[CORE] Neural interface initializing...
[AI] LLM router connecting...
[MEM] Memory graph loading...
[AGT] Agent chains on standby...
[SEC] Security protocols active...
[NET] Provider mesh established...
[UI] Workspace rendering...
[DONE] All systems nominal.
Welcome to PROMETHEUS OS.
READY · OFFLINE · SECURE · PRIVATE
🎨 THEME ENGINE
PROMETHEUS OS · 23 THEMES
Trial version: Dark + Light themes only.
Full theme access with PROMETHEUS OS license.
Dark theme active
Experience all 23 themes in PROMETHEUS OS

Data Engineering for AI: Pipelines, Quality, Labeling, Features and Privacy

Advanced 16 min· 4 exercises

Build the data foundation for ML systems: design batch and streaming pipelines, enforce data quality, run labeling with measured agreement, serve point-in-time-correct features, track lineage, protect PII and document datasets.

By the end of this lesson you will be able to
  • Compare batch and streaming pipelines and ETL vs ELT, and design idempotent, backfillable pipelines.
  • Implement data quality checks and explain how data contracts prevent silent failures.
  • Run a labeling workflow and compute inter-annotator agreement with Cohen's kappa.
  • Explain feature stores and implement a point-in-time-correct join that avoids label leakage.
  • Apply PII detection, redaction and pseudonymization in pipelines, and document a dataset with a datasheet.

Before this: The ML Lifecycle, Reproducibility and Experiment Tracking, The ML Workflow: Framing, Data, Splits and Leakage, Feature Engineering and Preprocessing Pipelines

Models are only as good as the data that reaches them, and in production most ML incidents trace back to data: a renamed column, a silently changed unit, a label that leaked from the future, a batch that arrived twice. Data engineering for AI is the work of moving data reliably from where it is produced to where models train and predict, while keeping it correct, traceable, lawful and documented. It is often the largest part of an ML team's effort.

Intuition: a water utility

Think of a city water system. Water is collected from many sources (applications, sensors, logs, vendors), carried through pipes (pipelines), treated (cleaned, validated, de-identified), stored in reservoirs (warehouses and lakes), and delivered to taps (training jobs and online models). Treatment plants test samples constantly, because by the time contamination reaches a tap it is too late. Every pipe has a map so engineers can trace a problem upstream. And there is a published water-quality report so consumers know what they are drinking.

Each piece has a counterpart in this lesson: pipelines, quality checks, storage, serving, lineage, and dataset documentation.

Pipelines: batch and streaming

Batch pipelines process bounded chunks of data on a schedule: "every night, compute yesterday's features". They are simpler, cheaper and easier to backfill. Streaming pipelines process unbounded event streams continuously with low latency: "update a user's fraud features within seconds of each transaction". Typical building blocks: event logs such as Apache Kafka; processing engines such as Apache Spark (batch and micro-batch) and Apache Flink (stream-first); orchestrators such as Apache Airflow or Dagster that schedule and monitor pipeline steps.

ConcernBatchStreaming
LatencyMinutes to hoursMilliseconds to seconds
ComplexityLowerHigher (state, ordering, late events)
Correctness toolsRe-run partitionsEvent-time windows, watermarks, exactly-once sinks
Typical ML useTraining sets, daily features, batch scoringReal-time features, online scoring, monitoring

Many systems combine both: streaming for fresh online features and batch for training and reconciliation. The danger is two separate code paths computing "the same" feature differently, which causes training-serving skew.

ETL vs ELT

  • ETL (extract, transform, load): transform before loading into the warehouse. Used when storage was expensive or data must be cleaned or de-identified before landing.
  • ELT (extract, load, transform): load raw data into a scalable warehouse or lakehouse first, then transform with SQL (tools like dbt). Keeps raw history for reprocessing; now the common default.

For AI, a layered layout is common: raw (immutable, as received), cleaned/conformed (typed, deduplicated, validated), and curated (features, training sets). Sensitive data may need to be redacted before the raw layer if policy forbids storing it.

Pipeline engineering principles

  • Idempotency: re-running a job for the same partition produces the same result and no duplicates (overwrite the partition, or upsert on a key).
  • Partitioning by time so that backfills and reprocessing are cheap and targeted.
  • Event time vs processing time: compute features by when events happened, not when they arrived; handle late data explicitly.
  • Schema evolution: additive changes are safe; renames and type changes need versioning and coordination.
  • Delivery semantics: at-least-once delivery plus idempotent writes is the usual practical route to effectively-once results.

Data quality and validation

Dimensions

  • Completeness: required fields present; expected row counts.
  • Validity: types, ranges, allowed categories, formats.
  • Uniqueness: no duplicate keys or events.
  • Consistency: values agree across tables (every order has a customer).
  • Timeliness: data arrives within its freshness window.
  • Accuracy: values match reality, usually checked by sampling and audits.

Checks and contracts

Validation should run at pipeline boundaries and fail loudly. Libraries such as Great Expectations, pandera and TensorFlow Data Validation let you declare expectations; the logic is simple enough to see in plain pandas:

import pandas as pd

def validate(df: pd.DataFrame, ref_null_rate: dict, max_null_drift=0.02):
    errors = []
    required = {"user_id", "event_time", "amount", "country"}
    missing = required - set(df.columns)
    if missing:
        return [f"missing columns: {sorted(missing)}"]
    if df["user_id"].isna().any():
        errors.append("user_id has nulls")
    if df.duplicated(["user_id", "event_time"]).any():
        errors.append("duplicate (user_id, event_time) rows")
    if not df["amount"].between(0, 50_000).all():
        errors.append("amount outside [0, 50000]")
    bad = set(df["country"].dropna()) - {"US", "CA", "MX", "GB", "DE"}
    if bad:
        errors.append(f"unexpected country codes: {sorted(bad)}")
    for col, ref in ref_null_rate.items():
        rate = df[col].isna().mean()
        if abs(rate - ref) > max_null_drift:
            errors.append(f"{col} null rate {rate:.3f} vs reference {ref:.3f}")
    return errors

df = pd.DataFrame({"user_id": [1, 2, 2], "event_time": ["t1", "t2", "t2"],
                   "amount": [20.0, -5.0, 13.0], "country": ["US", "XX", None]})
print(validate(df, ref_null_rate={"country": 0.01}))

Running it reports the duplicate row, the negative amount, the unknown country code and the null-rate jump (0.333 vs 0.01). Note the null-rate drift check: distribution checks against a reference catch silent upstream changes that schema checks miss.

A data contract is an explicit, versioned agreement between a data producer and its consumers about schema, semantics, quality thresholds and freshness, enforced in CI and at runtime. Contracts turn "the app team renamed a field and the model broke three weeks later" into a failed build on the day of the change.

Labeling workflows

Supervised learning needs labels, and label quality caps model quality. A professional labeling workflow:

  1. Guidelines: a written spec with definitions, edge cases and examples for each label. Iterate on it.
  2. Pilot: several annotators label the same small batch; measure agreement; revise guidelines where they disagree.
  3. Production labeling with gold items (known answers) mixed in to monitor annotator accuracy, and a fraction of items labeled by two or more annotators to keep measuring agreement.
  4. Adjudication: an expert resolves disagreements; disagreements also reveal ambiguous guidelines or genuinely ambiguous items.
  5. Efficiency: active learning prioritizes items the model is most uncertain about; model-assisted pre-labeling (including LLM-generated draft labels) speeds annotators up but can bias them toward the suggestion, so audit a sample without pre-labels.
  6. Ethics and operations: fair pay and working conditions, especially for distressing content; access control for sensitive data.

Cohen's kappa

Raw percent agreement overstates reliability because annotators agree some of the time by chance. Cohen's kappa (Cohen, 1960) corrects for chance agreement between two annotators:

κ=po−pe1−pe,\kappa = \frac{p_o - p_e}{1 - p_e},

where pop_o is the observed proportion of agreement and pep_e is the agreement expected by chance given each annotator's label frequencies:

pe=∑kpA,k pB,k,p_e = \sum_{k} p_{A,k}\, p_{B,k},

with pA,kp_{A,k} the fraction of items annotator A assigned to class kk (likewise B). κ=1\kappa = 1 is perfect agreement, κ=0\kappa = 0 is chance level, and negative values are worse than chance.

Worked example: two annotators labeling 100 messages as spam or ham

B: spamB: hamTotal (A)
A: spam401050
A: ham54550
Total (B)4555100
  1. Observed agreement: po=(40+45)/100=0.85p_o = (40 + 45)/100 = 0.85.
  2. Label frequencies: A says spam 50%, ham 50%; B says spam 45%, ham 55%.
  3. Chance agreement: pe=0.50×0.45+0.50×0.55=0.225+0.275=0.50p_e = 0.50 \times 0.45 + 0.50 \times 0.55 = 0.225 + 0.275 = 0.50.
  4. Kappa: κ=(0.85−0.50)/(1−0.50)=0.35/0.50=0.70\kappa = (0.85 - 0.50)/(1 - 0.50) = 0.35/0.50 = 0.70.

85% raw agreement becomes κ=0.70\kappa = 0.70 once chance is removed. A commonly cited (and admittedly arbitrary) scale by Landis and Koch (1977) calls 0.61-0.80 "substantial" agreement. In practice, judge kappa against your task: for subjective labels like toxicity, 0.6-0.7 may be good; for factual labels, it signals unclear guidelines.

The same computation with scikit-learn:

from sklearn.metrics import cohen_kappa_score

a = ["spam"] * 50 + ["ham"] * 50
b = ["spam"] * 40 + ["ham"] * 10 + ["spam"] * 5 + ["ham"] * 45
print(round(cohen_kappa_score(a, b), 3))   # 0.7

Kappa is sensitive to class imbalance: with very rare positives, high raw agreement can coexist with low kappa. For more than two annotators use Fleiss' kappa; for missing ratings or ordinal/interval labels use Krippendorff's alpha; for ordinal scales, weighted kappa credits near-misses.

Feature stores

A feature store manages features as reusable, versioned assets with two faces:

  • An offline store (warehouse or lake tables) with full history, used to build training sets.
  • An online store (a low-latency key-value database) holding the latest feature values per entity, used at inference.

Definitions are written once and materialized to both, which reduces training-serving skew and lets teams share features. Examples include the open-source Feast and features built into commercial ML and data platforms. A feature store is worth its operational cost when multiple models share features or when real-time features are needed; a single batch model often does fine with well-managed warehouse tables.

Point-in-time correctness

The most important job of the offline store is point-in-time-correct training data: for each training example (an entity and a timestamp), join each feature's value as it was known at that time, never a later value. Joining the latest values leaks the future into training, producing models that look excellent offline and fail in production.

import pandas as pd

labels = pd.DataFrame({
    "user_id": [1, 1, 2],
    "ts": pd.to_datetime(["2026-03-01", "2026-03-10", "2026-03-05"]),
    "churned": [0, 1, 0]})
features = pd.DataFrame({
    "user_id": [1, 1, 1, 2],
    "ts": pd.to_datetime(["2026-02-20", "2026-03-05", "2026-03-12", "2026-03-01"]),
    "logins_30d": [12, 4, 0, 7]})

train = pd.merge_asof(labels.sort_values("ts"), features.sort_values("ts"),
                      on="ts", by="user_id", direction="backward")
print(train)

merge_asof with direction="backward" picks, for each label row, the latest feature row at or before its timestamp. User 1 on 2026-03-10 gets logins_30d = 4 (from 03-05), not the 0 recorded on 03-12, which would leak the post-churn inactivity. In production, also account for feature availability lag: if a feature lands two hours after the event, shift its timestamps by two hours before joining.

Data versioning and lineage

  • Versioning: immutable snapshots of training sets (DVC, lakeFS, or table formats with time travel such as Delta Lake and Apache Iceberg), referenced by every training run (see the previous lesson).
  • Lineage: a graph of which jobs read and wrote which datasets, column by column where possible. Open standards such as OpenLineage let orchestrators and engines emit lineage events to a common catalog. Lineage answers impact questions ("which models consume this column we want to drop?") and audit questions ("which sources fed model v17?").
  • Retention and deletion: lineage is also what makes it feasible to honor deletion requests across derived datasets.

Privacy in pipelines

Detecting PII

Personally identifiable information (PII) includes direct identifiers (names, emails, phone numbers, government IDs, account numbers) and quasi-identifiers that identify people in combination (ZIP code, birth date, gender). Detection approaches:

  • Pattern rules (regular expressions with checksums, such as the Luhn check for card numbers) for structured identifiers: precise but brittle.
  • Named-entity recognition models for names, locations and organizations in free text.
  • Frameworks combining both, such as Microsoft Presidio.

No detector is perfect; measure recall on a labeled sample of your own data.

Redaction strategies

  • Masking/removal: replace with a placeholder like [EMAIL]. Simplest; destroys linkage.
  • Pseudonymization: replace with a consistent token derived by a keyed hash (HMAC) so records can still be joined, while the key is stored separately with strict access. Under laws such as the EU GDPR, pseudonymized data remains personal data for anyone who can re-identify it, including the organization holding the key.
  • Generalization: coarsen quasi-identifiers (age to a 10-year band, ZIP to its first three digits).
  • Minimization: the best protection is not collecting or keeping fields the model does not need.
import hashlib, hmac, os, re

KEY = os.environ.get("PSEUDO_KEY", "dev-only-key").encode()  # load from a secret manager in production
EMAIL = re.compile(r"[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}")
PHONE = re.compile(r"\+?\d[\d\s().-]{7,}\d")

def pseudonym(value: str) -> str:
    digest = hmac.new(KEY, value.lower().encode(), hashlib.sha256).hexdigest()
    return f"<EMAIL_{digest[:10]}>"

def redact(text: str) -> str:
    text = EMAIL.sub(lambda m: pseudonym(m.group()), text)
    return PHONE.sub("[PHONE]", text)

print(redact("Contact Ana at ana.r@example.com or +1 (214) 555-0199."))

Emails become consistent pseudonyms (the same address always maps to the same token, enabling joins), and phone numbers are masked. The name "Ana" is not caught, which illustrates why free text also needs an NER-based detector. Also scrub logs, prompts and model outputs: LLM applications often leak PII through these side channels.

Dataset documentation: datasheets

Gebru et al.'s "Datasheets for Datasets" proposes that every dataset ship with a document answering questions in seven areas:

  1. Motivation: why and by whom was it created, and who funded it?
  2. Composition: what do instances represent, how many, what is missing, does it contain sensitive or personal data?
  3. Collection process: how was data acquired, over what time frame, with what consent?
  4. Preprocessing, cleaning and labeling: what was done, and is the raw data available?
  5. Uses: what has it been used for, and what uses should be avoided?
  6. Distribution: how and under what license is it shared?
  7. Maintenance: who maintains it, how are errors reported, how is it versioned?

Related formats include Data Statements for NLP and Data Cards. Treat the datasheet as a living artifact versioned with the data; regulators and customers increasingly ask for this information (see AI governance frameworks).

Common pitfalls

  • Label leakage through time: joining latest feature values instead of point-in-time values.
  • Two code paths for training and serving features, producing skew.
  • Non-idempotent jobs that duplicate data on retries.
  • Validating schema only, missing distribution shifts (null rates, category frequencies, units).
  • Trusting percent agreement without chance correction; ignoring disagreement as a signal of ambiguous guidelines.
  • Regex-only PII detection on free text; forgetting logs and prompts.
  • Undocumented datasets whose provenance, consent and license are unknown when a customer or regulator asks.

In practice

  • Start with a layered ELT layout, time-partitioned, idempotent jobs and validation at each boundary.
  • Write data contracts for the sources your models depend on and enforce them in the producers' CI.
  • Run a labeling pilot, report Cohen's kappa (or Krippendorff's alpha), and revise guidelines until agreement is acceptable for the task before scaling.
  • Build training sets with point-in-time joins, whether or not you adopt a feature store.
  • Minimize, detect and pseudonymize PII as early in the pipeline as possible, and test detector recall.
  • Version every training snapshot, emit lineage, and ship a datasheet with each dataset.

Key takeaways

  • Reliable AI data pipelines are idempotent, time-partitioned and validated at every boundary, whether batch or streaming, ETL or ELT.
  • Data quality checks should cover distributions as well as schema, and data contracts make producers accountable for breaking changes.
  • Cohen's kappa corrects raw agreement for chance: in the worked example, 85% agreement is kappa = 0.70.
  • Point-in-time-correct joins, often provided by a feature store's offline store, prevent leakage and training-serving skew.
  • Privacy and documentation are pipeline features: minimize and pseudonymize PII early, track lineage, and ship a datasheet with each dataset.

Terms in this lesson

ELT
Extract, load, transform: loading raw data into a warehouse or lakehouse first and transforming it there.
Idempotent pipeline
A job that produces the same result without duplicates no matter how many times it is re-run for the same input.
Data contract
A versioned agreement between data producers and consumers specifying schema, semantics, quality and freshness.
Cohen's kappa
A chance-corrected measure of agreement between two annotators, (p_o - p_e)/(1 - p_e).
Feature store
A system that defines features once and serves them consistently from an offline store for training and an online store for inference.
Point-in-time correctness
Joining each training example only with feature values that were available at that example's timestamp.
Training-serving skew
A mismatch between features or data processing at training time and at inference time.
Pseudonymization
Replacing identifiers with consistent tokens, such as keyed hashes, that cannot be reversed without separately held information.
Datasheet for a dataset
Structured documentation of a dataset's motivation, composition, collection, processing, uses, distribution and maintenance.

Check your understanding

  1. 1.

    Two annotators agree on 90 of 100 items. Each labels 80% of items positive. What is Cohen's kappa?

  2. 2.

    A churn model scores 0.97 AUC offline but performs poorly in production. Training features were joined using each user's current values. What is the most likely cause?

  3. 3.

    Which approach keeps records joinable while removing raw email addresses from a training table?

  4. 4.

    A nightly batch job was retried after a timeout and the training table now has duplicate rows for that day. What design principle was missing?

  5. 5.

    You need fraud features updated within seconds of each card transaction, plus daily training sets. Which architecture fits best?

Exercises

Work each problem before opening the solution.

  1. Exercise 1Core

    Three-class sentiment labels from two annotators on 200 reviews:

    B: posB: neuB: neg
    A: pos60105
    A: neu84012
    A: neg21053

    Compute pop_o, pep_e and Cohen's kappa.

    Show solution

    Row totals (A): pos 75, neu 60, neg 65. Column totals (B): pos 70, neu 60, neg 70. Total 200.

    po=(60+40+53)/200=153/200=0.765p_o = (60 + 40 + 53)/200 = 153/200 = 0.765.

    pe=(75/200)(70/200)+(60/200)(60/200)+(65/200)(70/200)=0.375×0.35+0.30×0.30+0.325×0.35=0.13125+0.09+0.11375=0.335p_e = (75/200)(70/200) + (60/200)(60/200) + (65/200)(70/200) = 0.375 \times 0.35 + 0.30 \times 0.30 + 0.325 \times 0.35 = 0.13125 + 0.09 + 0.11375 = 0.335.

    κ=(0.765−0.335)/(1−0.335)=0.43/0.665≈0.647\kappa = (0.765 - 0.335)/(1 - 0.335) = 0.43/0.665 \approx 0.647.

    Most disagreement involves neutral (neu/neg 12 and 10), suggesting the guidelines need clearer rules for mildly negative reviews.

  2. Exercise 2Core

    Write a function kappa(a, b) in plain Python (no libraries) that computes Cohen's kappa for two equal-length label lists, and check it against the lesson's spam/ham example.

    Show solution
    from collections import Counter
    
    def kappa(a, b):
        assert len(a) == len(b) and a
        n = len(a)
        p_o = sum(x == y for x, y in zip(a, b)) / n
        ca, cb = Counter(a), Counter(b)
        p_e = sum((ca[k] / n) * (cb[k] / n) for k in set(ca) | set(cb))
        if p_e == 1:
            return 1.0  # both annotators used a single identical label
        return (p_o - p_e) / (1 - p_e)
    
    a = ["spam"] * 50 + ["ham"] * 50
    b = ["spam"] * 40 + ["ham"] * 10 + ["spam"] * 5 + ["ham"] * 45
    print(round(kappa(a, b), 3))  # 0.7
    

    It computes observed agreement, the chance agreement from each annotator's marginal frequencies, and applies the formula; the result matches the worked example (0.70).

  3. Exercise 3Core

    Labels arrive at times 2026-04-01 (user 7) and 2026-04-15 (user 7). Feature avg_basket_30d for user 7 has values 52.0 at 2026-03-25, 48.0 at 2026-04-10 and 15.0 at 2026-04-20. The feature is published with a 2-day lag (a value timestamped T becomes available at T + 2 days). What value should each label row receive, and why?

    Show solution

    Shift feature timestamps by the 2-day availability lag: 52.0 available 2026-03-27, 48.0 available 2026-04-12, 15.0 available 2026-04-22.

    • Label at 2026-04-01: latest available value at or before 04-01 is 52.0 (available 03-27).
    • Label at 2026-04-15: latest available value is 48.0 (available 04-12). The 15.0 value is not available until 04-22 and must not be used.

    In pandas: features["ts"] += pd.Timedelta(days=2) and then merge_asof(..., direction="backward"). Ignoring the lag happens to give the same values for these two rows, but it would wrongly assign 48.0 to any label dated 2026-04-10 or 2026-04-11, before that value was actually available.

  4. Exercise 4Challenge

    Design the data pipeline for a support-ticket classifier trained on customer emails. Cover: ingestion and storage layers, PII handling (what, where, how), validation checks, labeling plan with agreement targets, dataset versioning and lineage, and the sections of a datasheet you would fill in first. Keep it to a structured plan.

    Show solution

    Ingestion and layers. Daily batch export from the ticketing system (the classifier does not need sub-minute freshness). Land in a restricted raw zone with strict access and short retention (e.g. 30 days); immediately run redaction to produce the cleaned layer that ML engineers use; curated training sets are built from the cleaned layer. Jobs are partitioned by ticket creation date and overwrite their partition (idempotent).

    PII handling. Detect emails, phone numbers, card and account numbers with validated patterns (Luhn check for cards), plus an NER model for names and addresses; pseudonymize customer email and account id with HMAC (key in a secret manager) to allow per-customer deduplication and splitting; mask everything else. Drop attachments. Measure detector recall on 500 hand-labeled emails and require, e.g., at least 95% recall for direct identifiers before go-live; log only redacted text.

    Validation. Schema and types; non-empty body; language distribution and ticket-category frequencies vs a 30-day reference (alert on large shifts); duplicate ticket ids; freshness (yesterday's partition present by 06:00); fraction of tokens redacted within the usual range (a sudden drop can mean the detector broke).

    Labeling. Written guidelines with examples per category; pilot of 300 tickets double-labeled by 3 annotators; target Krippendorff's alpha (or pairwise Cohen's kappa) of at least 0.7 before scaling, revising guidelines for confusable categories; in production 10% double-labeled plus 5% gold items; expert adjudication; active learning to prioritize uncertain tickets.

    Versioning and lineage. Each training set is an immutable snapshot (DVC or table time travel) with its id logged in every training run; the pipeline emits lineage events (e.g. OpenLineage) from raw to cleaned to curated to model; deletion requests are propagated using lineage plus the pseudonymous id.

    Datasheet first sections. Composition (what a ticket contains, date range, languages, known gaps, presence of personal data after redaction); Collection process (source system, consent/notice basis, time frame); Preprocessing (redaction method and measured recall, deduplication, labeling procedure and agreement); Uses (intended: routing; out of scope: individual customer profiling); Maintenance (owner, refresh cadence, how to report errors).

Further reading

Go further

Sources

Fact-checked and last reviewed 2026-11-05

Stay in the loop
Prometheus OS™ · Native AI OS · Built for the future © 2025 - 2026