FHIR Chat · ConceptReference · methodology

Stream: methodology

Topic: ConceptReference


view this post on Zulip Grahame Grieve (Oct 29 2019 at 22:05):

Creating this thread to continue discussion on this subject: https://chat.fhir.org/#narrow/stream/179166-implementers/topic/Proposal.20for.20a.20new.20type.20ConceptReference

view this post on Zulip Grahame Grieve (Oct 29 2019 at 22:05):

as originally proposed here: https://wiki.hl7.org/Proposal_for_ConceptReference

view this post on Zulip Grahame Grieve (Nov 05 2019 at 22:54):

ping here

view this post on Zulip Grahame Grieve (Nov 05 2019 at 23:00):

Decision on MnM call: Draft a new data type

CodeableReference : Reference {
coding : Coding [0..1] // a reference to a code system or ontology that describes the essential properties of what is/might be referenced
}

This can have a binding. Update the specification accordingly, and let people look at it. Use it in MedicationRequest so we can see what it looks like

view this post on Zulip Grahame Grieve (Nov 05 2019 at 23:00):

If we like it, ask Pharmacy to endorse the change

view this post on Zulip Rob Hausam (Nov 12 2019 at 05:30):

I still think that the name CodeableReference isn't very clear (and is potentially misleading) as to what this actually is. Clunky (which it certainly is) or not, ReferenceOrConcept is much clearer in regard to the nature and intent.

view this post on Zulip Jose Costa Teixeira (Nov 12 2019 at 08:33):

The way I see it, this is a data structure that we use to refer to a something (a concept).
That reference, sometimes, happens to be coded.

view this post on Zulip Jose Costa Teixeira (Nov 12 2019 at 08:39):

I understand your reasoning, @Rob Hausam but I think ReferenceOrConcept sounds slightly misleading to me.
This is a designation of a concept. So if I were to do it clunky for sake of accuracy, I would go for "ReferenceOrCodedConcept" or "ReferencedConceptOrCodedConcept" (this is just to see if we mean the same). This is why I think CodeableReference would be good.

view this post on Zulip Lloyd McKenzie (Nov 12 2019 at 14:02):

The challenge is that is's not necessarily an 'or'. You could have a reference to both the drug code and a pointer to the Medication instance, for example.

view this post on Zulip Rob Hausam (Nov 12 2019 at 15:27):

Agree, @Lloyd McKenzie. And @Jose Costa Teixeira. CodeableReference does sound better and probably isn't really as unclear or potentially misleading as I originally thought. I'm coming around to it.

view this post on Zulip Grahame Grieve (Nov 29 2019 at 19:17):

Defining this data type... I do not find anywhere that we say that the types specified in the content models are final - that is, just because we define CodeableReference as a specialization of Reference, you cannot use it anywhere Reference is used

view this post on Zulip Grahame Grieve (Nov 29 2019 at 19:18):

and, in fact, strictly from that point of view, it's not a specialization of Reference. So I'm going to define it as a sibling, though we already have the specialization issue around Quantity

view this post on Zulip Grahame Grieve (Nov 29 2019 at 19:44):

I can't define it as a specialization of Reference - there's an invariant in must be to violate.

view this post on Zulip Grahame Grieve (Nov 29 2019 at 19:45):

I don't recall, from the discussion, is whether we intended to allow both coding and reference

view this post on Zulip Lloyd McKenzie (Nov 29 2019 at 19:48):

I can see situations where it might be helpful to provide both. You might have one profile that requires one and another that requires the other.

view this post on Zulip Grahame Grieve (Nov 29 2019 at 19:49):

did we discuss that?

view this post on Zulip Lloyd McKenzie (Nov 29 2019 at 19:51):

I think I'd asserted that it could be useful to have both, but I don't recall much discussion.

view this post on Zulip Grahame Grieve (Nov 29 2019 at 19:52):

I've run into a deeper problem - this type cannot be indexed and searched. The search syntax is ambiguous. I really think that the type will have to be

CodeableReference: coded: CodeableConcept 0.1 reference: Reference 0..1

view this post on Zulip Grahame Grieve (Nov 29 2019 at 19:54):

but I'll see how I go

view this post on Zulip Thomas Beale (Dec 01 2019 at 17:21):

I've run into a deeper problem - this type cannot be indexed and searched. The search syntax is ambiguous. I really think that the type will have to be

CodeableReference: coded: CodeableConcept 0.1 reference: Reference 0..1

Incidentally, that's what I proposed on this blog post: https://wolandscat.net/2019/05/08/a-fhir-experience-the-formalism/

class TypedReference
    type: CodeableConcept [0..1];
    reference: Reference <Any>[0..1];

    Invariants:
        type /= null or reference /= null
end

view this post on Zulip Grahame Grieve (Dec 01 2019 at 18:38):

well, I think that's the right thing to define.

view this post on Zulip Thomas Beale (Dec 01 2019 at 20:21):

If you use that universally, another bunch of confusing elements will evaporate, and the software implementations can be a bit smarter.

view this post on Zulip Grahame Grieve (Dec 01 2019 at 20:23):

I'm glad you think so

view this post on Zulip Thomas Beale (Dec 01 2019 at 20:37):

Unfortunately, that's only a small fix. The 'choice' types are a much bigger problem...

view this post on Zulip Grahame Grieve (Dec 01 2019 at 20:38):

so you keep saying, but also you keep misunderstanding

view this post on Zulip Thomas Beale (Dec 01 2019 at 21:09):

Anytime you want to post the formal justification of that feature so I can understand... :)

view this post on Zulip Lloyd McKenzie (Dec 01 2019 at 21:27):

The formal justification is "we need multiple types - and they don't necessarily have an inheritance structure". E.g. deceasedBoolean vs. deceasedDate. No common inheritance, but we don't need it to. And it's not terribly clear how that creates problems for implementers.

view this post on Zulip Thomas Beale (Dec 01 2019 at 21:38):

Yep, everyone has that need. And everyone uses inheritance. And then constraints where more is needed. After checking that the modelling is correct (often it is not). The basic problem, as has already been described over a decade ago is that the choice pattern a) subverts typing and b) doesn't map to normal programming languages (so yes it is am implementation problem as well). There is a reason no-one and no formalism (other than XML schema) uses this construct. But we have been over this at length, and I guess I am not going to convince anyone there. It's a pity because it is going to cause (is causing) real trouble.

view this post on Zulip Lloyd McKenzie (Dec 01 2019 at 22:02):

"everyone uses inheritance" is not true. And we have a thousands of FHIR implementations and haven't had complaints about the current approach.
(And those implementations certainly are mapped to normal programming languages.) You say that it's causing "real problems"? Can you be specific as to how? Implementation in Java, C#, Swift and several other languages don't seem to have a problem.

Sure, we could say that the type for "deceased" is "anyType" and put an invariant on it that says that it must be a boolean or date. A choice is intrinsically a constraint. If you prefer to think of it as an abstract type with a constraint, you're free to do so. But just listing "here's the allowed types" is much more straightforward than throwing the list of allowed types in amongst all of the other constraints we have.

view this post on Zulip Grahame Grieve (Dec 01 2019 at 22:13):

my view is that if you're looking for an OO model, then you do have a challenge. The reference implementations have reverse engineered and implied reference model, and that's what Tom is looking for. We agree to make some parts of it more explicit. But I don't see how we can practically make more of it for the choice attributes other than what we have already done... they have type Type. I've searched for common inheritance patterns but there aren't any

view this post on Zulip Thomas Beale (Dec 01 2019 at 23:08):

It's not to do with what I am looking for. It's just how the rest of the world works. The choice construct is a constraint mixed into a typed formalism, but with no underlying type to constrain (other than an assumed 'Any' type, which is useless). I'm not sure why you don't want to take account of what the rest of the industry does.

view this post on Zulip Thomas Beale (Dec 01 2019 at 23:10):

When you put a list of types as the type of something, it means you don't know what the type is, or what minimal characteristics are required there - so it means that no-one can know what things might or might not be allowed there. This is very basic.

view this post on Zulip Lloyd McKenzie (Dec 02 2019 at 00:49):

There are no "minimal characteristics" - you have a date or a boolean. And if you reference a Device or a Patient, you're not interested in minimum 'data' characteristics. When you make a choice of "should this resource be allowed as a target", the decision isn't at all based on what elements are present in those resources - it's based on what meaning the resources represent.

view this post on Zulip Lloyd McKenzie (Dec 02 2019 at 00:51):

We've had choice formalisms in HL7 for 30+ years - they were part of v2 (segment choices) and were a key part of v3. There were lots of things implementers complained about with v3 (for good reason), but choices was never one of them. Behind the scenes they were represented as an abstract class with specializations, but the choices were almost never driven by what elements were present

view this post on Zulip Thomas Beale (Dec 02 2019 at 14:53):

Right - the date / boolean thing. That is indeed a true constraint need; we see that all the time in health, as you do. So I'm not arguing that you don't need a way of writing 'Date|Boolean' - somewhere. But it is not true that you don't have a conceptual supertype in that slot - you do, it is called something like DataValue. So if you think about all the leaf domain data items across the resources, often you know it can only be Coded or Quantity or Date etc. Those cases are easy. But then you hit these cases like your example, and other common ones - {Quantity | Interval<Quantity> | String} and so on. So for those, the most specific type you can put is DataValue. One non-breaking solution for dealing with this problem is here: https://wolandscat.net/2019/09/12/fhir-fixes-the-observation-value-problem/

view this post on Zulip Thomas Beale (Dec 02 2019 at 14:58):

There are more systemic ways to solve it better in the type system, but that solution won't break anything you have now. With that typing, you can then remove the type choices to Profiles. So here's a key point: the actual choices you want to allow will not be universal across all uses of the slot in question. Only in some places will you want {Boolean | Date}. IN other places it will be known to be Boolean, and in others, Date. Somewhere else it will be later discovered to be String. Now, if you want to be super-smart, you can go one better and create a normative profile on the Resource element that limits the types that could ever be possibly used to e.g. {Boolean | Date | String}, and force all downstream Profiles to come from that one. Some software might then trust those normative profiles and use their assumptions. In any case, all software can assume that nothing other than descendants of DataValue can appear in that spot, and can process accordingly. Today, it can only assume 'Type', which is no use at all.

view this post on Zulip Thomas Beale (Dec 02 2019 at 15:00):

However ... the main cases where getting rid of 'choice' are more the Admin resources, also places where various kinds of Thing can occur (Substance etc), also various kinds of Event, types for the BasedOn field and so on.

view this post on Zulip Thomas Beale (Dec 02 2019 at 15:04):

I get that HL7 has had some kind of choice forever. So has XSD. Proponents of the latter probably have not seen the problem either, but I'd guess it is because they have not realised what things you can't do when you have that kind of approach to typing. No other typed formalism does this, and as I've said many times, there are many good reasons, all with a formal basis.

Hopefully you accept that I'm being critical of the approach on a formal basis, and with the idea in mind of avoiding both model development and maintenance problems (you have them right now) and downstream consequences. None of my comments should be taken to be about anyone's work etc.

view this post on Zulip Lloyd McKenzie (Dec 02 2019 at 15:45):

We don't want to move the type choices to profiles - the type choice of date|}boolean for deceased[x] is something that has to be enforced by the base standard - it's not something that can vary from implementation to implementation. Creating both a resource and a normative profile on the resource doesn't make a whole lot of sense? What's the value in having the split? What are the things we can't do by having a choice? I don't understand how what you're proposing is going to make either development or maintenance effort.

Fully understand you're not being critical of any individual - I'm just genuinely not understanding what the benefit of the change is. At the moment, I see more complexity without any additional benefit. So one of us (or both of us) are missing something :)

view this post on Zulip Thomas Beale (Dec 02 2019 at 16:15):

Well at the moment, the formal type of the deceased element you are talking about is 'Type'. That's not much help to software. But actually I had missed the fact that you were talking about deceased. For that element, I would argue {date | boolean} is wrong modelling anyway, because they convey different semantics. This is one of those annoying patterns that occurs when the data might a) contain a DOD only, or b) a 'deceased' flag only or maybe even c) both (terrible data design in the source system, but very common. So sometimes you could get a deceased Boolean as data; other times you can only get the Date, and the flag has to be inferred. This is a bit like the concept type / reference problem.

view this post on Zulip Thomas Beale (Dec 02 2019 at 16:24):

The kind of solution you want is a small type that encapsulated the semantics in the following way:

class EventDate {
    date: Date [0..1];                                //  date event occurred
    occurredFlag: Boolean [0..1];      // flag indicating even occurred
    occurred (): Boolean {
        if (occurredFlag)
            return True;
        else
            return date != null;
    }

    invariant
        date != null or occurredFlag
}

You can be a bit more correct by using a tri-state for occurredFlag, but the logical result is the same. The version of that in FHIR would just be the data part of it plus the invariant.

If you had this, it would replace a few pairs of data points across the Resources by a single element of this type. Obviously rename as you prefer but you get the idea. The type could potentially be considered a General-purpose data type in FHIR. Like the concept reference type, if you decide to do that, this just converts an annoyance to a semantic type that properly models the situation.

view this post on Zulip Lloyd McKenzie (Dec 02 2019 at 16:34):

We're defining the interface, not the implementation. And we don't want any system to ever send both deceasedDate and deceasedBoolean because a) it would be redundant; and b) it raises the possibility of inconsistency. We don't care what the class looks like - we care what the instance over the wire looks like and the instance needs to guarantee that you'll only ever have one or the other, not both.

view this post on Zulip Lloyd McKenzie (Dec 02 2019 at 16:34):

And of course, it's possible to have neither.

view this post on Zulip Thomas Beale (Dec 02 2019 at 16:36):

By the way, the analysis of the problem of mixing constraints with a typed system is here: https://wolandscat.net/on-additive-and-subtractive-formalisms/
There are /were a lot more papers on this last time I researched it some years ago, from IBM, the Relax NG authors etc. I have yet to put those refs in.

view this post on Zulip Thomas Beale (Dec 02 2019 at 16:37):

Right, so the definition of the kind of instance you want is:

class EventDate {
    date: Date [0..1];                                //  date event occurred
    occurredFlag: Tristate [0..1];       // flag indicating event occurred

    invariant
        date != null xor occurredFlag
}

view this post on Zulip Lloyd McKenzie (Dec 02 2019 at 16:38):

I don't see how that ensures that I can't have both date and occurredFlag in an instance?

view this post on Zulip Thomas Beale (Dec 02 2019 at 16:39):

Sorry hit return too fast. Refresh.

view this post on Zulip Thomas Beale (Dec 02 2019 at 16:40):

Where I put Tristate, you can just have a Boolean, as long as you consider that the field can be null or set, and then if so, you can simplify to this:

class EventDate {
    date: Date [0..1];                                //  date event occurred
    occurredFlag: Boolean [0..1];      // flag indicating event occurred

    invariant
        date != null xor occurredFlag != null
}

view this post on Zulip Thomas Beale (Dec 02 2019 at 16:42):

(I normally work in programming language terms, where primitive types are not nullable things, but always inline values, at least in many languages. Since you are working off the serial representation, anything can be null, even an int or bool field).

view this post on Zulip Thomas Beale (Dec 02 2019 at 16:42):

Also I meant to put an 'xor' in that invariant.

view this post on Zulip Thomas Beale (Dec 02 2019 at 16:44):

This is a data model of a concept you can think of as 'Event occurrence'. That would be a better name for the class in fact.

view this post on Zulip Lloyd McKenzie (Dec 02 2019 at 16:55):

Ok - so you've created an anonymous class that essentially duplicates what the existing choice structure does. To me, that seems more complex, and it's not terribly clear what it buys. I read through the document you referenced above. It warns that combining additive and subtractive approaches will create problems, but I can't see any explicit examples of what sort of problems. What is the benefit of separating out the constraints from the additive hierarchy? The implementation must adhere to both in practice - it can't ignore one or the other. Behind the interface, it doesn't matter how they code/store the data, all that matters is what get's shared over the wire. If they want to separate constraints from additive model in their internal implementation, nothing stops them.

view this post on Zulip Thomas Beale (Dec 02 2019 at 17:10):

Well the typed model is about data possibilities, in all circumstances. That what you can build reliable software on. Such models function on the basis of meaningful types. Every property in every class has a type a la UML. Now when you state the type of a property to be one of an ad hoc list of types, you are subverting the normal typing. Now, you might argue that the real type of that property is just Any or similar. If so, then that's really what it is. But that's not helpful.

For example, I assume you would agree at least in the abstract, that numerous Resource elements could notionally have the type Party, i.e. some supertype whose descendants are all, Roles, Actors, Persons, Practitioners and so on. Let's pretend for a moment that we agreed on that. Then you would type those elements as Party. Now, in a typed system, the meaning of assigning a type to a property (or method, or argument etc) is: the entity attached to this reference at runtime must conform to this type, and this type expresses the minimal set of characteristics required by the semantics of this model, for that element to have. That is what allows software to be written with polymorphism etc. If we know Party has at least identity and photo (let's just say), then the software can do things with that, without having to know the dynamic type of the attached thing. But if the type is Any, it can't do anything interesting.

At a modelling level, the whole conversation is different. Instead of trying to work out which other types could be added to the current disjoint ad hoc type list in a choice field, it would instead be oriented to determining the minimum semantics required by that field - 'must at least be a person' or so. Once this conversation is had, it is clear how to use that part of the model.

In summary, the software assumes what the model typing says. If you want to then apply constraints (= projections) for specific situations, then it should be done in another layer - one that allows different instantiations / usages of the model to have different type projections. Now, occasionally there are annoying cases like the example above. These are not however different - they are just (we believe) more or less universally applicable. If we really got our analysis right, then in such a case, some conjunction of normally disjoint types becomes a type in its own right, with the semantics understood from the analysis, and it can join the type system. Hence, a special type, addressing a universal special case (= a general case ;)

view this post on Zulip Grahame Grieve (Dec 02 2019 at 17:41):

so reading this:

view this post on Zulip Grahame Grieve (Dec 02 2019 at 17:44):

you can then remove the type choices to Profiles.

create a normative profile on the Resource element that limits the types that could ever be possibly used to e.g. {Boolean | Date | String}, and force all downstream Profiles to come from that one

Yes. that's exactly what we did. and then we put the base Profile in the type definition itself, so it was in the same place.

You have made 3 fundamental claims:

  • moving the choice into a specialised type instead of inlining it would be better. But why?
  • defining a DataValue would be better than just using Type - but it has no additional properties. So why better?
  • splitting the constraints into a separate model that people can't ignore would be better than having them inline in the one model. Again, why?

I think that what you really don't like is the inlining....

view this post on Zulip Lloyd McKenzie (Dec 02 2019 at 17:48):

The typed model does define the possibilities. The possibilities are boolean and date. Address, Telecom, string, etc. may be specializations of 'DataValue', but they are not possibilities in the model.

I'd prefer to ignore the participants discussion for right now and focus on boolean/date. (Though I think the same argument applies - we don't care about any commonality across attributes for the participants any more than we care about commonality of attributes for boolean|date)

view this post on Zulip Grahame Grieve (Dec 02 2019 at 17:49):

we don't care about any commonality across attributes for the participants

I just want to put on record that I don't agree with that

view this post on Zulip Lloyd McKenzie (Dec 02 2019 at 18:21):

Let me rephrase. The driver for whether we include something in the choice isn't driven by commonality of elements. When we say "yes, a device could author that", it's not driven by what data elements we've asserted that a device might have (or what constraints are in place or might be in place in the future). It's driven only by the fact that the scope of Device includes real-world entities that could be an author. Whether the device gets represented by a name, an identifier, a URL and/or a description (and how those attributes compare to what might be on Patient or Organization or Practitioner) doesn't come into play in the discussion.

view this post on Zulip Grahame Grieve (Dec 02 2019 at 18:52):

but it must be something more than random, right? There's some underlying principles in play?

I'm still waiting for @Thomas Beale to come up with a BFO sourced framework on this...

view this post on Zulip Lloyd McKenzie (Dec 02 2019 at 18:56):

Certainly not random, but as discussed previously, it's based on capability of the business entity, not what data elements we use to describe the business entity.

view this post on Zulip Grahame Grieve (Dec 02 2019 at 19:32):

there's really no link between those things?

view this post on Zulip Lloyd McKenzie (Dec 02 2019 at 23:06):

There might be, but it's not a given. The attributes of interest (as reflected in existing systems) and the capacities of interest have a loose correlation at best. I might choose, at the edge, to represent a device by type + URL and provider by name + optional license number. The decision when a new resource comes along about whether to add it to an existing choice structure is not "do the attributes look the same?" but "does it have the right capacity?". The attributes of Person are very similar to those of Patient, Practitioner and RelatedPerson. But we don't include it in any of the same choice structures because we've made a very firm decision that Person doesn't have any capacities other than linking instances. On the other hand, we do include Device and HealthCareService in several of those choices, despite the minimal overlap of attributes - because they share the relevant capacities.

view this post on Zulip Grahame Grieve (Dec 02 2019 at 23:34):

I do think we should ask, though, what the relevant attributes, and should they have them, or not? I note that in practice we have Role.class / Role.code either present or fixed for the resources, right?

view this post on Zulip Lloyd McKenzie (Dec 02 2019 at 23:47):

From a RIM mapping perspective, yes. So one of the capacities is certainly whether something is a Role or an Act. Another is the category of Act.mood. RIM doesn't have a useful hierarchy for things like "can take responsibility" or "can receive communications" unfortunately.

view this post on Zulip Grahame Grieve (Dec 03 2019 at 00:48):

BFO... :wink:

view this post on Zulip Thomas Beale (Dec 03 2019 at 09:14):

Firstly BFO itself won't help that much here - it is an upper ontology, and distinguishes only among categories of real-world things, primarily continuants (things that can be said to exist over time, including dependent continuants, e.g. qualities, roles, functions that depend on an independent continuant 'bearer' for existence) and occurrents - parts of processual entities. For our purposes, it mainly gives an understanding of how a 'role' should be understood. But for most other Qs I need to dig into Information Artefact Ontolog (IAO) and Ontology for Biomedical Investigations (OBI). These are more fluid and larger. I'll aim to do this this week.

view this post on Zulip Thomas Beale (Dec 03 2019 at 09:19):

@Grahame Grieve

You have made 3 fundamental claims:

  • moving the choice into a specialised type instead of inlining it would be better. But why?
  • defining a DataValue would be better than just using Type - but it has no additional properties. So why better?
  • splitting the constraints into a separate model that people can't ignore would be better than having them inline in the one model. Again, why?
    I think that what you really don't like is the inlining....
  1. and 3. are the same. So the real question is: what is your type declaration of (sticking with deceased) the data element?

Is it:
a) {Date | Boolean} i.e. the disjoint set of 2 types or
b) something else that is hidden behind these two, like Any, or Type?
Now a) is not a 'type', it's a conjunction of types. This does not create a type, and is not a type in a typed system. When you put something like this as the type, you still don't know what the type is. That's why zero programming languages, UML or anything else (apart from XSD and maybe some other serialisation languages) do this.

view this post on Zulip Grahame Grieve (Dec 03 2019 at 09:29):

I don't know how many times I will have to say this: it's a Type, per the UML, with a constraint (in OCL in the UML diagram) that it's one of a few types

view this post on Zulip Thomas Beale (Dec 03 2019 at 09:30):

@Lloyd McKenzie

There might be, but it's not a given. The attributes of interest (as reflected in existing systems) and the capacities of interest have a loose correlation at best. I might choose, at the edge, to represent a device by type + URL and provider by name + optional license number. The decision when a new resource comes along about whether to add it to an existing choice structure is not "do the attributes look the same?" but "does it have the right capacity?". The attributes of Person are very similar to those of Patient, Practitioner and RelatedPerson. But we don't include it in any of the same choice structures because we've made a very firm decision that Person doesn't have any capacities other than linking instances. On the other hand, we do include Device and HealthCareService in several of those choices, despite the minimal overlap of attributes - because they share the relevant capacities.

This is the core of the issue. What Lloyd is saying here that any named entity type in the model, such as Device, isn't defined to be what the model says it is (i.e. attributes, invariants, anything else formally stated in the model - it could be pure interface if you want), but is instead the meaning of that word in the real world. Principle #0 of all modelling is this: the meaning of a type T, for any computational use of the model, and also human uses, is just what the model says it is. Indeed, this is why we write models.

If we were to take the line that types names like Device, Patient etc are merely proxies for things going by those names (according to whom?) in the real world, we are dead from the outset. Then they become like a UN declaration, having its own meaning in every country it is taken back to. Modelling cannot function like that.

The way it does function is: you state the type of some attribute (property, argument, function, etc) and that is the contract in terms of typing of that feature. So if you put author:Party, it means author must at least be a Party (however we defined Party), and may be any formal descendant as well. Now, at some point someone comes a long and starts talking about how it really should only be someone who has accountability (let's just say), and you work through that, and decide, really it is author: Role, since Party also has Actor descendants that have no accountability. And so on.

view this post on Zulip Thomas Beale (Dec 03 2019 at 09:31):

If a community can't trust the meaning of the entities in the model to be what the model says they are... then the enterprise is over.

view this post on Zulip Thomas Beale (Dec 03 2019 at 09:34):

@Grahame Grieve

I don't know how many times I will have to say this: it's a Type, per the UML, with a constraint (in OCL in the UML diagram) that it's one of a few types

Ok, so all instances of Reference(A | B | C) and choice[x] should be understood as inlined constraints hiding the real type, which is always Type (which is something pretty close to Any)?

view this post on Zulip Grahame Grieve (Dec 03 2019 at 09:35):

That's true for choice[x].

Reference is something else - it's always Reference. The variance is in what it points to, which is not a direct property; it's a constraint on the type returned when you retrieve whatever the link points to

view this post on Zulip Thomas Beale (Dec 03 2019 at 09:36):

See the problem here? You are trying to state that in such locations, we would like the concrete types to include A, B, C etc, without having done the modelling to determine what abstract definition makes A, B, C valid, but not other types. So the formal type in most places is just Type.

view this post on Zulip Grahame Grieve (Dec 03 2019 at 09:38):

'done the modelling' means... what?

view this post on Zulip Thomas Beale (Dec 03 2019 at 09:39):

Ok, so let's leave Reference() for now. There are two problems here:

  1. you are saying that type names mean whatever people think they mean in the real world (guaranteed to be very variable)
  2. you haven't provided computationally useful types in all the choice[x] locations, only Type.

view this post on Zulip Thomas Beale (Dec 03 2019 at 09:41):

'done the modelling' means that there is a missing discussion(s) to establish the commonality for the disjoint set of types, which can be used to define an abstract type that you formally need, and will act as the criterion for things that can go in that slot.

Actually all of this applies to Reference() as well.

view this post on Zulip Grahame Grieve (Dec 03 2019 at 09:42):

I certainly don't agree to #1. I tried to follow your argument there but failed. I think you're making a false strawman.

With regard to #2... I don't think you've proposed anything different, or done anything different in openEHR

view this post on Zulip Thomas Beale (Dec 03 2019 at 09:47):

No 1. is what Lloyd just said.

view this post on Zulip Thomas Beale (Dec 03 2019 at 09:47):

No 2. is what you just said.

view this post on Zulip Thomas Beale (Dec 03 2019 at 09:48):

w.r.t. No 2, we have abstract types all over the place. That's why openEHR software can do useful things.

view this post on Zulip Grahame Grieve (Dec 03 2019 at 09:48):

I don't agree with how you characterised what Lloyd said. So let's leave #1. Lloyd can take that up

view this post on Zulip Thomas Beale (Dec 03 2019 at 09:49):

Well, either you agree that the meaning of some type T is what the model says or not; above I read that you don't, it's a variable thing, and can keep changing. So which is it?

view this post on Zulip Grahame Grieve (Dec 03 2019 at 09:49):

I'm open to argument about what additional abstract types we should define

view this post on Zulip Thomas Beale (Dec 03 2019 at 09:50):

Ok so, very simple question: I see choice[x] or Reference(A|B|C) somewhere, how can I know what the formal type behind those actually is?

view this post on Zulip Thomas Beale (Dec 03 2019 at 09:51):

i.e. the type that would tell me what other concrete types could possibly be allowed to be added to the conjunction type constraint, or even if those already on the list were valid?

view this post on Zulip Grahame Grieve (Dec 03 2019 at 09:53):

do you want to talk about Reference or choice[x]? because the answers are different. I'd rather talk about choice[x] for now

view this post on Zulip Thomas Beale (Dec 03 2019 at 10:13):

I don't think they should be. But let's stick with choice[x] for now.

view this post on Zulip Thomas Beale (Dec 03 2019 at 10:15):

Choice is mainly a data types question, as is obvious from this page - http://hl7.org/fhir/R4/choice-elements.json

view this post on Zulip Thomas Beale (Dec 03 2019 at 10:16):

So a single missing type - DataValue or similar, that is the common parent of all the various types mentioned there would give you the formal type in most of those locations.

view this post on Zulip Thomas Beale (Dec 03 2019 at 10:16):

And you can go better, if you agree on a single type to solve the CodeableConcept/Reference question, and a similar approach as discussed above on Boolean/Date.

view this post on Zulip Thomas Beale (Dec 03 2019 at 10:18):

The point to do this is that there is currently nothing saying that "ActivityDefinition.subject[x]": ["CodeableConcept", "Reference"] is the same as "CarePlan.activity.detail.product[x]": ["CodeableConcept", "Reference"] etc. They're just lists. How do we know that someone won't come and change one of them? If they really have some semantics, then it should be defined in a type.

view this post on Zulip Grahame Grieve (Dec 03 2019 at 10:19):

umm. so how does DataValue differ from Type?

view this post on Zulip Thomas Beale (Dec 03 2019 at 10:19):

That argument can be applied in a lot of places. E.g.

"Condition.onset[x]": ["dateTime", "Age", "Period", "Range", "string"],
"Condition.abatement[x]": ["dateTime", "Age", "Period", "Range", "string"],

There is presumably a reason why this particular set of types recurs.

view this post on Zulip Grahame Grieve (Dec 03 2019 at 10:20):

presumably you would think that not much was achieved if everything that currently specialises Type changes to specialise DataValue

view this post on Zulip Thomas Beale (Dec 03 2019 at 10:20):

Well the allowable descendants of DataValue will just be data types. Your Type type is more or less Any isn't it? Meaning the valid runtime substitutions are pretty well any Resource.

view this post on Zulip Grahame Grieve (Dec 03 2019 at 10:21):

when we started discussing this, I made a list of all the choices[x] to look for patterns. I din't pick up any ones strong enough to handle as a type

view this post on Zulip Grahame Grieve (Dec 03 2019 at 10:21):

Resources are not specializations of Type

view this post on Zulip Grahame Grieve (Dec 03 2019 at 10:21):

nor are Elements of resources

view this post on Zulip Thomas Beale (Dec 03 2019 at 10:22):

Ok, what's the formal definition of Type?

view this post on Zulip Grahame Grieve (Dec 03 2019 at 10:26):

It doesn't have one. We agreed to add it and formalise all this - one outcome of this discussion - but it's going to something along the lines of 'a reusable type definition that can be assigned as the type of an element'. I'm not sure that there's much more we can say

view this post on Zulip Thomas Beale (Dec 03 2019 at 10:27):

Well, where does it fit in the type conformance hierarchy? What does my Java etc type structure look like?

view this post on Zulip Grahame Grieve (Dec 03 2019 at 10:29):

pasted image

view this post on Zulip Grahame Grieve (Dec 03 2019 at 10:29):

all the general purpose types inherit from Type or maybe one of the specialisations

view this post on Zulip Thomas Beale (Dec 03 2019 at 10:30):

Ok so Type is another Element subtype. I'm not seeing its utility. Also, what is PrimitiveType?

view this post on Zulip Grahame Grieve (Dec 03 2019 at 10:31):

Type has pretty much the utility you associate with DataValue

view this post on Zulip Grahame Grieve (Dec 03 2019 at 10:32):

PrimitiveType is any of the primitives like string, integer etc. We don't ever use it in the resources, but it gets a lot of work out in the reference implementations

view this post on Zulip Thomas Beale (Dec 03 2019 at 10:36):

Ok, so that means that the descendants of Type are just the data values, i.e. Primitive Data Types and General Purpose Data Types?

view this post on Zulip Thomas Beale (Dec 03 2019 at 10:37):

Are the inheritance hierarchies shown at http://hl7.org/implement/standards/fhir/datatypes.html going to be changed to do that?

view this post on Zulip Thomas Beale (Dec 03 2019 at 10:38):

Then in cases like Observation.value, I don't know why there is any constraint (the one that is there is wrong...).

view this post on Zulip Thomas Beale (Dec 03 2019 at 10:39):

Also, why not call Type 'DataValue' or 'DataType' or similar?

view this post on Zulip Grahame Grieve (Dec 03 2019 at 10:40):

I'm actually working on changing the diagrams right now.

view this post on Zulip Thomas Beale (Dec 03 2019 at 10:40):

We still have not addressed:

view this post on Zulip Grahame Grieve (Dec 03 2019 at 10:41):

I could be comfortable calling it DataType instead of Type

view this post on Zulip Grahame Grieve (Dec 03 2019 at 10:42):

the many occurrences of the same type constraints on http://hl7.org/fhir/R4/choice-elements.json

That's a new subject to me. What do you see?

view this post on Zulip Thomas Beale (Dec 03 2019 at 10:42):

Here's a simple question. Why do we have :["positiveInt", "string"] and many other things of the same form - a type and String? I can guess but interested in the FHIR answer.

view this post on Zulip Thomas Beale (Dec 03 2019 at 10:43):

Well we already looked at ["CodeableConcept", "Reference"] - there's a specific semantic, and it would be better modelled as a single type the formally specifies that semantic.

view this post on Zulip Grahame Grieve (Dec 03 2019 at 10:44):

that specific type will be in the current build as soon as I can get a successful build (I've got a validation unit test oscillating between pass and fail for no reason I can see)

view this post on Zulip Thomas Beale (Dec 03 2019 at 10:45):

I assume all the ones with String as an option are meant to cope with the situation where there could a native Int / Date / whatever, OR just a String field with a serial form of such a type. But think about it: for all those cases, what that pattern is doing is loading onto 10,000 app developers the annoyance of working out the conversion, when the 100 system / vendor owners a) should know what the right conversion is and b) could convert it at source.

view this post on Zulip Grahame Grieve (Dec 03 2019 at 10:46):

the positiveInt / String .. I've been discussing that with PHER who defined that type.. what are they trying to achieve there.. They have may protocols where the immunization series are sequential numbers, but others have named sequences

view this post on Zulip Grahame Grieve (Dec 03 2019 at 10:46):

bzzt. you assume wrong.

view this post on Zulip Grahame Grieve (Dec 03 2019 at 10:47):

it's generally for where you have an unparseable human description of the value, probably vague

view this post on Zulip Thomas Beale (Dec 03 2019 at 10:52):

ok, that's why I was asking

view this post on Zulip Thomas Beale (Dec 03 2019 at 10:53):

but you are saying that those values are actually Identifiers?

view this post on Zulip Thomas Beale (Dec 03 2019 at 10:53):

They should probably just be strings...

view this post on Zulip Thomas Beale (Dec 03 2019 at 10:53):

But I assume I assume right for these ones:

"Immunization.occurrence[x]": ["dateTime", "string"],
    "Immunization.protocolApplied.doseNumber[x]": ["positiveInt", "string"],
    "Immunization.protocolApplied.seriesDoses[x]": ["positiveInt", "string"],
    "ImmunizationEvaluation.doseNumber[x]": ["positiveInt", "string"],
    "ImmunizationEvaluation.seriesDoses[x]": ["positiveInt", "string"],
    "ImmunizationRecommendation.recommendation.doseNumber[x]": ["positiveInt", "string"],
    "ImmunizationRecommendation.recommendation.seriesDoses[x]": ["positiveInt", "string"],

view this post on Zulip Thomas Beale (Dec 03 2019 at 10:55):

pasted image

view this post on Zulip Thomas Beale (Dec 03 2019 at 11:06):

or maybe you are saying its the same story for all of those. In which case they are all ids, not computable numbers.

view this post on Zulip Thomas Beale (Dec 03 2019 at 11:08):

And the documentation is wrong for xx.seriesDoses...

view this post on Zulip Lloyd McKenzie (Dec 03 2019 at 16:59):

Immunization.occurrence[x] of string would be something like "When they were 2 or 3" or "sometime in highschool" - because that's all you've got. The others are described in the documentation "A string should only be used in cases where an integer is not available (such as when documenting a recurring booster dose)." I.e. the notion of "sequence number" doesn't really make sense when you're talking about something like tetanus. You don't say this is tetanus shot 7 or 8. You just say "10-year booster" or something like that. Or, more specifically, that's what existing systems do - and the FHIR spec needs to support how existing systems capture their data.

view this post on Zulip Lloyd McKenzie (Dec 03 2019 at 17:21):

Going back to your earlier reply to my statement, elements in the model are defined by their definitions and scopes, not by their elements. The elements represent the properties that can be used to describe the resource, but they don't define the resource. I.e. An element in my database isn't a Patient because it has name, gender, date of birth, etc. It's a Patient because it meets the semantics of "Demographics and other administrative information about an individual or animal receiving care or other health-related services." and as further refined in the Scope and Usage section of the resource. The meaning isn't defined by any arbitrary implementer's interpretation of the word "Patient", it comes from the specific definition that HL7 has. For many systems, the "contract" for Practitioner and Patient will be identical - they'll capture name, identifier and not much else. But the fact that the contract is the same does not mean that the resources are interchangeable or that you can use Practitioner everywhere that you use Patient or vice versa.

view this post on Zulip Thomas Beale (Dec 03 2019 at 18:31):

Well, in any computing system, the only meaning (for computation) is the formal definition. If your model says that Patient has these N things, then that really is what 'patient' is in your system. It's what your software thinks it is; it's what your data says it is. It's what your analytics treats as 'patients' or not. It's no different in ontology-land, or SNOMED CT. The 'meaning' of any concept in SNOMED is only: its formally defined properties and relations with other concepts - that is how it is computed. I don't of course dispute the idea of some more nuanced / human-oriented idea of classes or Resources in the model - but at the end of the day, software doesn't read any of that.

view this post on Zulip Thomas Beale (Dec 03 2019 at 18:35):

@Lloyd McKenzie

For many systems, the "contract" for Practitioner and Patient will be identical - they'll capture name, identifier and not much else. But the fact that the contract is the same does not mean that the resources are interchangeable or that you can use Practitioner everywhere that you use Patient or vice vice versa

Well, the Patient and Practitioner resources are interchangeable for an in-memory reference (i.e. some variable in software) of static type Party (assume that it's a parent of both). That's what polymorphism is. Of course, that part of the software can only refer to properties defined on Party.

view this post on Zulip Thomas Beale (Dec 03 2019 at 18:42):

It doesn't mean they are generally semantically interchangeable of course - they are distinct types, so they can't be, except for a reference statically typed to any common parent.

view this post on Zulip Thomas Beale (Dec 03 2019 at 18:50):

Anyway, in the page http://hl7.org/fhir/R4/choice-elements.json I think nearly every item is replaceable by a type, it's just a question of analysing why you have these combinations. Mostly it is to do with either:

  • there is a real reference to an X OR an X inline AND/OR a code indicating the type of X
  • ideally there is a structured / formal data item, but sometimes there is only a string
  • variations on ways of representing the time of some event, e.g. a Date, Duration (how long ago), String (a description) etc
  • variations on a single point item and a range/interval of that type
    I think you have already agreed on a proper type to replace the very common ["CodeableConcept", "Reference"] combination.

I would look at ["dateTime", "Age", "Period", "Range", "string"] - it is pretty clear what that pattern is about. Create a class to encapsulate the semantics, and it will make devs much happier than having to write painful code trawling through various type-matching attempts.

Another one to look at: ["CodeableConcept", "Address", "Reference"].

I think analysing these out properly and constructing types, or modifications to the data type will simplify the Resources. I think you could get rid of all choice[x] doing this, which would remove a lot of programmer complexity, and significantly reduce the maintenance work for the Resources themselves.

view this post on Zulip Lloyd McKenzie (Dec 03 2019 at 19:04):

If I have two database tables (e.g. "ACTIVE_PATIENTS" and "PAST_PATIENTS") that happen to contain the same number of columns with the same names and types, that does not mean that the meaning of rows from both tables is the same. The meaning of computational systems absolutely comes from the names of the elements and the meaning that the designer of the system ascribes to those elements.

Sure, we could create specialized classes for each combination of data types that happen to prove useful somewhere. But what's the point? It adds complexity, but doesn't add value - at least to most implementers. We could create a class called "TimeyThing" that encapsulates the dateTime|Age|Period|Range|string choice. But all that means is that there's a layer of indirection in the spec and that implementers need to drill down to realize what types are allowed to be expressed within TimeyThing. It also creates complexity when there's a need to constrain. One system might constrain to dateTime|string, another to dateTime|age|Range, another to dateTime|Period|string, etc. Do those constraints now get buried too? Do we need named types for all of the sub-combinations that implementers must support and create a poly-hierarchy to reflect what can be constrained to what?

Even creating a common type for CodeableConcept or Reference is introducing complexity. And what's actually happening is that we're creating a merger rather than a true choice - i.e. you can have both the CodeableConcept and the Reference.

view this post on Zulip Thomas Beale (Dec 03 2019 at 19:11):

Well you don't create a new type for every accidental (i.e. contingent) conjunction of properties, only for sets of defining properties. This is how both ontology-building and modelling are (should be) done. It's an Aristotelian method, by which each type B is defined as an A that b's, e.g. a mammal is a vertebrate that produces milk to nourish young. That's a defining quality; but you would not define a class of 'stripy mammals', since fur / skin patterns are contingent, variable qualities

view this post on Zulip Thomas Beale (Dec 03 2019 at 19:22):

All those choices on that page are a measure of what kind of complexity you are dealing with - the complexity of 'dirty data'. But most of them are combinations that correspond to specific situations that recur all the time. E.g. a Date or a Period and so on. All, or nearly all of those combinations do have specific semantics and it helps a great deal to define them once and for all. That greatly reduces the pain for client programmers. For example, if you agree that there is a common situation where a Date or a Period could be returned to indicate when some past even occurred, the obvious response is to formalise what is going on there. It's nothing complicated, but a little class that puts those two things together takes away a certain number of decisions, errors and time-wasting from client programmers. E.g.:

class EventTime {
    absolute: DateTime[0..1];
    relative: Duration[0..1];

    invariant
        absolute != null xor relative != null
}

Now a software version of that class can include a useful bit of logic e.g. :

class EventTime {
    absolute: DateTime[0..1];
    relative: Duration[0..1];
    absolute_moment (): DateTime {
        if (absolute != null)
            return absolute;
        else
            return today() - relative;
    }

    invariant
        absolute != null xor relative != null
}

that's a bit rough, and the names could be different / better, but you get the idea. Let's imagine the core group did this analysis, and agreed on a type like the above. Now you have formal semantics for that situation, a single type instead of a choice, and another bunch of choice[x] evaporates.

It looks to me like nearly every other occurrence of choice - certainly the ones that occur multiple times - can be analysed and modelled this way.

view this post on Zulip Grahame Grieve (Dec 03 2019 at 19:37):

CodeableReference is added to the spec: http://build.fhir.org/references.html#codeablereference
it's used in MedicationRequest: http://build.fhir.org/medicationrequest-definitions.html#MedicationRequest.reason

It's added as MnM agreed to add it, except that it's flat not inheriting from CodeableConcept or Reference (there were technical reasons why that wasn't possible). But this structure is wrong for a few reasons, and it should be

class CodeableReference
    coded: CodeableConcept [0..1];
    reference: Reference <Any>[0..1];
end

view this post on Zulip Lloyd McKenzie (Dec 03 2019 at 19:38):

@Grahame Grieve How would we constrain CodeableReference to be just a Reference or just a CodeableConcept?

view this post on Zulip Grahame Grieve (Dec 03 2019 at 19:38):

looking at this list, I feel like we should analyse it by committee...

I don't know, why, for instance, these are different:

   "Claim.accident.location[x]": ["Address", "Reference"],
    "Claim.item.location[x]": ["CodeableConcept", "Address", "Reference"],

view this post on Zulip Grahame Grieve (Dec 03 2019 at 19:42):

But @Thomas Beale is right that dateTime | Period | Duration is a very common combination that does have some particular semantic challenges that might make it worth defining a reusable choice structure.

Though there's also date | Period which is giving me some pause. Does that imply that the Period should also be whole day periods? The resource doesn't say. Here's a committee where Tom's criticisms are totally valid.

view this post on Zulip Lloyd McKenzie (Dec 03 2019 at 19:43):

@Thomas Beale The challenge is that we need arbitrary combinations even in the main spec. The "arbitrary" bit comes from what's commonly needed/used. In principle, any time can be expressed as a fixed point (with varying constraints on precision), an uncertain range (possibly with unknown upper or lower bounds), a relative point (e.g. age), an uncertain relative range, or as free text. But in practice, not all of those options are relevant in the places where we capture timing information. Systems capturing Condition onsets may well capture "age at onset", but few lab systems capture "age at specimen collection". That's not because the notion couldn't still be meaningful, it's just not what business practice does. And the core specification needs to align with what business practice does - not with what's theoretically possible. As soon as you expand every element to the theoretically possible, the complexity becomes unmanageable and implementers (rightfully) refuse the spec.

view this post on Zulip Lloyd McKenzie (Dec 03 2019 at 19:45):

The semantics of date | Period actually vary. Sometimes the period indicates "this thing occurred with a duration". Sometimes it means "this thing occurred sometime between this start and this end". The difference is communicated in the definition of the element, not in the type. (We argued about whether distinct types were appropriate in the early days of FHIR and decided that from an implementer perspective, the distinction wasn't helpful/comprehensible.)

view this post on Zulip Lloyd McKenzie (Dec 03 2019 at 19:48):

(dateTime | Period), (date | Period), (dateTime | Period | Duration), (dateTime | Period | Age | Range | string), (dateTime | Period | string) are all combinations that appear. And the more complex combinations might be constrained to simpler combinations in downstream profiles. I don't see how introducing types and complex type hierarchies makes implementer lives easier here. It keeps being asserted that it does/will, but where are the programmers asking for it? Where are the examples of code that would be made simpler? I'm just not getting it...

view this post on Zulip Andy Stechishin (Dec 03 2019 at 19:49):

@Grahame Grieve for:

view this post on Zulip Lloyd McKenzie (Dec 03 2019 at 19:50):

For CodeableReference, the benefit of the type is the ability to convey both so that downstream systems that prefer one approach or the other can see the data as they wish. And even there, I don't think the current proposed type meets the bar because constraint in downstream profiles just became hard.

view this post on Zulip Andy Stechishin (Dec 03 2019 at 19:50):

"Claim.accident.location[x]": ["Address", "Reference"],
"Claim.item.location[x]": ["CodeableConcept", "Address", "Reference"],
The reason for the difference

view this post on Zulip Andy Stechishin (Dec 03 2019 at 19:53):

is that accidents by definition are accidental and as such seldom have a code to describe where they occurred. Whereas the Claim.item which can be a service will often occur somewhere in an institution that may be identified simply by a coded value. The code was explicitly asked to be added to the Claim.item by the community but it was felt by the same community that accident location did not require this change. I believe there may be description of this in the comments.

view this post on Zulip Thomas Beale (Dec 03 2019 at 19:59):

@Lloyd McKenzie

That's not because the notion couldn't still be meaningful, it's just not what business practice does. And the core specification needs to align with what business practice does - not with what's theoretically possible. As soon as you expand every element to the theoretically possible, the complexity becomes unmanageable and implementers (rightfully) refuse the spec.

I'm not saying you need to stop using plain Duration or Date - sure use them where they are correct. But in places where everyone agrees (as apparently has happened, quite a few times) that this Date | Duration situation (or maybe Date | Duration | Period is the general case) can happen, now you have a type for it. No discussions needed, no need to waste hours on that, since it is already solved.

view this post on Zulip Lloyd McKenzie (Dec 03 2019 at 20:02):

But it's not solved - the discussion needs to happen for every single element about what combination is reasonable - and commonly supported for that element. The fact that there's a FunkyTimeA type that has a particular combination doesn't mean that combination is appropriate when a new attribute comes along. Maybe it's identical to FunkyTimeA with one less or one more type. If the requirement genuinely is distinct, then we don't want to use a type that allows something or prohibits something inappropriately. The problem we do want to move away from is where types get excluded because they weren't thought about, or types get included inappropriately because the modeler didn't realize that a use was inappropriate. (e.g. having 'string' in a choice with CodeableConcept)

view this post on Zulip Grahame Grieve (Dec 03 2019 at 20:04):

@Andy Stechishin I looked again at the notes and still didn't see anything

view this post on Zulip Thomas Beale (Dec 03 2019 at 20:04):

@Grahame Grieve that new type shouldn't inherit from CodeableConcept. It could in theory inherit from Reference<T>, Although a more thorough analysis would probably lead to something more like a type CodedDataType<DataType>, i.e. a type that effective wraps some other type and adds a code to it. This takes care of cases like {Address | CodeableConcept} and probably others. But that's possibly a step too far.

view this post on Zulip Grahame Grieve (Dec 03 2019 at 20:04):

I was thinking about that... I think it is a step too far.

view this post on Zulip Grahame Grieve (Dec 03 2019 at 20:05):

for our analytical pleasure, here's the list of choice types grouped by the type set, and sorted by frequency:

view this post on Zulip Grahame Grieve (Dec 03 2019 at 20:05):

[CodeableConcept, Reference]: (41) [DataRequirement.subject[x], ActivityDefinition.subject[x], ActivityDefinition.product[x], CarePlan.activity.detail.product[x], ChargeItem.product[x], Claim.diagnosis.diagnosis[x], Claim.procedure.procedure[x], Contract.topic[x], Contract.term.topic[x], Contract.term.asset.valuedItem.entity[x], CoverageEligibilityRequest.item.diagnosis.diagnosis[x], DeviceRequest.code[x], EventDefinition.subject[x], ExplanationOfBenefit.diagnosis.diagnosis[x], ExplanationOfBenefit.procedure.procedure[x], Invoice.lineItem.chargeItem[x], Library.subject[x], Measure.subject[x], Medication.ingredient.item[x], MedicationAdministration.medication[x], MedicationDispense.statusReason[x], MedicationDispense.medication[x], MedicationKnowledge.ingredient.item[x], MedicationKnowledge.administrationGuidelines.indication[x], MedicationRequest.medication[x], MedicationStatement.medication[x], MedicinalProduct.specialDesignation.indication[x], MedicinalProductContraindication.otherTherapy.medication[x], MedicinalProductIndication.otherTherapy.medication[x], MedicinalProductInteraction.interactant.item[x], PlanDefinition.subject[x], PlanDefinition.action.subject[x], ResearchDefinition.subject[x], ResearchElementDefinition.subject[x], Specimen.container.additive[x], SpecimenDefinition.typeTested.container.additive.additive[x], Substance.ingredient.substance[x], SubstanceSpecification.property.definingSubstance[x], SubstanceSpecification.relationship.substance[x], SupplyDelivery.suppliedItem.item[x], SupplyRequest.item[x]]
[Period, dateTime]: (15) [BiologicallyDerivedProduct.collection.collected[x], BiologicallyDerivedProduct.processing.time[x], BiologicallyDerivedProduct.manipulation.time[x], ClinicalImpression.effective[x], CommunicationRequest.occurrence[x], DetectedIssue.identified[x], DiagnosticReport.effective[x], Media.created[x], MedicationAdministration.effective[x], MedicationStatement.effective[x], MedicinalProductAuthorization.procedure.date[x], Provenance.occurred[x], RiskAssessment.occurrence[x], Specimen.collection.collected[x], Specimen.processing.time[x]]
[Period, date]: (8) [Claim.supportingInfo.timing[x], Claim.item.serviced[x], ClaimResponse.addItem.serviced[x], CoverageEligibilityRequest.serviced[x], CoverageEligibilityResponse.serviced[x], ExplanationOfBenefit.supportingInfo.timing[x], ExplanationOfBenefit.item.serviced[x], ExplanationOfBenefit.addItem.serviced[x]]
[Period, Timing, dateTime]: (7) [ChargeItem.occurrence[x], Contract.term.action.occurrence[x], DeviceRequest.occurrence[x], DeviceUseStatement.timing[x], ServiceRequest.occurrence[x], SupplyDelivery.occurrence[x], SupplyRequest.occurrence[x]]
[positiveInt, string]: (6) [Immunization.protocolApplied.doseNumber[x], Immunization.protocolApplied.seriesDoses[x], ImmunizationEvaluation.doseNumber[x], ImmunizationEvaluation.seriesDoses[x], ImmunizationRecommendation.recommendation.doseNumber[x], ImmunizationRecommendation.recommendation.seriesDoses[x]]
[Attachment, Reference]: (5) [Consent.source[x], Contract.friendly.content[x], Contract.legal.content[x], Contract.rule.content[x], Contract.legallyBinding[x]]
[Age, Period, Range, dateTime, string]: (4) [AllergyIntolerance.onset[x], Condition.onset[x], Condition.abatement[x], Procedure.performed[x]]
[Address, CodeableConcept, Reference]: (4) [Claim.item.location[x], ClaimResponse.addItem.location[x], ExplanationOfBenefit.item.location[x], ExplanationOfBenefit.addItem.location[x]]
[canonical, uri]: (3) [ConceptMap.source[x], ConceptMap.target[x], PlanDefinition.action.definition[x]]
[Money, string, unsignedInt]: (3) [CoverageEligibilityResponse.insurance.item.benefit.allowed[x], CoverageEligibilityResponse.insurance.item.benefit.used[x], ExplanationOfBenefit.benefitBalance.financial.allowed[x]]
[Duration, Period, Timing, dateTime]: (3) [EvidenceVariable.characteristic.participantEffective[x], ResearchElementDefinition.characteristic.studyEffective[x], ResearchElementDefinition.characteristic.participantEffective[x]]
[CodeableConcept, boolean]: (3) [Dosage.asNeeded[x], MedicationRequest.substitution.allowed[x], ServiceRequest.asNeeded[x]]
[Attachment, Coding, Quantity, Reference, boolean, date, dateTime, decimal, integer, string, time, uri]: (3) [Contract.term.offer.answer.value[x], Questionnaire.item.initial.value[x], QuestionnaireResponse.item.answer.value[x]]
[Age, Duration, Period, Range, Timing, dateTime]: (3) [ActivityDefinition.timing[x], PlanDefinition.action.timing[x], RequestGroup.action.timing[x]]
[*]: (3) [StructureMap.group.rule.source.defaultValue[x], Task.input.value[x], Task.output.value[x]]
[boolean, canonical]: (2) [ImplementationGuide.definition.resource.example[x], ImplementationGuide.manifest.resource.example[x]]
[Reference, string]: (2) [Annotation.author[x], DeviceDefinition.manufacturer[x]]
[Ratio, SimpleQuantity]: (2) [MedicationAdministration.dosage.rate[x], NutritionOrder.enteralFormula.administration.rate[x]]
[Quantity, string]: (2) [SubstanceSpecification.moiety.amount[x], SubstanceSpecification.property.amount[x]]
[Quantity, Range, string]: (2) [SubstanceAmount.amount[x], SubstanceReferenceInformation.target.amount[x]]
[Duration, Range]: (2) [PlanDefinition.action.relatedAction.offset[x], RequestGroup.action.relatedAction.offset[x]]
[Coding, uri]: (2) [MessageDefinition.event[x], MessageHeader.event[x]]
[CodeableConcept, Quantity, Range, boolean]: (2) [DeviceRequest.parameter.value[x], SupplyRequest.parameter.value[x]]
[CodeableConcept, Period, Quantity, Range, Ratio, SampledData, boolean, dateTime, integer, string, time]: (2) [Observation.value[x], Observation.component.value[x]]
[Attachment, Reference, string]: (2) [Communication.payload.content[x], CommunicationRequest.payload.content[x]]
[Attachment, Quantity, Reference, boolean, string]: (2) [Claim.supportingInfo.value[x], ExplanationOfBenefit.supportingInfo.value[x]]
[Address, Reference]: (2) [Claim.accident.location[x], ExplanationOfBenefit.accident.location[x]]
[dateTime, string]: (1) [Immunization.occurrence[x]]
[boolean, integer]: (1) [Patient.multipleBirth[x]]
[boolean, decimal, id, integer, string]: (1) [StructureMap.group.rule.target.parameter.value[x]]
[boolean, dateTime]: (1) [Patient.deceased[x]]
[boolean, code, dateTime, decimal, integer, string, uri]: (1) [ValueSet.expansion.parameter.value[x]]
[base64Binary, string]: (1) [AuditEvent.entity.detail.value[x]]
[SimpleQuantity, string]: (1) [SpecimenDefinition.typeTested.container.minimumVolume[x]]
[Reference, url]: (1) [ImplementationGuide.definition.page.name[x]]
[Reference, boolean]: (1) [MedicationRequest.reported[x]]
[Reference, Timing, date, dateTime]: (1) [TriggerDefinition.timing[x]]
[Range, decimal]: (1) [RiskAssessment.prediction.probability[x]]
[Range, SimpleQuantity]: (1) [Dosage.doseAndRate.dose[x]]
[Range, Ratio, SimpleQuantity]: (1) [Dosage.doseAndRate.rate[x]]
[Quantity, Range, Ratio]: (1) [ServiceRequest.quantity[x]]
[Quantity, Range, Ratio, string]: (1) [SubstanceSpecification.relationship.amount[x]]
[Period, date, string]: (1) [FamilyMemberHistory.born[x]]
[Period, Timing, string]: (1) [CarePlan.activity.detail.scheduled[x]]
[Period, Timing, dateTime, instant]: (1) [Observation.effective[x]]
[Period, Range]: (1) [RiskAssessment.prediction.when[x]]
[Money, unsignedInt]: (1) [ExplanationOfBenefit.benefitBalance.financial.used[x]]
[Money, SimpleQuantity]: (1) [Coverage.costToBeneficiary.value[x]]
[Identifier, Reference]: (1) [Composition.relatesTo.target[x]]
[Duration, date]: (1) [Goal.target.due[x]]
[Duration, Period, dateTime]: (1) [DataRequirement.dateFilter.value[x]]
[Duration, Period, Range]: (1) [Timing.repeat.bounds[x]]
[Coding, boolean, code, dateTime, decimal, integer, string]: (1) [CodeSystem.concept.property.value[x]]
[Coding, Reference, date, integer, string, time]: (1) [Questionnaire.item.answerOption.value[x]]
[Coding, Quantity, Reference, boolean, date, dateTime, decimal, integer, string, time]: (1) [Questionnaire.item.enableWhen.answer[x]]
[CodeableConcept, date]: (1) [Goal.start[x]]
[CodeableConcept, canonical, uri]: (1) [GuidanceResponse.module[x]]
[CodeableConcept, SimpleQuantity]: (1) [MedicationKnowledge.administrationGuidelines.patientCharacteristics.characteristic[x]]
[CodeableConcept, SimpleQuantity, base64Binary, string]: (1) [MedicationKnowledge.drugCharacteristic.value[x]]
[CodeableConcept, Range]: (1) [Population.age[x]]
[CodeableConcept, Quantity, Range]: (1) [PlanDefinition.goal.target.detail[x]]
[CodeableConcept, Quantity, Range, Reference]: (1) [UsageContext.value[x]]
[CodeableConcept, Quantity, Range, Reference, boolean]: (1) [Group.characteristic.value[x]]
[CodeableConcept, Quantity, Range, Ratio, boolean, integer, string]: (1) [Goal.target.detail[x]]
[CodeableConcept, Duration]: (1) [Specimen.collection.fastingStatus[x]]
[CodeableConcept, DataRequirement, Expression, canonical]: (1) [ResearchElementDefinition.characteristic.definition[x]]
[CodeableConcept, DataRequirement, Expression, Reference, TriggerDefinition, canonical]: (1) [EvidenceVariable.characteristic.definition[x]]
[Age, Range, string]: (1) [FamilyMemberHistory.age[x]]
[Age, Range, boolean, date, string]: (1) [FamilyMemberHistory.deceased[x]]
[Age, Period, Range, string]: (1) [FamilyMemberHistory.condition.onset[x]]

view this post on Zulip Thomas Beale (Dec 03 2019 at 20:08):

@Lloyd McKenzie I think you are not looking at this in the right way. I would be looking for common situations, not assuming that every time some combination like {Date | Duration} pops up, it is an entirely de novo situation. There are reasons these combinations pop up; usually it is the same reason every time some particular combination pops up. So look for that, analyse it, and build a type for it so that it is no longer an ad hoc analysis every time. If on the n+1st time of seeing {Date | Duration} the group looks at the handy EventTime type, and decides, no, that doesn't correspond to our situation, ok, they can avoid it. But 95% of the time, it will correspond.

view this post on Zulip Thomas Beale (Dec 03 2019 at 20:08):

@Grahame Grieve you saved me the trouble, I was going to build exactly that list

view this post on Zulip Thomas Beale (Dec 03 2019 at 20:09):

Any chance you can do it with one entry on a line?

view this post on Zulip Grahame Grieve (Dec 03 2019 at 20:09):

one entry what?

view this post on Zulip Grahame Grieve (Dec 03 2019 at 20:09):

on the right side?

view this post on Zulip Thomas Beale (Dec 03 2019 at 20:12):

I just did it, a bit more readable.

view this post on Zulip Thomas Beale (Dec 03 2019 at 20:12):

sorry my bad - I just realised it is a 2-col list

view this post on Zulip Thomas Beale (Dec 03 2019 at 20:15):

Ok so I am prepared to bet that a lot of that long tail of (2) and (1) are just lesser versions of say combinations with (3) or more occurrences, but the relevant group hasn't thought it through (their minds being on more interesting questions no doubt).

view this post on Zulip Thomas Beale (Dec 03 2019 at 20:17):

No matter what I look at, there are reasons and semantics. E.g. [boolean, integer] - this is a repeat of the situation of 'presence and/or quantity' which is an analogue of 'occurred and/or when'.

view this post on Zulip Thomas Beale (Dec 03 2019 at 20:18):

There are explanations for all of these. Despite some undoubted missing types, and possibly some wrong extra types, you will find that when you interrogate everyone who created these things that in each case, they were trying to solve the same problem as some other group that created (nearly) the same pattern.

view this post on Zulip Grahame Grieve (Dec 03 2019 at 20:18):

ok. my comments:

  • [CodeableConcept, Reference]: (41) - already going away
  • [Period, dateTime]: (15) + [Period, date]: (8) - deserves it's own type to deal with the semantics. Probably with a profile.
  • [Period, Timing, dateTime]: (7) - maybe part of the previous type? Timing is always difficult
  • [positiveInt, string]: (6) - not a type. Not a pattern I like either. Discussion with committee (though on these cases the tooling should make the type PrimitiveType not Type)
  • [Attachment, Reference]: (5) - make Reference a property of Attachment - this is a current topic in the implementers channel
  • [Age, Period, Range, dateTime, string]: (4) - don't know. these are few and slippery things anyway
  • [Address, CodeableConcept, Reference] - this pattern should not exist; these are all Location things. Why inline things out of Location ?
  • [canonical, uri]: (3) - this pattern should not exists - part of the discussion about instantiate[x]

view this post on Zulip Thomas Beale (Dec 03 2019 at 20:20):

Grahame, your list looks good.

view this post on Zulip Grahame Grieve (Dec 03 2019 at 20:21):

So I propose the creation of a new DataType, called AbsoutelyHorribleTimeThingy, with dateTime, Period, Duration, and Timing in it

view this post on Zulip Grahame Grieve (Dec 03 2019 at 20:21):

as mutually exclusive choices

view this post on Zulip Thomas Beale (Dec 03 2019 at 20:21):

We have it on out TODO list in openEHR to solve this annoying timing question i.e. the 5 (or is it 23) ways of specifying time in various situations. I will try to accelerate that bit of modelling and share it. Might not quite do what you need, but could at least give you some ideas.

view this post on Zulip Lloyd McKenzie (Dec 03 2019 at 20:21):

What does it mean to have Period, dateTime as its own type? Can I still constrain it to just Period or just dateTime in my profile? Is there overhead in the instance due to having a distinct type?

view this post on Zulip Grahame Grieve (Dec 03 2019 at 20:22):

yes and yes

view this post on Zulip Lloyd McKenzie (Dec 03 2019 at 20:22):

Is the overhead in the instance worth it? What's the added value?

view this post on Zulip Thomas Beale (Dec 03 2019 at 20:22):

Nah, programmers will thank you for it, and will think of it as FantasticSavedMe3HoursOfPointlessArgumentTimingThingy...

view this post on Zulip Grahame Grieve (Dec 03 2019 at 20:22):

with regard to the overhead - it's a tradeoff. Tom's reasonable point is that there's trade off somewhere. Reusable semantics requires some structure to set up reusability

view this post on Zulip Thomas Beale (Dec 03 2019 at 20:23):

My working name for that kind of type is just TimeSpecifier...

view this post on Zulip Grahame Grieve (Dec 03 2019 at 20:23):

but I don't think it's as obvious a tradeoff as Tom thinks.

view this post on Zulip Grahame Grieve (Dec 03 2019 at 20:23):

It's the tricky input / display / transform semantics that make it worth making re-usable

view this post on Zulip Thomas Beale (Dec 03 2019 at 20:24):

Maybe I'm driving you guys up the wall here, but my point is: think of all the client programmers... every single unnecessary choice they have to make, is a a loss of time, and also a possible source of error. Make things a bit harder for the server-side, there will be 100x less of them. And big companies... ;)

view this post on Zulip Grahame Grieve (Dec 03 2019 at 20:25):

we do. and we get their input all the time. Both here and privately and face to face. And their feedback is nowhere unanimous along the lines you think

view this post on Zulip Lloyd McKenzie (Dec 03 2019 at 20:25):

I've heard lots of assertions about what developers will want. I haven't heard developers asking for these things yet, just a few modeler types. (And we're not trying to satisfy the needs of modeler types). @Grahame Grieve can you explain more concretely what having the new types will make easier for our standard 'simple' developers that will offset the cost of more named things to understand, more opacity around what data can be present and more complex instances? (Remembering as well that our objective is to keep things as simple as possible for the 'simple' developers, even if it means foisting more work on the more sophisticated developers.)

view this post on Zulip Thomas Beale (Dec 03 2019 at 20:25):

[Reference, url] - surely same as your last list item

view this post on Zulip Grahame Grieve (Dec 03 2019 at 20:26):

maybe

view this post on Zulip Grahame Grieve (Dec 03 2019 at 20:26):

similar principle

view this post on Zulip Lloyd McKenzie (Dec 03 2019 at 20:27):

(Note - when I say 'modeler type', I consider myself one of those. I try to guard, not always successfully, at pushing modeling ideals into the spec that conflict with what developers actually do/want.)

view this post on Zulip Thomas Beale (Dec 03 2019 at 20:28):

Nearly every time there is a boolean, it is represents 'presence' or 'occurred' - and it is with some other data type, which is the appropriate type for the thing being reported quantitatively - integer, decimal, dateTime etc. So there is a very common pattern there which is not instantly obvious in Grahame's table.

view this post on Zulip Lloyd McKenzie (Dec 03 2019 at 20:28):

Also, @Grahame Grieve, how do we constrain the new CodeableReference to just Reference or just CodeableConcept if that's what a profile needs? I don't see how that's possible?

view this post on Zulip Thomas Beale (Dec 03 2019 at 20:31):

Ha. 90% of developers want less crap to wade through and less decisions to make. And we are all sometimes in that mode. Quite often reading a spec we realise we wrote. When I see something that makes me write out a 5-way if/then/else type testing statement, I think - really, they didn't sort this out? Then I see it's something we wrote, and I think... ok, we need to sort this out. Anytime people have to write ad hoc type-testing code, something is wrong (or at least not done).

view this post on Zulip Grahame Grieve (Dec 03 2019 at 20:32):

so at present, we have a set of elements that have a set of related patterns -around date(Time) | Period | Duration | Timing. If I'm going to deal with those, I'm going to have to do some variation of the following tasks:

  • write User interface for input and also display
  • move content in from / out to other formats
  • figure out what it means for some actual code that interprets the meaning

In most of those cases, I'm pretty much going to have to write separate code for the different possible types. And I'm going to get a fair degree of re-use of that code across different elements.

But I will have to introduce case / switch statement for each element to deal with the choices. Moving the choice to a specific type means that I can centralise the case/switch statement and have cleaner easier to maintain visitor pattern type code etc, and it's wroth more in investing robustness around the type.

But it's a bit of moving deck chairs around the titanic. The complexity exists; I don't think we're fundamentally adding to it. It's just a question of where the cost of re-use is least painful

view this post on Zulip Thomas Beale (Dec 03 2019 at 20:32):

@Lloyd McKenzie don't you just want to force one of them to be 1..1? That should be easy.

view this post on Zulip Grahame Grieve (Dec 03 2019 at 20:33):

yes, the way I'd rather do it would make that simpler, but it's not special otherwise

view this post on Zulip Thomas Beale (Dec 03 2019 at 20:33):

means that I can centralise the case/switch statement and have cleaner easier to maintain visitor pattern type code etc, and it's wroth more in investing robustness around the type.

what that guy said...

view this post on Zulip Grahame Grieve (Dec 03 2019 at 20:36):

also centralising the definition of the choice allows us to have more to say about the play off between the choices and invest in profiling etc to make sense of things like date | Period

view this post on Zulip Grahame Grieve (Dec 03 2019 at 20:36):

for me, that's the swaying factor on the date related elements. Clearly we should be saying more about this stuff than we are .

view this post on Zulip Thomas Beale (Dec 03 2019 at 20:37):

For fun... search 'onset' in the table:

[Age, Period, Range, dateTime, string]    // 2 hits
[Age, Period, Range,          string]    // 1 hit

view this post on Zulip Grahame Grieve (Dec 03 2019 at 20:37):

Actually, I think that makes sense

view this post on Zulip Grahame Grieve (Dec 03 2019 at 20:38):

Though I'm not sure the sensibleness is that important as opposed to consistency

view this post on Zulip Thomas Beale (Dec 03 2019 at 20:40):

'timing':

[Period, date]    // 2 hits
[Period, Timing, dateTime]   // 1 hit
[Age, Duration, Period, Range, Timing, dateTime]    // 3 hits
[Reference, Timing, date, dateTime]             // 1 hit

view this post on Zulip Grahame Grieve (Dec 03 2019 at 20:41):

date | dateTime is not a sensible combination

view this post on Zulip Thomas Beale (Dec 03 2019 at 20:42):

'occurrence':

[Period, dateTime]    // 1 hit
[Period, Timing, dateTime]    // 6 hits; #7 is called 'timing'...
[dateTime, string]         // 1 hit

view this post on Zulip Grahame Grieve (Dec 03 2019 at 20:43):

ouch. there's some bug in my analysis

view this post on Zulip Grahame Grieve (Dec 03 2019 at 20:44):

no it's in your reformat, @Thomas Beale

view this post on Zulip Grahame Grieve (Dec 03 2019 at 20:44):

no whoops, I was just reading it wrong

view this post on Zulip Thomas Beale (Dec 03 2019 at 20:45):

What reformat?

view this post on Zulip Grahame Grieve (Dec 03 2019 at 20:45):

no just ignore that. I had a problem between the chair and the keyboard

view this post on Zulip Grahame Grieve (Dec 03 2019 at 20:46):

@Lloyd McKenzie does Timing | Reference(Schedule) make any sense?

view this post on Zulip Thomas Beale (Dec 03 2019 at 20:46):

There are 5 rows in http://hl7.org/fhir/R4/choice-elements.json with "date", "dateTime" (among other types)

view this post on Zulip Grahame Grieve (Dec 03 2019 at 20:46):

it looks incoherent to me

view this post on Zulip Grahame Grieve (Dec 03 2019 at 20:47):

2 of the 5 are the genuine Type ancestors

view this post on Zulip Grahame Grieve (Dec 03 2019 at 20:50):

@Bryn Rhodes I can't make sense of a a choice between Timing | Reference(Schedule) in TriggerDefinition. It looks like an error to me

view this post on Zulip Lloyd McKenzie (Dec 03 2019 at 20:53):

I don't understand how intermediary types = less crap to wade through and less decisions to make. The number of decisions are identical. If I have a choice of dateTime|Period|Age|Range|string, whether that's expressed as a type or as a choice, I still need to figure out: a) which of those types does my system support; b) how do I map my existing data structures into the FHIR format.

Remember - implementers aren't implementing FHIR. They aren't generally adding any new capabilities to their systems or changing their internal data models at all. They're just mapping what they've got into the FHIR structures.

In terms of crap to wade through, we've just added one more named thing, one more set of descriptions, one more click in the spec to see what's actually available and one more node to navigate through when defining path structures or creating instances.

view this post on Zulip Grahame Grieve (Dec 03 2019 at 20:54):

yes there's a cost.

view this post on Zulip Grahame Grieve (Dec 03 2019 at 20:55):

- implementers aren't implementing FHIR. They aren't generally adding any new capabilities to their systems or changing their internal data models at all.

That's not true for many implementers. There are some who are not changing things at all. but as many are changing some things.and others are building from scratch. particularly clients are generally building from scratch. And in case you hadn't noticed, that is getting more common

view this post on Zulip Grahame Grieve (Dec 03 2019 at 20:56):

so we are seeking for the least net pain. I'm not at all persuaded that we should get rid of choice elements generally, but I am persuaded that for 3 to 4 of the common patterns, it's worth it to define specific types to enable re-use and investment

view this post on Zulip Lloyd McKenzie (Dec 03 2019 at 20:58):

@Grahame Grieve thanks for that explanation. My impression of your explanation is that the types are a useful implementation artifact, but not necessarily a useful exchange artifact. It seems to me that expressing them in the standard leads to a more complex spec and more complex instances, with no real benefit in "specification" space. However, there is savings and re-use on the implementation side. Would it make sense to push this stuff into the reference implementations rather than the specifications? I.e. If the reference implementation sees a certain combination, it could treat that as a class and re-use certain code?

view this post on Zulip Lloyd McKenzie (Dec 03 2019 at 20:59):

Where is Timing|Reference(schedule) appearing? Can't see it in the list you provided

view this post on Zulip Lloyd McKenzie (Dec 03 2019 at 21:00):

My point is more that, with FHIR, we try very hard not to depend on implementers changing what they do - we try to make it so they can simply expose their data "as is" in the FHIR standard, though obviously mapping/transformation is necessary. The driver for changing behavior is supposed to be implementation guides, not the core specification itself.

view this post on Zulip Grahame Grieve (Dec 03 2019 at 21:00):

TriggerDefinition

view this post on Zulip Grahame Grieve (Dec 03 2019 at 21:01):

that point seems unrelated to this dsicussion

view this post on Zulip Grahame Grieve (Dec 03 2019 at 21:02):

I think that we should define them where they are useful definition artifact. Which I think is the case for CodeableReference and this HorribleDatePattern

view this post on Zulip Lloyd McKenzie (Dec 03 2019 at 21:04):

For TriggerDefinition, it could make sense, but more documentation is needed. Does it mean that the trigger fires once for each Slot in the Schedule?

view this post on Zulip Grahame Grieve (Dec 03 2019 at 21:05):

as it actually happens? or as it is used up? Weird...

view this post on Zulip Lloyd McKenzie (Dec 03 2019 at 21:06):

No clue as to the use-case. I can see it as a theoretical possibility, but definitely couldn't implement it as currently specified and don't know when I'd actually want to use it.

view this post on Zulip Lloyd McKenzie (Dec 03 2019 at 21:07):

My concerns are:
- adding complexity obfuscation to the specification
- adding layers to the instances
- making it harder to declare constraints
- making work groups feel that they can't use arbitrary collections that reflect the reality because their needs don't fit one of the "standard patterns"

Is exposing this stuff in the reference implementations but not the interface a possibility?

view this post on Zulip Bryn Rhodes (Dec 03 2019 at 21:10):

Yes, I think the reference to Schedule in the trigger there is a mistake.

view this post on Zulip Bryn Rhodes (Dec 03 2019 at 21:11):

When I defined it I was thinking "Schedule" more broadly than what it is actually representing.

view this post on Zulip Lloyd McKenzie (Dec 03 2019 at 21:15):

@Bryn Rhodes you'll write up the change request?

view this post on Zulip Bryn Rhodes (Dec 03 2019 at 21:23):

Yes, doing that now

view this post on Zulip Bryn Rhodes (Dec 03 2019 at 21:32):

https://jira.hl7.org/browse/FHIR-25269

view this post on Zulip Grahame Grieve (Dec 03 2019 at 22:20):

ok. MnM decisions:
- we'll announce that we are intending to migrate all CodeableConcept | Reference to the new type
- we'll announce that we're working on the new date/timing related type and create a new thread to do that here on the #methodology stream. If we can get consensus, then we'll put something in the spec and seek further comment

view this post on Zulip Grahame Grieve (Dec 04 2019 at 03:05):

@Thomas Beale do you want to create a change proposal for the seriesDoses thing? I have presume that's 3 of 6 kind of thing - don't know how that could not be a positiveInt

view this post on Zulip Lloyd McKenzie (Dec 04 2019 at 03:12):

The reason for it not being a positive int is when it's not actually part of a series. E.g. a Tetanus booster. (What's not clear is why they're sticking that in sequence as opposed to putting it somewhere else...)

view this post on Zulip Grahame Grieve (Dec 04 2019 at 05:22):

you described doseNumber[x]. seriesDoses[x] - that doesn't appear to make sense at all

view this post on Zulip Lloyd McKenzie (Dec 04 2019 at 05:27):

I'm just echoing the explanation given in the usage notes for the element. @Craig Newman, can you provide a better explanation of the choice of posInt|string?

view this post on Zulip Grahame Grieve (Dec 04 2019 at 05:30):

doseNumber[x] = Nominal position in a series. The use of an integer is preferred if known. A string should only be used in cases where an integer is not available (such as when documenting a recurring booster dose).

and

seriesDoses[x] = The recommended number of doses to achieve immunity.

I can only presume this pair is so you can say '2 of 4'. Under what circumstances is it meaningful for the second part ('of 4') o be non-numerical? ('Tetanus booster of However many boosters appropriate') ?

view this post on Zulip Thomas Beale (Dec 04 2019 at 07:40):

@Grahame Grieve that was my understanding reading the documentation as well. seriesDoses has to be numerical, or else its documentation, as well as the overall concept make no sense. So something is wrong here.

view this post on Zulip Grahame Grieve (Dec 04 2019 at 09:57):

right. do you want to make a task for them to fix it?

view this post on Zulip Thomas Beale (Dec 04 2019 at 13:07):

Well I don't think it is my business to make comments about the models of that group. I think (as I have said previously) that there is a methodology review function that is somewhat missing generally, given the number of questionable choices / errors we are seeing. I'm not saying that casually, what I mean is that the people working on these models don't have sufficient initial guidance on what they should / should not do (even if they are serious modelling experts - it's mainly a question of FHIR modelling norms etc), and also, there isn't a 'model QA' filter occurring before any committee changes are committed, or if there is, it's not working as intended.

So yes, I assume someone should raise a CR to fix that particular problem, but the fact is there are lots of these - if we comb through your table, we'll discover another dozen for sure. This is why we should make the effort to sort out these special Data types that deal with common situations of getting a Date | Duration, item | boolean, Ref | value and so on.

view this post on Zulip Craig Newman (Dec 04 2019 at 13:52):

"booster" or something like "annual" for flu shots is the use case for a string doseNumber. Most immunizations are administered as part of a primary series with a recommended number of doses (2 doses of MMR, 3 doses of Hepatitis A, etc) which is the "seriesDose". However when there are boosters recommended (a meningococcal vaccine every 10 years after the completion of the primary series) there is no known upper limit because you don't know how long the patient will ultimately live (or how many doses they actually get (they could wait 20 years for their first booster).

view this post on Zulip Lloyd McKenzie (Dec 04 2019 at 13:53):

Why is that being captured in 'sequence'? Wouldn't sequence just be non-applicable and you'd capture the purpose of the dose in a different element entirely?

view this post on Zulip Lloyd McKenzie (Dec 04 2019 at 13:53):

(@Craig Newman)

view this post on Zulip Craig Newman (Dec 04 2019 at 13:54):

That's one way to do it. I think in the immunization world, it's all one concept, it's just that "booster" isn't a number.

view this post on Zulip Lloyd McKenzie (Dec 04 2019 at 14:49):

@Craig Newman So, within most immunization databases, they'd have a single field that would capture either "1", "2", "3" or "booster"? Is there a constrained set of values that could exist other than the numbers? Could this be a code rather than string?

view this post on Zulip Craig Newman (Dec 04 2019 at 14:50):

An alternative would be to just make it a string. That would still allow a number to be sent if it's part of the primary series. I wouldn't necessarily expect that anyone will need even the primary series doses to be numbers (as opposed to a string like "first" or "second"). I think we just kept positiveInt because that was how the element was defined in STU3 and earlier versions

view this post on Zulip Craig Newman (Dec 04 2019 at 14:53):

I don't think EHRs or IIS (immunization registry systems) really even store data like this. The "2 out 3" really is more part of an on the fly evaluation of the patient's history against a set of recommendations (which are often expressed by the experts in non-computable forms). A patient may have a history of 2 doses of MMR but if the first one was given too early (prior to 12 months of age), then they may only have one valid dose and the recommendation would be for the second valid dose in a 2 dose series even though it would technically be the third dose given to the patient.

@Nathan Bunker might have input too.

view this post on Zulip Lloyd McKenzie (Dec 04 2019 at 14:53):

If you're not doing math or comparisons, then making it an 'int' may be unnecessary. However, still wondering whether 'code' might be a better choice for interoperability purposes?

view this post on Zulip Lloyd McKenzie (Dec 04 2019 at 14:54):

Presumably the number of relevant integers would be relatively small? What's the maximum int would could ever expect to see? Are there non-int values other than 'booster'?

view this post on Zulip Craig Newman (Dec 04 2019 at 14:56):

I'm not sure we could come up with a set of codes. The largest number of doses is probably in the 7-10 range for tetanus, diphtheria and pertussis (although many folks will separate those vaccines into separate childhood and adolescent series). "annual" for flu is the the only other non-number concept that comes to my mind and even that one isn't quite as common as boosters.

view this post on Zulip Craig Newman (Dec 04 2019 at 14:57):

I feel like these concepts are more for humans than computers as the CDS engines will likely do their own evaluations and come up with their own numbers.

view this post on Zulip Nathan Bunker (Dec 04 2019 at 14:59):

I would agree with Craig. The concept of "dose number" is not something that would ever be computable. For immunization purposes the ideal solution for these fields would be that they were strings. We don't want systems making computations off of them, but rather just display this to an end user. Saying they are due for TD #10 doesn't make sense to an end provider. But saying they are due for TD Booster does.

view this post on Zulip Nathan Bunker (Dec 04 2019 at 15:03):

In other areas the concept of dose number might be more computable, such as when you are giving a very defined series of medications in a week. But for immunizations the next dose number is really just a milepost for the clinician to help them understand where they are in the series. The actual dose number really doesn't matter, and doesn't affect clinical care. The rest of the recommendation contains the details of which vaccination to give and when and these are the ones that need to be computable.

As for making the concepts codeable, I'm not sure this would be a useful exercise. We don't know of any business rules that would be triggered that would require a standardized set of codes. For our business purposes simply having a human-understandable string works very well.

view this post on Zulip Lloyd McKenzie (Dec 04 2019 at 15:06):

So I guess the question then is what the reason is for introducing positiveInt?

view this post on Zulip Nathan Bunker (Dec 04 2019 at 15:12):

Good question. I don't know the history on this resource. But often the value is a positive number and often it is clinically thought about in this way. So then it often gets modeled as such. This is probably an over-simplification during modeling.

view this post on Zulip Vassil Peytchev (Dec 04 2019 at 15:16):

The concept of "dose number" is not something that would ever be computable.

Aren't there cases where it is computable? If you have guideline that says "Children after 1 y.o. need vaccine X administered in 3 doses", don't you want to record that an administration is dose 1 or 2 or 3?

view this post on Zulip Nathan Bunker (Dec 04 2019 at 15:22):

In practice we don't look at the dose number when computing which doses are due next. Instead we look at the entire history and determine the next dose needed. Even if a clinician said "I gave dose 2" we would not then recommend dose 3. Instead we look at the entire history and determine what is needed next. Sometimes we know about additional doses the clinician didn't know about, so the dose she gave was actually dose 3, not dose 2. Or too much time has passed and while that was dose 2 administered the patient is too old to get dose 3. So we are now moving on to the booster dose. So in practice if our systems get the dose number reported we completely ignore it. It's actually useless to know what dose the clinician thought they were giving, all we care about it which vaccines were given when.

view this post on Zulip Nathan Bunker (Dec 04 2019 at 15:25):

The only place we use dose number is when telling a clinician to give a vaccination. They understand the significance of the dose numbers and where they are in a vaccination series. So if we say the patient is due for dose 4 of 5. This makes sense to them and helps them understand what we are recommending. This is important because there actually may be 5 doses already on the record, but maybe third dose listed wasn't a valid dose, because it was given too soon. So then the clinician can see that and realize that while the patient did get the 5 required doses, one of them didn't count. Then they will accept the clinical decision support and do an extra dose.

view this post on Zulip Nathan Bunker (Dec 04 2019 at 15:26):

But at no time would this dose "number" be computed. And it is not needed to store on the vaccination record.

view this post on Zulip Vassil Peytchev (Dec 04 2019 at 15:34):

This seems to be assuming that "the vaccination record" only exists on one centralized location. If you have multiple vaccination records, wouldn't you want the information stored?

view this post on Zulip Craig Newman (Dec 04 2019 at 15:47):

Also keep in mind that it's all relative to the set of recommendations being compared to. And those are fluid to some extent. Not only do recommendations change, but even within a single static set of recommendations, the number of doses can change as the the patient ages and doses are or are not given. What may start out as a 4 dose series may change to a 3 dose series if the first dose isn't given on time. Or if you compare to a different schedule, the evaluation may be different.

view this post on Zulip Craig Newman (Dec 04 2019 at 15:50):

The only way you can give an accurate evaluation/forecast is if you have access to the compete history which may be spread over multiple system (but hopefully that is less and less the case as more providers contribute to and query IIS). If the system is spread out over multiple systems, the dose number becomes less and less reliable. If Doctor Jones gives the patient the first dose and Doctor Smith gives the patient the second dose and each record the doses in their respective (non-interoperable) systems, each one may flag their dose as "dose 1 of x" but clearly that's wrong.

view this post on Zulip Nathan Bunker (Dec 04 2019 at 15:56):

Just to echo what Craig said the dose number is a very fluid concept and changes with time. I did make a comment that implied a central location. Vaccinations are always given with the idea that the clinician has complete knowledge of the entire vaccination history. In the past this was done with a paper immunization "shot card" that recorded the basic information needed for the clinician to determine the proper next dose to give. Today this is done in a central immunization systems and/or EHR record. Wherever this happens the goal is the same, to get a complete history. The dose number is just an output of the recommendation engine at the point in time when recommendations are requested. It helps orientate the clinician to the results. But there is nothing that can be computed from the dose number and no decisions can be automated.

view this post on Zulip Thomas Beale (Dec 04 2019 at 16:07):

The concept of "dose number" is not something that would ever be computable.

Aren't there cases where it is computable? If you have guideline that says "Children after 1 y.o. need vaccine X administered in 3 doses", don't you want to record that an administration is dose 1 or 2 or 3?

in openEHR we wouldn't rely on any '1 of 3' data, we'd query the EHR and find out how many previous admins had been recorded (and probably the docs would double check with parents etc).

view this post on Zulip Lloyd McKenzie (Dec 04 2019 at 16:10):

They might not all be reflected in the EHR.

view this post on Zulip Nathan Bunker (Dec 04 2019 at 16:36):

That's what I'm worried about. You shouldn't query the EHR and count doses, because you won't know which vaccinations really count. Both because there are different types of combinations that count or don't count and also because which doses count depends on the age of the patient and what other vaccinations were given when. There might be 3 perfectly good doses on the EHR record but the second one might be given too close to another vaccination so it doesn't count and has to be repeated. You need a CDS engine to evaluate the history and come up with a recommendation. Then tell the user that "today the patient is due for dose 2 of 3".

Ideally we would just be reporting back a string that was human readable but not computable.

view this post on Zulip Thomas Beale (Dec 04 2019 at 17:16):

Indeed you can't trust that everything that happens in the real world turns up in the EHR. For that very reason, you will hardly see any data of the form '1st of 3' in any computable form, precisely for the reason that the patient might have moved, the system might have changed, all the usual reasons. But, if (say) 2 doses have been actually recorded and all the relevant details are there, you certainly know that those two shots have been done. So any data or person saying they are not sure, or that it was only one is probably wrong. You should always query the EHR; it's just a question of how you treat the results.

view this post on Zulip Grahame Grieve (Dec 04 2019 at 17:23):

having read the discussion, especially the 'this is not computable' comments, there's no reason for Immunization.protocolApplied.doseNumber to be anything but a string.

OTOH, The discussion never even came close to justififying the existence of Immunization.protocolApplied.seriesDoses at all, let alone that it is a choice of positiveInt | String

view this post on Zulip Craig Newman (Dec 04 2019 at 17:40):

From previous discussions with you, I thought that Immunization.protocolApplied.seriesDoses (and honestly Immunization.protocolApplied.doseNumber too) existed so that the provider could record their intent (at the time of administration they gave the vaccine because (one way or another) they had determined that the second dose of a four dose series was needed). These two concepts exist on all 3 immunization related resources although how they get populated are different:
-Immunization is the provider intent or understanding at the time of administration
-ImmunizationEvaluation is the CDS engine determination after comparing the patient history with a set of guidelines
-ImmunizationRecommendation is also the product of CDS engine evaluation and forecast against a set of guidelines
The values of the elements in the Immunization resource should not impact the evaluation and forecasting process of the CDS engine producing the ImmunizationEvaluation and ImmunizationRecommendation.

view this post on Zulip Nathan Bunker (Dec 04 2019 at 17:54):

For the US, I'm not aware of any use case for Immunization.protocolApplied.seriesDose, except perhaps for trying to translate between HL7 v2 where the base standard does have this concept. (Although we constrained out this usage for US immunization reporting use.) It's possible that in some situations it might be useful to know what dose the clinician intended this administration to be. My guess is that this is a concept in FHIR as it was also a required concept in HL7 v2 and presumably is being used in other situations.

As for the other two Craig mentions on ImmunizationEvaluation and ImmunizationRecommendation: Being able to communicate the dose number is very helpful in providing context and orientation to the clinician. The decision making model is supposed to be joint so the CDS engine needs a way of communicating which dose it is recommending and the clinician needs to review this. But this information would not be used to plan out a series of events. You only give one dose in a series during an encounter and then the CDS process should be re-run for future encounters. The clinician might be told to give dose 2 of 4 today, and then in six months that the patient is now complete (because they are too old to get the dose 4). The only way to know if this is the case is to generate a CDS evaluation every time you look at the record and update the dose number recommendation.

So ideally these fields would just be strings so that downstream systems don't try to do anything computable with them.

view this post on Zulip Grahame Grieve (Dec 04 2019 at 17:54):

I commonly run into doseNumber in national registries. I don't recall whether seriesDoses was implied in the national program's approaches to me or not.

view this post on Zulip Nathan Bunker (Dec 04 2019 at 17:56):

You can often assume the seriesDoses because most vaccines have a standard series. But the schedules are getting more complicated and some formulations allow for completing series faster (3 doses instead of 4 doses). So for completeness it's best to mention both the dose number and how many doses are in the series, that way the receiver isn't making the assumption.

view this post on Zulip Nathan Bunker (Dec 04 2019 at 18:00):

In HL7 v2 we could receive a dose number, but in the US we have consistently ignored it when collecting for a registry. This is because we have to merge the records together and the clinicians often don't have the complete record. So what they call dose 2 might actually be 3.

And then it gets even worse. If the registry stores the dose number, is this the number of the dose for only the valid shots that count towards the series or all the ones given? Also, how to handle combination vaccinations where a component might count or not count towards the series completion? Registries solve this problem by not assigning a permanent dose number, but rather by assigning it in real-time when displaying or returning data.

view this post on Zulip Grahame Grieve (Dec 04 2019 at 18:03):

I think that this proviso should documented in the spec. I don't know how national registries resolve this, but they strongly think that the clinicians intent needs to be recorded

view this post on Zulip Craig Newman (Dec 04 2019 at 18:05):

It sounds like we are closing in on an agreement that for all 3 immunization related resources the doseNumber and seriesDoses elements should be string only (remove the option for positiveInt) and clarify the source/intent of the elements in each resource (that is, in Immunization, it's the intent of the provider in administering the vaccine and in the other two, its the outcome of the CDS engine evaluation of patient history against the specific set of recommendations it uses). If we can agree on this, I'll go ahead and create a Jira ticket.

view this post on Zulip Grahame Grieve (Dec 04 2019 at 18:12):

yes please do

view this post on Zulip Nathan Bunker (Dec 04 2019 at 19:53):

Craig, that sounds good. I can agree with changing them to string only.

view this post on Zulip Craig Newman (Dec 05 2019 at 18:47):

I created and triaged Jira ticket 25277. I'll get this on a Public Health WG call and let folks know when it will be discussed.

https://jira.hl7.org/browse/FHIR-25277

view this post on Zulip Thomas Beale (Dec 09 2019 at 16:04):

So... there's a whole lot of other strange combinations of choice[x] that I think need to be looked at. E.g. this one from Group: pasted image

view this post on Zulip Thomas Beale (Dec 09 2019 at 16:06):

That's a generic value, like Observation.value - another place that should just have DataType. I can't think of any reason why the choice there doesn't have Duration, String, or most other possible data types.

view this post on Zulip Thomas Beale (Dec 09 2019 at 16:17):

Another one from Dosage. How do the 'rate' choice types make sense? pasted image

view this post on Zulip Lloyd McKenzie (Dec 09 2019 at 16:31):

For Group, string isn't useful for computation. Duration wouldn't be meaningful without an anchor. In short, the types that are listed are the ones for which we have use-cases.

Rate can be 30ml/5min, or 3ml/min-4ml/min or just 3ml/min

view this post on Zulip Thomas Beale (Dec 09 2019 at 16:52):

In Group, aren't these any characteristics that act as criteria for inclusion? Are you saying that the only possible use cases have those particular data types? Duration comes to mind as a number of years of experience, for example.

view this post on Zulip Thomas Beale (Dec 09 2019 at 16:52):

It's hard to imagine that all the use cases are known, so it's pretty close to certain that that list of types is deficient.

view this post on Zulip Thomas Beale (Dec 09 2019 at 16:53):

No argument with those rates - they are all Ratios. So how do Range and SimpleQuantity work? Where is the denominator?

view this post on Zulip Lloyd McKenzie (Dec 09 2019 at 17:21):

Group is for subjects - typically veterinary, public health and research scenarios. "Years of experience" isn't something that's been identified as a criteria there. The set of data types we use is driven by "what are the types typically used by systems capturing inclusion/exclusion criteria for research for for public health outreach. (Veterinary use tends to be enumerated and thus doesn't much rely on criteria.)

view this post on Zulip Thomas Beale (Dec 09 2019 at 19:17):

Right - I was forgetting that. But nevertheless, I would be very surprised to learn that just the set of data types defined now for characteristics is the definitive set. Why not just leave it open, and allow implementers to use whatever data type that represents what they find in their system? (There is a different discussion to be had about what a 'Group' really is anyway, but I'll leave that for the moment).

view this post on Zulip Lloyd McKenzie (Dec 09 2019 at 19:22):

When we leave it open, that imposes a cost on developers. It's saying to them "most implementers support this, you should to" - when that's not true. We prefer to define the choices we have evidence are used and allow extensions and future evolution of the specification to handle new stuff/edge requirements.

view this post on Zulip Grahame Grieve (Dec 09 2019 at 19:22):

Why not just leave it open, and allow implementers to use whatever data type

Obviously that would be convenient for anyone writing content, or closed systems. But less convenient for someone writing parsing/processing code in an open environment, since they don't get to choose which data types to implement.

view this post on Zulip Thomas Beale (Dec 09 2019 at 20:04):

But with a standard set of Data type libraries, there's nothing special for any developer to do, as far as I can see. Just bring back the data.

view this post on Zulip Grahame Grieve (Dec 09 2019 at 20:15):

sure, if you're only fetching the data, and doing nothing with it. But, you know, mostly, people expect systems to actually do something

view this post on Zulip Lloyd McKenzie (Dec 09 2019 at 21:04):

I.e. user interfaces, database tables, appropriate indexing, conversion to and from other interfaces, etc.

view this post on Zulip Thomas Beale (Dec 10 2019 at 00:56):

I don't expect a FHIR interface to do anything for me. I expect it to get back some data to my requesting system, where the computation will be done. No-one's expecting the message layer to do anything semantic; they have analytics, reporting and applications to do all the work.

view this post on Zulip Grahame Grieve (Dec 10 2019 at 00:58):

No-one's expecting the message layer to do anything semantic;

You're wrong there. There are plenty of people who do. But you're just moving the deckchairs around anyway: if you get the data, you have to process it somewhere, and if you have to process it, you have to deal with what you got, somewhere, in some way

view this post on Zulip Thomas Beale (Dec 10 2019 at 01:05):

Sure, that's the business of the receiver system. But that's nothing to do with FHIR.

view this post on Zulip Thomas Beale (Dec 10 2019 at 10:02):

You're forgetting a basic fact - the receiver system / application has its own type system. FHIR is just a transport. If you wanted it to be something else, it would need a genuinely different type system (and modelling methodology).

view this post on Zulip Thomas Beale (Dec 10 2019 at 10:03):

I'd rather it focussed on just getting the messages and interactions right... and I think we have some way to go there.

view this post on Zulip Thomas Beale (Dec 10 2019 at 11:05):

For example, I think it is worth examining further your inverted list of choice[x] choices, and making more progress on special data types adapted to dealing with common combinations of alternate representations of things, as we have discussed. Just leaving those as open lists of types in my view is a case of moving deck-chairs around. Let's stack 'em up neatly instead and minimise the ad hoc approach to this whole thing. My theory is choice[x] can be gotten rid of completely.

view this post on Zulip Thomas Beale (Dec 10 2019 at 11:05):

You don't agree, but we can obtain very useful improvements in trying to at least minimise it.


Last updated: Apr 12 2022 at 19:14 UTC