# svg-service backend Backend for SVG scheme upload, draft editing, pricing, 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 The backend works with a scheme lifecycle: 1. Upload SVG 2. Normalize and persist structure 3. Work in current draft 4. Create / update pricing 5. Build pricing snapshot 6. Inspect publish preview / readiness 7. Publish current draft 8. 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 Use this flow from frontend or operator scripts. ### 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` must 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` These guards prevent frontend/editor from mutating a stale draft after another version switch. Typical typed conflict payload: - `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` ## Typical local flow ## 1. Read current version Use: `GET /api/v1/schemes/{scheme_id}/current` ## 2. Ensure draft Use: `POST /api/v1/schemes/{scheme_id}/draft/ensure` Store returned: - `scheme_version_id` ## 3. Read draft state Use: - `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 Use: - `GET /pricing/coverage` - `GET /pricing/unpriced-seats` - `GET /pricing/explain/{seat_id}` - `GET /pricing/rules/diagnostics` ## 6. Create pricing snapshot Use: `POST /draft/pricing/snapshot?expected_scheme_version_id=...` ## 7. Inspect readiness Use: `GET /draft/publish-readiness?expected_scheme_version_id=...` ## 8. Publish Use: `POST /publish?expected_scheme_version_id=...` ## Typed error expectations Examples of stable typed errors already used in the service: ### Draft concurrency/state - `stale_current_version` - `stale_draft_version` - `draft_not_editable` ### Editor validation - `duplicate_seat_id` - `duplicate_seat_id_in_payload` - `duplicate_sector_id` - `duplicate_group_id` - `duplicate_sector_element_id` - `duplicate_group_element_id` - `unknown_sector_id` - `unknown_group_id` - `unknown_sector_ids` - `unknown_group_ids` - `unknown_target_sector_id` - `unknown_target_group_id` - `remap_filter_required` ### Pricing / publish - `invalid_amount` - `publish_not_ready` ## Draft summary semantics `GET /draft/summary` is the compact route for editor bootstrap. It returns: - current draft counters - validation summary - structure diff summary - publish readiness summary This route is intended for frontend side panels / header status / quick preflight. ## Notes - Draft-only routes must not mutate a published current version. - Published current version should require `draft/ensure` before any editor mutation. - Publish readiness can fail even if validation passes, for example when pricing snapshot is missing. - `api-map.md` and `smoke-regression.md` must be updated together with route changes.