Drug-allergy interaction checking for programmatic medication creation #1750
Replies: 1 comment
|
Great question — this is a feature-parity ask we've gotten before, so I can give you a fairly complete picture of what exists today. Short version: the drug-allergy (and drug-drug) screening engine is exposed to you programmatically via the SDK's ontologies HTTP client, so you can run it yourself whenever you need it. But it does not run automatically when you create a medication outside the UI command flow — a medication created via the FHIR API is stored as-is with no screening. Details below, mapped to your three questions. We haven't merged the public documentation for this yet, but see below on how to use the feature! 1. Is there a supported way to get the drug-allergy interaction result programmatically?Yes — via the SDK. The same screening the UI commands use is available to plugins through import json
from urllib.parse import urlencode
from canvas_sdk.utils.http import ontologies_http
# Drug-allergy check: candidate medication vs. the patient's allergy list
ontologies_http.get_json(
f"/fdb/medication-allergy/?{urlencode({
'consideredMedication': json.dumps(considered_codings),
'allergyList': json.dumps(allergy_ids),
})}"
).json()The response is a list of interaction objects (one per matched allergy), each distinguishing a direct match from a cross-sensitive one (e.g. penicillin allergy → a cephalosporin): [
{
"drug": { "...": "full FDB medication payload" },
"allergy_concept": {
"dam_allergen_concept_id": 476,
"dam_allergen_concept_id_type": 1,
"dam_allergen_concept_id_description": "Penicillins"
},
"specific_ingredients": [],
"cross_sensitive_ingredients": []
}
]An empty list Building the inputs from the SDK data modelsThis is the part that isn't obvious, so here's exactly what the two parameters look like and how to construct them from the SDK.
considered_codings = [["217012", "FDB"]]
allergy_ids = [["476", 1], ["1886", 6]]The SDK gives you everything you need. The SDK import json
from urllib.parse import urlencode
from canvas_sdk.commands.constants import CodeSystems
from canvas_sdk.utils.http import ontologies_http
from canvas_sdk.v1.data import Medication, Patient
def fdb_codings(med: Medication) -> list[list[str]]:
"""The medication's FDB codings as [code, "FDB"] pairs."""
return [
[c.code, "FDB"]
for c in med.codings.all()
if c.code and c.system == CodeSystems.FDB
]
def screen_medication_against_allergies(patient: Patient, considered_med: Medication) -> list[dict]:
"""Run Canvas's drug-allergy screening for one candidate medication against a patient's allergies."""
# Candidate medication: [[code, "FDB"], ...]
considered_codings = fdb_codings(considered_med)
# Patient's allergies: [[substance_code, category], ...]
# Use the allergy's FDB coding specifically — its code is the FDB allergen-concept
# id, which is what pairs with `category` (the FDB allergen-concept *type*). A
# non-FDB coding's code wouldn't be a valid pairing and would fail to match.
allergy_ids = []
for allergy in patient.allergy_intolerances.committed():
fdb_coding = next(
(c for c in allergy.codings.all() if c.code and c.system == CodeSystems.FDB),
None,
)
if fdb_coding:
allergy_ids.append([fdb_coding.code, allergy.category])
if not considered_codings or not allergy_ids:
return [] # nothing to screen → "all clear"
return ontologies_http.get_json(
f"/fdb/medication-allergy/?{urlencode({
'consideredMedication': json.dumps(considered_codings),
'allergyList': json.dumps(allergy_ids),
})}"
).json()This mirrors exactly what Canvas does internally when it builds the same request for the in-chart commands, so you'll get the same screening result. On the "event" part of your question: there's no passive event that broadcasts the UI's own screening results — Canvas computes those on demand to render the warnings and doesn't emit them. Instead, you run the screening yourself whenever you need it: call 2. If we create a medication via MedicationStatement, does any screening run?Via the FHIR API: no — the medication is stored as-is, with no interaction check. This is intentional and tied to what (Worth noting: The command path behaves differently, though. The automatic warnings are a feature of a command sitting in its staged / in-review state. If you originate a Medication Statement command through the Commands module (SDK) and don't commit it, it sits in that staged window just like a clinician-entered command would — and the interaction warnings will be computed and displayed. It's only the FHIR write that skips screening, because that lands committed immediately and never passes through the staged window. So:
Either way, once a medication is on the patient's active list it's part of the "screen-against" set the next time a clinician works a command in the chart. And if you want the screening result at the moment of a programmatic (FHIR) write, you run it yourself (see Q1). 3. If we route through a command (e.g. PrescribeCommand), is the interaction result exposed to us, or only rendered to the provider?Routing through the command does cause the screening to run at the provider-review step (that's exactly what populates the in-chart warnings, including the "block the send" gate when an allergy reaches the configured block level). But the screening result is computed for display to the provider — it is not handed back to your plugin/integration code as a structured value or event. So going through the command gets you the human-facing safety check, not a programmatic result you can act on. If what you need is the result in your own code, the command path won't give you that — calling Bonus: drug-drug interaction screeningSince you're working in this area you'll almost certainly want the drug-drug check too, so here it is documented the same way. It uses the sibling endpoint It takes a single candidate medication and the patient's existing medication list, and returns the interactions between the candidate and each existing med. Note: it only checks the candidate against the list — it does not check the existing meds against each other. Input shapes:
SDK helper — same data models, building the per-drug grouping correctly: import json
from urllib.parse import urlencode
from canvas_sdk.utils.http import ontologies_http
from canvas_sdk.v1.data import Medication, Patient
# reuse fdb_codings() from the drug-allergy helper above
def screen_drug_drug(patient: Patient, considered_med: Medication) -> list[dict]:
"""Run Canvas's drug-drug screening for one candidate medication against the patient's active meds."""
considered_medication = fdb_codings(considered_med)
# One inner list per existing drug. Exclude the candidate itself so it
# doesn't match against itself.
medication_list = [
fdb_codings(med)
for med in Medication.objects.for_patient(patient).active()
if med.id != considered_med.id
]
medication_list = [codings for codings in medication_list if codings]
if not considered_medication or not medication_list:
return [] # nothing to screen → "all clear"
return ontologies_http.get_json(
f"/fdb/medication-list-interaction/?{urlencode({
'consideredMedication': json.dumps(considered_medication),
'medicationList': json.dumps(medication_list),
})}"
).json()Response shape — a list of interaction objects, one per interacting pair: [
{
"existing_medication": 155744,
"considered_medication": 217012,
"existing_medication_description": "metformin 500 mg tablet",
"severity": 2,
"severity_text": "Severe Interaction: Action is required to reduce the risk of severe adverse interaction.",
"monograph_text": [
"Drug A / Drug B",
"Clinical Effects: ...",
"Mechanism of Action: ...",
"References: ..."
]
}
]
Note the convention: lower number = more clinically significant (1 is the most severe). The in-chart UI uses the same scale. An empty list Screening multiple new meds at once: if you're adding several new medications in one operation, checking each only against the patient's current list would miss interactions between the new meds. The fix is to accumulate — after screening each new med, add it to the list you screen the next one against: existing = list(Medication.objects.for_patient(patient).active())
for new_med in new_meds:
# screen new_med against `existing`, then:
existing.append(new_med) # so the next new med is checked against it tooThe same internal/unstable-endpoint caveats from Q1 apply here as well. Hope this helps! |
Uh oh!
There was an error while loading. Please reload this page.
Hi,
In the UI, when a provider adds a medication for a patient who has a recorded allergy to it, Canvas surfaces a drug-allergy interaction warning so the provider can make a decision. We need the equivalent safety check when medications are created programmatically rather than by a human in the UI command.
What we want: When we add a medication for a patient via the API/SDK, we want Canvas to tell us whether it conflicts with that patient's recorded allergies (e.g. penicillin allergy + a penicillin-class drug), so we can act on it — the same interaction result the UI already computes.
Questions:
Is there a supported way to get Canvas's drug-allergy interaction result programmatically — an endpoint, SDK call, or event — given a medication and a patient? Or is this screening currently available only inside the UI commands?
If we create a medication via MedicationStatement (or another API path), does any screening run, or is it stored as-is with no check?
If the recommended pattern is to route through a command (e.g. PrescribeCommand) so the check runs at the provider-review step, can you confirm that, and tell us whether the interaction result is exposed to us at all vs. only rendered to the provider?
This is really about API/UI feature parity — the interaction screening already exists in your platform, and we'd like to consume it on the programmatic path rather than have it only fire for a human in the UI. What's the supported way to do that today, and if there isn't one yet, is it on the roadmap?
Thanks,
Jayant
All reactions