# 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.yaml` - `GET /notificationsubscriptions?customerProfileId={id}&expand=eventTypeChannel` - `GET /notificationcategories?notificationCategoryId={id}&expand=eventTypeChannel` - `customers-crud-v2.yaml` - `GET /ovpaytokens?ovPayTokenId={id}` - `GET /devices?customerProfileId={id}` - `GET /customers?customerProfileId={id}` for person/email resource data - `SE-notifications.yaml` - `GET /customers/notificationpreferences` ## Runtime options ### Local FastAPI with real CRUD APIs ```powershell .\.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: ```powershell 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. ```powershell $env:USE_MOCK_DATA="true" $env:MOCK_SCENARIO="mixed" .\.venv\Scripts\python.exe -m uvicorn app.entrypoints.fastapi_app:app --reload ``` Open: ```text http://127.0.0.1:8000/docs ``` Health check: ```text 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 ```powershell .\.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: 1. Load customer `NotificationSubscriptions` with `NotificationPreferences` expanded to `EventTypeChannel`. 2. Load the default category definition with all `EventTypes` and `EventTypeChannels`. 3. Load customer resources for each channel resource type: - `resourceNameId = 8` -> `GET /devices?customerProfileId={id}` - `resourceNameId = 4` -> `GET /customers?customerProfileId={id}` and use the embedded `person` email data 4. For every default `EventTypeChannel`, write out resources explicitly. 5. If the channel is default-on and no preference exists, resource is active. 6. If the channel is default-off and no preference exists, resource is inactive. 7. A `NotificationPreference` row is interpreted as a deviation: - default-on + preference -> inactive unless explicit `isActive` says otherwise - default-off + preference -> active unless explicit `isActive` says otherwise 8. A null `resourceIdentifier` applies 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 `NotificationPreference` has explicit `isActive`; this example supports it, but also works when preference existence alone means deviation.