coforma/swift-tech-challenge

View on GitHub
src/app/utils/institutions.ts

Summary

Maintainability
A
1 hr
Test Coverage
A
94%
"use server";
import dynamoClient from "./libs/dynamodb-lib";
// types
import { College, degreeMap } from "../types";

const INSTITUTIONS_TABLE_NAME =
  process.env.INSTITUTIONS_DYNAMODB_TABLE ?? "institutions";

function mapDbDataRecordToCollege(item: any) {
  return {
    // basics
    id: item?.institutionId,
    name: item?.institutionName,
    city: item?.city,
    state: item?.state,
    zip: item?.zip,
    url: item?.url,
    type: item?.institutionType,
    description: item?.description,
    // focus
    predominantUndergradDegree: mapToDegreeString(
      item?.predominantUndergradDegree,
    ),
    highestDegreeAwarded: mapToDegreeString(item?.highestDegreeAwarded),
    specialties: convertStringToBoolObject(item?.specialties),
    // enrollment
    population: parseInt(item?.studentPopulation),
    demographics: convertFloatToObject(item?.raceDemographics),
    // admissions
    admissionRate: parseFloat(item?.admissionRate),
    satScores: convertIntToObject(item?.satScores),
    // cost
    avgCost: parseInt(item?.averageAttendanceCost),
    tuitionInState: parseInt(item?.tuitionInState),
    tuitionOutOfState: parseInt(item?.tuitionOutOfState),
    undergradWithFedLoan: parseFloat(item?.percentUndergradWithLoan),
    npcUrl: item?.netPriceCalculatorUrl,
    netPricePublic: convertIntToObject(item?.publicNetPrice),
    netPricePrivate: convertIntToObject(item?.netPricePrivate),
    // faculty & expenditures
    facultyAvgSalary: parseInt(item?.facultyAverageSalary),
    facultyEmployedFullTime: parseFloat(item?.facultyPercentageEmployedFull),
    studentFacultyRatio: parseInt(item?.studentToFacultyRatio),
    instructionalExpPerStudent: parseInt(item?.instructionalExpenditurePerSt),
    // outcomes
    completionRate: parseFloat(
      item?.completionRates.fourYearInstitution ||
        item?.completionRates.underFourYearInstitution,
    ),
    // awardIn8Yrs: "", TODO: Get after data ingestion update
    earnings: convertIntToObject(item?.earnings),
    retentionRate: parseFloat(
      item?.retentionRate.fourYearInstitution ||
        item?.retentionRate.underFourYearInstitution,
    ),
  };
}

export async function get20Institutions(startKey?: any) {
  let colleges: College[] = [];
  const params = {
    TableName: INSTITUTIONS_TABLE_NAME,
    FilterExpression: "recordType = :recordType",
    ExpressionAttributeValues: { ":recordType": "data" },
    ExclusiveStartKey: startKey ?? undefined,
    Limit: 20,
  };
  const result = await dynamoClient.singleScan(params);
  const lastKey = result.LastEvaluatedKey;
  const items = result?.Items;

  if (Array.isArray(items)) {
    for (const item of items) {
      const college = mapDbDataRecordToCollege(item);
      colleges.push(college);
    }
  }

  /*
   * Sort colleges by name in alpha order, could optimize but inserting
   * in sorted order if performance becomes an issue
   */
  colleges.sort((a, b) =>
    a.name.toLowerCase() > b.name.toLowerCase() ? 1 : -1,
  );
  return { colleges, lastKey };
}

export async function getInstitutions() {
  let colleges: College[] = [];
  const params = {
    TableName: INSTITUTIONS_TABLE_NAME,
    FilterExpression: "recordType = :recordType",
    ExpressionAttributeValues: { ":recordType": "data" },
  };
  const items = await dynamoClient.scanAll(params);

  if (Array.isArray(items)) {
    for (const item of items) {
      const college = mapDbDataRecordToCollege(item);
      colleges.push(college);
    }
  }

  /*
   * Sort colleges by name in alpha order, could optimize but inserting
   * in sorted order if performance becomes an issue
   */
  colleges.sort((a, b) =>
    a.name.toLowerCase() > b.name.toLowerCase() ? 1 : -1,
  );
  return colleges;
}

// TODO: Add tests for utility methods here
const mapToDegreeString = (key: number | string) =>
  degreeMap[key as keyof typeof degreeMap];

const convertStringToBoolObject: any = (obj: { [key: string]: string }) => {
  if (obj) {
    return Object.fromEntries(
      Object.keys(obj).map((el) => [el, obj[el] === "True"]),
    );
  } else return undefined;
};

const convertFloatToObject: any = (obj: { [key: string]: any }) => {
  if (obj) {
    return Object.fromEntries(
      Object.keys(obj).map((el) => [el, parseFloat(obj[el])]),
    );
  } else return undefined;
};

const convertIntToObject: any = (obj: { [key: string]: any }) => {
  if (obj) {
    return Object.fromEntries(
      Object.keys(obj).map((el) => [el, parseFloat(obj[el])]),
    );
  } else return undefined;
};

export async function getInstitutionApplication(institutionId: Number) {
  const params = {
    TableName: INSTITUTIONS_TABLE_NAME,
    Key: { institutionId: institutionId, recordType: "application" },
  };
  const result = await dynamoClient.get(params);
  return result?.Item;
}