import type { CarePlan } from 'fhir/r4'; import type { CollaborationTeam } from '../services/collaborationTeam'; // Which collaboration team a care plan belongs to. // // ATTACHING A PLAN TO A TEAM GRANTS NOBODY ANYTHING, and that is the load-bearing fact rather than a // caveat. Measured in the api on 2026-09-02: a practitioner on the team already holds FULL-CLINICAL // access to the patient through the patient-approved collaboration-team referral Consent, and that grant // is patient-wide rather than plan-scoped. A related person sees through their own link and tier. So the // attachment changes which team the WORK belongs to and never who may read it. // // So the line it produces must not read as a permission. It says whose team this plan is being run by, // the same way a referral says where somebody was sent without handing anybody a key. // // CARRIED AS A `meta.tag`, following the owning-organization tag exactly. A collaboration team is our own // resource with a `teamId`, NOT a FHIR CareTeam, so `CarePlan.careTeam` would be a reference to // something that does not exist in the datastore. export const COLLABORATION_TEAM_TAG_SYSTEM = 'https://akashichealthrecords.com/fhir/tags/collaboration-team'; export const getCarePlanTeamId = (plan: CarePlan | undefined): string | undefined => plan?.meta?.tag?.find((tag) => tag.system === COLLABORATION_TEAM_TAG_SYSTEM)?.code; /** * The plan, attached to a team - or detached when the id is absent. * * RETURNS A NEW PLAN AND NEVER MUTATES, because the caller is about to PUT it and the copy it holds is * what the editor is still drawing. Every other tag is preserved: the owning-organization tag lives in * the same array, and dropping it would lose the plan's provenance and take its editability with it. */ export const withCarePlanTeam = (plan: CarePlan, teamId: string | undefined): CarePlan => { const others = (plan.meta?.tag ?? []).filter((tag) => tag.system !== COLLABORATION_TEAM_TAG_SYSTEM); const tag = teamId ? [...others, { system: COLLABORATION_TEAM_TAG_SYSTEM, code: teamId }] : others; return { ...plan, meta: { ...plan.meta, ...(tag.length > 0 ? { tag } : {}) }, }; }; export interface PlanSharing { teamId: string; /** Ready to render - "Diabetes care team - 4 members". */ label: string; } /** * Who this plan is being run with, said plainly. * * NAMES THE TEAM OR SAYS NOTHING. A tag pointing at a team this caller cannot see is not a team they can * be told about - membership is what authorises knowing who is on a team - so an unresolvable id yields * a neutral line rather than leaking that some team exists. * * THE MEMBER COUNT IS WHAT WE CAN HONESTLY DERIVE. The drawing reads "4 members across 2 practices", but * a membership carries no organization, so the practice count would have to be invented. One true half * of a sentence beats a whole one that is guessed. */ export const describePlanSharing = ( plan: CarePlan | undefined, teams: CollaborationTeam[], ): PlanSharing | undefined => { const teamId = getCarePlanTeamId(plan); if (!teamId) return undefined; const team = teams.find((candidate) => candidate.teamId === teamId); if (!team) return { teamId, label: 'A collaboration team' }; const active = (team.members ?? []).filter((member) => member.state === 'active'); const count = active.length; return { teamId, label: count > 0 ? `${team.name} ยท ${count} member${count === 1 ? '' : 's'}` : team.name, }; }; /** The teams that could hold THIS patient's plan - a caller's team list spans their patients. */ export const teamsForPatient = (teams: CollaborationTeam[], patientId: string | undefined): CollaborationTeam[] => (patientId ? teams.filter((team) => team.patientId === patientId && team.status !== 'dissolved') : []);