Skip to content

Suggested-Resource Tasks

A suggested-resource task is a task carrying a suggested chart entry that somebody has to accept before it becomes part of the record. Clinical extraction reads an encounter note and suggests adding type 2 diabetes to the problem list. A decision-support rule notices an A1c above target and suggests a medication. A consult letter refutes a recorded allergy, and something suggests retiring it.

There is no separate profile, and that is the design. It is an ordinary Task, and one part of its shape is the whole definition:

A task is a suggested-resource task when it carries its suggested resources in a contained transaction Bundle.

Nothing else says so. Not Task.code, not Task.intent. Read the containment.

First: this is not the same thing as a proposal

Two nearby ideas share a neighbourhood, and it is worth thirty seconds to keep them apart.

A proposal is Task.intent = proposal: a task offered for a decision rather than an instruction, which a user sees in a Suggestions list rather than a Tasks list. That is about what is being asked.

A suggested-resource task -- this page -- is about what the task contains.

They are independent, and an authorized order carrying a suggested chart entry is a real and ordinary shape: the entry is already agreed and the task exists to get it recorded rather than decided. So read the containment, not Task.intent, to answer "does this carry a suggested chart entry?".

The Proposals page has the full treatment, including the table of all four combinations, approving and declining, alternatives and decision-support cards.

suggestion is not a code anywhere in this specification -- there is no such Task.code, and no code system defines one. (The English word still appears throughout: what went away is the term of art that stood for two different ideas.)

The payload is a template

The suggested resources ride in a contained transaction Bundle -- the same prototype-graph mechanism a reusable letter template and a form use:

wrapper points at the Bundle with
List (a named template) entry.item
Task (this page) input[suggestion]
Questionnaire (a form) SDC's templateExtractBundle

(A form's entries can additionally carry a templateExtractContext, so that an entry is produced once per answer, or not at all. A Task's suggested resources are already filled in, so its entries are simply the ones authored. See that page.)

"contained": [{
  "resourceType": "Bundle", "id": "suggested", "type": "transaction",
  "entry": [
    { "fullUrl": "urn:uuid:...01",
      "resource": { "resourceType": "Condition", ... },
      "request": { "method": "POST", "url": "Condition" } },
    { "fullUrl": "urn:uuid:...02",
      "resource": { "resourceType": "MedicationRequest", ... },
      "request": { "method": "PUT", "url": "MedicationRequest/abc" } }
  ]
}],
"input": [{ "type": { "coding": [{ "code": "suggested-resource" }] },
            "valueReference": { "reference": "#suggested" } }]

One pointer, one thing. input[suggestion] is 0..1: the Bundle holds every suggested resource, as its entries. A task that also contains its Provenance or an attachment has no ambiguity about which resources are being suggested.

Why a Bundle and not a List

dom-2: a contained resource SHALL NOT contain nested resources (contained.contained.empty(), error severity, unchanged in R5). Anything contained in a Task is forbidden from containing anything itself, so a contained List could not hold the prototypes -- they would have to scatter as its siblings with the List reduced to a manifest over them.

Bundle is the one container exempt, because it derives from Resource rather than DomainResource and has no contained element for the rule to find. That is not a loophole we invented: it is what SDC does.

What the transaction buys

  • entry.request.method is per entry. One suggested-resource task can create a Condition and update a MedicationRequest. An earlier revision put add-versus-update on Task.focus, which is 0..1, so a mixed suggestion was unrepresentable.
  • Atomicity is in the model. type = transaction: instantiated, or not.
  • ifNoneExist / ifMatch carry conditional writes, which is where idempotency belongs.
  • fullUrl is how prototypes reference each other -- and it sidesteps #id, which resolves against the root resource and would be ambiguous for a graph inside a Task.
  • Instantiating is just a transaction. Fill the prototypes in and POST the Bundle. Not an algorithm this specification defines -- an operation every FHIR server already implements.

Why containment at all

A suggested resource is never written to the chart as a first-class resource before someone accepts it, and the consequence is the point: type-level queries cannot see it. Not "should not" -- cannot. A Condition?patient= search returns asserted problems and nothing else, because a contained resource has no independent existence to be returned. Confirmed against both stores in use.

So no consumer anywhere needs a "filter out suggestions" rule. A team that has never read this page cannot accidentally show a patient an unapproved diagnosis, because the query they wrote was never capable of returning one.

It is also what the base specification asks for. Containment is for content that "cannot be identified independently" (R4 §2.3.0.8), and a suggested resource has no independent identity because nobody has asserted it. Identity is what acceptance mints.

Add, update, retire -- told apart by request.method

One shape, not three, and the distinction is per entry:

the suggestion entry.request the prototype is
add an entry POST to the type's endpoint the new resource
update an entry PUT to the existing resource its suggested new state
retire an entry PUT, as above its suggested new state, carrying entered-in-error

Retiring is a kind of update, because there is no delete in this chart. Nothing is removed; an entry that should not have been made is marked entered-in-error, which its own resource type already knows how to say.

So Task.code carries no vocabulary of actions, and neither does anything else on the Task. FHIR publishes one -- R4 binds Task.code at example strength to approve | fulfill | abort | replace | change | suspend | resume -- and this specification deliberately does not use it. The action is already in the Bundle, once per entry, in the element FHIR designed to carry it.

focus does not say what a suggested-resource task writes

focus names the chart element the task is filed against, and it always points outward: never a contained resource, never a local # reference (enforced by nexus-task-focus-not-contained). A suggested change to a medication may well name that medication on focus, because that is what the task is about -- but it is entry.request that makes it an update, and a reader must not infer one from the other.

Three verbs

Conflating any two of these is the failure this section exists to prevent.

verb changes what it does
approve Task.intent proposal → order moves the task from the Suggestions list to the Tasks list. One-way. Writes nothing to the chart.
accept Task.statusaccepted claims the work. Where the owner was a CareTeam, owner becomes the claiming person
instantiate the chart fills the prototypes in and POSTs the Bundle. This is the one that writes.

They are independent. A task can be approved without being instantiated -- the order still has to be carried out. A template can be instantiated with no task anywhere near it. And instantiate is deliberately the same verb, and the same operation, as instantiating a template a user picked from a list.

What instantiating does

  1. Clone the prototypes named by the graph's entries.
  2. Fill in what the graph could not know (see the evaluation context).
  3. Rewrite the references between them.
  4. Write them to the chart -- creating, or updating whatever focus names.
  5. Carry the shared harness business key onto what was written, and complete the task.

The written resources cannot basedOn the task -- most basedOn value sets exclude Task -- so traceback rides supportingInformation / Provenance plus that shared key, which is the one value present on both sides.

Declining

Task.status = cancelled, with statusReason saying a human declined it. The task and its contained graph are retained: what was offered and refused is part of the record.

There is deliberately no rejected status. cancelled covers both a proposal someone declined and one that became moot when its alternative was approved -- which is exactly why statusReason matters more here than anywhere else on the profile. Without it the record cannot say whether a clinician considered this and said no.

The evaluation context

A template names nobody until it is used, so its List.subject is a placeholder: the element is present and carries a population expression, with no reference and no identifier. A suggested-resource Bundle, by contrast, is already about one patient.

The expressions are written against a set of named context variables borrowed from SDC's launchContext, which is itself modelled on SMART's launch parameters -- so the same names travel from a SMART app launch through form population to template instantiation:

name is on a suggested-resource task
%patient the patient in context Task.for
%user who is acting whoever instantiated
%encounter the encounter in context Task.encounter
%location the location in context Task.location
%clinical the resource being reviewed or manipulated Task.focus

An element whose expression yields nothing is removed from the instantiated resource, which is how an optional element stays optional.

Borrowed notation, not borrowed machinery

SDC evaluates these expressions against a QuestionnaireResponse. This specification uses the same notation with the context above, which it defines itself. The syntax is shared; the extraction engine is not.

Cards, alternatives and who raised the task

One decision-support evaluation may raise several proposals at once, grouped by a shared groupIdentifier, and they are mutually exclusive alternatives only when they carry Task.intent = option. Those are properties of the task rather than of what it contains, so they live on the Proposals page along with what Task.requester says about the producer.

The AI label rides the task

Where the producer is a machine, the task carries the AIAST security label -- the same label every machine-produced resource in this specification carries, whatever its type -- on the task, never on the payload. That is not a stylistic choice: per dom-5 a contained resource cannot carry meta.security at all, which is one of the reasons the labelling lives where it does.

Review state never goes inside the payload

A prototype is written as the clinical fact its citations support, not as a draft.

So a suggested Condition extracted from a note documenting an established diagnosis carries verificationStatus = confirmed. Reaching for unconfirmed to mean "not yet approved" is the tempting mistake, and a damaging one: verificationStatus is a modifier element, so it changes how every downstream consumer reads the condition. A consumer that honours it treats a well-evidenced diagnosis as clinically doubtful -- and the wrong value survives into the chart on acceptance, because instantiating touches nothing inside the prototype.

The same rule keeps a suggestion instantiable: a prototype should already satisfy the profile its instantiated form will have, including required elements such as Condition.category. A suggestion that cannot be accepted without a reviewer filling in missing elements is not a complete one.

Citations travel with the suggestion

The Provenance is an entry in the Bundle, targeting the suggested resource by its fullUrl. It carries the nexus-attributed-fact extension: for each statement, the verbatim passage it rests on and the document that passage came from.

It has to be inside rather than beside, and the reason is a plain FHIR one: a urn:uuid resolves only within its own Bundle. A Provenance sitting next to the Bundle and pointing at an entry inside it is a dangling reference, and the IG Publisher says so.

So the citations are written too, on acceptance. That is the right outcome rather than a side effect: an accepted chart entry whose provenance was dropped is machine-produced content with no trail back to the passage it came from, which is exactly what the AI attribution rules exist to prevent. Decline instead and nothing is written -- the Bundle, Provenance included, stays contained on the retained task.

Do not file the same suggestion twice

A rule that runs nightly against an open care gap will file the same task every night unless something stops it. Every task the harness writes carries an identifier under https://fhir.apps.health/NamingSystem/nexus-harness-key, and a producer searches for it before writing:

GET [base]/Task?identifier=https://fhir.apps.health/NamingSystem/nexus-harness-key|dm-review:Patient/4021:a1c-overdue

The value is derived from what the task is about -- the gap being addressed -- and never from the run that produced it. A key containing a run id, thread id or execution timestamp is a new key on every execution, which is precisely the duplicate it was meant to prevent. Because two patients can present the same gap, a patient-scoped key must include the subject.

The payoff: a producer re-running against a gap that no longer holds finds its own task and resolves it, rather than leaving a stale suggestion in somebody's worklist. A gap that closes on its own closes its own task.

If this ever needs to change

Containment is a decision, not a foreclosure. If a real consumer needs first-class suggested resources -- CPOE, e-prescribing, an external interoperability contract -- request-pattern suggestions can graduate to first-class intent = proposal resources additively. The task spine, the business identifiers and the review lifecycle do not change.