Storing Patient Measurements
# support
m
I have a question regarding creating extensions for a patient resource. I have extra fields regarding a patients body composition like body fat percentage, and various body measurements that I intend on storing, and before i explore secondary solutions to store this data as well as utilize the clinical data repository medplum offers, I want to confirm if its possible to store such information through medplum and FHIR via extensions, and maybe a nudge in the right direction as to how I would go about doing so. Any resources that may help would also be great!
r
Hi @mikee6290 - these quantities are best stored as
Observation
resources. The
Observation
is a point in time measurement for some clinical quantity of a patient. Check out our guide here: https://www.medplum.com/docs/charting/capturing-vital-signs I think it will be very informative
m
Ah yea that makes more sense. I can add extensions to these resources as long as the extension URL is properly formatted, do i understand that correctly?
r
Yes that's true, but I believe you don't need extrensions for this. Can you clarify your use case for extensions?
m
I have body composition metrics I want to store for a patient upon registration, so Ideally storing it all within on clinical data repository is what I want, so i'm trying to see what I can store within medplum.
n
r
Yep this is exactly what medplum is for - storing all kinds of clinical data, including medical observations such as body composition metrics. Based on what you've described, I don't think you need extensions. The
Observation
resource is meant to record any "point-in-time" medical observation. This means you can use it to capture body-fat percentage, measured at a point in time
@node5585 in general we dissuade folks from using the multi-component observations, as it only really makes sense for things like blood pressure. Most of the time, you can create a new Observation for each measurement
n
Gotcha ya didn't know if there was a specific business rule for why these all needed to be stored in the same observation but if it's just a transactional requirement, the bundle transaction could be used to persist all atomically
m
Oh this isperfect, I am looking more into this right now. Thank you guys for pointing me in the right direction!
n
Read a bit into the fhir-router code and not 100% sure about the atomic guarantees that it currently offers with Bundle transaction. The transactional nature may or may not be an issue for your use case but thought I'd share. https://discord.com/channels/905144809105260605/1113936455954346005/1172295200719261737
m
So far what i gathered is i can have 10 body composition metrics put into the Observation resource for a patient due to the scope of the LOINC codes that are necessary. I am trying to shape my resource like the multi-component Observation resource structure from the medplum docs, and currently trying to fit the values in there. If I have these 10 metrics can i fit it all into one medplum.createResource function call. Im currently working on that but if there is a more standrd/efficient way I'd love to do it that way.
r
@mikee6290 it would be better to store those as 10 different Observation resources, each with their own LOINC code and observation
If there's a concern about about making too many HTTP requests, then @node5585 's suggestion about using Batch requests is applicable here. https://www.medplum.com/docs/fhir-datastore/fhir-batch-requests#creating-internal-references
m
That is the conclusion I came too and im setting it up that way now.
Oh then with the above approach, making it a batch request i can do it on one api call?
r
Yep, you can use a batch requests to create all 10 measures in a single HTTP request 🙂
m
Handy! Thanks Rahul, appreciate the help, will set up the api request right now.
r
Also consider the "autobatching" feature. Very useful for front-end calls
m
Hey guys, I am setting up the Bundle request for the Observation resources and i am getting the following errors and by the looks of it it seems like its all set up correctly, so if there are any suggestions to point me in the right direction as to why im getting this error, It would be a greatr help. This is in relation to what I was talking about above. The height and weight values are present and so is the patientId I'll provide the code as well as the query. Error:
>>>>> Error: Constraint bdl-8 not met: fullUrl cannot be a version specific reference ({"fhirpath":"fullUrl.contains('/_history/').not()"}) (Bundle.entry); Constraint bdl-8 not met: fullUrl cannot be a version specific reference ({"fhirpath":"fullUrl.contains('/_history/').not()"}) (Bundle.entry); Missing required property (Bundle.entry.resource.code); Invalid additional property "resource" (Bundle.entry.resource.subject.resource); Invalid additional property "coding" (Bundle.entry.resource.coding); Missing required property (Bundle.entry.resource.code); Invalid additional property "resource" (Bundle.entry.resource.subject.resource); Invalid additional property "coding" (Bundle.entry.resource.coding)
Setup of the query:
Copy code
const login = medplum.getActiveLogin();
const patientId = login?.profile.reference?.split("/")[1];
Copy code
await medplum.createResource({
          resourceType: "Bundle",
          type: "batch",
          entry: [
            {
              resource: {
                resourceType: "Observation",
                status: 'preliminary',
                coding: [
                  {
                    system: "http://loinc.org",
                    code: "8302-2",
                    display: "Body Height",
                  },
                ],
                subject: {
                  resource: {
                    resourceType: "Patient",
                    id: patientId,
                  },
                },
                valueQuantity: {
                  value: Number(firstQuizData?.height),
                  unit: "cm",
                  system: "http://unitsofmeasure.org/",
                  code: "cm",
                },
              },
              request: {
                method: "POST",
                url: "Observation",
              },
            },
            {
              resource: {
                resourceType: "Observation",
                status: 'preliminary',
                coding: [
                  {
                    system: "http://loinc.org",
                    code: "29463-7",
                    display: "Body Weight",
                  },
                ],
                subject: {
                  resource: {
                    resourceType: "Patient",
                    id: patientId,
                  },
                },
                valueQuantity: {
                  value: Number(firstQuizData?.weight),
                  unit: "lbs",
                  system: "http://unitsofmeasure.org/",
                  code: "lbs",
                },
              },
              request: {
                method: "POST",
                url: "Observation",
              },
            },
          ],
        });
I fixed the coding section in each Observation resource to look like below so it reduced the size of the error message:
Copy code
code: {
         coding: [
               {
                  system: "http://loinc.org",
                  code: "8302-2",
                   display: "Body Height",
               },
            ],
         },
n
@mikee6290 seems like your method of creating
patientId
is not working as expected You should use
createReference
from
@medplum/core
Copy code
typescript
import { createReference } from '@medplum/core';

createReference(login.profile)
Also, I notice you're using optional chaining on the
firstQuizData
.. is height & weight optional? If they are, you may want to prevent creating the Observation if you don't have the data.
m
The optional chaining is a safety measure at the moment to prevent errors. The height and width data exist and will be available everytime this Observation resource will be made.
I'll try to use the createReference method as well. Never worked with it. Is it providing a patient Id as well?
Is the createReference method supposed to be an alternative for getting the patient id?
So i have some update on my situation. I seemed to have fixed all the above errors by simply changing up the way im structuring the subject in my query as well as including a fullUrl, however now i am getting this error which i believe has to do with an access policy potentially. If you guys have any direct solution as to why im getting this error based off of the query I sent, that would help: error:
>>>>> Error: Forbidden
query:
`try { const login = medplum.getActiveLogin(); const patientId = login?.profile.reference?.split("/")[1]; console.log(patientId); const result = await medplum.createResource({ resourceType: "Bundle", type: "batch", entry: [ { fullUrl:
urn:uuid:${patientId}
, resource: { resourceType: "Observation", status: "preliminary", code: { coding: [ { system: "http://loinc.org", code: "8302-2", display: "Body Height", }, ], }, subject: { reference: "Patient/" + patientId, }, valueQuantity: { value: Number(firstQuizData?.height), unit: "cm", system: "http://unitsofmeasure.org/", code: "cm", }, }, request: { method: "POST", url: "Observation", }, }, ], }); console.log(result); } catch (error) { console.log(">>>>>", error); }`
r
@mikee6290 I think you're right that this is an access policy issue. Would you mind sharing the Access Policy that is applied to this client? That can help me debug
m
Sure, its our defaultPatientAccessPolicy. It looks like this:
Copy code
{
  "resource": [
    {
      "resourceType": "Patient"
    },
    {
      "resourceType": "Observation"
    },
    {
      "resourceType": "StructureDefinition"
    },
    {
      "resourceType": "SearchParameter"
    },
    {
      "resourceType": "ValueSet"
    },
    {
      "resourceType": "Questionnaire"
    },
    {
      "resourceType": "DiagnosticReport"
    },
    {
      "resourceType": "MedicationRequest"
    },
    {
      "resourceType": "Coverage"
    },
    {
      "resourceType": "PaymentNotice"
    },
    {
      "resourceType": "CarePlan"
    },
    {
      "resourceType": "Immunization"
    },
    {
      "resourceType": "Communication"
    },
    {
      "resourceType": "Organization",
      "readonly": true
    },
    {
      "resourceType": "Practitioner",
      "readonly": false
    },
    {
      "resourceType": "Schedule",
      "readonly": true
    },
    {
      "resourceType": "Slot",
      "readonly": true
    },
    {
      "resourceType": "Binary"
    }
  ],
  "resourceType": "AccessPolicy",
  "name": "defaultPatientAccessPolicy",
  "id": "63847d87-2bb2-4c2a-85b5-4ef279c728e8",
  "meta": {
    "versionId": "a5216012-0c29-4b59-b46f-4b1876139f52",
    "lastUpdated": "2023-10-05T17:05:37.217Z",
    "author": {
      "reference": "Practitioner/6c14b083-2771-406c-9709-bd56e1869440",
      "display": "Michael Kleyman"
    },
    "project": "b0aa142c-85e2-4fb3-a62c-fe949a0b0573",
    "compartment": [
      {
        "reference": "Project/b0aa142c-85e2-4fb3-a62c-fe949a0b0573"
      }
    ]
  }
}
r
Based on this AP, I don't see why this would block your batch request. Could you try it without the
fullURL
property. This should be unecessary. Since it's referring to a Patient, this might be the cause of the error?
m
My reason for putting it there was due to the error i was getting below, so when putting the fullUrl property I was getting rid of that error: Error:
Error: Constraint bdl-8 not met: fullUrl cannot be a version specific reference ({"fhirpath":"fullUrl.contains('/_history/').not()"}) (Bundle.entry); Constraint bdl-8 not met: fullUrl cannot be a version specific reference ({"fhirpath":"fullUrl.contains('/_history/').not()"})
This is the response from the network tab as well when removing the fullURL: { "resourceType": "OperationOutcome", "issue": [ { "severity": "error", "code": "invariant", "details": { "text": "Constraint bdl-8 not met: fullUrl cannot be a version specific reference" }, "expression": [ "Bundle.entry" ], "diagnostics": "{\"fhirpath\":\"fullUrl.contains('/_history/').not()\"}" } ], "extension": [ { "url": "https://medplum.com/fhir/StructureDefinition/tracing", "extension": [ { "url": "requestId", "valueUuid": "7e117723-f70f-44a9-a1c4-449017a0cb2c" }, { "url": "traceId", "valueUuid": "5fe2e6ec-17d2-44a9-a8ba-228798c120d4" } ] } ] }
r
Hi @mikee6290 , I think I see the problem. It seems like you are trying to "store: the bundle, rather than execute it as a batch operation
Were you able to refer to the guide here? https://www.medplum.com/docs/fhir-datastore/fhir-batch-requests The appropriate SDK function would be
executeBatch
, not
createResource
m
I will go and try replacing that sdk function right now, I guess it was an oversight on my end, got so used to using createResource haha.
r
🙂
m
Worked!
Appreciate the guidance !!
@rahul1 I have a followup question to all of this. In addition to making an Observation resource for something like Body Fat Percentage that does have a LOINC code, can i do the same for something that doesnt have a LOINC code like water weight or Adjusted BMI?
r
Yes of course! Using LOINC is a good data hygeine practice, but not required. You can make up your own code value, (e.g
body-water-weight
). Just make sure to use a
system
string that reflects that this is an "internal code" (e.g . http://hilohealth.com)
For more clarity on the system string, check out this blog post: https://www.medplum.com/blog/demystifying-fhir-systems
m
Oh awesome thats perfect!
Can you explain just for clarity what the system string is? I see you used a domain as an example. Is that what typically a system string in this scenario supposed to be?
I read through the blog post you sent. If we dont have a production domain yet, is it possible to have a localhost as a system string while in development?
r
The URL strings are non-functional, so they can really be anything you want. The purpose of the URL format is really just to enforce that it's a globally unique namespace. Given that, I'd avoid localhost, but anything that you antcipate being your product domain would be better. You'll have to change
localhost
anyway, so might as well use something that's closer to the real domain
m
Ah ok I understand. Is that the same idea for the urls that go in extensions? I was always a little confused on that.
r
yep, same idea
128 Views