No description
  • Python 71.9%
  • JavaScript 23.8%
  • CSS 2.5%
  • HTML 1.6%
  • Dockerfile 0.2%
Find a file
Repository files (latest commit first)
Filename Latest commit message Latest commit date
2026-08-11 12:52:13 +08:00
app
docs
static
templates
tests
.env.example
.gitignore
.pre-commit-config.yaml
.python-version
config.example.yaml
docker-compose.yml
Dockerfile
pyproject.toml
README.md
uv.lock

laser_estimator

Estimates how long a DXF/SVG cutting job will take on configured laser cutters with a specific material. A user (via an integrating external service) uploads a DXF/SVG, classifies each line as cut, etch, or fill using an embeddable widget, and gets back a time estimate broken down by cut/etch/fill/travel time.

Pieces

  • API (app/routes/api.py) — accepts DXF/SVG uploads, returns a unified GeoJSON-shaped vector format, accepts classified vectors, returns a time estimate. Authenticated with a shared X-API-Key — server-to-server only, see docs/contracts.md.
  • Embeddable widget (static/widget/laser-widget.js) — a vanilla-JS, Shadow-DOM-isolated component the integrating service embeds in its own page. Renders the vectors, lets a human classify them, hands the result back via a callback. Never calls this API itself — see docs/widget-integration.md.
  • Kinematics engine (app/kinematics/) — pure-Python trapezoidal motion-time estimation with junction-deviation cornering, greedy nearest-neighbor path ordering, and overscan-aware hatch-fill generation. No external CLI/slicer tool involved.
  • Worker pool (app/jobs/, RQ + Redis) — parsing and estimation run as background tasks consumed by a pool of worker containers, independently scalable (docker compose up --scale worker=N) and observable by standard Redis-queue-depth monitoring/autoscaling tooling.
  • Demo site (app/routes/ui.py, templates/estimate.html) — a same-origin test harness exercising the exact same widget contract the external integration uses, for testing without the real integration.

Quick start (local, no Docker)

Requires Python 3.12 and uv. The production target is a headless Debian server, so Linux/bash is the primary path below; a Windows/PowerShell equivalent is included for local dev.

Linux / macOS (bash):

uv sync
cp .env.example .env
# edit .env: set API_KEYS to a key of your choosing
uv run waitress-serve --listen=0.0.0.0:5000 --call app:create_app

Windows (PowerShell):

uv sync
Copy-Item .env.example .env
# edit .env: set API_KEYS to a key of your choosing
uv run waitress-serve --listen=0.0.0.0:5000 --call app:create_app

GET http://localhost:5000/health should return {"status": "ok", ...}. Machines/materials come from config.example.yaml (copy to config.yaml and point CONFIG_PATH at it, or edit in place) — see docs/contracts.md's "Machine/material registry" section for the schema.

Without Redis running, job creation will fail at the enqueue step. Either run Redis locally (docker run -p 6379:6379 redis:7-alpine, and set REDIS_URL=redis://localhost:6379/0) plus an rq worker process (uv run rq worker laser:parse laser:estimate), or use Docker Compose below, which wires all of this up.

Docker Compose (API + worker pool + Redis)

Linux / macOS (bash):

cp .env.example .env
# edit .env: set API_KEYS
docker compose up --build --scale worker=3

Windows (PowerShell):

Copy-Item .env.example .env
# edit .env: set API_KEYS
docker compose up --build --scale worker=3
  • API: http://localhost:5057 (configurable via API_PORT)
  • Monitoring dashboard: http://localhost:9181
  • Scale the worker pool independently: docker compose up --scale worker=N

No docker.sock mount anywhere — workers are ordinary long-running processes consuming a Redis queue, not sibling containers spawned on-demand. See docs/contracts.md for why.

Demo site

Set ENABLE_FRONTEND=true and FRONTEND_API_KEY (must also be one of the comma-separated API_KEYS) in .env, then visit /estimate. Upload a DXF/SVG, pick a machine/material/thickness, classify the parsed paths in the embedded widget, and watch the estimate come back — the same flow an external integration follows, just with this page playing the role of the external service's own frontend+backend.

Testing

Same commands on Linux/macOS and Windows:

uv run pytest tests/ -v
uv run pre-commit run --all-files

Tests don't need Redis or Docker — app.jobs.queue swaps in an in-memory fakeredis connection with synchronous (is_async=False) queues whenever config["testing"] is set (see tests/conftest.py), so .enqueue() runs the task immediately in-process. tests/test_worker_integration.py separately proves the real asynchronous wiring with a genuine rq SimpleWorker against fakeredis.

Documentation

  • docs/openapi.yaml — full API contract.
  • docs/vector-format.md — the unified GeoJSON-shaped vector format, and why it's shaped that way.
  • docs/widget-integration.md — the complete external-integration guide (self-sufficient; written for an implementer with no other context).
  • docs/contracts.md — conventions and invariants later code must not drift from (error handling, job state machine, auth, config registry).
  • docs/job-schema.json — JSON Schema mirror of the Job resource.

Project layout

app/
  interfaces.py       contracts-first type definitions (single source of truth)
  auth.py             X-API-Key / Bearer auth, the only credential type in the system
  config.py           YAML config + env overrides + machine/material registry
  kinematics/          pure-Python time-estimation engine (motion, hatch, path ordering)
  parsing/              DXF (ezdxf) / SVG (svgelements) -> unified vector format
  jobs/                 job store, RQ task queue + task functions, TTL reaper
  routes/                api.py (server-to-server), ui.py (demo site)
static/
  widget/               the embeddable classification widget
  demo/                  demo-site-only JS/CSS
templates/              demo site HTML
tests/                  pytest suite (unit + full HTTP flow)
docs/                   API contract, vector format, integration guide, conventions