Skip to content
New issue

Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? # to your account

fhir: Resource builders #696

Open
josephjclark opened this issue Jul 26, 2024 · 2 comments
Open

fhir: Resource builders #696

josephjclark opened this issue Jul 26, 2024 · 2 comments

Comments

@josephjclark
Copy link
Collaborator

josephjclark commented Jul 26, 2024

This is a concept to make it easy to generate FHIR resources.

Builder Functions.

We can introduce "builder" functions which make it easy to create a FHIR resource. Every FHIR resource type should have a builder (of course we can phase them in as we need to - maybe we'd try and start with the level 3 concepts).

Builders are chainable and return a plain, serializable JSON object at the end.

The builders are semantically rich and backed by constants. They should be nicely compatible with the openfn style and have sensible defaults.

For example (super rough sketch):

patient($.data.id)
  .name("joe", "clark") // add a name
  .name("joseph", "clark") // add a second name!
  .gender(MALE) // set gender with a constant

The builders should be smart. Patients should be presumed alive. Calling .deceased() with an optional date would set the right internal fields on the patient object.

Builder functions would be backed by full type definitions, so you get full code assist in Lightning. This lets you build a resource without having the fhir docs open.

Each builder has a typed entrypoint like patient(id, data), where data is just a blob of fhir JSON that will be assignged by default (if you would rather use json than the helper functions, go for it).

Implementation Notes

The builders would have to use subclasses or mixins or something so that the fhir concepts can be layered

Eg you might have a Name builder and an Address builder, and the Patient builder subclasses or mixes in both of those. So we have a lot of internal compositionality.

Opportunity #1

If we release the fhir builders as a standalone package, they could be used by any Javascript package using fhir resources. The fhir npm package is pretty generic.

Opportunity #2

A lot of this stuff is fairly rote, I think. Could we use AI to generate at least a starting point?

@github-project-automation github-project-automation bot moved this to New Issues in v2 Jul 26, 2024
@josephjclark
Copy link
Collaborator Author

josephjclark commented Aug 2, 2024

WIP Detailed pitch

I have been using AI this week to have a crack at a standalone fhir-builders package (unrelated to openfn) to understand the cost and size and feasibility of it.

My hope is that I can make a core solution - a pattern - and use AI to scale it up.

I'm going to have a crack at documenting the work here in this comment, then I can move it up into the main issue.

API

I've set up a good basic pattern for the API with strong type support.

One thing I've realised with the chaining design is that some chaining will have to be scoped.

So you can do patient().name().name(), where name will return the chain for the top level.

But some things are deeply nested, like contract.term(), where term needs to return a different chain, a scoped chain.

So as well as supporting nested chains, I wonder if we need a .out() or .back() or .root() to let you traverse back out of the chain you're in.

Do these scopes provide .json() calls? And if so does that return json for the whole thing?

Types

Bit of a sticking point here.

There is a @types/fhir package which we can build on. Hurrah!

Frustratingly though, every property is duplicated with an underscore, so the code assist experience is terrible:

Screenshot from 2024-08-02 09-33-33

I am not sure what to do about this. We may need to duplicate the types for a good UX

Versions

The version problem is abig one.

The builders need to be version aware.

My dream API would be this:

patient({}, '4') // this will create a v4 patient
  .toVersion('5') // this will auto migrate the patient to v5

Internally, we'd have to have a builder for each version. This ideally can be a re-assembly and override of the v1 )(or maybe we start at v5?). Each version also needs migration functions to the version either side, so you can migrate from v5 to v1 by recursively walking the stack.

It's just a lot of work. I don' t know if AI can scale it.

Here is a diff from v5 to v4 https://hl7.org/fhir/R5/diff.html

Documentation

The addition of all these builders is going to add a lot of noise to the docs.

Basically for each of the hundred or so Fhir Resource types, we now have a helper function (not an operation) to build it.

I don't want to document these indivdiually. I think we should expose a builder or b namespace, so you do b.patient(). And we document that namespace as a whole with examples.

The idea really is that every fhir resource has a builder function, with the same name, and full code assist when you start coding the mappings. So it shouldn't need a lot of docs.

@josephjclark josephjclark moved this from New Issues to Backlog in v2 Aug 14, 2024
@josephjclark josephjclark moved this from Backlog to In progress in v2 Aug 14, 2024
@josephjclark
Copy link
Collaborator Author

One of the biggest problems these resource builders are going to have to solve is different implementation standards.

Different FHIR implementations come with different extensions and standards built-in. Each resource will have some default fields and boilerplate content, and extensions are added along with codings to the standard types (eg, residential-type on address).

A perfect builder function would handle this for you. It would set defaults and apply coding rules - all the user has to do is provide the actual value.

So the builder would be like .address({ city:'addis ababa', residential-type: 'rural' }), and it would generate JSON like this:

"address" : [
    {
      "extension" : [
        {
          "url" : "http://moh.gov.et/fhir/hiv/StructureDefinition/residential-type",
          "valueCodeableConcept" : {
            "coding" : [
              {
                "system" : "http://snomed.info/sct",
                "code" : "224804009"
              }
            ],
            "text" : "Rural"
          }
        }
      ],
      "city" : "Addis Ababa",
  ],

Maybe it would even provide content assist on the value 'rural'. 90% of the JSON there is boiler-plate, and that's EXACTLY the kind of thing the builders are designed to help.

So the concept is sound. The problem is that generic helpers aren't much use - they have to be implementation specific.

Solutions:

  • Generate builders from a FHIR spec. I would love to do this. We probably need to establish the pattern first, but yeah this means generating a whole adaptor for a specific fhir implementation, like fhir-jembi.
  • Pass mappings and extensions into the builder. You could have like a define() function which defines the codeable concept extension for residentila-type. But it's a lot of setup to do in the job to get this coded up right

@josephjclark josephjclark moved this from In progress to Backlog in v2 Aug 16, 2024
# for free to join this conversation on GitHub. Already have an account? # to comment
Labels
None yet
Projects
Status: Backlog
Development

No branches or pull requests

2 participants