Factory data pipeline engineer
Skill nonlinear-xyz/factory-kit/skills/factory-data-pipeline-engineer
Personal Claude Code factory — synthesized cross-build skills, specialist subagents, and slash commands. Phase 0.
npx -y skills add nonlinear-xyz/factory-kit --skill factory-data-pipeline-engineerAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
One thing to look at
- 9 stars9 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.
What its author says it does
Copied from the file, not written here
Use when designing or implementing data ingestion, CSV imports, time-series storage, Python services that sit alongside Next.js, simulation pipelines, or external-API integration with submit/poll/fetch shapes. Carries the factory's data-pipeline conventions — TS scripts with Papa Parse, JSONB envelopes for time-series, the three-entry-point Python pattern (CLI / Cloud Run API / Pub/Sub), YAML config for service-level data, converter/service split, Cloud Run + ephemeral Neon deployment.
SKILL.md
5.8 KB, as published. Nobody here has run it
Apply the data-pipeline-engineer specialist workflow. Design data flow that fits the factory's pipeline conventions, not bespoke ETL plumbing. Load factory-data-pipelines and factory-stack through the host's skill capability when needed.
How to think (in order)
-
What's the data shape? Pick one:
- One-shot or scheduled CSV → TS script in
scripts/data_processing/ - Event stream / time-series → JSONB envelope on a structured parent table
- External API ingestion (slow operation) → submit/poll/fetch async pattern
- Reference data (slowly changing) → YAML config (Python side)
- Compute job (sim, optimization, ML) → Python service with three entry points
If it doesn't match one, that's the finding — name it.
- One-shot or scheduled CSV → TS script in
-
TS or Python? Default: Next.js side (TS). Move to Python when:
- Numeric / scientific libraries are non-trivial (geopandas, shapely, numpy/scipy)
- Existing Python expertise / models
- Compute runtime > Vercel function timeout (~10s on hobby, 60s on pro)
-
Storage shape? Drizzle table with structured columns for what drives queries + JSONB for what doesn't. Rule: if you need to filter or sort by it at app speed, it earns a column.
-
Deployment shape?
- TS script → run locally or in GitHub Action; commits the data to DB
- Cloud Run API → HTTP endpoint, FastAPI, API key dependency
- Cloud Run Pub/Sub handler → async job processor
- Long-running compute → Cloud Run with extended timeout, or Cloud Run Jobs
-
Migrations? Run in CI, not at runtime. Drizzle's generate + push, or dbmate for raw SQL projects.
-
Idempotency? Default to upsert-on-conflict for CSV imports. Wrap in a transaction. Don't assume "imported once."
-
Converter vs service split? Pure transforms in
*-converter.ts(client-safe). I/O in*-service.ts(server-only). Don't blur.
Reference: canonical TS import script
// scripts/data_processing/import-foo.ts
import { readFileSync } from 'fs';
import Papa from 'papaparse';
import { db } from '@/db';
import { foo } from '@/db/schema';
const csvText = readFileSync(process.argv[2], 'utf8');
const { data, errors } = Papa.parse<FooRow>(csvText, {
header: true,
skipEmptyLines: true,
dynamicTyping: true,
});
if (errors.length) {
console.error('Parse errors:', errors);
process.exit(1);
}
await db.transaction(async (tx) => {
for (const row of data) {
await tx.insert(foo).values({
externalId: row.external_id,
name: row.name,
// ... map every column
}).onConflictDoUpdate({
target: foo.externalId,
set: { name: row.name, updatedAt: new Date() },
});
}
});
console.log(`Imported ${data.length} rows`);
Reference: canonical Python service layout
models/<service>/
├── Dockerfile # Pub/Sub variant
├── Dockerfile.api # API variant
├── pyproject.toml
├── main.py # CLI entry
├── main_api.py # FastAPI entry — Cloud Run HTTP
├── main_pubsub.py # Pub/Sub handler entry
├── config/
│ ├── routes.yaml
│ └── vehicles.yaml
├── src/
│ ├── api/
│ │ └── deps.py # API key, request context
│ ├── models/
│ │ ├── request.py # Pydantic
│ │ └── message.py # Pub/Sub message shape
│ ├── services/
│ │ ├── data_service.py # YAML loader
│ │ ├── mapbox_service.py
│ │ └── gcs_service.py
│ └── simulation/
│ └── simulation_runner.py # core work; mode-aware
└── tests/
└── conftest.py
Output format
## Restated request
<one sentence>
## Pipeline shape
- Data shape: <CSV / event stream / external API / reference data / compute>
- Runtime: <TS / Python / mixed — why>
- Storage: <columns / JSONB envelope / both>
- Deploy: <local script / Vercel scripts / Cloud Run API / Cloud Run Pub/Sub>
## Files to create or modify
<bulleted list with paths>
## Code
<organized by file>
## Operational details
- Idempotency: <how>
- Migrations: <CI step, not runtime>
- Trace ID: <yes — middleware in place>
- Logging: <PostHog / Sentry / structured logs>
## Open questions
<things the user should confirm>
What you do NOT do
- Don't pre-build
libs/py-libs/shared utilities. Wait for the second consumer. - Don't query inside JSONB at app speed. Promote the field to a column.
- Don't write a Cloud Run job before the second use case. Standalone TS script is enough until proven.
- Don't share Pydantic models across the three Python entry points by copy-paste. Define once in
models/, import everywhere. - Don't run migrations at runtime. CI's job.
- Don't blur converter / service boundaries. Pure-transform stays in
*-converter.ts; I/O in*-service.ts. - Don't write Drizzle in raw
pgstyle. If you're considering raw SQL with row-mappers, you're doing it wrong. - Don't put YAML paths in hardcoded strings. Env-driven base paths.
When the request is too small for this framework
If the user asks for a one-off query, a single Drizzle insert, or "just read this CSV once," do it directly without the full pipeline framework. The framework is for recurring or productionized data flow.