Models¶
SQLAlchemy ORM models for authentication database.
This module defines the database schema for user authentication, role-based access control (RBAC), and messaging. All models use SQLAlchemy 2.0 declarative style with Mapped type hints for enhanced type safety.
The schema includes: - User: User accounts with credentials and TOTP settings - Role: Role definitions for RBAC - user_role: Many-to-many association table linking users to roles - PatientMetadata: Application-specific patient metadata (activation status, etc.) - Conversation: Messaging thread about a patient - ConversationParticipant: Who is in a conversation - Message: SQL projection of FHIR Communication resources
user_role
module-attribute
¶
user_role = Table('user_role', metadata, Column('user_id', ForeignKey('users.id'), primary_key=True), Column('role_id', ForeignKey('roles.id'), primary_key=True))
Association table for many-to-many relationship between users and roles.
organisation_staff_member
module-attribute
¶
organisation_staff_member = Table('organisation_staff_member', metadata, Column('organisation_id', ForeignKey('organizations.id'), primary_key=True), Column('user_id', ForeignKey('users.id'), primary_key=True), Column('is_primary', Boolean, default=False, nullable=False))
Association table for many-to-many relationship between organisations and staff.
organisation_patient_member
module-attribute
¶
organisation_patient_member = Table('organisation_patient_member', metadata, Column('organisation_id', ForeignKey('organizations.id'), primary_key=True), Column('patient_id', String(255), primary_key=True), Column('is_primary', Boolean, default=False, nullable=False))
Association table for many-to-many relationship between organisations and patients.
message_organisation
module-attribute
¶
message_organisation = Table('message_organisation', metadata, Column('conversation_id', ForeignKey('conversations.id', ondelete='CASCADE'), primary_key=True), Column('organisation_id', ForeignKey('organizations.id', ondelete='CASCADE'), primary_key=True))
Association table linking conversations to organisations.
Base ¶
Bases: DeclarativeBase
Base class for all database models.
Source code in app/models.py
37 38 39 40 | |
Role ¶
Bases: Base
Role definition for role-based access control.
Attributes:
| Name | Type | Description |
|---|---|---|
id |
Mapped[int]
|
Primary key. |
name |
Mapped[str]
|
Unique role name (e.g., "Clinician", "Administrator"). |
users |
Mapped[list[User]]
|
List of users assigned to this role. |
Source code in app/models.py
52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 | |
User ¶
Bases: Base
User account with authentication credentials and settings.
Attributes:
| Name | Type | Description |
|---|---|---|
id |
Mapped[int]
|
Primary key. |
username |
Mapped[str]
|
Unique username for login (indexed). |
full_name |
Mapped[str | None]
|
User's full display name (optional). |
email |
Mapped[str]
|
Unique email address. |
password_hash |
Mapped[str]
|
Argon2 password hash. |
totp_secret |
Mapped[str | None]
|
Base32-encoded TOTP secret (optional for 2FA). |
is_totp_enabled |
Mapped[bool]
|
Whether two-factor authentication is enabled. |
is_active |
Mapped[bool]
|
Whether the account is active (for soft delete). |
roles |
Mapped[list[Role]]
|
List of roles assigned to this user. |
base_profession |
Mapped[str]
|
Base profession template (e.g., "consultant", "patient"). |
additional_competencies |
Mapped[list[str]]
|
Extra competencies beyond base profession. |
removed_competencies |
Mapped[list[str]]
|
Competencies removed from base profession. |
professional_registrations |
Mapped[dict | None]
|
Professional registration details (GMC, NMC, etc.). |
Source code in app/models.py
73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 | |
get_final_competencies ¶
get_final_competencies()
Compute final competencies for this user.
Returns:
| Type | Description |
|---|---|
list[str]
|
List of competency IDs this user has. |
Source code in app/models.py
139 140 141 142 143 144 145 146 147 148 149 | |
PatientMetadata ¶
Bases: Base
Application-specific metadata for patients.
This table stores metadata about patients that is specific to the Quill Medical application, separate from clinical data stored in FHIR. The patient_id field links to the FHIR Patient resource ID.
Attributes:
| Name | Type | Description |
|---|---|---|
id |
Mapped[int]
|
Primary key. |
patient_id |
Mapped[str]
|
FHIR Patient resource ID (unique). |
is_active |
Mapped[bool]
|
Whether the patient is active in the system. Deactivated patients are hidden from clinical views but visible in admin pages. |
Source code in app/models.py
152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 | |
Organization ¶
Bases: Base
Healthcare organisation (hospital, GP practice, clinic, department).
Represents a named group of healthcare staff who share responsibility for a defined group of patients. Uses American spelling in code/API (FHIR-aligned) but British spelling "Organisation" in UI.
Attributes:
| Name | Type | Description |
|---|---|---|
id |
Mapped[int]
|
Primary key. |
name |
Mapped[str]
|
Organisation name (e.g., "Great Eastern Hospital"). |
type |
Mapped[str]
|
Organisation type (hospital_team, gp_practice, private_clinic, department, teaching_establishment). |
location |
Mapped[str | None]
|
Optional location/address information. |
created_at |
Mapped[datetime]
|
Timestamp when organisation was created. |
updated_at |
Mapped[datetime]
|
Timestamp when organisation was last updated. |
staff_members |
Mapped[list[User]]
|
List of users (staff) who belong to this organisation. |
Source code in app/models.py
202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 | |
OrganisationFeature ¶
Bases: Base
Feature flag for an organisation.
Row existence = feature is enabled. Deleting the row disables the
feature. No separate enabled boolean — avoids ambiguity between
"row exists but disabled" and "row absent".
Attributes:
| Name | Type | Description |
|---|---|---|
id |
Mapped[int]
|
Primary key. |
organisation_id |
Mapped[int]
|
FK to the owning organisation. |
feature_key |
Mapped[str]
|
Feature identifier (e.g. "epr", "teaching"). |
enabled_at |
Mapped[datetime]
|
When the feature was enabled. |
enabled_by |
Mapped[int | None]
|
FK to the user who enabled it. |
Source code in app/models.py
253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 | |
ExternalPatientAccess ¶
Bases: Base
Per-patient access grant for external HCPs and patient advocates.
Links an external user (external_hcp or patient_advocate) to a specific patient they have been granted access to. Access is granted via invite and can only be revoked by an admin.
Attributes:
| Name | Type | Description |
|---|---|---|
id |
Mapped[int]
|
Primary key. |
user_id |
Mapped[int]
|
FK to the external user. |
patient_id |
Mapped[str]
|
FHIR Patient resource ID. |
granted_by_user_id |
Mapped[int]
|
FK to the user (patient or admin) who granted access. |
granted_at |
Mapped[datetime]
|
When access was granted. |
revoked_at |
Mapped[datetime | None]
|
When access was revoked (nullable; null = active). |
access_level |
Mapped[str]
|
Access granularity (default "full"). |
Source code in app/models.py
324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 | |
Conversation ¶
Bases: Base
Messaging thread about a patient.
Each conversation belongs to exactly one patient (identified by FHIR
patient UUID). Staff and patients are added as participants.
Thread metadata (status, subject) lives here; message content is the
source of truth in FHIR Communication resources and projected into
the Message table for fast reads.
Attributes:
| Name | Type | Description |
|---|---|---|
id |
Mapped[int]
|
Primary key. |
fhir_conversation_id |
Mapped[str]
|
UUID used in FHIR extensions to group Communication resources into a thread. |
patient_id |
Mapped[str]
|
FHIR Patient resource UUID this thread is about. |
subject |
Mapped[str | None]
|
Optional human-readable thread topic. |
status |
Mapped[str]
|
Conversation lifecycle state. |
created_at |
Mapped[datetime]
|
When the conversation was started. |
updated_at |
Mapped[datetime]
|
When the last activity occurred. |
participants |
Mapped[list[ConversationParticipant]]
|
Joined participants (via ConversationParticipant). |
messages |
Mapped[list[Message]]
|
Messages in this thread (via Message). |
Source code in app/models.py
381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 | |
ConversationParticipant ¶
Bases: Base
Records a user's membership in a conversation.
Attributes:
| Name | Type | Description |
|---|---|---|
id |
Mapped[int]
|
Primary key. |
conversation_id |
Mapped[int]
|
FK to conversation. |
user_id |
Mapped[int]
|
FK to users table. |
role |
Mapped[str]
|
How the user joined (initiator / participant / tagged). |
joined_at |
Mapped[datetime]
|
When the user was added. |
last_read_at |
Mapped[datetime | None]
|
Timestamp of the last time the user viewed the conversation — used to calculate unread counts. |
Source code in app/models.py
444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 | |
Message ¶
Bases: Base
SQL projection of a FHIR Communication resource.
Each row mirrors a Communication stored in HAPI FHIR. The FHIR resource is the source of truth; this table provides fast SQL queries for threading, unread counts, and search.
Messages are append-only: no editing or deletion. Corrections are
handled via the amends_id self-referential FK (amendment model).
Redaction fields are placeholders for a future two-person sign-off
workflow.
Attributes:
| Name | Type | Description |
|---|---|---|
id |
Mapped[int]
|
Primary key. |
fhir_communication_id |
Mapped[str]
|
FHIR resource ID (unique). |
conversation_id |
Mapped[int]
|
FK to conversations table. |
sender_id |
Mapped[int]
|
FK to users table. |
body |
Mapped[str]
|
Message text (markdown supported). |
amends_id |
Mapped[int | None]
|
Self-FK to the message this one corrects (nullable). |
redacted_at |
Mapped[datetime | None]
|
Future — when the message was redacted. |
redacted_by_id |
Mapped[int | None]
|
Future — FK to user who performed redaction. |
created_at |
Mapped[datetime]
|
Immutable creation timestamp. |
Source code in app/models.py
495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 | |