The GP Booking App (gp_booking_app) is a Django 5.2 multi-tenant healthcare platform serving GP practices, dental surgeries, and partner organisations. It is designed to meet NHS DSPT/DTAC Category 3 compliance requirements.
Live URL: https://gp.veripath.co.uk
Codebase: /var/www/html/gp_booking_app/
┌──────────────────────────────────────────────────────────────────┐
│ Users (Browsers) │
│ gp.veripath.co.uk │ dental.veripath.co.uk │ patient.* │
└──────────────────────┬───────────────────────────────────────────┘
│ HTTPS (Nginx reverse proxy)
┌──────────────────────▼───────────────────────────────────────────┐
│ Nginx (SSL, security headers) │
└──────────────────────┬───────────────────────────────────────────┘
│
┌──────────────────────▼───────────────────────────────────────────┐
│ Django App (Gunicorn, 5 workers) │
│ ┌─────────┐ ┌──────────┐ ┌──────────┐ ┌───────────┐ │
│ │ GP │ │ Dental │ │ Partner │ │ Patient │ │
│ │ Apps │ │ Apps │ │ Apps │ │ Portal │ │
│ └────┬────┘ └────┬─────┘ └────┬─────┘ └─────┬─────┘ │
│ │ │ │ │ │
│ ┌────▼───────────▼────────────▼──────────────▼─────┐ │
│ │ RBAC Middleware + Tenant Middleware │ │
│ └──────────────────────┬───────────────────────────┘ │
└──────────────────────────┼───────────────────────────────────────┘
│
┌──────────────────────────▼───────────────────────────────────────┐
│ Database Router (tenancy.routers.SectorDatabaseRouter) │
│ │
│ ┌────────────────┐ ┌────────────────┐ ┌──────────────────┐ │
│ │ dsp_clinic │ │ veripath_dental│ │ Client DB │ │
│ │ (GP Sector) │ │ (Dental Sector)│ │ (Infrastructure) │ │
│ └────────────────┘ └────────────────┘ └──────────────────┘ │
└───────────────────────────────────────────────────────────────────┘
│
┌──────────────────────────▼───────────────────────────────────────┐
│ Supporting Services │
│ Redis │ Keycloak │ MailHog │ AES Portal │ NHS APIs │
└───────────────────────────────────────────────────────────────────┘
| Component | Technology |
|---|---|
| Framework | Django 5.2 (Python 3.11) |
| Database | PostgreSQL 15+ |
| Cache/Broker | Redis |
| Task Queue | Celery |
| Web Server | Nginx + Gunicorn |
| Container | Docker / Docker Compose |
| Auth | django-oidc + Keycloak |
| API | Django REST Framework |
| Encryption | django-encrypted-model-fields (Fernet) |
| Security Scanning | Lynis |
| E-Signatures | AES Portal (PAdES-B-LTA) |
The application uses a multi-database architecture with a custom SectorDatabaseRouter for data segregation.
dsp_clinic (default) — GP SectorThe primary database holding all GP-related data. Contains the majority of application tables.
veripath_dental — Dental SectorSeparate database for dental practice data, including:
dental_dentalpractice — Practice detailsdental_dentalprovider — Clinician/provider records (GDC-registered)dental_dentalappointment — Dental appointmentsdental_fp17claim — NHS FP17 claim formsdental_dentaltransaction — Payment transactions (Stripe)dental_dentalclinicalnote — Clinical notes with tooth mappingdental_dentalscheduleblock — Provider schedule blocksUsed by the client_monitor app (unmanaged models reading from external infrastructure):
infrastructure_clientcapacitysnapshot — DB, disk, CPU, memory metrics per tenantinfrastructure_clienttunnelhealth — SSH tunnel health checksinfrastructure_clientbackuplog — Backup status trackingNote: The GP and Dental databases share the same PostgreSQL instance but are separate logical databases. The
SectorDatabaseRouterroutes queries based on Django model app labels.
dsp_clinic/, core/)tenancy.middleware.TenantMiddleware)tenancy/)Sector ──1:N──> Tenant
users/)CustomUser (AbstractUser)
├── role: PATIENT | CLINICIAN | RECEPTIONIST | PRACTICE_MANAGER | ADMIN
├── Dental roles: DENTAL_ADMIN, DENTAL_CLINICIAN, DENTAL_HYGIENIST, etc.
├── Partner roles: ORG_ADMIN, PARTNER_ADMIN, PARTNER_REGISTERED_MANAGER, etc.
├── nhs_number (encrypted)
├── phone_number, address, postcode (encrypted)
├── date_of_birth (encrypted)
├── verification_level: P5 / P9 / UNVERIFIED
├── org_id (partner org scoping)
└── prescriber_type + professional_registration_number
Supports 20+ roles across GP, Dental, and Partner sectors. All PII encrypted at rest.
appointments/)Appointment ──> Patient (CustomUser)
──> Clinician (CustomUser)
──> Clinic (slot_management)
──> AppointmentType
──> ReminderLog (notifications)
ProviderSession ──> Clinician
──> Clinic
──> AppointmentTypes (M2M)
BookingConfig ──> Practice (1:1)
AppointmentMessage ──> Practice
Key features:
reason fieldPricing fields on Appointment: price_amount, deposit_amount, payment_status (PENDING / DEPOSIT_PAID / PAID / REFUNDED / CANCELLED), stripe_payment_intent_id. Each AppointmentType has a price field (GBP).
clinical_data/)ClinicalNote ──> Appointment
──> Author (Clinician)
Prescription ──> Patient
──> Prescribing Clinician
──> Appointment (optional)
──> PrescriptionAuditLog
PatientHealthProfile ──> Patient (1:1)
PatientProfile ──> Patient (1:1)
──> PartnerOrg (optional)
──> PatientMedication (1:N)
──> PatientCondition (1:N)
──> PatientAllergy (1:N)
──> PatientConsent (1:N)
PatientImage ──> Patient
──> Appointment (optional)
Key features:
PrescriptionService (clinical_data/services.py) — Orchestrates the full prescription lifecycle:
DRAFT ──issue_prescription()──> ISSUED ──sign_with_aes()──> SIGNED
│ │
└──cancel_prescription()──> CANCELLED
| Method | Function |
|---|---|
validate_medication() |
Validates medication JSON structure (name, form, strength, quantity, dosage) |
create_audit_entry() |
Creates PrescriptionAuditLog for every action |
issue_prescription() |
Issues locally + optionally sends to NHS EPS FHIR API. Stores eps_task_id in medication_details |
sign_with_aes() |
Signs via AES Portal producing PAdES-B-LTA PDF. Triggers GP notification + pharmacy notification |
cancel_prescription() |
Cancels locally + optionally cancels via NHS EPS FHIR API |
_prescription_to_fhir_task() |
Converts Prescription model to FHIR Task resource for EPS |
slot_management/)Clinic ──1:N──> SlotTemplate
pcn_booking/)PCNClinic ──> Clinic
dental/)DentalPractice ──1:N──> DentalProvider
──1:N──> DentalAppointment
──1:N──> DentalRecallRule
──1:N──> DentalAppointmentCategory
──1:1──> DentalPaymentConfig
DentalAppointment ──> Patient
──> DentalProvider
──> DentalPractice
──1:1──> FP17Claim
──1:N──> DentalClinicalNote
──1:N──> DentalTransaction
DentalScheduleBlock ──> DentalPractice
──> DentalProvider
──> DentalAppointmentCategory (zone)
Key features:
DentalPaymentConfig, DentalTransaction)partner/)Organisation ──1:1──> PartnerOrg
──1:N──> OnboardingAuditLog
PartnerOrg ──1:N──> BreachReport
──1:N──> MedicalDevice
──1:N──> PatientProfile (via clinical_data)
Key features:
onboarding/)ICB ──1:N──> Practice
Practice ──1:N──> StaffRoleRecord
──1:N──> Invitation
──1:N──> InformationAssetRegister
──1:N──> RecordOfProcessingActivity (ROPA)
──1:N──> RetentionTimetable
──1:N──> PolicyAcknowledgment
──1:N──> PolicyDocument (via M2M)
StaffRoleRecord ──> User
──> Practice
──1:N──> StaffOnboardingAudit
RiskManagementRegister (system-wide)
Comprehensive DSPT compliance features:
staff_management/)LeaveType ──> Practice
LeaveRequest ──> StaffRoleRecord
──> LeaveType
LeaveBalance ──> StaffRoleRecord
──> LeaveType
PracticeTeam ──> Practice
TeamMembership ──> PracticeTeam
──> StaffRoleRecord
notifications/)ReminderLog ──> Appointment
dashboards/)FormalRole (SIRO, CLINICIAN, RECEPTIONIST, etc.)
UserRole ──> User
──> FormalRole
Dashboard (PATIENT, RECEPTIONIST, CLINICIAN, PRACTICE_MANAGER, etc.)
DashboardPermission ──> FormalRole
──> Dashboard
Eight dashboards:
| Dashboard | Data Category | Primary Users |
|---|---|---|
| Patient Dashboard | Clinical | Patients |
| Receptionist Dashboard | Administrative | Receptionists |
| Clinician/GP Dashboard | Clinical | Clinicians |
| Practice Manager Dashboard | Administrative | Practice Managers |
| ICB/Regional Admin Dashboard | Commercial | ICB Admins |
| Super-User/System Admin Dashboard | System | System Admins |
| Accounts & Contract Management | Commercial | Billing Admins |
| Compliance Administration | Administrative | Compliance Officers |
Data segregation enforced: clinical roles cannot access commercial dashboards and vice versa.
auditing/)AuditLog ──> User (optional)
clinical_safety/)HazardLog (DCB0129)
reporting/)ICBContract ──> Practice
──> ICB
PlatformActivityEvent ──> Practice
──> User (optional)
MonthlyActivitySummary ──> Practice
CQCAnalyticsCache ──> Practice
ComplianceActivity ──> Practice
──> PartnerOrg (optional)
integrations/)NHSIntegrationConfig (GP_CONNECT, PDS, EPS, NHS_NOTIFY, AES)
EmailIntegrationConfig (SMTP, NHSmail)
AgentConfig ──> User (optional)
LynisScanResult ──> User (optional)
The AES system produces PAdES-B-LTA signed PDF prescriptions via a dedicated aes-portal service.
┌──────────────────────────────────────────────────────────────────┐
│ AES Signing Flow │
│ │
│ PrescriptionService.sign_with_aes() │
│ │ │
│ ▼ │
│ AESClient (clinical_data/integrations/aes_client.py) │
│ │ │
│ ├── _get_token() ───> Keycloak (client_credentials grant) │
│ │ │
│ ├── sign_prescription() ──POST /api/sign──> aes-portal:8000 │
│ │ Returns: signed_pdf_path, prescription_id │
│ │ │
│ ├── list_pharmacies() ──GET /api/pharmacies──> aes-portal │
│ │ Returns: [{id, name, address, postcode, ods_code}] │
│ │ │
│ └── verify_connectivity() ──GET /──> aes-portal │
│ │
│ After signing: │
│ ├── GPNotificationService.record_prescription_notification()│
│ └── GPNotificationService.send_pharmacy_notification() │
└───────────────────────────────────────────────────────────────────┘
Configuration: Stored encrypted in NHSIntegrationConfig where integration_type = 'AES':
base_url — AES Portal endpoint (default: http://aes-portal:8000)client_id — Keycloak service account (default: aes-signer-sa)client_secret — Keycloak client secret (encrypted at rest)additional_config.org_id — Organisation identifier sent with each signing requestPrescription signing lifecycle:
DRAFT ──issue_prescription()──> ISSUED ──sign_with_aes()──> SIGNED
│
EPS FHIR API ──> eps_task_id
│
medication_details {
eps_task_id,
aes_signed_pdf_path,
aes_prescription_id,
aes_signed_at,
aes_status: "signed",
aes_delivery_status
}
ai_sidebar/)NoteMapping (URL pattern → note path)
SidebarNote (cached rendered HTML)
SidebarSettings (key-value config)
patient_portal/)Conversation ──> Patient
──> Practice
Message ──> Conversation
──> Sender (User)
PushSubscription ──> Patient
client_monitor/)ClientCapacitySnapshot ──> Tenant
ClientTunnelHealth ──> Tenant
ClientBackupLog ──> Tenant
infrastructure/)Listed in INSTALLED_APPS as a shell app with no Python files yet. Reserved for future infrastructure-level functionality (e.g. system health dashboards, configuration management).
The app has basic payment capture but is not yet a complete private practice billing system.
| Feature | Location | Status |
|---|---|---|
| Stripe PaymentIntents (one-off) | dental/payment.py |
✅ Working |
| Stripe webhook handling | dental/webhooks.py |
✅ Working |
| Payment status tracking | Appointment.payment_status enum |
✅ Implemented |
| Pricing per appointment type | AppointmentType.price (GBP) |
✅ Implemented |
| Deposit capture (configurable %) | DentalPaymentConfig.deposit_percentage |
✅ Implemented |
| Dental transaction ledger | DentalTransaction model |
✅ Implemented |
| ICB contract management | ICBContract model + Accounts Dashboard |
✅ Implemented |
| FP17 NHS claim forms | FP17Claim model |
✅ Implemented |
| Feature | Typical Solution | Status |
|---|---|---|
| Healthcode insurance billing | Healthcode API (AXA, Bupa, Aviva) | ❌ Not built |
| Insurance company/policy models | PatientInsurancePolicy, AuthorizationNumber |
❌ Not built |
| Invoice generation | Invoice model, line items, PDF rendering |
❌ Not built |
| Billing ledger (per patient) | Statement history, credit notes, write-offs | ❌ Not built |
| Xero / QuickBooks sync | Accounting export API | ❌ Not built |
| Payment allocation (partials) | Splitting payments across invoices | ❌ Not built |
| Revenue reporting dashboard | P&L, aged receivables, claims aging | ❌ Not built |
| Private prescription billing | FP10PCD templates, prescription charges | ❌ Not built |
services/nhs_notify.py)Sends appointment reminders and notifications via the NHS Notify API (sandbox.api.service.nhs.uk/comms/v1/messages). Supports NHS App, Email, and SMS delivery channels. Uses NHS API key for authentication.
services/email_service.py, services/mail_engine.py)SMTP-based email delivery via NHSmail bridge. Supports appointment confirmations, invitation emails, and a compose UI for ad-hoc messaging. Configured via DEFAULT_FROM_EMAIL in settings.
| Integration | Purpose | Endpoint |
|---|---|---|
| GP Connect | Patient record retrieval (FHIR) | clinical_data/integrations/gp_connect.py |
| PDS | Personal Demographics Service | Configured in settings |
| EPS | Electronic Prescription Service | clinical_data/integrations/eps_fhir.py |
| AES | Advanced Electronic Signature (PAdES-B-LTA) | clinical_data/integrations/aes_client.py → aes-portal:/api/sign |
| NHS Notify | Appointment reminders | services/nhs_notify.py |
| NHS OAuth2 | Authentication | clinical_data/integrations/nhs_auth.py |
All integrations operate in sandbox mode by default (USE_MOCK_EPS = True).
┌─────────────────────┐ ┌───────────────────────┐
│ CustomUser │ │ Clinic │
│ (users_customuser) │ │ (slot_management) │
├─────────────────────┤ ├───────────────────────┤
│ role: PATIENT │ │ name, address, phone │
│ nhs_number (enc) │ │ is_active │
│ role │ └───────────┬───────────┘
└────────┬────────────┘ │
│ │
│ patient ┌───────┴───────┐
▼ │ │
┌────────────────────┐ ┌────────▼──┐ ┌────────▼────────┐
│ Appointment │ │ SlotTemp │ │ PCNClinic │
│ (appointments) │ │ late │ │ (pcn_booking) │
├────────────────────┤ │ (slot_man)│ └─────────────────┘
│ patient FK ────────┼───┘ │
│ clinician FK ──────┼──┐ ┌────────┘
│ clinic FK ─────────┼──┤ │
│ appointment_time │ │ │ ┌──────────────────┐
│ reason (enc) │ │ │ │ AppointmentType │
│ status │ │ │ │ price (GBP) │
│ price_amount │ │ │ └──────────────────┘
│ deposit_amount │ │ │
│ payment_status │ │ │
│ stripe_intent_id │ │ │
└────────┬────────────┘ │ │
│ │ │
│ clinical │ │
▼ │ │
┌──────────────────┐ │ │ ┌──────────────────┐
│ ClinicalNote │ │ │ │ Practice │
│ (clinical_data) │ │ │ │ (onboarding) │
├──────────────────┤ │ │ ├──────────────────┤
│ appointment FK │ │ │ │ name, ods_code │
│ author FK (clin) │ │ │ │ icb FK ────────┐ │
│ notes_text (enc) │ │ │ │ clinic FK ─────┘ │
│ snomed_code │ │ │ └──────────────────┘
└──────────────────┘ │ │
│ │
┌──────────────────────┐ │ │
│ Prescription │ │ │
│ (clinical_data) │ │ │
├──────────────────────┤ │ │
│ patient FK │ │ │
│ clinician FK │ │ │
│ medication_name │ │ │
│ medication_details { │ │ │
│ eps_task_id, │ │ │
│ aes_signed_pdf_path│ │ │
│ aes_status, │ │ │
│ pharmacy_id │ │ │
│ } │ │ │
│ status │ │ │
│ eps_integration │ │ │
└──────────────────────┘ │ │
│ │
┌──────────────────┐ │ │
│ ReminderLog │ │ │
│ (notifications) │ │ │
├──────────────────┤ │ │
│ appointment FK ──┼─────┘ │
│ cancellation_tok │ │
│ channel │ │
│ status │ │
└──────────────────┘ │
│
┌──────────────────┐ │
│ StaffRoleRecord │ │
│ (onboarding) │ │
├──────────────────┤ │
│ user FK │ │
│ practice FK ─────┼────────┘
│ role_title │
│ access_level │
│ is_active │
└──────────────────┘
integrations appAll sensitive patient and staff data is encrypted at rest using django-encrypted-model-fields (Fernet symmetric encryption):
gp_booking_app Docker containergp.veripath.co.ukauth.veripath.co.uk (realm: veripath)wiki.veripath.co.ukaes-portal:8000| Service | Container | Purpose |
|---|---|---|
app |
gp_booking_app |
Django + Gunicorn |
celery_worker |
(separate) | Async task processing |
celery_beat |
gp_booking_celery_beat |
Scheduled tasks |
mailhog |
mailhog |
Dev email capture |
aes-portal |
(external) | PAdES-B-LTA signing service |
| Nginx | (host or container) | Reverse proxy + SSL |
scripts/)| Script | Purpose |
|---|---|
entrypoint.sh |
Container startup: database wait, migrations, static collection |
deploy.sh |
Build/deploy automation |
onboard_dental_practice.py |
Automated dental practice setup |
audit-dependencies.sh |
Dependency vulnerability scanning |
migrate_env_settings.sh |
Environment migration helper |
# Deploy
/opt/deploy-veripath-site.sh
# Management
python manage.py migrate
python manage.py createsuperuser
python manage.py update_agent_prompts