How in JS to execute inside a batch loop only on first iteration one function

Viewed 42

On the following snippet inside the forEach loop, I'm executing a function called saveCandidatesData({ screeningNumbers, pruneScreeningNumbers: prune })

The function generally is about saving some data in batches not to overload the request.

My issue is that the function saveCandidatesData need to be executed for every batch but the part called pruneScreeningNumbers needs to be executed only on the first batch iteration.

For example

If you have 3 batches then request saveCandidatesData should be executed 3 times.

  1. screeningNumbers - data for batch 1 + pruneScreeningNumbers

  2. screeningNumbers - data for batch 2 (no prune)

  3. screeningNumbers - data for batch 3 (no prune)

I don't know how to implement this in the following code

const screeningNumbersCalculations = async (data, batches) => {
  console.log('batches: ', batches);
  const results = {};
  const mappedDateId = dateIdMap(data);

  batches.map(b => {
    b.forEach(t => {
      const matchingStatusesTime = Object.values(mappedDateId).filter(s => {
        return new Date(s.statusFrom) < endOfDay(t) && (s.statusTo === null || new Date(t) <= new Date(s.statusTo));
      });

      matchingStatusesTime.forEach(ms => {
        const label = assignLabel(ms.statusType, ms.eligible);
        const key = `${t}${ms.studyId}${ms.siteId}${label}`;
        if (!results[key]) {
          results[key] = {
            days: [t],
            studyId: ms.studyId,
            siteId: ms.siteId,
            screeningStage: 'SELF_ASSESSMENT',
            label,
            total: 0,
          };
        }
        results[key].total += 1;
      });
      const screeningNumbers = Object.values(results);

      const cnt = saveCandidatesData({ screeningNumbers, pruneScreeningNumbers: prune });

      log.info('Replay screening numbers completed in %d', cnt);
    });
    return Promise.resolve();
  });
};

Right now the prune is executed every time and I need to change that to be only on the first batch iteration.

I have no clue how to fix that

This is a working snippet of a simplified version of the above snippet but without the function at the moment. However, you can have an idea on how it works

const mockData = [{
    id: "C10-TBX4",
    studyId: "TBX4",
    siteId: "USA-1",
    statusType: "INCOMPLETE",
    statusFrom: "2020-12-05T01:00:00.000Z",
    statusTo: "2020-12-05T02:00:00.000Z"
  },
  {
    id: "C10-TBX4",
    studyId: "TBX4",
    siteId: "USA-1",
    statusType: "DROPOUT_PRESCREENER",
    eligible: true,
    statusFrom: "2020-12-05T02:00:00.000Z",
    statusTo: "2020-12-05T03:00:00.000Z"
  },
  {
    id: "C10-TBX4",
    studyId: "TBX4",
    siteId: "USA-1",
    statusType: "INCOMPLETE",
    statusFrom: "2020-12-05T03:00:00.000Z",
    statusTo: null
  }
];

function endOfDay(time) {
  const date = new Date(time);
  date.setUTCHours(23, 59, 59, 999);
  return date;
}

const assignLabel = (statusType, eligible) => {
  if (statusType === "INCOMPLETE") return "pending";
  if (statusType === "REJECTED_PRESCREENER") return "ineligible";
  if (statusType === "DROPOUT_PRESCREENER" && eligible) return "eligible";
  if (statusType === "DROPOUT_PRESCREENER" && !eligible) return "abandoned";
  return "completed";
};

// This dates are generated from a fuction bases on which dates range we need
// In this cas is just one day
const batches = ["2020-12-05T00:00:00.000Z"];

const screeningNumbersCalculations = () => {
  const results = {}; // Dont use array since you are using it as dictionary Object.
  // Putting latest records at end so we can confidently overwrite with records of same day in next step
  mockData.sort((a, b) => endOfDay(a).getTime() - endOfDay(b).getTime());
  const dateidMap = mockData.reduce((accum, item) => {
    accum[`${endOfDay(item).getTime()}.${item.id}`] = item;
    return accum;
  }, {});
  console.log(dateidMap)
  batches.forEach((t) => {
    // or filter matching times first and generate dateidMap later.
    const matchingStatusesTime = Object.values(dateidMap).filter((s) => {
      return (
        new Date(s.statusFrom) < endOfDay(t) &&
        (s.statusTo === null || new Date(t) <= new Date(s.statusTo))
      );
    });

    matchingStatusesTime.forEach((ms) => {
      const key = `${t}${ms.studyId}${ms.siteId}${assignLabel(
        ms.statusType,
        ms.eligible
      )}`;
      if (!results[key]) {
        results[key] = {
          days: [t],
          studyId: ms.studyId,
          siteId: ms.siteId,
          screeningStage: "SELF_ASSESSMENT",
          label: assignLabel(ms.statusType, ms.eligible),
          total: 0
        };
      }
      results[key].total++;
    });
  });
  return Object.values(results);
};
const screeningNumbers = screeningNumbersCalculations();

console.log(JSON.stringify(screeningNumbers, null, 2));

1 Answers

map() receives the array index as the 2nd argument. So you can test if this is 0 to tell if you're in the first batch.

Also, you should use forEach() rather than map() if you don't need the array of the results of each call that map() creates.

You need to move the code that calls saveCandidatesData to the outer loop. And you should initialize results = {} at the beginning of this loop so you don't combine each batch with the previous batches.

const screeningNumbersCalculations = async(data, batches) => {
  console.log('batches: ', batches);
  const mappedDateId = dateIdMap(data);

  batches.forEach((b, batch_index) => {
    const results = {};

    b.forEach(t => {
      const matchingStatusesTime = Object.values(mappedDateId).filter(s => {
        return new Date(s.statusFrom) < endOfDay(t) && (s.statusTo === null || new Date(t) <= new Date(s.statusTo));
      });

      matchingStatusesTime.forEach(ms => {
        const label = assignLabel(ms.statusType, ms.eligible);
        const key = `${t}${ms.studyId}${ms.siteId}${label}`;
        if (!results[key]) {
          results[key] = {
            days: [t],
            studyId: ms.studyId,
            siteId: ms.siteId,
            screeningStage: 'SELF_ASSESSMENT',
            label,
            total: 0,
          };
        }
        results[key].total += 1;
      });
    });
    const screeningNumbers = Object.values(results);

    const options = {
      screeningNumbers
    };
    if (batch_index == 0) {
      options.pruneScreeningNumbers = prune;
    }
    const cnt = saveCandidatesData(options);

    log.info('Replay screening numbers completed in %d', cnt);
    return Promise.resolve();
  });
};

Related