avniproject / JSSCP

0 stars 0 forks source link

[JSSCP Support] Update rules to not use keyword "Class" #321

Closed nupoorkhandelwal closed 11 months ago

nupoorkhandelwal commented 11 months ago

Context

Support ticket - https://avni.freshdesk.com/a/tickets/3147

On program encounter cancellation visits are not getting properly generated and corresponding rule is failing with error message -174:5:invalid statement encountered.'

Recently in the product javascript engine was changed from jsc to hermes, and one of the quirks of hermes is that it does not support the use of "class" within an eval statement.

Acceptance Criteria

  1. Update the rule to not use the class keyword, and instead use var as done in the sample rule that Vinay has shared in the support ticket.
  2. All rule failures with error message -174:5:invalid statement encountered to be closed, making the rule failure telelmtry show 0 count for this rule.
  3. Using scripts confirm that none of the other rules in any other class keyword

Tech approach

  1. Update UAT with the prod bundle
  2. Reproduce the rule failure on UAT by cancelling one of the visits that is mapped to Program Encounter Cancellation Form on JSSCP.
  3. Replace the rule with suggested changes
  4. Test and see there are no similar rule failures any more, mark them closed
  5. Get the changes QA done and then move them to prod asap.
  6. Once cross check that no other rule uses class keyword on prod.
vedfordev commented 11 months ago

Developer Note:

Backup:
"use strict";
({params, imports}) => {
    const programEncounter = params.entity;
    const scheduleBuilder = new imports.rulesConfig.VisitScheduleBuilder({
        programEncounter
    });

    const hasExitedProgram = programEncounter => programEncounter.programEnrolment.programExitDateTime;
    const cancelReason = programEncounter.findCancelEncounterObservation('Visit cancel reason').getReadableValue();

    if (!hasExitedProgram(programEncounter)) {
        if (programEncounter.encounterType.name == 'ANC Clinic Visit' && cancelReason != 'Delivery/Abortion') {

            const nextANCDate = programEncounter.findCancelEncounterObservation('Date of next ANC Visit').getReadableValue();
          //  const edd = programEncounter.programEnrolment.hasObservation('EDD') ? programEncounter.programEnrolment.getObservationValue('EDD') : //imports.moment().toDate();
            const edd = programEncounter.programEnrolment.getObservationValue('EDD');
            const visitDate = programEncounter.earliestVisitDateTime || programEncounter.encounterDateTime;

            if (imports.moment(edd).isSameOrBefore(imports.moment(nextANCDate), 'date')) {
                if ((programEncounter.programEnrolment.hasEncounter('Delivery', 'Delivery')) === false)
                    scheduleBuilder
                        .add({
                            name: "Delivery",
                            encounterType: "Delivery",
                            earliestDate: imports.moment(edd).toDate(),
                            maxDate: imports.moment(edd).add(0, 'days').toDate()
                        });
            } else {
                scheduleBuilder
                    .add({
                        name: "ANC Clinic Visit",
                        encounterType: "ANC Clinic Visit",
                        earliestDate: imports.moment(nextANCDate).toDate(),
                        maxDate: imports.moment(nextANCDate).add(8, 'days').toDate()
                    }).whenItem(!_.isEmpty(nextANCDate))
                    .is.not.truthy;
                if (!_.isEqual(programEncounter.individual.lowestAddressLevel.name, 'Non programme village')) {
                    scheduleBuilder
                        .add({
                            name: "ANC Home Visit",
                            encounterType: "ANC Home Visit",
                            earliestDate: imports.moment(visitDate).add(1, 'days').toDate(),
                            maxDate: imports.moment(visitDate).add(8, 'days').toDate()
                        }).whenItem(!_.isEmpty(visitDate))
                        .is.not.truthy;
                }
            }
        }

      // if (programEncounter.encounterType.name == 'ANC Home Visit' && cancelReason != 'Delivery/Abortion') {
      //     const nextANCDate = programEncounter.findCancelEncounterObservation('Date of next ANC Visit').getReadableValue();
      //     const edd = programEncounter.programEnrolment.getObservationValue('EDD');
      //     if (imports.moment(edd).isSameOrBefore(imports.moment(nextANCDate), 'date')) {
      //         if ((programEncounter.programEnrolment.hasEncounter('Delivery', 'Delivery')) === false)
      //             scheduleBuilder
      //                  .add({
      //                      name: "Delivery",
      //                      encounterType: "Delivery",
      //                      earliestDate: imports.moment(edd).toDate(),
      //                      maxDate: imports.moment(edd).add(0, 'days').toDate()
      //                  });
      //      } else if (!_.isEqual(programEncounter.individual.lowestAddressLevel.name, 'Non programme village')) {
      //          scheduleBuilder
      //               .add({
      //                  name: "ANC Home Visit",
      //                  encounterType: "ANC Home Visit",
      //                  earliestDate: imports.moment(nextANCDate).toDate(),
      //                  maxDate: imports.moment(nextANCDate).add(8, 'days').toDate()
      //              }).whenItem(!_.isEmpty(nextANCDate))
      //              .is.not.truthy;
      //      }
      //  }

        if (programEncounter.encounterType.name == 'Abortion followup') {
            const visitDate = programEncounter.encounterDateTime || programEncounter.earliestVisitDateTime;
            const dateOfAbortion = programEncounter.programEnrolment
                .getObservationReadableValueInEntireEnrolment('Date and time of abortion');
            const encounterScheduleAbortion = [
                {"name": "Abortion followup-2", "earliest": 7, "max": 11},
                {"name": "Abortion followup-3", "earliest": 15, "max": 19}
            ];

            var schedule = _.chain(encounterScheduleAbortion).filter(e => imports.moment(visitDate).isSameOrBefore(imports.moment(dateOfAbortion).add(e.earliest, 'days'), 'date') === true).filter(e => (programEncounter.programEnrolment.hasEncounter('Abortion followup', e.name)) === false).first().value();

            if (!_.isEmpty(schedule)) {
                scheduleBuilder
                    .add({
                        name: schedule.name,
                        encounterType: "Abortion followup",
                        earliestDate: imports.moment(dateOfAbortion).add(schedule.earliest, 'days').toDate(),
                        maxDate: imports.moment(dateOfAbortion).add(schedule.max, 'days').toDate()
                    });
            }
        }

        if (programEncounter.encounterType.name == 'Mother PNC') {
            const visitDate = programEncounter.encounterDateTime || programEncounter.earliestVisitDateTime;

            const dateOfDelivery = programEncounter.programEnrolment
                .getObservationReadableValueInEntireEnrolment('Date and time when baby was out');

            const encounterSchedulePNC = [
                {"name": "PNC 2", "earliest": 1, "max": 4},
                {"name": "PNC 3", "earliest": 2, "max": 5},
                {"name": "PNC 4", "earliest": 6, "max": 9},
                {"name": "PNC 5", "earliest": 14, "max": 17},
                {"name": "PNC 6", "earliest": 28, "max": 31},
                {"name": "PNC 7", "earliest": 41, "max": 44}
            ];

            var schedule = _.chain(encounterSchedulePNC).filter(e => imports.moment(visitDate).isSameOrBefore(imports.moment(dateOfDelivery).add(e.earliest, 'days'), 'date') === true).filter(e => (programEncounter.programEnrolment.hasEncounter('Mother PNC', e.name)) === false).first().value();

            if (!_.isEmpty(schedule)) {
                scheduleBuilder
                    .add({
                        name: schedule.name,
                        encounterType: "Mother PNC",
                        earliestDate: imports.moment(dateOfDelivery).add(schedule.earliest, 'days').toDate(),
                        maxDate: imports.moment(dateOfDelivery).add(schedule.max, 'days').toDate()
                    });
            }
        }

        if (programEncounter.encounterType.name == 'Child PNC') {
            const visitDate = programEncounter.encounterDateTime || programEncounter.earliestVisitDateTime;

            const dateOfBirth = programEncounter.individual.dateOfBirth;
            const encounterSchedulePNC = [
                  {"name": "PNC 2", "earliest": 1, "max": 4},
                  {"name": "PNC 3", "earliest": 2, "max": 5},
                  {"name": "PNC 4", "earliest": 6, "max": 9},
                  {"name": "PNC 5", "earliest": 14, "max": 17},
                  {"name": "PNC 6", "earliest": 28, "max": 31},
                  {"name": "PNC 7", "earliest": 41, "max": 44}
            ];

            var schedule = _.chain(encounterSchedulePNC).filter(e => imports.moment(visitDate).isSameOrBefore(imports.moment(dateOfBirth).add(e.earliest, 'days'), 'date') === true).filter(e => (programEncounter.programEnrolment.hasEncounter('Child PNC', e.name)) === false).first().value();

            if (!_.isEmpty(schedule)) {
                scheduleBuilder
                    .add({
                        name: schedule.name,
                        encounterType: "Child PNC",
                        earliestDate: imports.moment(dateOfBirth).add(schedule.earliest, 'days').toDate(),
                        maxDate: imports.moment(dateOfBirth).add(schedule.max, 'days').toDate()
                    });
            }
        }

        if (programEncounter.encounterType.name == 'Referral Status') {
            const visitDate = programEncounter.encounterDateTime || programEncounter.earliestVisitDateTime;

            const followupDate = imports.moment(visitDate).add(4, 'days').toDate();
            scheduleBuilder
                .add({
                    name: "Referral Status-2",
                    encounterType: "Referral Status",
                    earliestDate: followupDate,
                    maxDate: imports.moment(followupDate).add(2, 'days').toDate()
                }).whenItem(programEncounter.name == 'Referral Status-1')
                .is.truthy;
        }
    }

    const startOfNextMonth = (date) => {
        return imports.moment(date).startOf('month').add(1, 'months').startOf('day').toDate();
    };

    const FEB = 1;
    const AUG = 7;

    class albendazole {
        static findSlot(anyDate) {
            anyDate = imports.moment(anyDate).startOf('day').toDate();
            if (imports.moment(anyDate).month() < FEB) {
                return imports.moment(anyDate).startOf('month').month(FEB).toDate();
            }
            if (imports.moment(anyDate).month() === FEB) {
                return anyDate;
            }
            if (imports.moment(anyDate).month() < AUG) {
                return imports.moment(anyDate).startOf('month').month(AUG).toDate();
            }
            if (imports.moment(anyDate).month() === AUG) {
                return anyDate;
            }
            return imports.moment(anyDate).add(1, 'year').month(FEB).startOf('month').toDate();
        }

        static getVisitSchedule(_earliestDate) {
            let earliestDate = imports.moment(_earliestDate).startOf('day').toDate();
            let maxDate = imports.moment(earliestDate).endOf('month').toDate();
            if (imports.moment(_earliestDate).month() === FEB) {
                return {
                    name: 'Albendazole FEB',
                    encounterType: 'Albendazole',
                    earliestDate, maxDate,
                }
            }
            return {
                name: 'Albendazole AUG',
                encounterType: 'Albendazole',
                earliestDate, maxDate,
            }
        }

        //it should simply be
        //return albendazole.findSlot(moment(currentVisitScheduledDate).add(6, 'months').startOf('month').toDate())
        //but need to test all scenarios
        static findNextSlot(currentVisitScheduledDate) {
            let guessedDate = startOfNextMonth(currentVisitScheduledDate);
            return albendazole.findSlot(guessedDate);
        }
    }

    class gmp {
        static scheduleNext(scheduledDateTime, dayOfMonth) {
            const earliestDate = imports.moment(scheduledDateTime).add(1, 'M').date(dayOfMonth).toDate();
            const maxDate = imports.moment(earliestDate).add(3, 'days').toDate();
            return {
                name: "Growth Monitoring Visit",
                encounterType: "Growth Monitoring",
                earliestDate: earliestDate,
                maxDate: maxDate
            };
        }

        static scheduleOn(date) {
            return {
                name: "Growth Monitoring Visit",
                encounterType: "Growth Monitoring",
                earliestDate: date,
                maxDate: date
            };
        }

        static scheduleOnCancel(scheduledDateTime, dayOfMonth) {
            const scheduleOnSameMonth = imports.moment(scheduledDateTime).date() < dayOfMonth;
            const earliestDate = imports.moment(scheduledDateTime)
                .add(scheduleOnSameMonth ? 0 : 1, 'M').date(dayOfMonth).toDate();
            const maxDate = imports.moment(earliestDate).add(3, 'days').toDate();
            return {
                name: "Growth Monitoring Visit",
                encounterType: "Growth Monitoring",
                earliestDate: earliestDate,
                maxDate: maxDate
            }
        }
    }

    if (programEncounter.encounterType.name == 'Growth Monitoring') {
        const _ = imports.lodash;
        if (programEncounter.programEnrolment.isActive) {
           const myGroups = programEncounter.programEnrolment.individual.groups;
           const groupSubject = _.get(_.find(myGroups, g => !g.voided && g.groupSubject.subjectType.name === 'Phulwari'), 'groupSubject');

            if(!_.isNil(groupSubject)){
            const dayOfMonth = groupSubject.getObservationReadableValue("Day of month for growth monitoring visit");
            const year = imports.moment(programEncounter.earliestVisitDateTime).format("YYYY");
            if (year != 2019) {
                scheduleBuilder.add(gmp.scheduleOnCancel(programEncounter.earliestVisitDateTime, dayOfMonth));
            }
          }
        }
    }

    if (programEncounter.encounterType.name == 'Albendazole') {
        if (programEncounter.programEnrolment.isActive) {
            scheduleBuilder.add(albendazole.getVisitSchedule(albendazole
                .findNextSlot(programEncounter.earliestVisitDateTime)));
        }
    }

    return scheduleBuilder.getAll();
};
vedfordev commented 11 months ago

Developer Note:

Check :

set role jsscp;

-- to check rule failure
select *
from rule_failure_telemetry where is_closed = false
and error_message = '174:5:invalid statement encountered.'
;

-- close the failure rule
update rule_failure_telemetry
set is_closed = true,
    last_modified_date_time = now(),
    close_date_time = now(),
    last_modified_by_id = (select id
                           from public.users where username = 'vedantr@jsscp')
where is_closed = false and error_message = '174:5:invalid statement encountered.'

-- rules which uses class keyword
select *
from form where visit_schedule_rule ilike '%class%' or decision_rule ilike '%class%' or validation_rule ilike '%class%';
vedfordev commented 11 months ago

@nupoorkhandelwal closed rule_failure_telemetry. moved it to prod.

vedfordev commented 11 months ago

Check :

--------------------------------

set role jsscp;

select *
from rule_failure_telemetry where is_closed = false
and ( error_message = 'Cannot read property ''chain'' of undefined' or error_message = 'Cannot read property ''isEmpty'' of undefined')
and rule_uuid = '8ffd8274-ca88-449f-857d-9638b7db792f'
;

update rule_failure_telemetry
set is_closed = true,
    last_modified_date_time = now(),
    close_date_time = now(),
    last_modified_by_id = (select id
                           from public.users where username = 'vedantr@jsscp')
where is_closed = false
  and ( error_message = 'Cannot read property ''chain'' of undefined' or error_message = 'Cannot read property ''isEmpty'' of undefined')
  and rule_uuid = '8ffd8274-ca88-449f-857d-9638b7db792f';
vedfordev commented 11 months ago

@nupoorkhandelwal

resolved and closed error Cannot read property 'chain' of undefined and Cannot read property 'isEmpty' of undefined for rule_uuid '8ffd8274-ca88-449f-857d-9638b7db792f'.

moved changes to prod.