UI 수정:화면 수정
This commit is contained in:
@@ -25,7 +25,10 @@ export const INVALID_CHIP_DAM_NAMES = ['불일치', '이력제부재'];
|
||||
|
||||
/** ===============================개별 제외 개체 목록 (분석불가 등 특수 사유) 하단 개체 정보없음 확인필요=================*/
|
||||
export const EXCLUDED_COW_IDS = [
|
||||
'KOR002191642861', // 1회 분석 반려내역서 재분석 불가능
|
||||
'KOR002191642861',
|
||||
// 일치인데 정보가 없음 / 김정태님 유전체 내역 빠짐 1두
|
||||
// 일단 모근 1회분량이고 재검사어려움 , 모근상태 불량으로 인한 DNA분해로 인해 분석불가 상태로 넣음
|
||||
|
||||
];
|
||||
//=================================================================================================================
|
||||
|
||||
|
||||
@@ -228,25 +228,36 @@ export class CowService {
|
||||
// 각 개체별로 점수 계산
|
||||
const cowsWithScore = await Promise.all(
|
||||
cows.map(async (cow) => {
|
||||
// Step 1: cowId로 직접 형질 상세 데이터 조회 (35개 형질의 EBV 값)
|
||||
const traitDetails = await this.genomeTraitDetailRepository.find({
|
||||
where: { cowId: cow.cowId, delDt: IsNull() },
|
||||
});
|
||||
|
||||
// 형질 데이터가 없으면 점수 null
|
||||
if (traitDetails.length === 0) {
|
||||
return { entity: cow, sortValue: null, details: [] };
|
||||
}
|
||||
|
||||
// Step 2: 해당 개체의 최신 유전체 분석 의뢰 조회 (친자감별 확인용)
|
||||
// Step 1: 해당 개체의 최신 유전체 분석 의뢰 조회 (친자감별 확인용)
|
||||
const latestRequest = await this.genomeRequestRepository.findOne({
|
||||
where: { fkCowNo: cow.pkCowNo, delDt: IsNull() },
|
||||
order: { requestDt: 'DESC', regDt: 'DESC' },
|
||||
});
|
||||
|
||||
// Step 3: 친자감별 확인 - 아비 KPN "일치"가 아니면 분석 불가
|
||||
// Step 2: 친자감별 확인 - 유효하지 않으면 분석 불가
|
||||
if (!latestRequest || !isValidGenomeAnalysis(latestRequest.chipSireName, latestRequest.chipDamName, cow.cowId)) {
|
||||
return { entity: cow, sortValue: null, details: [] };
|
||||
// 분석불가 사유 결정
|
||||
let unavailableReason = '미분석';
|
||||
if (latestRequest) {
|
||||
if (latestRequest.chipSireName !== '일치') {
|
||||
unavailableReason = '부 불일치';
|
||||
} else if (latestRequest.chipDamName === '불일치') {
|
||||
unavailableReason = '모 불일치';
|
||||
} else if (latestRequest.chipDamName === '이력제부재') {
|
||||
unavailableReason = '모 이력제부재';
|
||||
}
|
||||
}
|
||||
return { entity: { ...cow, unavailableReason }, sortValue: null, details: [] };
|
||||
}
|
||||
|
||||
// Step 3: cowId로 직접 형질 상세 데이터 조회 (35개 형질의 EBV 값)
|
||||
const traitDetails = await this.genomeTraitDetailRepository.find({
|
||||
where: { cowId: cow.cowId, delDt: IsNull() },
|
||||
});
|
||||
|
||||
// 형질 데이터가 없으면 점수 null (친자는 일치하지만 형질 데이터 없음)
|
||||
if (traitDetails.length === 0) {
|
||||
return { entity: { ...cow, unavailableReason: '형질정보없음' }, sortValue: null, details: [] };
|
||||
}
|
||||
|
||||
// Step 4: 가중 평균 계산
|
||||
|
||||
@@ -45,9 +45,12 @@ export class GenomeController {
|
||||
* 농가의 보은군 내 순위 조회 (대시보드용)
|
||||
* @param farmNo - 농장 번호
|
||||
*/
|
||||
@Get('farm-region-ranking/:farmNo')
|
||||
getFarmRegionRanking(@Param('farmNo') farmNo: string) {
|
||||
return this.genomeService.getFarmRegionRanking(+farmNo);
|
||||
@Post('farm-region-ranking/:farmNo')
|
||||
getFarmRegionRanking(
|
||||
@Param('farmNo') farmNo: string,
|
||||
@Body() body: { traitConditions?: { traitNm: string; weight?: number }[] }
|
||||
) {
|
||||
return this.genomeService.getFarmRegionRanking(+farmNo, body.traitConditions);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1322,13 +1322,18 @@ export class GenomeService {
|
||||
};
|
||||
}
|
||||
|
||||
// Step 4: 가중 평균 계산
|
||||
// Step 4: 가중 평균 계산 ================================================================================
|
||||
let weightedSum = 0; // Σ(EBV × 가중치)
|
||||
let totalWeight = 0; // Σ(가중치)
|
||||
let percentileSum = 0; // 백분위 합계 (평균 계산용)
|
||||
let percentileCount = 0; // 백분위 개수
|
||||
let hasAllTraits = true; // 모든 선택 형질 존재 여부 (리스트와 동일 로직)
|
||||
const details: { traitNm: string; ebv: number; weight: number; contribution: number }[] = [];
|
||||
const details: {
|
||||
traitNm: string;
|
||||
ebv: number;
|
||||
weight: number;
|
||||
contribution: number
|
||||
}[] = [];
|
||||
|
||||
for (const condition of traitConditions) {
|
||||
const trait = traitDetails.find((d) => d.traitName === condition.traitNm);
|
||||
@@ -1359,8 +1364,8 @@ export class GenomeService {
|
||||
}
|
||||
}
|
||||
|
||||
// Step 6: 최종 점수 계산 (모든 선택 형질이 있어야만 계산)
|
||||
const score = (hasAllTraits && totalWeight > 0) ? weightedSum / totalWeight : null;
|
||||
// Step 6: 최종 점수 계산 (모든 선택 형질이 있어야만 계산) ================================================================
|
||||
const score = (hasAllTraits && totalWeight > 0) ? weightedSum : null;
|
||||
const percentile = percentileCount > 0 ? percentileSum / percentileCount : null;
|
||||
|
||||
// Step 7: 현재 개체의 농장/지역 정보 조회
|
||||
@@ -1482,7 +1487,7 @@ export class GenomeService {
|
||||
|
||||
// 모든 선택 형질이 있는 경우만 점수에 포함
|
||||
if (hasAllTraits && totalWeight > 0) {
|
||||
const score = weightedSum / totalWeight;
|
||||
const score = weightedSum;
|
||||
allScores.push({
|
||||
cowId: request.cow.cowId,
|
||||
score: Math.round(score * 100) / 100,
|
||||
@@ -1494,6 +1499,7 @@ export class GenomeService {
|
||||
|
||||
// 점수 기준 내림차순 정렬
|
||||
allScores.sort((a, b) => b.score - a.score);
|
||||
console.log('[calculateRanks] 샘플 점수:', allScores.slice(0, 5).map(s => ({ cowId: s.cowId, score: s.score })));
|
||||
|
||||
// 농가 순위 및 평균 선발지수 계산
|
||||
let farmRank: number | null = null;
|
||||
@@ -1667,7 +1673,10 @@ export class GenomeService {
|
||||
* 성능 최적화: N+1 쿼리 문제 해결 - 모든 데이터를 한 번에 조회 후 메모리에서 처리
|
||||
* @param farmNo - 농장 번호
|
||||
*/
|
||||
async getFarmRegionRanking(farmNo: number): Promise<{
|
||||
async getFarmRegionRanking(
|
||||
farmNo: number,
|
||||
inputTraitConditions?: { traitNm: string; weight?: number }[]
|
||||
): Promise<{
|
||||
farmNo: number;
|
||||
farmerName: string | null;
|
||||
farmAvgScore: number | null;
|
||||
@@ -1728,7 +1737,12 @@ export class GenomeService {
|
||||
'안심rate', '등심rate', '채끝rate', '목심rate', '앞다리rate',
|
||||
'우둔rate', '설도rate', '사태rate', '양지rate', '갈비rate',
|
||||
];
|
||||
const traitConditions = ALL_TRAITS.map(traitNm => ({ traitNm, weight: 1 }));
|
||||
// inputTraitConditions가 있으면 사용, 없으면 35개 형질 기본값 사용
|
||||
const traitConditions = inputTraitConditions && inputTraitConditions.length > 0
|
||||
? inputTraitConditions
|
||||
: ALL_TRAITS.map(traitNm => ({ traitNm, weight: 1 }));
|
||||
|
||||
console.log('[getFarmRegionRanking] traitConditions:', traitConditions.length, 'traits');
|
||||
|
||||
// 5. 각 개체별 점수 계산 (메모리에서 처리 - DB 쿼리 없음)
|
||||
const allScores: { cowId: string; score: number; farmNo: number | null }[] = [];
|
||||
@@ -1758,7 +1772,7 @@ export class GenomeService {
|
||||
}
|
||||
|
||||
if (hasAllTraits && totalWeight > 0) {
|
||||
const score = weightedSum / totalWeight;
|
||||
const score = weightedSum;
|
||||
allScores.push({
|
||||
cowId: request.cow.cowId,
|
||||
score: Math.round(score * 100) / 100,
|
||||
@@ -1815,6 +1829,14 @@ export class GenomeService {
|
||||
? Math.round((allScores.reduce((sum, s) => sum + s.score, 0) / allScores.length) * 100) / 100
|
||||
: null;
|
||||
|
||||
// 디버깅: 상위 5개 농가 점수 출력
|
||||
console.log('[getFarmRegionRanking] 결과:', {
|
||||
myFarmRank: farmRankInRegion,
|
||||
myFarmScore: myFarmData?.avgScore,
|
||||
totalFarms: farmAverages.length,
|
||||
top5: farmAverages.slice(0, 5).map(f => ({ farmNo: f.farmNo, score: f.avgScore }))
|
||||
});
|
||||
|
||||
return {
|
||||
farmNo,
|
||||
farmerName: farm.farmerName || null,
|
||||
|
||||
Reference in New Issue
Block a user