import { GENERIC_RESOURCE_LABEL, resourceLabel } from './resourceLabel'; // / My History - say WHAT HAPPENED as well as what it happened to. // // The feed rendered every non-access row as "updated", because the row's kind was the only thing // consulted and the kind does not carry the verb. So a creation - the commonest change in a live record, // and the only one an order or a new note can be - told the patient their record had been CHANGED. On the // one screen built so somebody can judge whether what happened to their record should have happened, // "your clinician altered something about you" and "your clinician ordered you a test" are not // interchangeable sentences, and only the second one is true of a create. // // The verb comes from the FHIR AuditEvent action the server already sends. It is not inferred from // whether a previous version can be found: an absent history is a fact about what this client could // read, never a fact about what the clinician did. const VERBS: Readonly> = { C: 'added', U: 'updated', D: 'removed', }; // UNKNOWN IS ITS OWN CASE, and it says less rather than guessing. `R` never reaches here (reads are // access rows) and `E` is an execute the change branch catches by falling through, so an action this // map does not name is one nobody has classified. "changed" is true of a create, an update and a delete // alike, which is exactly the claim the evidence supports when the action is missing or unrecognised. // Defaulting to "updated" instead would reintroduce the defect for every row the map has not caught up // with, which is how the original wording came to be wrong in the first place. const UNKNOWN_VERB = 'changed'; // A REFUSED ATTEMPT IS NOT A CHANGE, AND IT IS NOT NOTHING EITHER. The row stays - section 7 of the design: // this feed must never show less than the truth - but it says the attempt did not go through. FHIR // AuditEvent outcome: "0" success; "4", "8", "12" failures of rising severity. Absent is treated as // success, the reading every older row has always had. const INFINITIVES: Readonly> = { C: 'add', U: 'update', D: 'remove', }; export const DID_NOT_GO_THROUGH = 'it did not go through'; // The generic label needs a different SENTENCE, not a different word. "updated your record" is English; // "added your record" is not, and "removed your record" is alarming as well as untrue - the record is // not what was removed, something in it was. So the unmapped case gets a phrasing that keeps the verb // honest while staying vague about the noun, which is all this client knows. const GENERIC_PHRASES: Readonly> = { C: `added something to ${GENERIC_RESOURCE_LABEL}`, U: `updated ${GENERIC_RESOURCE_LABEL}`, D: `removed something from ${GENERIC_RESOURCE_LABEL}`, }; /** * The verb phrase for one change row: what was done, and to what, in the patient's own words. * * `action` is the FHIR AuditEvent action code; `resourceType` is the raw type, which is mapped to * patient language by `resourceLabel` and NEVER rendered as itself. */ export const describeChange = ( action: string | undefined, resourceType: string | undefined, outcome?: string | undefined, ): string => { const label = resourceLabel(resourceType); const verb = (action && VERBS[action]) || UNKNOWN_VERB; if (outcome !== undefined && outcome !== '0') { const attempted = (action && INFINITIVES[action]) || 'change'; return `tried to ${attempted} ${label} - ${DID_NOT_GO_THROUGH}`; } if (label === GENERIC_RESOURCE_LABEL) { return (action && GENERIC_PHRASES[action]) || `changed ${GENERIC_RESOURCE_LABEL}`; } return `${verb} ${label}`; };