Tuesday, March 28, 2023

Translating CDA to FHIR: SNOMED CT Codes


One of the challenges in translating from CDA (or C-CDA) to FHIR is related to variations in how CDA and FHIR store complex SNOMED-CT codes using qualifiers.  As you might expect, these differences are especially significant when the concept being encoded uses the SNOMED CT Expressions

In CDA, these codes are stored using the ConceptDescriptor (CD) data type, and unpacks the concept using the Qualifier component of the concept description when the code is a SNOMED CT expression.  This is arguably a flaw in HL7 Version 3 standards, as the qualifier concept is variably expressed in SNOMED CT and other terminology systems differently through their own expression languages, and shouldn't be syntactically unpacked in the CD data type.  It's overengineering the terminology component to meet the needs of a data model, and overgeneralizing (in the favor of SNOMED) how this may be handled in other terminologies.

HL7 FHIR uses the CodableConcept data type, and doesn't unpack the expression (fixing this flaw).  If we take the SNOMED CT example provided at that link and shown below:

Example

And look at it in CDA, we would see:

<code xsi:type="CD" code="284196006" codeSystem="2.16.840.1.113883.6.96" 
      displayName="burn of skin">
    <qualifier>
        <name code="363698007" codeSystem="2.16.840.1.113883.6.96" displayName="finding site" />
        <value code="770850006" codeSystem="2.16.840.1.113883.6.96" 
               displayName="Skin structure of left index finger"/>
    </qualifier>
</code>

But in FHIR:

<code>
  <coding>
    <code value='284196006|burn of skin|:363698007|finding site|=770850006|Skin structure of left index finger|' 
          system='http://snomed.info/sct'/>
  </coding>
</code>
                    <!-- OR Better yet (IMNSHO*) -->
<code>
  <coding>
    <code value='284196006:363698007=770850006' system='http://snomed.info/sct'
          display='|burn of skin|:|finding site|=|Skin structure of left index finger|'/>
  </coding>
</code>
NOTE, the latter example is just a simplified version of the former without any display name values.

This use of SNOMED CT Expressions is advanced, and doesn't show up often in the real world, but when it does, it is a real challenge for implementers.

To convert from a SNOMED CT code with qualifiers in CDA to a SNOMED CT Code in FHIR, apply the following algorithm:

  1. Generate new nested <fhir:code>, <fhir:coding> and <fhir:code> elements.
  2. If there is a cda:code/@code attribute, generate a value attribute in the final fhir:code element as follows:
    1. Set fhir:code/@value attribute in FHIR to the value of cda:code/@code
    2. (Optional and legal, but not recommended): If there is a cda:code/@displayName attribute in the CDA, append "|" + @displayName + "|" to fhir:code/@value.  NOTE: This captures displayNames, which you should only trust from your own terminology service, and it also appends a string to fhir:code/@value which will complicate FHIR Search operations in most FHIR implementations.
    3. For each cda:qualifier element from SNOMED CT in the cda:code element. 
      1. Append ":" + cda:qualifer/cda:name/@code to fhir:code/@value
      2. Again, optional, legal and not recommended: append "|" + @displayName + "|" to fhir:code/@value
      3. Append "=" + cda:qualifer/cda:value/@code to fhir:code/@value
  3. If there isn't a code/@code attribute, generate an appropriate uncoded value in FHIR (possibly using null flavors depending on the IG).  NOTE: If there's NO @code, but there are cda:qualifier elements, this is bogus, and should be reported as a validation error on input, at least for SNOMED CT.  You can't qualify an uncoded value.
Some comments:
  1. SNOMED CT expressions allow display name values to be appended to a code between vertical bars | (we call these pipes in HL7 V2).  I don't recommend putting the display names into the code even though it's legal and semantically correct.  Pragmatically, it's going to cause your FHIR implementation to have problems in search.
    1. This is legal:
      <code value='284196006|burn of skin|:363698007|finding site|=770850006|Skin structure of left index finger|'  system='http://snomed.info/sct'/>
    2. But this is MUCH better:
      <code value='284196006:363698007=770850006'
            display='|burn of skin|:|finding site|=|Skin structure of left index finger|'
            system='http://snomed.info/sct'/>

  2. Don't trust display name values from external systems.  Use the display name values from your own, validated terminology service.

  3. Consider how incorporating qualifiers into codes will impact your search.  In general, qualifiers reflect refinement in SNOMED CT, which implies subclasses of the core code.  You might consider coding it twice, once without the qualifiers, and a second time with the qualifiers:
    <code>
      <coding>
    
        <code value='284196006' system='http://snomed.info/sct'
              display='burn of skin'/>
        <code value='284196006:363698007=770850006' system='http://snomed.info/sct'
    display='|burn of skin|:|finding site|=|Skin structure of left index finger|'/> </coding> </code>
    I like this better because a search for fhir/Condition?code=
    http://snomed.info/sct|284196006 is going to find what the end user expects, all Condition resources representing a burn of skin.

  4. If you go with #3 above, you may be concerned (and rightly so) about negation.  There are a couple of recommendations you might consider:
    1. Insert a human into the mix and ensure that qualifiers related to negation or other situations with explicit context are correctly interpreted in workflows that involve importing data from CDA into the patient chart.
    2. Read through and understand SNOMED CT documentation on this topic.
    3. Few systems today use SNOMED CT expressions.  Fewer yet use them with negation.  That doesn't mean you can safely ignore it. Make it an exceptional process in your conversions, AND don't try to automate processing it, just detect it and call for (human) assistance in interpretation.  The number of times that happens is likely be small enough to avoid user complaint, yet remain safe for patient care.  And note, my expert opinion does not excuse you from making your own risk assessments and code accordingly.

Keith

P.S. To make it even uglier, technically, the FHIR representation would also be legal in CDA, if about as rare as a snowball in Central Africa.

<code xsi:type="CD" value='284196006:363698007=770850006'
      displayName='|burn of skin|:|finding site|=|Skin structure of left index finger|'
      codeSystem="2.16.840.1.113883.6.96">
* IMNSHO = In My Not So Humble Opinion

Friday, March 17, 2023

One more hack for TUS for 0 Content

Image of A Zero (Source)

I've been playing around with the TUS Protocol lately as I've mentioned in previous posts.  I implemented both a TUS Server and a TUS Client using open-source libraries.  I've also been testing with an external system that uses TUSD underneath.

One of the challenges I ran into with the client implementation had to do with not having a Content-Length field on requests such as GET, OPTIONS or HEAD with no body, that shouldn't actually require the Content-Length field.

What I wound up doing in this case was to:

  1. Set Content-Length explicitly to 0 in the HttpURLConnection class:
    con.setAttribute("Content-Length", 0);
  2. And when that didn't work, made sure that I actually retrieved the output stream from the connection even though nothing was written to it:
    if (con.getDoOutput()) {
      con.getOutputStream();
    }
    This is what seems to have the impact on the Content-Length value sent by the underlying HttpsURLConnectionImpl class, although I can't really see what it is doing because as I said previously, good luck finding the source for that.  It's in one of the sun packages that doesn't ship with the Java JRE source code.  I suppose I could decompile those classes, but for the most part, it's not worth the effort.
NOTE: Both of these activities are done in my overridden: prepareConnection() method in a subclassed TusClient class.

Once again, I'm using URLConnection because that's what's in use by the TUS Java Client, not because it's my first choice.  If I had a choice, I'd probably be using the Apache HTTP Client.

TUS is an interesting protocol because it allows for resumable uploads.  I think it could be better if it were able to handle uploads of multiple chunks over separate network connections, so that you could take better advantage of the resumability AND available network bandwidth and CPU.  The protocol would have to be adjusted to deal with that.

     Keith

P.S.  The folks behind TUS would prefer if we wrote it tus, but somehow my fingers keep capitalizing it.  Since it is a TLA, I'll probably keep writing it in all caps.


Wednesday, March 15, 2023

Patching PATCH for JDK 11


Last post I talked about HttpsURLConnection, and how one could intercept calls to the underlying socket to report on the HTTP protocol messages.  In this post, I'll explain how you can work around another shortcoming in Java where HttpURLConnection (and thus HttpsURLConnection which derives from it) fail to support the HTTP PATCH method defined in RFC5789.

The critical problem comes from the fact that this doesn't work:

  URL url = new URL("https://localhost");
  HttpURLConnection con = (HttpURLConnection)url.openConnection();
  con.setRequestMethod("PATCH");

It will throw a ProtocolException.  Here's the JDK Source code for the static variables and method in question:

  /* valid HTTP methods */
  private static final String[] methods = {
    "GET", "POST", "HEAD", "OPTIONS", "PUT", "DELETE", "TRACE"
  };

  /**
    * Set the method for the URL request, one of:
    * <UL>
    * <LI>GET
    * <LI>POST
    * <LI>HEAD
    * <LI>OPTIONS
    * <LI>PUT
    * <LI>DELETE
    * <LI>TRACE
    * </UL> are legal, subject to protocol restrictions. The default
    * method is GET.
    * @param method the HTTP method
    * @exception ProtocolException if the method cannot be reset or if
    * the requested method isn't valid for HTTP.
    * @exception SecurityException if a security manager is set and the
    * method is "TRACE", but the "allowHttpTrace"
    * NetPermission is not granted.
    * @see #getRequestMethod()
  */

  public void setRequestMethod(String method) throws ProtocolException {
    if (connected) {
      throw new ProtocolException("Can't reset method: already connected");
    }
    // This restriction will prevent people from using this class to
    // experiment w/ new HTTP methods using java. But it should
    // be placed for security - the request String could be
    // arbitrarily long.
    for (int i = 0; i < methods.length; i++) {
      if (methods[i].equals(method)) {
        if (method.equals("TRACE")) {
          SecurityManager s = System.getSecurityManager();
          if (s != null) {
            s.checkPermission(new NetPermission("allowHttpTrace"));
          }
        }
        this.method = method;
        return;
      }
    }
    throw new ProtocolException("Invalid HTTP method: " + method);
}

What's a developer to do?  Well, you could use an alternate implementation to connect instead of HttpsURLConnection.  Some alternatives include the Apache HTTP Client, and Spring's RestTemplate.

Sometimes using an alternative isn't a choice (for example, when an underlying library uses it and you can't change the implementation).  In my particular case, I was experimenting with the tus-java-client to talk to a TUS Server to experiment with sending data to a DEX endpoint.  

The Java tus client uses HttpURLConnection, and some servers may not like the X-HTTP-Method-Override header that has been included to work around this problem with the expected use of PATCH.

A bit of Googling turns up a number of different solutions, one of which I implemented in a static method called during application startup.

private static void patchThePatch() {
  try {
    String[] methods = { "PATCH" };
    Field methodsField = HttpURLConnection.class.getDeclaredField("methods");
    Field modifiersField = Field.class.getDeclaredField("modifiers");
    modifiersField.setAccessible(true);
    modifiersField.setInt(methodsField, methodsField.getModifiers() & ~Modifier.FINAL);
    methodsField.setAccessible(true);
    String[] oldMethods = (String[]) methodsField.get(null);
    Set<String> methodsSet = new LinkedHashSet<>(Arrays.asList(oldMethods));
    methodsSet.addAll(Arrays.asList(methods));
    String[] newMethods = methodsSet.toArray(new String[0]);
    methodsField.set(null/*static field*/, newMethods);
    URL url = new URL("https://localhost");
    HttpURLConnection con = (HttpURLConnection)url.openConnection();
    con.setRequestMethod("PATCH");
    // Turn off caching
    con.setDefaultUseCaches(false);
    LOGGER.info("PATCH updated in HttpURLConnection");
  } catch (Exception ex) {
    LOGGER.info("Could not adjust HttpURLConnection to accept PATCH: {}", ex.getMessage(), ex);
  }
}

This code was inspired by:

https://stackoverflow.com/questions/25163131/httpurlconnection-invalid-http-method-patch/40606633#40606633

If you are using a JDK later than JDK-11, you might want to look into:

https://stackoverflow.com/questions/56039341/get-declared-fields-of-java-lang-reflect-fields-in-jdk12



Tuesday, March 14, 2023

Intercepting Secure Socket Calls

Transport Layer Security is something I've been working with since the very first publication of Cross Enterprise Document Sharing (XDS).  For that very first Connectathon, we had to demonstrate that we could secure the XDS communications channels, and I struggled with that part.  So much so that I wrote what might be considered my first blog post, the ATNA FAQ.

Since then, I've become somewhat expert in implementing, diagnosing, and resolving issues with TLS implementations in Java.  One of my most recent challenges involved having to diagnose problems with a protocol where the usual methods in the ATNA FAQ don't work, because the underlying implementation is on Bouncy Castle FIPS, and that doesn't respond to the usual Java -Djavax.net.debug options I first wrote about 20 years ago.  There's a good reason for that, but it makes debugging protocol implementations a bit of a beast when you cannot see what is happening.  Sure, you can wireshark it, but that doesn't always work when you aren't in control (as I wasn't) the responding service.

The solution is rather ugly, and it involves interceptors.  What I wound up doing was getting a hold of the client socket factory, wrapping it with my own implementation, intercepting calls to the Socket creation code, wrapping them in interceptors which catch calls get underlying Input and Output streams, and wrapping each of those in an Interceptor which filters the input or output stream to duplicate the output onto my console.

This is brutually tedious code, because it involves overriding EVERY implemented public method of these classes, calling the original intercepted object's method and returning the result (or slightly modifying it in a very few cases).  Fortunately, modern IDEs will write MOST of this code for you and search and replace will finish it (except for the special cases).  I won't reproduce every line here, but I'll explain the technique.

First, understand that I'm dealing with client calls to a server, and these are built on HttpsURLConnection.  For those of you who've been programming in Java, you may already understand that this is not an interface, but rather an abstract class, and the finicky details are found underneath it in sun.net.www.protocol.http.HttpsURLConnectionImpl (BTW: Good luck finding the source for that).

When you have your URL Connection, you will need to wrap the SSL Socket Factory it is using with a new socket factory that intercepts calls to the existing one.

    HttpsURLConnection conx = (HttpsURLConnection)url.openConnection();
    if (debug) {
      conx.setSSLSocketFactory(new MySSLSocketFactory(conx.getSSLSocketFactory()));
    }

Next you are going to create this new SSLSocketFactory:

public class MySSLSocketFactory {
  private final SSLSocketFactory base;
  MySSLSocketFactory(SSLSocketFactory base) {
    this.base = base;
  }
   ...
}

Inside this class you will override every method implemented in the class (Eclipse's Source | Override Implement Methods menu makes this easy).

Each override follows one of two patterns:

  @Override
  public void methodX(Type0 arg0, Type1 arg1) {
    base.methodX(arg0, arg1);
  }

Or for methods returning a value:

  @Override
  public TypeR methodX(Type0 arg0, Type1 arg1) {
    return base.methodX(arg0, arg1);
  }

For the createSocket methods, you will vary this slightly:

  @Override
  public Socket createSocket(Type0 arg0, Type1 arg1) {
    return new MySocketWrapper(base.methodX(arg0, arg1));
  }

Next, you will create MySocketWrapper following similar instructions.  This time there are 60-70 methods you must override (I did say this was tedious, did I not).

public class MySSLSocketWrapper extends SSLSocket {
  private final SSLSocket base;
  public MySSLSocketWrapper(SSLSocket base) {
    this.base = base;
  }

  @Override
  public InputStream getInputStream() throws IOException {
    return new MyInterceptingInputStream(base.getInputStream());
  }

  @Override
  public OutputStream getOutputStream() throws IOException {
    return new MyInterceptingOutputStream(base.getOutputStream());
  }

  // ... Add other Overrides here just calling the base method of the same name
}

Finally, create the MyInterceptingInputStream and MyInterceptingOutputStream classes just as before, overriding the read or write methods to copy the input or output to another place of your choosing.  I just write what was read or written to System.err, but in the case of the InputStream, I also write a couple of escape sequences before and after to change the color of the output in my console so I can distinguish which stream I'm seeing.  Here's an example of what I mean.

  private static final byte[] BLUE = { '\033', '[', '3', '4', 'm' };
  private static final byte[] NORMAL = { '\033', '[', '0', 'm' };
  @Override
  public int read(byte[] b, int off, int len) throws IOException {
    int val = in.read(b, off, len);
    if (val > 0) {
      try {
        out.write(BLUE);
        out.write(b, off, val);
        out.write(NORMAL);
      } catch (IOException ex) {
        // Swallow it.
      }
    }
    return val;
}

Now, when I set debug to true, I'll get the HTTP communications appearing in my console.

Later this week I'll show you another hack I implemented to make HttpURLConnection support PATCH in JDK-11.

Wednesday, December 7, 2022

New CMS Proposed Rule Released on PatientAccess and a Feature Request for the @FedRegister Web Site

 CMS Announced the updates to the Patient Access Rule yesterday.  So another tweet tweet through and summary are coming by EOW (my time zone).  You can find the preview here.  My comments on the prior rule are here, and you might read that first before starting on this one.

I'm not going to get through this one right away because I have other high priority tasks in front of that (releasing is a very important feature).

I've been a Federal Register user for quite some time.  I really appreciate all the work that has gone into it, especially for reviewing regulation.  My biggest use of it is for providing feedback on regulations, and that leads me to a feature request or three that I'd love to see.

  1. Provide users with the capability to make notes on individual paragraphs and sentences in a publication requesting feedback (e.g., a rule or proposed rule).
  2. Enable users to coordinate with other users so that multiple users can make notes in the same note repository.
  3. Enable users to access and download that feedback in a machine readable, preferably standardized format.
  4. Enable users to upload/merge feedback from other exports into a note repository.
  5. Enable users to share a link to that feedback with others for read or write access.
  6. Enable users to automatically submit a repository with additional attachments as feedback on a proposed rule, RFI or other publication.
  7. Annotations should take the format: Link to original text (a span of text in the rule), proposed wording, comments, and author.  See the last paragraph below for why this is the format.
What this would do is enable users to comment on regulations, provide a standardized format that would enable federal agencies to automate much of the work of coordinating comments from different users, and enable feedback to be tied directly back to required sections.  This could vastly improve response times for feedback.

I try to provide feedback on regulations the same way as I do for standards: Here's the original text, here's how it should read, and here's why.  When writing the "how it should read", I try to use the same voice as the original proposed rule so that the text can just be copy-pasted into the final rule. It makes everyone's day easier, and that's how I've gotten verbatim text I and my colleagues have written to appear in at least three different rules (yes, you too can write text that appears in a final rule).

     Keith


Tuesday, November 29, 2022

42CFR2_NPRM: Summary of a Tweet Through

The awaited 42 CFR Part 2 update NPRM has been published in preview form in the Federal Register, and is expected to be published later this week.  I finished my tweet through last night, and you can find all the text below.

A thread on the NPRM revising rules on Confidentiality of Substance Use Disorder (SUD) Patient Records #42cfr2_nprm

#42cfr2_nprm This rule is required to implement requirements in section 3221 of the CARES act (see https://congress.gov/116/plaws/publ136/PLAW-116publ136.pdf#page=95)


That law changed the consent requirements, simplifying them, but rule changes were necessary to conform.  Thus we now have the long-awaited #42cfr2_nprm

There's a 60 day comment period starting soon (official publication in FR), then a final rule, effective 60 days after its publication, and requiring compliance 22 months later.  So, all of this would be REQUIRED of entities within about 2.5 years. #42cfr2_nprm

#42cfr2_nprm Is two years enough for compliance (plus the ~ 6 months additional warning this NPRM gives you)?  @HHSGov specifically asks this question on page 8.

#42cfr2_nprm Major proposals include: aligning more with HIPAA in various places, including noting that this rules does not prevent anything required by HIPAA, referencing HIPAA content, or revising existing HIPAA content for use in this rule.

#42cfr2_nprm Major proposals (cont.): Apply breach notification requirements for records covered by part 2, 

align w/ HIPAA accounting of disclosures, patient right to request restrictions, HIPAA authorizations with 42CFR2 Consents, ...

#42cfr2_nprm next one is huge: replace ... requiring consent for uses & disclosures for payment & certain health care operations with permission to use & disclose records for TPO with a single consent ... until ... patient revokes ... in writing

HIPAA NPP and Part 2 SUD Notice to patients are being aligned under #42cfr2_nprm

And NOTE, this rule also mods some regs under HIPAA too in minor ways to attain better uniformity across both.

I'm guessing that post-implementation, this page https://hhs.gov/hipaa/filing-a-complaint/index.html will undergo revisions, so that there's one place to complain the secretary regardless of whether it's a HIPAA or Part2 violation.

hhs.gov

45 CFR 164.520 (HIPAA Privacy stuff) will undergo some changes to enable alignment with #42cfr2_nprm for NPP, Accounting of Disclosures, but will wait on some HITECH stuff to complete before finalizing privacy rule provisions to make implementation easier on affected entities.

"The existing Part 2 regulations do not permit the disclosure of Part 2 records for public health purposes", but #42cfr2_nprm would "permit Part 2 programs to disclose de-identified health information to public health authorities." NOTE: De-identified = HIPAA 45 CFR 164.514(b)

There's a big chunk of stuff in the #42cfr2_nprm about court orders for disclosure for various purposes.  This is not my bailiwick, both literally and figuratively.

And now we have completed the bulk of our reading, and get to @HHSGov's request for specific feedback at page 115 for the #42cfr2_nprm 

This is followed at page 126 by a summary of what I just summarized (mine is shorter).

I'm going to skip the cost benefit analysis in #42cfr2_prm; b/c that takes a while for me to process.  It isn't just reading and gut feel and an understanding of history.  I make models when these are important to me, and this one is.

But to provide a short summary of the #42cfr2_nprm analysis: The ROI is about 6 years.

The proposed regulatory text for #42cfr2_nprm appears starting at page 202.  We aren't going over that today (although I may later).

This is expected to be published in the FR on 12/2

I like #42cfr2_nprm.  It will probably get some minor revisions, and possibly some delayed activation dates, but frankly, I think many providers and much of the #HealthIT industry would support its content. From a patient perspective, I find it good, but requiring a deeper reads.

And yes, deeper reads is plural. I read a rule three times.  This was just my first cursory read-through/tweet through.



Friday, November 4, 2022

Porch 2.0

This started off as a tweet stream, but is also a great blog post.  It's about how every project is like an onion.  You think you understand it, but as you peel it, you find that there are layers that need to be trimmed out.  Most onions are completely salvageable, you just need to plan for the fact that some parts will need to be thrown out.

This spring I took the screen frames off my screened in porch to replace the screens. The frames turned out to be rotted. I'll build new ones eventually.  In the meantime, it still looks better this way.  

After pulling out the frames, I noted that one 4x4 had been badly weathered and needed replacement. I bought $100 of lumber to fix it and the posts mounted on it. Well, after removing it, I realized the rot extended to the deck beneath it.

I pulled off the facia and the side frame also needed replacement. Both it and the end frame were also badly joined to 2ft extenders.

I searched for and got estimates from deck/porch contractors to demolish the old porch, expand and add a deck and handicap access ramp (combining another project into this one).  That started a few weeks ago.

Two days ago, while digging the footings, they drilled through my well line.

Today, the well company came out to fix the well line.  While trenching to reroute the line they found the power to my barn and severed it with their backhoe.

I now have to hire an electrician to run a new (installed to code) electrical line out to the barn now.

In case you were wondering, this is not a disaster.  It's just another day building upon the experiences I routinely have in my day job as a software developer.

The title of this story should be something about how Porch Maintenance Release 1.2.0.1 subsumed and became Porch 2.0, with Water 1.5.1 Patch, with a subsequent Barn Electrical Patch 1.3.2, which will probably ship with the MVP of Barn/Office 2.0.

For what it's worth, my farmhouse is a collection of 1895 and 1970 construction with few extra (uninspected) homeowner modifications built in, and an add-on side deck I had professionally installed when I moved in (complete now with hot tub).

-------------
Postscript:

Because of all the on/off hammer with the well water supply, I now have a leak in the cold water to the upstairs bathroom, and a toilet valve that's constantly running downstairs, so two more tiny patches. Also, I have a small driveway patch to make because they had to extend the hole for the water feed into the macadam across the front step. I'm sure there will be more to come.