Convert your FHIR JSON -> XML and back here. The CDA Book is sometimes listed for Kindle here and it is also SHIPPING from Amazon! See here for Errata.

Monday, February 12, 2018

Mapping from a FHIR Composition to an XDS DocumentEntry

Rene Spronk asked this one on one of the HL7 lists ... basically the question is what is the mapping from XDSDocumentEntry to Composition.  I give the mapping backwards, how do you produce an XDS DocumentEntry from a composition, rather than forwards, but it can be used in either direction.

XDSDocumentEntry Composition ClinicalDocument
formatCode   (meta.profile) ClinicalDocument.templateId
uniqueId    (meta.versionId)
languageCode   (language) ClinicalDocument.languageCode
    identifier .setId
    status n/a
classCode (mapped from type), typeCode     type .code
    class n/a
patientId, sourcePatientId, sourcePatientInfo     subject .recordTarget
healthcareFacilityType, practiceSettingCode
via reference to encounter/location/physicalType and encounter/location/type
    encounter .componentOf.encompassingEncounter
creationTime      date .effectiveTime
authorInstitution, authorSpecialty, authorPerson, authorRole
via reference to author resource
    author .author.assignedAuthor
title      title .title
confidentialityCode      confidentiality .confidentialityCode
    attester .authenticator/.legalAuthenticator
        mode implied by .authenticator/.legalAuthenticator
        time .authenticator.time
legalAuthenticator          party .authenticator.assignedEnttty
    custodian .custodian.assignedCustodian
    relatesTo .relatedDocument
parentDocumentRelationship          code .relatedDocument.typeCode
parentDocumentId          target[x] .relatedDocument.id
    event .documentationOf.serviceEvent
eventCodeList          code .code
        period .effectiveTime
serviceStartTime             low
serviceStopTime             high

Tuesday, January 30, 2018

Nominations are now being Accepted for the Ad Hoc Harley Award


It's always been true that I am open to others making recommendations for the Ad Hoc Harley award, and in fact, several have been awarded based on the recommendations of others.  In fact, the very first one was created based on a recommendation that I do something for a deserving individual.

I can't keep with everyone who might deserve an Ad-Hoc Harley award these days, but want to continue the tradition with more frequency than I've been able to lately.

So, feel free to e-mail me (click the e-mail me link to the right) your nomination, or if you are at the HL7 WGM this week, catch me at break today or tomorrow (or during Q5 in the appropriate bar).

I'm looking for two things:

  • Someone who hasn't previously been recognized for their service.
  • A unique contribution to Health IT and/or Health IT Standards
Everything else is open, and well kinda Ad Hoc ...

   Keith



Getting Water from a FHIR Operation

Water drop impact on a water-surface - (1)
According to the FHIR Operation specification (STU2 and 3 say the same thing):


If there is only one out parameter, which is a Resource with the parameter name "return" then the parameter format is not used, and the response is simply the resource itself.

So, if you want to return non-FHIR content in your operation, return a Binary resource. The FHIR Server (if properly implemented) would interpret the resource as content to be returned in the usual web way.  

I've verified that the HAPI 3.0 Server does in fact operate correctly when you return a Binary resource for an operation.

So, if you want your FHIR operation to return a CDA document, you can get it to do so.  Heck, if you really wanted to, you could get it to return V3 or even better (sic) a V2 message.  DICOM anyone?

   Keith

Thursday, January 25, 2018

Architecture is about all the nouns

People, organization, technology, and processes. They're nouns.

These are the nouns that makes an architecture succeed.  A good architect can design a beautiful building (a thing).  But a great one will design a building that also works well in its environment.

As a software architect, I've had to learn this the hard way.  It's not just about making the technology (another thing) do what it needs from scratch in a green field.  It's about making it easy to transition from the way it was done to a better way, a more efficient way.

The very first XDS implementations listed documents for patients and gave people a way to search for documents and list them out in various orders on the screen, and view them.  This is the way processes (ideas) at that time often worked worked, because documents were "different" from data. 

Today, we use XDS (and its variants) in all kinds of different ways, where the document is simply payload for other activity and where the views of what was communicated in the document as shown by the user interface is many and varied, and sometimes completely under the covers.  This is architecture that not only fits into its environment, but one which improves it significantly.

XDS worked so well in part because it enabled people to work in familiar ways (using existing processes) at the same time as it enabled existing processes.  It also worked well because it enabled NEW processes to be developed that could be delivered over time.  It overcame objection to change because it eliminated steps, rather than adding new ones or changing existing ones.  It also worked WITH the technology that already existed.

Understanding the people, the organizations and their processes is just as critical to making technology work as understanding the technology.

   Keith

Thursday, January 18, 2018

CQL for Me, or what not to do at an IHE Connectathon

Testing22222
If you think you are going to implement a brand new feature during Connectathon week, then you probably are about as brain dead as our poster child for accidents waiting to happen on my right.  Having said that, my job for this week is to at least get started on implementing a clinical quality language execution engine into my FHIR Server.

So much for brains, right?  One of the biggest challenges I've had is that while there's plenty of open source implementations out there, there's not a whole lot of documentation on how to use them.  And while as a language, CQL is certainly easy enough to read, there's some very basic assumptions about a programming language that are left completely unstated in the standard.

"What does hello world look like in CQL?" is one of those questions that I hadn't found (nor could Google) anyone addressing.

That's a really good question.  The answer isn't to be found in the CQL Specification, but I did eventually figure it out.  It looks like this:

define result: 'Hello world!'

Pretty simple.  The execution of a CQL program basically produces a list of results, where each result has a name and a value.  This makes it really ideal to use a JSON representation for output, by the way.  There's also a type (associated with the value), and a location associated with the definition that you can get out of the engine I'm using (which makes it really handy for debugging).

This was my DUH moment last night.  Now it doesn't have to be yours.  I think I'm about to join that open source community as a doc writer (at least to start off).

   Keith

Tuesday, January 16, 2018

Converting STU2 to STU3 in HAPI on FHIR

AC Converter Adapter USA-EU
So, you've built your HAPI FHIR Server, and if you started when I did, you may have even started with STU2.  But these days IHE profiles are in STU3.  What's a guy to do?  Well, if that guy is an engineer, the first thing you WON'T DO is rewrite your server from scratch.  For PIXm, PDQm, QEDm and likely even MHD (although I haven't checked that profile out yet), the simple answer is to front end your STU2 services with an STU3 facade.

The pattern for such a facade is fairly simple:

public class MySTU3ResourceProvider  {
  private final MySTU2ResourceProvider stu2Provider;
  private VersionConvertor_10_30 converter = 
     new VersionConvertor_10_30(new NullVersionConverterAdvisor30());

  public MySTU3ResourceProvider(MySTU2ResourceProvider stu2Provider) {
     this.stu2Provider = stu2Provider;
  }

  @Search() Bundle doQuery(...) {
     try {
       // call the previous method and get the bundle response
       return coverter.convertResource(stu2Provider.doQuery(...));
     } catch (BaseServerResponseException ex) {
       // We got a "normal" exception from the processing.
       Object o = 
           converter.convertOperationOutcome(
               (OperationOutcome) ex.getOperationOutcome());
             ex.setOperationOutcome((IBaseOperationOutcome) o);
       throw ex;
     } catch (FHIRException fex) {
       throw new InternalErrorException(fex);
     }
   }
   @Read Resource doRead() {
     try {
       // call the previous method and get the bundle response
       return coverter.convertResource(stu2Provider.doRead(...));
     } catch (BaseServerResponseException ex) {
       Object o = 
           converter.convertOperationOutcome(
               (OperationOutcome) ex.getOperationOutcome());
             ex.setOperationOutcome((IBaseOperationOutcome) o);
       throw ex;
     } catch (FHIRException fex) {
       throw new InternalErrorException(fex);
     }
   }
}

One of the things I learned this week was that I needed to add the conversion trick to handle any cases where the original method throws something derived from BaseServerResponseException.

This is great.  Until you discover that for some reason, convertMedicationOrder hasn't been implemented, and you need to turn your previous MedicationOrder into a MedicationRequest.

So, buckle down and write convertMedicationRequest(), or use the code I wrote below.  Here's how I started: It took me about 3 minutes to find that the code had been commented out.  It took me another 15 or so to code it up, and a few more to test and tweak it.  All told, about 30 minutes. To skip to the chase, here's the code (it's not pretty, but it seems to compile and run):

VersionConvertor_10_30 converter = new VersionConvertor_10_30(new NullVersionConverterAdvisor30()) {
    
      public org.hl7.fhir.dstu3.model.Resource convertResource(org.hl7.fhir.instance.model.Resource src) throws FHIRException {
      if (src instanceof org.hl7.fhir.instance.model.MedicationOrder) {
      return convertMedicationOrder((org.hl7.fhir.instance.model.MedicationOrder)src);
      }
      return super.convertResource(src);
      }
      
      public org.hl7.fhir.dstu3.model.MedicationRequest convertMedicationOrder(org.hl7.fhir.instance.model.MedicationOrder src) throws FHIRException {
      if (src == null || src.isEmpty())
        return null;
      org.hl7.fhir.dstu3.model.MedicationRequest tgt = new org.hl7.fhir.dstu3.model.MedicationRequest();
      copyDomainResource(src, tgt);
      for (org.hl7.fhir.instance.model.Identifier t : src.getIdentifier())
        tgt.addIdentifier(convertIdentifier(t));
      tgt.setStatus(MedicationRequestStatus.fromCode(src.getStatus().toCode()));
      tgt.setMedication(convertType(src.getMedication()));
      tgt.setSubject(convertReference(src.getPatient()));
      tgt.setContext(convertReference(src.getEncounter()));
      if (src.hasDateWritten())
        tgt.setAuthoredOn(src.getDateWritten());
      tgt.getRequester().setAgent(convertReference(src.getPrescriber()));
      if (src.hasReasonCodeableConcept())
        tgt.addReasonCode(convertCodeableConcept(src.getReasonCodeableConcept()));
      if (src.hasReasonReference())
        tgt.addReasonReference(convertReference(src.getReasonReference()));
      if (src.hasNote())
        tgt.addNote().setText(src.getNote());
      for (org.hl7.fhir.instance.model.MedicationOrder.MedicationOrderDosageInstructionComponent t : src.getDosageInstruction())
        tgt.addDosageInstruction(convertMedicationOrderDosageInstructionComponent(t));
      if (src.hasDispenseRequest()) {
      MedicationRequestDispenseRequestComponent dr = tgt.getDispenseRequest();
      MedicationOrderDispenseRequestComponent sr = src.getDispenseRequest();
      if (sr.hasValidityPeriod()) 
      dr.setValidityPeriod(convertPeriod(sr.getValidityPeriod()));
      if (sr.hasNumberOfRepeatsAllowed())
      dr.setNumberOfRepeatsAllowed(sr.getNumberOfRepeatsAllowed());
      if (sr.hasQuantity()) 
      dr.setQuantity(convertSimpleQuantity(sr.getQuantity()));
      if (sr.hasExpectedSupplyDuration()) 
      dr.setExpectedSupplyDuration(convertDuration(sr.getExpectedSupplyDuration()));
      }
      if (src.hasSubstitution()) 
      tgt.getSubstitution().setReason(convertCodeableConcept(src.getSubstitution().getReason()));
      tgt.setPriorPrescription(convertReference(src.getPriorPrescription()));
      return tgt;
    }


    };

This week is all about getting things close enough.  Afterwards I get to bring the code back to turn it into production.  But I'm guessing that I'm not the only one wanting to convert STU2 MedicationOrder resources into STU3 MedicationRequest resources.  So, here you go.

   Keith

P.S.  As to why this is commented out, my suspicion is that the code generator that is producing the converter from the FHIR Specification hasn't quite been tuned to deal with Resource name changes yet.

Thursday, January 11, 2018

Twenty things I've learned about the ED as a Chronic Pain Patient's Advocate

Cbh emergencydepartment1
I've been spending a good bit of time as a patient advocate for a soon-to-be family member over the last month (and prior).  There were 7 ED visits, 2 ambulance rides and 3 911 calls due to untreated pain in the month of November.  It took 3 ED visits to get a diagnosis for their acute pain (Endometriosis), and some forward motion toward treatment, and another 4 since then while waiting for follow-up visits (see The Patient is Waiting ... in Constant Pain).  We've been in 2 hospitals, and 9 ED beds, 15% of the beds in the local ED.  I almost want to start carving initials.

Here's the thing, for certain conditions causing acute, level 9 and above pain, it's hard to get a diagnosis, and difficult to get consistent treatment.  Having an advocate has been crucial to making any sort of progress. When a patient is in crippling pain, it's damn near impossible for them to have an intelligent discussion about what is going on. When, as a result of ongoing chronic pain, other issues arise such as anxiety or depression, it's damn near impossible for them to get respect as a patient. Finally, when one of your diagnosis is "questionable" (e.g., Fibromyalgia) in the mind of the doctor (even though as an ED physician they are not the specialist who provided that diagnosis), it's challenging to even get good treatment (see It's not real).

What I learned in November:
  1. Be aware that if you show up on a weekend during or after any holiday, life is going to have some extra suckitude in it.  These are busy times and this is "high season" in the ED.  If you can, call ahead, better yet, have a doctor do it. 
  2. Coming in by ambulance will get the patient into an ED room immediately, but they may be moved later (many EDs reserve specific rooms for ambulance arrivals).  
  3. Treatment for pain CAN start in the ambulance if the ambulance is appropriately staffed and willing.  My local ambulance staff are fantastic (but I also live in a town where everyone knows everyone else), others are probably good but don't know us. Our local guys called the nearby paramedics from one town over to meet them en route, and having relief start in the Ambulance is huge!  
  4. If the patient isn't dying on the table, be prepared to wait with them ... in acute pain, for some time.  Remember that the ED doc's purpose is first to stabilize you, if you are in pain but not obviously dying, and your heart rate and blood pressure aren't super critical, you are probably at a level of stable that means you won't get as much attention as you'd like.
  5. When the patient is in pain, don't be afraid to let them cry or make noise, and to keep the door open. They'll get attention faster.
  6. Wait obviously, ask for attention gently, be aware that the folks you are working with are probably crazy busy, and will work better with you if you can be gentle (if insistent) with them.  
  7. If you have a specific need, ask for it (from the right person).  A pillow, water, a straw can be gotten by just about anyone, but the doctor is the worst person to ask these for.  Turning the monitor alert off requires someone trained to do that (doctor or nurse usually).
  8. If the monitor is beeping but nobody is responding, press the call button. It's faster than asking someone.  If there's a problem with over-alerting on the monitor, get them to address it.  Monitor beeps are purposefully attention getting, which can increase patient agitation -- not a good thing for a patient in pain.
  9. Come in with a clinical, rather than psychiatric reason for visit.
  10. Know the lingo. The more you speak "medical", the better they listen.
  11. Talk about what your expectation for the visit are, and get answers about what the doctor's expectations are.  Ask for times, let them know you are seeking approximate answers so that you know what to expect, rather than precise answers, and that you know they are busy and that stuff happens.
  12. You are rolling the dice on providers to be seen.  If you have a clue about what kind of provider, or know a name of someone in the hospital you have seen before, use it, ask to see them if they are available. When the shift changes, the attending doctor and plan may change too, be prepared for that.
  13. Have the medical proxy paperwork in your hands. A power of attorney is better than a medical proxy form if it allows you to take more charge (Most proxy forms address "patient is unable", but a PoA can simply designate you with certain authority).  Introduce yourself to staff by your relationship, both personal and legal to the patient.  Use the personal first, but place some emphasis on the legal authority if you have it.
  14. Keep track of the patient records, and have them on hand, electronically if at all possible.  We use MyChart extensively because it is available through all of our providers.  If you (or the patient) can state dates for relevant events, their medications including dose, and all of their conditions and allergies, you'll get more regard from the staff.
  15. If you have a choice of hospitals, consider them carefully.  My local regional hospital is beautifully laid out with modern equipment and a new facility, but the care provided by the staff is inconsistent.  On the other hand, our visit to the nearby academic medical center left me quite sad about the state of their physical infrastructure, but the one visit made there was FAST, provided excellent care and pain relief within 90 minutes AND to a degree I hadn't seen in the regional facility.  We were in and out in about 5 hours, which for us was about three times faster than the regional.  From a facilities perspective, I'd rather be in the regional, but from a care perspective, if we have a choice, we'll go back to the academic facility.
  16. The ambulance will take you to the nearest hospital, which is NOT necessarily the one you want to go to.  If hospital choice makes a difference, consider your options carefully.  Acute pain is not always life threatening, but when it is, you want to be in the ambulance.
  17. If the patient has a wheelchair or other mobility device, bring it with you. It helps to get attention needed, and it can make getting there easier.
  18. Keep a bag packed for the visit; we now have a go bag packed.  It includes plenty of water, some form of food for snacks: Oreo cookies are a favorite, but we also include protein bars, pudding in favorite flavors, and Slim Jims. The food is for both you and the patient if they are allowed to eat something. 
  19. Include a phone charger in your go bag.  Bring your own soft fuzzy blanket and whatever else will bring you comfort.  Dress in easy to access clothing, bring an extra pair of underwear.
  20. As an advocate, know a wide variety of ways to deal with pain.  Distraction helps.  Usually by the time you are headed to the ED, you've already tried everything you know.  Here are some things I've discovered.  Deep breathing is essential. Distraction works: Tell good stories, engage in pleasant and fun conversation, have the patient tell you a favorite story. Use a variety of relaxation techniques -- techniques that have already been practiced several times work better usually. Gentle massage is good.  A warm shower can sometimes help reduce pain.  If the patient is locked in pain, is having muscle spasms only makes it worse.  Do what you can to get them to relax.  
#15 was probably the most important thing I learned in November.