개체 상세페이지 수정

This commit is contained in:
2025-12-12 17:03:30 +09:00
parent feb795ea91
commit b9117f231b
3 changed files with 47 additions and 11 deletions

View File

@@ -27,6 +27,13 @@ import {
} from './dto/ranking-request.dto'; } from './dto/ranking-request.dto';
import { isValidGenomeAnalysis } from '../common/config/GenomeAnalysisConfig'; import { isValidGenomeAnalysis } from '../common/config/GenomeAnalysisConfig';
/**
* 낮을수록 좋은 형질 목록 (부호 반전 필요)
* - 등지방두께: 지방이 얇을수록(EBV가 낮을수록) 좋은 형질
* - 선발지수 계산 시 EBV 부호를 반전하여 적용
*/
const NEGATIVE_TRAITS = ['등지방두께'];
/** /**
* 개체(소) 관리 서비스 * 개체(소) 관리 서비스
* *
@@ -291,13 +298,16 @@ export class CowService {
if (trait && trait.traitEbv !== null) { if (trait && trait.traitEbv !== null) {
// EBV 값이 있으면 가중치 적용하여 합산 // EBV 값이 있으면 가중치 적용하여 합산
const ebv = Number(trait.traitEbv); const ebv = Number(trait.traitEbv);
weightedSum += ebv * weight; // EBV × 가중치 // 등지방두께 등 낮을수록 좋은 형질은 부호 반전
const isNegativeTrait = NEGATIVE_TRAITS.includes(condition.traitNm);
const adjustedEbv = isNegativeTrait ? -ebv : ebv;
weightedSum += adjustedEbv * weight; // EBV × 가중치
totalWeight += weight; // 가중치 누적 totalWeight += weight; // 가중치 누적
// 상세 내역 저장 (응답용) // 상세 내역 저장 (응답용)
details.push({ details.push({
code: condition.traitNm, // 형질명 code: condition.traitNm, // 형질명
value: ebv, // EBV 값 value: adjustedEbv, // EBV 값 (부호 반전 적용)
weight, // 적용된 가중치 weight, // 적용된 가중치
}); });
} else { } else {

View File

@@ -10,6 +10,13 @@ import { FarmModel } from '../farm/entities/farm.entity';
import { GenomeRequestModel } from './entities/genome-request.entity'; import { GenomeRequestModel } from './entities/genome-request.entity';
import { GenomeTraitDetailModel } from './entities/genome-trait-detail.entity'; import { GenomeTraitDetailModel } from './entities/genome-trait-detail.entity';
/**
* 낮을수록 좋은 형질 목록 (부호 반전 필요)
* - 등지방두께: 지방이 얇을수록(EBV가 낮을수록) 좋은 형질
* - 선발지수 계산 시 EBV 부호를 반전하여 적용
*/
const NEGATIVE_TRAITS = ['등지방두께'];
/** /**
* 형질명 → 카테고리 매핑 상수 * 형질명 → 카테고리 매핑 상수
* - 성장: 월령별 체중 관련 형질 * - 성장: 월령별 체중 관련 형질
@@ -1368,7 +1375,10 @@ export class GenomeService {
if (trait && trait.traitEbv !== null) { if (trait && trait.traitEbv !== null) {
const ebv = Number(trait.traitEbv); const ebv = Number(trait.traitEbv);
const contribution = ebv * weight; // EBV × 가중치 // 등지방두께 등 낮을수록 좋은 형질은 부호 반전
const isNegativeTrait = NEGATIVE_TRAITS.includes(condition.traitNm);
const adjustedEbv = isNegativeTrait ? -ebv : ebv;
const contribution = adjustedEbv * weight; // EBV × 가중치
weightedSum += contribution; weightedSum += contribution;
totalWeight += weight; totalWeight += weight;
@@ -1505,7 +1515,11 @@ export class GenomeService {
const weight = condition.weight || 1; const weight = condition.weight || 1;
if (trait && trait.traitEbv !== null) { if (trait && trait.traitEbv !== null) {
weightedSum += Number(trait.traitEbv) * weight; // 등지방두께 등 낮을수록 좋은 형질은 부호 반전
const ebv = Number(trait.traitEbv);
const isNegativeTrait = NEGATIVE_TRAITS.includes(condition.traitNm);
const adjustedEbv = isNegativeTrait ? -ebv : ebv;
weightedSum += adjustedEbv * weight;
totalWeight += weight; totalWeight += weight;
} else { } else {
hasAllTraits = false; hasAllTraits = false;
@@ -1813,7 +1827,11 @@ export class GenomeService {
const weight = condition.weight || 1; const weight = condition.weight || 1;
if (trait && trait.traitEbv !== null) { if (trait && trait.traitEbv !== null) {
weightedSum += Number(trait.traitEbv) * weight; // 등지방두께 등 낮을수록 좋은 형질은 부호 반전
const ebv = Number(trait.traitEbv);
const isNegativeTrait = NEGATIVE_TRAITS.includes(condition.traitNm);
const adjustedEbv = isNegativeTrait ? -ebv : ebv;
weightedSum += adjustedEbv * weight;
totalWeight += weight; totalWeight += weight;
} else { } else {
hasAllTraits = false; hasAllTraits = false;

View File

@@ -6,6 +6,9 @@ import { Card, CardContent } from "@/components/ui/card"
// 기본 7개 형질 // 기본 7개 형질
const DEFAULT_TRAITS = ['도체중', '등심단면적', '등지방두께', '근내지방도', '체장', '체고', '등심weight'] const DEFAULT_TRAITS = ['도체중', '등심단면적', '등지방두께', '근내지방도', '체장', '체고', '등심weight']
// 낮을수록 좋은 형질 (부호 반전 색상 적용)
const NEGATIVE_TRAITS = ['등지방두께']
// 형질명 표시 (전체 이름) // 형질명 표시 (전체 이름)
const TRAIT_SHORT_NAMES: Record<string, string> = { const TRAIT_SHORT_NAMES: Record<string, string> = {
'도체중': '도체중', '도체중': '도체중',
@@ -82,12 +85,17 @@ function TraitListView({ traits, cowName }: { traits: Array<{ name: string; shor
</td> </td>
<td className="px-3 sm:px-5 py-4 text-left"> <td className="px-3 sm:px-5 py-4 text-left">
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<span className={`text-base sm:text-xl font-bold ${(trait.actualValue ?? 0) > 0 <span className={`text-base sm:text-xl font-bold ${(() => {
? 'text-green-600' const value = trait.actualValue ?? 0
: (trait.actualValue ?? 0) < 0 const isNegativeTrait = NEGATIVE_TRAITS.includes(trait.name)
? 'text-red-600' // 등지방두께: 음수가 좋음(녹색), 양수가 나쁨(빨간색)
: 'text-muted-foreground' // 나머지: 양수가 좋음(녹색), 음수가 나쁨(빨간색)
}`}> if (value === 0) return 'text-muted-foreground'
if (isNegativeTrait) {
return value < 0 ? 'text-green-600' : 'text-red-600'
}
return value > 0 ? 'text-green-600' : 'text-red-600'
})()}`}>
{trait.actualValue !== undefined ? ( {trait.actualValue !== undefined ? (
<>{trait.actualValue > 0 ? '+' : ''}{trait.actualValue.toFixed(1)}</> <>{trait.actualValue > 0 ? '+' : ''}{trait.actualValue.toFixed(1)}</>
) : '-'} ) : '-'}