Friday, January 31, 2020

Tactics for Playing Beat Sabre and for Project Management

I got an Oculus Quest Virtual Reality Headset for Christmas (everyone in the house gets to use it though), and also purchased Beat Sabre.  If you aren't familiar with the game, it's a like combining a dance-off machine with a light saber.  Instead of dance moves though, you have to cut colored blocks out of the sky to popular tunes.  I do OK with it, and it's a great cardio workout.  I've developed a number of different tactics while playing the game, and as I was enumerating them (while playing the other day), I realized that many of them are also applicable to any software development or standards project.
  • Switch tactics often
    Of all the tactics that I list, this is the only one that doesn't have an opposite on the list.  That's because no single tactic serves to improve your game to the utmost.  You have to switch tactics to deal with what is happening now, or what might be coming up soon, and you have to learn to recognize which tactics work in each situation.
  • Look ahead
    No matter what you are doing, if you aren't prepared for what's coming next, you can get overwhelmed.  Pay attention to what's past your immediate event horizon so that you can be ready for it.  You don't have to continuously monitor this, but you should do it periodically.
  • Stay focused on what is in front of you
    Pay attention to the next thing, and the thing after that.  If you can do this well, you'll keep moving forward.
  • Stay forward
    Get out in front, stay as far ahead in the space as you can, because it will give you more time to react appropriately, and you can often knock things out of your way before you absolutely have to deal with them later.
  • Step back
    When things start coming at you hot and heavy and fast, step back a bit while you deal with them to give yourself more time to respond. While this is the opposite of stay forward, you can never step back if you didn't start in a forward position.
  • Don't think
    You've got this, even if it looks difficult.  You've trained for this, you've played this game more than a few times, let your instincts work. Overthinking can slow you down, and that can result in "Game Over" (see also Go fast).  
  • Think
    OK, so maybe you are stuck somewhere, or a particular thing you are working on, just isn't working for you. Think about what is happening, and where you are failing. Look at the situation carefully (see also Go slow).
  • Go fast
    When you practice, put the pressure on, even if you don't have to. You'll train yourself to deal with the things that are the most important, and eventually learn the way to do things more efficiently and effectively.
  • Go slow
    When you get to a difficult section, slow it down, and pay attention to what you are doing. If you get stuck on something, try to go at it at a slower pace, so you can think about what you are doing.
  • Stick with what works
    If you've got this part, keep doing it the same way, otherwise you are going to mess with your rhythm.
  • Try something different
    If you don't have it, try a different approach. You may need to move in a different direction than you have been for this part. Keep trying, eventually you will work it out.
  • Work hard
    The more you do, and the harder you work at it, the better you'll get.
  • Make it look easy
    When you do something, do it with the least energy possible to succeed well, and make it look easy and simple, even if it's devilishly hard.  I don't mean do a half-assed job, I mean nail it, but make it look and feel like you didn't have to do much to get it perfectly right.  This is perhaps the hardest to learn (you actually have to work hard to get there), but also one of the most beneficial.  Because if you can do something really hard, but make it feel like it was easy, then in fact ... it is easy ... at least for you.

Thursday, January 30, 2020

Fine-grained access control with SMART on FHIR

One of the few things that people are aware of is that SMART on FHIR enables, but does not explicitly specify how to support access controls on patient data.  Folks who are familiar with the OAuth 2.0 specifications will readily understand that the principle mechanism by which access is requested and controlled is through scopes.  A scope is a named thing that describes something that the application desires access to.  Within the OAuth 2.0 flow, the client app requests one or more scopes, and the authorization flow grants access to a possibly limited subset of those scopes (or the full set of requested scopes).  You can see this in action in certain app workflows for Apps working with Twitter or Facebook where you are requested for a set of capabilities (e.g., e-mail address, contact lists, the ability to post on your behalf), and you can reject one or more of these, but the application still works (possibly with limited functionality).

SMART on FHIR specifies a flow in which the user authorizes the app (see Authorized App in the diagram below).



The expectation is that the application will first authenticate the user, and then request them to authorize certain accesses to resources they control.  Normally these requests are related to specific scopes.  In SMART on FHIR, there are a defined set of standard* SMART on FHIR scopes.  Common scopes might related to the proposed rule might be things like Patient/Observation.read (the ability to read any Observation), or Patient/Condition.read.  Other scopes it defines include things like Patient/Appointment.write (the ability to create or update an appointment).

But implementers of API services don't have have to limit themselves to just the SMART scopes.  They can offer the patient the ability to further restrict what data the application can access.  For example, consider the case where I might want to use an application that does some useful stuff with my blood pressure, height, weight, cholesterol, A1C and blood pressure data, but it also does stuff with other lab results that I don't want it to access?  Could I limit the access?  Well, I personally cannot, but the implementer of the API service CAN offer me the ability to restrict Observations to a set of common results (or even let me create a list of LOINC codes).

How would this work?  The EHR Authorization server would have to be smart (as well as SMART), and offer me opportunities to support not just restricting scopes, but to also gather additional information from me about other restrictions I want to place on the data.  It could then store that information in memory and associates it with the code it returns to the client application.  When the client application exchanges that code for an access token, these additional restrictions can be recorded in claims on the access token it returns.

Then, when the application makes a request, the API server can check to see that the request is consistent with the claims in the access token.  And when the APP makes a request that the patient wanted blocked, it can do one of several things:  The first is to act as if the data that the application requested simply isn't present, and otherwise act normally.  This is a good case because it won't intrude on application integration because there will always be cases where data it asks for isn't present, and so it will deal with it.  The second thing the API server could do is report an error, in which case the application itself might break (good ones won't break), or have reduced functionality because some query it thought it combine fails.

The beauty of this is that it allows the API server implementer to go beyond just SMART on FHIR scopes, still work according to the standard, and yet provide the patient with more control over what information is accessed on a PER application basis.  This App can have access to my weight, that one (which makes stupid remarks when it goes up or down) cannot.  Furthermore, there's nothing that says this access need remain.  If I'm a really clever implementer, I can simply associate the restrictions with a claim in the token that points somewhere to the patient preferences for what the app using that token can access, and the patient  can later change those preferences.

So there you have it, how to have fine-grained access control in SMART on FHIR.  And while everyone might think fine-grained is better, recent research seems to indicate otherwise (at least for espresso).


     Keith




* OK, Draft Standard, as SMART on FHIR is not yet quite a full-fledged standard, but you get the point.

Querying with Dates in FHIR, dateTime to date

In a previous post I talked about querying from date to dateTime in FHIR, and indexing strategies to deal with that.  I also mentioned that I'd go the other way for it.

So let's look at the problem in reverse.

Consider a dateTime for an event: January 20th, 2020, at 6:00am EST.   Consider also that some systems may not store the timestamp for this event (so it would simply be 2020-01-20 with no timestamp or time zone).

Let's say that something important happened at that time, and I want to find all events that had a dateTime after that event.

What if there are five Observation resources, with effectiveDateTime values of 

  1. 2020-01-20T05:59:00-05:00
  2. 2020-01-20T06:01:00-05:00
  3. 2020-01-19
  4. 2020-01-20
  5. 2020-01-21

And I query for le2020-01-20T06:00-05:00, which ones should I get?

Well, pretty clearly I should get #1, and I shouldn't get #2.
But what about 3-5?

Here's what FHIR has to say about the le operator in this case:


lethe value for the parameter in the resource is less or equal to the provided valuethe range below the search value intersects (i.e. overlaps) with the range of the target value or the range of the search value fully contains the range of the target value

Ok, so the search value is 2020-01-20T06:00-05:00.  What is the range below it?  It covers all time before 2020-01-20T06:00-05:00.  Assuming a maximum clock precision in milliseconds, that includes all times up to and including 2020-01-20T05:59:59.999-05:00.

What is the range of the search value?  It's 2020-01-20T06:00:00.000-05:00 to 2020-01-20T06:00:59.999-05:00 inclusive  (using the same assumption on precision) .

We can start off by recognizing that the range of the search value can safely be ignored.  It doesn't fully contain any day of any year.  So we only need to worry about the range below the search value.

If we assume the user and the date associated with the event are in the same time zone (a probably, but not great assumption), then arguably #3 is before, #4 is unknown, and #5 is after this timestamp.  But if we cannot make that assumption, then what?  Time zones range from UTC-12 to UTC+14 (a 26 hour range possibly covering 3 different dates).

As it happens, in UTC-12, this event occurred on 2020-01-19, in Boston on 2020-01-20, and in UTC+14, on 2020-01-21.  So, without any assumptions on time zone, we have no clue whether these events occurred before, after on on any of the given dates in 3 - 5.  We could say for certain when the date is 2020-01-18 or 2020-01-22 that it's before or after, because given the CURRENT set of time zones (which could change based on local legislation), that these dates MUST be before or after, but cannot say otherwise without making an assumption about time zone.

This isn't a problem when both items to compare are dates (in the evil twin scenario of my previous post, my evil twin was born a day before me EVEN though we were born at the exact same time).

There is really only one way around this problem, and that is to assign a time zone to use in these comparisons.  There are two time zones that we already know about in this situation when making the comparison.  The first is the time zone given in the user's query parameter, and the second is the time zone of the server.  And then there's the argument that every time comparison should be made with respect to UTC.

I find the latter argument about UTC to be based on the needs of developers, rather than users, and thus inadequate.  Similarly, I find the server argument to be based on technical rather than user-oriented requirements.  So, my preferred solution would be to use the time zone specified in the users query parameter.

That means that 2020-01-19 would be "less than" 2020-01-20T06:00-05:00, and 2020-01-21 would be "greater than" it, and 2020-01-20 would fit into the categories of "could be less than" and "could be greater than".  And from the user's perspective, this would be as close as we can get in responding.

It's NOT a satisfying answer, because of the "could be" category.  This results from the definition of the eq operator.

We generally think that if A = B, that B = A.  However, FHIR defines equals in this way:
eqthe value for the parameter in the resource is equal to the provided valuethe range of the search value fully contains the range of the target value
If A is the same range as B, then A = B and B = A, but if the range of A is larger than the the range of B, this is not true.

clock, doctor who, and gif image


Wednesday, January 22, 2020

A Brief Reflection on 3 decades of interoperability at The IHE USA Connectathon conference


My bit from the IHE Connectathon conference today in summary:

What do we want? We want our data.  When do we want it?  Yesterday.  We’re now entering our third decade into attention on interoperability in healthcare. Hopefully, it’s not same stuff, different decade (SSDD). In the naughts, we focused on what we aught to do ... build interoperable systems.  In the teens, we focused on interoperability in the betweens, between providers, and to some extent between providers and patients.  In the twenties, my hope is that we focus on what our want is...

What have we learned over the last two decades?  We need standards, multiple standards, to support interoperability [standards are like potato chips, you cannot just have one].  Standards are essential.  Good standards make it easy for smart people to create interoperable solutions.  What we actually need are GREAT standards, because great standards make it possible for ANYONE to interoperate.

Tuesday, January 21, 2020

Querying with Dates in FHIR, date to dateTime

Oh, the vagaries of time .. and date .. and time zones.  What does it mean to query by date when there is a timestamp?

FHIR has this to say:
Where possible, the system should correct for time zones when performing queries. Dates do not have time zones, and time zones should not be considered. Where both search parameters and resource element date times do not have time zones, the servers local time zone should be assumed.
I find this unhelpful without a lot more thought.

What happens (or should happen) when you compare a date (in your query) to a dateTime in a resource?

Consider a dateTime for birth: January 20th, 1965, at 3:30am EST.  Assume this is stored in the Patient resource, and except for the hour (which I changed to explain the problem), this is my birth date.

Now, query for date of birth using 1965-01-20.  This is a whole day.  If I perform the query using Unix Epoch times, this day started in EDT at -156106800, and ended at -156020401.  But in UTC it starts on -156124800 and ends at -156038401.  And in AKST (Alaska), it's different again.

The same time in Alaska is 4 hours earlier than it is in Boston, and which is another 5 hours earlier than the time London.  So, what how do I compare the date?

Let's say I had an evil twin brother from another mother born in Alaska the same time as I was born in Philadelphia.  Well, his birth date is a day before mine.  He's evil because he got to get his driver's permit a day before I got mine ... and birthday presents a day earlier, and a bunch of other stuff I didn't get.

But we were born at the exact same time.  So if I query a FHIR server that has both our patient records for fhir/Patient?birthdate=eq1965-01-20, should his record also appear?

My answer to this question is NO, b/c according to his record, it says: "birthDate": "1965-01-19T23:30:00-09:00", and mine says "1965-01-20T03:30:00-05:00".

So, how does one turn the query parameter 1965-01-20 into an appropriate value to probe for these records, and how does one turn our dates of birth into appropriate values in an index in order to query these correctly?  I had originally thought to use the querant's time zone as part of the answer, but then you get different answers to the question based on where the querant is at the time (and since I travel quite a bit, I know how confusing that would be), or based on where the system the querant is using at the time (which may not be anywhere near where they are located ... consider someone like Graham Grieve updating the HL7 site stored on AWS servers in the US from Australia).

The answer is fairly straightforward, but not obvious.  A date and a dateTime are similar, but not identical data types.  The full state of a dateTime is captured in the date, time, precision, and time zone information. While the same information can capture the full state of a date, all you need is date, and precision because time and timezone don't matter.  And since two different dateTimes can resolve to different dates, they are different species of data (same genus, but one has some extra genes as it were).

That means that to index a DateTime or Instant, you need to capture the date alone in one index field, and the timestamp (with or without timezone) in another.  Only in this way can you correctly do comparisons between date (in the query) and dateTime (in the resource).  And if you do it this way, you will find me, and not my evil twin.

When the queried value is to the day or less, compare against the date field.  When more precise, compare against the dateTime field.

Later this week, I'll take up the other direction.  Is 1965-01-20 greater, less than, or equal to 1965-01-20T03:30:00-05:00?  What about 1965-01-19T23:30:00-09:00?
————
Thanks to Ryan Moehrke (yes, he’s John’s son) for the correction on my time stamps.