setup project nest js with init icd codes
This commit is contained in:
22
src/app.controller.spec.ts
Normal file
22
src/app.controller.spec.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
import { Test, TestingModule } from '@nestjs/testing';
|
||||
import { AppController } from './app.controller';
|
||||
import { AppService } from './app.service';
|
||||
|
||||
describe('AppController', () => {
|
||||
let appController: AppController;
|
||||
|
||||
beforeEach(async () => {
|
||||
const app: TestingModule = await Test.createTestingModule({
|
||||
controllers: [AppController],
|
||||
providers: [AppService],
|
||||
}).compile();
|
||||
|
||||
appController = app.get<AppController>(AppController);
|
||||
});
|
||||
|
||||
describe('root', () => {
|
||||
it('should return "Hello World!"', () => {
|
||||
expect(appController.getHello()).toBe('Hello World!');
|
||||
});
|
||||
});
|
||||
});
|
||||
12
src/app.controller.ts
Normal file
12
src/app.controller.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
import { Controller, Get } from '@nestjs/common';
|
||||
import { AppService } from './app.service';
|
||||
|
||||
@Controller()
|
||||
export class AppController {
|
||||
constructor(private readonly appService: AppService) {}
|
||||
|
||||
@Get()
|
||||
getHello(): string {
|
||||
return this.appService.getHello();
|
||||
}
|
||||
}
|
||||
11
src/app.module.ts
Normal file
11
src/app.module.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { AppController } from './app.controller';
|
||||
import { AppService } from './app.service';
|
||||
import { IcdModule } from './icd/icd.module';
|
||||
|
||||
@Module({
|
||||
imports: [IcdModule],
|
||||
controllers: [AppController],
|
||||
providers: [AppService],
|
||||
})
|
||||
export class AppModule {}
|
||||
8
src/app.service.ts
Normal file
8
src/app.service.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
|
||||
@Injectable()
|
||||
export class AppService {
|
||||
getHello(): string {
|
||||
return 'Hello World!';
|
||||
}
|
||||
}
|
||||
6
src/icd/dto/search-icd.dto.ts
Normal file
6
src/icd/dto/search-icd.dto.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
export class SearchIcdDto {
|
||||
category?: 'ICD9' | 'ICD10';
|
||||
search?: string;
|
||||
page?: number;
|
||||
limit?: number;
|
||||
}
|
||||
80
src/icd/icd.controller.ts
Normal file
80
src/icd/icd.controller.ts
Normal file
@@ -0,0 +1,80 @@
|
||||
import { Controller, Get, Post, Query, Logger } from '@nestjs/common';
|
||||
import { IcdService } from './icd.service';
|
||||
import { SearchIcdDto } from './dto/search-icd.dto';
|
||||
|
||||
@Controller('icd')
|
||||
export class IcdController {
|
||||
private readonly logger = new Logger(IcdController.name);
|
||||
|
||||
constructor(private readonly icdService: IcdService) {}
|
||||
|
||||
@Post('import')
|
||||
async importData() {
|
||||
try {
|
||||
this.logger.log('Starting ICD data import...');
|
||||
const result = await this.icdService.importIcdData();
|
||||
return {
|
||||
success: true,
|
||||
message: 'ICD data imported successfully',
|
||||
data: result,
|
||||
};
|
||||
} catch (error) {
|
||||
this.logger.error('Error importing ICD data:', error);
|
||||
return {
|
||||
success: false,
|
||||
message: 'Failed to import ICD data',
|
||||
error: error.message,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@Get('search')
|
||||
async searchIcdCodes(
|
||||
@Query('category') category?: string,
|
||||
@Query('search') search?: string,
|
||||
@Query('page') page?: string,
|
||||
@Query('limit') limit?: string,
|
||||
) {
|
||||
try {
|
||||
const pageNum = page ? parseInt(page, 10) : 1;
|
||||
const limitNum = limit ? parseInt(limit, 10) : 10;
|
||||
|
||||
const result = await this.icdService.findIcdCodes(
|
||||
category,
|
||||
search,
|
||||
pageNum,
|
||||
limitNum,
|
||||
);
|
||||
|
||||
return {
|
||||
success: true,
|
||||
...result,
|
||||
};
|
||||
} catch (error) {
|
||||
this.logger.error('Error searching ICD codes:', error);
|
||||
return {
|
||||
success: false,
|
||||
message: 'Failed to search ICD codes',
|
||||
error: error.message,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@Get('statistics')
|
||||
async getStatistics() {
|
||||
try {
|
||||
const stats = await this.icdService.getStatistics();
|
||||
return {
|
||||
success: true,
|
||||
data: stats,
|
||||
};
|
||||
} catch (error) {
|
||||
this.logger.error('Error getting statistics:', error);
|
||||
return {
|
||||
success: false,
|
||||
message: 'Failed to get statistics',
|
||||
error: error.message,
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
10
src/icd/icd.module.ts
Normal file
10
src/icd/icd.module.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { IcdController } from './icd.controller';
|
||||
import { IcdService } from './icd.service';
|
||||
|
||||
@Module({
|
||||
controllers: [IcdController],
|
||||
providers: [IcdService],
|
||||
exports: [IcdService],
|
||||
})
|
||||
export class IcdModule {}
|
||||
25
src/icd/icd.service.spec.ts
Normal file
25
src/icd/icd.service.spec.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
import { Test, TestingModule } from '@nestjs/testing';
|
||||
import { IcdService } from './icd.service';
|
||||
|
||||
describe('IcdService', () => {
|
||||
let service: IcdService;
|
||||
|
||||
beforeEach(async () => {
|
||||
const module: TestingModule = await Test.createTestingModule({
|
||||
providers: [IcdService],
|
||||
}).compile();
|
||||
|
||||
service = module.get<IcdService>(IcdService);
|
||||
});
|
||||
|
||||
it('should be defined', () => {
|
||||
expect(service).toBeDefined();
|
||||
});
|
||||
|
||||
describe('cleanString', () => {
|
||||
it('should clean string properly', () => {
|
||||
// Test private method indirectly through public methods
|
||||
expect(service).toBeDefined();
|
||||
});
|
||||
});
|
||||
});
|
||||
223
src/icd/icd.service.ts
Normal file
223
src/icd/icd.service.ts
Normal file
@@ -0,0 +1,223 @@
|
||||
import { Injectable, Logger } from '@nestjs/common';
|
||||
import { PrismaClient } from '../../generated/prisma';
|
||||
import * as XLSX from 'xlsx';
|
||||
import * as path from 'path';
|
||||
import * as fs from 'fs';
|
||||
|
||||
interface IcdData {
|
||||
code: string;
|
||||
display: string;
|
||||
version: string;
|
||||
}
|
||||
|
||||
@Injectable()
|
||||
export class IcdService {
|
||||
private readonly logger = new Logger(IcdService.name);
|
||||
private readonly prisma = new PrismaClient();
|
||||
|
||||
async importIcdData(): Promise<{
|
||||
icd9Count: number;
|
||||
icd10Count: number;
|
||||
total: number;
|
||||
}> {
|
||||
try {
|
||||
this.logger.log('Starting ICD data import...');
|
||||
|
||||
// Import ICD-9 data
|
||||
const icd9Data = await this.readExcelFile(
|
||||
'test/[PUBLIC] ICD-9CM e-klaim.xlsx',
|
||||
'ICD9',
|
||||
);
|
||||
|
||||
// Import ICD-10 data
|
||||
const icd10Data = await this.readExcelFile(
|
||||
'test/[PUBLIC] ICD-10 e-klaim.xlsx',
|
||||
'ICD10',
|
||||
);
|
||||
|
||||
// Clear existing data
|
||||
await this.prisma.icdCode.deleteMany({});
|
||||
this.logger.log('Cleared existing ICD data');
|
||||
|
||||
// Insert ICD-9 data
|
||||
const icd9Count = await this.bulkInsertData(icd9Data, 'ICD9');
|
||||
this.logger.log(`Imported ${icd9Count} ICD-9 codes`);
|
||||
|
||||
// Insert ICD-10 data
|
||||
const icd10Count = await this.bulkInsertData(icd10Data, 'ICD10');
|
||||
this.logger.log(`Imported ${icd10Count} ICD-10 codes`);
|
||||
|
||||
const total = icd9Count + icd10Count;
|
||||
this.logger.log(`Total imported: ${total} ICD codes`);
|
||||
|
||||
return {
|
||||
icd9Count,
|
||||
icd10Count,
|
||||
total,
|
||||
};
|
||||
} catch (error) {
|
||||
this.logger.error('Error importing ICD data:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
private async readExcelFile(
|
||||
filePath: string,
|
||||
category: string,
|
||||
): Promise<IcdData[]> {
|
||||
try {
|
||||
const fullPath = path.join(process.cwd(), filePath);
|
||||
|
||||
if (!fs.existsSync(fullPath)) {
|
||||
throw new Error(`File not found: ${fullPath}`);
|
||||
}
|
||||
|
||||
this.logger.log(`Reading ${category} file: ${filePath}`);
|
||||
|
||||
const workbook = XLSX.readFile(fullPath);
|
||||
const sheetName = workbook.SheetNames[0];
|
||||
const worksheet = workbook.Sheets[sheetName];
|
||||
|
||||
// Convert sheet to JSON
|
||||
const jsonData = XLSX.utils.sheet_to_json(worksheet, { header: 1 });
|
||||
|
||||
// Skip header row and process data
|
||||
const icdData: IcdData[] = [];
|
||||
|
||||
for (let i = 1; i < jsonData.length; i++) {
|
||||
const row = jsonData[i] as any[];
|
||||
|
||||
if (row && row.length >= 3) {
|
||||
const code = this.cleanString(row[0]);
|
||||
const display = this.cleanString(row[1]);
|
||||
const version = this.cleanString(row[2]);
|
||||
|
||||
if (code && display && version) {
|
||||
icdData.push({
|
||||
code,
|
||||
display,
|
||||
version,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.logger.log(`Found ${icdData.length} valid ${category} records`);
|
||||
return icdData;
|
||||
} catch (error) {
|
||||
this.logger.error(`Error reading ${category} file:`, error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
private async bulkInsertData(
|
||||
data: IcdData[],
|
||||
category: string,
|
||||
): Promise<number> {
|
||||
try {
|
||||
const batchSize = 1000;
|
||||
let totalInserted = 0;
|
||||
|
||||
for (let i = 0; i < data.length; i += batchSize) {
|
||||
const batch = data.slice(i, i + batchSize);
|
||||
|
||||
const insertData = batch.map((item) => ({
|
||||
code: item.code,
|
||||
display: item.display,
|
||||
version: item.version,
|
||||
category,
|
||||
}));
|
||||
|
||||
await this.prisma.icdCode.createMany({
|
||||
data: insertData,
|
||||
skipDuplicates: true,
|
||||
});
|
||||
|
||||
totalInserted += batch.length;
|
||||
this.logger.log(
|
||||
`Inserted batch ${Math.floor(i / batchSize) + 1} for ${category}: ${batch.length} records`,
|
||||
);
|
||||
}
|
||||
|
||||
return totalInserted;
|
||||
} catch (error) {
|
||||
this.logger.error(`Error inserting ${category} data:`, error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
private cleanString(value: any): string {
|
||||
if (value === null || value === undefined) {
|
||||
return '';
|
||||
}
|
||||
return String(value).trim();
|
||||
}
|
||||
|
||||
async findIcdCodes(
|
||||
category?: string,
|
||||
search?: string,
|
||||
page: number = 1,
|
||||
limit: number = 10,
|
||||
) {
|
||||
try {
|
||||
const where: any = {};
|
||||
|
||||
if (category) {
|
||||
where.category = category;
|
||||
}
|
||||
|
||||
if (search) {
|
||||
where.OR = [
|
||||
{ code: { contains: search, mode: 'insensitive' } },
|
||||
{ display: { contains: search, mode: 'insensitive' } },
|
||||
];
|
||||
}
|
||||
|
||||
const skip = (page - 1) * limit;
|
||||
|
||||
const [data, total] = await Promise.all([
|
||||
this.prisma.icdCode.findMany({
|
||||
where,
|
||||
skip,
|
||||
take: limit,
|
||||
orderBy: { code: 'asc' },
|
||||
}),
|
||||
this.prisma.icdCode.count({ where }),
|
||||
]);
|
||||
|
||||
return {
|
||||
data,
|
||||
total,
|
||||
page,
|
||||
limit,
|
||||
totalPages: Math.ceil(total / limit),
|
||||
};
|
||||
} catch (error) {
|
||||
this.logger.error('Error finding ICD codes:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
async getStatistics() {
|
||||
try {
|
||||
const [icd9Count, icd10Count, total] = await Promise.all([
|
||||
this.prisma.icdCode.count({ where: { category: 'ICD9' } }),
|
||||
this.prisma.icdCode.count({ where: { category: 'ICD10' } }),
|
||||
this.prisma.icdCode.count(),
|
||||
]);
|
||||
|
||||
return {
|
||||
icd9Count,
|
||||
icd10Count,
|
||||
total,
|
||||
};
|
||||
} catch (error) {
|
||||
this.logger.error('Error getting statistics:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
async onModuleDestroy() {
|
||||
await this.prisma.$disconnect();
|
||||
}
|
||||
}
|
||||
8
src/main.ts
Normal file
8
src/main.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
import { NestFactory } from '@nestjs/core';
|
||||
import { AppModule } from './app.module';
|
||||
|
||||
async function bootstrap() {
|
||||
const app = await NestFactory.create(AppModule);
|
||||
await app.listen(process.env.PORT ?? 3000);
|
||||
}
|
||||
bootstrap();
|
||||
Reference in New Issue
Block a user