Install any skill in seconds. Free to start, no credit card required.
Get Started Free →Use when you need to review or design an ETL/ELT data pipeline for idempotency, incremental loads, orchestration (Airflow/Dagster/cron), backfill safety, partitioning, and failure recovery.
.claude/skills/bilal140202-data-pipeline-review/SKILL.md| Model | Eval pass | Runs |
|---|---|---|
| gemini-3.6-flash | 100% | 11 |
| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-20 | ✗→✓ | ▲ Improved | 136% | 0% |
| case-07 | ✓→✗ | ▼ Worse | 100% | 0% |
| case-22 | ✓→✗ | ▼ Worse | 8% | 0% |
| case-01 | ✓→✓ | = Same ✓ | 82% | 0% |
| case-02 | ✓→✓ | = Same ✓ | 68% | 0% |
Review or design a batch or streaming data pipeline so that it is idempotent, restartable, and correct under retries and backfills. Cover extraction, transformation, load semantics, orchestration (Airflow, Dagster, Prefect, or cron), partitioning, watermarking for incremental loads, and recovery from partial failure. The goal is a pipeline that produces the same output when re-run on the same input, never double-writes, and can backfill a historical window without corrupting current data.
bash# Locate orchestration definitions find . -path '*/dags/*.py' -o -name 'dagster*.py' -o -name '*_flow.py' 2>/dev/null | grep -v __pycache__ | head -30 ls dbt_project.yml profiles.yml 2>/dev/null grep -rn "schedule_interval\|@daily\|cron\|ScheduleDefinition\|@schedule" . --include="*.py" | head -20
Identify each task, its upstream inputs, its output table or file, and the trigger cadence.
bash# Look for non-idempotent appends vs idempotent upsert/overwrite grep -rn "INSERT INTO\|\.to_sql(\|if_exists=\|COPY INTO\|MERGE INTO\|ON CONFLICT" . --include="*.py" --include="*.sql" | head -30 # Truncate/overwrite patterns grep -rn "TRUNCATE\|DELETE FROM\|overwrite\|replace" . --include="*.py" --include="*.sql" | head -20
Confirm each load is one of: full overwrite of a partition, MERGE/upsert on a key, or insert into a fresh partition that is atomically swapped. A bare INSERT/append with retries will duplicate rows.
bash# Watermark / high-water-mark tracking grep -rn "watermark\|last_run\|updated_at\|incremental\|max(\|execution_date\|data_interval" . --include="*.py" --include="*.sql" | head -25
The cursor must use an event/business timestamp (not wall-clock), tolerate late data with a lookback window, and persist the watermark only after a successful load.
bash# Retries, idempotency keys, catchup behavior, dependencies grep -rn "retries\|retry_delay\|catchup\|depends_on_past\|max_active_runs\|wait_for_downstream" . --include="*.py" | head -25
Confirm retries are bounded, catchup/backfill behavior is intentional, and concurrent runs are limited so two runs cannot write the same partition.
Confirm there is a parameterized way to reprocess a date range that targets only the affected partitions, runs against a staging location or with MERGE, and never deletes live data before the replacement is validated.
MERGE/upsert on a stable key, or atomic partition swap — not a bare append under retry.retries and retry_delay.max_active_runs / concurrency prevents two runs writing the same partition.catchup / depends_on_past is set deliberately, not left at an accidental default.MERGE, never destructive on live tables.Idempotent partitioned upsert (SQL):
sql-- Load a single day's partition idempotently using MERGE. -- Safe to re-run: matched rows update, new rows insert, no duplicates. MERGE INTO analytics.orders AS t USING staging.orders_2026_06_01 AS s ON t.order_id = s.order_id WHEN MATCHED THEN UPDATE SET status = s.status, amount = s.amount, updated_at = s.updated_at WHEN NOT MATCHED THEN INSERT (order_id, status, amount, updated_at) VALUES (s.order_id, s.status, s.amount, s.updated_at);
Idempotent incremental extract with watermark and lookback (Python):
pythonfrom datetime import timedelta def incremental_window(last_watermark, now, lookback=timedelta(hours=2)): """Return [start, end) for an incremental pull. Re-pulls a lookback window so late-arriving rows are captured. Caller must MERGE (not append) to stay idempotent. """ start = last_watermark - lookback end = now return start, end def run(conn, store): last = store.get_watermark("orders") # persisted business timestamp start, end = incremental_window(last, now_utc()) rows = conn.fetch( "SELECT * FROM source.orders " "WHERE updated_at >= %s AND updated_at < %s", (start, end), ) upsert_orders(conn, rows) # MERGE, not INSERT store.set_watermark("orders", end) # persist ONLY after success
Airflow task skeleton with bounded retries and no accidental catchup:
pythonfrom airflow import DAG from airflow.operators.python import PythonOperator from datetime import datetime, timedelta default_args = {"retries": 3, "retry_delay": timedelta(minutes=5)} with DAG( dag_id="orders_incremental", schedule_interval="@hourly", start_date=datetime(2026, 1, 1), catchup=False, # do not silently backfill on deploy max_active_runs=1, # never two writers on one partition default_args=default_args, ) as dag: load = PythonOperator(task_id="load_orders", python_callable=run)
INSERT inside a task with retries — every retry duplicates rows.now() / wall-clock instead of the event timestamp, dropping late data.catchup=True left on by default, triggering an unintended multi-month backfill at deploy time.DELETEs live partitions first, leaving a gap if the reload fails.Produce a structured report with:
file:line | issue | risk | concrete fix.| Case | Status | Duration (ms) | Turns | Tokens | Tool calls | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Without | With | Δ | Without | With | Δ | Without | With | Δ | Without | With | Δ | ||
case-01 | pass→pass | 13,827 | 12,642 | -9% | 1 | 1 | 0% | 2,281 | 4,141 | +82% | 0 | 0 | — |
case-02 | pass→pass | 17,239 | 19,134 | +11% | 1 | 1 | 0% | 3,435 | 5,786 | +68% | 0 | 0 | — |
case-03 | pass→pass | 18,385 | 15,159 | -18% | 1 | 1 | 0% | 3,819 | 5,715 | +50% | 0 | 0 | — |
case-09 | pass→pass | 16,447 | 19,267 | +17% | 1 | 1 | 0% | 2,555 | 5,350 | +109% | 0 | 0 | — |
case-04 | fail→fail | 18,274 | 4,631 | -75% | 1 | 1 | 0% | 3,699 | 2,398 | -35% | 0 | 0 | — |
case-05 | pass→pass | 12,813 | 11,407 | -11% | 1 | 1 | 0% | 2,348 | 4,182 | +78% | 0 | 0 | — |
case-06 | pass→pass | 8,904 | 19,273 | +116% | 1 | 1 | 0% | 1,658 | 5,653 | +241% | 0 | 0 | — |
case-07 | pass→fail | 12,395 | 12,059 | -3% | 1 | 1 | 0% | 2,203 | 4,413 | +100% | 0 | 0 | — |
case-08 | pass→pass | 10,183 | 11,325 | +11% | 1 | 1 | 0% | 1,727 | 4,056 | +135% | 0 | 0 | — |
case-10 | pass→pass | 14,142 | 19,918 | +41% | 1 | 1 | 0% | 2,155 | 5,522 | +156% | 0 | 0 | — |
case-11 | pass→pass | 13,050 | 17,506 | +34% | 1 | 1 | 0% | 2,262 | 5,146 | +127% | 0 | 0 | — |
case-12 | fail→fail | 13,959 | 5,023 | -64% | 1 | 1 | 0% | 2,502 | 2,416 | -3% | 0 | 0 | — |
case-13 | pass→pass | 5,832 | 19,801 | +240% | 1 | 1 | 0% | 1,007 | 5,669 | +463% | 0 | 0 | — |
case-14 | pass→pass | 12,249 | 11,302 | -8% | 1 | 1 | 0% | 2,190 | 4,154 | +90% | 0 | 0 | — |
case-15 | fail→fail | 10,028 | 7,388 | -26% | 1 | 1 | 0% | 1,982 | 3,608 | +82% | 0 | 0 | — |
case-16 | pass→pass | 10,143 | 16,479 | +62% | 1 | 1 | 0% | 2,073 | 5,622 | +171% | 0 | 0 | — |
case-17 | pass→pass | 10,731 | 8,999 | -16% | 1 | 1 | 0% | 1,856 | 3,886 | +109% | 0 | 0 | — |
case-18 | pass→pass | 11,482 | 12,255 | +7% | 1 | 1 | 0% | 2,358 | 4,376 | +86% | 0 | 0 | — |
case-19 | pass→pass | 10,458 | 13,645 | +30% | 1 | 1 | 0% | 1,746 | 4,702 | +169% | 0 | 0 | — |
case-20 | fail→pass | 12,179 | 17,199 | +41% | 1 | 1 | 0% | 2,322 | 5,478 | +136% | 0 | 0 | — |
case-21 | pass→pass | 6,994 | 7,359 | +5% | 1 | 1 | 0% | 1,240 | 3,512 | +183% | 0 | 0 | — |
case-22 | pass→fail | 11,022 | 5,011 | -55% | 1 | 1 | 0% | 2,294 | 2,486 | +8% | 0 | 0 | — |
DecimalAI ran this skill against gemini-3.6-flash twice over the same eval suite — once with the skill loaded and once without — and compared the two runs case by case. 22 cases were attempted, and 19 counted toward the lift figure. The other 3 produced results that are not comparable between the two arms, so they are excluded from the headline rather than averaged into it. The headline lift of -5 percentage points is the difference between those two pass rates over the 19 comparable cases. 2 cases got worse with the skill loaded, and they are included in that figure.
Without the skill loaded, the model failed this case. With it loaded, the same prompt on the same model passed. This is one improved case from the latest verified run; every case, including any that regressed, is in the table above.
Other measured skills in the registry, with their headline benchmark lift.