import { Injectable, NotFoundException } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { IsNull, Repository } from 'typeorm';
import {
  isValidGenomeAnalysis,
  VALID_REGION,
} from '../common/config/GenomeAnalysisConfig';
import {
  ALL_TRAITS,
  NEGATIVE_TRAITS,
  TRAIT_CATEGORY_MAP,
  getTraitCategory,
} from '../common/const/TraitTypes';
import { CowModel } from '../cow/entities/cow.entity';
import { FarmModel } from '../farm/entities/farm.entity';
import { GenomeRequestModel } from './entities/genome-request.entity';
import { GenomeTraitDetailModel } from './entities/genome-trait-detail.entity';
import { MptModel } from '../mpt/entities/mpt.entity';
import { GeneDetailModel } from '../gene/entities/gene-detail.entity';
import { CategoryAverageDto, ComparisonAveragesDto } from './dto/comparison-averages.dto';
import { TraitAverageDto, TraitComparisonAveragesDto } from './dto/trait-comparison.dto';

/**
 * 유전체 분석 서비스
 *
 * 주요 기능:
 * 1. 유전체 분석 의뢰(Request) CRUD
 * 2. 형질(Trait) 데이터 관리
 * 3. 형질 상세(TraitDetail) 데이터 관리
 * 4. 전국/지역/농장별 EBV 평균 비교 분석
 */
@Injectable()
export class GenomeService {
  constructor(
    // 유전체 분석 의뢰 Repository
    @InjectRepository(GenomeRequestModel)
    private readonly genomeRequestRepository: Repository<GenomeRequestModel>,

    // 형질 상세 정보 Repository
    @InjectRepository(GenomeTraitDetailModel)
    private readonly genomeTraitDetailRepository: Repository<GenomeTraitDetailModel>,

    // 개체(소) 정보 Repository
    @InjectRepository(CowModel)
    private readonly cowRepository: Repository<CowModel>,

    // 농장 정보 Repository
    @InjectRepository(FarmModel)
    private readonly farmRepository: Repository<FarmModel>,

    // 번식능력검사 Repository
    @InjectRepository(MptModel)
    private readonly mptRepository: Repository<MptModel>,

    // 유전자검사 상세 Repository
    @InjectRepository(GeneDetailModel)
    private readonly geneDetailRepository: Repository<GeneDetailModel>,
  ) { }

  // ============================================
  // 대시보드 통계 관련 메서드
  // ============================================

  /**
   * 대시보드용 농가 통계 데이터
   * - 연도별 분석 현황
   * - 형질별 농장 평균 EBV
   * - 접수 내역 목록
   *
   * @usedBy /dashboard - 대시보드 페이지
   * @usedBy /cow/[cowNo]/genome - 개체 상세 > 유전체 통합 비교
   * @param farmNo - 농장 번호
   */
  async getDashboardStats(farmNo: number): Promise<{
    // 연도별 분석 현황
    yearlyStats: {
      year: number;
      totalRequests: number;      // 총 접수
      analyzedCount: number;      // 분석 완료 (친자일치)
      pendingCount: number;       // 분석 대기/불일치
      sireMatchCount: number;     // 친자 일치 수
      analyzeRate: number;        // 분석 완료율 (%)
      sireMatchRate: number;      // 친자 일치율 (%)
    }[];
    // 형질별 농장 평균 (전체 개체 기준)
    traitAverages: {
      traitName: string;
      category: string;
      avgEbv: number;
      avgEpd: number;               // 육종가(EPD) 평균
      avgPercentile: number;
      count: number;
      rank: number | null;          // 보은군 내 농가 순위
      totalFarms: number;           // 보은군 내 총 농가 수
      percentile: number | null;    // 상위 백분율
    }[];
    // 접수 내역 목록
    requestHistory: {
      pkRequestNo: number;
      cowId: string;
      cowRemarks: string | null;
      requestDt: string | null;
      chipSireName: string | null;  // 친자감별 결과
      chipReportDt: string | null;
      status: string;               // 완료/대기/불일치
    }[];
    // 요약
    summary: {
      totalCows: number;        // 검사 받은 전체 개체 수 (합집합, 중복 제외)
      genomeCowCount: number;   // 유전체 분석 개체 수
      geneCowCount: number;     // 유전자검사 개체 수
      mptCowCount: number;      // 번식능력검사 개체 수
      totalRequests: number;    // 유전체 의뢰 건수 (기존 호환성)
      analyzedCount: number;
      pendingCount: number;
      mismatchCount: number;
      maleCount: number;      // 수컷 수
      femaleCount: number;    // 암컷 수
    };
    // 검사 종류별 현황
    testTypeStats: {
      snp: { total: number; completed: number };
      ms: { total: number; completed: number };
    };
    // 친자감별 결과 현황 (상호 배타적 분류)
    paternityStats: {
      analysisComplete: number;  // 분석 완료 (부 일치 + 모 일치/null/정보없음)
      sireMismatch: number;      // 부 불일치
      damMismatch: number;       // 모 불일치 (부 일치 + 모 불일치)
      damNoRecord: number;       // 모 이력제부재 (부 일치 + 모 이력제부재)
      notAnalyzed: number;       // 미분석
    };
    // 월별 접수 현황
    monthlyStats: { month: number; count: number }[];
    // 칩 종류별 분포
    chipTypeStats: { chipType: string; count: number }[];
    // 모근량별 분포
    sampleAmountStats: { sampleAmount: string; count: number }[];
    // 연도별 주요 형질 평균 (차트용)
    yearlyTraitAverages: {
      year: number;
      traits: { traitName: string; avgEbv: number | null }[];
    }[];
    // 연도별 평균 표준화육종가 (농가 vs 보은군 비교)
    yearlyAvgEbv: {
      year: number;
      farmAvgEbv: number;      // 농가 평균
      regionAvgEbv: number;    // 보은군 평균
      traitCount: number;
    }[];
  }> {
    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() },
      relations: ['cow', 'traitDetails'],
      order: { requestDt: 'DESC', regDt: 'DESC' },
    });
    console.log(`[Dashboard] Step1 농장 요청 조회: ${Date.now() - startTime}ms`);

    // Step 2: 연도별 통계 계산
    const yearMap = new Map<number, { total: number; analyzed: number; pending: number; sireMatch: number }>();

    for (const req of requests) {
      const year = req.requestDt ? new Date(req.requestDt).getFullYear() : new Date().getFullYear();

      if (!yearMap.has(year)) {
        yearMap.set(year, { total: 0, analyzed: 0, pending: 0, sireMatch: 0 });
      }

      const stat = yearMap.get(year)!;
      stat.total++;

      if (req.chipSireName === '일치') {
        stat.analyzed++;
        stat.sireMatch++;
      } else {
        stat.pending++;
      }
    }

    // 연도 정렬 (최신순)
    const yearlyStats = Array.from(yearMap.entries())
      .sort((a, b) => b[0] - a[0])
      .map(([year, stat]) => ({
        year,
        totalRequests: stat.total,
        analyzedCount: stat.analyzed,
        pendingCount: stat.pending,
        sireMatchCount: stat.sireMatch,
        // 분석완료율: 분석완료 / 총접수
        analyzeRate: stat.total > 0 ? Math.round((stat.analyzed / stat.total) * 100) : 0,
        // 친자일치율: 친자일치 / 총접수
        sireMatchRate: stat.total > 0 ? Math.round((stat.sireMatch / stat.total) * 100) : 0,
      }));

    // Step 3: 분석 완료된 개체의 형질 데이터 수집
    const validRequests = requests.filter(r => r.chipSireName === '일치');
    const traitDataMap = new Map<string, { sum: number; epdSum: number; percentileSum: number; count: number; category: string }>();

    // 연도별 형질 평균 계산용 (전체 35개 형질)
    const yearlyTraitMap = new Map<number, Map<string, { sum: number; count: number }>>();

    for (const request of validRequests) {
      // relations로 조회된 traitDetails 사용
      const details = request.traitDetails || [];
      if (details.length === 0) continue;

      // 연도 추출
      const year = request.requestDt ? new Date(request.requestDt).getFullYear() : new Date().getFullYear();

      for (const detail of details) {
        if (detail.traitEbv !== null && detail.traitName) {
          const traitName = detail.traitName;
          const category = TRAIT_CATEGORY_MAP[traitName] || '기타';

          // 전체 형질 평균
          if (!traitDataMap.has(traitName)) {
            traitDataMap.set(traitName, { sum: 0, epdSum: 0, percentileSum: 0, count: 0, category });
          }

          const t = traitDataMap.get(traitName)!;
          t.sum += Number(detail.traitEbv);
          t.epdSum += Number(detail.traitVal) || 0;  // 육종가(EPD) 합계
          t.percentileSum += Number(detail.traitPercentile) || 50;
          t.count++;

          // 연도별 전체 형질 평균 (차트용)
          if (!yearlyTraitMap.has(year)) {
            yearlyTraitMap.set(year, new Map());
          }
          const yearTraits = yearlyTraitMap.get(year)!;
          if (!yearTraits.has(traitName)) {
            yearTraits.set(traitName, { sum: 0, count: 0 });
          }
          const yt = yearTraits.get(traitName)!;
          yt.sum += Number(detail.traitEbv);
          yt.count++;
        }
      }
    }

    // 형질별 평균 계산 (순위 계산을 위해 보은군 내 모든 농가 데이터 필요)
    // DB 집계로 최적화 + 병렬 실행
    console.log(`[Dashboard] Step3 DB 집계 쿼리 시작 (병렬): ${Date.now() - startTime}ms`);

    // 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')
        .where('detail.delDt IS NULL')
        .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 집계) - 순수하게 보은군만
      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')
        .where('detail.delDt IS NULL')
        .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(),
    ]);

    // 형질별로 모든 농가의 평균 EBV 정렬 (순위용)
    const traitRankingMap = new Map<string, { farmNo: number; avgEbv: number }[]>();
    for (const row of farmTraitAvgResults) {
      const traitName = row.traitName;
      const farmNo = Number(row.farmNo);
      const avgEbv = parseFloat(row.avgEbv);

      if (!traitRankingMap.has(traitName)) {
        traitRankingMap.set(traitName, []);
      }
      traitRankingMap.get(traitName)!.push({ farmNo, avgEbv });
    }

    // 각 형질별로 EBV 내림차순 정렬 (높을수록 좋음)
    for (const [, farms] of traitRankingMap) {
      farms.sort((a, b) => b.avgEbv - a.avgEbv);
    }

    const regionTraitEpdMap = new Map<string, { sum: number; count: number }>();
    for (const row of regionEpdResults) {
      const count = parseInt(row.count);
      regionTraitEpdMap.set(row.traitName, {
        sum: parseFloat(row.avgEpd) * count,  // 평균 * count = 합계
        count,
      });
    }

    console.log(`[Dashboard] Step3 DB 집계 쿼리 완료: ${Date.now() - startTime}ms`);

    // 형질별 평균 및 순위 계산 (표준 경쟁 순위 방식: 동률 시 같은 순위, 다음 순위 건너뜀)
    const traitAverages = Array.from(traitDataMap.entries()).map(([traitName, data]) => {
      const avgEbv = Math.round((data.sum / data.count) * 100) / 100;
      const avgEpd = Math.round((data.epdSum / data.count) * 100) / 100;  // 농가 평균 육종가(EPD)
      const rankings = traitRankingMap.get(traitName) || [];
      const totalFarms = rankings.length;

      // 보은군 평균 EPD 계산
      const regionData = regionTraitEpdMap.get(traitName);
      const regionAvgEpd = regionData && regionData.count > 0
        ? Math.round((regionData.sum / regionData.count) * 100) / 100
        : 0;

      // 표준 경쟁 순위 계산: 동률 처리
      let rank: number | null = null;
      const farmData = rankings.find(r => r.farmNo === farmNo);
      if (farmData) {
        // 등지방두께 등 낮을수록 좋은 형질은 순위 계산 반전
        const isNegativeTrait = NEGATIVE_TRAITS.includes(traitName);
        if (isNegativeTrait) {
          // 나보다 낮은 점수를 가진 농장 수 + 1 = 내 순위 (낮을수록 좋음)
          const lowerCount = rankings.filter(r => r.avgEbv < farmData.avgEbv).length;
          rank = lowerCount + 1;
        } else {
          // 나보다 높은 점수를 가진 농장 수 + 1 = 내 순위 (높을수록 좋음)
        const higherCount = rankings.filter(r => r.avgEbv > farmData.avgEbv).length;
        rank = higherCount + 1;
        }
      }

      const percentile = rank !== null && totalFarms > 0 ? Math.round((rank / totalFarms) * 100) : null;

      return {
        traitName,
        category: data.category,
        avgEbv,
        avgEpd,  // 농가 평균 육종가(EPD)
        regionAvgEpd,  // 보은군 평균 육종가(EPD) 추가
        avgPercentile: Math.round((data.percentileSum / data.count) * 100) / 100,
        count: data.count,
        rank,
        totalFarms,
        percentile,
      };
    });

    // 연도별 전체 형질 평균 계산
    const yearlyTraitAverages = Array.from(yearlyTraitMap.entries())
      .sort((a, b) => a[0] - b[0])
      .map(([year, traitsMap]) => ({
        year,
        traits: Array.from(traitsMap.entries()).map(([traitName, data]) => ({
          traitName,
          avgEbv: Math.round((data.sum / data.count) * 100) / 100,
        })),
      }));

    // 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')
      .addSelect('COUNT(*)', 'count')
      .where('detail.delDt IS NULL')
      .andWhere('req.delDt IS NULL')
      .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();

    const regionYearlyTraitMap = new Map<number, Map<string, { sum: number; count: number }>>();
    for (const row of regionYearlyResults) {
      const year = parseInt(row.year);
      const traitName = row.traitName;

      if (!regionYearlyTraitMap.has(year)) {
        regionYearlyTraitMap.set(year, new Map());
      }
      regionYearlyTraitMap.get(year)!.set(traitName, {
        sum: parseFloat(row.sum),
        count: parseInt(row.count),
      });
    }

    // 연도별 평균 표준화육종가 (농가 vs 보은군 비교)
    const allYears = new Set([...yearlyTraitMap.keys(), ...regionYearlyTraitMap.keys()]);
    const yearlyAvgEbv = Array.from(allYears)
      .sort((a, b) => a - b)
      .map(year => {
        // 농가 평균
        let farmTotalSum = 0;
        let farmTotalCount = 0;
        const farmTraitsMap = yearlyTraitMap.get(year);
        if (farmTraitsMap) {
          farmTraitsMap.forEach((data) => {
            farmTotalSum += data.sum;
            farmTotalCount += data.count;
          });
        }

        // 보은군 평균
        let regionTotalSum = 0;
        let regionTotalCount = 0;
        const regionTraitsMap = regionYearlyTraitMap.get(year);
        if (regionTraitsMap) {
          regionTraitsMap.forEach((data) => {
            regionTotalSum += data.sum;
            regionTotalCount += data.count;
          });
        }

        return {
          year,
          farmAvgEbv: farmTotalCount > 0 ? Math.round((farmTotalSum / farmTotalCount) * 100) / 100 : 0,
          regionAvgEbv: regionTotalCount > 0 ? Math.round((regionTotalSum / regionTotalCount) * 100) / 100 : 0,
          traitCount: farmTraitsMap?.size || 0,
        };
      });

    // Step 4: 접수 내역 목록 생성
    const requestHistory = requests.map(req => {
      let status = '대기';
      if (req.chipSireName === '일치') {
        status = '완료';
      } else if (req.chipSireName && req.chipSireName !== '일치') {
        status = '불일치';
      }

      return {
        pkRequestNo: req.pkRequestNo,
        cowId: req.cow?.cowId || '',
        cowRemarks: req.cowRemarks,
        requestDt: req.requestDt ? req.requestDt.toString().split('T')[0] : null,
        chipSireName: req.chipSireName,
        chipReportDt: req.chipReportDt ? req.chipReportDt.toString().split('T')[0] : null,
        status,
      };
    });

    // Step 5: 요약 계산
    const totalRequests = requests.length;
    const analyzedCount = requests.filter(r => r.chipSireName === '일치').length;
    const mismatchCount = requests.filter(r => r.chipSireName && r.chipSireName !== '일치').length;
    const pendingCount = totalRequests - analyzedCount - mismatchCount;

    // Step 5.1: 검사 유형별 개체 수 계산 (합집합, 중복 제외)
    // 농장 소유 개체의 cowId 목록 조회
    const farmCows = await this.cowRepository.find({
      where: { fkFarmNo: farmNo, delDt: IsNull() },
      select: ['cowId', 'cowSex'],
    });
    const farmCowIds = new Set(farmCows.map(c => c.cowId).filter(Boolean));
    const farmCowMap = new Map(farmCows.map(c => [c.cowId, c]));

    // 각 검사별 cowId 조회 (병렬 처리 + cow JOIN으로 farmNo 조건)
    console.log(`[Dashboard] Step5 4개 테이블 조회 시작: ${Date.now() - startTime}ms`);

    const [farmGenomeRequestCowIds, farmGenomeCowIds, farmGeneCowIds, farmMptCowIds] = await Promise.all([
      // 유전체 분석 의뢰가 있는 개체 (cow JOIN으로 farmNo 필터)
      this.genomeRequestRepository
        .createQueryBuilder('request')
        .innerJoin('request.cow', 'cow')
        .select('DISTINCT cow.cowId', 'cowId')
        .where('request.delDt IS NULL')
        .andWhere('cow.fkFarmNo = :farmNo', { farmNo })
        .andWhere('cow.delDt IS NULL')
        .getRawMany()
        .then(rows => rows.map((r: { cowId: string }) => r.cowId).filter(Boolean)),
      // 유전체 분석 개체 (cow JOIN으로 farmNo 필터)
      this.genomeTraitDetailRepository
        .createQueryBuilder('trait')
        .innerJoin('tb_cow', 'cow', 'trait.cowId = cow.cowId')
        .select('DISTINCT trait.cowId', 'cowId')
        .where('trait.delDt IS NULL')
        .andWhere('cow.fkFarmNo = :farmNo', { farmNo })
        .andWhere('cow.delDt IS NULL')
        .getRawMany()
        .then(rows => rows.map((r: { cowId: string }) => r.cowId).filter(Boolean)),
      // 유전자검사 개체 (cow JOIN으로 farmNo 필터)
      this.geneDetailRepository
        .createQueryBuilder('gene')
        .innerJoin('tb_cow', 'cow', 'gene.cowId = cow.cowId')
        .select('DISTINCT gene.cowId', 'cowId')
        .where('gene.delDt IS NULL')
        .andWhere('cow.fkFarmNo = :farmNo', { farmNo })
        .andWhere('cow.delDt IS NULL')
        .getRawMany()
        .then(rows => rows.map((r: { cowId: string }) => r.cowId).filter(Boolean)),
      // 번식능력검사 개체 (cow JOIN으로 farmNo 필터)
      this.mptRepository
        .createQueryBuilder('mpt')
        .innerJoin('tb_cow', 'cow', 'mpt.cowId = cow.cowId')
        .select('DISTINCT mpt.cowId', 'cowId')
        .where('mpt.delDt IS NULL')
        .andWhere('cow.fkFarmNo = :farmNo', { farmNo })
        .andWhere('cow.delDt IS NULL')
        .getRawMany()
        .then(rows => rows.map((r: { cowId: string }) => r.cowId).filter(Boolean)),
    ]);
    console.log(`[Dashboard] Step5 4개 테이블 조회 완료: ${Date.now() - startTime}ms`);

    // 합집합 계산 (중복 제외) - genomeRequest 포함 (리스트 페이지와 일치)
    const isTestFarm = viewerFarm?.regionSi !== VALID_REGION;

    /**
     * 대시보드 개체 수 집계 로직
     *
     * [일반 농가 (regionSi = '보은군')]
     * - 실제 검사(유전체/유전자/번식능력)를 받은 개체만 집계
     * - 검사 데이터가 없는 개체는 대시보드에 표시되지 않음
     *
     * [테스트/기관 농가 (regionSi != '보은군', 예: '코쿤')]
     * - 검사 여부와 관계없이 농장 소유 전체 개체를 집계
     * - UI 테스트 목적으로 가짜 데이터 없이도 개체 목록 확인 가능
     * - 실제 운영 데이터에는 영향 없음 (순위/평균 계산에서 제외됨)
     */
    const allTestedCowIds = isTestFarm
      ? farmCowIds  // 테스트 농가: 모든 소
      : new Set([
          ...farmGenomeRequestCowIds,
          ...farmGenomeCowIds,
          ...farmGeneCowIds,
          ...farmMptCowIds,
        ]);

    const totalCows = allTestedCowIds.size;
    const genomeCowCount = farmGenomeCowIds.length;
    const geneCowCount = farmGeneCowIds.length;
    const mptCowCount = farmMptCowIds.length;

    // 성별 체크 - 전체 검사 개체 기준 (M/수/1 = 수컷, 그 외 모두 암컷으로 처리)
    let maleCount = 0;
    let femaleCount = 0;
    for (const cowId of allTestedCowIds) {
      const cow = farmCowMap.get(cowId);
      const sex = cow?.cowSex?.toUpperCase();
      if (sex === 'M' || sex === '수' || sex === '1') {
        maleCount++;
      } else {
        femaleCount++;
      }
    }

    // Step 6: 검사 종류별 현황 (SNP, MS)
    const testTypeStats = {
      snp: {
        total: requests.filter(r => r.snpTest === 'Y' || r.snpTest === '유').length,
        completed: requests.filter(r => (r.snpTest === 'Y' || r.snpTest === '유') && r.chipSireName === '일치').length,
      },
      ms: {
        total: requests.filter(r => r.msTest === 'Y' || r.msTest === '유').length,
        completed: requests.filter(r => (r.msTest === 'Y' || r.msTest === '유') && r.msResultStatus).length,
      },
    };

    // Step 7: 친자감별 결과 현황 (상호 배타적 분류 - 총합이 totalRequests와 일치)
    // 1. 분석 완료: 부 일치 + 모가 불일치/이력제부재가 아닌 경우
    // 2. 부 불일치: 부가 일치가 아닌 경우
    // 3. 모 불일치: 부 일치 + 모 불일치
    // 4. 모 이력제부재: 부 일치 + 모 이력제부재
    const paternityStats = {
      // 분석 완료 (부 일치 + 모가 불일치/이력제부재 아님)
      analysisComplete: requests.filter(r =>
        r.chipSireName === '일치' &&
        r.chipDamName !== '불일치' &&
        r.chipDamName !== '이력제부재'
      ).length,
      // 부 불일치 (부가 일치가 아닌 모든 경우)
      sireMismatch: requests.filter(r => r.chipSireName && r.chipSireName !== '일치').length,
      // 모 불일치 (부 일치 + 모 불일치)
      damMismatch: requests.filter(r => r.chipSireName === '일치' && r.chipDamName === '불일치').length,
      // 모 이력제부재 (부 일치 + 모 이력제부재)
      damNoRecord: requests.filter(r => r.chipSireName === '일치' && r.chipDamName === '이력제부재').length,
      // 미분석 (chipSireName이 없는 경우)
      notAnalyzed: requests.filter(r => !r.chipSireName).length,
    };

    // Step 8: 월별 접수 현황 (올해 기준)
    const currentYear = new Date().getFullYear();
    const monthlyStats: { month: number; count: number }[] = [];
    for (let m = 1; m <= 12; m++) {
      const count = requests.filter(r => {
        if (!r.requestDt) return false;
        const dt = new Date(r.requestDt);
        return dt.getFullYear() === currentYear && dt.getMonth() + 1 === m;
      }).length;
      monthlyStats.push({ month: m, count });
    }

    // Step 9: 칩 종류별 분포
    const chipTypeMap = new Map<string, number>();
    for (const req of requests) {
      if (req.chipType) {
        chipTypeMap.set(req.chipType, (chipTypeMap.get(req.chipType) || 0) + 1);
      }
    }
    const chipTypeStats = Array.from(chipTypeMap.entries()).map(([chipType, count]) => ({
      chipType,
      count,
    }));

    // Step 10: 모근량별 분포
    const sampleAmountMap = new Map<string, number>();
    for (const req of requests) {
      if (req.sampleAmount) {
        sampleAmountMap.set(req.sampleAmount, (sampleAmountMap.get(req.sampleAmount) || 0) + 1);
      }
    }
    const sampleAmountStats = Array.from(sampleAmountMap.entries()).map(([sampleAmount, count]) => ({
      sampleAmount,
      count,
    }));

    console.log(`[Dashboard] 완료: ${Date.now() - startTime}ms`);

    return {
      yearlyStats,
      traitAverages,
      yearlyTraitAverages,
      yearlyAvgEbv,
      requestHistory,
      summary: {
        totalCows,          // 검사 받은 전체 개체 수 (합집합)
        genomeCowCount,     // 유전체 분석 개체 수
        geneCowCount,       // 유전자검사 개체 수
        mptCowCount,        // 번식능력검사 개체 수
        totalRequests,      // 유전체 의뢰 건수 (기존 호환성)
        analyzedCount,
        pendingCount,
        mismatchCount,
        maleCount,
        femaleCount,
      },
      testTypeStats,
      paternityStats,
      monthlyStats,
      chipTypeStats,
      sampleAmountStats,
    };
  }

  // ============================================
  // 유전체 분석 의뢰 (Genome Request) 관련 메서드
  // ============================================

  /**
   * 개체식별번호(cowId)로 유전체 데이터 조회
   * 가장 최근 분석 의뢰의 형질 상세 정보까지 포함하여 반환
   *
   * @usedBy /cow/[cowNo] - 개체 상세 페이지 (유전체 데이터 조회)
   * @param cowId - 개체식별번호 (예: KOR123456789)
   * @returns 유전체 분석 결과 배열
   *          - request: 분석 의뢰 정보
   *          - trait: 형질 기본 정보
   *          - genomeCows: 형질별 상세 데이터 (EBV, 백분위 등)
   * @throws NotFoundException - 개체를 찾을 수 없는 경우
   */
  async findByCowId(cowId: string): Promise<any[]> {
    // Step 1: cowId(개체식별번호)로 개체 정보 조회
    const cow = await this.cowRepository.findOne({
      where: { cowId: cowId, delDt: IsNull() },
    });

    // 개체가 없으면 404 에러
    if (!cow) {
      throw new NotFoundException(`Cow with cowId ${cowId} not found`);
    }

    // Step 2: 개체의 PK로 유전체 분석 의뢰 목록 조회
    const requests = await this.genomeRequestRepository.find({
      where: { fkCowNo: cow.pkCowNo, delDt: IsNull() },
      relations: ['cow', 'farm'],
      order: { regDt: 'DESC' },  // 최신순
    });

    // 분석 의뢰가 없으면 빈 배열 반환
    if (requests.length === 0) {
      return [];
    }

    // Step 3: cowId로 직접 형질 데이터 조회
    const latestRequest = requests[0];  // 최신 의뢰
    const traitDetails = await this.genomeTraitDetailRepository.find({
      where: { cowId: cowId, delDt: IsNull() },
    });

    // 형질 데이터가 없으면 빈 배열 반환
    if (traitDetails.length === 0) {
      return [];
    }

    // Step 4: 프론트엔드에서 사용할 형식으로 데이터 가공하여 반환
    return [{
      request: latestRequest,           // 분석 의뢰 정보
      genomeCows: traitDetails.map(detail => ({
        traitVal: detail.traitVal,              // 형질 측정값
        breedVal: detail.traitEbv,              // EBV (추정육종가)
        percentile: detail.traitPercentile,    // 백분위 순위
        traitName: detail.traitName,           // 형질명 (평평한 구조)
        traitCategory: getTraitCategory(detail.traitName || ''),  // 카테고리
        traitDesc: '',                         // 형질 설명 (빈값)
      })),
    }];
  }

  /**
   * 개체식별번호(cowId)로 유전체 분석 의뢰 정보 조회
   *
   * @usedBy /cow/[cowNo] - 개체 상세 페이지 (분석 의뢰 정보 조회)
   * @param cowId - 개체식별번호 (예: KOR002115897818)
   * @returns 최신 분석 의뢰 정보 (없으면 null)
   */
  async findRequestByCowIdentifier(cowId: string): Promise<GenomeRequestModel | null> {
    // Step 1: cowId로 개체 조회
    const cow = await this.cowRepository.findOne({
      where: { cowId: cowId, delDt: IsNull() },
    });

    if (!cow) {
      return null;
    }

    // Step 2: 해당 개체의 최신 분석 의뢰 조회
    const request = await this.genomeRequestRepository.findOne({
      where: { fkCowNo: cow.pkCowNo, delDt: IsNull() },
      relations: ['cow', 'farm'],
      order: { requestDt: 'DESC', regDt: 'DESC' },
    });

    return request || null;
  }

  // ============================================
  // 비교 분석 (Comparison) 관련 메서드
  // ============================================

  /**
   * 개체 기준 전국/지역/농장 카테고리별 평균 EBV 비교 데이터 조회
   *
   * 사용 목적: 특정 개체의 유전능력을 전국 평균, 같은 지역(군) 평균,
   *           같은 농장 평균과 비교하여 상대적 위치 파악
   *
   * @usedBy /cow/[cowNo] - 개체 상세 페이지 (카테고리별 레이더 차트)
   * @param cowId - 개체식별번호 (예: KOR123456789)
   * @returns 전국/지역/농장별 카테고리 평균 EBV
   * @throws NotFoundException - 개체를 찾을 수 없는 경우
   */
  async getComparisonAverages(cowId: string): Promise<ComparisonAveragesDto> {
    // Step 1: 개체식별번호로 개체 정보 조회
    const cow = await this.cowRepository.findOne({
      where: { cowId: cowId, delDt: IsNull() },
    });

    if (!cow) {
      throw new NotFoundException(`Cow with cowId ${cowId} not found`);
    }

    // Step 2: 개체의 최신 분석 의뢰에서 농장 정보 조회
    const request = await this.genomeRequestRepository.findOne({
      where: { fkCowNo: cow.pkCowNo, delDt: IsNull() },
      relations: ['farm'],
      order: { regDt: 'DESC' },
    });

    // 농장 번호 및 지역(시/군) 정보 추출
    const farmNo = request?.fkFarmNo;
    let regionSi: string | null = null;

    if (farmNo) {
      const farm = await this.farmRepository.findOne({
        where: { pkFarmNo: farmNo, delDt: IsNull() },
      });
      regionSi = farm?.regionSi || null;  // 농장의 지역(시/군) 정보 (보은군)
    }

    // Step 3: 전국 평균 계산 (필터 없이 모든 데이터)
    const nationwideAvg = await this.calculateCategoryAverages();

    // Step 4: 지역(시/군) 평균 계산 (보은군 전체)
    // - 지역 정보가 있으면 해당 지역만 필터링
    // - 없으면 전국 평균과 동일하게 처리
    const regionAvg = regionSi
      ? await this.calculateCategoryAverages({ regionSi })
      : nationwideAvg;

    // Step 5: 농장 평균 계산
    // - 농장 정보가 있으면 해당 농장만 필터링
    // - 없으면 지역 평균과 동일하게 처리
    const farmAvg = farmNo
      ? await this.calculateCategoryAverages({ farmNo })
      : regionAvg;

    // Step 6: 결과 반환
    return {
      nationwide: nationwideAvg,  // 전국 평균
      region: regionAvg,          // 지역 평균
      farm: farmAvg,              // 농장 평균
    };
  }

  /**
   * 개체 기준 전국/지역/농장 형질별 평균 EBV 비교 데이터 조회
   *
   * 사용 목적: 폴리곤 차트에서 형질별로 정확한 비교를 위해
   *           전국/지역/농장 평균을 형질 단위로 제공
   *
   * @usedBy /cow/[cowNo] - 개체 상세 페이지 (형질별 폴리곤 차트)
   * @param cowId - 개체식별번호 (예: KOR123456789)
   * @returns 전국/지역/농장별 형질별 평균 EBV
   * @throws NotFoundException - 개체를 찾을 수 없는 경우
   */
  async getTraitComparisonAverages(cowId: string): Promise<TraitComparisonAveragesDto> {
    // Step 1: 개체식별번호로 개체 정보 조회
    const cow = await this.cowRepository.findOne({
      where: { cowId: cowId, delDt: IsNull() },
    });

    if (!cow) {
      throw new NotFoundException(`Cow with cowId ${cowId} not found`);
    }

    // Step 2: 개체의 최신 분석 의뢰에서 농장 정보 조회
    const request = await this.genomeRequestRepository.findOne({
      where: { fkCowNo: cow.pkCowNo, delDt: IsNull() },
      relations: ['farm'],
      order: { regDt: 'DESC' },
    });

    // 농장 번호 및 지역(시/군) 정보 추출
    const farmNo = request?.fkFarmNo;
    let regionSi: string | null = null;

    if (farmNo) {
      const farm = await this.farmRepository.findOne({
        where: { pkFarmNo: farmNo, delDt: IsNull() },
      });
      regionSi = farm?.regionSi || null;
    }

    // Step 3: 전국 형질별 평균 계산
    const nationwideAvg = await this.calculateTraitAverages();

    // Step 4: 지역(시/군) 형질별 평균 계산 (보은군 전체)
    const regionAvg = regionSi
      ? await this.calculateTraitAverages({ regionSi })
      : nationwideAvg;

    // Step 5: 농장 형질별 평균 계산
    const farmAvg = farmNo
      ? await this.calculateTraitAverages({ farmNo })
      : regionAvg;

    // Step 6: 결과 반환
    return {
      nationwide: nationwideAvg,
      region: regionAvg,
      farm: farmAvg,
    };
  }

  /**
   * 형질별 평균 EBV 계산 (Private 메서드)
   *
   * @param filter - 필터 조건 (선택사항)
   * @returns 형질별 평균 EBV 배열
   */
  private async calculateTraitAverages(
    filter?: { farmNo?: number; regionSi?: string }
  ): Promise<TraitAverageDto[]> {
    // QueryBuilder를 사용한 동적 쿼리 구성
    const qb = this.genomeTraitDetailRepository
      .createQueryBuilder('detail')
      .innerJoin('tb_genome_request', 'request', 'detail.fk_request_no = request.pk_request_no')
      .where('detail.delDt IS NULL')
      .andWhere('detail.traitEbv IS NOT NULL');

    // 농장 필터 적용
    if (filter?.farmNo) {
      qb.andWhere('request.fk_farm_no = :farmNo', { farmNo: filter.farmNo });
    }

    // 지역(시/군) 필터 적용 (보은군 전체)
    if (filter?.regionSi) {
      qb.innerJoin('tb_farm', 'farm', 'request.fk_farm_no = farm.pk_farm_no')
        .andWhere('farm.region_si = :regionSi', { regionSi: filter.regionSi });
    }

    // 형질별 평균 계산 (GROUP BY 사용)
    const results = await qb
      .select('detail.traitName', 'traitName')
      .addSelect('AVG(detail.traitEbv)', 'avgEbv')
      .addSelect('AVG(detail.traitVal)', 'avgEpd')  // 육종가(EPD) 평균 추가
      .addSelect('COUNT(*)', 'count')
      .groupBy('detail.traitName')
      .getRawMany();

    // 결과 변환
    return results.map(row => ({
      traitName: row.traitName,
      category: TRAIT_CATEGORY_MAP[row.traitName] || '기타',
      avgEbv: Math.round(parseFloat(row.avgEbv) * 100) / 100,
      avgEpd: Math.round(parseFloat(row.avgEpd || 0) * 100) / 100,  // 육종가(EPD) 평균
      count: parseInt(row.count, 10),
    }));
  }

  /**
   * 카테고리별 평균 EBV 계산 (Private 메서드)
   *
   * 형질 상세 데이터를 조회하여 카테고리별로 그룹화 후 평균 계산
   *
   * @param filter - 필터 조건 (선택사항)
   *                 - farmNo: 특정 농장으로 필터링
   *                 - regionSi: 특정 지역(시/군)으로 필터링 (보은군 전체)
   * @returns 카테고리별 평균 EBV 배열 (성장/생산/체형/무게/비율)
   */
  private async calculateCategoryAverages(
    filter?: { farmNo?: number; regionSi?: string }
  ): Promise<CategoryAverageDto[]> {
    // QueryBuilder를 사용한 동적 쿼리 구성
    const qb = this.genomeTraitDetailRepository
      .createQueryBuilder('detail')
      // 분석 의뢰 테이블 JOIN (농장 정보 접근을 위해) - detail에서 직접 request로 연결
      .innerJoin('tb_genome_request', 'request', 'detail.fk_request_no = request.pk_request_no')
      // 삭제되지 않은 데이터만
      .where('detail.delDt IS NULL')
      // EBV 값이 있는 데이터만
      .andWhere('detail.traitEbv IS NOT NULL');

    // 농장 필터 적용
    if (filter?.farmNo) {
      qb.andWhere('request.fk_farm_no = :farmNo', { farmNo: filter.farmNo });
    }

    // 지역(시/군) 필터 적용 (보은군 전체)
    if (filter?.regionSi) {
      qb.innerJoin('tb_farm', 'farm', 'request.fk_farm_no = farm.pk_farm_no')
        .andWhere('farm.region_si = :regionSi', { regionSi: filter.regionSi });
    }

    // 형질명, EBV, EPD 모두 SELECT하여 조회
    const details = await qb
      .select(['detail.traitName', 'detail.traitEbv', 'detail.traitVal'])
      .getRawMany();

    // 카테고리별 합계/개수를 저장할 Map (EBV와 EPD 각각)
    const categoryMap = new Map<string, { ebvSum: number; epdSum: number; count: number }>();

    // 각 상세 데이터를 카테고리별로 분류 및 합산
    for (const detail of details) {
      const traitName = detail.detail_trait_name;  // Raw query 결과의 컬럼명
      const ebv = parseFloat(detail.detail_trait_ebv);
      const epd = parseFloat(detail.detail_trait_val);  // EPD (원래 육종가)

      // 유효하지 않은 데이터 스킵
      if (!traitName || isNaN(ebv)) continue;

      // 형질명으로 카테고리 결정 (매핑에 없으면 '기타')
      const category = TRAIT_CATEGORY_MAP[traitName] || '기타';

      // 해당 카테고리가 처음이면 초기화
      if (!categoryMap.has(category)) {
        categoryMap.set(category, { ebvSum: 0, epdSum: 0, count: 0 });
      }

      // 합계와 개수 누적
      const cat = categoryMap.get(category)!;
      cat.ebvSum += ebv;
      cat.epdSum += isNaN(epd) ? 0 : epd;
      cat.count += 1;
    }

    // 고정된 카테고리 순서로 결과 배열 생성
    const categories = ['성장', '생산', '체형', '무게', '비율'];
    return categories.map(category => {
      const data = categoryMap.get(category);
      return {
        category,
        // 평균 계산: 소수점 2자리까지 반올림
        avgEbv: data ? Math.round((data.ebvSum / data.count) * 100) / 100 : 0,
        avgEpd: data ? Math.round((data.epdSum / data.count) * 100) / 100 : 0,
        count: data?.count || 0,
      };
    });
  }

  // ============================================================
  // 선발지수 계산 메서드
  // ============================================================

  /**
   * 단일 개체 선발지수(가중 평균) 계산 + 전국/지역 순위
   *
   * @usedBy /cow/[cowNo] - 개체 상세 페이지 (선발지수 계산)
   * @param cowId - 개체식별번호 (예: KOR002119144049)
   * @param traitConditions - 형질별 가중치 조건 배열
   * @returns 선발지수 점수, 순위, 상세 내역
   *
   * @example
   * traitConditions = [
   *   { traitNm: '도체중', weight: 8 },
   *   { traitNm: '근내지방도', weight: 10 }
   * ]
   */
  async getSelectionIndex(
    cowId: string,
    traitConditions: { traitNm: string; weight?: number }[]
  ): Promise<{
    score: number | null;
    percentile: number | null;
    farmRank: number | null;        // 농가 순위
    farmTotal: number;              // 농가 전체 수
    regionRank: number | null;      // 지역(보은군) 순위
    regionTotal: number;            // 지역 전체 수
    regionName: string | null;      // 지역명
    farmerName: string | null;      // 농가명 (농장주명)
    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로 개체 조회
    const cow = await this.cowRepository.findOne({
      where: { cowId: cowId, delDt: IsNull() },
    });

    if (!cow) {
      throw new NotFoundException(`Cow with cowId ${cowId} not found`);
    }

    // Step 2: 최신 유전체 분석 의뢰 조회
    const latestRequest = await this.genomeRequestRepository.findOne({
      where: { fkCowNo: cow.pkCowNo, delDt: IsNull() },
      order: { requestDt: 'DESC', regDt: 'DESC' },
    });

    if (!latestRequest) {
      return {
        score: null, percentile: null,
        farmRank: null, farmTotal: 0,
        regionRank: null, regionTotal: 0, regionName: null, farmerName: null,
        farmAvgScore: null, regionAvgScore: null,
        details: [], histogram: [], message: '유전체 분석 데이터 없음'
      };
    }

    // Step 3: cowId로 직접 형질 상세 데이터 조회 (35개 형질의 EBV 값)
    const traitDetails = await this.genomeTraitDetailRepository.find({
      where: { cowId: cowId, delDt: IsNull() },
    });

    if (traitDetails.length === 0) {
      return {
        score: null, percentile: null,
        farmRank: null, farmTotal: 0,
        regionRank: null, regionTotal: 0, regionName: null, farmerName: null,
        farmAvgScore: null, regionAvgScore: null,
        details: [], histogram: [], message: '형질 데이터 없음'
      };
    }

    // 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
    }[] = [];

    for (const condition of traitConditions) {
      const trait = traitDetails.find((d) => d.traitName === condition.traitNm);
      const weight = condition.weight || 1;  // 가중치 (기본값: 1)

      if (trait && trait.traitEbv !== null) {
        const ebv = Number(trait.traitEbv);
        // 등지방두께 등 낮을수록 좋은 형질은 부호 반전
        const isNegativeTrait = NEGATIVE_TRAITS.includes(condition.traitNm);
        const adjustedEbv = isNegativeTrait ? -ebv : ebv;
        const contribution = adjustedEbv * weight;  // EBV × 가중치

        weightedSum += contribution;
        totalWeight += weight;

        // 백분위 평균 계산용
        if (trait.traitPercentile !== null) {
          percentileSum += Number(trait.traitPercentile);
          percentileCount++;
        }

        details.push({
          traitNm: condition.traitNm,
          ebv: ebv,
          weight: weight,
          contribution: contribution,
        });
      } else {
        // 형질이 없거나 EBV가 null이면 플래그 설정
        hasAllTraits = false;
      }
    }

    // Step 6: 최종 점수 계산 (모든 선택 형질이 있어야만 계산) ================================================================
    const score = (hasAllTraits && totalWeight > 0) ? weightedSum : null;
    const percentile = percentileCount > 0 ? percentileSum / percentileCount : null;

    // Step 7: 현재 개체의 농장/지역 정보 조회
    let regionName: string | null = null;
    let farmerName: string | null = null;
    const farmNo: number | null = latestRequest.fkFarmNo;

    if (farmNo) {
      const farm = await this.farmRepository.findOne({
        where: { pkFarmNo: farmNo, delDt: IsNull() },
      });
      regionName = farm?.regionSi || null;
      farmerName = farm?.farmerName || null;
    }

    // Step 8: 선택 형질 누락 시 조기 반환
    if (!hasAllTraits) {
      return {
        score: null,
        percentile: null,
        farmRank: null,
        farmTotal: 0,
        regionRank: null,
        regionTotal: 0,
        regionName,
        farmerName,
        farmAvgScore: null,
        regionAvgScore: null,
        details,
        histogram: [],
        message: '선택한 형질 중 일부 데이터가 없습니다',
      };
    }

    // Step 9: 농가/지역 순위 및 평균 선발지수 계산
    const { farmRank, farmTotal, regionRank, regionTotal, farmAvgScore, regionAvgScore, histogram } =
      await this.calculateRanks(cowId, score, traitConditions, farmNo, regionName);

    return {
      score: score !== null ? Math.round(score * 100) / 100 : null,
      percentile: percentile !== null ? Math.round(percentile * 100) / 100 : null,
      farmRank,
      farmTotal,
      regionRank,
      regionTotal,
      regionName,
      farmerName,
      farmAvgScore,
      regionAvgScore,
      details,
      histogram,
    };
  }

  /**
   * 농가/지역 순위 계산 (Private)
   *
   * @param currentCowId - 현재 개체 식별번호
   * @param currentScore - 현재 개체 점수
   * @param traitConditions - 형질별 가중치 조건
   * @param farmNo - 농가 번호
   * @param regionName - 지역명 (보은군 등)
   */
  private async calculateRanks(
    currentCowId: string,
    currentScore: number | null,
    traitConditions: { traitNm: string; weight?: number }[],
    farmNo: number | null,
    regionName: string | null
  ): Promise<{
    farmRank: number | null;
    farmTotal: number;
    regionRank: number | null;
    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, histogram: [] };
    }

    // 모든 유전체 분석 의뢰 조회 (유전체 데이터가 있는 모든 개체)
    const allRequests = await this.genomeRequestRepository.find({
      where: { delDt: IsNull() },
      relations: ['cow', 'farm'],
    });

    // 각 개체별 점수 계산
    const allScores: { cowId: string; score: number; farmNo: number | null; regionSi: string | null }[] = [];

    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;

      // cowId로 직접 형질 상세 데이터 조회
      const traitDetails = await this.genomeTraitDetailRepository.find({
        where: { cowId: request.cow.cowId, delDt: IsNull() },
      });

      if (traitDetails.length === 0) continue;

      // 가중 평균 계산 (모든 형질 있어야 점수 계산)
      let weightedSum = 0;
      let totalWeight = 0;
      let hasAllTraits = true;

      for (const condition of traitConditions) {
        const trait = traitDetails.find((d) => d.traitName === condition.traitNm);
        const weight = condition.weight || 1;

        if (trait && trait.traitEbv !== null) {
          // 등지방두께 등 낮을수록 좋은 형질은 부호 반전
          const ebv = Number(trait.traitEbv);
          const isNegativeTrait = NEGATIVE_TRAITS.includes(condition.traitNm);
          const adjustedEbv = isNegativeTrait ? -ebv : ebv;
          weightedSum += adjustedEbv * weight;
          totalWeight += weight;
        } else {
          hasAllTraits = false;
        }
      }

      // 모든 선택 형질이 있는 경우만 점수에 포함
      if (hasAllTraits && totalWeight > 0) {
        const score = weightedSum;
        allScores.push({
          cowId: request.cow.cowId,
          score: Math.round(score * 100) / 100,
          farmNo: request.fkFarmNo,
          regionSi: request.farm?.regionSi || null,
        });
      }
    }

    // 점수 기준 내림차순 정렬
    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;
    let farmTotal = 0;
    let farmAvgScore: number | null = null;

    if (farmNo) {
      const farmScores = allScores.filter(s => s.farmNo === farmNo);
      farmTotal = farmScores.length;
      const farmIndex = farmScores.findIndex(s => s.cowId === currentCowId);
      farmRank = farmIndex >= 0 ? farmIndex + 1 : null;

      // 농가 평균 선발지수 계산
      if (farmScores.length > 0) {
        const farmScoreSum = farmScores.reduce((sum, s) => sum + s.score, 0);
        farmAvgScore = Math.round((farmScoreSum / farmScores.length) * 100) / 100;
      }
    }

    // 지역(보은군) 순위 및 평균 선발지수 계산 - 전체 유전체 데이터가 보은군이므로 필터링 없이 전체 사용
    let regionRank: number | null = null;
    const regionTotal = allScores.length;
    let regionAvgScore: number | null = null;

    const regionIndex = allScores.findIndex(s => s.cowId === currentCowId);
    regionRank = regionIndex >= 0 ? regionIndex + 1 : null;

    // 보은군(지역) 평균 선발지수 계산
    if (allScores.length > 0) {
      const regionScoreSum = allScores.reduce((sum, s) => sum + s.score, 0);
      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,
      regionRank,
      regionTotal,
      farmAvgScore,
      regionAvgScore,
      histogram,
    };
  }

  /**
   * 개별 형질 기준 순위 조회
   *
   * @usedBy /cow/[cowNo]/genome - 개체 상세 > 유전체 통합 비교, 정규분포 차트
   * @param cowId - 개체식별번호 (KOR...)
   * @param traitName - 형질명 (도체중, 근내지방도 등)
   */
  async getTraitRank(cowId: string, traitName: string): Promise<{
    traitName: string;
    cowEbv: number | null;
    cowEpd: number | null;      // 개체 육종가(EPD)
    farmRank: number | null;
    farmTotal: number;
    regionRank: number | null;
    regionTotal: number;
    farmAvgEbv: number | null;
    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({
      where: { cowId, delDt: IsNull() },
    });

    if (!cow) {
      return {
        traitName,
        cowEbv: null,
        cowEpd: null,
        farmRank: null,
        farmTotal: 0,
        regionRank: null,
        regionTotal: 0,
        farmAvgEbv: null,
        regionAvgEbv: null,
        farmAvgEpd: null,
        regionAvgEpd: null,
        histogram: [],
      };
    }

    const currentRequest = await this.genomeRequestRepository.findOne({
      where: { fkCowNo: cow.pkCowNo, delDt: IsNull() },
      relations: ['farm'],
    });

    if (!currentRequest) {
      return {
        traitName,
        cowEbv: null,
        cowEpd: null,
        farmRank: null,
        farmTotal: 0,
        regionRank: null,
        regionTotal: 0,
        farmAvgEbv: null,
        regionAvgEbv: null,
        farmAvgEpd: null,
        regionAvgEpd: null,
        histogram: [],
      };
    }

    const farmNo = currentRequest.fkFarmNo;

    // 2. 모든 유전체 분석 의뢰 조회
    const allRequests = await this.genomeRequestRepository.find({
      where: { delDt: IsNull() },
      relations: ['cow', 'farm'],
    });

    // 3. 각 개체별 해당 형질 EBV, EPD 수집
    const allScores: { cowId: string; ebv: number; epd: number | null; farmNo: number | null }[] = [];

    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({
        where: {
          fkRequestNo: request.pkRequestNo,
          traitName: traitName,
          delDt: IsNull()
        },
      });

      if (traitDetail && traitDetail.traitEbv !== null) {
        allScores.push({
          cowId: request.cow.cowId,
          ebv: Number(traitDetail.traitEbv),
          epd: traitDetail.traitVal !== null ? Number(traitDetail.traitVal) : null,  // 육종가(EPD)
          farmNo: request.fkFarmNo,
        });
      }
    }

    // 4. EBV 기준 내림차순 정렬
    allScores.sort((a, b) => b.ebv - a.ebv);

    // 5. 현재 개체의 EBV, EPD 찾기
    const currentCowData = allScores.find(s => s.cowId === cowId);
    const cowEbv = currentCowData?.ebv ?? null;
    const cowEpd = currentCowData?.epd ?? null;

    // 6. 보은군 전체 순위
    const regionRank = currentCowData
      ? allScores.findIndex(s => s.cowId === cowId) + 1
      : null;
    const regionTotal = allScores.length;

    // 보은군 평균 EBV, EPD
    const regionAvgEbv = allScores.length > 0
      ? Math.round((allScores.reduce((sum, s) => sum + s.ebv, 0) / allScores.length) * 100) / 100
      : null;
    const regionEpdValues = allScores.filter(s => s.epd !== null).map(s => s.epd as number);
    const regionAvgEpd = regionEpdValues.length > 0
      ? Math.round((regionEpdValues.reduce((sum, v) => sum + v, 0) / regionEpdValues.length) * 100) / 100
      : null;

    // 7. 농가 내 순위
    const farmScores = allScores.filter(s => s.farmNo === farmNo);
    const farmRank = currentCowData && farmNo
      ? farmScores.findIndex(s => s.cowId === cowId) + 1
      : null;
    const farmTotal = farmScores.length;

    // 농가 평균 EBV, EPD
    const farmAvgEbv = farmScores.length > 0
      ? Math.round((farmScores.reduce((sum, s) => sum + s.ebv, 0) / farmScores.length) * 100) / 100
      : null;
    const farmEpdValues = farmScores.filter(s => s.epd !== null).map(s => s.epd as number);
    const farmAvgEpd = farmEpdValues.length > 0
      ? 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;
        
        // rate 형질 여부 확인 (형질명에 'rate' 또는 'Rate' 포함)
        const isRateTrait = traitName.toLowerCase().includes('rate');
        
        // 구간 크기 결정
        let binSize: number;
        if (isRateTrait) {
          // rate 형질: 소수점 binSize 사용 (더 촘촘한 구간)
          binSize = range > 0 ? range / 25 : 0.1;
          // 너무 작으면 최소값 보장
          if (binSize < 0.1) binSize = 0.1;
          // 소수점 둘째자리까지 반올림
          binSize = Math.round(binSize * 100) / 100;
          
          console.log(`📊 [${traitName}] rate 형질 히스토그램 생성:`, {
            범위: `${minEpd.toFixed(2)} ~ ${maxEpd.toFixed(2)}`,
            range: range.toFixed(2),
            binSize: binSize.toFixed(2),
            구간방식: '소수점'
          });
        } else {
          // 일반 형질: 기존 로직 (정수 binSize)
          binSize = range > 0 ? Math.ceil(range / 25) : 1;
        }
        
        // 구간별 집계
        const binMap = new Map<number, { count: number; farmCount: number }>();
        
        epdValues.forEach(({ epd, farmNo: scoreFarmNo }) => {
          // rate 형질은 소수점 구간, 일반 형질은 정수 구간
          const binStart = isRateTrait 
            ? Math.round((Math.floor(epd / binSize) * binSize) * 100) / 100  // 소수점 둘째자리까지
            : 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을 배열로 변환 및 정렬
        const sortedHistogram = Array.from(binMap.entries())
          .map(([bin, data]) => ({ bin, count: data.count, farmCount: data.farmCount }))
          .sort((a, b) => a.bin - b.bin);
        
        histogram.push(...sortedHistogram);
        
        // rate 형질일 때만 로그 출력
        if (isRateTrait && sortedHistogram.length > 0) {
          console.log(`📊 [${traitName}] 최종 히스토그램:`, {
            구간수: sortedHistogram.length,
            첫구간: sortedHistogram[0].bin,
            마지막구간: sortedHistogram[sortedHistogram.length - 1].bin,
            샘플: sortedHistogram.slice(0, 5).map(h => `${h.bin.toFixed(2)}(${h.count}마리)`)
          });
        }
      }
    }

    return {
      traitName,
      cowEbv: cowEbv !== null ? Math.round(cowEbv * 100) / 100 : null,
      cowEpd: cowEpd !== null ? Math.round(cowEpd * 100) / 100 : null,
      farmRank: farmRank && farmRank > 0 ? farmRank : null,
      farmTotal,
      regionRank: regionRank && regionRank > 0 ? regionRank : null,
      regionTotal,
      farmAvgEbv,
      regionAvgEbv,
      farmAvgEpd,
      regionAvgEpd,
      histogram,  // 실제 데이터 분포 추가
    };
  }

  /**
   * 농가의 보은군 내 순위 조회 (대시보드용)
   * JOIN으로 한 번에 조회
   *
   * @usedBy /dashboard - 대시보드 페이지 (농가 순위 카드)
   * @param farmNo - 농장 번호
   */
  async getFarmRegionRanking(
    farmNo: number,
    inputTraitConditions?: { traitNm: string; weight?: number }[]
  ): Promise<{
    farmNo: number;
    farmerName: string | null;
    farmAvgScore: number | null;
    regionAvgScore: number | null;
    farmRankInRegion: number | null;
    totalFarmsInRegion: number;
    percentile: number | null;
    farmCowCount: number;
    regionCowCount: number;
  }> {
    // 1. 농가 정보 조회
    const farm = await this.farmRepository.findOne({
      where: { pkFarmNo: farmNo, delDt: IsNull() },
    });

    if (!farm) {
      return {
        farmNo,
        farmerName: null,
        farmAvgScore: null,
        regionAvgScore: null,
        farmRankInRegion: null,
        totalFarmsInRegion: 0,
        percentile: null,
        farmCowCount: 0,
        regionCowCount: 0,
      };
    }

    // 2. 모든 유전체 분석 의뢰 조회 (traitDetails 포함)
    const allRequests = await this.genomeRequestRepository.find({
      where: { delDt: IsNull() },
      relations: ['cow', 'farm', 'traitDetails'],
    });

    // 3. inputTraitConditions가 있으면 사용, 없으면 35개 형질 기본값 사용 (ALL_TRAITS는 공통 상수에서 import)
    const traitConditions = inputTraitConditions && inputTraitConditions.length > 0
      ? inputTraitConditions // 프론트에서 보낸 형질사용
      : ALL_TRAITS.map(traitNm => ({ traitNm, weight: 1 })); // 기본값 사용

    console.log('[getFarmRegionRanking] traitConditions:', traitConditions.length, 'traits');

    // 4. 각 개체별 점수 계산
    const allScores: { cowId: string; score: number; farmNo: number | null }[] = [];

    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 사용
      const traitDetails = request.traitDetails;
      if (!traitDetails || traitDetails.length === 0) continue;

      let weightedSum = 0;
      let totalWeight = 0;
      let hasAllTraits = true;

      for (const condition of traitConditions) {
        const trait = traitDetails.find((d) => d.traitName === condition.traitNm);
        const weight = condition.weight || 1;

        if (trait && trait.traitEbv !== null) {
          // 등지방두께 등 낮을수록 좋은 형질은 부호 반전
          const ebv = Number(trait.traitEbv);
          const isNegativeTrait = NEGATIVE_TRAITS.includes(condition.traitNm);
          const adjustedEbv = isNegativeTrait ? -ebv : ebv;
          weightedSum += adjustedEbv * weight;
          totalWeight += weight;
        } else {
          hasAllTraits = false;
        }
      }

      if (hasAllTraits && totalWeight > 0) {
        const score = weightedSum;
        allScores.push({
          cowId: request.cow.cowId,
          score: Math.round(score * 100) / 100,
          farmNo: request.fkFarmNo,
        });
      }
    }

    // 6. 농가별 평균 점수 계산
    const farmScoresMap = new Map<number, { scores: number[]; farmerName: string | null }>();

    for (const score of allScores) {
      if (score.farmNo === null) continue;

      if (!farmScoresMap.has(score.farmNo)) {
        const farmInfo = allRequests.find(r => r.fkFarmNo === score.farmNo)?.farm;
        farmScoresMap.set(score.farmNo, {
          scores: [],
          farmerName: farmInfo?.farmerName || null,
        });
      }
      farmScoresMap.get(score.farmNo)!.scores.push(score.score);
    }

    // 7. 농가별 평균 계산 및 정렬
    const farmAverages: { farmNo: number; avgScore: number; cowCount: number; farmerName: string | null }[] = [];

    for (const [fNo, data] of farmScoresMap.entries()) {
      if (data.scores.length > 0) {
        const avg = data.scores.reduce((a, b) => a + b, 0) / data.scores.length;
        farmAverages.push({
          farmNo: fNo,
          avgScore: Math.round(avg * 100) / 100,
          cowCount: data.scores.length,
          farmerName: data.farmerName,
        });
      }
    }

    // 내림차순 정렬
    farmAverages.sort((a, b) => b.avgScore - a.avgScore);

    // 8. 현재 농가 순위 찾기 (표준 경쟁 순위: 동률 시 같은 순위)
    const myFarmData = farmAverages.find(f => f.farmNo === farmNo);
    let farmRankInRegion: number | null = null;
    if (myFarmData) {
      // 나보다 높은 점수를 가진 농장 수 + 1 = 내 순위
      const higherCount = farmAverages.filter(f => f.avgScore > myFarmData.avgScore).length;
      farmRankInRegion = higherCount + 1;
    }

    // 9. 보은군 전체 평균
    const regionAvgScore = allScores.length > 0
      ? 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,
      farmAvgScore: myFarmData?.avgScore ?? null,
      regionAvgScore,
      farmRankInRegion,
      totalFarmsInRegion: farmAverages.length,
      percentile: farmRankInRegion !== null && farmAverages.length > 0
        ? Math.round((farmRankInRegion / farmAverages.length) * 100)
        : null,
      farmCowCount: myFarmData?.cowCount || 0,
      regionCowCount: allScores.length,
    };
  }

  /**
   * 연도별 EBV 통계 조회 (개체상세용)
   * getDashboardStats의 yearlyStats와 yearlyAvgEbv 부분만 반환
   *
   * @usedBy /cow/[cowNo]/genome - 개체 상세 > 유전체 통합 비교
   * @param farmNo - 농장 번호
   */
  async getYearlyEbvStats(farmNo: number): Promise<{
    yearlyStats: {
      year: number;
      totalRequests: number;
      analyzedCount: number;
      pendingCount: number;
      sireMatchCount: number;
      analyzeRate: number;
      sireMatchRate: number;
    }[];
    yearlyAvgEbv: {
      year: number;
      farmAvgEbv: number;
      regionAvgEbv: number;
      traitCount: number;
    }[];
  }> {
    const dashboardStats = await this.getDashboardStats(farmNo);
    return {
      yearlyStats: dashboardStats.yearlyStats,
      yearlyAvgEbv: dashboardStats.yearlyAvgEbv,
    };
  }

  /**
   * 연도별 유전능력 추이 (형질별/카테고리별)
   * JOIN으로 한 번에 조회
   *
   * @usedBy /dashboard - 대시보드 페이지 (연도별 추이 차트)
   * @param farmNo - 농장 번호
   * @param traitName - 형질명 (선택, 없으면 카테고리 전체)
   * @param category - 카테고리명 (성장/생산/체형/무게/비율)
   */
  async getYearlyTraitTrend(
    farmNo: number,
    category: string,
    traitName?: string,
  ): Promise<{
    category: string;
    traitName: string | null;
    yearlyData: {
      year: number;
      farmAvgEbv: number;
      regionAvgEbv: number;
      farmCount: number;
      regionCount: number;
    }[];
    traitList: string[];
    farmRank: {
      rank: number | null;
      totalFarms: number;
      percentile: number | null;
      farmAvgEbv: number | null;
      regionAvgEbv: number;
    };
  }> {
    // 해당 카테고리의 형질 목록
    const traitsInCategory = Object.entries(TRAIT_CATEGORY_MAP)
      .filter(([_, cat]) => cat === category)
      .map(([trait, _]) => trait);

    // 대상 형질 결정
    const targetTraits = traitName ? [traitName] : traitsInCategory;

    // 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',
        'd.cow_id = c.cow_id AND d.del_dt IS NULL',
      )
      .select('r.fk_farm_no', 'reqFarmNo')
      .addSelect('r.chip_sire_name', 'chipSireName')
      .addSelect('r.chip_dam_name', 'chipDamName')
      .addSelect('c.cow_id', 'cowId')
      .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();

    // cowId별로 데이터 그룹화
    const cowDataMap = new Map<string, {
      farmNo: number;
      year: number;
      regionSi: string | null;
      chipSireName: string | null;
      chipDamName: string | null;
      traits: { traitName: string; traitVal: number }[];
    }>();

    for (const row of allData) {
      const cowId = row.cowId;
      if (!cowId) continue;

      if (!cowDataMap.has(cowId)) {
        cowDataMap.set(cowId, {
          farmNo: row.reqFarmNo,
          year: row.year || new Date().getFullYear(),
          regionSi: row.regionSi || null,
          chipSireName: row.chipSireName,
          chipDamName: row.chipDamName,
          traits: [],
        });
      }
      if (row.traitVal !== null) {
        cowDataMap.get(cowId)!.traits.push({
          traitName: row.traitName,
          traitVal: parseFloat(row.traitVal),
        });
      }
    }

    // 연도별/농가별 데이터 집계
    const farmYearMap = new Map<number, { sum: number; count: number }>();
    const regionYearMap = new Map<number, { sum: number; count: number }>();
    const farmEbvMap = new Map<number, { sum: number; count: number }>();

    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;

      // 대상 형질의 평균 육종가 계산
      const targetTraitData = data.traits.filter(t => targetTraits.includes(t.traitName));
      if (targetTraitData.length === 0) continue;

      const avgVal = targetTraitData.reduce((sum, t) => sum + t.traitVal, 0) / targetTraitData.length;
      const year = data.year;
      const cowFarmNo = data.farmNo;

      // 보은군 전체 (연도별)
      if (!regionYearMap.has(year)) {
        regionYearMap.set(year, { sum: 0, count: 0 });
      }
      const regionYearData = regionYearMap.get(year)!;
      regionYearData.sum += avgVal;
      regionYearData.count++;

      // 요청 농가 (연도별)
      if (cowFarmNo === farmNo) {
        if (!farmYearMap.has(year)) {
          farmYearMap.set(year, { sum: 0, count: 0 });
        }
        const farmYearData = farmYearMap.get(year)!;
        farmYearData.sum += avgVal;
        farmYearData.count++;
      }

      // 농가별 평균 (순위용)
      if (cowFarmNo) {
        if (!farmEbvMap.has(cowFarmNo)) {
          farmEbvMap.set(cowFarmNo, { sum: 0, count: 0 });
        }
        const farmData = farmEbvMap.get(cowFarmNo)!;
        farmData.sum += avgVal;
        farmData.count++;
      }
    }

    // 모든 연도 합치기
    const allYears = new Set([...farmYearMap.keys(), ...regionYearMap.keys()]);
    const yearlyData = Array.from(allYears)
      .sort((a, b) => a - b)
      .map(year => {
        const farmData = farmYearMap.get(year);
        const regionData = regionYearMap.get(year);
        return {
          year,
          farmAvgEbv: farmData ? Math.round((farmData.sum / farmData.count) * 100) / 100 : 0,
          regionAvgEbv: regionData ? Math.round((regionData.sum / regionData.count) * 100) / 100 : 0,
          farmCount: farmData?.count || 0,
          regionCount: regionData?.count || 0,
        };
      });

    // 농가별 평균 계산 및 정렬
    const farmAverages = Array.from(farmEbvMap.entries())
      .map(([fNo, data]) => ({
        farmNo: fNo,
        avgEbv: data.count > 0 ? Math.round((data.sum / data.count) * 100) / 100 : 0,
      }))
      .sort((a, b) => b.avgEbv - a.avgEbv);

    // 현재 농가 순위
    const myFarmIndex = farmAverages.findIndex(f => f.farmNo === farmNo);
    const myFarmData = farmAverages.find(f => f.farmNo === farmNo);

    // 보은군 전체 평균
    const allFarmEbvs = farmAverages.map(f => f.avgEbv);
    const regionAvgEbv = allFarmEbvs.length > 0
      ? Math.round((allFarmEbvs.reduce((a, b) => a + b, 0) / allFarmEbvs.length) * 100) / 100
      : 0;

    return {
      category,
      traitName: traitName || null,
      yearlyData,
      traitList: traitsInCategory,
      // 농가 순위 정보 추가
      farmRank: {
        rank: myFarmIndex >= 0 ? myFarmIndex + 1 : null,
        totalFarms: farmAverages.length,
        percentile: myFarmIndex >= 0 && farmAverages.length > 0
          ? Math.round(((myFarmIndex + 1) / farmAverages.length) * 100)
          : null,
        farmAvgEbv: myFarmData?.avgEbv || null,
        regionAvgEbv,
      },
    };
  }

  /**
   * 농장의 가장 최근 분석 연도 조회
   * chip_report_dt 또는 ms_report_dt 중 가장 최근 날짜의 년도 반환
   * 둘 다 없으면 현재 년도 반환
   *
   * @param farmNo - 농장 번호
   * @returns { year: number } - 가장 최근 분석 연도
   */
  async getLatestAnalysisYear(farmNo: number): Promise<{ year: number }> {
    console.log(`[getLatestAnalysisYear] farmNo: ${farmNo}`);
    
    // 농장의 모든 분석 의뢰 조회
    const requests = await this.genomeRequestRepository.find({
      where: { fkFarmNo: farmNo, delDt: IsNull() },
      select: ['chipReportDt', 'msReportDt'],
    });

    console.log(`[getLatestAnalysisYear] Found ${requests?.length || 0} requests`);

    if (!requests || requests.length === 0) {
      console.log('[getLatestAnalysisYear] No requests found, returning current year');
      return { year: new Date().getFullYear() };
    }

    // chip_report_dt와 ms_report_dt 중 가장 최근 날짜 찾기
    let latestDate: Date | null = null;
    let latestChipDate: Date | null = null;
    let latestMsDate: Date | null = null;

    for (const request of requests) {
      // chip_report_dt 확인
      if (request.chipReportDt) {
        const chipDate = new Date(request.chipReportDt);
        if (!latestChipDate || chipDate > latestChipDate) {
          latestChipDate = chipDate;
        }
        if (!latestDate || chipDate > latestDate) {
          latestDate = chipDate;
        }
      }

      // ms_report_dt 확인
      if (request.msReportDt) {
        const msDate = new Date(request.msReportDt);
        if (!latestMsDate || msDate > latestMsDate) {
          latestMsDate = msDate;
        }
        if (!latestDate || msDate > latestDate) {
          latestDate = msDate;
        }
      }
    }

    console.log(`[getLatestAnalysisYear] Latest chip_report_dt: ${latestChipDate?.toISOString()}`);
    console.log(`[getLatestAnalysisYear] Latest ms_report_dt: ${latestMsDate?.toISOString()}`);
    console.log(`[getLatestAnalysisYear] Latest date overall: ${latestDate?.toISOString()}`);

    // 가장 최근 날짜가 있으면 그 연도, 없으면 현재 연도
    const year = latestDate ? latestDate.getFullYear() : new Date().getFullYear();

    console.log(`[getLatestAnalysisYear] Returning year: ${year}`);

    return { year };
  }
}