# svg-service backend Backend for SVG scheme upload, draft editing, pricing, diagnostics, publish preview, and publish lifecycle. ## Stack - Python 3.11 - FastAPI - SQLAlchemy async - PostgreSQL 16 - Docker Compose ## Runtime Default backend port: `9020` Health check: - `GET /healthz` Main API prefix: - `/api/v1` Auth header: - `X-API-Key` Default local admin key: - `admin-local-dev-key` ## Core lifecycle 1. Upload SVG 2. Normalize and persist structure 3. Enter editor flow through context + ensure draft 4. Edit sectors / groups / seats in current draft 5. Configure pricing and inspect diagnostics 6. Build pricing snapshot 7. Inspect publish readiness and publish preview 8. Publish current draft 9. If editing is needed after publish, create or ensure a new draft again ## Main concepts ### Scheme Top-level business entity. ### Scheme version Concrete version of the scheme. A version can be `draft` or `published`. ### Current version The version referenced by the scheme registry as active current. ### Draft Editable current version. All editor mutations and draft pricing operations must target a current draft version only. ### Published version Non-editable current version. If current version is published, editor flow must first create or ensure a new draft. ### Upload artifacts Stored technical artifacts, including: - original svg - sanitized svg - normalized json - display svg - publish preview json ## Editor entry flow ### 1. Inspect editor state `GET /api/v1/schemes/{scheme_id}/editor/context` Response tells whether: - current version is draft - editor is available - a new draft should be created - recommended action is `use_current_draft` or `create_draft` ### 2. Ensure editable draft `POST /api/v1/schemes/{scheme_id}/draft/ensure` Behavior: - if current version is already draft: returns it with `created=false` - if current version is published: clones current version into a new current draft and returns it with `created=true` Returned `scheme_version_id` should be reused as: - `expected_scheme_version_id` for draft reads and mutations. ## Optimistic concurrency Mutable draft flows support optimistic concurrency through query params: - `expected_current_scheme_version_id` - `expected_scheme_version_id` Typical typed conflicts: - `stale_current_version` - `stale_draft_version` - `draft_not_editable` - `publish_not_ready` ## Main operator routes ### System - `GET /healthz` - `GET /api/v1/ping` - `GET /api/v1/db/ping` - `GET /api/v1/manifest` ### Uploads - `POST /api/v1/schemes/upload` - `GET /api/v1/uploads` - `GET /api/v1/uploads/{upload_id}` - `GET /api/v1/uploads/{upload_id}/normalized` ### Scheme registry - `GET /api/v1/schemes` - `GET /api/v1/schemes/{scheme_id}` - `GET /api/v1/schemes/{scheme_id}/current` - `GET /api/v1/schemes/{scheme_id}/versions` - `POST /api/v1/schemes/{scheme_id}/versions` - `GET /api/v1/schemes/{scheme_id}/publish/validation` - `GET /api/v1/schemes/{scheme_id}/draft/publish-readiness` - `POST /api/v1/schemes/{scheme_id}/publish` - `POST /api/v1/schemes/{scheme_id}/unpublish` - `POST /api/v1/schemes/{scheme_id}/rollback` ### Editor / draft - `GET /api/v1/schemes/{scheme_id}/editor/context` - `POST /api/v1/schemes/{scheme_id}/draft/ensure` - `GET /api/v1/schemes/{scheme_id}/draft/summary` - `GET /api/v1/schemes/{scheme_id}/draft/structure` - `GET /api/v1/schemes/{scheme_id}/draft/validation` - `GET /api/v1/schemes/{scheme_id}/draft/compare-preview` - `GET /api/v1/schemes/{scheme_id}/draft/seats/records/{seat_record_id}` - `GET /api/v1/schemes/{scheme_id}/draft/sectors/records/{sector_record_id}` - `GET /api/v1/schemes/{scheme_id}/draft/groups/records/{group_record_id}` - `POST /api/v1/schemes/{scheme_id}/draft/sectors` - `POST /api/v1/schemes/{scheme_id}/draft/groups` - `DELETE /api/v1/schemes/{scheme_id}/draft/sectors/records/{sector_record_id}` - `DELETE /api/v1/schemes/{scheme_id}/draft/groups/records/{group_record_id}` - `PATCH /api/v1/schemes/{scheme_id}/draft/seats/records/{seat_record_id}` - `POST /api/v1/schemes/{scheme_id}/draft/seats/bulk` - `PATCH /api/v1/schemes/{scheme_id}/draft/sectors/records/{sector_record_id}` - `PATCH /api/v1/schemes/{scheme_id}/draft/groups/records/{group_record_id}` - `POST /api/v1/schemes/{scheme_id}/draft/repair-references` ### Pricing - `GET /api/v1/schemes/{scheme_id}/pricing` - `POST /api/v1/schemes/{scheme_id}/pricing/categories` - `PUT /api/v1/schemes/{scheme_id}/pricing/categories/{pricing_category_id}` - `DELETE /api/v1/schemes/{scheme_id}/pricing/categories/{pricing_category_id}` - `POST /api/v1/schemes/{scheme_id}/pricing/rules` - `PUT /api/v1/schemes/{scheme_id}/pricing/rules/{price_rule_id}` - `DELETE /api/v1/schemes/{scheme_id}/pricing/rules/{price_rule_id}` ### Pricing diagnostics - `GET /api/v1/schemes/{scheme_id}/pricing/coverage` - `GET /api/v1/schemes/{scheme_id}/pricing/unpriced-seats` - `GET /api/v1/schemes/{scheme_id}/pricing/explain/{seat_id}` - `GET /api/v1/schemes/{scheme_id}/pricing/rules/diagnostics` ### Publish preview - `POST /api/v1/schemes/{scheme_id}/draft/pricing/snapshot` - `GET /api/v1/schemes/{scheme_id}/draft/publish-preview` - `POST /api/v1/schemes/{scheme_id}/draft/remap/preview` - `POST /api/v1/schemes/{scheme_id}/draft/remap/apply` ### Structure read model - `GET /api/v1/schemes/{scheme_id}/current/sectors` - `GET /api/v1/schemes/{scheme_id}/current/groups` - `GET /api/v1/schemes/{scheme_id}/current/seats` - `GET /api/v1/schemes/{scheme_id}/current/seats/{seat_id}/price` - `GET /api/v1/schemes/{scheme_id}/current/svg` - `GET /api/v1/schemes/{scheme_id}/current/svg/display` - `GET /api/v1/schemes/{scheme_id}/current/svg/display/meta` ### Test mode - `GET /api/v1/schemes/{scheme_id}/test/seats/{seat_id}` ### Audit - `GET /api/v1/schemes/{scheme_id}/audit` ### Admin / ops - `GET /api/v1/admin/schemes/{scheme_id}/current/artifacts` - `GET /api/v1/admin/schemes/{scheme_id}/current/validation` - `POST /api/v1/admin/schemes/{scheme_id}/current/display/regenerate` - `POST /api/v1/admin/display/backfill` - `GET /api/v1/admin/artifacts/publish-preview/audit` - `POST /api/v1/admin/artifacts/publish-preview/cleanup` - `GET /api/v1/admin/schemes/{scheme_id}/pricing/categories/cleanup-preview` - `POST /api/v1/admin/schemes/{scheme_id}/pricing/categories/cleanup` ## Cleanup of test pricing data Cleanup endpoints are intended for removing diagnostic / test categories accidentally accumulated in a shared scheme. Preview candidates: `GET /api/v1/admin/schemes/{scheme_id}/pricing/categories/cleanup-preview` Execute cleanup: `POST /api/v1/admin/schemes/{scheme_id}/pricing/categories/cleanup` Safety notes: - use `dry_run=true` first - keep `delete_only_without_rules=true` unless you intentionally want a harder cleanup - prefer matching by prefixes instead of raw ids for repetitive test artifacts Helper script: - `backend/scripts/cleanup_test_pricing_data.sh` Example: `SCHEME_ID=... DRY_RUN=true ./backend/scripts/cleanup_test_pricing_data.sh` ## Typical local flow ### 1. Read current version `GET /api/v1/schemes/{scheme_id}/current` ### 2. Ensure draft `POST /api/v1/schemes/{scheme_id}/draft/ensure` Store returned: - `scheme_version_id` ### 3. Read draft state - `GET /draft/summary?expected_scheme_version_id=...` - `GET /draft/structure?expected_scheme_version_id=...` - `GET /draft/validation?expected_scheme_version_id=...` - `GET /draft/compare-preview?expected_scheme_version_id=...` ### 4. Perform editor mutations Pass: - `expected_scheme_version_id={draft_scheme_version_id}` on every mutation route. ### 5. Inspect pricing quality - `GET /pricing/coverage` - `GET /pricing/unpriced-seats` - `GET /pricing/explain/{seat_id}` - `GET /pricing/rules/diagnostics` ### 6. Build snapshot and inspect readiness - `POST /draft/pricing/snapshot` - `GET /draft/publish-readiness` - `GET /draft/publish-preview?refresh=true` ### 7. Publish - `POST /publish?expected_scheme_version_id=...` ## Regression Main operator regressions: - `backend/scripts/smoke_regression.sh` - `backend/scripts/editor_mutation_regression.sh` Run: `API_URL=http://127.0.0.1:9020 API_KEY=admin-local-dev-key SCHEME_ID=... ./backend/scripts/smoke_regression.sh` `API_URL=http://127.0.0.1:9020 API_KEY=admin-local-dev-key SCHEME_ID=... ./backend/scripts/editor_mutation_regression.sh`