service 로직 수정3
This commit is contained in:
@@ -23,6 +23,9 @@ export const VALID_CHIP_SIRE_NAME = '일치';
|
||||
/** 제외할 어미 칩 이름 값 목록 */
|
||||
export const INVALID_CHIP_DAM_NAMES = ['불일치', '이력제부재'];
|
||||
|
||||
/** 순위/평균 집계 대상 지역 (이 지역만 집계에 포함, 테스트/기관 계정은 다른 regionSi 사용) */
|
||||
export const VALID_REGION = '보은군';
|
||||
|
||||
/** ===============================개별 제외 개체 목록 (분석불가 등 특수 사유) 하단 개체 정보없음 확인필요=================*/
|
||||
export const EXCLUDED_COW_IDS = [
|
||||
'KOR002191642861',
|
||||
|
||||
@@ -3,6 +3,7 @@ import { InjectRepository } from '@nestjs/typeorm';
|
||||
import { IsNull, Repository } from 'typeorm';
|
||||
import {
|
||||
isValidGenomeAnalysis,
|
||||
VALID_REGION,
|
||||
} from '../common/config/GenomeAnalysisConfig';
|
||||
import {
|
||||
ALL_TRAITS,
|
||||
@@ -151,6 +152,12 @@ export class GenomeService {
|
||||
const startTime = Date.now();
|
||||
console.log('[Dashboard] 시작');
|
||||
|
||||
// Step 0: 조회자의 농장 정보 확인 (테스트 농가 여부 판단)
|
||||
const viewerFarm = await this.farmRepository.findOne({
|
||||
where: { pkFarmNo: farmNo, delDt: IsNull() },
|
||||
});
|
||||
const isViewerTestFarm = viewerFarm?.regionSi !== VALID_REGION;
|
||||
|
||||
// Step 1: 농장의 모든 분석 의뢰 조회 (traitDetails 포함)
|
||||
const requests = await this.genomeRequestRepository.find({
|
||||
where: { fkFarmNo: farmNo, delDt: IsNull() },
|
||||
@@ -245,12 +252,13 @@ export class GenomeService {
|
||||
// DB 집계로 최적화 + 병렬 실행
|
||||
console.log(`[Dashboard] Step3 DB 집계 쿼리 시작 (병렬): ${Date.now() - startTime}ms`);
|
||||
|
||||
// 1, 2번 쿼리 병렬 실행
|
||||
// 1, 2번 쿼리 병렬 실행 (보은군 농가만, 테스트 농가가 조회 시 자기 데이터도 포함)
|
||||
const [farmTraitAvgResults, regionEpdResults] = await Promise.all([
|
||||
// 1. 농가별 형질 평균 EBV (DB 집계)
|
||||
this.genomeTraitDetailRepository
|
||||
.createQueryBuilder('detail')
|
||||
.innerJoin('detail.genomeRequest', 'req')
|
||||
.innerJoin('tb_farm', 'farm', 'req.fkFarmNo = farm.pkFarmNo')
|
||||
.select('req.fkFarmNo', 'farmNo')
|
||||
.addSelect('detail.traitName', 'traitName')
|
||||
.addSelect('AVG(detail.traitEbv)', 'avgEbv')
|
||||
@@ -258,14 +266,18 @@ export class GenomeService {
|
||||
.andWhere('req.delDt IS NULL')
|
||||
.andWhere('req.chipSireName = :match', { match: '일치' })
|
||||
.andWhere('detail.traitEbv IS NOT NULL')
|
||||
// 보은군 농가 또는 조회자 자신(테스트 농가일 때)
|
||||
.andWhere('(farm.regionSi = :region OR req.fkFarmNo = :viewerFarmNo)',
|
||||
{ region: VALID_REGION, viewerFarmNo: isViewerTestFarm ? farmNo : -1 })
|
||||
.groupBy('req.fkFarmNo')
|
||||
.addGroupBy('detail.traitName')
|
||||
.getRawMany(),
|
||||
|
||||
// 2. 보은군 전체 형질별 평균 EPD (DB 집계)
|
||||
// 2. 보은군 전체 형질별 평균 EPD (DB 집계) - 순수하게 보은군만
|
||||
this.genomeTraitDetailRepository
|
||||
.createQueryBuilder('detail')
|
||||
.innerJoin('detail.genomeRequest', 'req')
|
||||
.innerJoin('tb_farm', 'farm', 'req.fkFarmNo = farm.pkFarmNo')
|
||||
.select('detail.traitName', 'traitName')
|
||||
.addSelect('AVG(detail.traitVal)', 'avgEpd')
|
||||
.addSelect('COUNT(*)', 'count')
|
||||
@@ -273,6 +285,7 @@ export class GenomeService {
|
||||
.andWhere('req.delDt IS NULL')
|
||||
.andWhere('req.chipSireName = :match', { match: '일치' })
|
||||
.andWhere('detail.traitVal IS NOT NULL')
|
||||
.andWhere('farm.regionSi = :region', { region: VALID_REGION })
|
||||
.groupBy('detail.traitName')
|
||||
.getRawMany(),
|
||||
]);
|
||||
@@ -363,10 +376,11 @@ export class GenomeService {
|
||||
})),
|
||||
}));
|
||||
|
||||
// 3. 보은군 연도별 형질 데이터 (DB 집계)
|
||||
// 3. 보은군 연도별 형질 데이터 (DB 집계) - 순수하게 보은군만
|
||||
const regionYearlyResults = await this.genomeTraitDetailRepository
|
||||
.createQueryBuilder('detail')
|
||||
.innerJoin('detail.genomeRequest', 'req')
|
||||
.innerJoin('tb_farm', 'farm', 'req.fkFarmNo = farm.pkFarmNo')
|
||||
.select('EXTRACT(YEAR FROM req.requestDt)', 'year')
|
||||
.addSelect('detail.traitName', 'traitName')
|
||||
.addSelect('SUM(detail.traitEbv)', 'sum')
|
||||
@@ -376,6 +390,7 @@ export class GenomeService {
|
||||
.andWhere('req.chipSireName = :match', { match: '일치' })
|
||||
.andWhere('detail.traitEbv IS NOT NULL')
|
||||
.andWhere('req.requestDt IS NOT NULL')
|
||||
.andWhere('farm.regionSi = :region', { region: VALID_REGION })
|
||||
.groupBy('EXTRACT(YEAR FROM req.requestDt)')
|
||||
.addGroupBy('detail.traitName')
|
||||
.getRawMany();
|
||||
@@ -512,12 +527,22 @@ export class GenomeService {
|
||||
console.log(`[Dashboard] Step5 4개 테이블 조회 완료: ${Date.now() - startTime}ms`);
|
||||
|
||||
// 합집합 계산 (중복 제외) - genomeRequest 포함 (리스트 페이지와 일치)
|
||||
const TEST_FARM_NO = 26; // 코쿤 테스트 농장
|
||||
const isTestFarm = farmNo === TEST_FARM_NO;
|
||||
const isTestFarm = viewerFarm?.regionSi !== VALID_REGION;
|
||||
|
||||
// 테스트 농장(26번)은 tb_cow 전체, 그 외는 검사 받은 개체만
|
||||
/**
|
||||
* 대시보드 개체 수 집계 로직
|
||||
*
|
||||
* [일반 농가 (regionSi = '보은군')]
|
||||
* - 실제 검사(유전체/유전자/번식능력)를 받은 개체만 집계
|
||||
* - 검사 데이터가 없는 개체는 대시보드에 표시되지 않음
|
||||
*
|
||||
* [테스트/기관 농가 (regionSi != '보은군', 예: '코쿤')]
|
||||
* - 검사 여부와 관계없이 농장 소유 전체 개체를 집계
|
||||
* - UI 테스트 목적으로 가짜 데이터 없이도 개체 목록 확인 가능
|
||||
* - 실제 운영 데이터에는 영향 없음 (순위/평균 계산에서 제외됨)
|
||||
*/
|
||||
const allTestedCowIds = isTestFarm
|
||||
? farmCowIds
|
||||
? farmCowIds // 테스트 농가: 모든 소
|
||||
: new Set([
|
||||
...farmGenomeRequestCowIds,
|
||||
...farmGenomeCowIds,
|
||||
@@ -1202,6 +1227,11 @@ export class GenomeService {
|
||||
for (const request of allRequests) {
|
||||
if (!request.cow?.cowId) continue;
|
||||
|
||||
// 보은군 농가만 포함 (테스트 농가가 조회 시 자기 데이터도 포함)
|
||||
const isValidRegion = request.farm?.regionSi === VALID_REGION;
|
||||
const isViewerOwnData = request.fkFarmNo === farmNo;
|
||||
if (!isValidRegion && !isViewerOwnData) continue;
|
||||
|
||||
// 친자감별 결과가 '일치'인 경우만 포함 (분석불가 개체 제외)
|
||||
if (!isValidGenomeAnalysis(request.chipSireName, request.chipDamName, request.cow?.cowId)) continue;
|
||||
|
||||
@@ -1366,6 +1396,12 @@ export class GenomeService {
|
||||
|
||||
for (const request of allRequests) {
|
||||
if (!request.cow?.cowId) continue;
|
||||
|
||||
// 보은군 농가만 포함 (테스트 농가가 조회 시 자기 데이터도 포함)
|
||||
const isValidRegion = request.farm?.regionSi === VALID_REGION;
|
||||
const isViewerOwnData = request.fkFarmNo === farmNo;
|
||||
if (!isValidRegion && !isViewerOwnData) continue;
|
||||
|
||||
if (!isValidGenomeAnalysis(request.chipSireName, request.chipDamName, request.cow?.cowId)) continue;
|
||||
|
||||
const traitDetail = await this.genomeTraitDetailRepository.findOne({
|
||||
@@ -1498,6 +1534,12 @@ export class GenomeService {
|
||||
|
||||
for (const request of allRequests) {
|
||||
if (!request.cow?.cowId) continue;
|
||||
|
||||
// 보은군 농가만 포함 (테스트 농가가 조회 시 자기 데이터도 포함)
|
||||
const isValidRegion = request.farm?.regionSi === VALID_REGION;
|
||||
const isViewerOwnData = request.fkFarmNo === farmNo;
|
||||
if (!isValidRegion && !isViewerOwnData) continue;
|
||||
|
||||
if (!isValidGenomeAnalysis(request.chipSireName, request.chipDamName, request.cow?.cowId)) continue;
|
||||
|
||||
// relations로 조회된 traitDetails 사용
|
||||
@@ -1676,10 +1718,11 @@ export class GenomeService {
|
||||
// 대상 형질 결정
|
||||
const targetTraits = traitName ? [traitName] : traitsInCategory;
|
||||
|
||||
// JOIN으로 한 번에 조회 (genome_request + cow + genome_trait_detail)
|
||||
// JOIN으로 한 번에 조회 (genome_request + cow + genome_trait_detail + farm)
|
||||
const allData = await this.genomeRequestRepository
|
||||
.createQueryBuilder('r')
|
||||
.innerJoin('r.cow', 'c')
|
||||
.innerJoin('r.farm', 'f')
|
||||
.innerJoin(
|
||||
GenomeTraitDetailModel,
|
||||
'd',
|
||||
@@ -1692,6 +1735,7 @@ export class GenomeService {
|
||||
.addSelect('EXTRACT(YEAR FROM r.request_dt)', 'year')
|
||||
.addSelect('d.trait_name', 'traitName')
|
||||
.addSelect('d.trait_val', 'traitVal')
|
||||
.addSelect('f.region_si', 'regionSi')
|
||||
.where('r.del_dt IS NULL')
|
||||
.andWhere('d.trait_name IN (:...targetTraits)', { targetTraits })
|
||||
.getRawMany();
|
||||
@@ -1700,6 +1744,7 @@ export class GenomeService {
|
||||
const cowDataMap = new Map<string, {
|
||||
farmNo: number;
|
||||
year: number;
|
||||
regionSi: string | null;
|
||||
chipSireName: string | null;
|
||||
chipDamName: string | null;
|
||||
traits: { traitName: string; traitVal: number }[];
|
||||
@@ -1713,6 +1758,7 @@ export class GenomeService {
|
||||
cowDataMap.set(cowId, {
|
||||
farmNo: row.reqFarmNo,
|
||||
year: row.year || new Date().getFullYear(),
|
||||
regionSi: row.regionSi || null,
|
||||
chipSireName: row.chipSireName,
|
||||
chipDamName: row.chipDamName,
|
||||
traits: [],
|
||||
@@ -1734,6 +1780,12 @@ export class GenomeService {
|
||||
for (const [cowId, data] of cowDataMap) {
|
||||
// 유효한 분석인지 확인
|
||||
if (!isValidGenomeAnalysis(data.chipSireName, data.chipDamName, cowId)) continue;
|
||||
|
||||
// 보은군 농가만 포함 (테스트 농가가 조회 시 자기 데이터도 포함)
|
||||
const isValidRegion = data.regionSi === VALID_REGION;
|
||||
const isViewerOwnData = data.farmNo === farmNo;
|
||||
if (!isValidRegion && !isViewerOwnData) continue;
|
||||
|
||||
if (data.traits.length === 0) continue;
|
||||
|
||||
// 대상 형질의 평균 육종가 계산
|
||||
|
||||
Reference in New Issue
Block a user