import type { TeamMembership } from "../services"; /** * WHAT A TEAM IS, IN ONE LINE UNDER ITS NAME. * * The page carried a member count, which restates the row above it: the members are listed a * hundred pixels down and counting them again says nothing a reader could not see. The approved * deck writes the subtitle as "5 members across 2 practices and your family", and the two extra * facts are the two that make a collaboration team worth having at all - it SPANS practices, and * it can hold the people who help you as well as the people who treat you. * * THE PRACTICE COUNT IS THE PRACTICES THE TEAM SPANS, NOT MEMBERSHIPS OR AFFILIATIONS. Five people at * one practice is one practice, and a second affiliation is not a second practice. The membership does not say which * practice a member joined for, so the count is the fewest practices that account for every member * whose practice is known, chosen greedily - teams are a handful of people. * * AND AN UNRESOLVED PRACTICE IS NOT COUNTED, which is the denial rule reaching a summary. A member * whose organizations could not be read has `organizationDisplays` absent - not empty - and * counting them would put a number on a screen that the rows underneath cannot account for. An * EMPTY array is a different answer and is also not counted: it is a positive statement that the * person belongs to no organization, which is what an outside practitioner is. * * "YOURS" IS DELIBERATELY NOT BUILT. The deck opens the line with it because the deck has a second * tab for teams somebody else owns; the app has nowhere to show one - the patient's list query is * scoped to their own partition - so "Yours" would be true of every team on the screen and would * separate nothing. It goes in the day the other tab does. */ const countSpannedPractices = (affiliations: string[][]): number => { let uncovered = affiliations.filter((displays) => displays.length > 0); let count = 0; while (uncovered.length > 0) { const reach = new Map(); for (const displays of uncovered) { for (const display of new Set(displays)) reach.set(display, (reach.get(display) ?? 0) + 1); } const [widest] = [...reach.entries()].reduce((best, entry) => (entry[1] > best[1] ? entry : best)); uncovered = uncovered.filter((displays) => !displays.includes(widest)); count += 1; } return count; }; export const describeRoster = (members: TeamMembership[]): string => { const people = members.length; const affiliations: string[][] = []; let family = false; for (const member of members) { if (member.memberClass === "related-person") family = true; affiliations.push(member.organizationDisplays ?? []); } const practices = countSpannedPractices(affiliations); const parts = [`${people} member${people === 1 ? "" : "s"}`]; if (practices > 0) { parts.push(`across ${practices} practice${practices === 1 ? "" : "s"}`); } if (family) parts.push("and your family"); return parts.join(" "); }; export default describeRoster;