4.4 KiB
4.4 KiB
Notification preferences transformation service - Python example
Reference implementation for the Service Engine GET /customers/notificationpreferences endpoint. It composes raw CRUD API data into the frontend-friendly response model from SE-notifications.yaml.
Supplied contracts used
notifications-crud.yamlGET /notificationsubscriptions?customerProfileId={id}&expand=eventTypeChannelGET /notificationcategories?notificationCategoryId={id}&expand=eventTypeChannel
customers-crud-v2.yamlGET /ovpaytokens?ovPayTokenId={id}GET /devices?customerProfileId={id}GET /customers?customerProfileId={id}for person/email resource data
SE-notifications.yamlGET /customers/notificationpreferences
Runtime options
Local FastAPI with real CRUD APIs
.\.venv\Scripts\python.exe -m pip install -r requirements.txt
$env:NOTIFICATIONS_CRUD_BASE_URL="https://your-notifications-crud-api.example.nl"
$env:CUSTOMERS_CRUD_BASE_URL="https://your-customers-crud-api.example.nl"
.\.venv\Scripts\python.exe -m uvicorn app.entrypoints.fastapi_app:app --reload
Call:
Invoke-RestMethod `
-Uri "http://127.0.0.1:8000/customers/notificationpreferences?deviceId=device-1" `
-Headers @{"X-HTM-CUSTOMER-PROFILE-ID-HEADER"="42"}
Local FastAPI with mock data
Use mock mode when you want to demo or test the transformation without live CRUD APIs.
$env:USE_MOCK_DATA="true"
$env:MOCK_SCENARIO="mixed"
.\.venv\Scripts\python.exe -m uvicorn app.entrypoints.fastapi_app:app --reload
Open:
http://127.0.0.1:8000/docs
Health check:
http://127.0.0.1:8000/health
Available mock scenarios:
| Scenario | Purpose |
|---|---|
mixed |
Default-on push, default-off email, plus customer deviations |
defaults_only |
No customer preferences, so only defaults are applied |
global_override |
Preference with resourceIdentifier = null applies to all resources |
inactive_subscription |
Subscription is returned but marked inactive |
Azure Function
Use app/entrypoints/azure_function.py as the HTTP-trigger body. Keep the app/services and app/domain modules unchanged.
Running automated tests
.\.venv\Scripts\python.exe -m pytest
The tests cover:
- default-on channel without preference -> active
- default-on channel with preference -> inactive deviation
- default-off channel without preference -> inactive
- default-off channel with preference -> active deviation
- global preference where
resourceIdentifier = null - device filtering via
deviceId - inactive subscription still returned as inactive
Design choice
The implementation deliberately separates:
- HTTP entrypoints: request/response only
- CRUD client: raw REST calls only
- Mock CRUD client: deterministic local/demo data
- Service layer: composition and transformation logic
- Domain models: frontend/SE response shape
This makes the same transformation reusable in Azure Functions, Container Apps, and tests.
Business rule interpretation
For each customer subscription:
- Load customer
NotificationSubscriptionswithNotificationPreferencesexpanded toEventTypeChannel. - Load the default category definition with all
EventTypesandEventTypeChannels. - Load customer resources for each channel resource type:
resourceNameId = 8->GET /devices?customerProfileId={id}resourceNameId = 4->GET /customers?customerProfileId={id}and use the embeddedpersonemail data
- For every default
EventTypeChannel, write out resources explicitly. - If the channel is default-on and no preference exists, resource is active.
- If the channel is default-off and no preference exists, resource is inactive.
- A
NotificationPreferencerow is interpreted as a deviation:- default-on + preference -> inactive unless explicit
isActivesays otherwise - default-off + preference -> active unless explicit
isActivesays otherwise
- default-on + preference -> inactive unless explicit
- A null
resourceIdentifierapplies to all resources for that EventTypeChannel.
Production hardening points
- Add authentication propagation from the SE API to both CRUD APIs.
- Decide whether category defaults should be cached across requests.
- Decide whether missing category defaults should become
502 Bad Gateway, partial data, or an empty category. - Confirm whether the real
NotificationPreferencehas explicitisActive; this example supports it, but also works when preference existence alone means deviation.