Archive for the ‘openmrs’ Category

jsonpretty: handy when playing with web services

Monday, October 17th, 2011

I just ran into a handy little utility: jsonpretty.

$sudo gem install json jsonpretty

While

$curl -i http://localhost:8081/openmrs-standalone/ws/rest/v1/catalog

gives you something like this:

{"catalog":[{"name":"Cohort","operations":[{"name":"GET http://localhost:8081/openmrs/ws/rest/v1/cohort?q","description":"Fetch all non-retired that match this parameter"},{"name":"GET http://localhost:8081/openmrs/ws/rest/v1/cohort/{uuid}","description":"Fetch by unique uuid"},{"name":"GET http://localhost:8081/openmrs/ws/rest/v1/cohort","description":"Fetch all non-retired"},{"name":"POST http://localhost:8081/openmrs/ws/rest/v1/cohort","description":"Create with properties in request"},{"name":"POST http://localhost:8081/openmrs/ws/rest/v1/cohort/{uuid}","description":"Edit with given uuid, only modifying properties in request"},{"name":"DELETE http://localhost:8081/openmrs/ws/rest/v1/cohort/{uuid}?!purge","description":"Delete this object from the database"},{"name":"DELETE http://localhost:8081/openmrs/ws/rest/v1/cohort/{uuid}?purge","description":"Delete this object from the database"}],"url":"http://localhost:8081/openmrs/ws/rest/v1/cohort","representations":[{"name":"ref","properties":["uuid","display","links"]},{"name":"default","properties":["uuid","name","description","voided","memberIds","links"]},{"name":"full","properties":["uuid","name","description","memberIds","voided","auditInfo","links"]}]},{"name":"CohortMember","operations":[{"name":"GET http://localhost:8081/openmrs/ws/rest/v1/cohort/{parentUuid}/members/{uuid}","description":"Fetch by unique uuid"},{"name":"GET http://localhost:8081/openmrs/ws/rest/v1/cohort/{parentUuid}/members","description":"Fetch all non-retired"},{"name":"POST http://localhost:8081/openmrs/ws/rest/v1/cohort/{parentUuid}/members","description":"Create with properties in request"},{"name":"POST http://localhost:8081/openmrs/ws/rest/v1/cohort/{parentUuid}/members/{uuid}","description":"Edit with given uuid, only modifying properties in request"},{"name":"DELETE http://localhost:8081/openmrs/ws/rest/v1/cohort/{parentUuid}/members/{uuid}?!purge","description":"Delete this object from the database"},{"name":"DELETE http://localhost:8081/openmrs/ws/rest/v1/cohort/{parentUuid}/members/{uuid}?purge","description":"Delete this object from the database"}],...

adding jsonpretty:

$curl -i http://localhost:8081/openmrs-standalone/ws/rest/v1/catalog | jsonpretty

gets you something much prettier:

{
  "catalog": [
    {
      "name": "Cohort",
      "operations": [
        {
          "name": "GET http://localhost:8081/openmrs/ws/rest/v1/cohort?q",
          "description": "Fetch all non-retired that match this parameter"
        },
        {
          "name": "GET http://localhost:8081/openmrs/ws/rest/v1/cohort/{uuid}",
          "description": "Fetch by unique uuid"
        },
        {
          "name": "GET http://localhost:8081/openmrs/ws/rest/v1/cohort",
          "description": "Fetch all non-retired"
        },
        {
          "name": "POST http://localhost:8081/openmrs/ws/rest/v1/cohort",
          "description": "Create with properties in request"
        },
        {
          "name": "POST http://localhost:8081/openmrs/ws/rest/v1/cohort/{uuid}",
          "description": "Edit with given uuid, only modifying properties in request"
        },
        {
          "name": "DELETE http://localhost:8081/openmrs/ws/rest/v1/cohort/{uuid}?!purge",
          "description": "Delete this object from the database"
        },
        {
          "name": "DELETE http://localhost:8081/openmrs/ws/rest/v1/cohort/{uuid}?purge",
          "description": "Delete this object from the database"
        }
      ],
      "url": "http://localhost:8081/openmrs/ws/rest/v1/cohort",
      "representations": [
        {
          "name": "ref",
          "properties": [
            "uuid",
            "display",
            "links"
          ]
        },
        {
          "name": "default",
          "properties": [
            "uuid",
            "name",
            "description",
            "voided",
            "memberIds",
            "links"
          ]
        },
        {
          "name": "full",
          "properties": [
            "uuid",
            "name",
            "description",
            "memberIds",
            "voided",
            "auditInfo",
            "links"
          ]
        }
      ]
    },
    ...

Nice! Thank you jsonpretty! :-)

Displaying OpenMRS observations using Groovy

Monday, July 11th, 2011

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

Sometimes you need a little web server

Saturday, July 2nd, 2011

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.

Flowsheet Code Jam in Chennai

Saturday, January 15th, 2011

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!

Improving OpenMRS Code Review

Thursday, April 15th, 2010

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.

  • Review fewer than 200-400 lines of code at a time
  • Aim for less than 300-500 LOC/hour
  • Not more than 60-90 minutes at a time
  • Authors annotate prior to review
  • Establish quantifiable goals and capture metrics to improve the process (I’m noticing a theme here)
  • Checklists are good
  • Verify that defects are fixed
  • Managers must foster a good code review culture in which finding defects is viewed positively.
  • Beware the “Big Brother” effect.
  • The Ego Effect: Do at least some code review, even if you don’t have time to review it all.
  • Lightweight-style code reviews are efficient, practical, and effective at finding bugs.

And here (and here) is an interesting take by Torvalds that Paul found.

  • There will be bugs.  Don’t aim for zero.
  • “Same goes for ‘we should all just spend time looking at each others patches and trying to find bugs in them’. That’s not a solution, that’s a drug-induced dream you’re living in.”

Plan on talking with developers to brainstorm on it…

The World is Flat

Monday, October 26th, 2009

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!

Groovy thought

Friday, August 21st, 2009

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{ """
  <b>${it[0]}</b>
  <small><em><font color=gray>${it[1]}</font></em></small>
  """ }
.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)

Active Lists in OpenMRS

Thursday, April 30th, 2009

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:

  • patient id — the subject for the list, do we want to open this up to person id?
  • list type — allergy vs. problem, other types of lists could follow
  • obs id — reference to the observation that created this list entry
  • sort weight — for managing sequence of lists
  • date started
  • date stopped
  • ?stop reason (resolved vs. error correction)
  • voided — when the associated obs is voided, the list entry gets voided as well
  • void reason
  • creator
  • date created
  • changed by
  • date changed

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:

  • allergy type — drug, food, misc, etc. (see HL7 table 0127)
  • allergen
  • reaction
  • severity — severe, moderate, mild, unknown (see HL7 table 0128)

Problem list properties:

  • diagnosis/symptom concept
  • certainty

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.

Logic-a-thon Wrap-up

Friday, November 7th, 2008

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:

  • Rules tokens need to be case insensitive (#1104)
  • Encounter datasource expansion (#1105)
  • Creating an Operand interface (#1106)

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!

OpenMRS Logic-a-thon

Thursday, November 6th, 2008

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…