update_cow_list_detail_page
This commit is contained in:
@@ -1044,6 +1044,7 @@ export class GenomeService {
|
||||
farmAvgScore: number | null; // 농가 평균 선발지수
|
||||
regionAvgScore: number | null; // 보은군(지역) 평균 선발지수
|
||||
details: { traitNm: string; ebv: number; weight: number; contribution: number }[];
|
||||
histogram: { bin: number; count: number; farmCount: number }[]; // 실제 분포
|
||||
message?: string;
|
||||
}> {
|
||||
// Step 1: cowId로 개체 조회
|
||||
@@ -1067,7 +1068,7 @@ export class GenomeService {
|
||||
farmRank: null, farmTotal: 0,
|
||||
regionRank: null, regionTotal: 0, regionName: null, farmerName: null,
|
||||
farmAvgScore: null, regionAvgScore: null,
|
||||
details: [], message: '유전체 분석 데이터 없음'
|
||||
details: [], histogram: [], message: '유전체 분석 데이터 없음'
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1082,7 +1083,7 @@ export class GenomeService {
|
||||
farmRank: null, farmTotal: 0,
|
||||
regionRank: null, regionTotal: 0, regionName: null, farmerName: null,
|
||||
farmAvgScore: null, regionAvgScore: null,
|
||||
details: [], message: '형질 데이터 없음'
|
||||
details: [], histogram: [], message: '형질 데이터 없음'
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1138,7 +1139,7 @@ export class GenomeService {
|
||||
// Step 7: 현재 개체의 농장/지역 정보 조회
|
||||
let regionName: string | null = null;
|
||||
let farmerName: string | null = null;
|
||||
let farmNo: number | null = latestRequest.fkFarmNo;
|
||||
const farmNo: number | null = latestRequest.fkFarmNo;
|
||||
|
||||
if (farmNo) {
|
||||
const farm = await this.farmRepository.findOne({
|
||||
@@ -1162,12 +1163,13 @@ export class GenomeService {
|
||||
farmAvgScore: null,
|
||||
regionAvgScore: null,
|
||||
details,
|
||||
histogram: [],
|
||||
message: '선택한 형질 중 일부 데이터가 없습니다',
|
||||
};
|
||||
}
|
||||
|
||||
// Step 9: 농가/지역 순위 및 평균 선발지수 계산
|
||||
const { farmRank, farmTotal, regionRank, regionTotal, farmAvgScore, regionAvgScore } =
|
||||
const { farmRank, farmTotal, regionRank, regionTotal, farmAvgScore, regionAvgScore, histogram } =
|
||||
await this.calculateRanks(cowId, score, traitConditions, farmNo, regionName);
|
||||
|
||||
return {
|
||||
@@ -1182,6 +1184,7 @@ export class GenomeService {
|
||||
farmAvgScore,
|
||||
regionAvgScore,
|
||||
details,
|
||||
histogram,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1207,10 +1210,11 @@ export class GenomeService {
|
||||
regionTotal: number;
|
||||
farmAvgScore: number | null; // 농가 평균 선발지수
|
||||
regionAvgScore: number | null; // 보은군(지역) 평균 선발지수
|
||||
histogram: { bin: number; count: number; farmCount: number }[]; // 실제 분포
|
||||
}> {
|
||||
// 점수가 없으면 순위 계산 불가
|
||||
if (currentScore === null) {
|
||||
return { farmRank: null, farmTotal: 0, regionRank: null, regionTotal: 0, farmAvgScore: null, regionAvgScore: null };
|
||||
return { farmRank: null, farmTotal: 0, regionRank: null, regionTotal: 0, farmAvgScore: null, regionAvgScore: null, histogram: [] };
|
||||
}
|
||||
|
||||
// 모든 유전체 분석 의뢰 조회 (유전체 데이터가 있는 모든 개체)
|
||||
@@ -1297,7 +1301,7 @@ export class GenomeService {
|
||||
|
||||
// 지역(보은군) 순위 및 평균 선발지수 계산 - 전체 유전체 데이터가 보은군이므로 필터링 없이 전체 사용
|
||||
let regionRank: number | null = null;
|
||||
let regionTotal = allScores.length;
|
||||
const regionTotal = allScores.length;
|
||||
let regionAvgScore: number | null = null;
|
||||
|
||||
const regionIndex = allScores.findIndex(s => s.cowId === currentCowId);
|
||||
@@ -1309,6 +1313,38 @@ export class GenomeService {
|
||||
regionAvgScore = Math.round((regionScoreSum / allScores.length) * 100) / 100;
|
||||
}
|
||||
|
||||
// 히스토그램 생성 (선발지수 실제 분포)
|
||||
const histogram: { bin: number; count: number; farmCount: number }[] = [];
|
||||
if (allScores.length > 0) {
|
||||
// 최소/최대값 찾기
|
||||
const scores = allScores.map(s => s.score);
|
||||
const minScore = Math.min(...scores);
|
||||
const maxScore = Math.max(...scores);
|
||||
const range = maxScore - minScore;
|
||||
|
||||
// 구간 크기 결정 (전체 범위를 약 20-30개 구간으로 나눔)
|
||||
const binSize = range > 0 ? Math.ceil(range / 25) : 1;
|
||||
|
||||
// 구간별 집계
|
||||
const binMap = new Map<number, { count: number; farmCount: number }>();
|
||||
|
||||
allScores.forEach(({ score, farmNo: scoreFarmNo }) => {
|
||||
const binStart = Math.floor(score / binSize) * binSize;
|
||||
const existing = binMap.get(binStart) || { count: 0, farmCount: 0 };
|
||||
existing.count += 1;
|
||||
if (scoreFarmNo === farmNo) {
|
||||
existing.farmCount += 1;
|
||||
}
|
||||
binMap.set(binStart, existing);
|
||||
});
|
||||
|
||||
// Map을 배열로 변환 및 정렬
|
||||
histogram.push(...Array.from(binMap.entries())
|
||||
.map(([bin, data]) => ({ bin, count: data.count, farmCount: data.farmCount }))
|
||||
.sort((a, b) => a.bin - b.bin)
|
||||
);
|
||||
}
|
||||
|
||||
return {
|
||||
farmRank,
|
||||
farmTotal,
|
||||
@@ -1316,6 +1352,7 @@ export class GenomeService {
|
||||
regionTotal,
|
||||
farmAvgScore,
|
||||
regionAvgScore,
|
||||
histogram,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1338,6 +1375,7 @@ export class GenomeService {
|
||||
regionAvgEbv: number | null;
|
||||
farmAvgEpd: number | null; // 농가 평균 육종가(EPD)
|
||||
regionAvgEpd: number | null; // 보은군 평균 육종가(EPD)
|
||||
histogram: { bin: number; count: number; farmCount: number }[]; // 실제 데이터 분포
|
||||
}> {
|
||||
// 1. 현재 개체의 의뢰 정보 조회
|
||||
const cow = await this.cowRepository.findOne({
|
||||
@@ -1357,6 +1395,7 @@ export class GenomeService {
|
||||
regionAvgEbv: null,
|
||||
farmAvgEpd: null,
|
||||
regionAvgEpd: null,
|
||||
histogram: [],
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1378,6 +1417,7 @@ export class GenomeService {
|
||||
regionAvgEbv: null,
|
||||
farmAvgEpd: null,
|
||||
regionAvgEpd: null,
|
||||
histogram: [],
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1459,6 +1499,42 @@ export class GenomeService {
|
||||
? Math.round((farmEpdValues.reduce((sum, v) => sum + v, 0) / farmEpdValues.length) * 100) / 100
|
||||
: null;
|
||||
|
||||
// 8. 실제 데이터 분포 히스토그램 생성 (EPD 기준)
|
||||
const histogram: { bin: number; count: number; farmCount: number }[] = [];
|
||||
if (allScores.length > 0) {
|
||||
// EPD 값들 수집 (EPD가 실제 육종가 값)
|
||||
const epdValues = allScores.filter(s => s.epd !== null).map(s => ({ epd: s.epd as number, farmNo: s.farmNo }));
|
||||
|
||||
if (epdValues.length > 0) {
|
||||
// 최소/최대값 찾기
|
||||
const minEpd = Math.min(...epdValues.map(v => v.epd));
|
||||
const maxEpd = Math.max(...epdValues.map(v => v.epd));
|
||||
const range = maxEpd - minEpd;
|
||||
|
||||
// 구간 크기 결정 (전체 범위를 약 20-30개 구간으로 나눔)
|
||||
const binSize = range > 0 ? Math.ceil(range / 25) : 1;
|
||||
|
||||
// 구간별 집계
|
||||
const binMap = new Map<number, { count: number; farmCount: number }>();
|
||||
|
||||
epdValues.forEach(({ epd, farmNo: scoreFarmNo }) => {
|
||||
const binStart = Math.floor(epd / binSize) * binSize;
|
||||
const existing = binMap.get(binStart) || { count: 0, farmCount: 0 };
|
||||
existing.count += 1;
|
||||
if (scoreFarmNo === farmNo) {
|
||||
existing.farmCount += 1;
|
||||
}
|
||||
binMap.set(binStart, existing);
|
||||
});
|
||||
|
||||
// Map을 배열로 변환 및 정렬
|
||||
histogram.push(...Array.from(binMap.entries())
|
||||
.map(([bin, data]) => ({ bin, count: data.count, farmCount: data.farmCount }))
|
||||
.sort((a, b) => a.bin - b.bin)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
traitName,
|
||||
cowEbv: cowEbv !== null ? Math.round(cowEbv * 100) / 100 : null,
|
||||
@@ -1471,6 +1547,7 @@ export class GenomeService {
|
||||
regionAvgEbv,
|
||||
farmAvgEpd,
|
||||
regionAvgEpd,
|
||||
histogram, // 실제 데이터 분포 추가
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user