Bifrost synchronises patient data between the SyNCH CCMDD APIs and us.
- Python 3.10 or newer
uv
- Apply migrations:
uv run ./manage.py migrate- Create an admin user if you want to use the Django admin:
uv run ./manage.py createsuperuserStart the Django development server:
uv run ./manage.py runserverTo run Celery against RabbitMQ locally, point the CELERY_BROKER_URL environment variable at your broker if you are not using the default guest account on localhost.
Sync uses these CCMDD settings:
CCMDD_BASE_URLCCMDD_USERNAMECCMDD_PASSWORD
EDRWeb appointment reminder sync uses these settings:
EDRWEB_BASE_URLEDRWEB_USERNAMEEDRWEB_PASSWORD
The post-sync Turn import uses these settings:
TURN_BASE_URLTURN_TOKEN
The OTP delivery API uses these settings:
TURN_OTP_TOKENTURN_OTP_TEMPLATE_NAMESPACETURN_OTP_TEMPLATE_NAMETURN_OTP_TEMPLATE_LANGUAGEOTP_DELIVERY_THROTTLE_RATE
This repo also contains standalone Turn.io Lua apps under turn_apps/. They are separate deployables from Django with their own tests and packaging.
Current Turn app:
turn_apps/synch_delivery_failures: suppresses SynCH reminders after permanent WhatsApp delivery failures (131026,131050) and records the first failure insynch_delivery_failure_message_id
Run Turn app commands from the app directory:
cd turn_apps/synch_delivery_failures
make test
make buildSentry is optional. Bifrost only initializes Sentry when SENTRY_DSN is set. You can further configure it with SENTRY_ENVIRONMENT, SENTRY_RELEASE, SENTRY_SEND_DEFAULT_PII, SENTRY_TRACES_SAMPLE_RATE, SENTRY_PROFILES_SAMPLE_RATE, and SENTRY_DEBUG.
Start a worker:
uv run celery -A bifrost worker --loglevel=infoStart Celery Beat:
uv run celery -A bifrost beat --loglevel=infoCelery Beat schedules CCMDD synchronization every 5 minutes. The scheduled task syncs facilities, prescriptions, and patients, refreshes next-appointment contact fields in Turn for all patients, imports qualifying new patients into Turn, and then handles changed patient phone numbers under a single top-level lock, so only one full CCMDD sync run can proceed at a time even if multiple workers are active. Turn sync uses the most recent valid patient phone number across prescriptions, writes synch_patient_id from the raw CCMDD patient identifier, treats appointments as usable only when they are on or after today and resolve to a facility with a non-blank name, and falls back to the most recent prescription with a usable facility name whenever no usable upcoming appointment remains after that filtering. In that fallback case it keeps synch_next_appointment_date blank but still populates the facility contact fields. Changed phone numbers retire the previous Turn contact by disabling synch_reminders, then set synch_new_user on the new Turn contact after the appointment fields have been refreshed.
Celery Beat schedules the EDRWeb appointment reminder delta pull every 4 hours and full reconciliation weekly. After each completed EDRWeb pull commits local snapshots, Bifrost refreshes EDRWeb Turn contact fields from all stored EDRWebPatient rows. Active EDRWeb patients get edrweb_patient_id and appointment context fields. Inactive EDRWeb patients get edrweb_reminders set to False without clearing historical EDRWeb context fields.
Useful local URLs:
- App health endpoint:
http://127.0.0.1:8000/health - API schema:
http://127.0.0.1:8000/api/schema/ - Swagger docs:
http://127.0.0.1:8000/api/docs/ - Django admin:
http://127.0.0.1:8000/admin/
There is a base bifrost.settings.base settings module for local development, which is extended by bifrost.settings.production for running in production environments.
If SENTRY_DSN environment variable is set, sentry will be configured.
bifrost.settings.production requires the SECRET_KEY, ALLOWED_HOSTS, DATABASE_URL, and CSRF_TRUSTED_ORIGINS environment variables to be set at runtime.
The docker image uses bifrost.settings.production by default, so the required environment variables must be set when running the container.
GitHub Actions publishes the image to ghcr.io/<owner>/<repo> on every branch push using a branch-prefixed SHA tag, and on pushes of tags matching v* using the semantic version tag.
Build the image:
docker build -t bifrost .To run the app against PostgreSQL in Docker, start a database container on a shared network and point DATABASE_URL at it:
docker network create bifrost
docker run -d --rm \
--name bifrost-db \
--network bifrost \
-e POSTGRES_DB=bifrost \
-e POSTGRES_USER=bifrost \
-e POSTGRES_PASSWORD=bifrost \
postgres:16
docker run --rm -p 8000:8000 \
--network bifrost \
-e SECRET_KEY=change-me \
-e ALLOWED_HOSTS='*' \
-e DATABASE_URL='postgresql://bifrost:bifrost@bifrost-db:5432/bifrost' \
bifrostAfter every code change, run the full local verification suite:
uv run ruff format .
uv run ruff check --fix .
uv run ty check
uv run ./manage.py test