Skip to content

Repository files navigation

AcmeParts Cloud

CI License: MIT Python 3.12 FastAPI Docker

All data is synthetic. Meridian Fabrication Co. is a fictional company created for integration demonstrations. No real companies, part numbers, or employees are represented.

A self-contained FastAPI + PostgreSQL sandbox that ships 5,000+ parts, 400 suppliers, 20,000 change orders, and 30,000 purchase orders, all seeded with deterministic, configurable messiness.

Built for engineers who need a realistic target system to build and demo data pipelines, ETL connectors, or AI-assisted data cleaning without touching production.

Seeding a messy enterprise database in one command


What's Inside

Entity Rows (medium messiness) Key quirks
Parts 5,000 Three part-number formats from three eras
Part Revisions ~12,000 Mixed revision schemes (A/B/C vs 1/2/3)
Suppliers 400 Near-duplicate names, invalid emails
Change Orders 20,000 5 state vocabulary variants, impossible dates
Purchase Orders 30,000 Mixed currencies, price magnitude errors
Users 150 Inactive users still referenced in COs
Audit Log auto Missing actors

See QUIRKS.md for the full defect catalog.


Architecture

graph TD
    Browser["Browser / API Client"] --> API["FastAPI :8000"]
    API --> DB["PostgreSQL 16"]
    API --> Seed["Seed Engine"]
    Seed --> Gen["generators.py<br>5K parts · 400 suppliers<br>20K COs · 30K POs"]
    Seed --> Manifest["mess_manifest.json<br>(ground truth)"]
    Seed --> Exporter["exporter.py<br>CSV · XLSX · legacy encodings"]
    API --> UI["Jinja2 + HTMX<br>Dashboard · Parts · Suppliers · COs"]
Loading

Quick Start

SQLite — no Docker, no Postgres (quickest)

Point DATABASE_URL at SQLite and scale the row counts down so a laptop seeds in seconds:

python -m venv .venv && source .venv/bin/activate
pip install -r requirements.txt

export DATABASE_URL="sqlite:///acme.db"
export PARTS_COUNT=500 CHANGE_ORDERS_COUNT=2000 PURCHASE_ORDERS_COUNT=3000 AUDIT_LOG_COUNT=5000

python -m app.seed.run
uvicorn app.main:app --reload

Open http://localhost:8000/. Drop the count overrides to seed the full-size dataset (slower on SQLite).

Docker + Postgres (full dataset)

git clone https://github.com/RedBeret/acme-parts-cloud.git
cd acme-parts-cloud
docker compose up --build

Open http://localhost:8000/; the seeder runs automatically on first boot. On Windows, run.bat does the venv setup for you.

Local venv against your own Postgres

export DATABASE_URL=postgresql+psycopg2://acme:acme@localhost:5432/acmeparts
export SEED=42
export MESSINESS=medium  # clean | medium | chaos
python -m app.seed.run
uvicorn app.main:app --reload

Messiness Levels

Control defect injection with the MESSINESS environment variable:

Level Configurable injection rate Use case
clean ~2% Baseline; test happy-path pipelines
medium ~10% Default; realistic enterprise system
chaos ~25% Stress-test your cleaning and dedup logic

These rates apply to configurable format, duplicate, email, state, date, and price injections. Structural quirks such as mixed currencies, inactive records, missing actors, and export schemas remain present at fixed rates.

The seeder writes mess_manifest.json after each run. Manifest v2 records a stable row key for every injected defect and derives its summary counts from those keys.


API Reference

Endpoint Method Description
/api/parts GET List/search parts (search, status, category, after)
/api/parts/{id} GET Get one part
/api/suppliers GET List/search suppliers (search, active, after)
/api/suppliers/{id} GET Get one supplier
/api/change-orders GET List COs (search, state, priority, after)
/api/change-orders/{id} GET Get one change order
/admin/healthz GET Liveness check
/admin/reset POST Opt-in reseed with ?seed=N
/docs GET Interactive Swagger UI

All list endpoints use cursor pagination via the after parameter (last seen id).


Sample Exports

The samples/ directory contains small committed samples of each export format:

samples/
  parts_v1_sample.csv         — 2019-era column names
  parts_v2_sample.csv         — schema drift (extra legacy_ref column)
  change_orders_sample.xlsx   — merged header row, embedded newlines
  suppliers_legacy_sample.csv — UTF-8 preview of the Windows-1252 full export

Spot the drift — same part, two export versions:

# parts_v1 (2019-era headers)
partNo,partName,cat,measure,partStatus

# parts_v2 (renamed uom column, new legacy_ref, different column order)
part_number,name,uom,category,status,superseded_by,created_at,legacy_ref

The committed samples contain both legacy part-number formats. The seeder records each affected key in mess_manifest.json, so any cleanup tool you point at this data can be scored, not just eyeballed.

Full exports are available at runtime:

curl http://localhost:8000/exports/parts/v1  > parts_v1.csv
curl http://localhost:8000/exports/parts/v2  > parts_v2.csv
curl http://localhost:8000/exports/suppliers/legacy > suppliers_legacy.csv
curl http://localhost:8000/exports/change-orders > change_orders.xlsx

Case Study — Data Cleaning Pipeline Demo

Scenario: An engineer is building a Snowflake connector to ingest AcmeParts supplier data for procurement analytics. The pipeline breaks on first run.

Root cause chain discovered using this sandbox:

  1. SELECT * FROM suppliers WHERE active = true — silently drops 8% of historical PO data because defunct suppliers are still referenced in purchase_orders.
  2. GROUP BY supplier_name — returns 3 rows for "Vortex Metals" because of near-duplicate name variants (Vortex Metals, Vortex Metals Inc., VORTEX METALS). Entity resolution required before aggregation.
  3. SUM(unit_price) across POs — produces a meaningless number because unit_price is in mixed currencies (USD, EUR, GBP) with no exchange rate table.
  4. Opening suppliers_legacy.csv as UTF-8 — garbled output because the file is Windows-1252 encoded.

Every one of those defects is recorded in mess_manifest.json when the data is seeded, so you can check what your pipeline caught against ground truth instead of guessing. That is the whole point of the sandbox: the bugs are known, counted, and reproducible from a seed.


Reseed on Demand

# Via API (disabled by default)
ADMIN_RESET_TOKEN="choose-a-local-token" ENABLE_ADMIN_RESET=true docker compose up -d
curl -X POST -H "X-Admin-Reset-Token: choose-a-local-token" \
  "http://localhost:8000/admin/reset?seed=99"

# Via CLI (inside container)
docker compose exec api python -m app.seed.run --reset

Same SEED + MESSINESS always produces byte-identical manifest output, and the optional count environment variables let you scale the dataset down for quick CI runs or up for heavier demos.


Contributing

See CONTRIBUTING.md.

License

MIT — see LICENSE.

About

Synthetic parts catalog API with intentional data quality defects — seed-driven messiness engine for testing data pipelines

Topics

Resources

Contributing

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages