export function getDashboardSummary()

in src/app/functions/server/dashboard.ts [98:445]


export function getDashboardSummary(
  scannedResults: OnerepScanResultDataBrokerRow[],
  subscriberBreaches: SubscriberBreach[],
  enabledFeatureFlags?: FeatureFlagName[],
): DashboardSummary {
  const summary: DashboardSummary = {
    dataBreachTotalNum: 0,
    dataBreachResolvedNum: 0,
    dataBreachUnresolvedNum: 0,
    dataBreachTotalDataPointsNum: 0,
    dataBreachFixedDataPointsNum: 0,
    dataBrokerTotalNum: scannedResults.length,
    dataBrokerTotalDataPointsNum: 0,
    dataBrokerAutoFixedNum: 0,
    dataBrokerRemovalUnderMaintenance: 0,
    dataBrokerAutoFixedDataPointsNum: 0,
    dataBrokerInProgressNum: 0,
    dataBrokerInProgressDataPointsNum: 0,
    dataBrokerManuallyResolvedNum: 0,
    dataBrokerManuallyResolvedDataPointsNum: 0,
    totalDataPointsNum: 0,
    allDataPoints: {
      emailAddresses: 0,
      phoneNumbers: 0,
      addresses: 0,
      familyMembers: 0,

      // data breaches
      socialSecurityNumbers: 0,
      ipAddresses: 0,
      passwords: 0,
      creditCardNumbers: 0,
      pins: 0,
      securityQuestions: 0,
      bankAccountNumbers: 0,
    },
    unresolvedDataPoints: {
      emailAddresses: 0,
      phoneNumbers: 0,
      addresses: 0,
      familyMembers: 0,

      // data breaches
      socialSecurityNumbers: 0,
      ipAddresses: 0,
      passwords: 0,
      creditCardNumbers: 0,
      pins: 0,
      securityQuestions: 0,
      bankAccountNumbers: 0,
    },
    inProgressDataPoints: {
      emailAddresses: 0,
      phoneNumbers: 0,
      addresses: 0,
      familyMembers: 0,

      // data breaches
      socialSecurityNumbers: 0,
      ipAddresses: 0,
      passwords: 0,
      creditCardNumbers: 0,
      pins: 0,
      securityQuestions: 0,
      bankAccountNumbers: 0,
    },
    fixedDataPoints: {
      emailAddresses: 0,
      phoneNumbers: 0,
      addresses: 0,
      familyMembers: 0,

      // data breaches
      socialSecurityNumbers: 0,
      ipAddresses: 0,
      passwords: 0,
      creditCardNumbers: 0,
      pins: 0,
      securityQuestions: 0,
      bankAccountNumbers: 0,
    },
    manuallyResolvedDataBrokerDataPoints: {
      emailAddresses: 0,
      phoneNumbers: 0,
      addresses: 0,
      familyMembers: 0,

      // data breaches
      socialSecurityNumbers: 0,
      ipAddresses: 0,
      passwords: 0,
      creditCardNumbers: 0,
      pins: 0,
      securityQuestions: 0,
      bankAccountNumbers: 0,
    },
    unresolvedSanitizedDataPoints: [],
    fixedSanitizedDataPoints: [],
  };

  // calculate broker summary from scanned results
  if (scannedResults) {
    scannedResults.forEach((r) => {
      // check removal status
      const isManuallyResolved = r.manually_resolved;
      const isAutoFixed =
        r.status === RemovalStatusMap.Removed && !isManuallyResolved;
      const isInProgress =
        (r.status === RemovalStatusMap.OptOutInProgress ||
          r.status === RemovalStatusMap.WaitingForVerification) &&
        !isManuallyResolved;

      // TODO: MNTOR-3886 - Remove EnableRemovalUnderMaintenanceStep feature flag
      // If the flag is disabled, include the data.
      // If the flag is enabled, include the data only if the broker status is not
      const isRemovalUnderMaintenance =
        r.broker_status === DataBrokerRemovalStatusMap.RemovalUnderMaintenance;

      // The condition ensures that removal under maintenance is only considered when the flag is enabled.
      /* c8 ignore next 3 */
      const countRemovalUnderMaintenanceData =
        !enabledFeatureFlags?.includes("EnableRemovalUnderMaintenanceStep") ||
        !isRemovalUnderMaintenance;

      if (isInProgress) {
        if (countRemovalUnderMaintenanceData) {
          summary.dataBrokerInProgressNum++;
        }
      } else if (isAutoFixed) {
        summary.dataBrokerAutoFixedNum++;
      } else if (isManuallyResolved) {
        summary.dataBrokerManuallyResolvedNum++;
      }
      // total data points: add email, phones, addresses, relatives, full name (1)
      const dataPointsIncrement =
        r.emails.length +
        r.phones.length +
        r.addresses.length +
        r.relatives.length;
      summary.totalDataPointsNum += dataPointsIncrement;
      summary.dataBrokerTotalDataPointsNum += dataPointsIncrement;

      // for all data points: email, phones, addresses, relatives, full name (1)
      summary.allDataPoints.emailAddresses += r.emails.length;
      summary.allDataPoints.phoneNumbers += r.phones.length;
      summary.allDataPoints.addresses += r.addresses.length;
      summary.allDataPoints.familyMembers += r.relatives.length;

      if (isInProgress) {
        if (countRemovalUnderMaintenanceData) {
          summary.inProgressDataPoints.emailAddresses += r.emails.length;
          summary.inProgressDataPoints.phoneNumbers += r.phones.length;
          summary.inProgressDataPoints.addresses += r.addresses.length;
          summary.inProgressDataPoints.familyMembers += r.relatives.length;
          summary.dataBrokerInProgressDataPointsNum += dataPointsIncrement;
        }
      }

      // for fixed data points: email, phones, addresses, relatives, full name (1)
      if (isAutoFixed) {
        summary.fixedDataPoints.emailAddresses += r.emails.length;
        summary.fixedDataPoints.phoneNumbers += r.phones.length;
        summary.fixedDataPoints.addresses += r.addresses.length;
        summary.fixedDataPoints.familyMembers += r.relatives.length;
        summary.dataBrokerAutoFixedDataPointsNum += dataPointsIncrement;
      }

      if (isManuallyResolved) {
        summary.manuallyResolvedDataBrokerDataPoints.emailAddresses +=
          r.emails.length;
        summary.manuallyResolvedDataBrokerDataPoints.phoneNumbers +=
          r.phones.length;
        summary.manuallyResolvedDataBrokerDataPoints.addresses +=
          r.addresses.length;
        summary.manuallyResolvedDataBrokerDataPoints.familyMembers +=
          r.relatives.length;
        summary.dataBrokerManuallyResolvedDataPointsNum += dataPointsIncrement;
      }
    });
  }

  // calculate breaches summary from breaches data
  // TODO: Modify after MNTOR-1947: Refactor user breaches object
  subscriberBreaches.forEach((b) => {
    // According to the type, b.dataClasses should always be defined, so unit
    // tests always define it. That said, since the type was added later, I'd
    // rather not remove the `?? []` just in case it is wrong, hence:
    /* c8 ignore next */
    const dataClasses = b.dataClasses ?? [];
    // b.emailsAffected *should* always be non-empty, since `subscriberBreaches`
    // was specifically constructed to contain all breaches that affect the
    // user. However, some basic `git blame` archeology doesn't turn up any info
    // on why the `?? 0` was added, so I'm leaving it in place for now:
    /* c8 ignore next */
    const increment = b.emailsAffected.length ?? 0;

    // count emails
    if (dataClasses.includes(BreachDataTypes.Email)) {
      summary.totalDataPointsNum += increment;
      summary.dataBreachTotalDataPointsNum += increment;
      summary.allDataPoints.emailAddresses += increment;
      if (b.resolvedDataClasses.includes(BreachDataTypes.Email)) {
        summary.fixedDataPoints.emailAddresses += increment;
        summary.dataBreachFixedDataPointsNum += increment;
      }
    }

    // count phone numbers
    if (dataClasses.includes(BreachDataTypes.Phone)) {
      summary.totalDataPointsNum += increment;
      summary.dataBreachTotalDataPointsNum += increment;
      summary.allDataPoints.phoneNumbers += increment;
      if (b.resolvedDataClasses.includes(BreachDataTypes.Phone)) {
        summary.fixedDataPoints.phoneNumbers += increment;
        summary.dataBreachFixedDataPointsNum += increment;
      }
    }

    // count password
    if (dataClasses.includes(BreachDataTypes.Passwords)) {
      summary.totalDataPointsNum += increment;
      summary.dataBreachTotalDataPointsNum += increment;
      summary.allDataPoints.passwords += increment;
      if (b.resolvedDataClasses.includes(BreachDataTypes.Passwords)) {
        summary.fixedDataPoints.passwords += increment;
        summary.dataBreachFixedDataPointsNum += increment;
      }
    }

    // count ssn
    if (dataClasses.includes(BreachDataTypes.SSN)) {
      summary.totalDataPointsNum += increment;
      summary.dataBreachTotalDataPointsNum += increment;
      summary.allDataPoints.socialSecurityNumbers += increment;
      if (b.resolvedDataClasses.includes(BreachDataTypes.SSN)) {
        summary.fixedDataPoints.socialSecurityNumbers += increment;
        summary.dataBreachFixedDataPointsNum += increment;
      }
    }

    // count IP
    if (dataClasses.includes(BreachDataTypes.IP)) {
      summary.totalDataPointsNum += increment;
      summary.dataBreachTotalDataPointsNum += increment;
      summary.allDataPoints.ipAddresses += increment;
      if (b.resolvedDataClasses.includes(BreachDataTypes.IP)) {
        summary.fixedDataPoints.ipAddresses += increment;
        summary.dataBreachFixedDataPointsNum += increment;
      }
    }

    // count credit card
    if (dataClasses.includes(BreachDataTypes.CreditCard)) {
      summary.totalDataPointsNum += increment;
      summary.dataBreachTotalDataPointsNum += increment;
      summary.allDataPoints.creditCardNumbers += increment;
      if (b.resolvedDataClasses.includes(BreachDataTypes.CreditCard)) {
        summary.fixedDataPoints.creditCardNumbers += increment;
        summary.dataBreachFixedDataPointsNum += increment;
      }
    }

    // count pin numbers
    if (dataClasses.includes(BreachDataTypes.PIN)) {
      summary.totalDataPointsNum += increment;
      summary.dataBreachTotalDataPointsNum += increment;
      summary.allDataPoints.pins += increment;
      if (b.resolvedDataClasses.includes(BreachDataTypes.PIN)) {
        summary.fixedDataPoints.pins += increment;
        summary.dataBreachFixedDataPointsNum += increment;
      }
    }

    // count security questions
    if (dataClasses.includes(BreachDataTypes.SecurityQuestions)) {
      summary.totalDataPointsNum += increment;
      summary.dataBreachTotalDataPointsNum += increment;
      summary.allDataPoints.securityQuestions += increment;
      if (b.resolvedDataClasses.includes(BreachDataTypes.SecurityQuestions)) {
        summary.fixedDataPoints.securityQuestions += increment;
        summary.dataBreachFixedDataPointsNum += increment;
      }
    }

    if (dataClasses.includes(BreachDataTypes.BankAccount)) {
      summary.totalDataPointsNum += increment;
      summary.dataBreachTotalDataPointsNum += increment;
      summary.allDataPoints.bankAccountNumbers += increment;
      if (b.resolvedDataClasses.includes(BreachDataTypes.BankAccount)) {
        summary.fixedDataPoints.bankAccountNumbers += increment;
        summary.dataBreachFixedDataPointsNum += increment;
      }
    }

    if (b.isResolved) summary.dataBreachResolvedNum++;
  });

  // count unique breaches
  summary.dataBreachTotalNum = subscriberBreaches.length;
  summary.dataBreachUnresolvedNum =
    summary.dataBreachTotalNum - summary.dataBreachResolvedNum;
  const isBreachesOnly = summary.dataBrokerTotalNum === 0;

  // count unresolved data points
  summary.unresolvedDataPoints = Object.keys(summary.allDataPoints).reduce(
    (a, k) => {
      a[k as keyof DataPoints] =
        summary.allDataPoints[k as keyof DataPoints] -
        summary.fixedDataPoints[k as keyof DataPoints] -
        summary.inProgressDataPoints[k as keyof DataPoints] -
        summary.manuallyResolvedDataBrokerDataPoints[k as keyof DataPoints];
      return a;
    },
    {} as DataPoints,
  );

  // sanitize unresolved data points
  summary.unresolvedSanitizedDataPoints = sanitizeDataPoints(
    summary.unresolvedDataPoints,
    summary.totalDataPointsNum -
      summary.dataBreachFixedDataPointsNum -
      summary.dataBrokerAutoFixedDataPointsNum -
      summary.dataBrokerInProgressDataPointsNum -
      summary.dataBrokerManuallyResolvedDataPointsNum,
    isBreachesOnly,
  );

  // count fixed and manually resolved (data brokers) data points
  const dataBrokerFixedManuallyResolved = Object.keys(
    summary.fixedDataPoints,
  ).reduce((a, k) => {
    a[k as keyof DataPoints] =
      summary.fixedDataPoints[k as keyof DataPoints] +
      summary.manuallyResolvedDataBrokerDataPoints[k as keyof DataPoints];
    return a;
  }, {} as DataPoints);

  // sanitize fixed and removed data points
  summary.fixedSanitizedDataPoints = sanitizeDataPoints(
    dataBrokerFixedManuallyResolved,
    summary.dataBreachFixedDataPointsNum +
      summary.dataBrokerAutoFixedDataPointsNum +
      summary.dataBrokerManuallyResolvedDataPointsNum,
    isBreachesOnly,
  );

  return summary;
}