Find this workflow
This workflow is available as a template in Workflow Automation.
Log in and open Workflow Automation.
Select Templates from the left-hand menu.
Search for
ACP- you will see three ACP templates listed.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 |
| How many days of activity history to check | 7 |
| 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_omissionSeed 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_idto 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 /activityQuery parameters:
start: 0limit: 1000column: iddirection: 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] }}EqualstrueCombine: 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 /casesQuery parameters:
start: 0limit: 1column: iddirection: 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 /casesNo 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 AnalysisDescription:
{{ $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 ActivitiesLink 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 |
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 |
