setup project nest js with init icd codes

This commit is contained in:
arifal
2025-08-21 23:31:05 +07:00
commit 21567a0a7c
24 changed files with 11629 additions and 0 deletions

View 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
View 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
View 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
View File

@@ -0,0 +1,8 @@
import { Injectable } from '@nestjs/common';
@Injectable()
export class AppService {
getHello(): string {
return 'Hello World!';
}
}

View 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
View 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
View 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 {}

View 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
View 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
View 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();