Last active
September 26, 2018 08:58
-
-
Save pacharanero/f83f66d8e266ef4d223405f1f56ced85 to your computer and use it in GitHub Desktop.
An illustration of how OpenEHR Expression Language woud look as Ruby
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# real ruby libraries you can use to save having to develop them anew | |
require 'iso-639' | |
# fictional ruby libraries that would add the missing datatypes | |
require 'icd10' # a library which handles ICD-10 bindings | |
require 'snomed-ct' # a library which handles SNOMED-CT bindings | |
require 'adl-binding' # a library which lets us handle ADL as a ruby object | |
require 'terminology' # some arbitrary terminology helper library | |
# openEHR-ELOM.breast_cancer_treatment.v1 | |
# inherits from BasicOpenEhrRule | |
class BreastCancerRule < BasicOpenEhrRule | |
# metadata | |
def initialize(cancer_diagnosis, | |
has_metastasis, | |
er_positive, | |
pr_positive, | |
her2_positive, | |
ki67, | |
tnm_t, | |
tnm_n, | |
tnm_g, | |
has_dx_transmural_mi, | |
ejection_fraction, | |
hs_dx_hf_stage_2_4) | |
# @@class_variables | |
@@title = 'openEHR-ELOM.breast_cancer_treatment.v1' | |
@@language = { | |
original_language: ISO_639.find("en") # using an existing language means the existing libraries for this stuff are already done! | |
} | |
@@description = { | |
lifecycle_state: 'unmanaged', | |
original_author: 'placeholder text' | |
} | |
@@use_model = 'org.openehr.rm' | |
end | |
def data_context | |
# I'm guessing this bit is a kind of 'capability statement' that the parser will use | |
inputs = { | |
cancer_diagnosis: "Terminology_code", | |
has_metastasis: "Boolean", | |
er_positive: "Boolean", | |
pr_positive: "Boolean", | |
her2_positive: "Boolean", | |
ki67: "Real", | |
tnm_t: "Boolean", | |
tnm_n: "Boolean", | |
tnm_g: "String", | |
has_dx_transmural_mi: "Boolean", | |
ejection_fraction: "Real", | |
hs_dx_hf_stage_2_4: "Boolean", | |
} | |
outputs = { recommendation: "String" } | |
end | |
def preconditions | |
return recommendation = "unsuitable rule for this diagnosis" unless cancer_diagnosis = ICD10::Code([ICD_10::C50|Breast cancer|)] | |
end | |
def rule_definition | |
if has_metastasis | |
# Luminal A | |
if tumor_molecular_subtype(er_positive, pr_positive, her2_positive, ki67) = Terminology.code("[1111|luminal A|]") then recommendation = "rec1" | |
# Luminal B (HER2 negative) | |
elsif tumor_molecular_subtype (er_positive, pr_positive, her2_positive, ki67) = Terminology.code("[2222|Luminal B (HER2 negative)|]") then recommendation = "rec2" | |
# Luminal B (HER2 positive) | |
elsif tumor_molecular_subtype (er_positive, pr_positive, her2_positive, ki67) = Terminology.code("[3333|Luminal B (HER2 positive)|]") then recommendation = "rec3" | |
# HER2 type | |
elsif tumor_molecular_subtype (er_positive, pr_positive, her2_positive, ki67) = Terminology.code("[4444|HER2|]") then recommendation = "rec4" | |
# Triple negative | |
elsif tumor_molecular_subtype (er_positive, pr_positive, her2_positive, ki67) = Terminology.code("[55555|Triple negative|]") then recommendation = "rec5" | |
end | |
return recommendation | |
else | |
# ruby has a 'case...when' switch statement which avoids too much madness with nested if..then..else..end | |
case tumor_molecular_subtype (er_positive, pr_positive, her2_positive, ki67) | |
when Terminology.code([1111|luminal A|] | |
if tnm_major_number (tnm_t) < 3 && tnm_major_number (tnm_n) < 2 && tnm_major_number (tnm_g) < 3 then | |
recommendation = :no_intervention_message | |
else | |
# consider contraindications to anthracyclines | |
if has_dx_transmural_mi || ejection_fraction < 0.4 || hs_dx_hf_stage_2_4 then | |
recommendation = :recommend_cmf_message | |
else | |
if has_critical_cardio_pathology or age > 75 | |
recommendation = :recommend_apirubicin_cyclophosphamide_message | |
else | |
recommendation = :recommend_ac_message | |
end | |
end | |
end | |
when Terminology.code("[2222|Luminal B (HER2 negative)|]") | |
if tnm_t = "1a" && tnm_major_number (tnm_n) = 0 then recommendation = "rec6" | |
when Terminology.code("[3333|Luminal B (HER2 positive)|]") | |
if tnm_t = "1a" && tnm_major_number (tnm_n) = 0 then recommendation = "rec7" | |
elsif tnm_t matches {"T1b", "T1c"} && tnm_major_number (tnm_n) = 0 then recommendation = "rec8" | |
elsif tnm_major_number (tnm_t) matches {|2..4|} && tnm_major_number (tnm_n) > 0 then recommendation = "rec9" | |
end | |
when Terminology.code("[4444|HER2|]") | |
if tnm_t = "1a" && tnm_major_number (tnm_n) = 0 then recommendation = "re10c5" | |
elsif tnm_t matches {"T1b", "T1c"} && tnm_major_number (tnm_n) = 0 then recommendation = "rec11" | |
elsif tnm_major_number (tnm_t) matches {|2..4|} && tnm_major_number (tnm_n) > 0 then recommendation = "rec12" | |
else | |
end | |
when Terminology.code("[55555|Triple negative|]") | |
recommendation = "rec5" | |
end | |
return recommendation | |
end # end of rule_definition | |
# this utility method returns a terminology based on the inputs about ER status etc | |
def tumor_molecular_subtype(is_er_positive, is_pr_positive, is_her2_positive, ki67_level) | |
# these rules are all one-liners in Ruby, although you can have a multiline if..elsif..else..end instead | |
if is_er_positive && !is_her2_positive && ki67_level < 0.20 then return Terminology.new("[1111|luminal A|]") | |
# Luminal B (HER2 negative) | |
elsif is_er_positive && !is_her2_positive && ki67_level >= 0.20 then return Terminology.new("[2222|Luminal B (HER2 negative)|]") | |
# Luminal B (HER2 positive) | |
elsif is_er_positive && is_her2_positive then return Terminology.new("[3333|Luminal B (HER2 positive)|]") | |
# HER2 | |
elsif !is_er_positive && !is_pr_positive && is_her2_positive then return Terminology.new("[4444|HER2|]") | |
# Triple negative | |
elsif !is_er_positive && !is_pr_positive && !is_her2_positive then return Terminology.new("[55555|Triple negative|]") | |
end | |
end | |
def terminology | |
symbol_definitions = AdlBinding.new("< | |
["en"] = < | |
["er_positive"] = < | |
text = <"Oestrogen receptor positive"> | |
description = <"Oestrogen receptor positive"> | |
> | |
> | |
>") | |
end | |
def data_bindings | |
content_bindings = AdlBinding.new('< | |
["openEHR-EHR-OBSERVATION.cancer_investigation.v1"] = < | |
["er_positive"] = < | |
target = <"/data/events[id3]/data/items[id5]/value/magnitude"> | |
direction = <"in"> | |
> | |
></div> | |
> | |
>') | |
query_bindings = AdlBinding.new('< | |
["https://oncology.health.org/cdr/"] = < | |
["has_dx_transmural_mi"] = < | |
query_id = <"dx_transmural_mi">, | |
parameters = < | |
["type"] = <"xxx"> | |
> | |
> | |
>') | |
end | |
def execute | |
preconditions # execute the preconditions | |
rule_definition # execute the rule | |
end | |
end #end of class definition |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
This is an example only and is probably not completely valid ruby, but you get the idea