개체분석 상태 값 수정
This commit is contained in:
146
backend/doc/검사가능조건요약.md
Normal file
146
backend/doc/검사가능조건요약.md
Normal file
@@ -0,0 +1,146 @@
|
||||
# 유전체/유전자 검사 가능 조건 요약
|
||||
|
||||
## 1. DB 상태값 정의
|
||||
|
||||
### chipSireName (아비명)
|
||||
|
||||
| DB 값 | 의미 | 분석 가능 여부 |
|
||||
|-------|------|----------------|
|
||||
| `일치` | 친자감별 일치 | 가능 |
|
||||
| `불일치` | 친자감별 불일치 | 유전체 불가 / 유전자 가능 |
|
||||
| `분석불가` | 모근 오염/불량 등 기타 사유 | 불가 |
|
||||
| `정보없음` | 개체 식별번호/형식 오류 | 불가 |
|
||||
| `null` | 미분석 (의뢰 없음) | - 표시 |
|
||||
|
||||
## - 아비명 가능한 개체에 대해서 어미명 판단 진행
|
||||
|
||||
### chipDamName (어미명)
|
||||
|
||||
| DB 값 | 의미 | 분석 가능 여부 |
|
||||
|-------|------|----------------|
|
||||
| `일치` | 친자감별 일치 | 통과 |
|
||||
| `불일치` | 친자감별 불일치 | 유전체 불가 / 유전자 가능 |
|
||||
| `이력제부재` | 모 이력제 정보 없음 | 유전체 불가 / 유전자 가능 |
|
||||
| `정보없음` | 정보 없음 | 통과 |
|
||||
| `null` | 정보 없음 | 통과 |
|
||||
|
||||
---
|
||||
|
||||
## 2. 탭별 검사 가능 조건
|
||||
|
||||
### 유전체 탭
|
||||
```
|
||||
유효 조건 (모두 충족해야 함):
|
||||
1. chipSireName === '일치'
|
||||
2. chipDamName !== '불일치'
|
||||
3. chipDamName !== '이력제부재'
|
||||
4. cowId가 EXCLUDED_COW_IDS에 포함되지 않음
|
||||
```
|
||||
|
||||
### 유전자 탭
|
||||
```
|
||||
유효 조건:
|
||||
1. chipSireName !== '분석불가'
|
||||
2. chipSireName !== '정보없음'
|
||||
3. cowId가 EXCLUDED_COW_IDS에 포함되지 않음
|
||||
|
||||
※ 불일치/이력제부재도 유전자 데이터가 있으면 표시 가능
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. 개체 목록 배지 표시 (unavailableReason)
|
||||
|
||||
### 분석일자 컬럼
|
||||
|
||||
| unavailableReason | 배지 색상 | 표시 텍스트 |
|
||||
|-------------------|-----------|-------------|
|
||||
| `null` | - | `-` |
|
||||
| `분석불가` | 회색 | 분석불가 |
|
||||
| `부 불일치` | 빨간색 | 부 불일치 |
|
||||
| `모 불일치` | 주황색 | 모 불일치 |
|
||||
| `모 이력제부재` | 주황색 | 모 이력제부재 |
|
||||
| `형질정보없음` | 회색 | 형질정보없음 |
|
||||
|
||||
### unavailableReason 결정 로직 (cow.service.ts)
|
||||
|
||||
```typescript
|
||||
if (!latestRequest || !latestRequest.chipSireName) {
|
||||
unavailableReason = null; // '-' 표시
|
||||
} else if (chipSireName === '분석불가' || chipSireName === '정보없음') {
|
||||
unavailableReason = '분석불가';
|
||||
} else if (chipSireName !== '일치') {
|
||||
unavailableReason = '부 불일치';
|
||||
} else if (chipDamName === '불일치') {
|
||||
unavailableReason = '모 불일치';
|
||||
} else if (chipDamName === '이력제부재') {
|
||||
unavailableReason = '모 이력제부재';
|
||||
}
|
||||
|
||||
// 형질 데이터 없으면
|
||||
unavailableReason = '형질정보없음';
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. 개체 상세 페이지 배지
|
||||
|
||||
### 부 KPN 배지 (renderSireBadge)
|
||||
|
||||
| 조건 | 배지 색상 | 표시 |
|
||||
|------|-----------|------|
|
||||
| `EXCLUDED_COW_IDS` 포함 | 회색 | 분석불가 |
|
||||
| `chipSireName === '분석불가'` | 회색 | 분석불가 |
|
||||
| `chipSireName === '정보없음'` | 회색 | 분석불가 |
|
||||
| `chipSireName === '일치'` | 초록색 | 일치 |
|
||||
| 그 외 | 빨간색 | 불일치 |
|
||||
| `null` | - | 표시 안 함 |
|
||||
|
||||
### 모 개체 배지 (renderDamBadge)
|
||||
|
||||
| 조건 | 배지 색상 | 표시 |
|
||||
|------|-----------|------|
|
||||
| `chipDamName === '일치'` | 초록색 | 일치 |
|
||||
| `chipDamName === '불일치'` | 빨간색 | 불일치 |
|
||||
| `chipDamName === '이력제부재'` | 주황색 | 이력제부재 |
|
||||
| 그 외/null | - | 표시 안 함 |
|
||||
|
||||
---
|
||||
|
||||
## 5. 분석불가 안내 문구
|
||||
|
||||
| 상태 | 안내 문구 |
|
||||
|------|-----------|
|
||||
| `분석불가` (DB) | 모근 오염 및 불량 등 기타 사유로 유전체 분석 보고서를 제공할 수 없습니다. |
|
||||
| `정보없음` (DB) | 개체 식별번호 및 형식오류로 유전체 분석 보고서를 제공할 수 없습니다. |
|
||||
| `부 불일치` | 부 친자감별 결과가 불일치하여 유전체 분석 보고서를 제공할 수 없습니다. |
|
||||
| `모 불일치` | 모 친자감별 결과가 불일치하여 유전체 분석 보고서를 제공할 수 없습니다. |
|
||||
| `모 이력제부재` | 모 이력제 정보가 부재하여 유전체 분석 보고서를 제공할 수 없습니다. |
|
||||
| `EXCLUDED_COW_IDS` | 모근 오염 및 불량 등 기타 사유로 유전체 분석 보고서를 제공할 수 없습니다. |
|
||||
|
||||
---
|
||||
|
||||
## 6. 관련 파일
|
||||
|
||||
### 백엔드
|
||||
- `backend/src/common/config/GenomeAnalysisConfig.ts` - 유효성 검사 함수
|
||||
- `backend/src/cow/cow.service.ts` - unavailableReason 결정 로직
|
||||
|
||||
### 프론트엔드
|
||||
- `frontend/src/lib/utils/genome-analysis-config.ts` - 유효성 검사, 메시지 함수
|
||||
- `frontend/src/app/cow/page.tsx` - 개체 목록 배지
|
||||
- `frontend/src/app/cow/[cowNo]/page.tsx` - 개체 상세 배지, 탭 조건
|
||||
|
||||
---
|
||||
|
||||
## 7. 제외 개체 목록 (EXCLUDED_COW_IDS)
|
||||
|
||||
특수 사유로 분석 불가한 개체를 하드코딩으로 관리:
|
||||
|
||||
```typescript
|
||||
export const EXCLUDED_COW_IDS = [
|
||||
'KOR002191642861', // 모근상태 불량으로 인한 DNA분해
|
||||
];
|
||||
```
|
||||
|
||||
> 이 목록에 포함된 개체는 유전체/유전자 탭 모두 분석불가로 처리됨
|
||||
@@ -64,9 +64,15 @@ export function isValidGenomeAnalysis(
|
||||
chipDamName: string | null | undefined,
|
||||
cowId?: string | null,
|
||||
): boolean {
|
||||
// 개별 제외 개체만 확인 (부/모 불일치여도 유전자 데이터 있으면 표시)
|
||||
// 1. 개별 제외 개체 확인
|
||||
if (cowId && EXCLUDED_COW_IDS.includes(cowId)) return false;
|
||||
|
||||
// 2. 아비명이 '일치'가 아니면 무효 (null, 불일치, 분석불가, 정보없음 등)
|
||||
if (chipSireName !== VALID_CHIP_SIRE_NAME) return false;
|
||||
|
||||
// 3. 어미명이 '불일치' 또는 '이력제부재'면 무효
|
||||
if (chipDamName && INVALID_CHIP_DAM_NAMES.includes(chipDamName)) return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -45,7 +45,7 @@ export const MPT_NORMAL_RANGES = {
|
||||
* 총 글로불린 (Total Globulin)
|
||||
* 단위: g/dL
|
||||
*/
|
||||
totalGlobulin: { min: 9.1, max: 36.1 },
|
||||
globulin: { min: 9.1, max: 36.1 },
|
||||
|
||||
/**
|
||||
* A/G 비율 (Albumin/Globulin Ratio)
|
||||
@@ -75,7 +75,7 @@ export const MPT_NORMAL_RANGES = {
|
||||
* 지방간 지수 (Fatty Liver Index)
|
||||
* 단위: 지수
|
||||
*/
|
||||
fattyLiverIndex: { min: -1.2, max: 9.9 },
|
||||
fattyLiverIdx: { min: -1.2, max: 9.9 },
|
||||
|
||||
/**
|
||||
* 칼슘 (Calcium)
|
||||
@@ -103,10 +103,10 @@ export const MPT_NORMAL_RANGES = {
|
||||
|
||||
// ========== 기타 카테고리 ==========
|
||||
/**
|
||||
* 크레아티닌 (Creatinine)
|
||||
* 크레아틴 (Creatine)
|
||||
* 단위: mg/dL
|
||||
*/
|
||||
creatinine: { min: 1.0, max: 1.3 },
|
||||
creatine: { min: 1.0, max: 1.3 },
|
||||
} as const;
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,105 +0,0 @@
|
||||
/**
|
||||
* 추천 시스템 설정 상수
|
||||
*
|
||||
* @description
|
||||
* KPN 추천, 개체 추천, 패키지 추천 등 추천 시스템 관련 설정값
|
||||
*
|
||||
* @source PRD 기능요구사항20.md SFR-COW-016, SFR-COW-037
|
||||
*/
|
||||
export const RECOMMENDATION_CONFIG = {
|
||||
/**
|
||||
* 유전자 매칭 점수 관련
|
||||
*/
|
||||
GENE_SCORE: {
|
||||
/**
|
||||
* 점수 차이 임계값
|
||||
* 유전자 매칭 점수 차이가 이 값보다 작으면 근친도를 우선 고려
|
||||
*/
|
||||
DIFF_THRESHOLD: 5,
|
||||
},
|
||||
|
||||
/**
|
||||
* 기본값
|
||||
*/
|
||||
DEFAULTS: {
|
||||
/**
|
||||
* 근친도 임계값 (%)
|
||||
* Wright's Coefficient 기준
|
||||
*/
|
||||
INBREEDING_THRESHOLD: 12.5,
|
||||
|
||||
/**
|
||||
* 추천 개수
|
||||
* 상위 N개의 KPN/개체를 추천
|
||||
*/
|
||||
RECOMMENDATION_LIMIT: 10,
|
||||
|
||||
/**
|
||||
* 세대제약 기준
|
||||
* 최근 N세대 이내 사용된 KPN을 추천에서 제외
|
||||
*/
|
||||
GENERATION_THRESHOLD: 3,
|
||||
},
|
||||
|
||||
/**
|
||||
* KPN 패키지 설정
|
||||
*/
|
||||
PACKAGE: {
|
||||
/**
|
||||
* 기본 패키지 크기
|
||||
* 추천할 KPN 세트 개수
|
||||
*/
|
||||
DEFAULT_SIZE: 5,
|
||||
|
||||
/**
|
||||
* 최소 패키지 크기
|
||||
*/
|
||||
MIN_SIZE: 3,
|
||||
|
||||
/**
|
||||
* 최대 패키지 크기
|
||||
*/
|
||||
MAX_SIZE: 10,
|
||||
},
|
||||
|
||||
/**
|
||||
* 커버리지 기준 (%)
|
||||
* 유전자 목표 달성률 평가 기준
|
||||
*/
|
||||
COVERAGE: {
|
||||
/**
|
||||
* 우수 기준
|
||||
* 50% 이상 커버리지
|
||||
*/
|
||||
EXCELLENT: 50,
|
||||
|
||||
/**
|
||||
* 양호 기준
|
||||
* 30% 이상 커버리지
|
||||
*/
|
||||
GOOD: 30,
|
||||
|
||||
/**
|
||||
* 최소 기준
|
||||
* 20% 이상 커버리지
|
||||
*/
|
||||
MINIMUM: 20,
|
||||
},
|
||||
|
||||
/**
|
||||
* KPN 순환 전략
|
||||
*/
|
||||
ROTATION: {
|
||||
/**
|
||||
* 최소 KPN 개수
|
||||
* 순환 전략 적용 최소 개수
|
||||
*/
|
||||
MIN_KPN_COUNT: 3,
|
||||
|
||||
/**
|
||||
* 재사용 안전 세대
|
||||
* 동일 KPN을 이 세대 이후에 재사용 가능
|
||||
*/
|
||||
SAFE_REUSE_GENERATION: 4,
|
||||
},
|
||||
} as const;
|
||||
@@ -19,6 +19,7 @@ import { CowService } from './cow.service';
|
||||
import { CowModel } from './entities/cow.entity';
|
||||
import { GenomeRequestModel } from '../genome/entities/genome-request.entity';
|
||||
import { GenomeTraitDetailModel } from '../genome/entities/genome-trait-detail.entity';
|
||||
import { GeneDetailModel } from '../gene/entities/gene-detail.entity';
|
||||
import { FilterEngineModule } from '../shared/filter/filter-engine.module';
|
||||
|
||||
@Module({
|
||||
@@ -27,6 +28,7 @@ import { FilterEngineModule } from '../shared/filter/filter-engine.module';
|
||||
CowModel, // 개체 기본 정보 (tb_cow)
|
||||
GenomeRequestModel, // 유전체 분석 의뢰 (tb_genome_request)
|
||||
GenomeTraitDetailModel, // 유전체 형질 상세 (tb_genome_trait_detail)
|
||||
GeneDetailModel, // 유전자 상세 (tb_gene_detail)
|
||||
]),
|
||||
FilterEngineModule, // 필터 엔진 모듈
|
||||
],
|
||||
|
||||
@@ -19,6 +19,7 @@ import { Repository, IsNull } from 'typeorm';
|
||||
import { CowModel } from './entities/cow.entity';
|
||||
import { GenomeRequestModel } from '../genome/entities/genome-request.entity';
|
||||
import { GenomeTraitDetailModel } from '../genome/entities/genome-trait-detail.entity';
|
||||
import { GeneDetailModel } from '../gene/entities/gene-detail.entity';
|
||||
import { FilterEngineService } from '../shared/filter/filter-engine.service';
|
||||
import {
|
||||
RankingRequestDto,
|
||||
@@ -57,6 +58,10 @@ export class CowService {
|
||||
@InjectRepository(GenomeTraitDetailModel)
|
||||
private readonly genomeTraitDetailRepository: Repository<GenomeTraitDetailModel>,
|
||||
|
||||
// 유전자 상세 Repository (SNP 데이터 접근용)
|
||||
@InjectRepository(GeneDetailModel)
|
||||
private readonly geneDetailRepository: Repository<GeneDetailModel>,
|
||||
|
||||
// 동적 필터링 서비스 (검색, 정렬, 페이지네이션)
|
||||
private readonly filterEngineService: FilterEngineService,
|
||||
) { }
|
||||
@@ -116,10 +121,10 @@ export class CowService {
|
||||
* 개체식별번호(cowId)로 단건 조회
|
||||
*
|
||||
* @param cowId - 개체식별번호 (예: KOR002119144049)
|
||||
* @returns 개체 정보 (farm 포함)
|
||||
* @returns 개체 정보 (farm 포함) + dataStatus (데이터 존재 여부)
|
||||
* @throws NotFoundException - 개체를 찾을 수 없는 경우
|
||||
*/
|
||||
async findByCowId(cowId: string): Promise<CowModel> {
|
||||
async findByCowId(cowId: string): Promise<CowModel & { dataStatus: { hasGenomeData: boolean; hasGeneData: boolean } }> {
|
||||
const cow = await this.cowRepository.findOne({
|
||||
where: { cowId: cowId, delDt: IsNull() },
|
||||
relations: ['farm'],
|
||||
@@ -127,7 +132,26 @@ export class CowService {
|
||||
if (!cow) {
|
||||
throw new NotFoundException(`Cow with cowId ${cowId} not found`);
|
||||
}
|
||||
return cow;
|
||||
|
||||
// 데이터 존재 여부 확인 (가벼운 COUNT 쿼리)
|
||||
const [genomeCount, geneCount] = await Promise.all([
|
||||
this.genomeTraitDetailRepository.count({
|
||||
where: { cowId, delDt: IsNull() },
|
||||
take: 1,
|
||||
}),
|
||||
this.geneDetailRepository.count({
|
||||
where: { cowId, delDt: IsNull() },
|
||||
take: 1,
|
||||
}),
|
||||
]);
|
||||
|
||||
return {
|
||||
...cow,
|
||||
dataStatus: {
|
||||
hasGenomeData: genomeCount > 0,
|
||||
hasGeneData: geneCount > 0,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
@@ -260,16 +284,23 @@ export class CowService {
|
||||
// Step 2: 친자감별 확인 - 유효하지 않으면 분석 불가
|
||||
if (!latestRequest || !isValidGenomeAnalysis(latestRequest.chipSireName, latestRequest.chipDamName, cow.cowId)) {
|
||||
// 분석불가 사유 결정
|
||||
let unavailableReason = '분석불가';
|
||||
if (latestRequest) {
|
||||
if (latestRequest.chipSireName !== '일치') {
|
||||
unavailableReason = '부 불일치';
|
||||
} else if (latestRequest.chipDamName === '불일치') {
|
||||
unavailableReason = '모 불일치';
|
||||
} else if (latestRequest.chipDamName === '이력제부재') {
|
||||
unavailableReason = '모 이력제부재';
|
||||
}
|
||||
let unavailableReason: string | null = null;
|
||||
|
||||
if (!latestRequest || !latestRequest.chipSireName) {
|
||||
// latestRequest 없거나 chipSireName이 null → '-' 표시 (프론트에서 null은 '-'로 표시)
|
||||
unavailableReason = null;
|
||||
} else if (latestRequest.chipSireName === '분석불가' || latestRequest.chipSireName === '정보없음') {
|
||||
// 분석불가, 정보없음 → 분석불가
|
||||
unavailableReason = '분석불가';
|
||||
} else if (latestRequest.chipSireName !== '일치') {
|
||||
// 불일치 등 그 외 → 부 불일치
|
||||
unavailableReason = '부 불일치';
|
||||
} else if (latestRequest.chipDamName === '불일치') {
|
||||
unavailableReason = '모 불일치';
|
||||
} else if (latestRequest.chipDamName === '이력제부재') {
|
||||
unavailableReason = '모 이력제부재';
|
||||
}
|
||||
|
||||
return { entity: { ...cow, unavailableReason }, sortValue: null, details: [] };
|
||||
}
|
||||
|
||||
|
||||
@@ -232,14 +232,14 @@ export class MptModel extends BaseModel {
|
||||
magnesium: number;
|
||||
|
||||
@Column({
|
||||
name: 'creatinine',
|
||||
name: 'creatine',
|
||||
type: 'decimal',
|
||||
precision: 10,
|
||||
scale: 2,
|
||||
nullable: true,
|
||||
comment: '크레아틴',
|
||||
})
|
||||
creatinine: number;
|
||||
creatine: number;
|
||||
|
||||
// Relations
|
||||
@ManyToOne(() => FarmModel, { onDelete: 'CASCADE' })
|
||||
|
||||
Reference in New Issue
Block a user