- add backend README and refresh API map and smoke regression docs - add full backend smoke regression script - add admin pricing cleanup preview and dry-run endpoints - add helper script for test pricing cleanup - verify typed error contracts, draft flow, publish readiness and preview flows - verify publish preview retention and clean backend startup behavior
8.0 KiB
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
- Upload SVG
- Normalize and persist structure
- Enter editor flow through context + ensure draft
- Edit sectors / groups / seats in current draft
- Configure pricing and inspect diagnostics
- Build pricing snapshot
- Inspect publish readiness and publish preview
- Publish current draft
- 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_draftorcreate_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_idexpected_scheme_version_id
Typical typed conflicts:
stale_current_versionstale_draft_versiondraft_not_editablepublish_not_ready
Main operator routes
System
GET /healthzGET /api/v1/pingGET /api/v1/db/pingGET /api/v1/manifest
Uploads
POST /api/v1/schemes/uploadGET /api/v1/uploadsGET /api/v1/uploads/{upload_id}GET /api/v1/uploads/{upload_id}/normalized
Scheme registry
GET /api/v1/schemesGET /api/v1/schemes/{scheme_id}GET /api/v1/schemes/{scheme_id}/currentGET /api/v1/schemes/{scheme_id}/versionsPOST /api/v1/schemes/{scheme_id}/versionsGET /api/v1/schemes/{scheme_id}/publish/validationGET /api/v1/schemes/{scheme_id}/draft/publish-readinessPOST /api/v1/schemes/{scheme_id}/publishPOST /api/v1/schemes/{scheme_id}/unpublishPOST /api/v1/schemes/{scheme_id}/rollback
Editor / draft
GET /api/v1/schemes/{scheme_id}/editor/contextPOST /api/v1/schemes/{scheme_id}/draft/ensureGET /api/v1/schemes/{scheme_id}/draft/summaryGET /api/v1/schemes/{scheme_id}/draft/structureGET /api/v1/schemes/{scheme_id}/draft/validationGET /api/v1/schemes/{scheme_id}/draft/compare-previewGET /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/sectorsPOST /api/v1/schemes/{scheme_id}/draft/groupsDELETE /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/bulkPATCH /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}/pricingPOST /api/v1/schemes/{scheme_id}/pricing/categoriesPUT /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/rulesPUT /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/coverageGET /api/v1/schemes/{scheme_id}/pricing/unpriced-seatsGET /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/snapshotGET /api/v1/schemes/{scheme_id}/draft/publish-previewPOST /api/v1/schemes/{scheme_id}/draft/remap/previewPOST /api/v1/schemes/{scheme_id}/draft/remap/apply
Structure read model
GET /api/v1/schemes/{scheme_id}/current/sectorsGET /api/v1/schemes/{scheme_id}/current/groupsGET /api/v1/schemes/{scheme_id}/current/seatsGET /api/v1/schemes/{scheme_id}/current/seats/{seat_id}/priceGET /api/v1/schemes/{scheme_id}/current/svgGET /api/v1/schemes/{scheme_id}/current/svg/displayGET /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/artifactsGET /api/v1/admin/schemes/{scheme_id}/current/validationPOST /api/v1/admin/schemes/{scheme_id}/current/display/regeneratePOST /api/v1/admin/display/backfillGET /api/v1/admin/artifacts/publish-preview/auditPOST /api/v1/admin/artifacts/publish-preview/cleanupGET /api/v1/admin/schemes/{scheme_id}/pricing/categories/cleanup-previewPOST /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=truefirst - keep
delete_only_without_rules=trueunless 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/coverageGET /pricing/unpriced-seatsGET /pricing/explain/{seat_id}GET /pricing/rules/diagnostics
6. Build snapshot and inspect readiness
POST /draft/pricing/snapshotGET /draft/publish-readinessGET /draft/publish-preview?refresh=true
7. Publish
POST /publish?expected_scheme_version_id=...
Regression
Main operator regression:
backend/scripts/smoke_regression.sh
Run:
API_URL=http://127.0.0.1:9020 API_KEY=admin-local-dev-key SCHEME_ID=... ./backend/scripts/smoke_regression.sh