Data Integration

Plugins can automate triage of inbound clinical documents in the Data Integration queue — lab reports, imaging reports, faxes, clinical and administrative documents, and other uploaded files awaiting staff review before they’re attached to a patient’s chart.

Most of these effects work by writing a prefill suggestion to the IntegrationTask. The Data Integration UI surfaces that suggestion as a pre-populated value in the staff member’s review form — the suggested patient, document type, template values, or reviewer assignment — along with optional annotation badges. A staff member still reviews and commits the change; the plugin doesn’t directly mutate the IntegrationTask.

The exceptions are JunkDocument and RemoveDocumentFromPatient, which act on the IntegrationTask’s status or patient link immediately without writing a prefill.

Assigning a Reviewer #

To assign a staff member or team as the reviewer for a document in the Data Integration queue, import the AssignDocumentReviewer class from canvas_sdk.effects.data_integration and create an instance of it.

Attribute TypeDescription
document_idrequiredstringThe id of the IntegrationTask document.
reviewer_idoptionalstringThe id of the Staff member to assign as reviewer.
team_idoptionalstringThe id of the Team to assign as reviewer.
review_modeoptionalReviewModeReview mode. Defaults to ReviewMode.REVIEW_REQUIRED.
annotationsoptionallistList of annotations for display in the UI. See Annotations.

Supply either reviewer_id or team_id, not both. If both are supplied, the staff reviewer is used and the team is ignored when the Data Integration UI pre-populates the reviewer field. Supplying neither makes the effect a no-op.

ReviewMode #

ValueDescription
ReviewMode.REVIEW_REQUIRED (default)Document requires explicit review.
ReviewMode.ALREADY_REVIEWEDDocument is marked as already reviewed.
ReviewMode.REVIEW_NOT_REQUIREDDocument does not require review.

An example of assigning a staff reviewer. Annotations render as colored badges next to the reviewer field in the Data Integration UI — useful for surfacing why the plugin chose this reviewer:

from canvas_sdk.effects import Effect
from canvas_sdk.effects.data_integration import AssignDocumentReviewer, ReviewMode
from canvas_sdk.events import EventType
from canvas_sdk.handlers import BaseHandler


class AssignReviewerHandler(BaseHandler):
    RESPONDS_TO = EventType.Name(EventType.DOCUMENT_RECEIVED)

    def compute(self) -> list[Effect]:
        return [
            AssignDocumentReviewer(
                document_id=self.event.target.id,
                reviewer_id="4150cd20de8a470aa570a852859ac87e",
                review_mode=ReviewMode.ALREADY_REVIEWED,
                annotations=[
                    {"text": "Team lead", "color": "#4CAF50"},
                    {"text": "Auto-assigned", "color": "#FF9800"},
                ],
            ).apply()
        ]

An example of assigning a team instead of an individual reviewer:

from canvas_sdk.effects.data_integration import AssignDocumentReviewer

assign_reviewer = AssignDocumentReviewer(
    document_id="d2194110-5c9a-4842-8733-ef09ea5ead11",
    team_id="3f8a2e1c-9b4d-4f5a-8c7e-1d2b3a4c5d6e",
    annotations=[
        {"text": "Routed to Coding team", "color": "#2196F3"},
    ],
)

Categorizing a Document #

To categorize a document in the Data Integration queue into a specific document type, import the CategorizeDocument class from canvas_sdk.effects.data_integration and create an instance of it.

Attribute TypeDescription
document_idrequiredstringThe id of the IntegrationTask document to categorize.
document_typerequiredDocumentTypeDocument type information for categorizing the document.
annotationsoptionallistList of annotations for display in the UI. See Annotations.

DocumentType #

The document_type parameter is a dictionary with the following fields:

Key TypeDescription
keyrequiredstringThe unique key identifying the document type. Must match a key from the Supported Document Types table below.
namerequiredstringThe human-readable name of the document type. Should match the catalog’s Name for that key.
report_typerequiredstringThe type of report. Must be "CLINICAL" or "ADMINISTRATIVE".
template_typerequiredstring | nullMust be "LabReportTemplate", "ImagingReportTemplate", "SpecialtyReportTemplate", or null.

Supported Document Types #

Canvas’s built-in document type catalog. Use the Key value in your document_type dict; the other columns show the catalog’s report_type and the matching parse template (when applicable).

NameKeyReport TypeTemplate Type
Advance Beneficiary Notice5375e6ae238e41b4972717174be99d10ADMINISTRATIVEnull
Advance Directive / Living Will1d5a4821140dab935e90d9d73bfd7a35ADMINISTRATIVEnull
CDL (Commercial Driver License)7639c58fb4b75e2ff74270787eda80a7ADMINISTRATIVEnull
Care Management6b4b539a233145fe871e8ac703f39fcbCLINICALnull
Disability Forma5fd8d81026747c0b01f757b7935f82aADMINISTRATIVEnull
Emergency Department Report67037fd377654984b8b368b47d0ab0e4CLINICALnull
External Medical Recordsb61d0a4ebf4316a1a3beea32bec88052CLINICALnull
Handicap Parking Permit649a852657357c20491856f4eb7a2690ADMINISTRATIVEnull
Home Care Reportd368eaa8f1b2419cb792bb7876bfac8aCLINICALnull
Hospital Discharge Summary6be998e1335a4d9689cae33ec7ed6968CLINICALnull
Hospital History & Physicald9060893790744589c5252ddb81b785eCLINICALnull
Imaging Report87041869c5954337b84fd10094fe5c0aCLINICALImagingReportTemplate
In-Office Testing372e7248ba944dbeab54712078c0ec44CLINICALnull
Insurance Card2551841bcfd34e1aa839cb1e3b7ef48fADMINISTRATIVEnull
Insurer Prior Authorization0394e7a3a5c847f495414e7511d12543ADMINISTRATIVEnull
Lab Reportf605e084dcad4beca16c0f62e6586d76CLINICALLabReportTemplate
Medicaid Documentse02e0f6dc76d42aaa61384c85ca90830ADMINISTRATIVEnull
Nursing Homefc14824cdc9fdcd1e2ce005aa3019d20CLINICALnull
Operative Report7f118206607248b7b13409e69c638ebaCLINICALnull
POLST (Provider Order for Life Sustaining Treatment)cf72e522dd95fa1da1297cf3bf5e54e8ADMINISTRATIVEnull
Patient Administrative Intake Formb25601a3e8ac543f5f2d7a85006de223ADMINISTRATIVEnull
Patient Agreement2e16ccd7ad5a4bcf9d21dd51b4d16cb9ADMINISTRATIVEnull
Patient Assistanceebd8c4f6f35b4c008e512d0e3c666e95ADMINISTRATIVEnull
Patient Clinical Intake Form8c9ca86c76704d57a775cd6f48a02b6cCLINICALnull
Patient Consent7ce5a6eefedcff89a5a460f6be89d308ADMINISTRATIVEnull
Physical Examsb1146b76cd2c4b488c964cf497fb1dceCLINICALnull
Power of Attorney1ff5f640868528e633a5d45f2142161eADMINISTRATIVEnull
Prescription Card0d002c5fe86c44b0a63c08e70e0df37cADMINISTRATIVEnull
Prescription Refill Request714a8229339b4af3989403a308bdcbfbCLINICALnull
Rehabilitation Report660f6fce32a64b3f9817f4bad3d56c79CLINICALnull
Release of Information Request2283372cb0fa4962a6fcff2eb3ca080bADMINISTRATIVEnull
Specialist Consult Reportf0f1398f6f4640d29e4ff80d5481eb3fCLINICALSpecialtyReportTemplate
Uncategorized Administrative Document7ebe754f4c3b860cf80d3aa9ebd8494cADMINISTRATIVEnull
Uncategorized Clinical Document52ef59487ecabc9cdd645c21c7a35458CLINICALnull
Worker’s Compensation Documents54a06a3f06b48cbebf8dec88707272c3ADMINISTRATIVEnull

Example #

The DOCUMENT_RECEIVED event context includes available_document_types, a list of every document type the instance supports — each entry carries the key, name, report_type, and template_type you’ll need to construct a CategorizeDocument effect. The idiomatic pattern is to read from that list rather than hardcode catalog values, so a plugin keeps working as the catalog changes.

The handler below matches by name (using the Supported Document Types table above as a reference for what names to expect) and forwards the matched values to the effect. ReportType and TemplateType enum instances are constructed explicitly from the context’s string values — the SDK rejects raw strings.

from canvas_sdk.effects import Effect
from canvas_sdk.effects.data_integration import CategorizeDocument
from canvas_sdk.effects.data_integration.types import ReportType, TemplateType
from canvas_sdk.events import EventType
from canvas_sdk.handlers import BaseHandler


class CategorizeLabReports(BaseHandler):
    RESPONDS_TO = EventType.Name(EventType.DOCUMENT_RECEIVED)

    def compute(self) -> list[Effect]:
        available = self.event.context.get("available_document_types", [])
        lab_report = next((dt for dt in available if dt["name"] == "Lab Report"), None)

        if not lab_report:
            return []

        template_type = lab_report.get("template_type")

        return [
            CategorizeDocument(
                document_id=self.event.target.id,
                document_type={
                    "key": lab_report["key"],
                    "name": lab_report["name"],
                    "report_type": ReportType(lab_report["report_type"]),
                    "template_type": TemplateType(template_type) if template_type else None,
                },
                annotations=[
                    {"text": "AI Categorized", "color": "#4CAF50"},
                ],
            ).apply()
        ]

For full end-to-end usage including discovery, error handling, and the other Data Integration effects in a single handler, see the data_integration_example plugin in canvas-plugins.

Linking a Document to a Patient #

To link a document in the Data Integration queue to a patient, import the LinkDocumentToPatient class from canvas_sdk.effects.data_integration and create an instance of it. The plugin is responsible for matching the patient and supplying their key — the interpreter does not search for matching patients itself.

Attribute TypeDescription
document_idrequiredstringThe id of the IntegrationTask document.
patient_keyrequiredstringThe id of the Patient to link the document to.
annotationsoptionallistList of annotations for display in the UI. See Annotations.

An example of linking a document to a patient:

from canvas_sdk.effects import Effect
from canvas_sdk.effects.data_integration import LinkDocumentToPatient
from canvas_sdk.events import EventType
from canvas_sdk.handlers import BaseHandler
from canvas_sdk.v1.data.patient import Patient


class LinkDocumentHandler(BaseHandler):
    RESPONDS_TO = EventType.Name(EventType.DOCUMENT_RECEIVED)

    def compute(self) -> list[Effect]:
        document_id = self.event.target.id
        document_title = self.event.context.get("document", {}).get("title", "")

        # Plugin-specific matching logic — e.g., parse the document title or
        # call an OCR/LLM service to extract patient demographics, then look
        # up the patient via the SDK data module.
        patient = Patient.objects.filter(...).first()
        if not patient:
            return []

        return [
            LinkDocumentToPatient(
                document_id=document_id,
                patient_key=patient.id,
                annotations=[
                    {"text": "AI 92%", "color": "#4CAF50"},
                    {"text": f"Matched from '{document_title}'", "color": "#2196F3"},
                ],
            ).apply()
        ]

Marking a Document as Junk #

To mark a document in the Data Integration queue as junk (spam), import the JunkDocument class and create an instance of it.

Attribute TypeDescription
document_idrequiredstringThe id of the IntegrationTask document to mark as junk.

JunkDocument only works on IntegrationTasks in early-stage states: Unread, Read, Error, Unread error, or Junk (already). IntegrationTasks in Processed or Reviewed states cannot be junked — attempting to do so raises a validation error. The effect also validates that document_id resolves to an existing IntegrationTask and is a well-formed UUID; missing, malformed, or unknown IDs raise validation errors before the IntegrationTask is touched.

To avoid the validation error for tasks that have moved past the early-stage states, you can read the status field from the SDK data module and preflight-check before emitting the effect.

An example that preflight-checks the IntegrationTask status before junking:

from canvas_sdk.effects import Effect
from canvas_sdk.effects.data_integration import JunkDocument
from canvas_sdk.events import EventType
from canvas_sdk.handlers import BaseHandler
from canvas_sdk.v1.data.integration_task import IntegrationTask, IntegrationTaskStatus


class JunkDocumentHandler(BaseHandler):
    RESPONDS_TO = EventType.Name(EventType.DOCUMENT_RECEIVED)

    def compute(self) -> list[Effect]:
        document_id = self.event.target.id
        task = IntegrationTask.objects.get(id=document_id)

        # Skip tasks that have already been processed or reviewed
        if task.status in (IntegrationTaskStatus.PROCESSED, IntegrationTaskStatus.REVIEWED):
            return []

        return [JunkDocument(document_id=document_id).apply()]

Prefilling Document Fields #

PrefillDocumentFields pre-populates the parse-template fields on an IntegrationTask. Only three document types support field prefill — those whose template_type is a known parse-template family:

Document typetemplate_typeSDK data module
Lab ReportLabReportTemplateLabReportTemplate
Imaging ReportImagingReportTemplateImagingReportTemplate
Specialist Consult ReportSpecialtyReportTemplateSpecialtyReportTemplate

Document types with inline fields (Patient Consent, Power of Attorney, the Uncategorized variants, and the other ~30 entries in the Supported Document Types catalog) cannot have their fields prefilled by this effect.

The wire-level effect type for this class is UPDATE_DOCUMENT_FIELDS, which is what appears in event logs and the effects table. The class name is PrefillDocumentFields.

Attribute TypeDescription
document_idrequiredstringThe id of the IntegrationTask document.
templatesrequiredlist[PrefillTemplate]One or more templates to prefill. Must contain at least one entry.
annotationsoptionallistList of annotations for display in the UI. See Annotations.

PrefillTemplate #

A PrefillTemplate is a dictionary with the following keys:

Key TypeDescription
template_idrequiredintThe integer dbid of a LabReportTemplate, ImagingReportTemplate, or SpecialtyReportTemplate record. (Note: this is the integer primary key, not the id UUID.) Look this up via the SDK data module — see the example below.
template_namerequiredstringThe matching template’s .name.
fieldsrequireddict[str, PrefillDocumentFieldData]Map of field name to field data. Keys must match the label of a field on the chosen template — iterate template.fields.all() to discover the available labels, units, types, and required-ness.

PrefillDocumentFieldData #

A PrefillDocumentFieldData is a dictionary with the following keys:

Key TypeDescription
valuerequiredstringThe field value, stringified into the form field at render time. The same rules apply to all three template families (LabReportTemplateField, ImagingReportTemplateField, SpecialtyReportTemplateField). Currently supported are fields whose type is "float", "text", "date", "select", or "radio". For "select" and "radio", the value must exactly match one of the field’s options[*].key for the dropdown to pre-select.
unitoptionalstringUnit of measurement. Should match the corresponding template field’s units (LabReportTemplateField.units, ImagingReportTemplateField.units, or SpecialtyReportTemplateField.units).
reference_rangeoptionalstringReference range for the value.
abnormaloptionalboolWhether the value is abnormal.
annotationsoptionallistPer-field annotations. Same shape as Annotations.

Discovering Available Fields #

Before constructing a PrefillDocumentFields payload, you can inspect the template to see what labels, units, types, and option keys it defines. The same approach works for ImagingReportTemplate and SpecialtyReportTemplate.

from canvas_sdk.v1.data import LabReportTemplate
from logger import log


def inspect_lab_template(template_name) -> None:
    template = (
        LabReportTemplate.objects
        .active()
        .filter(name=template_name)
        .first()
    )
    if not template:
        log.info(f"No template found with name {template_name}")
        return

    log.info(f"Template: {template.name} (dbid={template.dbid})")
    for field in template.fields.all().order_by("sequence"):
        log.info(
            f"  {field.label}",
            type=field.type,
            units=field.units,
            required=field.required,
            code=field.code,
            code_system=field.code_system,
        )
        for opt in field.options.all():
            log.info(f"    option", key=opt.key, label=opt.label)

Sample output for a CBC Panel template:

Template: CBC Panel (dbid=42)
  Hemoglobin   type=float  units=g/dL    required=True   code=718-7   code_system=LOINC
  WBC          type=float  units=10^3/uL required=True   code=6690-2  code_system=LOINC
  Differential type=select units=        required=False  code=        code_system=
    option key=NORMAL    label=Normal
    option key=ABNORMAL  label=Abnormal

With that you can see which labels go into the fields dict’s keys, what unit value to send, and — for select/radio fields — which option.key values are valid for the value field.

Example #

An example of prefilling a Lab Report. The plugin looks up the CBC Panel template via the LabReportTemplate SDK data module, then iterates the template’s fields relation to discover which labels and units are available before filling in the values it extracted from the document.

from canvas_sdk.effects import Effect
from canvas_sdk.effects.data_integration import PrefillDocumentFields
from canvas_sdk.events import EventType
from canvas_sdk.handlers import BaseHandler
from canvas_sdk.v1.data import LabReportTemplate


class PrefillLabReportHandler(BaseHandler):
    RESPONDS_TO = EventType.Name(EventType.DOCUMENT_RECEIVED)

    def compute(self) -> list[Effect]:
        cbc = (
            LabReportTemplate.objects
            .active()
            .filter(name__icontains="CBC")
            .first()
        )
        if not cbc:
            return []

        # Values the plugin extracted from the document via OCR/LLM.
        # Hardcoded here for clarity.
        extracted = {
            "Hemoglobin": ("13.5", False),
            "WBC": ("11.2", True),
        }

        # Iterate the template's fields to discover which labels and units
        # the template defines, then fill in only the ones the plugin has
        # values for. Each field exposes `label`, `units`, `type`, and
        # `required` (see LabReportTemplateField).
        prefill_fields: dict[str, dict] = {}
        for field in cbc.fields.all():
            if field.label not in extracted:
                continue
            value, abnormal = extracted[field.label]
            prefill_fields[field.label] = {
                "value": value,
                "unit": field.units or "",
                "abnormal": abnormal,
            }

        return [
            PrefillDocumentFields(
                document_id=self.event.target.id,
                templates=[
                    {
                        "template_id": cbc.dbid,
                        "template_name": cbc.name,
                        "fields": prefill_fields,
                    },
                ],
                annotations=[
                    {"text": "Prefilled via AI", "color": "#FF9800"},
                ],
            ).apply()
        ]

Removing a Document from a Patient #

To unlink a document from its currently-linked patient in the Data Integration queue, import the RemoveDocumentFromPatient class and create an instance of it. An IntegrationTask carries at most one patient link at a time, so this effect simply clears that link.

Attribute TypeDescription
document_idrequiredstringThe id of the IntegrationTask document to unlink from its patient.

An example of unlinking a document from its patient:

from canvas_sdk.effects import Effect
from canvas_sdk.effects.data_integration import RemoveDocumentFromPatient
from canvas_sdk.events import EventType
from canvas_sdk.handlers import BaseHandler


class RemoveDocumentHandler(BaseHandler):
    RESPONDS_TO = EventType.Name(EventType.DOCUMENT_LINKED_TO_PATIENT)

    def compute(self) -> list[Effect]:
        return [
            RemoveDocumentFromPatient(
                document_id=self.event.target.id,
            ).apply()
        ]

Annotations #

The annotations field on any data integration effect accepts a list of dictionaries with the following keys:

KeyTypeDescription
textstringThe annotation text to display (e.g., “AI 95%”).
colorstringHex color code (e.g., “#4CAF50” for green).