Skip to main content

Medication Omission Automation Workflow Technical Reference

A node-by-node technical reference for the Medication Omission Automation Workflow template - covers node configuration, data flow, and connections for developers and administrators setting up, customising, or troubleshooting the workflow.

Written by Cameron Falconer

Find this workflow

This workflow is available as a template in Workflow Automation.

  1. Log in and open Workflow Automation.

  2. Select Templates from the left-hand menu.

  3. Search for ACP - you will see three ACP templates listed.

  4. Click Use this template on the Medication Omission Automation Workflow to create your own editable copy.

Note: Clicking Use this template creates a new workflow based on the template. You can then modify and configure it without affecting the original template.

Parameters

Before activating the workflow, add the following two parameters:

Parameter

Purpose

Default

medication_omission_activityWindow

How many days of activity history to check

7

medication_omission_activityThershold

Minimum number of missed medications to trigger an alert

3

Important: Both parameters must be added to the workflow before activating the template. The workflow will use default values if they are not set, but it is best practice to configure them explicitly for your organisation's needs.

How the workflow runs

This workflow automatically detects customers with a high number of incomplete medication activities within a configurable time window, and sends a notification to the workflow owner via the Evo Web Feed. It runs on a schedule and skips customers who have already been notified for the same pattern, preventing duplicate alerts.

The workflow has two entry points, then follows a single path through data lookup, filtering, and per-customer notification logic.

Node reference

1. Schedule Trigger

Type: Trigger

Starts the workflow automatically every 15 minutes. No parameters are required - the workflow runs continuously in the background without any manual action.

2. Manual Trigger

Type: Trigger

Allows you to start the workflow on demand for testing or one-off runs. No parameters are available on this node.

3. create_omission_table

Type: Data Spaces - Create Data Space & Insert Records

Creates the acp_data_omission Data Space if it does not already exist, and seeds it with a placeholder record (notificationId: "-1").

This Data Space is used throughout the workflow to track which notifications have already been sent.

Note: The "Only create if it does not exist" option is set to false, meaning the node will attempt to create the space on every run. The Data Space connector handles this gracefully.

Technical detail

  • Resource: Data Space

  • Operation: Create Data Space & Insert Records

  • Name: acp_data_omission

  • Seed record:

[{ "notificationId": "-1" }]

The seed record ensures the Data Space is never empty, which prevents query errors in downstream nodes.

4. find_data_spaces

Type: Data Spaces - List Data Spaces

Retrieves a list of all available Data Spaces in the connected instance. The result is passed to the next node to identify the correct Data Space ID for acp_data_omission.

Technical detail

  • Resource: Data Space

  • Operation: List Data Spaces

  • Output is consumed by data_sapce_id to locate the correct space by name.

5. data_sapce_id

Type: Code - JavaScript

Searches the list of Data Spaces returned by find_data_spaces and extracts the ID of the space named acp_data_omission. This ID is used by all subsequent Data Space nodes to read from and write to the correct space.

If no matching space is found, the node returns null, which will cause downstream nodes to fail gracefully.

Technical detail

const dataSpaces = {{ $node["find_data_spaces"].output.data }}; const targetName = "acp_data_omission";  const matchingIds = dataSpaces   .filter(space => space.name === targetName)   .map(space => space.id);  result = matchingIds.length > 0 ? matchingIds : null;

6. 7 Days Ago

Type: Code - JavaScript

Calculates the start of the activity window by subtracting the configured number of days from the current execution time. The result is an ISO 8601 date string used to filter activities in the next node.

Technical detail

Uses the medication_omission_activityWindow workflow parameter (defaults to 7 if not set).

const executionStartTime = new Date($execution.startedAt); const activityWindow = $params.medication_omission_activityWindow ?? 7; const windowStart = new Date(executionStartTime); windowStart.setDate(windowStart.getDate() - activityWindow); result = windowStart.toISOString();

7. Activity_Connector

Type: Connector - Care Planning API

Retrieves all medication activities marked as "Not Complete" that have a completedDateTime on or after the date calculated by the 7 Days Ago node. Returns up to 1,000 records, sorted by ID ascending.

Technical detail

  • Instance: Care Planning (83d9e6e61bbe4c84a52e65788fed04cb)

  • Folder: Activity Controller

  • Endpoint: GET /activity

  • Query parameters:

    • start: 0

    • limit: 1000

    • column: id

    • direction: 0 (ascending)

  • MQL filter:

type=Medication,status=Not Complete,completedDateTime>={{ $node["7 Days Ago"].output.data[0] }}

8. Filter_Group_Activities_By_Customer

Type: Code - JavaScript

Groups the activities returned by Activity_Connector by customer ID, counts how many are incomplete per customer, and filters out any customers who are below the configured threshold. Only customers who have missed at or above the threshold number of medications are passed forward.

Technical detail

Uses the medication_omission_activityThershold workflow parameter (defaults to 3 if not set).

const activities = {{ $node["Activity_Connector"].output.data }}; const threshold = $params.medication_omission_activityThershold ?? 3;  const grouped = {}; activities.forEach(activity => {   const id = activity.customerId;   if (!grouped[id]) {     grouped[id] = { customerId: id, missedCount: 0, activities: [], lastUpdatedDateTime: null };   }   if (activity.status === "Not Complete") {     grouped[id].missedCount++;   }   grouped[id].activities.push(activity);   grouped[id].lastUpdatedDateTime = activity.lastUpdatedDateTime; });  result = Object.values(grouped).filter(g => g.missedCount >= threshold);

Output: array of objects, each with customerId, missedCount, activities[], and lastUpdatedDateTime.

9. Loop

Type: Loop - For Each Item

Iterates over each customer group produced by Filter_Group_Activities_By_Customer. All nodes from here onwards run once per customer. The loop has a maximum of 1,000 iterations as a safety limit.

Technical detail

  • Loop Mode: For Each Item

  • Input Array: {{ $node["Filter_Group_Activities_By_Customer"].output.data }}

  • Max Iterations: 1000

  • Each iteration exposes the current customer group as $node["Loop"].output.item.

10. resolve_notification_id

Type: Code - JavaScript

Builds a unique notification ID for the current customer by concatenating their customerId, missedCount, and lastUpdatedDateTime. This ID is used to check whether a notification for this exact pattern has already been sent.

Technical detail

// Access the Loop output data const loopItem = {{ $node["Loop"].output.item }};  // Create notification ID by concatenating customerId, missedCount, and lastUpdatedDateTime const notificationId =   `${loopItem.customerId}:${loopItem.missedCount}:${loopItem.lastUpdatedDateTime}`;  result = notificationId;

Example output: "customer-123:5:2026-08-19T09:00:00.000Z"

11. notification_already_exists

Type: Data Spaces - Query Records

Queries the acp_data_omission Data Space to retrieve all previously logged notification records. The result is used by the next node to check whether the current notification ID has already been recorded.

Technical detail

  • Resource: Record

  • Operation: Query Records

  • Data Space: {{ $node["data_sapce_id"].output.data[0] }}

  • Query Mode: Simple (no filters - returns all records)

  • Maximum Records: 0 (returns all matching records)

12. find_notification

Type: Code - JavaScript

Checks whether the notification ID generated by resolve_notification_id already exists in the records returned by notification_already_exists. Returns true if the notification has not been sent yet (it is safe to proceed), or false if it has already been sent.

Technical detail

const existingNotifications = {{ $node["notification_already_exists"].output.data }}; const resolvedNotificationId = `{{$node["resolve_notification_id"].output.data[0]}}`;  const found = existingNotifications.some(item =>   item.data.notificationId === resolvedNotificationId );  result = !found;

Returns true = notification not yet sent - continue. Returns false = already sent - loop moves to next customer.

13. notification_not_sent

Type: Filter / Branch

Gates the rest of the workflow. If find_notification returns true (notification not yet sent), the workflow continues to send the alert. If it returns false, this branch stops and the loop moves on to the next customer, preventing duplicate notifications.

Technical detail

  • Condition: {{ $node["find_notification"].output.data[0] }} Equals true

  • Combine: AND (all must match)

14. Case_Connector

Type: Connector - Care Planning API

Looks up the care case for the current customer to retrieve their branch name. Returns a maximum of 1 result, filtered by the current customer's ID.

Technical detail

  • Instance: Care Planning

  • Folder: Case Controller

  • Endpoint: GET /cases

  • Query parameters:

    • start: 0

    • limit: 1

    • column: id

    • direction: 0 (ascending)

    • skipCount: true

  • MQL filter: customer.id=={{ $node["Loop"].output.item.customerId }}

15. Application_Subscription

Type: Connector - Care Planning API

Retrieves the application subscription record for the current customer's case. This is used by the Evo_Web_Feed_2 node to construct the deep link URL that takes the notification recipient directly to the customer's case activities screen.

Technical detail

  • Instance: Care Planning

  • Folder: Case Controller

  • Endpoint: GET /cases

  • No MQL filter is applied - the node relies on upstream context to scope the result.

  • Output (acpServerUrl) is used to build the desktop URL in the web feed notification.

16. Format_Message_Feed

Type: Code - JavaScript

Builds the markdown-formatted body of the notification. It pulls the customer's full name from their activity records, the branch name from Case_Connector, and lists up to three of the missed activities with their deadlines. If there are more than three, a summary line is appended.

Technical detail

const item = {{ $node["Loop"].output.item }}; const customerId = item.customerId;  let customerName = "Unknown Customer"; if (item.activities && item.activities.length > 0 && item.activities[0].customer?.fullName) {   customerName = item.activities[0].customer.fullName; }  let branchName = "No branch assigned"; const caseResults = {{ $node["Case_Connector"].output.data.results }}; if (caseResults?.length > 0 && caseResults[0].branch?.name) {   branchName = caseResults[0].branch.name; }  const missedCount = item.missedCount || 0;  const formatDateTime = (dateString) => {   if (!dateString) return "No deadline";   const date = new Date(dateString);   const formattedDate = date.toLocaleDateString('en-GB', { day: 'numeric', month: 'short', year: 'numeric' });   const formattedTime = date.toLocaleTimeString('en-GB', { hour: '2-digit', minute: '2-digit', hour12: false });   return `${formattedDate} at ${formattedTime}`; };  let activitiesText = ""; if (item.activities?.length > 0) {   const displayActivities = item.activities.slice(0, 3);   const additionalCount = Math.max(0, item.activities.length - 3);   activitiesText = displayActivities     .map(a => `* ${a.name || a.id} - Deadline: ${formatDateTime(a.deadline)}`)     .join("\n");   if (additionalCount > 0) activitiesText += `\n* ... and ${additionalCount} more incomplete activities`; } else {   activitiesText = "* No activities recorded"; }  const markdown = `# *Customer:* ${customerName}  **Branch:** ${branchName}  **Incomplete Activities Count:** ${missedCount}  **Activities:** ${activitiesText}`;  result = markdown;

17. Evo Web Feed 2

Type: Evo Web Feed - Send Feed Item

Sends the formatted notification to the Evo Web Feed. By default, the notification is delivered to the workflow owner (the identity the workflow runs as). The feed item includes a direct link to open the customer's case activities in Access Care Planning.

Technical detail

  • Operation: Send Feed Item

  • Correlation ID: {{ $evo.execution.id }} (set at run time - ensures idempotency)

  • Title: Medication Omission Analysis

  • Description: {{ $node["Format_Message_Feed"].output.data[0] }} (the markdown body built by node 16)

  • Feed Type: Information (FYI - no user action required)

  • Include Date: false

  • Include Links: true

    • Link 1 text: Open Case Activities

    • Link 1 URL: {{ $node["Application_Subscription"].output.data[0].acpServerUrl }}/eoutcomeservice/branch/case_activities?caseId=...

  • Include Copilot Prompts: false

  • Include Actions: false

  • Include MFE: false

18. log_notification

Type: Data Spaces - Insert Records

Writes the notification ID for the current customer to the acp_data_omission Data Space. This ensures that on the next workflow run, find_notification will detect that this customer has already been notified and skip them, preventing duplicate alerts.

Technical detail

  • Resource: Record

  • Operation: Insert Records

  • Data Space: {{ $node["data_sapce_id"].output.data[0] }}

  • Record inserted:

[{   "notificationId": "{{ $node["Loop"].output.item.customerId }}:{{ $node["Loop"].output.item.missedCount }}:{{ $node["Loop"].output.item.lastUpdatedDateTime }}" }]

Workflow summary

Stage

Nodes

What happens

Trigger

1-2

Workflow starts on schedule or manually

Setup

3-5

Creates/finds the acp_data_omission tracking Data Space

Data fetch

6-7

Calculates the activity window and retrieves incomplete medication activities

Filter

8

Groups activities by customer and applies the missed medication threshold

Per-customer loop

9-18

For each at-risk customer: checks for duplicate, sends notification, logs the record


Did this answer your question?