Groovy Module
Contents |
[edit]
Overview
This module was created as a proof of concept (for embedding Groovy into OpenMRS) and to serve as a base module for other modules that want to use Groovy scripting as well.
[edit]
Setup
Download and install the Groovy module omod file from the module repository.
[edit]
Usage
Find and click on the Groovy Scripting Form link within the Groovy Module section of OpenMRS' administration page. You should be taken to a simple web form with a single textarea followed by a "GO" button. Type any valid Groovy script into the textarea and click the button to execute the script.
Some variables are automatically provided (most of these represent the API service):
- cohort
- concept
- encounter
- form
- locale
- logic
- obs
- patient
- person
- user
[edit]
List 100 concepts?
import groovy.xml.MarkupBuilder writer = new StringWriter() b = new MarkupBuilder(writer) b.table(border: 1) { 1.upto(100) { c = concept.getConcept(it) tr { td(c.name, style:"font-weight:bold"); td(c.name.description) } } } println writer
[edit]
Display some encounters?
p = patient.getPatient(2) println "<h2>${p.givenName} ${p.familyName} Encounters</h2>" for (e in encounter.getEncounters(p)) { println "${String.format('%tF', e.encounterDatetime)} - ${e.creator}<br />" println "<ul>" for (o in e.getObs()) { println "<li>${o.concept.name}</li>" } println "</ul>" }
[edit]
Display some observations
p = patient.getPatient(2) println "<h2>${p.givenName} ${p.familyName}</h2>" for (o in obs.getObservations(p, false)) { println "${o.concept.name} : ${o.getValueAsString(locale)}<br />" }
[edit]
Search for patients by name
s = "nga" for (p in patient.getPatientsByName(s)) { n = "${p.givenName} ${p.familyName}".replaceAll("(?i)($s)", "<b>\$1</b>") println "${n}<br />" }
[edit]
Manage modules
import org.openmrs.module.ModuleFactory // Uncomment the next line to start the printing module // ModuleFactory.startModule(ModuleFactory.getModuleById("printing")) // list out your running modules for (m in ModuleFactory.startedModules) { println "${m.name} : ${m.started}<br />" // uncomment the next line to stop the printing module // if (m.name == "Printing") ModuleFactory.stopModule(m) }
[edit]
A more complex example
/* * For adult return visits within two clinic modules during November 2008, examine the CD4 counts * available to the clinician at the time of the encounter, calculate several decision support reminder * triggers, and then output -- as comma-separated values -- each visit along with whether or not each * reminder would have been triggered, whether the clinician ordered a CD4 count at the visit, and whether * or not a CD4 was obtained during the visit (or within the following week). Include all known CD4 counts * the the patient at the end of each line for quick validation. */ import org.openmrs.Obs import org.openmrs.Person import org.openmrs.Encounter // Easy SQL calls def sql(s) { admin.executeSQL(s,false) } // Desired date format def df = new java.text.SimpleDateFormat('MM/dd/yyyy') // Used to compare observations -- e.g., to find max/min def byValue = [compare:{ a, b -> a.valueNumeric <=> b.valueNumeric }] as Comparator def byDate = [compare:{ a, b -> a.obsDatetime <=> b.obsDatetime }] as Comparator // Some magic Groovy Obs methods Obs.metaClass.'asOf' = { Date d -> delegate.obsDatetime < d } Obs.metaClass.'above' = { value -> delegate.valueNumeric > value } Obs.metaClass.'below' = { value -> delegate.valueNumeric < value } Obs.metaClass.'text' = { "${delegate.valueNumeric} on ${df.format(delegate.obsDatetime)}" } // Add some magic to lists (ArrayList is Groovy's default for lists) for convenience ArrayList.metaClass.'asOf' = { Date d -> delegate.findAll{ it.asOf(d) }} ArrayList.metaClass.'text' = { delegate.collect{it.text()} } // More Groovy fun: checking list sizes in a way a doctor might understand -- e.g., 2.of(this) && 3.orMore(that) Integer.metaClass.'of' = { list -> list != null && list.size() == delegate } Integer.metaClass.'orMore' = { list -> list != null && list.size() >= delegate } // And why not make it easy to lookup CD4s or identifiers using internal IDs while we're enjoying Groovy magic def CD4_CONCEPT = concept.getConcept(5497) // Our CD4 concept Integer.metaClass.'cd4s' = { obs.getObservationsByPersonAndConcept(new Person(delegate),CD4_CONCEPT) } Integer.metaClass.'identifiers' = { patient.getPatient(delegate).identifiers.collect{ it.identifier } } // Fetch names of target locations targetLocations = [13, 14] // 13 = Module 2, 14 = Module 3 location = org.openmrs.api.context.Context.locationService locationName = [:]; targetLocations.each { locationName[it] = location.getLocation(it).name } // Methods to check for orders or results reported within an encounter def TESTS_ORDERED = concept.getConcept(1271) // Our TEST ORDERED concept def CD4_PANEL = concept.getConcept(657) // Our CD4 PANEL concept (answer to TESTS ORDERED if doctor ordered a CD4) // Return true if CD4 ordered within encounter for patient def cd4Ordered = { pid, encId -> 1.orMore(obs.getObservations([new Person(pid)],[new Encounter(encId)],[TESTS_ORDERED], [CD4_PANEL],null,null,null,null,null,null,null,false)) } // Return true if CD4 result within one week of encounter date def cd4Obtained = { pid, encDate -> 1.orMore(obs.getObservations([new Person(pid)],null,[CD4_CONCEPT], null,null,null,null,null,null,encDate-1,encDate+7,false)) } // Our reminders def reminders = [ // No prior CD4 1 : { encDate, cd4s -> cd4s == null || 0.of(cd4s) }, // Only one CD4 over 6 months ago // TODO: need to match Brian's logic for what "6 months" means 2 : { encDate, cd4s -> 1.of(cd4s) && cd4s[0].asOf(encDate-180) }, // Only two prior CD4s, at least one < 400, both over 6 months ago 3 : { encDate, cd4s -> 2.of(cd4s) && cd4s.min(byValue).below(400) && cd4s.max(byDate).asOf(encDate-180) }, // More than two prior CD4s, latest < 400 and over 6 months ago 4 : { encDate, cd4s -> 3.orMore(cd4s) && cd4s.max(byDate).below(400) && cd4s.max(byDate).asOf(encDate-180) }, // More than two prior CD4s (or only two if both >400) with most recent over 12 months ago and >400 5 : { encDate, cd4s -> (3.orMore(cd4s) || (2.of(cd4s) && cd4s.min(byValue).above(400))) && cd4s.max(byDate).asOf(encDate - 365) } ] // end of reminders // Select adult return visit encounters at our target locations between 3-Nov-2008 and 28-Nov-2008 rs = sql(""" select patient_id, encounter_id, encounter_datetime, location_id from encounter where encounter_datetime >= '2008-11-03' and encounter_datetime < '2008-11-29' and encounter_type = 2 /* ADULTRETURN encounter type */ and location_id in (${targetLocations.join(',')}) """) // Loop through the encounters, creating a CSV output println "Date,Patient,pid,Encounter,Module,${reminders.keySet().join(',')},Any,Ordered,Obtained,CD4s" rs.each { row -> (pid, encId, encDate, locationId) = row identifier = pid.identifiers()[0] // first patient identifier is preferred module = locationName[locationId] // the clinic module allCd4s = pid.cd4s() // all known CD4 counts for the patient cd4s = allCd4s.asOf(encDate) // only CD4s prior to the current encounter ordered = cd4Ordered(pid, encId) ? 'Y' : 'N' obtained = cd4Obtained(pid, encDate) ? 'Y' : 'N' reminderResults = reminders.collect { n, fn -> fn(encDate,cd4s) ? 1 : 0 } // one line runs our reminders anyReminder = reminderResults.sum() > 0 ? 1 : 0 // output in CSV format print "${df.format(encDate)},$identifier,$pid,$encId,$module,${reminderResults.join(',')},$anyReminder," print """$ordered,$obtained,"${allCd4s.sort(byDate).reverse().text().join(',')}"\n""" }
[edit]
Errors
[edit]
java.lang.ClassFormatError: Invalid method Code length 82342 in class file Script1
Solution: Java only allows methods that are 64K long. Reduce the size of your groovy script.
