Expose child count to contact summary

One of our requirements is to show a conditional warning card when there’s a mismatch between the expected household member count and the actual count.

Adding an expected_household_member_count to the household create/edit forms is trivial and easily accessible in the contact-summary.js file. However, there doesn’t seem to be a way to access a contact.child.count that can be used for comparison.

At the moment, we’re considering appending and updating another variable, actual_household_member_count, to the household record with a custom sentinel transition. We’re not too well-versed in that space, so it would take a little time to find our way around.
As such, we’re wondering if there isn’t an easier way to expose this value.

As far as I know, we do not have any easy way currently to get data about the current contact’s children. However, I cannot see any major technical challenges to adding support for this functionality. I have logged this issue:

Interestingly, if you do not try to handle the case of loading the contact-summary data into forms (and are just worried about the contact summary cards, etc on a contact’s profile page) then I think the code to pass the children to the contact summary function would be trivial. Basically, at this point in the contacts effect, I believe the children contacts are already present on selected.children. You would just need to pass them through to the generatorFunction. As far as custom code goes, that would probably be easier to wire up then a Sentinel transition.


All that being said, one possibility (particularly on 4.7.0+ with this fix), is that the list of reports provided to the contact summary calculation includes the reports for all the person children as well. If you had a report doc that you know would always be written for each person (but only once), you could check the count of those in the list. (Or, even just filter through the whole list of reports and look for how many distinct contacts the various reports are associated with…)

1 Like

As always, @jkuester, you’re a legend.
We ended up getting it working after also updating the enkito.service.ts file and adding a linting exclusion rule to our newly exposed children property.

contact-summary.service.ts:

...
private getGeneratorFunction() {
    if (!this.generatorFunction) {
      const script = this.settings[this.SETTING_NAME];

      if (!script) {
        this.generatorFunction = function() {};
      } else {
        this.generatorFunction = new Function(
          'contact',
          'reports',
          'lineage',
          'children',
...
get(contact, reports, lineage, children, targetDoc?) {
    return this.ngZone.runOutsideAngular(() => this._get(contact, reports, lineage, children, targetDoc));
  }

  private async _get(contact, reports, lineage, children, targetDoc?) {
    if (!this.settings) {
      this.settings = await this.settingsService.get();
    }

    if (!this.visitCountSettings) {
      this.visitCountSettings = this.uhcSettingsService.getVisitCountSettings(this.settings);
    }

    const generatorFunction = this.getGeneratorFunction();
    const uhcStats = {
      homeVisits: await this.uhcStatsService.getHomeVisitStats(contact, this.visitCountSettings),
      uhcInterval: this.uhcStatsService.getUHCInterval(this.visitCountSettings)
    };

    const chtScriptApi = await this.chtScriptApiService.getApi();

    try {
      const summary = generatorFunction(contact, reports || [], lineage || [], children, uhcStats, chtScriptApi, targetDoc);
...

enkito.service.ts:

...
private getContactSummary(doc, instanceData) {
    const contact = instanceData?.contact;
    if (!doc.hasContactSummary || !contact) {
      return Promise.resolve();
    }
    return Promise
      .all([
        this.getContactReports(contact),
        this.getLineage(contact),
      ])
      .then(([reports, lineage]) => this.contactSummaryService.get(contact, reports, lineage, contact.children));
...

contacts.effects.ts:

...
private loadContactSummary(contactId) {
    const selected = this.selectedContact;
    return this.contactSummaryService
      .get(selected.doc, selected.reports, selected.lineage, selected.children, selected.targetDoc)
      .then(summary => {
        return this
          .verifySelectedContactNotChanged(contactId)
          .then(() => {
            this.contactsActions.setContactsLoadingSummary(false);
            return this.contactsActions.updateSelectedContactSummary(summary);
          });
      });
  }
...

contact-summary-templated.js:

...
const getChildCount = (items, key) => {
  for (let i = 0; i < items.length; i++) {
    const element = items[i];
    if (element.type.id === key) {
      return element.contacts.length;
    }
  }
  return -1;
};
// eslint-disable-next-line no-undef
const contactChildren = children;
// The above property is being custom supplied by us.
// We're required to check the enumerated household member count against the expected count.
...
const hhmCount = getChildCount(contactChildren,'hhm');
const cards = [
  {
    appliesToType: ['household'],
    appliesIf: () => hhmCount !== parseInt(thisContact.expected_member_count || 0), // Although captured as an integer, the `expected_member_count` value comes through as a string.
    label: 'Attention!',
    fields: [
      { label: `Please ensure all household members have been captured.\nExpected count: ${thisContact.expected_member_count}\nActual count: ${hhmCount}`, icon: 'icon-risk' },
...

Visual result:

Unfortunately I’m unable to deploy the code without that linting rule exclusion. Do you perhaps know if there’s some sort of polyfill or something that needs updating for that to work?

1 Like

Sorry for the dreadfully late response here. It has been one of those weeks!

I am glad you got this working! Regarding the linting error, I think the solution is probably a simple one. If you are using an eslint config similar to the default one you just need to include children in the array of globals and the linter should stop complaining…

1 Like

No problem at all! I hope this week will be much smoother.

Thank you for your guidance! That definitely solved it, we were using the default eslint config.

Purely out of interest sake:
I’ve noticed that some parts of the app use TS and others remain in JS. Is the app in the process of being converted to all TS or are some aspects of the app too difficult/time consuming to convert?

I believe that TypeScript was initially introduced because it was a requirement for moving from AngularJS to Angular. At that time, we “migrated” the webapp code to TypeScript, but did not have capacity to update the code to properly take advantage of TS features (which is why, if you read the code, it feels a lot like JS code that was just forced into a TS shape… :sweat_smile: ).

There is no goal of dropping everything to convert the rest of the cht-core codebase to TS since we would probably just end up with more janky TS code like we have in webapp. However, I think it is fair to say that developers at Medic are very interested in expanding the usage of TS in cht-core and improving the quality and effectiveness of the TS code. Perhaps the first step is the new TS lib we are adding. Having that in place is going to form a nice foundation we can start to build on (both in terms of having some patterns laid down for how our TS code should look as well as providing some actual value to the consuming code which can leverage the typed data coming out of cht-datasource…). The next step is probably going to be deploying some tsconfig in places like api and sentinel that would allow for JS/TS code to live side-by-side. Then we can gradually migrate more and more of the codebase, file-by-file, over to TS.

1 Like

Thank you for the detailed explanation! This is a very interesting topic, and the link provided was a fun read. Agreed. I hope to see more & more TS make it’s way into CHT over time, perhaps with some of those code docs sprinkled in :grin:.