I wanted to list out some observations for a patient and used the following Groovy script:
def sql(s) {admin.executeSQL(s,true) }
patientIdentifier = "999-3" // a test patient, of course
sql("""
select
date(o.obs_datetime),
(select name from concept_name where concept_id=o.concept_id limit 1) as question,
case c.datatype_id
when 1 /* numeric */ then cast(o.value_numeric as char)
when 2 /* coded */ then (select min(name) from concept_name where concept_id=o.value_coded)
when 3 /* text */ then value_text
when 6 /* date */ then cast(date(o.value_datetime) as char)
when 7 /* time */ then cast(time(o.value_datetime) as char)
when 8 /* datetime */ then cast(o.value_datetime as char)
when 10 /* boolean */ then if(o.value_numeric=1,'TRUE','FALSE')
else '?'
end as answer
from
obs o
left outer join
concept c
on c.concept_id=o.concept_id
where
o.person_id = (select patient_id from patient_identifier where identifier = '$patientIdentifier' limit 1)
order by
o.obs_datetime desc
""").collect{ it.join(": ") }.join("n")
which generated output like this:
2011-02-18: PATIENT HAD SEX IN LAST 6MO: TRUE 2011-02-18: PlAN FOR METHOD OF FAMILY PLANNING, DETAILED: ? 2011-02-18: QUANTITY: 5 2011-02-18: FAMILY PLANNING METHOD PLAN: INITIATION 2011-02-18: METHOD OF FAMILY PLANNING: ECPS 2011-02-18: HIV DISCLOSURE TO ANYONE, SPECIFIC: OTHER HOUSEHOLD MEMBER 2011-02-18: REASON FOR REFUSAL - FAMILY PLANNING: TRYING TO CONCEIVE NOW 2011-02-18: PlAN FOR METHOD OF FAMILY PLANNING, DETAILED: ? 2011-02-18: METHOD OF FAMILY PLANNING: MALE CONDOMS 2011-02-18: QUANTITY: 5 2011-02-18: FAMILY PLANNING METHOD PLAN: INITIATION 2011-02-18: METHOD OF FAMILY PLANNING: BTL 2011-02-18: FREETEXT, GENERAL: POSITIVE 2011-02-18: REASON FOR REFUSAL - FAMILY PLANNING: ABSTINENCE 2011-02-18: FAMILY PLANNING: TRUE 2011-02-18: REVIEW OF MEDICAL HISTORY: ICTERUS 2011-02-18: PATIENT REPORTED PROBLEM: YES 2011-02-18: CURRENT MEDICATIONS: ALUVIA 2011-02-18: REVIEW OF MEDICAL HISTORY: DEPRESSION
Here’s a simple jetty server using a short Groovy script:
@Grab(group='org.mortbay.jetty', module='jetty-embedded', version='6.1.14')
import org.mortbay.jetty.*
import org.mortbay.jetty.servlet.*
import groovy.servlet.*
import javax.servlet.*
import javax.servlet.http.*
class MyServlet extends HttpServlet {
void doPost(HttpServletRequest request, HttpServletResponse response) {
println "--- Received ${new Date()}n" + request.getParameter('data')
}
}
def server = new Server(8888)
def root = new Context(server, "/", Context.SESSIONS)
root.setResourceBase(".")
root.addServlet(new ServletHolder(new MyServlet()), "/")
server.start()
Running the script with groovy AtlasServer.groovy will accept HTTP POSTs at the designated port (8888) and dump posted data to the screen. For example:
$ curl -X POST -d "data=Hello world!" http://localhost:8888/
should dump “Hello world!” to the screen. Press Ctrl+C to abort the server.
Here’s a shout out to our friends at ThoughtWorks. They had a code jam on the Flowsheet Module a few weeks ago and knocked out over half a dozen key tickets in a single day as well as making progress on others. The Flowsheet Module is being installed into OpenMRS at AMPATH in Kenya to allow both data managers and providers convenient access to their patients’ data and to help improve the efficiency and quality of care for the over 300,000 patients within the system… thanks to: Khaarthigha S, Rajiv RA, Pavithra K, Geetha B, Saravana K, Arvind Kumar C, Senthil V S, Prabha P, Shanum, Ponnulingam R, Alexal, Vinoth KR, Nithyan, Gobinath T, Balaji G, Ragavan G, and Chandru. You all rock! And it looks like you had some fun in the process. I especially enjoyed the image of two laptops per lap!
Trying to figure out how we can improve the efficiency of our code reviews. Finding some interesting stuff on the web…
Like this article: Limit the checklist to 7±2 items. Automate the automate-able and let existing code review data drive the list of common mistakes.
Here is a list of articles, white-papers, and documentation around peer code review. Hmmm. Several resources, but not enough time to look at them all.
Some nice pointers here, but nothing game changing.
A good summary article here.
And here (and here) is an interesting take by Torvalds that Paul found.
Plan on talking with developers to brainstorm on it…
At the IMeCA Meeting in Lima, Peru listening to Hamish present about OpenMRS in English with real-time Spanish translation … and Hamish is in Kigali, Rwanda! Now that is cool!

During some noodling about OpenMRS design issues with Paul, we wanted to look at the most recent version of some of our tables. Did I open up MySQL? No sirree! I have 1.6 dev running on an appliance, so I just opened the Groovy module and ran this script:
/* Let's make a convenient function for executing SQL */
def sql = { s -> admin.executeSQL(s, false /* read-only */ ) }
sql("describe obs")
.collect{ """
${it[0]}
${it[1]}
""" }
.join("n") /* separate lines */
Which dumps out this:
obs_id int(11)
person_id int(11)
concept_id int(11)
encounter_id int(11)
order_id int(11)
obs_datetime datetime
location_id int(11)
obs_group_id int(11)
accession_number varchar(255)
value_group_id int(11)
value_boolean tinyint(1)
value_coded int(11)
value_coded_name_id int(11)
value_drug int(11)
value_datetime datetime
value_numeric double
value_modifier varchar(2)
value_text text
date_started datetime
date_stopped datetime
comments varchar(255)
creator int(11)
date_created datetime
voided smallint(6)
voided_by int(11)
date_voided datetime
void_reason varchar(255)
value_complex varchar(255)
uuid char(36)

I took some time this afternoon to brainstorm about active lists w/ Paul…in preparation for an OIP project this summer with Upul.
Fundamentally, we need to add support for allergy lists and problem lists within core OpenMRS. In the end, we’ll have allergy and problem methods within the Patient Service; however, the pattern of list management cries out for a core list management machine.
We could either simply create new data points for these lists, but since we have traditionally and it makes sense to continue storing observations for these. So, we imagined that these lists would, for the most part, be references to existing observations. We’re even more confident in this approach after learning that some HL7 v3 RIM-based projects at Regenstrief are defining allergy and problem lists this way (as references to observations).
Here is the basic model for Lists we would propose:
Start and stop dates could go on the obs (we considered putting these in obs before), but on further reflection HL7 v2 has these in the OBR and it’s for timed urines and other observations that may take some time — tracking how long it took to obtain the observation and not tracking the duration of the concept itself.
Active list entries for patient would be all rows of a certain list type that are not voided and have a null stop date.
Basic properties of an allergy:
Problem list properties:
Getting this done may depend on building the answer_class and answer_set features (ticket 73), so that — for example — the scope of possible allergens could be defined as any concept in the allergens set and any concepts with a class of medication, medication set, etc.
Here’s some ideas of possible service methods…
PatientService
List<Allergy> getAllergies(Patient p);
List<Allergy> getAllergiesAsOfDate(Patient p);
void addAllergy(Patient p, Allergy a);
void updateAllergy(Allergy a);
List<Problem> getProblems(Patient p);
List<Problem> getProblemsAsOfDate(Problem p);
void addProblem(Patient p, Problem p);
void updateProblem(Problem p);
This is a work in progress. I wanted to get it posted for discussion on today’s developers conf call.

This was a great hack-a-thon. We tackled a lot of different issues for the logic service. Unfortunately, a number of these will require additional effort to be completed.
We knocked out some of the simpler tickets:
We made some decisions (like not moving Result methods to a utility class) and we make some serious inroads (including code) on how to solve some of the more difficult issues (persisting tokens #924, persisting token tags #969, caching issues #1107, and changing “today” within criteria #929).
All-in-all. A great job by all involved! A special thanks to Tammy for making it a success!
So far, so good at the OpenMRS! To our surprise, we’re actually getting code written. We’re not sure if it’s because of Tammy’s amazing prep work or because Paul & Hamish couldn’t make it. 🙂
Looking forward to tomorrow…
As with many of our hackathons, we learned a lot about where we stand… in this case, on unit testing. We worked through a lot of the conventions and methodology to create a pathway to improve our unit testing coverage. We’re embracing at least the method-naming aspects of BDD. I am confident that we can increase our success rate:
And while we may have a ways to go to think that we’ve got a decent percentage of our behaviors covered, we’ve at least got Darius covered: