import type { QuestionnaireResponse } from 'fhir/r4'; import { answeredQuestions } from './questionnaireAnswerText'; /** One answer compared with the previous sent version, which comes from the resource's own history. */ export interface ChangedAnswer { linkId: string; question: string; /** What the answer says now. Absent when the amendment removed it. */ answer?: string; /** What it said before, on a changed answer. Absent when the question had no answer before. */ was?: string; /** Whether the answer differs from the previous version, including being added or removed. */ changed: boolean; } /** * The current answers, each marked with whether it changed, plus the answers the amendment removed. * * With no previous version nothing has changed. A removed answer keeps its place, after the answer that * preceded it in the previous version, so the reader sees it where the question was asked. */ export const changedAnswers = ( current: QuestionnaireResponse | undefined, previous: QuestionnaireResponse | undefined, ): ChangedAnswer[] => { const now = answeredQuestions(current); if (!previous) { return now.map(({ linkId, question, answer }) => ({ linkId, question, answer, changed: false })); } const before = answeredQuestions(previous); const beforeById = new Map(before.map((entry) => [entry.linkId, entry.answer])); const nowIds = new Set(now.map((entry) => entry.linkId)); const rows: ChangedAnswer[] = now.map(({ linkId, question, answer }) => { const was = beforeById.get(linkId); return was === answer ? { linkId, question, answer, changed: false } : { linkId, question, answer, was, changed: true }; }); before.forEach((entry, index) => { if (nowIds.has(entry.linkId)) return; let insertAt = 0; for (let i = index - 1; i >= 0; i -= 1) { const at = rows.findIndex((row) => row.linkId === before[i].linkId); if (at !== -1) { insertAt = at + 1; break; } } rows.splice(insertAt, 0, { linkId: entry.linkId, question: entry.question, was: entry.answer, changed: true }); }); return rows; }; /** How many answers changed. */ export const changedAnswerCount = ( current: QuestionnaireResponse | undefined, previous: QuestionnaireResponse | undefined, ): number => changedAnswers(current, previous).filter((entry) => entry.changed).length; /** * The version the displayed answers are compared with: the most recent version sent before it. * * History arrives newest first and can lag the resource by a version, so the displayed version is placed * by its number, never by position: a newer version is never its past. Without an id the newest is taken * to be the displayed one. An autosave written while amending was never sent, so it is skipped. */ // An id that is not a number can only be placed by finding it; one the history read does not hold has no known past. const placedAfter = (versions: QuestionnaireResponse[], versionId: string): QuestionnaireResponse[] => { const at = versions.findIndex((version) => version.meta?.versionId === versionId); return at < 0 ? [] : versions.slice(at + 1); }; export const previousSentVersion = ( versions: QuestionnaireResponse[], displayedVersionId?: string, ): QuestionnaireResponse | undefined => { const shown = Number(displayedVersionId); const earlier = !displayedVersionId ? versions.slice(1) : Number.isInteger(shown) ? versions .filter((version) => Number(version.meta?.versionId) < shown) .sort((left, right) => Number(right.meta?.versionId) - Number(left.meta?.versionId)) : placedAfter(versions, displayedVersionId); return earlier.find((version) => version.status !== 'in-progress'); };