FastAPI + Celery + Postgres + Redis SaaS skeleton for Shopify dynamic pricing with guardrails, rollback, and audit trail.
A working skeleton for a dynamic-pricing service that can run against a Shopify catalog without exposing the merchant to the usual blast radius. The interesting problem is that it's keeping a catalog-scale pricing engine from doing damage. This repo splits authority across four layers: the pricing engine proposes a price, the merchant policy gates it, the Shopify client executes the approved change, and the audit trail records every event so any change is reversible. The operational shape is Docker Compose, Celery workers, structured logs, health and readiness probes, and an Alembic migration path.
| Layer | Technologies |
|---|---|
| API | FastAPI, Pydantic v2, uvicorn |
| Workers | Celery, Redis broker |
| Data | Postgres 16, SQLAlchemy 2.x, Alembic |
| Integration | Shopify Admin API (stub), HMAC webhook verification |
| Observability | Structured JSON logs, X-Process-Time middleware, Sentry (optional) |
| Frontend | React 18, TypeScript, Vite |
| Infra | Docker Compose, GitHub Actions |
- Authority separation. The engine proposes, the policy gates, Shopify executes, the audit records. No layer holds two responsibilities. This is what makes the system safe to run unattended on a real catalog.
- Guardrails are inputs, not exceptions.
PricingEngine(min_margin, max_change_pct, new_product_protection_days)accepts its constraints in its constructor. The engine cannot produce a suggestion that violates them. There is notry/exceptpath that "handles" a guardrail breach because no such breach is reachable. - Audit trail is the rollback substrate. Every applied price change writes an append-only
price_eventsrow. Rolling back N steps reads N events back and writes a new event withsource=rollback, so the rollback itself is also auditable. - Observability is first-class. Structured logs are JSON. Every response carries
X-Process-Time./health/readyprobes Postgres and Redis individually so a degraded dependency is visible at the load-balancer layer.
| Area | Status | Notes |
|---|---|---|
| Shopify Admin API client | Stub | Method signature and HMAC verification are real; the wire call is a no-op. Cutover plan in TECHNICAL_DEBT.md. |
| Shopify OAuth flow | Not implemented | access_token field exists on merchants; install/callback routes are not wired. |
| Frontend | Operator console stub | Single page reads /health/ready. The suggestion-review and rollback UI are the next milestone. |
| Pricing signal | Toy proposal | The raw signal is demand_signal - inventory_pressure. The guardrail layer is the real engineering. Replacing the proposal step does not change the safety surface. |
| Auth on API | None | API routes are unauthenticated. Add merchant-scoped JWT or session middleware before exposing publicly. |
+--------------------+
demand / | PricingEngine | pure function
inventory ---> | (guardrails as | --+
signal | constructor args)| |
+--------------------+ | PriceSuggestion
v
+--------------------+
| MerchantPolicy | approve / hold / reject
+--------------------+
|
v approved
+--------------------+ +-------------------+
| AuditTrail.record |------->| price_events |
+--------------------+ | (append-only) |
| +-------------------+
v
+--------------------+
| ShopifyClient | external write
+--------------------+
Data flow on a price update:
- Demand or inventory event triggers a Celery task (or an HTTP
/pricing/suggestcall). - Engine returns a
PriceSuggestionwith the guardrails that fired. - Policy reads the suggestion and emits an
ApprovalDecision— auto-apply, hold for review, or reject. - On approval,
AuditTrail.recordwrites aprice_eventsrow and updatesproducts.current_pricein the same flush. ShopifyClient.update_variant_pricepropagates the change to the merchant's storefront (currently stubbed).- Rollback reads the last N
price_eventsfor a product and emits a new event withsource=rollback.
Requirements: Docker, Docker Compose.
cp .env.example .env
# edit .env if you need to override defaults
docker compose up --build
The API serves on http://localhost:8000. The frontend dev server is cd frontend && npm install && npm run dev (port 5173, proxies /api to the backend).
Bootstrapping the schema is handled by the api service on startup (alembic upgrade head). If running the API outside Docker:
cd backend
pip install -r requirements-dev.txt
alembic upgrade head
uvicorn app.main:app --reload
cd backend
pip install -r requirements-dev.txt
pytest
The suite covers the pricing engine's guardrail surface, the merchant policy's auto-apply window, and the health endpoints. The Shopify client and Celery task layers are not exercised by tests in this repo — see TECHNICAL_DEBT.md.
backend/
app/
main.py FastAPI app, middleware, route wiring
config.py pydantic-settings
database.py SQLAlchemy engine, session, Base
models.py Merchant, Product, PriceEvent
schemas.py Pydantic I/O models
policy.py MerchantPolicy
observability.py logging + Sentry init
pricing/engine.py PricingEngine + PricingInputs
audit/service.py AuditTrail (record, history, rollback)
shopify/client.py Stub + HMAC webhook verification
api/ health, pricing, audit routers
tasks/ Celery app + pricing_tasks
alembic/ migrations
tests/
frontend/ React + TS operator console (stub)
docker-compose.yml
See ROADMAP.md.
MIT. See LICENSE.
Mac McFall — m87studio.net — github.com/MacFall7