Independent quotation and vector nesting application. React/Vite renders normalized SVG geometry; FastAPI handles REST, file import, nesting, costing, sharing, and exports. SQLAlchemy supports SQLite locally and PostgreSQL in Compose. The normalized GeoJSON part model, rather than the uploaded file, feeds preview, nesting, and prices.
Production deployment #
The prepared calc.sign-expert.eu deployment uses a systemd backend bound to
127.0.0.1:8000, Caddy automatic HTTPS, and compiled frontend files served
from /var/www/signage-estimator. It reuses the existing SQLite database and
storage; no fresh database is created. See deploy/DEPLOYMENT.md
for the guarded cutover, backup, health, rollback, and verification steps.
The repository also contains the additive Caddy site,
systemd unit, and a read-only
production check. Host service, firewall, DNS,
TLS, and GitHub verification must be completed on the actual server; a
restricted build shell cannot certify the public deployment.
Run locally on this server #
cd /root/signage-estimator
python3 -m venv .venv
.venv/bin/pip install -r backend/requirements.txt
cd frontend && npm ci && cd ..
cd backend && ../.venv/bin/alembic upgrade head && cd ..
.venv/bin/python -m backend.app.bootstrap_admin
.venv/bin/uvicorn backend.app.main:app --host 0.0.0.0 --port 8000 --no-access-log
# in another terminal
cd frontend && npm run dev
Open http://SERVER-IP:5173 for local/trusted-network development. The Vite server proxies /api to port 8000 and redirects unauthenticated private UI routes to /login. The protected REST docs are at http://SERVER-IP:8000/docs. The one-time bootstrap command prompts for an email and a password of at least 12 characters; it refuses to run after any user exists. Set DATABASE_URL and STORAGE_DIR for custom locations. Run migrations from backend/ with ../.venv/bin/alembic upgrade head. Set TRUSTED_HOSTS to the exact hostname or development IP clients use. Serve production logins through HTTPS and set SESSION_COOKIE_SECURE=true.
Docker deployment #
Docker was not installed on the inspected host, so this configuration is prepared but not executed here.
cp .env.example .env
# set a strong POSTGRES_PASSWORD, TRUSTED_HOSTS and desired WEB_PORT in .env
docker compose up -d --build
docker compose exec backend python -m backend.app.bootstrap_admin
Compose binds port 8080 to 127.0.0.1 by default. It starts private PostgreSQL, a non-root backend with migrations, and Nginx serving the compiled frontend. Put TLS in front, set TRUSTED_HOSTS to the public domain and SESSION_COOKIE_SECURE=true, then use a reverse proxy such as the additive Caddy example. Do not expose Vite or direct uvicorn as the final production entry point. No existing reverse proxy configuration is changed. Bootstrap is one-time and interactive; keep .env private.
Accounts and master data #
Every API operation except health and login requires a revocable HttpOnly session cookie. Mutations also require the per-session CSRF token. Admin and Estimator have the same estimating, supplier, machine, operation and AI-settings rights; only Admin can manage users and view the audit UI. User profiles set display name, email, password, preferred language, Copilot name and voice preference. The default Copilot name is Vlad (Влад when Russian is selected).
Settings now has Suppliers, Price Lists, Supplier Offers, Machines & Technology, Operation Rules, AI / Copilot, and Admin-only Users and Audit Log. Upload XLSX, XLS, CSV or PDF under a supplier. All nonempty parsed rows are retained as supplier products, including unused products. Price lists start STAGED; review mapping suggestions, confirm mappings, and activate the list. PDF extraction is deliberately marked uncertain for manual review. Supplier products remain separate from canonical materials. Confirmed active offers can be compared and explicitly preferred.
New material rows capture a price snapshot with supplier, product, price-list revision, currency, dimensions and sheet price. Cutting rows freeze the chosen configured technology profile, machine feed and hourly rate. Later supplier or machine edits do not silently rewrite existing prices; POST /api/calculations/{id}/refresh-prices explicitly takes current offers and technology. Saved calculation revisions include snapshots.
Configurable product technologies now support versioned fields, rules, material components, operation routes, component-specific nesting caches, and frozen price/rate snapshots. See Channel Letters architecture and extension guide for lifecycle, cache inputs, demo-rate warnings, and steps to add another product technology without editing core code.
Vector imports preserve source contours and use a separate Geometry Review interpretation layer before nesting. See Geometry import and production interpretation for the roles, revision behavior, and current topology limitations.
Use #
Create a calculation, choose product type, then add a rectangle/circle or upload PDF/SVG/DXF. Product rules suggest a material group and list expected operations; detailed channel-letter assemblies still need manual rows. For a 1:10 drawing set source scale to 10. Select a material and run nesting. A persisted job status shows preparation, actual pieces processed, layout validation, saving, completion or failure; the Cancel button stops a running job. All sheets appear in one vertical workspace with a shared zoom and true sheet proportions. Edit an override by double-clicking a costing cell, then Save. The revision number advances with each Save. Share copies a read-only UI link. The Copilot panel works without an AI key using deterministic commands such as Calculate 300 rectangles 600x387 mm from 5 mm PMMA and Try to fit this on 3 sheets. Its conversation is saved per calculation and remains available after restoring a revision.
Use Print / PDF above the nesting workspace to open a vector print preview. Choose one or two nesting sheets per A4 page and Auto, Portrait, or Landscape orientation, then print or save as PDF from the browser. Drawings are proportionally scaled to the printable area; their dimensional labels remain in millimeters. The preview is a production overview, not a 1:1 cutting template.
Oversize parts remain unplaced. Select a part, enter a horizontal or vertical split position, then rerun nesting. The sheet layout can be exported as SVG; the table as CSV; the summary as PDF. Manual placement uses X/Y/rotation fields with collision and sheet-boundary validation.
API and data #
Core endpoints include POST /api/calculations, GET /api/calculations/{id}, POST /api/calculations/{id}/files, GET /api/calculations/{id}/parts, POST /api/calculations/{id}/nest, GET /api/calculations/{id}/totals, revision and share endpoints, and /api/settings. POST /api/calculations/{id}/nest returns HTTP 202 with a nesting_run_id; poll GET /api/nesting-runs/{id} for stage, progress, elapsed time, placed-piece count, current sheets, utilization and errors. POST /api/nesting-runs/{id}/cancel requests cancellation. A small in-process worker pool performs nesting, so no Redis service is needed for this deployment. Active jobs interrupted by a backend restart are marked failed and may be rerun. Job records and project Copilot history are stored in the database. GET /api/calculations/{id}/copilot/messages returns the primary project thread and messages; each message records its active revision. Tool actions use current calculation state, while history survives revisions. OpenAPI is available at /docs. Copilot calls the same constrained action layer exposed at /api/calculations/{id}/copilot/actions; it has no raw database tool.
Additional endpoints: /api/auth/login, /api/auth/me, /api/auth/logout, /api/profile, Admin /api/users, /api/suppliers, /api/suppliers/{id}/price-lists, /api/price-lists/{id}/products, /api/supplier-products/{id}/mapping, /api/materials/{id}/offers, /api/machines, /api/technology-profiles, /api/operation-rules, /api/calculations/{id}/operations, /api/ai-config, and Admin /api/audit-logs. API clients must retain cookies and send the signage_csrf cookie value as X-CSRF-Token on writes.
Copilot and voice #
local_adapter / deterministic is active when no provider is configured. Select Gemini or an OpenAI-compatible endpoint in AI settings, or set AI_PROVIDER and AI_MODEL in the backend environment. For Gemini set GEMINI_API_KEY; the server uses the google-genai SDK. For a local model set LOCAL_AI_BASE_URL and LOCAL_AI_API_KEY if needed; the endpoint must be reachable from the backend process. A local model is optional. CPU-only inference may be slow; choose hardware and quantization for the selected model separately.
The text Copilot and voice use the same project thread. The voice panel offers an offline conversational test mode using browser speech recognition and spoken replies. It requires a supported browser and HTTPS or localhost for microphone access. To use Gemini Live, configure AI_PROVIDER=gemini, AI_VOICE_MODEL to a Live-capable model, GEMINI_API_KEY, and enable voice in AI settings. The browser sends 16 kHz PCM to a server-side WebSocket bridge; the server holds the API key, executes only whitelisted tools, and returns audio and transcript events. Real Gemini Live audio was not exercised on this host because credentials are absent. Browser voice availability and real-world audio quality require testing after HTTPS and credentials are configured.
Shapely polygons preserve exterior rings and holes. PDF vectors come from PyMuPDF get_drawings(), not OCR. The nesting heuristic tests actual polygon distance/intersection with spacing and margin. It is a fast constructive heuristic, not a globally optimal optimizer. Prices are per configured sheet, allocated across parts by area, plus configured cutting time/rate. The demo material and cutting configuration must be reviewed before commercial quotes.
Tests and backups #
.venv/bin/pytest -q
cd frontend && npm run build
Back up both the database and source file storage. For local SQLite, copy signage.db after stopping the backend, plus storage/. For Compose, run docker compose exec -T db pg_dump -U signage signage > signage-backup.sql and back up the source_files volume. Restore SQL into a fresh database and restore files to their stored paths. Test restoration before relying on backups.
Known limits: complex PDF clipping/transparency semantics and DXF unitless files need artwork-specific validation; irregular contour nesting is heuristic; all authenticated users share calculation access (there are no organizations or ownership partitions yet); mixed-material nesting uses one sheet size per calculation; arbitrary PDF supplier catalogs need review; no DXF export yet. See TODO.md and SECURITY.md.
The older security review is reconciled item by item in SECURITY_RECONCILIATION.md. Current controls cap part quantity at 10,000, total nesting pieces at 20,000, dimensions at 100,000 mm, source scale at 0.001–1000, PDF pages at 100, vector contours at 5,000 and vector points at 200,000. Nesting runs in a killable subprocess with a configurable 180-second wall-clock safety ceiling. The solver reuses prepared orientations, keeps candidate anchors linear in placed contours, and checks cached bounds before exact polygon distance/intersection. Worker logs include geometry preparation, candidate generation, collision and GEOS predicate timings, slowest piece timings, and layout-validation time. A routine 32-piece circle layout takes about one second on the development server; the ceiling is not its expected runtime. Document parsing runs in a short-lived subprocess with a 30-second parser budget, memory/CPU limits and no backend secrets in its environment. Upload bodies are capped before multipart parsing; price-list imports additionally cap XLSX expansion, rows and cell length. Limits can be tuned with the MAX_* environment variables in security.py. HTTP rate counters are process-local and need edge or distributed limits for multiple replicas.
Nesting progress reflects real pieces processed within a staged job. The current “optimizing” stage validates geometry and reports the heuristic result; it does not run a second global optimization search. NESTING_PERFORMANCE.md records the 32-circle timeout investigation and before/after benchmarks. The in-process queue is suitable for one backend process. Use a durable external queue and shared cancellation mechanism before running multiple backend replicas or handling high concurrent nesting volume. Browser A4 PDF output preserves SVG vectors but still needs production print styling and printer-specific margin checks.