페이지 화면 수정 및 dockerfile 수정

This commit is contained in:
2025-12-10 12:02:40 +09:00
parent 83dc4c86da
commit 6731eec802
11 changed files with 931 additions and 115 deletions

0
backend/src/gene/dto Normal file
View File

View File

@@ -0,0 +1,113 @@
import { BaseModel } from 'src/common/entities/base.entity';
import { GenomeRequestModel } from 'src/genome/entities/genome-request.entity';
import {
Column,
Entity,
Index,
JoinColumn,
ManyToOne,
PrimaryGeneratedColumn,
} from 'typeorm';
/**
* 유전자 상세 정보 (tb_gene_detail)
* 1개체당 N개 SNP 마커 결과 저장
* 예: KOR002108023350, 1:110900379, 1, 110900379, [A/T], A, A, 설명
*/
@Entity({ name: 'tb_gene_detail' })
@Index('idx_gene_detail_cow_id', ['cowId'])
@Index('idx_gene_detail_snp_name', ['snpName'])
@Index('idx_gene_detail_cow_snp', ['cowId', 'snpName'])
export class GeneDetailModel extends BaseModel {
@PrimaryGeneratedColumn({
name: 'pk_gene_detail_no',
type: 'int',
comment: '유전자상세번호 PK',
})
pkGeneDetailNo: number;
@Column({
name: 'fk_request_no',
type: 'int',
nullable: true,
comment: '의뢰번호 FK',
})
fkRequestNo: number;
@Column({
name: 'cow_id',
type: 'varchar',
length: 20,
nullable: true,
comment: '개체식별번호 (KOR)',
})
cowId: string;
@Column({
name: 'snp_name',
type: 'varchar',
length: 100,
nullable: true,
comment: 'SNP 이름 (예: 1:110900379)',
})
snpName: string;
@Column({
name: 'chromosome',
type: 'varchar',
length: 10,
nullable: true,
comment: '염색체 위치 (Chr)',
})
chromosome: string;
@Column({
name: 'position',
type: 'varchar',
length: 20,
nullable: true,
comment: '위치 (Position)',
})
position: string;
@Column({
name: 'snp_type',
type: 'varchar',
length: 20,
nullable: true,
comment: 'SNP 구분 (예: [A/T])',
})
snpType: string;
@Column({
name: 'allele1',
type: 'varchar',
length: 10,
nullable: true,
comment: '첫번째 대립유전자 (Allele1...Top)',
})
allele1: string;
@Column({
name: 'allele2',
type: 'varchar',
length: 10,
nullable: true,
comment: '두번째 대립유전자 (Allele2...Top)',
})
allele2: string;
@Column({
name: 'remarks',
type: 'varchar',
length: 500,
nullable: true,
comment: '비고 (설명)',
})
remarks: string;
// Relations
@ManyToOne(() => GenomeRequestModel, { onDelete: 'CASCADE' })
@JoinColumn({ name: 'fk_request_no' })
genomeRequest: GenomeRequestModel;
}

View File

@@ -1,7 +1,66 @@
import { Controller } from '@nestjs/common';
import { Controller, Get, Param, Post, Body } from '@nestjs/common';
import { GeneService } from './gene.service';
import { GeneDetailModel } from './entities/gene-detail.entity';
@Controller('gene')
export class GeneController {
constructor(private readonly geneService: GeneService) {}
/**
* 개체식별번호로 유전자 상세 정보 조회
* GET /gene/:cowId
*/
@Get(':cowId')
async findByCowId(@Param('cowId') cowId: string): Promise<GeneDetailModel[]> {
return this.geneService.findByCowId(cowId);
}
/**
* 개체별 유전자 요약 정보 조회
* GET /gene/summary/:cowId
*/
@Get('summary/:cowId')
async getGeneSummary(@Param('cowId') cowId: string): Promise<{
total: number;
homozygousCount: number;
heterozygousCount: number;
}> {
return this.geneService.getGeneSummary(cowId);
}
/**
* 의뢰번호로 유전자 상세 정보 조회
* GET /gene/request/:requestNo
*/
@Get('request/:requestNo')
async findByRequestNo(@Param('requestNo') requestNo: number): Promise<GeneDetailModel[]> {
return this.geneService.findByRequestNo(requestNo);
}
/**
* 유전자 상세 정보 단건 조회
* GET /gene/detail/:geneDetailNo
*/
@Get('detail/:geneDetailNo')
async findOne(@Param('geneDetailNo') geneDetailNo: number): Promise<GeneDetailModel> {
return this.geneService.findOne(geneDetailNo);
}
/**
* 유전자 상세 정보 생성
* POST /gene
*/
@Post()
async create(@Body() data: Partial<GeneDetailModel>): Promise<GeneDetailModel> {
return this.geneService.create(data);
}
/**
* 유전자 상세 정보 일괄 생성
* POST /gene/bulk
*/
@Post('bulk')
async createBulk(@Body() dataList: Partial<GeneDetailModel>[]): Promise<GeneDetailModel[]> {
return this.geneService.createBulk(dataList);
}
}

View File

@@ -1,9 +1,16 @@
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { GeneService } from './gene.service';
import { GeneController } from './gene.controller';
import { GeneDetailModel } from './entities/gene-detail.entity';
import { GenomeRequestModel } from '../genome/entities/genome-request.entity';
@Module({
imports: [
TypeOrmModule.forFeature([GeneDetailModel, GenomeRequestModel]),
],
controllers: [GeneController],
providers: [GeneService],
exports: [GeneService],
})
export class GeneModule {}

View File

@@ -1,4 +1,129 @@
import { Injectable } from '@nestjs/common';
import { Injectable, NotFoundException } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { IsNull, Repository } from 'typeorm';
import { GeneDetailModel } from './entities/gene-detail.entity';
import { GenomeRequestModel } from '../genome/entities/genome-request.entity';
@Injectable()
export class GeneService {}
export class GeneService {
constructor(
@InjectRepository(GeneDetailModel)
private readonly geneDetailRepository: Repository<GeneDetailModel>,
@InjectRepository(GenomeRequestModel)
private readonly genomeRequestRepository: Repository<GenomeRequestModel>,
) {}
/**
* 개체식별번호(cowId)로 유전자 상세 정보 조회
* @param cowId 개체식별번호 (KOR...)
* @returns 유전자 상세 정보 배열
*/
async findByCowId(cowId: string): Promise<GeneDetailModel[]> {
const results = await this.geneDetailRepository.find({
where: {
cowId,
delDt: IsNull(),
},
relations: ['genomeRequest'],
order: {
chromosome: 'ASC',
position: 'ASC',
},
});
return results;
}
/**
* 의뢰번호(requestNo)로 유전자 상세 정보 조회
* @param requestNo 의뢰번호
* @returns 유전자 상세 정보 배열
*/
async findByRequestNo(requestNo: number): Promise<GeneDetailModel[]> {
const results = await this.geneDetailRepository.find({
where: {
fkRequestNo: requestNo,
delDt: IsNull(),
},
order: {
chromosome: 'ASC',
position: 'ASC',
},
});
return results;
}
/**
* 개체별 유전자 요약 정보 조회
* @param cowId 개체식별번호
* @returns 동형접합/이형접합 개수 요약
*/
async getGeneSummary(cowId: string): Promise<{
total: number;
homozygousCount: number;
heterozygousCount: number;
}> {
const geneDetails = await this.findByCowId(cowId);
let homozygousCount = 0;
let heterozygousCount = 0;
geneDetails.forEach((gene) => {
if (gene.allele1 && gene.allele2) {
if (gene.allele1 === gene.allele2) {
homozygousCount++;
} else {
heterozygousCount++;
}
}
});
return {
total: geneDetails.length,
homozygousCount,
heterozygousCount,
};
}
/**
* 유전자 상세 정보 단건 조회
* @param geneDetailNo 유전자상세번호
* @returns 유전자 상세 정보
*/
async findOne(geneDetailNo: number): Promise<GeneDetailModel> {
const result = await this.geneDetailRepository.findOne({
where: {
pkGeneDetailNo: geneDetailNo,
delDt: IsNull(),
},
relations: ['genomeRequest'],
});
if (!result) {
throw new NotFoundException(`유전자 상세 정보를 찾을 수 없습니다. (geneDetailNo: ${geneDetailNo})`);
}
return result;
}
/**
* 유전자 상세 정보 생성
* @param data 생성할 데이터
* @returns 생성된 유전자 상세 정보
*/
async create(data: Partial<GeneDetailModel>): Promise<GeneDetailModel> {
const geneDetail = this.geneDetailRepository.create(data);
return await this.geneDetailRepository.save(geneDetail);
}
/**
* 유전자 상세 정보 일괄 생성
* @param dataList 생성할 데이터 배열
* @returns 생성된 유전자 상세 정보 배열
*/
async createBulk(dataList: Partial<GeneDetailModel>[]): Promise<GeneDetailModel[]> {
const geneDetails = this.geneDetailRepository.create(dataList);
return await this.geneDetailRepository.save(geneDetails);
}
}

0
backend/src/genome/dto Normal file
View File