From 34201de480dee4faeb21b9b62266b140b8a62b69 Mon Sep 17 00:00:00 2001 From: Yazeed Loonat Date: Thu, 20 Jun 2024 09:25:24 -0700 Subject: [PATCH] feat: ami chart import script runner (#4125) * feat: ami chart import script runner * fix: updates per morgan * fix: updates per Emily --- .../controllers/script-runner.controller.ts | 18 ++ .../script-runner/ami-chart-import.dto.ts | 24 +++ api/src/modules/script-runner.module.ts | 3 +- api/src/services/script-runner.service.ts | 56 ++++++ .../services/script-runner.service.spec.ts | 184 ++++++++++++++++++ shared-helpers/src/types/backend-swagger.ts | 33 ++++ 6 files changed, 317 insertions(+), 1 deletion(-) create mode 100644 api/src/dtos/script-runner/ami-chart-import.dto.ts diff --git a/api/src/controllers/script-runner.controller.ts b/api/src/controllers/script-runner.controller.ts index d4331bdbc7..964bffa4a0 100644 --- a/api/src/controllers/script-runner.controller.ts +++ b/api/src/controllers/script-runner.controller.ts @@ -16,6 +16,7 @@ import { OptionalAuthGuard } from '../guards/optional.guard'; import { AdminOrJurisdictionalAdminGuard } from '../guards/admin-or-jurisdiction-admin.guard'; import { DataTransferDTO } from '../dtos/script-runner/data-transfer.dto'; import { BulkApplicationResendDTO } from '../dtos/script-runner/bulk-application-resend.dto'; +import { AmiChartImportDTO } from '../dtos/script-runner/ami-chart-import.dto'; @Controller('scriptRunner') @ApiTags('scriptRunner') @@ -63,4 +64,21 @@ export class ScirptRunnerController { bulkApplicationResendDTO, ); } + + @Put('amiChartImport') + @ApiOperation({ + summary: + 'A script that takes in a standardized string and outputs the input for the ami chart create endpoint', + operationId: 'amiChartImport', + }) + @ApiOkResponse({ type: SuccessDTO }) + async amiChartImport( + @Body() amiChartImportDTO: AmiChartImportDTO, + @Request() req: ExpressRequest, + ): Promise { + return await this.scriptRunnerService.amiChartImport( + req, + amiChartImportDTO, + ); + } } diff --git a/api/src/dtos/script-runner/ami-chart-import.dto.ts b/api/src/dtos/script-runner/ami-chart-import.dto.ts new file mode 100644 index 0000000000..978b5ffdc9 --- /dev/null +++ b/api/src/dtos/script-runner/ami-chart-import.dto.ts @@ -0,0 +1,24 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { Expose } from 'class-transformer'; +import { IsDefined, IsString } from 'class-validator'; +import { ValidationsGroupsEnum } from '../../enums/shared/validation-groups-enum'; + +export class AmiChartImportDTO { + @Expose() + @IsString({ groups: [ValidationsGroupsEnum.default] }) + @IsDefined({ groups: [ValidationsGroupsEnum.default] }) + @ApiProperty() + values: string; + + @Expose() + @IsString({ groups: [ValidationsGroupsEnum.default] }) + @IsDefined({ groups: [ValidationsGroupsEnum.default] }) + @ApiProperty() + name: string; + + @Expose() + @IsString({ groups: [ValidationsGroupsEnum.default] }) + @IsDefined({ groups: [ValidationsGroupsEnum.default] }) + @ApiProperty() + jurisdictionId: string; +} diff --git a/api/src/modules/script-runner.module.ts b/api/src/modules/script-runner.module.ts index eb2c5ff25a..26fb5fafe3 100644 --- a/api/src/modules/script-runner.module.ts +++ b/api/src/modules/script-runner.module.ts @@ -4,9 +4,10 @@ import { ScriptRunnerService } from '../services/script-runner.service'; import { PrismaModule } from './prisma.module'; import { PermissionModule } from './permission.module'; import { EmailModule } from './email.module'; +import { AmiChartModule } from './ami-chart.module'; @Module({ - imports: [PrismaModule, PermissionModule, EmailModule], + imports: [PrismaModule, PermissionModule, EmailModule, AmiChartModule], controllers: [ScirptRunnerController], providers: [ScriptRunnerService], exports: [ScriptRunnerService], diff --git a/api/src/services/script-runner.service.ts b/api/src/services/script-runner.service.ts index be15215430..26f5d14b69 100644 --- a/api/src/services/script-runner.service.ts +++ b/api/src/services/script-runner.service.ts @@ -9,6 +9,9 @@ import { DataTransferDTO } from '../dtos/script-runner/data-transfer.dto'; import { BulkApplicationResendDTO } from '../dtos/script-runner/bulk-application-resend.dto'; import { EmailService } from './email.service'; import { Application } from '../dtos/applications/application.dto'; +import { AmiChartImportDTO } from '../dtos/script-runner/ami-chart-import.dto'; +import { AmiChartCreate } from '../dtos/ami-charts/ami-chart-create.dto'; +import { AmiChartService } from './ami-chart.service'; /** this is the service for running scripts @@ -19,6 +22,7 @@ export class ScriptRunnerService { constructor( private prisma: PrismaService, private emailService: EmailService, + private amiChartService: AmiChartService, ) {} /** @@ -134,6 +138,58 @@ export class ScriptRunnerService { return { success: true }; } + /** + * + * @param amiChartImportDTO this is a string in a very specific format like: + * percentOfAmiValue_1 householdSize_1_income_value householdSize_2_income_value \n percentOfAmiValue_2 householdSize_1_income_value householdSize_2_income_value + * @returns successDTO + * @description takes the incoming AMI Chart string and stores it as a new AMI Chart in the database + */ + async amiChartImport( + req: ExpressRequest, + amiChartImportDTO: AmiChartImportDTO, + ): Promise { + // script runner standard start up + const requestingUser = mapTo(User, req['user']); + await this.markScriptAsRunStart( + `AMI Chart ${amiChartImportDTO.name}`, + requestingUser, + ); + + // parse incoming string into an amichart create dto + const createDTO: AmiChartCreate = { + items: [], + name: amiChartImportDTO.name, + jurisdictions: { + id: amiChartImportDTO.jurisdictionId, + }, + }; + + const rows = amiChartImportDTO.values.split('\n'); + rows.forEach((row: string) => { + const values = row.split(' '); + const percentage = values[0]; + values.forEach((value: string, index: number) => { + if (index > 0) { + createDTO.items.push({ + percentOfAmi: Number(percentage), + householdSize: index, + income: Number(value), + }); + } + }); + }); + + await this.amiChartService.create(createDTO); + + // script runner standard spin down + await this.markScriptAsComplete( + `AMI Chart ${amiChartImportDTO.name}`, + requestingUser, + ); + return { success: true }; + } + /** this is simply an example */ diff --git a/api/test/unit/services/script-runner.service.spec.ts b/api/test/unit/services/script-runner.service.spec.ts index 4f367b5332..3121551db9 100644 --- a/api/test/unit/services/script-runner.service.spec.ts +++ b/api/test/unit/services/script-runner.service.spec.ts @@ -5,6 +5,7 @@ import { ScriptRunnerService } from '../../../src/services/script-runner.service import { PrismaService } from '../../../src/services/prisma.service'; import { User } from '../../../src/dtos/users/user.dto'; import { EmailService } from '../../../src/services/email.service'; +import { AmiChartService } from '../../../src/services/ami-chart.service'; describe('Testing script runner service', () => { let service: ScriptRunnerService; @@ -22,6 +23,7 @@ describe('Testing script runner service', () => { applicationScriptRunner: jest.fn(), }, }, + AmiChartService, ], }).compile(); @@ -192,6 +194,188 @@ describe('Testing script runner service', () => { ); }); + it('should build ami chart import object', async () => { + const id = randomUUID(); + prisma.scriptRuns.findUnique = jest.fn().mockResolvedValue(null); + prisma.scriptRuns.create = jest.fn().mockResolvedValue(null); + prisma.scriptRuns.update = jest.fn().mockResolvedValue(null); + prisma.amiChart.create = jest.fn().mockResolvedValue(null); + + const name = 'example name'; + const scriptName = `AMI Chart ${name}`; + const jurisdictionId = 'example jurisdictionId'; + const valueItem = + '15 18400 21000 23650 26250 28350 30450 32550 34650\n30 39150 44750 50350 55900 60400 64850 69350 73800\n50 65250 74600 83900 93200 100700 108150 115600 123050'; + const res = await service.amiChartImport( + { + user: { + id, + } as unknown as User, + } as unknown as ExpressRequest, + { + values: valueItem, + name, + jurisdictionId, + }, + ); + expect(res.success).toEqual(true); + expect(prisma.scriptRuns.findUnique).toHaveBeenCalledWith({ + where: { + scriptName, + }, + }); + expect(prisma.scriptRuns.create).toHaveBeenCalledWith({ + data: { + scriptName, + triggeringUser: id, + }, + }); + expect(prisma.scriptRuns.update).toHaveBeenCalledWith({ + data: { + didScriptRun: true, + triggeringUser: id, + }, + where: { + scriptName, + }, + }); + expect(prisma.amiChart.create).toHaveBeenCalledWith({ + data: { + name, + items: [ + { + percentOfAmi: 15, + householdSize: 1, + income: 18400, + }, + { + percentOfAmi: 15, + householdSize: 2, + income: 21000, + }, + { + percentOfAmi: 15, + householdSize: 3, + income: 23650, + }, + { + percentOfAmi: 15, + householdSize: 4, + income: 26250, + }, + { + percentOfAmi: 15, + householdSize: 5, + income: 28350, + }, + { + percentOfAmi: 15, + householdSize: 6, + income: 30450, + }, + { + percentOfAmi: 15, + householdSize: 7, + income: 32550, + }, + { + percentOfAmi: 15, + householdSize: 8, + income: 34650, + }, + { + percentOfAmi: 30, + householdSize: 1, + income: 39150, + }, + { + percentOfAmi: 30, + householdSize: 2, + income: 44750, + }, + { + percentOfAmi: 30, + householdSize: 3, + income: 50350, + }, + { + percentOfAmi: 30, + householdSize: 4, + income: 55900, + }, + { + percentOfAmi: 30, + householdSize: 5, + income: 60400, + }, + { + percentOfAmi: 30, + householdSize: 6, + income: 64850, + }, + { + percentOfAmi: 30, + householdSize: 7, + income: 69350, + }, + { + percentOfAmi: 30, + householdSize: 8, + income: 73800, + }, + { + percentOfAmi: 50, + householdSize: 1, + income: 65250, + }, + { + percentOfAmi: 50, + householdSize: 2, + income: 74600, + }, + { + percentOfAmi: 50, + householdSize: 3, + income: 83900, + }, + { + percentOfAmi: 50, + householdSize: 4, + income: 93200, + }, + { + percentOfAmi: 50, + householdSize: 5, + income: 100700, + }, + { + percentOfAmi: 50, + householdSize: 6, + income: 108150, + }, + { + percentOfAmi: 50, + householdSize: 7, + income: 115600, + }, + { + percentOfAmi: 50, + householdSize: 8, + income: 123050, + }, + ], + jurisdictions: { + connect: { + id: jurisdictionId, + }, + }, + }, + include: { + jurisdictions: true, + }, + }); + }); + // | ---------- HELPER TESTS BELOW ---------- | // it('should mark script run as started if no script run present in db', async () => { prisma.scriptRuns.findUnique = jest.fn().mockResolvedValue(null); diff --git a/shared-helpers/src/types/backend-swagger.ts b/shared-helpers/src/types/backend-swagger.ts index 84d9b77919..d12342c11d 100644 --- a/shared-helpers/src/types/backend-swagger.ts +++ b/shared-helpers/src/types/backend-swagger.ts @@ -2059,6 +2059,28 @@ export class ScriptRunnerService { configs.data = data + axios(configs, resolve, reject) + }) + } + /** + * A script that takes in a standardized string and outputs the input for the ami chart create endpoint + */ + amiChartImport( + params: { + /** requestBody */ + body?: AmiChartImportDTO + } = {} as any, + options: IRequestOptions = {} + ): Promise { + return new Promise((resolve, reject) => { + let url = basePath + "/scriptRunner/amiChartImport" + + const configs: IRequestConfig = getConfigs("put", "application/json", url, options) + + let data = params.body + + configs.data = data + axios(configs, resolve, reject) }) } @@ -5262,6 +5284,17 @@ export interface BulkApplicationResendDTO { listingId: string } +export interface AmiChartImportDTO { + /** */ + values: string + + /** */ + name: string + + /** */ + jurisdictionId: string +} + export enum ListingViews { "fundamentals" = "fundamentals", "base" = "base",