Complete an Assessment
Start an assessment and work through each kind of task it issues.
An assessment is the front door to care: a scoped intake ("Skin concern", "Thyroid and energy") that gathers what the care team needs before anything is prescribed. This guide is the whole loop: check the agreements, start the assessment, complete each kind of task it issues, and show the patient where their answers went.
Everything runs as the signed-in patient with a PalamondClient (see the Quickstart):
import { PalamondClient } from '@palamond/sdk';
export const palamond = new PalamondClient({ clientId: 'your-client-id' });Starting the assessment
Pick an assessment from the catalog
protocols.list reads the care catalog. Entries with an assessment canonical are startable assessments.
const catalog = await palamond.protocols.list();
const assessments = catalog.filter((entry) => entry.assessment);
// [{ healthcareService: 'HealthcareService/...', name: 'Skin concern', assessment: 'https://...|1.0.0' }]Check the agreements
Care usually requires signed agreements (terms of service, telehealth consent). Check what is unmet for the chosen service before starting, so you can present the documents as part of your flow rather than as an error:
const { satisfied, unmet } = await palamond.agreements.requirements({
healthcareService: assessments[0].healthcareService,
});
if (!satisfied) {
// Render each agreement (unmet[n].sourceUrl is the document),
// collect the patient's acceptance, then record it:
await palamond.agreements.accept(
unmet.map(({ url, version }) => ({ agreementUrl: url, agreementVersion: version })),
);
}unmet is always the exact remainder: agreements the patient accepted before (in an earlier session, or for another service) never reappear in it, so this same code handles a partially-signed patient without special cases.
The server enforces this independently: if anything is still unmet when you start, the call answers 409 with code consent_required and the same agreement list in details.agreements. Treat that as the backstop, not the flow. See Agreements and Consent for the full model.
Start it
One call issues the whole assessment: the episode, the intake visit, and the patient's tasks.
const started = await palamond.assessments.start({
healthcareService: assessments[0].healthcareService,
});
// {
// episode: 'EpisodeOfCare/...',
// appointment: 'Appointment/...', // anchors every task of this assessment
// tasks: ['Task/...', ...], // every issued task, entry point first
// scope: 'Skin concern'
// }Starting is idempotent on the platform side: nothing is duplicated by calling it again. An assessment that already ran answers 409 with code conflict; details.appointment then anchors the prior run, so route the patient to its tasks instead of starting over:
import { getApiError } from '@palamond/sdk';
let anchor: string;
try {
const started = await palamond.assessments.start({ healthcareService: service });
anchor = started.appointment;
} catch (err) {
const api = getApiError(err);
if (api?.code !== 'conflict') {
throw err;
}
// api.details.reason: 'already-started' | 'already-completed'
anchor = api.details?.appointment as string;
}Read the task list
Every task of the assessment hangs off the anchor appointment. This search is also how you resume: it reconstructs the to-do list at any time, on any device.
import type { Task } from '@palamond/sdk/fhir';
const tasks: Task[] = await palamond.searchResources('Task', {
'based-on': started.appointment,
status: 'requested,in-progress',
});Task.code.text is the step heading, Task.description is the patient-facing "why", and Task.status tracks progress. What to render for each task depends on its kind, which the task itself tells you.
The kinds of tasks
An assessment is composed of components ("what we gather"), and each becomes a task of a specific kind. The SDK's getTaskKind tells you which one you are looking at:
getTaskKind(task) | What the patient does | How it completes |
|---|---|---|
'questionnaire' | Answers the questionnaire in task.focus | tasks.complete with a questionnaireResponse |
'measurements' | Reports the requested readings | tasks.complete with inline measurements |
'medication' | Picks one of the suggested medications, or none | tasks.complete with a medicationRequest proposal, or decline |
'order' | Decides the proposed order in task.focus (assessment labs arrive this way) | orders.decide on the proposal |
Work through the list one task at a time: take the next open task, branch on its kind, and render that one screen. The sections below are the four branches.
import { getTaskKind } from '@palamond/sdk';
const task = tasks[0]; // the next open task, from the search above
switch (getTaskKind(task)) {
case 'questionnaire': // render the questionnaire
case 'measurements': // render the entry fields
case 'medication': // render the suggestions
case 'order': // render the order decision
}An assessment carries one questionnaire at most; the other kinds can appear any number of times. Completion is one-way for all of them: a completed task cannot be edited afterwards, and each completion advances the workflow server-side.
Questionnaire tasks
Load the questionnaire the task points at and render its items:
import type { Questionnaire } from '@palamond/sdk/fhir';
const questionnaire = await palamond.readReference<Questionnaire>(task.focus);
// Render questionnaire.item; collect answers keyed by linkId.The patient's answers are a QuestionnaireResponse, and the patient authors it, so it is written directly on the record plane. You can submit everything at once:
import type { QuestionnaireResponse } from '@palamond/sdk/fhir';
const response = await palamond.createResource<QuestionnaireResponse>({
resourceType: 'QuestionnaireResponse',
status: 'completed',
questionnaire: `${questionnaire.url}|${questionnaire.version}`,
subject: task.for,
item: [
{ linkId: 'chief-complaint', answer: [{ valueString: 'Recurring breakouts' }] },
{ linkId: 'duration-months', answer: [{ valueInteger: 6 }] },
],
});Or save answer by answer, so the patient can leave and come back mid-questionnaire. Create the response as in-progress, update it as each answer lands, and flip it to completed at the end:
// First answer: create the draft.
let response = await palamond.createResource<QuestionnaireResponse>({
resourceType: 'QuestionnaireResponse',
status: 'in-progress',
questionnaire: `${questionnaire.url}|${questionnaire.version}`,
subject: task.for,
item: [{ linkId: 'chief-complaint', answer: [{ valueString: 'Recurring breakouts' }] }],
});
// Each further answer: update it.
response = await palamond.updateResource({
...response,
item: [...(response.item ?? []), { linkId: 'duration-months', answer: [{ valueInteger: 6 }] }],
});
// The last screen: mark the response complete.
response = await palamond.updateResource({ ...response, status: 'completed' });Either way, the final action is completing the task with the response. That is what moves the workflow; the answers stay a draft until you do:
await palamond.tasks.complete(task.id, {
questionnaireResponse: `QuestionnaireResponse/${response.id}`,
});Measurement tasks
A measurements task names the readings it wants, so your UI can render one field per measurement:
import { getRequestedMeasurements } from '@palamond/sdk';
const requested = getRequestedMeasurements(task);
// [{ system: 'http://loinc.org', code: '8480-6', display: 'Systolic blood pressure' }, ...]Completing the task carries the readings inline; the platform validates each value and writes the Observations for you:
await palamond.tasks.complete(task.id, {
measurements: [
{ loinc: '8480-6', value: 118, unit: 'mmHg', code: 'mm[Hg]' },
{ loinc: '8462-4', value: 76, unit: 'mmHg', code: 'mm[Hg]' },
],
});One entry per requested reading, keyed by LOINC. Omit a reading the patient could not take. The stored observations are readable back on the record plane (GET /v1/fhir/Observation?category=vital-signs).
Medication suggestion tasks
Some assessments suggest medications for the patient to consider. The task carries the suggestions:
import { getMedicationOptions } from '@palamond/sdk';
const options = getMedicationOptions(task);
// [{ system: '...drug-catalog', code: 'sku-123', display: 'Tretinoin 0.05% cream' }, ...]A patient never orders a medication; they propose, and a clinician reviews. Record the pick as a proposal, then complete the task with it:
const chosen = options[0];
const { request } = await palamond.requests.create({
kind: 'medication',
medication: chosen.display,
medicationCoding: chosen,
});
await palamond.tasks.complete(task.id, {
medicationRequest: `MedicationRequest/${request.id}`,
});If none of the suggestions fit, complete the task with decline instead; the clinician still reviews the rest of the assessment:
await palamond.tasks.complete(task.id, { decline: true });Order tasks
A lab component arrives as an order task: a proposal the patient decides, not an order placed over their head. The task's focus is the proposal RequestGroup, and the patient chooses how to fulfil it (a home kit or a visit to a draw site). Deciding completes the task:
import type { RequestGroup } from '@palamond/sdk/fhir';
const proposal = await palamond.readReference<RequestGroup>(task.focus);
const item = proposal.action?.[0]?.resource?.reference; // 'ServiceRequest/...'
await palamond.orders.decide(task.focus.reference, [
{ item, decision: 'accept', method: 'home-kit' },
]);The accepted lab becomes an order and the task waits on the result server-side; there is nothing further for the patient to do on it. Decide an Order covers decisions in full, including declining and in-person draws.
When the tasks are done
The end of the assessment is observable, not implied. When the task search from above returns no open tasks, everything the patient can do is done:
const open = await palamond.searchResources('Task', {
'based-on': started.appointment,
status: 'requested,in-progress',
});
const submitted = open.length === 0;Concretely, at that point:
- Each completed
Taskis on the record withstatus: completedand its outputs attached (the response, the readings, the proposal), so a "what you submitted" screen is a plain read. - The platform opens a review task for the care team with the intake attached. That task is the care team's, not the patient's, so it does not appear in patient task searches.
- The review's outcome arrives as new patient tasks and proposals on the same record: a proposed care plan to consent to, an order to decide. Your app picks those up with the very search it already runs for the to-do list, and completes them the same way.
Show the patient a clear "submitted, your care team is reviewing" state, keep polling the task list, and render the next step when it arrives.
Related
- Questionnaires - Rendering questionnaire items and recording answers.
- Encounters and Tasks - The task model in full.
- Clinical Spine - The gates between an assessment and active care.