# POPS — Claude Code context ## What this project is Web-based production management system for a 3D print farm. FastAPI backend, Jinja2 server-rendered UI, MySQL 8 (external server), Docker deployment. ## Running locally ```bash docker compose up --build # starts on http://localhost:8720 ``` Alembic migrations run automatically at startup (stamps if tables exist, upgrades if not). ## Environment Copy `.env.install` → `.env` and fill in values. Key variables: | Variable | Purpose | |---|---| | `MYSQL_*` | External MySQL 8 connection | | `STL_UPLOAD_DIR` | Where STL/3MF/gcode files are stored (default `/data/stl`) | | `TZ` | Timezone for all timestamps (e.g. `Europe/Berlin`) | The `docker-compose.yml` mounts a named volume `stl_files` at `/data/stl`. ## Database - External MySQL 8 server — user needs `ALL PRIVILEGES` on the database (Alembic requires DDL). - `create_db.sql` — run once as root to create the DB and user. - `db.sql` — raw schema reference (not used for migrations, kept for reference). - Migrations live in `alembic/versions/`, numbered `0001_…`, `0002_…` etc. To add a migration: ```bash docker compose run --rm app alembic revision --autogenerate -m "describe change" # then review and commit the generated file ``` ## Project structure ``` app/ main.py — FastAPI app, lifespan (DB check + auto-migration) db.py — SQLAlchemy engine, SessionLocal, get_db, run_migrations stl.py — STL file directory helpers (get_stl_dir, list_stl_files) models/ — SQLAlchemy 2.x declarative models (one file per table) routers/ ui.py — All HTML routes (GET + form POST → redirect) printers.py — JSON API: /api/printers printer_types.py filaments.py print_jobs.py todos.py pricing.py templates/ — Jinja2 templates extending base.html alembic/ env.py — reads MYSQL_* env vars at runtime versions/ — numbered migration files ``` ## Conventions - **API routes** live under `/api/*` (JSON, for programmatic access). - **UI routes** live at root (`/`, `/printers`, `/jobs`, …) in `app/routers/ui.py`. - Form POSTs redirect with HTTP 303 — no JavaScript needed for basic operations. - All models have a `to_dict()` method from `Base` for JSON serialization in API routes. - Timestamps use the container's `TZ` env var — keep it consistent with MySQL server timezone. - `job_uuid` (UUID4) is generated in Python on job creation, not by MySQL. - Job creation always writes a "Job created" entry to `job_logs`. - Printer log and job log both use `ondelete="CASCADE"` — deleting a printer/job cleans up logs. ## Models at a glance | Model | Table | Notes | |---|---|---| | `PrinterType` | `printer_types` | name + bed X/Y/Z mm | | `Printer` | `printers` | FK → printer_type; lifecycle dates; logs | | `PrinterLog` | `printer_logs` | timestamped text per printer | | `Filament` | `filaments` | material enum + color + weight + price | | `PrintJob` | `print_jobs` | UUID, customer, STL filename, FK → printer + filament | | `JobLog` | `job_logs` | timestamped text per job (auto-entry on creation) | | `Todo` | `todos` | optional FK → printer | | `PricingConfig` | `pricing_config` | electricity rate, markup %, etc. | ## UI pages | URL | Description | |---|---| | `/` | Dashboard — stats cards + printer overview + recent jobs | | `/printers` | Printer list + Add Printer modal | | `/printers/{id}` | Printer detail — lifecycle info + log | | `/printer-types` | Manage printer types (name + bed volume) | | `/jobs` | Job list with search (customer / filename) + Add Job modal | | `/filaments` | Filament inventory with progress bars | | `/todos` | To-do list | | `/pricing` | Pricing config cards |