Cht-core: Display additional information in contact profile

Created on 14 Nov 2016  Âˇ  24Comments  Âˇ  Source: medic/cht-core

Some use cases and workflows require additional information to be displayed in the contact profile. For example, seeing a woman's EDD on her contact profile or knowing if her pregnancy is high-risk by looking at her profile. Another example would be showing a child's height and weight for growth monitoring for malnutrition.

This should be configurable, so we can adjust what displays in the contact profile on a per-project basis or based on the type of contact profile we are viewing.

Feature

Most helpful comment

This is really cool!
Good job @sglangevin and @abbyad and @guptasanchay for the thorough and helpful input, @SCdF for nailing all the design to the end, and @garethbowen for implementing it.

All 24 comments

Information like the EDD could then be passed to the action context predicate to enable showing actions based on status.

Has anyone got a clear idea of how this should be configured, or any examples apart from EDD?

I have some ideas, happy to chat about it. Other examples would be patient height and weight for growth monitoring (malnutrition) or a list of immunizations received by a child. The main piece is just being able to pull in data from previous reports to display in the contact profile.

The way I've been thinking about it is to be able to configure certain fields from a report xform to store in the patient's contact doc, making them available for display, setting form context or for pulling into a later form.

@abbyad may have thoughts as well.

Mockups

Here are some mockups, showing some different configurable subgroups:

image

image

image

Configuration

Since we want these modifications to be done even completely offline, we'll need to rely on XForms and not transitions. As a result, we'd want to be able to specify which fields should go to the person or place's profile in the XForm, using a special trigger group (could be named action, but thought that would be confusing since we refer to "Actions" in the app).

For example, a form could have a person (or place) group inside an trigger group where all sub fields would update values for matching keys in the profile for the person (or place). The top level fields would correspond to top level fields, and those in further subgroups would be grouped accordingly in the contact's doc. These would then be represented as groups like Immunization or Pregnancy in the mockups above. From my understanding there could 0 to n groups on a single profile, limited by project design considerations.

XForm

For instance, a form would have:

  • trigger/person/_id used as a reference to the person to update. We'd obviously not allow updating the _id, but could we would use NEW for creating a person using the same configuration notation (#2912).
  • trigger/person/date_of_birth create/update a value in top level field
  • trigger/person/pregnancy/edd update a specific value in a subgroup

Profile

On the profile we'd list all fields accordingly in the profile, and grouping those from subgroups. It is not an immediate requirement to show/hide specific fields on the profile, but that is likely to be requested at a later time. When we do this we'd want to do it similarly to the way we specify it for report fields on the Reports tab.

Labels

We can configure projects to have labels for the profile fields based on the paths of the fields, like we do for a report in the Reports tab. Eg
"trigger.person.pregnancy": "Pregancy"
"trigger.person.pregnancy.edd": "Expected Date of Delivery (EDD)"

Two more things it might be worthwhile to consider after chatting about this more with @sglangevin and @abbyad:

  1. It seems like most fields for the different "configurable subgroups" are coming from patient forms, not contact forms. What special considerations would need to be made around this feature in order to enable displaying additional information from a contact form like 'Add Person' in a configurable way? And if a contact was later edited ('Edit Person'), would the updated value be reflected on the profile?

  2. Some information from the 'Add Person' form (i.e. info about a child's chronic illness) may be better to include in the top section of the profile page, which contains static information about a person like Name, Age, and Gender. It's worth considering what types of info should be in the top profile card vs. the other card for growth monitoring/pregnancy info/etc. Could the top card have configurable fields for profile info?

Statically setting values on the report has a couple of downsides we might wish to consider:

  • It means we're constantly re-writing a doc that we don't necessarily have to re-write
  • There may be use cases where this strategy is too brittle to implement them properly

That last point is really important IMO. We should come up with as many realistic and diverse examples as possible, to make sure that we can cover these with this system. There are things I can think of that might be challenging to do in this system safely, but I don't know if they are ever relevant in real world cases. Think any kind of "off by one" style of error, or anything where the answer dynamically changes over time without user input. Examples:

  • Off by one: you want to count how many pregnant women a CHW is managing. Every time you record a new pregnancy, you add 1. Everytime they give birth you subtract one. However, there are multiple different reports you might want to fill out that count as a subtraction, and it is valid to fill out more than one for the same person. You can no longer just negate the value safely.
  • dynamic change: You want to show how many women under a CHW have given birth in a facility in the last 6 months. You have to re-calculate this every time you view the CHW, it can't be statically written to them easily.

The more flexible approach would be to have form designers write code that pulls this related data in real-time when you view the person, instead of statically writing it to said record. The downside is that they are writing code, but then again we already expect people to be able to do that with Nools. It would suffer far less from potential "off by one" errors.

@SCdF I noticed that the examples you are citing are related to aggregate numbers of the work a CHW is doing. This issue was created to help keep track of current data, not data over time. So, for example, having a way to store a woman's EDD and display that on their profile or storing a child's heigh and weight and displaying that on their profile. The data we're talking about is individual patient information as opposed to aggregates and we're talking about specific points in time as opposed to tracking over time. So I wouldn't expect this feature to support either of the use cases you mentioned. Those would be handled with analytics.

One use case that might be relevant when thinking about "off by one" errors would be tracking the number of ANC visits a particular woman has had, but there is only one form that would add to that number and there are no subtractions. The number of ANC visits would just be cleared when the woman delivered.

I'm not sure what you meant by "have form designers write code that pulls this related data in real-time when you view the person". Are you saying that we would have to create a form that then reviews all of the forms about a person before displaying their profile?

I noticed that the examples you are citing are related to aggregate numbers of the work a CHW is doing. This issue was created to help keep track of current data, not data over time

As you say, ANC visits is an aggregate though right? As are how many immunisations they've had.

Looking at the examples above, I'd also be concerned about how you'd implement the chronic disabilities example. I.e. would that be a string that gets composed out of many different forms that get filled out over time (i.e. people don't always have the disability, they get added over time). Pregnancy risk could be similar.

I don't want to make this more complicated than it needs to be, which is why I'm interested in a list of use cases and how people think they would be implemented. It may be that we can just make some really simple rules that cover everything we want, i.e.

  • Can only be applied to patients (so only one person, their CHW, will ever write to that patient record)
  • Can only be writing a fixed known value to the patient (eg their height or weight)
  • Or incrementing or decrementing a value by 1.
  • .. or something.

@SCdF , here’s a list of use cases I can think of for malnutrition and how I think they’ll be implemented. I’ll re-paste the mockup from earlier to help illustrate the examples:

image

  1. Point to a field from a patient form (like an ‘Assessment’) submitted by a CHW. This should come from the last form submitted for that patient, so if a child gets three assessments over the course of 3 months, the value displayed should come from the third assessment.

Examples: In the Growth Monitoring card above, “Last Visit”, “Weight”, and “Height” will all come from the most recent child assessment form. Each will basically just be a read-out of a single calculation, string, date, integer, etc.

  1. Point to a field from a patient form submitted by a Branch Manager. There are some cases where a CHW’s supervisor must make an important decision about a patient after reviewing the assessment the CHW conducted, like deciding whether they should be given food supplement or whether they should continue to receive assessments. This information should come from the last submitted form for that patient, so if a Branch Manager assigns food supplement on three separate occasions, the value displayed should come from the third action.

Examples: In the Growth Monitoring card above, “Supplementation” will come from the most recent “Assign Supplement” form and “Status” will come from the most recent “Assign Status” form, both of which are actions that a Branch Manager would take. Each will basically just be a read-out of a single calculation, string, or select_one option.

  1. Point to a field from a contact form (like ‘Add Person’) submitted by a CHW. If that form is later edited (‘Edit Person’), then the value displayed should come from the last ‘Edit Person’ form submitted for that patient. It would be nice to have information like this stored at the top of the profile with the other contact information like Name, ID, Age, etc., but if that is too difficult then down below is completely fine.

Example: In the Growth Monitoring card above, “Chronic Illness or Disability” will come from the ‘Add Person’ form. If a child has no chronic illness or disability when he is first registered, but the CHW later submits an ‘Edit Person’ form and changes the answer to “Asthma,” the profile should then be updated to show asthma. This will either be a string or a select_one option. To answer your earlier question Stefan, this wouldn’t be a string that gets composed from many forms but rather a single string from the latest edit person form.

These are the examples I can think of that are relevant for projects we are already deploying or that are critical for projects we are deploying very soon. I’ll loop back if I can think of any other use cases we might run up against in future malnutrition scenarios.

I’ll let others add in other examples before we synthesize into rules that cover everything we want.

@SCdF I think your assessment is mostly correct:

  • Can only be applied to patients (so only one person, their CHW, will ever write to that patient record)

A CHW or Nurse/Branch Manager could submit a form about a person - but in all cases they are submitting a report about a single person.

Can only be writing a fixed known value to the patient (eg their height or weight)

Or incrementing or decrementing a value by 1.

Correct, with our planned uses we'll be writing a string (eg new score, or value with units, or adding visit), or clear the value (eg a new pregnancy report clears the number of ANC visits back to 0). The math or string manipulation can happen in the form even if previous values are needed, because fields on the person's profile would be included as input for the form. This means that a simple increment is easy to do in the XForm - which is probably easier than having different trigger types.

That said, there are still some things for us to consider:

  1. Even though we want to display some fields, there are cases where we'd want to hide some fields as well. For instance, although we are displaying the weight as a string "12.1KG (Grade 2)", we may want to also store the values 12.1 and 2 as separate fields to be used next time in a form's calculation. We'd need a way to specify that a patient field should be hidden.
  2. The more complex dynamic stats or analytics for each person/family's profile is an interesting feature request, but not one we need to tackle for known 2017 projects - but something we can keep in mind for 2018 projects. I think we should only consider doing this now if it is easier to configure, or also helps tackle the next point.
  3. We may eventually need similar functionality for SMS reports. Following the current way we deal with SMS reports, this could be done as a transition for a Patient Report, where we specify the person's field that needs to be set and an expression to determine the value.

@abbyad I agree with your comments. In terms of adding this functionality for SMS reports, what happens once SMS reports are being sent via task rules?

In terms of adding this functionality for SMS reports, what happens once SMS reports are being sent via task rules?

Curious about @garethbowen's thoughts on this, but I think I recall him saying that the transitions in app_settings would live on.

@abbyad I've talked to @garethbowen about approaches. From a technical stand point we'd like to look at extending nools to be used for this. Effectively configurers would write rules that emit values against the thing (emit visit against a patient id for example), and we would provide a service that found those and exposed them to the form in an understood way. This would also be available to the context, solving #2913.

Upsides is that you don't get unsolvable off by one / ordering / static errors in the future, because if there is a bug in configuration you just get the fix and refresh. The (potential) downside is that only people with the data to generate this summary data will be able to generate and thus see it. For example, admins / branch managers wouldn't be able to see how many visits a patient has had[1]. This is solvable in the same way that we're considering things like targets to be solvable, either by running nools on the server and making it online-viewable, or by generating summary docs at lower levels and pushing them upwards.

Let us know when you've got time so we can chat about it on a call.

[1] if they can even see the patient: I'm not sure if we've decided how much information people other then CHWs can see offline yet?

@SCdF I'd be interested in chatting with you guys about this as well if you don't mind including me in the call.

Sounds like an interesting approach that will be more powerful in the long run. Let's chat with Sharon about this as soon as possible.

Talked with @garethbowen, @abbyad and @sglangevin. We've decided to not bother with Nools for this because we're not really getting anything from it.

Instead we'll do something similar: the code they write will accept a list of documents[1] (as opposed to the similar but different nools structure), and we will run that code ourselves "manually" when the contact page is loaded. This can be extended cleanly in the future when we need to support summary documents (eg branch managers etc) by having something that watches the changes feed and runs the code for the relative contact and saves the outputted block if it changes.


In more boring technical detail (IMO, @garethbowen fight me ❤️ ):

  • Tech leads / configurers will write a code block in the app config. This will be a function that takes a list of documents[1], and is expected to return a JSON object with two top level properties, context and display (or something, names tbd)
  • context is for extra arbitrarily structured data you want to expose to the context code block to allow it to make decisions, but you don't want to display it on the page
  • display should be a JSON object structured in the same way you'd like it to be displayed. It can support two layers, the section and then the value. The third layer can be used as key/value pairs to fill out complex translation keys. Example below.
  • When a user loads a contact page, if there is one of these code blocks, we use a view to load all reports for that contact, and then pass them and eval[2] that code block in a promise.
  • Upon receiving the JSON structure we combine it and pass it to the configured context function
  • We also take just the display portion, and generate the extra UI sections from it.

Example, taken from the immunisations example above.

The code block would return something like this:

{
  display: {
    immunisations: {
      dateRecorded: 1235245322, // eg ms since 1970
      immuComplete: {
        bcg: 1,
        oralPolio: 4,
        penta: 3,
        pneumo: 3,
        rota: 2,
        measles: 1
      },
      immuPending: {
        measles: 2,
        vitA: 10
      }
    }
  context: {
    // maybe some stuff here
  }
}

The configurer would also provide translations like this:

prefix.immunisations = "Immunisations"
prefix.immunisations.dateRecorded = "Date Recorded"
prefix.immunisations.dateRecoded.value = "{{0 | formatDate}}"[3]
prefix.immunisations.immuComplete = "Immunisations Complete"
prefix.immunisations.immuComplete.value = "BCG ({{bcg}} of 1), Oral Polio ({{oralPolio}} of 4), etc etc"
prefix.immunisations.immuPending = "Immunisations Pending"
prefix.immunisations.immuPending.value = "Measles ({{measles}} of 2), Vitamin A ({{vitA}} of 10)"
  • prefix tbd
  • the rest of the path is just the json path
  • .value is the translation key for the third-layer property. Note that the third layer can be a json object as well. If that's the case the keys are used in the translation key

[1] And the contact as well @garethbowen, for good measure / flexibility? We already have that loaded so it's free to pass.
[2] As scary as that is, I can't imagine how else nools is doing it.
[3] @garethbowen how smart can our translations get? Would we want to do this kind of thing in the code block, or leave it to the translations?

Looks pretty good. IMO display should be an array not an object to allow multiple sections with the same name...

1) Yes include the contact as well.
3) I don't think we can do date formatting in translations. We could either expose date formatting to the configurer to call and just give us a string, or try and do something clever to detect dates, numbers, etc.

But yeah, we pretty much agree :)

Looks good, with a minor area to clarify: the code the Tech Leads would write is for all person/place profiles... not _per form_.

Code review please @estellecomment : https://github.com/medic/medic-webapp/pull/3106 . It's rather large and also solves #3037 #2913 and #1930

Configuration documentation added here: https://github.com/medic/medic-docs/blob/master/md/config/contact-summary.md

For acceptance testing try out different configuration options and ensure that you can generate the whole range of data for display.

This is really cool!
Good job @sglangevin and @abbyad and @guptasanchay for the thorough and helpful input, @SCdF for nailing all the design to the end, and @garethbowen for implementing it.

Re: code review, my main complaint is the naming of fields in the object that the script returns, the rest is smaller things. https://github.com/medic/medic-webapp/pull/3106#pullrequestreview-20733429

I love your documentation! And I'll love it even more after you've added this million things I'm asking for : https://github.com/medic/medic-docs/commit/3c2a73ee58436acc6230728d79add04318b8f92a

Cool!

This is working well so far!

Profile fields and cards are configurable and display as expected in tests.

We should add examples in the documentation for the context section of the card configuration, as well as for the context passed to each form's expression.

Was this page helpful?
0 / 5 - 0 ratings