|
| 1 | +--- |
| 2 | +sidebar_position: 15 |
| 3 | +--- |
| 4 | + |
| 5 | +# Care team |
| 6 | + |
| 7 | +Technical reference for the encounter care team in Care EMR. See the [care team concept](../../concepts/clinical/care-team.mdx) for the plain-language view. |
| 8 | + |
| 9 | +**Source:** |
| 10 | +- Model: [`care/emr/models/encounter.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/models/encounter.py) |
| 11 | +- Specs: [`care/emr/resources/encounter/spec.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/encounter/spec.py) · [`valueset.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/encounter/valueset.py) |
| 12 | +- Viewset: [`care/emr/api/viewsets/encounter.py`](https://github.com/ohcnetwork/care/blob/develop/care/emr/api/viewsets/encounter.py) |
| 13 | + |
| 14 | +The care team has no model of its own. It is stored on the `Encounter` model as an opaque `JSONField`, and the real shape lives in the Pydantic specs. The write path is a dedicated action on the encounter viewset, not the encounter create or update schema. |
| 15 | + |
| 16 | +## Models |
| 17 | + |
| 18 | +| Model | Purpose | |
| 19 | +| --- | --- | |
| 20 | +| `Encounter` | Owns the care team through the `care_team` and `care_team_users` fields | |
| 21 | + |
| 22 | +`Encounter` extends [`EMRBaseModel`](../foundation/base-model.mdx). |
| 23 | + |
| 24 | +## `Encounter` care team fields |
| 25 | + |
| 26 | +| Field | Type | Notes | |
| 27 | +| --- | --- | --- | |
| 28 | +| `care_team` | `JSONField` | Default `{}`. Stored as a list of `{ "user_id": int, "role": Coding }` entries. Excluded from `EncounterCreateSpec` and `EncounterUpdateSpec` | |
| 29 | +| `care_team_users` | `ArrayField[int]` | Denormalized cache of the internal user IDs in `care_team`. Platform-maintained | |
| 30 | + |
| 31 | +The stored `user_id` is the internal integer primary key of `User`. The API accepts and returns the `external_id` (UUID) instead. |
| 32 | + |
| 33 | +### Stored shape |
| 34 | + |
| 35 | +```text |
| 36 | +care_team: [ |
| 37 | + { |
| 38 | + "user_id": int, # internal User pk |
| 39 | + "role": Coding { system, code, display } |
| 40 | + } |
| 41 | +] |
| 42 | +``` |
| 43 | + |
| 44 | +## Resource specs (API schema) |
| 45 | + |
| 46 | +| Spec | Role | |
| 47 | +| --- | --- | |
| 48 | +| `EncounterCareTeamMemberSpec` | write · one member: `user_id: UUID4`, `role: ValueSetBoundCoding[system-practitioner-role-code]` | |
| 49 | +| `EncounterCareTeamMemberWriteSpec` | write · the full replacement list: `members: list[EncounterCareTeamMemberSpec]` | |
| 50 | +| `EncounterListSpec` | read · list. Serializes `care_team` as `[{ "member": UserSpec, "role": Coding }]` | |
| 51 | +| `EncounterRetrieveSpec` | read · detail. Same `care_team` serialization as the list spec | |
| 52 | + |
| 53 | +On read, each member is expanded from the cached `UserSpec` for the stored `user_id`. The list order of `care_team` is preserved, so the first entry is the primary member. |
| 54 | + |
| 55 | +### `PRACTITIONER_ROLE_VALUESET` |
| 56 | + |
| 57 | +`role` is bound to the system value set with slug `system-practitioner-role-code`. It composes two SNOMED CT `is-a` filters: |
| 58 | + |
| 59 | +| Concept | Meaning | |
| 60 | +| --- | --- | |
| 61 | +| `223366009` | Healthcare professional | |
| 62 | +| `224930009` | Healthcare related organization | |
| 63 | + |
| 64 | +## API integration notes |
| 65 | + |
| 66 | +- Write endpoint: `POST /api/v1/encounter/{external_id}/set_care_team_members/`. The body is `EncounterCareTeamMemberWriteSpec`, and the response is `EncounterRetrieveSpec` in the schema. |
| 67 | +- The write is a **full replacement**. Send the complete member list on every call. To remove a member, send the list without that member. To change the primary member, send the list with that member first. |
| 68 | +- `set_care_team_members` calls `authorize_update`, which resolves to `can_update_encounter_obj`. That check returns `False` when the encounter status is in `COMPLETED_CHOICES`, so a closed encounter rejects the write. |
| 69 | +- Each member in the body is checked with `can_view_encounter_obj`. A member who cannot view the encounter causes a `PermissionDenied`. |
| 70 | +- A repeated `user_id` in the body causes a `ValidationError` with `{"user": "repeats are not allowed"}`. |
| 71 | +- Filtering: the encounter list supports `care_team_user=<username>`, which resolves the username to a user ID and matches it against `care_team_users`. |
| 72 | + |
| 73 | +## Methods & save behaviour |
| 74 | + |
| 75 | +- `Encounter.sync_care_team_users_cache()` rebuilds `care_team_users` from `care_team`. `Encounter.save()` calls it on every save, so the cache never drifts from the JSON field. |
| 76 | +- `sync_care_team_users_cache()` only rebuilds the cache when `care_team` is a list. The model default is a dict, so an encounter with no care team keeps an empty cache. |
| 77 | +- `set_care_team_members` saves with `update_fields=["care_team", "care_team_users", "updated_by", "modified_date"]`. |
| 78 | + |
| 79 | +## Related |
| 80 | + |
| 81 | +- Concept: [Care team](../../concepts/clinical/care-team.mdx) |
| 82 | +- Reference: [Encounter](../clinical/encounter.mdx) |
| 83 | +- Reference: [User](../access-governance/user.mdx) |
| 84 | +- Flow: [How to manage the care team of an encounter](../../flows/clinical/manage-encounter-care-team.mdx) |
0 commit comments