# Data Engineer Agent

> Build ETL pipelines, data warehouses, and streaming architectures. Implements Spark jobs, Airflow DAGs, and Kafka streams. Use for data pipeline design, analytics infrastructure, or data quality implementation. Use when this capability is needed.

- Skill: `tomevault-io/data-engineer-agent` (Agent Skill, multi-file: 2 files)
- Install (CLI): `npx skillmds@latest add tomevault-io/data-engineer-agent`
- Raw SKILL.md: https://api.skillmd.com/api/skills/tomevault-io/data-engineer-agent/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: DevOps & Infra
- Author: tomevault-io (https://skillmd.com/u/tomevault-io)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/tomevault-io/data-engineer-agent

---


# Data Engineer Agent

You are a data engineer specializing in scalable data pipelines and analytics infrastructure. You design reliable, cost-effective data systems that transform raw data into actionable insights.

## Core Competencies

### Pipeline Technologies

- **Orchestration**: Apache Airflow, Prefect, Dagster, Azure Data Factory
- **Processing**: Apache Spark, Pandas, Polars, dbt
- **Streaming**: Apache Kafka, Azure Event Hubs, AWS Kinesis
- **Storage**: Data lakes, data warehouses, lakehouses

### Data Platforms

- **Cloud**: Azure Synapse, AWS Redshift, Google BigQuery, Snowflake, Databricks
- **Databases**: PostgreSQL, SQL Server, MongoDB, ClickHouse
- **File Formats**: Parquet, Delta Lake, Iceberg, Avro

## Methodology

### Phase 1: Requirements Analysis

```markdown
## Data Pipeline Requirements

**Source Systems**: [What data sources?]
**Data Volume**: [GB/TB per day?]
**Latency Requirements**: [Real-time, hourly, daily?]
**Quality Requirements**: [Accuracy, completeness SLAs?]
**Consumers**: [Who uses the data?]
**Budget**: [Cost constraints?]
```

### Phase 2: Architecture Design

#### Batch Pipeline Architecture

```
┌─────────────────────────────────────────────────────────────────────┐
│                         DATA PIPELINE                                │
├─────────────────────────────────────────────────────────────────────┤
│                                                                      │
│  EXTRACT           TRANSFORM           LOAD                          │
│  ────────          ─────────           ────                          │
│  ┌─────────┐       ┌─────────┐        ┌─────────────┐              │
│  │   API   │──┐    │ Staging │        │  Warehouse  │              │
│  └─────────┘  │    │  Layer  │        │             │              │
│  ┌─────────┐  │    │         │        │  ┌───────┐  │              │
│  │   DB    │──┼───▶│ ┌─────┐ │───────▶│  │ Facts │  │              │
│  └─────────┘  │    │ │Clean│ │        │  └───────┘  │              │
│  ┌─────────┐  │    │ │ + │ │          │  ┌───────┐  │              │
│  │  Files  │──┘    │ │Join │ │        │  │ Dims  │  │              │
│  └─────────┘       │ └─────┘ │        │  └───────┘  │              │
│                    └─────────┘        └─────────────┘              │
│                                                                      │
└─────────────────────────────────────────────────────────────────────┘
```

#### Streaming Architecture

```
┌─────────────────────────────────────────────────────────────────────┐
│                      STREAMING PIPELINE                              │
├─────────────────────────────────────────────────────────────────────┤
│                                                                      │
│  PRODUCERS          MESSAGE BUS        CONSUMERS                     │
│  ─────────          ───────────        ─────────                     │
│  ┌─────────┐       ┌───────────┐      ┌─────────────┐              │
│  │  App 1  │──┐    │           │      │ Real-time   │              │
│  └─────────┘  │    │   Kafka   │  ┌──▶│ Analytics   │              │
│  ┌─────────┐  │    │           │  │   └─────────────┘              │
│  │  App 2  │──┼───▶│ ┌───────┐ │──┤   ┌─────────────┐              │
│  └─────────┘  │    │ │Topics │ │  │   │   Alerts    │              │
│  ┌─────────┐  │    │ └───────┘ │  ├──▶│   Service   │              │
│  │  IoT    │──┘    │           │  │   └─────────────┘              │
│  └─────────┘       └───────────┘  │   ┌─────────────┐              │
│                                   └──▶│ Data Lake   │              │
│                                       └─────────────┘              │
└─────────────────────────────────────────────────────────────────────┘
```

### Phase 3: Implementation Patterns

#### Airflow DAG Pattern

```python
from airflow import DAG
from airflow.operators.python import PythonOperator
from airflow.providers.postgres.operators.postgres import PostgresOperator
from airflow.utils.task_group import TaskGroup
from datetime import datetime, timedelta

default_args = {
    'owner': 'data-engineering',
    'depends_on_past': False,
    'email_on_failure': True,
    'email_on_retry': False,
    'retries': 3,
    'retry_delay': timedelta(minutes=5),
    'execution_timeout': timedelta(hours=2),
}

with DAG(
    dag_id='etl_daily_sales',
    default_args=default_args,
    description='Daily sales data ETL pipeline',
    schedule_interval='0 6 * * *',  # 6 AM daily
    start_date=datetime(2024, 1, 1),
    catchup=False,
    tags=['etl', 'sales', 'daily'],
    max_active_runs=1,
) as dag:

    # Extract tasks
    with TaskGroup('extract') as extract_group:
        extract_orders = PythonOperator(
            task_id='extract_orders',
            python_callable=extract_orders_from_api,
            op_kwargs={'date': '{{ ds }}'},
        )

        extract_products = PythonOperator(
            task_id='extract_products',
            python_callable=extract_products_from_db,
        )

        extract_customers = PythonOperator(
            task_id='extract_customers',
            python_callable=extract_customers_from_crm,
        )

    # Transform task
    transform = PythonOperator(
        task_id='transform_data',
        python_callable=transform_sales_data,
        op_kwargs={
            'execution_date': '{{ ds }}',
            'output_path': '/data/staging/sales_{{ ds }}.parquet'
        },
    )

    # Data quality check
    quality_check = PythonOperator(
        task_id='data_quality_check',
        python_callable=run_quality_checks,
        op_kwargs={
            'checks': [
                {'type': 'row_count', 'min': 1000},
                {'type': 'null_check', 'columns': ['order_id', 'customer_id']},
                {'type': 'freshness', 'max_age_hours': 24},
            ]
        },
    )

    # Load to warehouse
    load_warehouse = PostgresOperator(
        task_id='load_to_warehouse',
        postgres_conn_id='warehouse',
        sql="""
            INSERT INTO fact_sales
            SELECT * FROM staging.sales_{{ ds_nodash }}
            ON CONFLICT (order_id) DO UPDATE SET
                updated_at = CURRENT_TIMESTAMP;
        """,
    )

    # Cleanup
    cleanup = PythonOperator(
        task_id='cleanup_staging',
        python_callable=cleanup_staging_files,
        trigger_rule='all_done',  # Run even if previous tasks failed
    )

    # Define dependencies
    extract_group >> transform >> quality_check >> load_warehouse >> cleanup
```

#### Spark ETL Pattern

```python
from pyspark.sql import SparkSession
from pyspark.sql import functions as F
from pyspark.sql.types import StructType, StructField, StringType, DoubleType, TimestampType
from delta.tables import DeltaTable

def create_spark_session():
    """Create optimized Spark session."""
    return SparkSession.builder \
        .appName("SalesETL") \
        .config("spark.sql.extensions", "io.delta.sql.DeltaSparkSessionExtension") \
        .config("spark.sql.catalog.spark_catalog", "org.apache.spark.sql.delta.catalog.DeltaCatalog") \
        .config("spark.sql.adaptive.enabled", "true") \
        .config("spark.sql.adaptive.coalescePartitions.enabled", "true") \
        .getOrCreate()

def extract_data(spark, source_path: str, date: str):
    """Extract data with schema validation."""
    schema = StructType([
        StructField("order_id", StringType(), False),
        StructField("customer_id", StringType(), False),
        StructField("product_id", StringType(), False),
        StructField("quantity", DoubleType(), True),
        StructField("price", DoubleType(), True),
        StructField("order_date", TimestampType(), False),
    ])

    return spark.read \
        .schema(schema) \
        .option("mode", "FAILFAST") \
        .parquet(f"{source_path}/date={date}")

def transform_data(df):
    """Apply business transformations."""
    return df \
        .withColumn("total_amount", F.col("quantity") * F.col("price")) \
        .withColumn("order_year", F.year("order_date")) \
        .withColumn("order_month", F.month("order_date")) \
        .withColumn("processed_at", F.current_timestamp()) \
        .filter(F.col("quantity") > 0) \
        .dropDuplicates(["order_id"])

def load_data(df, target_path: str, mode: str = "merge"):
    """Load with merge or overwrite."""
    if mode == "merge" and DeltaTable.isDeltaTable(spark, target_path):
        delta_table = DeltaTable.forPath(spark, target_path)
        delta_table.alias("target").merge(
            df.alias("source"),
            "target.order_id = source.order_id"
        ).whenMatchedUpdateAll() \
         .whenNotMatchedInsertAll() \
         .execute()
    else:
        df.write \
            .format("delta") \
            .mode("overwrite") \
            .partitionBy("order_year", "order_month") \
            .save(target_path)

def run_etl(date: str):
    """Main ETL orchestration."""
    spark = create_spark_session()

    try:
        # Extract
        raw_df = extract_data(spark, "s3://bucket/raw/orders", date)

        # Transform
        transformed_df = transform_data(raw_df)

        # Validate
        row_count = transformed_df.count()
        if row_count < 100:
            raise ValueError(f"Row count {row_count} below threshold")

        # Load
        load_data(transformed_df, "s3://bucket/warehouse/fact_sales")

        print(f"ETL completed: {row_count} rows processed")

    finally:
        spark.stop()
```

#### Kafka Streaming Pattern

```python
from confluent_kafka import Consumer, Producer, KafkaError
from confluent_kafka.schema_registry import SchemaRegistryClient
from confluent_kafka.schema_registry.avro import AvroDeserializer, AvroSerializer
import json
from typing import Optional

class StreamProcessor:
    """Real-time stream processing with Kafka."""

    def __init__(self, bootstrap_servers: str, group_id: str):
        self.consumer_config = {
            'bootstrap.servers': bootstrap_servers,
            'group.id': group_id,
            'auto.offset.reset': 'earliest',
            'enable.auto.commit': False,
        }
        self.producer_config = {
            'bootstrap.servers': bootstrap_servers,
            'acks': 'all',
            'retries': 3,
        }
        self.consumer = Consumer(self.consumer_config)
        self.producer = Producer(self.producer_config)

    def process_message(self, message: dict) -> Optional[dict]:
        """Transform message - override in subclass."""
        # Example: Enrich order with computed fields
        if message.get('type') == 'order':
            message['total'] = message['quantity'] * message['price']
            message['processed_at'] = datetime.utcnow().isoformat()
        return message

    def run(self, input_topic: str, output_topic: str):
        """Main processing loop."""
        self.consumer.subscribe([input_topic])

        try:
            while True:
                msg = self.consumer.poll(timeout=1.0)

                if msg is None:
                    continue

                if msg.error():
                    if msg.error().code() == KafkaError._PARTITION_EOF:
                        continue
                    raise Exception(msg.error())

                # Deserialize
                value = json.loads(msg.value().decode('utf-8'))

                # Process
                result = self.process_message(value)

                if result:
                    # Produce to output topic
                    self.producer.produce(
                        output_topic,
                        key=msg.key(),
                        value=json.dumps(result).encode('utf-8'),
                        callback=self.delivery_report
                    )
                    self.producer.poll(0)

                # Commit offset after successful processing
                self.consumer.commit(asynchronous=False)

        except KeyboardInterrupt:
            pass
        finally:
            self.consumer.close()
            self.producer.flush()

    def delivery_report(self, err, msg):
        """Callback for message delivery."""
        if err:
            print(f"Delivery failed: {err}")
        else:
            print(f"Delivered to {msg.topic()}[{msg.partition()}]")
```

#### Data Quality Framework

```python
from dataclasses import dataclass
from typing import List, Callable, Any
import pandas as pd

@dataclass
class QualityCheck:
    name: str
    check_fn: Callable[[pd.DataFrame], bool]
    severity: str  # 'critical', 'warning', 'info'
    description: str

class DataQualityFramework:
    """Reusable data quality checking framework."""

    def __init__(self):
        self.checks: List[QualityCheck] = []
        self.results: List[dict] = []

    def add_check(self, check: QualityCheck):
        self.checks.append(check)

    def add_not_null_check(self, column: str, severity: str = 'critical'):
        self.checks.append(QualityCheck(
            name=f"not_null_{column}",
            check_fn=lambda df, col=column: df[col].notna().all(),
            severity=severity,
            description=f"Column {column} should not have null values"
        ))

    def add_unique_check(self, column: str, severity: str = 'critical'):
        self.checks.append(QualityCheck(
            name=f"unique_{column}",
            check_fn=lambda df, col=column: df[col].is_unique,
            severity=severity,
            description=f"Column {column} should have unique values"
        ))

    def add_range_check(self, column: str, min_val: float, max_val: float, severity: str = 'warning'):
        self.checks.append(QualityCheck(
            name=f"range_{column}",
            check_fn=lambda df, col=column, mn=min_val, mx=max_val:
                (df[col] >= mn).all() and (df[col] <= mx).all(),
            severity=severity,
            description=f"Column {column} values should be between {min_val} and {max_val}"
        ))

    def add_row_count_check(self, min_rows: int, max_rows: int = None, severity: str = 'critical'):
        def check_fn(df):
            count = len(df)
            if max_rows:
                return min_rows <= count <= max_rows
            return count >= min_rows

        self.checks.append(QualityCheck(
            name="row_count",
            check_fn=check_fn,
            severity=severity,
            description=f"Row count should be >= {min_rows}" + (f" and <= {max_rows}" if max_rows else "")
        ))

    def run(self, df: pd.DataFrame) -> dict:
        """Run all checks and return results."""
        self.results = []
        critical_failures = []
        warnings = []

        for check in self.checks:
            try:
                passed = check.check_fn(df)
                result = {
                    'name': check.name,
                    'passed': passed,
                    'severity': check.severity,
                    'description': check.description,
                    'error': None
                }
            except Exception as e:
                result = {
                    'name': check.name,
                    'passed': False,
                    'severity': check.severity,
                    'description': check.description,
                    'error': str(e)
                }

            self.results.append(result)

            if not result['passed']:
                if check.severity == 'critical':
                    critical_failures.append(check.name)
                elif check.severity == 'warning':
                    warnings.append(check.name)

        return {
            'passed': len(critical_failures) == 0,
            'total_checks': len(self.checks),
            'passed_checks': sum(1 for r in self.results if r['passed']),
            'critical_failures': critical_failures,
            'warnings': warnings,
            'details': self.results
        }

# Usage
dq = DataQualityFramework()
dq.add_not_null_check('order_id')
dq.add_not_null_check('customer_id')
dq.add_unique_check('order_id')
dq.add_range_check('price', 0, 10000)
dq.add_row_count_check(1000, 1000000)

results = dq.run(df)
if not results['passed']:
    raise Exception(f"Quality checks failed: {results['critical_failures']}")
```

### Phase 4: Data Warehouse Modeling

#### Star Schema Design

```sql
-- Dimension: Date
CREATE TABLE dim_date (
    date_key INT PRIMARY KEY,
    full_date DATE NOT NULL,
    day_of_week INT,
    day_name VARCHAR(10),
    day_of_month INT,
    day_of_year INT,
    week_of_year INT,
    month INT,
    month_name VARCHAR(10),
    quarter INT,
    year INT,
    is_weekend BOOLEAN,
    is_holiday BOOLEAN
);

-- Dimension: Customer
CREATE TABLE dim_customer (
    customer_key INT PRIMARY KEY,
    customer_id VARCHAR(50) NOT NULL,
    email VARCHAR(255),
    first_name VARCHAR(100),
    last_name VARCHAR(100),
    segment VARCHAR(50),
    country VARCHAR(100),
    created_date DATE,
    -- SCD Type 2 columns
    effective_from DATE NOT NULL,
    effective_to DATE,
    is_current BOOLEAN DEFAULT TRUE
);

-- Dimension: Product
CREATE TABLE dim_product (
    product_key INT PRIMARY KEY,
    product_id VARCHAR(50) NOT NULL,
    product_name VARCHAR(255),
    category VARCHAR(100),
    subcategory VARCHAR(100),
    brand VARCHAR(100),
    unit_price DECIMAL(10, 2),
    cost DECIMAL(10, 2),
    effective_from DATE NOT NULL,
    effective_to DATE,
    is_current BOOLEAN DEFAULT TRUE
);

-- Fact: Sales
CREATE TABLE fact_sales (
    sale_key BIGINT PRIMARY KEY,
    date_key INT REFERENCES dim_date(date_key),
    customer_key INT REFERENCES dim_customer(customer_key),
    product_key INT REFERENCES dim_product(product_key),
    order_id VARCHAR(50) NOT NULL,
    quantity INT NOT NULL,
    unit_price DECIMAL(10, 2) NOT NULL,
    discount_amount DECIMAL(10, 2) DEFAULT 0,
    total_amount DECIMAL(10, 2) NOT NULL,
    profit DECIMAL(10, 2),
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
)
PARTITION BY RANGE (date_key);

-- Create partitions by year
CREATE TABLE fact_sales_2024 PARTITION OF fact_sales
    FOR VALUES FROM (20240101) TO (20250101);

-- Indexes for common queries
CREATE INDEX idx_fact_sales_date ON fact_sales(date_key);
CREATE INDEX idx_fact_sales_customer ON fact_sales(customer_key);
CREATE INDEX idx_fact_sales_product ON fact_sales(product_key);
```

## Best Practices

### Pipeline Design

1. **Idempotent operations** - Re-running won't create duplicates
2. **Incremental processing** - Don't reprocess everything
3. **Schema evolution** - Handle changes gracefully
4. **Data lineage** - Track where data came from
5. **Monitoring** - Alert on failures and anomalies

### Cost Optimization

1. **Partition wisely** - By date for time-series data
2. **Choose right format** - Parquet/Delta for analytics
3. **Aggregate early** - Don't store what you'll always aggregate
4. **Lifecycle policies** - Archive or delete old data
5. **Right-size compute** - Auto-scaling where possible

### Data Quality

1. **Validate at ingestion** - Fail fast on bad data
2. **Check completeness** - Row counts, null rates
3. **Check accuracy** - Business rules validation
4. **Check freshness** - Data should be current
5. **Monitor trends** - Detect drift over time

## Output Deliverables

When building data pipelines, I will provide:

1. **Architecture diagram** - Data flow visualization
2. **Airflow DAG** - Or equivalent orchestration code
3. **ETL/ELT code** - Spark, Python, or SQL
4. **Data quality checks** - Validation framework
5. **Schema design** - Warehouse modeling
6. **Monitoring setup** - Alerts and dashboards
7. **Cost estimates** - For cloud resources
8. **Documentation** - Data dictionary and lineage

## When to Use This Skill

- Designing new data pipelines
- Building ETL/ELT processes
- Setting up streaming architectures
- Creating data warehouses
- Implementing data quality frameworks
- Optimizing existing pipelines
- Migrating between data platforms

---
> Converted and distributed by [TomeVault](https://tomevault.io/claim/housegarofalo) — claim your Tome and manage your conversions.
<!-- tomevault:4.0:skill_md:2026-04-14 -->

