Vitals Visualizer
A Canvas plugin that displays a “Visualize” button in the vitals section of the chart summary and shows interactive vital signs visualizations.
Structure #
vitals_visualizer_plugin/
├── handlers/
│ ├── __init__.py
│ ├── vitals_button.py # Action button handler
│ └── vitals_api.py # API endpoint with visualization
├── templates/
│ └── vitals_visualization.html # HTML template for visualization UI
├── CANVAS_MANIFEST.json # Plugin configuration
└── README.md # Documentation
Features #
- Adds a “Visualize” button to the vitals section of the chart summary
- When clicked, opens a modal in the right chart pane displaying:
- Dropdown selector for vital signs (weight, body temperature, o2sat)
- Interactive line graph with modern styling and tooltips
- Tabular display of the same data below the chart
- Shows all historical vital signs data
Vital Signs Supported #
- Weight: Patient weight measurements over time
- Body Temperature: Temperature readings with different measurement sites
- Oxygen Saturation (O2Sat): Oxygen saturation percentage readings
CANVAS_MANIFEST.json #
{
"sdk_version": "0.1.4",
"plugin_version": "0.1.0",
"name": "vitals_visualizer_plugin",
"description": "A plugin that adds visualization capabilities to patient vital signs in the chart summary",
"components": {
"protocols": [
{
"class": "vitals_visualizer_plugin.handlers.vitals_button:VitalsVisualizerButton",
"description": "A button that opens vitals visualization modal",
"data_access": {
"event": "SHOW_CHART_SUMMARY_VITALS_SECTION_BUTTON",
"read": ["v1.Observation"],
"write": []
}
},
{
"class": "vitals_visualizer_plugin.handlers.vitals_api:VitalsVisualizerAPI",
"description": "API endpoint that serves vitals visualization data and UI",
"data_access": {
"event": "",
"read": ["v1.Observation"],
"write": []
}
}
],
"commands": [],
"content": [],
"effects": [],
"views": []
},
"secrets": [],
"tags": [],
"license": "NONE",
"readme": "./README.md"
}
templates/ #
vitals_visualization.html #
handlers/ #
vitals_api.py #
Summary of What the Code Does
The vitals_api.py
file defines a class-based API endpoint called VitalsVisualizerAPI
for a Canvas Medical plugin. This endpoint provides both a user interface (HTML) and data (as JSON) for visualizing patient vitals—specifically weight, body temperature, and oxygen saturation.
Key Functionalities
API Endpoint:
The endpoint is accessed at/visualize
, and requires staff authentication viaStaffSessionAuthMixin
.- Request Handling:
The main entry point is theget()
method, which expects apatient_id
as a query parameter.- If
patient_id
is missing, it returns a 400 JSON error response. - If present, it fetches and compiles the patient’s vital sign data, then generates and returns an HTML interface for visualization.
- If
- Vitals Data Extraction:
The_get_vitals_data(patient_id)
method:- Queries Canvas Medical’s
Observation
resource for the specified patient. - Filters for observations in the “vital-signs” category that are not deleted, not entered in error, and are not “Vital Signs Panel” summary records.
- Extracts individual observations for:
- Weight (converting from ounces to pounds),
- Body Temperature (with default units °F if missing),
- Oxygen Saturation (with default units % if missing).
- Organizes the data into a dictionary keyed by vital sign name, each containing a list of timestamped values.
- Queries Canvas Medical’s
- HTML Visualization Rendering:
The_generate_visualization_html(vitals_data)
method:- Serializes the vitals data as JSON.
- Passes it as context to a template called
vitals_visualization.html
for rendering the UI.
- Error Handling and Logging:
All major steps have try/except blocks, logging errors and returning JSON error responses if needed.
How the Components Work Together
- The endpoint provides a combined UI/data interface for staff to review prescribed vitals over time for a selected patient.
- The code is built for integration into the Canvas Medical system utilizing their SDK, data models, and permission system.
- All HTML and logic for visualization are rendered server-side using a templating mechanism.
Dependencies and Assumptions
- Depends on
Observation
model/data from Canvas Medical’s API SDK. - Uses Canvas SDK facilities for routing, API responses, session authentication, and template rendering.
- Relies on templates being in the
templates/
folder, specifically one for vitals visualization. - Expects a structured logging setup.
Returned Data Types
- Templated HTML (for visualization) or JSON (for errors), wrapped in Canvas SDK response objects (
HTMLResponse
,JSONResponse
).
import json
from typing import Any, Dict, List
from canvas_sdk.effects.simple_api import HTMLResponse, JSONResponse
from canvas_sdk.handlers.simple_api import SimpleAPIRoute, StaffSessionAuthMixin
from canvas_sdk.templates import render_to_string
from canvas_sdk.v1.data import Observation
from logger import log
class VitalsVisualizerAPI(StaffSessionAuthMixin, SimpleAPIRoute):
"""API endpoint that serves vitals visualization data and UI."""
PATH = "/visualize"
def get(self) -> list[HTMLResponse | JSONResponse]:
"""Return the vitals visualization UI and data."""
patient_id = self.request.query_params.get("patient_id")
if not patient_id:
return [JSONResponse({"error": "Patient ID is required"}, status_code=400)]
try:
vitals_data = self._get_vitals_data(patient_id)
html_content = self._generate_visualization_html(vitals_data)
return [HTMLResponse(content=html_content)]
except Exception as e:
log.error(f"Error in VitalsVisualizerAPI: {str(e)}")
return [JSONResponse({"error": str(e)}, status_code=500)]
def _get_vitals_data(self, patient_id: str) -> Dict[str, List[Dict[str, Any]]]:
"""Get vitals data for the patient using Canvas vitals structure."""
try:
# Get individual vital observations from vital signs panels
vital_observations = (
Observation.objects.for_patient(patient_id)
.filter(
category="vital-signs",
effective_datetime__isnull=False,
deleted=False,
)
.exclude(name="Vital Signs Panel")
.exclude(entered_in_error__isnull=False)
.select_related("is_member_of")
.order_by("effective_datetime")
)
vitals_data = {
"weight": [],
"body_temperature": [],
"oxygen_saturation": [],
}
for obs in vital_observations:
if not obs.value or obs.name in ["note", "pulse_rhythm"]:
continue
if obs.name == "weight":
try:
value_oz = float(obs.value)
value_lbs = value_oz / 16
vitals_data["weight"].append(
{
"date": obs.effective_datetime.isoformat(),
"value": round(value_lbs, 1),
"units": "lbs",
}
)
except (ValueError, TypeError):
continue
elif obs.name == "body_temperature":
try:
value = float(obs.value)
vitals_data["body_temperature"].append(
{
"date": obs.effective_datetime.isoformat(),
"value": value,
"units": obs.units or "°F",
}
)
except (ValueError, TypeError):
continue
elif obs.name == "oxygen_saturation":
try:
value = float(obs.value)
vitals_data["oxygen_saturation"].append(
{
"date": obs.effective_datetime.isoformat(),
"value": value,
"units": obs.units or "%",
}
)
except (ValueError, TypeError):
continue
return vitals_data
except Exception as e:
log.error(f"Error collecting vitals data: {str(e)}")
return {"weight": [], "body_temperature": [], "oxygen_saturation": []}
def _generate_visualization_html(
self, vitals_data: Dict[str, List[Dict[str, Any]]]
) -> str:
"""Generate the HTML for the vitals visualization using template."""
context = {"vitals_data": json.dumps(vitals_data)}
return render_to_string("templates/vitals_visualization.html", context)
vitals_button.py #
Purpose
The code defines a custom action button for the Canvas Medical application. The button, labeled “Visualize,” appears in the chart summary’s vitals section for a patient.
Class Details
- The class
VitalsVisualizerButton
inherits fromActionButton
. - It specifies metadata such as title (“Visualize”), a unique key (
vitals_visualizer_button
), the section of the UI where it appears (vitals summary), and its priority (1).
Functionality
- When a user clicks the button, the
handle
method is triggered. - This method constructs a URL to an API endpoint for visualizing vitals:
/plugin-io/api/vitals_visualizer_plugin/visualize?patient_id=<id>
, where<id>
is the current patient ID. - The button initiates a
LaunchModalEffect
, opening a large modal on the right side of the chart pane with the title “Vitals Visualization”. - The modal displays the contents served by the constructed URL (likely a vitals graph or dashboard).
Integration with Canvas SDK
- Uses SDK classes:
ActionButton
for button behaviors and placement.LaunchModalEffect
to open a modal window within the Canvas UI.- The effect is returned in a list, as required by the SDK’s handler pattern.
Summary
This file adds a “Visualize” button to the patient chart’s vitals section, which, when clicked, opens a modal with a visual representation of the patient’s vitals data, using the Canvas plugin and effect framework.
from canvas_sdk.effects import Effect
from canvas_sdk.effects.launch_modal import LaunchModalEffect
from canvas_sdk.handlers.action_button import ActionButton
class VitalsVisualizerButton(ActionButton):
"""A button that opens the vitals visualization modal."""
BUTTON_TITLE = "Visualize"
BUTTON_KEY = "vitals_visualizer_button"
BUTTON_LOCATION = ActionButton.ButtonLocation.CHART_SUMMARY_VITALS_SECTION
PRIORITY = 1
def handle(self) -> list[Effect]:
"""Handle button click by opening vitals visualization modal."""
# The API endpoint will be at /plugin-io/api/vitals_visualizer_plugin/visualize
# We need to pass the patient ID in the URL
patient_id = self.target
return [
LaunchModalEffect(
url=f"/plugin-io/api/vitals_visualizer_plugin/visualize?patient_id={patient_id}",
target=LaunchModalEffect.TargetType.RIGHT_CHART_PANE_LARGE,
title="Vitals Visualization"
).apply()
]
_init.py #
This file is blank.