페이지 화면 수정 및 dockerfile 수정
This commit is contained in:
0
backend/src/gene/dto
Normal file
0
backend/src/gene/dto
Normal file
113
backend/src/gene/entities/gene-detail.entity.ts
Normal file
113
backend/src/gene/entities/gene-detail.entity.ts
Normal 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;
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 {}
|
||||
|
||||
@@ -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
0
backend/src/genome/dto
Normal file
Reference in New Issue
Block a user