package com.ruoyi.web.modules.screen.support;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.time.LocalDate;
import java.time.YearMonth;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;

import com.ruoyi.web.modules.industryservice.domain.*;
import com.ruoyi.web.modules.industryservice.domain.dto.BreedingIndustryYakSnapshot;
import com.ruoyi.web.modules.industryservice.domain.vo.BreedingIndustryAgeBandVo;
import com.ruoyi.web.modules.industryservice.domain.vo.BreedingIndustryDegradationStatVo;
import com.ruoyi.web.modules.industryservice.support.BreedingIndustryCalculator;
import com.ruoyi.web.modules.industryservice.support.BreedingIndustryRules;
import com.ruoyi.web.modules.industryservice.support.GrasslandRules;
import com.ruoyi.web.modules.industryservice.support.GrassLivestockImbalanceWarningRules;
import com.ruoyi.web.modules.screen.domain.dto.HomeScreenAppointmentStatDto;
import com.ruoyi.web.modules.screen.domain.dto.HomeScreenCoopBusinessModelSumDto;
import com.ruoyi.web.modules.screen.domain.dto.HomeScreenDisposableIncomeYearDto;
import com.ruoyi.web.modules.screen.domain.dto.HomeScreenEnterpriseFinanceSumDto;
import com.ruoyi.web.modules.screen.domain.dto.HomeScreenFarmerHouseholdClassSumDto;
import com.ruoyi.web.modules.screen.domain.dto.HomeScreenHerdCowAgeSumDto;
import com.ruoyi.web.modules.screen.domain.dto.HomeScreenHerdGenderMonthDto;
import com.ruoyi.web.modules.screen.domain.dto.HomeScreenHerdSpeciesSumDto;
import com.ruoyi.web.modules.screen.domain.dto.HomeScreenHerdYakStructureSumDto;
import com.ruoyi.web.modules.screen.domain.dto.HomeScreenMonthCountDto;
import com.ruoyi.web.modules.screen.domain.dto.HomeScreenOutboundSpeciesSumDto;
import com.ruoyi.web.modules.screen.domain.dto.HomeScreenSelfConsumptionSumDto;
import com.ruoyi.web.modules.screen.domain.dto.HomeScreenStatPeriodDto;
import com.ruoyi.web.modules.screen.domain.dto.HomeScreenYearMonthBullCowDto;
import com.ruoyi.web.modules.screen.domain.dto.HomeScreenYearMonthCountDto;
import com.ruoyi.web.modules.screen.domain.dto.HomeScreenSubsidyTypeSumDto;
import com.ruoyi.web.modules.screen.domain.dto.HomeScreenTrainingMonthDto;
import com.ruoyi.web.modules.screen.domain.vo.*;

/**
 * 大屏首页口径组装与纯计算（无 DB / HTTP 依赖，便于单测）。
 * <p>
 * 核心约定：{@link #resolveStatDate(int)} 决定统计截止日；当年未到的月份在趋势类方法中补 0。
 */
public final class HomeScreenSupport
{
    private static final DateTimeFormatter STAT_DATE_FMT = DateTimeFormatter.ofPattern("yyyy-MM-dd");

    private static final String[] DEGRADATION_LABELS = {
            "", "未退化", "轻度", "中度", "重度", "极重度"
    };

    private static final int PROVIDER_VET = 1;

    private static final int PROVIDER_ORG = 2;

    private static final int PROVIDER_EXPERT = 3;

    /** 母牛分龄 bandCode（存栏填报口径：0-1.5 / 1.5+） */
    public static final String BAND_COW_YOUNG = "COW_YOUNG";

    public static final String BAND_COW_ADULT = "COW_ADULT";

    public static final String LABEL_COW_YOUNG = "0～1.5岁幼畜";

    public static final String LABEL_COW_ADULT = "1.5岁以上成畜";

    public static final String SPECIES_YAK = "YAK";

    public static final String SPECIES_DAIRY = "DAIRY";

    public static final String SPECIES_SHEEP = "SHEEP";

    public static final String LABEL_SPECIES_YAK = "牦牛";

    public static final String LABEL_SPECIES_DAIRY = "奶牛";

    public static final String LABEL_SPECIES_SHEEP = "羊";

    public static final String YAK_CAT_BULL = "BULL";

    public static final String YAK_CAT_COW = "COW";

    public static final String YAK_CAT_INSURED = "INSURED";

    public static final String LABEL_YAK_CAT_BULL = "公牛";

    public static final String LABEL_YAK_CAT_COW = "母牛";

    public static final String LABEL_YAK_CAT_INSURED = "保险牛";

    public static final String YAK_DETAIL_BREEDING_BULL = "BREEDING_BULL";

    public static final String YAK_DETAIL_BULL_CASTRATED = "BULL_CASTRATED";

    public static final String YAK_DETAIL_COW_YOUNG = "COW_YOUNG";

    public static final String YAK_DETAIL_COW_ADULT = "COW_ADULT";

    public static final String YAK_DETAIL_INSURED = "INSURED";

    public static final String LABEL_YAK_DETAIL_BREEDING_BULL = "种公牛";

    public static final String LABEL_YAK_DETAIL_BULL_CASTRATED = "公牛/阉畜";

    public static final String LABEL_YAK_DETAIL_COW_YOUNG = "0～1.5岁";

    public static final String LABEL_YAK_DETAIL_COW_ADULT = "1.5岁以上";

    public static final String LABEL_YAK_DETAIL_INSURED = "保险";

    public static final String OUTBOUND_YAK = "YAK";

    public static final String OUTBOUND_CATTLE = "CATTLE";

    public static final String OUTBOUND_SHEEP = "SHEEP";

    public static final String OUTBOUND_GOAT = "GOAT";

    public static final String OUTBOUND_HORSE_MULE = "HORSE_MULE";

    public static final String OUTBOUND_DONKEY = "DONKEY";

    public static final String LABEL_OUTBOUND_YAK = "牦牛";

    public static final String LABEL_OUTBOUND_CATTLE = "黄牛/奶牛/犏牛";

    public static final String LABEL_OUTBOUND_SHEEP = "绵羊";

    public static final String LABEL_OUTBOUND_GOAT = "山羊";

    public static final String LABEL_OUTBOUND_HORSE_MULE = "马骡";

    public static final String LABEL_OUTBOUND_DONKEY = "驴";

    public static final String UNIT_HEAD = "头";

    public static final String UNIT_ONLY = "只";

    public static final String UNIT_PI = "匹";

    public static final String SELF_CATTLE = "CATTLE";

    public static final String SELF_SHEEP = "SHEEP";

    public static final String SELF_GOAT = "GOAT";

    public static final String LABEL_SELF_CATTLE = "牛";

    public static final String LABEL_SELF_SHEEP = "绵羊";

    public static final String LABEL_SELF_GOAT = "山羊";

    public static final String FINANCE_INCOME = "INCOME";

    public static final String FINANCE_EXPENSE = "EXPENSE";

    public static final String FINANCE_PROFIT = "PROFIT";

    public static final String FINANCE_SUBSIDY = "SUBSIDY";

    public static final String LABEL_FINANCE_INCOME = "收入";

    public static final String LABEL_FINANCE_EXPENSE = "支出";

    public static final String LABEL_FINANCE_PROFIT = "利润";

    public static final String LABEL_FINANCE_SUBSIDY = "补助资金";

    public static final String COOP_WHOLE_VILLAGE = "WHOLE_VILLAGE";

    public static final String COOP_SHARE = "SHARE";

    public static final String COOP_JOINT = "JOINT";

    public static final String COOP_MULTI = "MULTI_BUSINESS";

    public static final String COOP_COMPANY = "COMPANY_TYPE";

    public static final String COOP_OTHER = "OTHER";

    public static final String LABEL_COOP_WHOLE_VILLAGE = "整村合作社";

    public static final String LABEL_COOP_SHARE = "股份合作社";

    public static final String LABEL_COOP_JOINT = "联合合作社";

    public static final String LABEL_COOP_MULTI = "多种经营合作社";

    public static final String LABEL_COOP_COMPANY = "公司型合作社";

    public static final String LABEL_COOP_OTHER = "其他";

    public static final String FARMER_PURE_HERDER = "PURE_HERDER";

    public static final String FARMER_NO_LIVESTOCK = "NO_LIVESTOCK";

    public static final String FARMER_EXTINCT = "EXTINCT";

    public static final String FARMER_OVERLOAD = "OVERLOAD";

    public static final String FARMER_NEED_CAP = "NEED_CAP";

    public static final String FARMER_NEED_FLOOR = "NEED_FLOOR";

    public static final String LABEL_FARMER_PURE_HERDER = "纯牧户";

    public static final String LABEL_FARMER_NO_LIVESTOCK = "无畜户";

    public static final String LABEL_FARMER_EXTINCT = "绝户";

    public static final String LABEL_FARMER_OVERLOAD = "超载牧户";

    public static final String LABEL_FARMER_NEED_CAP = "需要封顶";

    public static final String LABEL_FARMER_NEED_FLOOR = "需要保底";

    public static final String LABEL_SUBJECT_FAMILY_RANCH = "家庭牧场";

    public static final String LABEL_SUBJECT_FARMER_HOUSEHOLD = "农牧户";

    public static final String LABEL_SUBJECT_LARGE_LIVESTOCK_FARMER = "养殖大户";

    public static final String LABEL_GRASS_BALANCED_YES = "是";

    public static final String LABEL_GRASS_BALANCED_NO = "否";

    /** @deprecated 旧四档常量保留避免外部硬编码编译失败，大屏已切两档 */
    @Deprecated
    public static final String BAND_COW_UNDER_1 = BAND_COW_YOUNG;

    @Deprecated
    public static final String BAND_COW_1_3 = "COW_1_3";

    @Deprecated
    public static final String BAND_COW_3_12 = "COW_3_12";

    @Deprecated
    public static final String BAND_COW_OVER_12 = "COW_O12";

    @Deprecated
    public static final String LABEL_COW_UNDER_1 = LABEL_COW_YOUNG;

    @Deprecated
    public static final String LABEL_COW_1_3 = "1～3岁";

    @Deprecated
    public static final String LABEL_COW_3_12 = "3～12岁";

    @Deprecated
    public static final String LABEL_COW_OVER_12 = "12岁以上";

    private static final Map<String, String> STANDARD_TYPE_NAMES;

    static
    {
        Map<String, String> map = new HashMap<>();
        map.put("001001", "繁育作业");
        map.put("001002", "饲养工作");
        map.put("001003", "免疫程序");
        map.put("001004", "环境调控");
        map.put("001005", "饲料配方");
        map.put("001006", "牦牛投喂");
        map.put("001007", "设备操作");
        map.put("001008", "设备保养");
        map.put("001009", "设备排障");
        map.put("001010", "设备维修");
        map.put("001011", "牦牛生长");
        map.put("001012", "牦牛出栏");
        map.put("001013", "疾病决策");
        STANDARD_TYPE_NAMES = Collections.unmodifiableMap(map);
    }

    private HomeScreenSupport()
    {
    }

    /**
     * 统计截止日：当前年取上海时区今天，历史年取 12-31。
     * 所有大屏模块的年区间 [{@code yearStart}, {@code yearEnd}] 均依赖此方法。
     */
    public static LocalDate resolveStatDate(int statYear)
    {
        LocalDate today = LocalDate.now(ZoneId.of(BreedingIndustryRules.ZONE_SHANGHAI));
        if (statYear == today.getYear())
        {
            return today;
        }
        return LocalDate.of(statYear, 12, 31);
    }

    public static String formatStatDate(LocalDate statDate)
    {
        return statDate.format(STAT_DATE_FMT);
    }

    /** 截止 statDate 月末仍计入存栏的牦牛数量（复用养殖产业快照口径）。 */
    public static int countInventoryAtDate(List<BreedingIndustryYakSnapshot> assets, LocalDate statDate)
    {
        int count = 0;
        for (BreedingIndustryYakSnapshot asset : assets)
        {
            if (BreedingIndustryCalculator.countsAtMonthEnd(asset, statDate))
            {
                count++;
            }
        }
        return count;
    }

    public static int[] countGenderAtDate(List<BreedingIndustryYakSnapshot> assets, LocalDate statDate)
    {
        int bull = 0;
        int cow = 0;
        for (BreedingIndustryYakSnapshot asset : assets)
        {
            if (!BreedingIndustryCalculator.countsAtMonthEnd(asset, statDate))
            {
                continue;
            }
            String gender = BreedingIndustryCalculator.normalizeGender(asset.getGender());
            if (BreedingIndustryRules.GENDER_BULL.equals(gender))
            {
                bull++;
            }
            else if (BreedingIndustryRules.GENDER_COW.equals(gender))
            {
                cow++;
            }
        }
        return new int[] { bull, cow };
    }

    /**
     * 公母牛月度存栏趋势；当前年未来月份填 0。
     *
     * @return 长度 12 的列表，{@code month} 为 1～12
     */
    public static List<HomeScreenInventoryMonthVo> buildBullCowMonthlyTrend(
            List<BreedingIndustryYakSnapshot> assets, LocalDate statDate)
    {
        List<HomeScreenInventoryMonthVo> points = new ArrayList<>();
        int statYear = statDate.getYear();
        int futureAfter = statDate.getMonthValue();
        for (int month = 1; month <= 12; month++)
        {
            HomeScreenInventoryMonthVo point = new HomeScreenInventoryMonthVo();
            point.setMonth(month);
            if (statYear == LocalDate.now(ZoneId.of(BreedingIndustryRules.ZONE_SHANGHAI)).getYear()
                    && month > futureAfter)
            {
                point.setBullCount(0);
                point.setCowCount(0);
            }
            else
            {
                LocalDate monthEnd = BreedingIndustryCalculator.resolveMonthEnd(statYear, month, statDate);
                int bull = 0;
                int cow = 0;
                for (BreedingIndustryYakSnapshot asset : assets)
                {
                    if (!BreedingIndustryCalculator.countsAtMonthEnd(asset, monthEnd))
                    {
                        continue;
                    }
                    String gender = BreedingIndustryCalculator.normalizeGender(asset.getGender());
                    if (BreedingIndustryRules.GENDER_BULL.equals(gender))
                    {
                        bull++;
                    }
                    else if (BreedingIndustryRules.GENDER_COW.equals(gender))
                    {
                        cow++;
                    }
                }
                point.setBullCount(bull);
                point.setCowCount(cow);
            }
            points.add(point);
        }
        return points;
    }

    /** 距 anchor 月（含）向前共 12 个自然月的 yyyyMM 键。 */
    public static int yearMonthKey(YearMonth ym)
    {
        return ym.getYear() * 100 + ym.getMonthValue();
    }

    /** 当前上海时区年月，作为「最近 12 个月」窗口右端点。 */
    public static YearMonth currentYearMonth()
    {
        return YearMonth.now(ZoneId.of(BreedingIndustryRules.ZONE_SHANGHAI));
    }

    /** 成果/趋势类 stat_period 格式 {@code yyyy-MM}。 */
    public static String formatStatPeriod(YearMonth ym)
    {
        return String.format("%04d-%02d", ym.getYear(), ym.getMonthValue());
    }

    /**
     * 滚动 12 月窗口结束日：当前月取今天，历史月取该月最后一天（上海时区）。
     */
    public static LocalDate rolling12MonthWindowEnd(YearMonth anchor)
    {
        ZoneId zone = ZoneId.of(BreedingIndustryRules.ZONE_SHANGHAI);
        if (anchor.equals(YearMonth.now(zone)))
        {
            return LocalDate.now(zone);
        }
        return anchor.atEndOfMonth();
    }

    /**
     * 存栏变动：最近 12 个月公母牛合计趋势（按时间升序，含 {@code statYear}、{@code month}）。
     */
    public static List<HomeScreenInventoryMonthVo> buildRolling12MonthBullCowTrend(
            List<HomeScreenYearMonthBullCowDto> rows, YearMonth anchor)
    {
        Map<Integer, HomeScreenYearMonthBullCowDto> byYm = new HashMap<>();
        if (rows != null)
        {
            for (HomeScreenYearMonthBullCowDto row : rows)
            {
                if (row.getStatYear() != null && row.getStatMonth() != null)
                {
                    byYm.put(yearMonthKey(YearMonth.of(row.getStatYear(), row.getStatMonth())), row);
                }
            }
        }
        List<HomeScreenInventoryMonthVo> points = new ArrayList<>();
        YearMonth start = anchor.minusMonths(11);
        for (int slot = 0; slot < 12; slot++)
        {
            YearMonth ym = start.plusMonths(slot);
            HomeScreenYearMonthBullCowDto src = byYm.get(yearMonthKey(ym));
            HomeScreenInventoryMonthVo point = new HomeScreenInventoryMonthVo();
            point.setStatYear(ym.getYear());
            point.setMonth(ym.getMonthValue());
            point.setBullCount(src != null && src.getBullCount() != null ? src.getBullCount() : 0);
            point.setCowCount(src != null && src.getCowCount() != null ? src.getCowCount() : 0);
            points.add(point);
        }
        return points;
    }

    /**
     * 出栏统计：最近 12 个月牦牛出栏合计（按时间升序，含 {@code statYear}、{@code month}）。
     */
    public static List<HomeScreenOutboundMonthVo> buildRolling12MonthOutbound(
            List<HomeScreenYearMonthCountDto> rows, YearMonth anchor)
    {
        Map<Integer, Integer> byYm = new HashMap<>();
        if (rows != null)
        {
            for (HomeScreenYearMonthCountDto row : rows)
            {
                if (row.getStatYear() != null && row.getStatMonth() != null && row.getCount() != null)
                {
                    byYm.put(yearMonthKey(YearMonth.of(row.getStatYear(), row.getStatMonth())), row.getCount());
                }
            }
        }
        List<HomeScreenOutboundMonthVo> list = new ArrayList<>();
        YearMonth start = anchor.minusMonths(11);
        for (int slot = 0; slot < 12; slot++)
        {
            YearMonth ym = start.plusMonths(slot);
            HomeScreenOutboundMonthVo vo = new HomeScreenOutboundMonthVo();
            vo.setStatYear(ym.getYear());
            vo.setMonth(ym.getMonthValue());
            vo.setOutboundCount(byYm.getOrDefault(yearMonthKey(ym), 0));
            list.add(vo);
        }
        return list;
    }

    /**
     * 线下服务预约：最近 12 个月堆叠柱（按时间升序，含 {@code statYear}、{@code month}）。
     */
    public static List<HomeScreenAppointmentMonthVo> buildRolling12MonthAppointmentStacked(
            List<HomeScreenAppointmentStatDto> rows, YearMonth anchor)
    {
        Map<Integer, Map<Integer, Integer>> byYmAndType = new HashMap<>();
        if (rows != null)
        {
            for (HomeScreenAppointmentStatDto row : rows)
            {
                if (row.getStatYear() != null && row.getStatMonth() != null && row.getProviderType() != null)
                {
                    int key = yearMonthKey(YearMonth.of(row.getStatYear(), row.getStatMonth()));
                    byYmAndType.computeIfAbsent(key, k -> new HashMap<>())
                            .put(row.getProviderType(), row.getCount() != null ? row.getCount() : 0);
                }
            }
        }
        List<HomeScreenAppointmentMonthVo> stacked = new ArrayList<>();
        YearMonth start = anchor.minusMonths(11);
        for (int slot = 0; slot < 12; slot++)
        {
            YearMonth ym = start.plusMonths(slot);
            Map<Integer, Integer> types = byYmAndType.getOrDefault(yearMonthKey(ym), Collections.emptyMap());
            HomeScreenAppointmentMonthVo point = new HomeScreenAppointmentMonthVo();
            point.setStatYear(ym.getYear());
            point.setMonth(ym.getMonthValue());
            point.setVetCount(types.getOrDefault(PROVIDER_VET, 0));
            point.setExpertCount(types.getOrDefault(PROVIDER_EXPERT, 0));
            point.setOrgCount(types.getOrDefault(PROVIDER_ORG, 0));
            stacked.add(point);
        }
        return stacked;
    }

    /**
     * 实战培训报名：最近 12 个月活动数/报名人数（按时间升序，含 {@code statYear}、{@code month}）。
     */
    public static List<HomeScreenTrainingMonthVo> buildRolling12MonthTraining(
            List<HomeScreenTrainingMonthDto> rows, YearMonth anchor)
    {
        Map<Integer, HomeScreenTrainingMonthDto> byYm = new HashMap<>();
        if (rows != null)
        {
            for (HomeScreenTrainingMonthDto row : rows)
            {
                if (row.getStatYear() != null && row.getStatMonth() != null)
                {
                    byYm.put(yearMonthKey(YearMonth.of(row.getStatYear(), row.getStatMonth())), row);
                }
            }
        }
        List<HomeScreenTrainingMonthVo> monthly = new ArrayList<>();
        YearMonth start = anchor.minusMonths(11);
        for (int slot = 0; slot < 12; slot++)
        {
            YearMonth ym = start.plusMonths(slot);
            HomeScreenTrainingMonthDto src = byYm.get(yearMonthKey(ym));
            HomeScreenTrainingMonthVo point = new HomeScreenTrainingMonthVo();
            point.setStatYear(ym.getYear());
            point.setMonth(ym.getMonthValue());
            point.setActivityCount(src != null && src.getActivityCount() != null ? src.getActivityCount() : 0);
            point.setEnrolledCount(src != null && src.getEnrolledCount() != null ? src.getEnrolledCount() : 0);
            monthly.add(point);
        }
        return monthly;
    }

    /**
     * 公母牛月度存栏趋势（存栏填报表 {@code bull_total}/{@code cow_total} 全县按月合计）。
     * <p>
     * 以所选年填报数据中的<strong>最大月份 N</strong>为准，输出 1～N 月（按月升序）；
     * 区间内缺月补 0；无任何填报月则返回空列表。
     * </p>
     *
     * @param rows     按月合计行
     * @param statDate 所选年统计截止日（取年份写入 {@code statYear}）
     */
    public static List<HomeScreenInventoryMonthVo> buildBullCowMonthlyTrendFromHerdReport(
            List<HomeScreenHerdGenderMonthDto> rows, LocalDate statDate)
    {
        Map<Integer, HomeScreenHerdGenderMonthDto> byMonth = new HashMap<>();
        int maxMonth = 0;
        if (rows != null)
        {
            for (HomeScreenHerdGenderMonthDto row : rows)
            {
                if (row == null || row.getMonth() == null)
                {
                    continue;
                }
                int month = row.getMonth();
                if (month < 1 || month > 12)
                {
                    continue;
                }
                byMonth.put(month, row);
                if (month > maxMonth)
                {
                    maxMonth = month;
                }
            }
        }
        if (maxMonth <= 0)
        {
            return Collections.emptyList();
        }
        int statYear = statDate != null ? statDate.getYear()
                : LocalDate.now(ZoneId.of(BreedingIndustryRules.ZONE_SHANGHAI)).getYear();
        List<HomeScreenInventoryMonthVo> points = new ArrayList<>(maxMonth);
        for (int month = 1; month <= maxMonth; month++)
        {
            HomeScreenInventoryMonthVo point = new HomeScreenInventoryMonthVo();
            point.setStatYear(statYear);
            point.setMonth(month);
            HomeScreenHerdGenderMonthDto src = byMonth.get(month);
            point.setBullCount(src != null && src.getBullCount() != null ? src.getBullCount() : 0);
            point.setCowCount(src != null && src.getCowCount() != null ? src.getCowCount() : 0);
            points.add(point);
        }
        return points;
    }

    /** 母牛分龄结构（存栏填报两档）；接口字段 bandCode/bandLabel/count/ratio 不变。 */
    public static List<HomeScreenAgeBandVo> buildCowAgeStructureFromHerdReport(HomeScreenHerdCowAgeSumDto sums)
    {
        int young = safeInt(sums != null ? sums.getCowYoungCount() : null);
        int adult = safeInt(sums != null ? sums.getCowAdultCount() : null);
        int total = young + adult;
        List<HomeScreenAgeBandVo> result = new ArrayList<>();
        result.add(cowAgeBandVo(BAND_COW_YOUNG, LABEL_COW_YOUNG, young, total));
        result.add(cowAgeBandVo(BAND_COW_ADULT, LABEL_COW_ADULT, adult, total));
        return result;
    }

    /**
     * 畜群存栏结构（牦牛 / 奶牛 / 羊）；单位均为头；占比按三者之和。
     */
    public static List<HomeScreenHerdSpeciesItemVo> buildHerdInventoryStructure(HomeScreenHerdSpeciesSumDto sums)
    {
        int yak = safeInt(sums != null ? sums.getYakTotal() : null);
        int dairy = safeInt(sums != null ? sums.getDairyTotal() : null);
        int sheep = safeInt(sums != null ? sums.getSheepTotal() : null);
        int total = yak + dairy + sheep;
        List<HomeScreenHerdSpeciesItemVo> result = new ArrayList<>();
        result.add(speciesItemVo(SPECIES_YAK, LABEL_SPECIES_YAK, yak, total));
        result.add(speciesItemVo(SPECIES_DAIRY, LABEL_SPECIES_DAIRY, dairy, total));
        result.add(speciesItemVo(SPECIES_SHEEP, LABEL_SPECIES_SHEEP, sheep, total));
        return result;
    }

    /**
     * 牦牛存栏结构（公牛 / 母牛 / 保险牛及细分）；双层饼图数据；单位均为头。
     */
    public static List<HomeScreenYakStructureCategoryVo> buildYakInventoryStructure(
            HomeScreenHerdYakStructureSumDto sums)
    {
        int breedingBull = safeInt(sums != null ? sums.getBreedingBullCount() : null);
        int bullCastrated = safeInt(sums != null ? sums.getBullCastratedCount() : null);
        int cowYoung = safeInt(sums != null ? sums.getCowYoungCount() : null);
        int cowAdult = safeInt(sums != null ? sums.getCowAdultCount() : null);
        int insured = safeInt(sums != null ? sums.getInsuredYakCount() : null);
        int bullTotal = breedingBull + bullCastrated;
        int cowTotal = cowYoung + cowAdult;
        int total = bullTotal + cowTotal + insured;

        List<HomeScreenYakStructureCategoryVo> result = new ArrayList<>();
        result.add(yakCategoryVo(YAK_CAT_BULL, LABEL_YAK_CAT_BULL, bullTotal, total,
                yakDetailVo(YAK_DETAIL_BREEDING_BULL, LABEL_YAK_DETAIL_BREEDING_BULL, breedingBull, total),
                yakDetailVo(YAK_DETAIL_BULL_CASTRATED, LABEL_YAK_DETAIL_BULL_CASTRATED, bullCastrated, total)));
        result.add(yakCategoryVo(YAK_CAT_COW, LABEL_YAK_CAT_COW, cowTotal, total,
                yakDetailVo(YAK_DETAIL_COW_YOUNG, LABEL_YAK_DETAIL_COW_YOUNG, cowYoung, total),
                yakDetailVo(YAK_DETAIL_COW_ADULT, LABEL_YAK_DETAIL_COW_ADULT, cowAdult, total)));
        result.add(yakCategoryVo(YAK_CAT_INSURED, LABEL_YAK_CAT_INSURED, insured, total,
                yakDetailVo(YAK_DETAIL_INSURED, LABEL_YAK_DETAIL_INSURED, insured, total)));
        return result;
    }

    /**
     * 牲畜出栏结构（矩形树图）；占比按六畜数量之和；单位按畜种。
     */
    public static List<HomeScreenOutboundSpeciesItemVo> buildLivestockOutboundStructure(
            HomeScreenOutboundSpeciesSumDto sums)
    {
        int yak = safeInt(sums != null ? sums.getYakOutboundCount() : null);
        int cattle = safeInt(sums != null ? sums.getCattleOutboundCount() : null);
        int sheep = safeInt(sums != null ? sums.getSheepOutboundCount() : null);
        int goat = safeInt(sums != null ? sums.getGoatOutboundCount() : null);
        int horseMule = safeInt(sums != null ? sums.getHorseMuleOutboundCount() : null);
        int donkey = safeInt(sums != null ? sums.getDonkeyOutboundCount() : null);
        int total = yak + cattle + sheep + goat + horseMule + donkey;
        List<HomeScreenOutboundSpeciesItemVo> result = new ArrayList<>();
        result.add(outboundSpeciesItem(OUTBOUND_YAK, LABEL_OUTBOUND_YAK, yak, total, UNIT_HEAD));
        result.add(outboundSpeciesItem(OUTBOUND_CATTLE, LABEL_OUTBOUND_CATTLE, cattle, total, UNIT_HEAD));
        result.add(outboundSpeciesItem(OUTBOUND_SHEEP, LABEL_OUTBOUND_SHEEP, sheep, total, UNIT_ONLY));
        result.add(outboundSpeciesItem(OUTBOUND_GOAT, LABEL_OUTBOUND_GOAT, goat, total, UNIT_ONLY));
        result.add(outboundSpeciesItem(OUTBOUND_HORSE_MULE, LABEL_OUTBOUND_HORSE_MULE, horseMule, total, UNIT_PI));
        result.add(outboundSpeciesItem(OUTBOUND_DONKEY, LABEL_OUTBOUND_DONKEY, donkey, total, UNIT_HEAD));
        return result;
    }

    /**
     * 自食情况（柱状图）；展示所选年最大填报月牛/绵羊/山羊，单位均为头。
     */
    public static HomeScreenSelfConsumptionVo buildSelfConsumption(
            HomeScreenStatPeriodDto period, HomeScreenSelfConsumptionSumDto sums)
    {
        HomeScreenSelfConsumptionVo vo = new HomeScreenSelfConsumptionVo();
        if (period != null)
        {
            vo.setStatYear(period.getStatYear());
            vo.setStatMonth(period.getStatMonth());
        }
        int cattle = safeInt(sums != null ? sums.getCattleCount() : null);
        int sheep = safeInt(sums != null ? sums.getSheepCount() : null);
        int goat = safeInt(sums != null ? sums.getGoatCount() : null);
        List<HomeScreenSelfConsumptionItemVo> items = new ArrayList<>();
        items.add(selfConsumptionItem(SELF_CATTLE, LABEL_SELF_CATTLE, cattle));
        items.add(selfConsumptionItem(SELF_SHEEP, LABEL_SELF_SHEEP, sheep));
        items.add(selfConsumptionItem(SELF_GOAT, LABEL_SELF_GOAT, goat));
        vo.setItems(items);
        return vo;
    }

    /**
     * 企业年度收入与补助（家庭牧场 / 养殖大户）；金额已为万元，保留 2 位小数。
     */
    public static HomeScreenEnterpriseFinanceVo buildEnterpriseFinance(
            HomeScreenStatPeriodDto period, HomeScreenEnterpriseFinanceSumDto sums)
    {
        HomeScreenEnterpriseFinanceVo vo = new HomeScreenEnterpriseFinanceVo();
        if (period != null)
        {
            vo.setStatYear(period.getStatYear());
            vo.setStatMonth(period.getStatMonth());
        }
        List<HomeScreenEnterpriseFinanceItemVo> items = new ArrayList<>();
        items.add(financeItem(FINANCE_INCOME, LABEL_FINANCE_INCOME,
                sums != null ? sums.getIncomeSum() : null));
        items.add(financeItem(FINANCE_EXPENSE, LABEL_FINANCE_EXPENSE,
                sums != null ? sums.getExpenseSum() : null));
        items.add(financeItem(FINANCE_PROFIT, LABEL_FINANCE_PROFIT,
                sums != null ? sums.getProfitSum() : null));
        items.add(financeItem(FINANCE_SUBSIDY, LABEL_FINANCE_SUBSIDY,
                sums != null ? sums.getSubsidyAmountSum() : null));
        vo.setItems(items);
        return vo;
    }

    /**
     * 合作社经营模式分类（饼图）；单位：个；占比按六类之和。
     */
    public static List<HomeScreenNamedCountItemVo> buildCooperativeBusinessModel(
            HomeScreenCoopBusinessModelSumDto sums)
    {
        int whole = safeInt(sums != null ? sums.getWholeVillageCount() : null);
        int share = safeInt(sums != null ? sums.getShareCount() : null);
        int joint = safeInt(sums != null ? sums.getJointCount() : null);
        int multi = safeInt(sums != null ? sums.getMultiBusinessCount() : null);
        int company = safeInt(sums != null ? sums.getCompanyTypeCount() : null);
        int other = safeInt(sums != null ? sums.getOtherCount() : null);
        int total = whole + share + joint + multi + company + other;
        List<HomeScreenNamedCountItemVo> result = new ArrayList<>();
        result.add(namedCountItem(COOP_WHOLE_VILLAGE, LABEL_COOP_WHOLE_VILLAGE, whole, total));
        result.add(namedCountItem(COOP_SHARE, LABEL_COOP_SHARE, share, total));
        result.add(namedCountItem(COOP_JOINT, LABEL_COOP_JOINT, joint, total));
        result.add(namedCountItem(COOP_MULTI, LABEL_COOP_MULTI, multi, total));
        result.add(namedCountItem(COOP_COMPANY, LABEL_COOP_COMPANY, company, total));
        result.add(namedCountItem(COOP_OTHER, LABEL_COOP_OTHER, other, total));
        return result;
    }

    /**
     * 农牧户分类（柱状图）；各「是」标记的村级记录数，单位：个。
     */
    public static List<HomeScreenNamedCountItemVo> buildFarmerHouseholdClassification(
            HomeScreenFarmerHouseholdClassSumDto sums)
    {
        List<HomeScreenNamedCountItemVo> result = new ArrayList<>();
        result.add(namedCountItem(FARMER_PURE_HERDER, LABEL_FARMER_PURE_HERDER,
                safeInt(sums != null ? sums.getPureHerderCount() : null), 0));
        result.add(namedCountItem(FARMER_NO_LIVESTOCK, LABEL_FARMER_NO_LIVESTOCK,
                safeInt(sums != null ? sums.getNoLivestockCount() : null), 0));
        result.add(namedCountItem(FARMER_EXTINCT, LABEL_FARMER_EXTINCT,
                safeInt(sums != null ? sums.getExtinctHouseholdCount() : null), 0));
        result.add(namedCountItem(FARMER_OVERLOAD, LABEL_FARMER_OVERLOAD,
                safeInt(sums != null ? sums.getOverloadHerderCount() : null), 0));
        result.add(namedCountItem(FARMER_NEED_CAP, LABEL_FARMER_NEED_CAP,
                safeInt(sums != null ? sums.getNeedCapCount() : null), 0));
        result.add(namedCountItem(FARMER_NEED_FLOOR, LABEL_FARMER_NEED_FLOOR,
                safeInt(sums != null ? sums.getNeedFloorCount() : null), 0));
        return result;
    }

    /**
     * 人均收入最近 5 年趋势（含 endYear 往前共 5 年）；缺年补 0；单位元。
     * {@code endYear} 应为库中可支配收入最大所属年。
     */
    public static List<HomeScreenPerCapitaIncomeYearVo> buildPerCapitaIncomeTrend(
            List<HomeScreenDisposableIncomeYearDto> rows, int endYear)
    {
        int startYear = endYear - 4;
        Map<Integer, HomeScreenDisposableIncomeYearDto> byYear = new HashMap<>();
        if (rows != null)
        {
            for (HomeScreenDisposableIncomeYearDto row : rows)
            {
                if (row.getStatYear() != null)
                {
                    byYear.put(row.getStatYear(), row);
                }
            }
        }
        List<HomeScreenPerCapitaIncomeYearVo> result = new ArrayList<>();
        for (int y = startYear; y <= endYear; y++)
        {
            HomeScreenPerCapitaIncomeYearVo point = new HomeScreenPerCapitaIncomeYearVo();
            point.setStatYear(y);
            HomeScreenDisposableIncomeYearDto src = byYear.get(y);
            point.setRuralDisposableIncome(scaleWan(src != null ? src.getRuralDisposableIncome() : null));
            point.setPovertyNetIncome(scaleWan(src != null ? src.getPovertyNetIncome() : null));
            result.add(point);
        }
        return result;
    }

    public static List<HomeScreenCooperativeDevelopmentListItemVo> buildCooperativeDevelopmentListItems(List<BizCooperativeDevelopment> rows)
    {
        if (rows == null || rows.isEmpty())
        {
            return Collections.emptyList();
        }
        List<HomeScreenCooperativeDevelopmentListItemVo> result = new ArrayList<>(rows.size());
        for (BizCooperativeDevelopment row : rows)
        {
            result.add(toCooperativeDevelopmentListItem(row));
        }
        return result;
    }

    public static HomeScreenCooperativeDevelopmentListItemVo toCooperativeDevelopmentListItem(BizCooperativeDevelopment row)
    {
        HomeScreenCooperativeDevelopmentListItemVo vo = new HomeScreenCooperativeDevelopmentListItemVo();
        if (row == null)
        {
            return vo;
        }
        vo.setId(row.getId());
        vo.setStatYear(row.getStatYear());
        vo.setStatMonth(row.getStatMonth());
        vo.setTownDeptId(row.getTownDeptId());
        vo.setTownName(row.getTownName());
        vo.setVillageDeptId(row.getVillageDeptId());
        vo.setVillageName(row.getVillageName());
        vo.setCooperativeName(row.getCooperativeName());
        vo.setLegalRepresentative(row.getLegalRepresentative());
        vo.setEstablishDate(row.getEstablishDate());
        vo.setRegisteredCapital(row.getRegisteredCapital());
        vo.setYearEndTotalAssets(row.getYearEndTotalAssets());
        vo.setMemberHouseholdCount(row.getMemberHouseholdCount());
        vo.setMemberPopulationCount(row.getMemberPopulationCount());
        vo.setPovertyMonitorCount(row.getPovertyMonitorCount());
        vo.setCattleShareCount(row.getCattleShareCount());
        vo.setSheepShareCount(row.getSheepShareCount());
        vo.setLivestockShareTotal(row.getLivestockShareTotal());
        vo.setGrasslandShareArea(row.getGrasslandShareArea());
        vo.setLaborShareCount(row.getLaborShareCount());
        vo.setCashShareAmount(row.getCashShareAmount());
        vo.setOtherShareAmount(row.getOtherShareAmount());
        vo.setTotalIncome(row.getTotalIncome());
        vo.setNetIncome(row.getNetIncome());
        vo.setCashDividend(row.getCashDividend());
        vo.setInKindDividend(row.getInKindDividend());
        vo.setRevolvingFund(row.getRevolvingFund());
        vo.setLossAmount(row.getLossAmount());
        vo.setCollegeGraduateCount(row.getCollegeGraduateCount());
        vo.setCollegeGraduateWage(row.getCollegeGraduateWage());
        vo.setFarmerTransferEmploymentCount(row.getFarmerTransferEmploymentCount());
        vo.setFarmerTransferWage(row.getFarmerTransferWage());
        vo.setCattleInventoryCount(row.getCattleInventoryCount());
        vo.setCattleOutboundCount(row.getCattleOutboundCount());
        vo.setSheepInventoryCount(row.getSheepInventoryCount());
        vo.setSheepOutboundCount(row.getSheepOutboundCount());
        vo.setTrademarkProductCount(row.getTrademarkProductCount());
        vo.setQualityCertProductCount(row.getQualityCertProductCount());
        vo.setTrainingManageCount(row.getTrainingManageCount());
        vo.setTrainingSkillCount(row.getTrainingSkillCount());
        vo.setTrainingOtherCount(row.getTrainingOtherCount());
        vo.setBusinessScope(row.getBusinessScope());
        vo.setBusinessModel(row.getBusinessModel());
        vo.setOperationStatus(row.getOperationStatus());
        return vo;
    }

    public static List<HomeScreenFamilyRanchListItemVo> buildFamilyRanchListItems(List<BizFamilyRanch> rows)
    {
        if (rows == null || rows.isEmpty())
        {
            return Collections.emptyList();
        }
        List<HomeScreenFamilyRanchListItemVo> result = new ArrayList<>(rows.size());
        for (BizFamilyRanch row : rows)
        {
            result.add(toFamilyRanchListItem(row));
        }
        return result;
    }

    public static HomeScreenFamilyRanchListItemVo toFamilyRanchListItem(BizFamilyRanch row)
    {
        HomeScreenFamilyRanchListItemVo vo = new HomeScreenFamilyRanchListItemVo();
        if (row == null)
        {
            return vo;
        }
        vo.setId(row.getId());
        vo.setStatYear(row.getStatYear());
        vo.setStatMonth(row.getStatMonth());
        vo.setTownDeptId(row.getTownDeptId());
        vo.setTownName(row.getTownName());
        vo.setVillageDeptId(row.getVillageDeptId());
        vo.setVillageName(row.getVillageName());
        vo.setEnterpriseName(row.getEnterpriseName());
        vo.setLegalRepresentative(row.getLegalRepresentative());
        vo.setEnterpriseType(row.getEnterpriseType());
        vo.setRegisteredCapital(row.getRegisteredCapital());
        vo.setEstablishDate(row.getEstablishDate());
        vo.setLivestockLaborCount(row.getLivestockLaborCount());
        vo.setStockSubtotal(row.getStockSubtotal());
        vo.setCattleYearEndStock(row.getCattleYearEndStock());
        vo.setCattleBreedingFemaleStock(row.getCattleBreedingFemaleStock());
        vo.setSheepYearEndStock(row.getSheepYearEndStock());
        vo.setSheepBreedingFemaleStock(row.getSheepBreedingFemaleStock());
        vo.setCattleOutbound(row.getCattleOutbound());
        vo.setSheepOutbound(row.getSheepOutbound());
        vo.setGrasslandAreaSubtotal(row.getGrasslandAreaSubtotal());
        vo.setGrassBalanceArea(row.getGrassBalanceArea());
        vo.setTransferGrassArea(row.getTransferGrassArea());
        vo.setApprovedCarryingCapacity(row.getApprovedCarryingCapacity());
        vo.setYearEndStockSheepUnit(row.getYearEndStockSheepUnit());
        vo.setIsGrassBalanced(row.getIsGrassBalanced());
        vo.setIncome(row.getIncome());
        vo.setExpense(row.getExpense());
        vo.setProfit(row.getProfit());
        vo.setSubsidyYear(row.getSubsidyYear());
        vo.setHasSubsidy(row.getHasSubsidy());
        vo.setSubsidyAmount(row.getSubsidyAmount());
        vo.setHasTrademark(row.getHasTrademark());
        vo.setHasQualityCert(row.getHasQualityCert());
        vo.setSalesChannel(row.getSalesChannel());
        vo.setDrivenPopulation(row.getDrivenPopulation());
        vo.setWagePaid(row.getWagePaid());
        return vo;
    }

    public static List<HomeScreenLargeLivestockFarmerListItemVo> buildLargeLivestockFarmerListItems(List<BizLargeLivestockFarmer> rows)
    {
        if (rows == null || rows.isEmpty())
        {
            return Collections.emptyList();
        }
        List<HomeScreenLargeLivestockFarmerListItemVo> result = new ArrayList<>(rows.size());
        for (BizLargeLivestockFarmer row : rows)
        {
            result.add(toLargeLivestockFarmerListItem(row));
        }
        return result;
    }

    public static HomeScreenLargeLivestockFarmerListItemVo toLargeLivestockFarmerListItem(BizLargeLivestockFarmer row)
    {
        HomeScreenLargeLivestockFarmerListItemVo vo = new HomeScreenLargeLivestockFarmerListItemVo();
        if (row == null)
        {
            return vo;
        }
        vo.setId(row.getId());
        vo.setStatYear(row.getStatYear());
        vo.setStatMonth(row.getStatMonth());
        vo.setTownDeptId(row.getTownDeptId());
        vo.setTownName(row.getTownName());
        vo.setVillageDeptId(row.getVillageDeptId());
        vo.setVillageName(row.getVillageName());
        vo.setEnterpriseName(row.getEnterpriseName());
        vo.setLegalRepresentative(row.getLegalRepresentative());
        vo.setEnterpriseType(row.getEnterpriseType());
        vo.setRegisteredCapital(row.getRegisteredCapital());
        vo.setEstablishDate(row.getEstablishDate());
        vo.setLivestockLaborCount(row.getLivestockLaborCount());
        vo.setStockSubtotal(row.getStockSubtotal());
        vo.setCattleYearEndStock(row.getCattleYearEndStock());
        vo.setCattleBreedingFemaleStock(row.getCattleBreedingFemaleStock());
        vo.setSheepYearEndStock(row.getSheepYearEndStock());
        vo.setSheepBreedingFemaleStock(row.getSheepBreedingFemaleStock());
        vo.setCattleOutbound(row.getCattleOutbound());
        vo.setSheepOutbound(row.getSheepOutbound());
        vo.setGrasslandAreaSubtotal(row.getGrasslandAreaSubtotal());
        vo.setGrassBalanceArea(row.getGrassBalanceArea());
        vo.setTransferGrassArea(row.getTransferGrassArea());
        vo.setApprovedCarryingCapacity(row.getApprovedCarryingCapacity());
        vo.setYearEndStockSheepUnit(row.getYearEndStockSheepUnit());
        vo.setIsGrassBalanced(row.getIsGrassBalanced());
        vo.setIncome(row.getIncome());
        vo.setExpense(row.getExpense());
        vo.setProfit(row.getProfit());
        vo.setSubsidyYear(row.getSubsidyYear());
        vo.setHasSubsidy(row.getHasSubsidy());
        vo.setSubsidyAmount(row.getSubsidyAmount());
        vo.setHasTrademark(row.getHasTrademark());
        vo.setHasQualityCert(row.getHasQualityCert());
        vo.setSalesChannel(row.getSalesChannel());
        vo.setDrivenPopulation(row.getDrivenPopulation());
        vo.setWagePaid(row.getWagePaid());
        return vo;
    }

    public static List<HomeScreenYakOutboundReportListItemVo> buildYakOutboundReportListItems(List<BizYakOutboundReport> rows)
    {
        if (rows == null || rows.isEmpty())
        {
            return Collections.emptyList();
        }
        List<HomeScreenYakOutboundReportListItemVo> result = new ArrayList<>(rows.size());
        for (BizYakOutboundReport row : rows)
        {
            result.add(toYakOutboundReportListItem(row));
        }
        return result;
    }

    public static HomeScreenYakOutboundReportListItemVo toYakOutboundReportListItem(BizYakOutboundReport row)
    {
        HomeScreenYakOutboundReportListItemVo vo = new HomeScreenYakOutboundReportListItemVo();
        if (row == null)
        {
            return vo;
        }
        vo.setId(row.getId());
        vo.setStatYear(row.getStatYear());
        vo.setStatMonth(row.getStatMonth());
        vo.setTownDeptId(row.getTownDeptId());
        vo.setTownName(row.getTownName());
        vo.setVillageDeptId(row.getVillageDeptId());
        vo.setVillageName(row.getVillageName());
        vo.setFarmerHouseholdCount(row.getFarmerHouseholdCount());
        vo.setFarmerPopulationCount(row.getFarmerPopulationCount());
        vo.setYakOutboundCount(row.getYakOutboundCount());
        vo.setCattleOutboundCount(row.getCattleOutboundCount());
        vo.setSheepOutboundCount(row.getSheepOutboundCount());
        vo.setGoatOutboundCount(row.getGoatOutboundCount());
        vo.setHorseMuleOutboundCount(row.getHorseMuleOutboundCount());
        vo.setDonkeyOutboundCount(row.getDonkeyOutboundCount());
        vo.setSelfConsumptionCattleCount(row.getSelfConsumptionCattleCount());
        vo.setSelfConsumptionSheepCount(row.getSelfConsumptionSheepCount());
        vo.setSelfConsumptionGoatCount(row.getSelfConsumptionGoatCount());
        return vo;
    }

    public static List<HomeScreenYakHerdInventoryListItemVo> buildYakHerdInventoryListItems(List<BizYakHerdInventory> rows)
    {
        if (rows == null || rows.isEmpty())
        {
            return Collections.emptyList();
        }
        List<HomeScreenYakHerdInventoryListItemVo> result = new ArrayList<>(rows.size());
        for (BizYakHerdInventory row : rows)
        {
            result.add(toYakHerdInventoryListItem(row));
        }
        return result;
    }

    public static HomeScreenYakHerdInventoryListItemVo toYakHerdInventoryListItem(BizYakHerdInventory row)
    {
        HomeScreenYakHerdInventoryListItemVo vo = new HomeScreenYakHerdInventoryListItemVo();
        if (row == null)
        {
            return vo;
        }
        vo.setId(row.getId());
        vo.setStatYear(row.getStatYear());
        vo.setStatMonth(row.getStatMonth());
        vo.setTownDeptId(row.getTownDeptId());
        vo.setTownName(row.getTownName());
        vo.setVillageDeptId(row.getVillageDeptId());
        vo.setVillageName(row.getVillageName());
        vo.setVillageCount(row.getVillageCount());
        vo.setHerdsmanHouseholdCount(row.getHerdsmanHouseholdCount());
        vo.setBreedingBullCount(row.getBreedingBullCount());
        vo.setBullCastratedCount(row.getBullCastratedCount());
        vo.setBullTotal(row.getBullTotal());
        vo.setCowYoungCount(row.getCowYoungCount());
        vo.setCowAdultCount(row.getCowAdultCount());
        vo.setCowTotal(row.getCowTotal());
        vo.setMilkingCowCount(row.getMilkingCowCount());
        vo.setFertileCowCount(row.getFertileCowCount());
        vo.setDairyTotal(row.getDairyTotal());
        vo.setSheepTotal(row.getSheepTotal());
        vo.setMilkingSheepCount(row.getMilkingSheepCount());
        vo.setInsuredYakCount(row.getInsuredYakCount());
        vo.setYakTotal(row.getYakTotal());
        vo.setGrandTotal(row.getGrandTotal());
        vo.setYakSheepTotal(row.getYakSheepTotal());
        return vo;
    }

    public static List<HomeScreenLivestockOwnershipHouseholdListItemVo> buildLivestockOwnershipHouseholdListItems(List<BizLivestockOwnershipHousehold> rows)
    {
        if (rows == null || rows.isEmpty())
        {
            return Collections.emptyList();
        }
        List<HomeScreenLivestockOwnershipHouseholdListItemVo> result = new ArrayList<>(rows.size());
        for (BizLivestockOwnershipHousehold row : rows)
        {
            result.add(toLivestockOwnershipHouseholdListItem(row));
        }
        return result;
    }

    public static HomeScreenLivestockOwnershipHouseholdListItemVo toLivestockOwnershipHouseholdListItem(BizLivestockOwnershipHousehold row)
    {
        HomeScreenLivestockOwnershipHouseholdListItemVo vo = new HomeScreenLivestockOwnershipHouseholdListItemVo();
        if (row == null)
        {
            return vo;
        }
        vo.setId(row.getId());
        vo.setStatYear(row.getStatYear());
        vo.setStatMonth(row.getStatMonth());
        vo.setTownDeptId(row.getTownDeptId());
        vo.setTownName(row.getTownName());
        vo.setVillageDeptId(row.getVillageDeptId());
        vo.setVillageName(row.getVillageName());
        vo.setWithLivestockHouseholdCount(row.getWithLivestockHouseholdCount());
        vo.setWithLivestockPopulationCount(row.getWithLivestockPopulationCount());
        vo.setWithLivestockLaborCount(row.getWithLivestockLaborCount());
        vo.setWithLivestockAnimalCount(row.getWithLivestockAnimalCount());
        vo.setWithoutLivestockHouseholdCount(row.getWithoutLivestockHouseholdCount());
        vo.setWithoutLivestockPopulationCount(row.getWithoutLivestockPopulationCount());
        vo.setWithoutLivestockLaborCount(row.getWithoutLivestockLaborCount());
        return vo;
    }

    /**
     * 农牧户数据明细行（大屏）；映射填报表全部业务字段。
     */
    public static List<HomeScreenFarmerHouseholdListItemVo> buildFarmerHouseholdListItems(
            List<BizFarmerHousehold> rows)
    {
        if (rows == null || rows.isEmpty())
        {
            return Collections.emptyList();
        }
        List<HomeScreenFarmerHouseholdListItemVo> result = new ArrayList<>(rows.size());
        for (BizFarmerHousehold row : rows)
        {
            result.add(toFarmerHouseholdListItem(row));
        }
        return result;
    }

    public static HomeScreenFarmerHouseholdListItemVo toFarmerHouseholdListItem(BizFarmerHousehold row)
    {
        HomeScreenFarmerHouseholdListItemVo vo = new HomeScreenFarmerHouseholdListItemVo();
        if (row == null)
        {
            return vo;
        }
        vo.setId(row.getId());
        vo.setStatYear(row.getStatYear());
        vo.setStatMonth(row.getStatMonth());
        vo.setTownDeptId(row.getTownDeptId());
        vo.setTownName(row.getTownName());
        vo.setVillageDeptId(row.getVillageDeptId());
        vo.setVillageName(row.getVillageName());
        vo.setHouseholdCount(row.getHouseholdCount());
        vo.setPopulationCount(row.getPopulationCount());
        vo.setPastureContractArea(row.getPastureContractArea());
        vo.setGrazingBanArea(row.getGrazingBanArea());
        vo.setGrassBalanceArea(row.getGrassBalanceArea());
        vo.setApprovedCarryingCapacity(row.getApprovedCarryingCapacity());
        vo.setYearEndStockSheepUnit(row.getYearEndStockSheepUnit());
        vo.setIsPureHerder(row.getIsPureHerder());
        vo.setIsNoLivestock(row.getIsNoLivestock());
        vo.setIsExtinctHousehold(row.getIsExtinctHousehold());
        vo.setIsOverloadHerder(row.getIsOverloadHerder());
        vo.setNeedCap(row.getNeedCap());
        vo.setNeedFloor(row.getNeedFloor());
        vo.setGrazingBanSubsidy(row.getGrazingBanSubsidy());
        vo.setGrassBalanceReward(row.getGrassBalanceReward());
        vo.setSubsidyRewardSubtotal(row.getSubsidyRewardSubtotal());
        vo.setAfterCapFloorSubsidy(row.getAfterCapFloorSubsidy());
        vo.setVillageSupervisorSubsidy(row.getVillageSupervisorSubsidy());
        vo.setSubsidyTotal(row.getSubsidyTotal());
        return vo;
    }

    /**
     * 人均收入明细行（大屏）；字段直接映射填报表。
     */
    public static List<HomeScreenPerCapitaIncomeListItemVo> buildPerCapitaIncomeListItems(
            List<BizDisposableIncome> rows)
    {
        if (rows == null || rows.isEmpty())
        {
            return Collections.emptyList();
        }
        List<HomeScreenPerCapitaIncomeListItemVo> result = new ArrayList<>(rows.size());
        for (BizDisposableIncome row : rows)
        {
            result.add(toPerCapitaIncomeListItem(row));
        }
        return result;
    }

    public static HomeScreenPerCapitaIncomeListItemVo toPerCapitaIncomeListItem(BizDisposableIncome row)
    {
        HomeScreenPerCapitaIncomeListItemVo vo = new HomeScreenPerCapitaIncomeListItemVo();
        if (row == null)
        {
            return vo;
        }
        vo.setStatYear(row.getStatYear());
        vo.setRuralDisposableIncome(row.getRuralDisposableIncome());
        vo.setIncomeIncrease(row.getIncomeIncrease());
        vo.setIncomeGrowthRate(row.getIncomeGrowthRate());
        vo.setPovertyNetIncome(row.getPovertyNetIncome());
        return vo;
    }

    /**
     * 草畜失衡预警明细行（大屏）；主体类型中文名；是否草畜平衡按载畜量与存栏比较。
     */
    public static List<HomeScreenImbalanceWarningListItemVo> buildImbalanceWarningListItems(
            List<BizGrassLivestockImbalanceWarning> rows)
    {
        if (rows == null || rows.isEmpty())
        {
            return Collections.emptyList();
        }
        List<HomeScreenImbalanceWarningListItemVo> result = new ArrayList<>(rows.size());
        for (BizGrassLivestockImbalanceWarning row : rows)
        {
            result.add(toImbalanceWarningListItem(row));
        }
        return result;
    }

    public static HomeScreenImbalanceWarningListItemVo toImbalanceWarningListItem(
            BizGrassLivestockImbalanceWarning row)
    {
        HomeScreenImbalanceWarningListItemVo vo = new HomeScreenImbalanceWarningListItemVo();
        if (row == null)
        {
            return vo;
        }
        vo.setStatYear(row.getStatYear());
        vo.setStatMonth(row.getStatMonth());
        vo.setTownName(row.getTownName());
        vo.setVillageName(row.getVillageName());
        vo.setSubjectName(row.getSubjectName());
        vo.setSubjectTypeCode(row.getDataSource());
        vo.setSubjectType(resolveSubjectTypeLabel(row.getDataSource()));
        vo.setApprovedCarryingCapacity(row.getApprovedCarryingCapacity());
        vo.setYearEndStockSheepUnit(row.getYearEndStockSheepUnit());
        vo.setIsGrassBalanced(resolveIsGrassBalanced(
                row.getApprovedCarryingCapacity(), row.getYearEndStockSheepUnit()));
        return vo;
    }

    public static String resolveSubjectTypeLabel(String dataSource)
    {
        if (GrassLivestockImbalanceWarningRules.DATA_SOURCE_FAMILY_RANCH.equals(dataSource))
        {
            return LABEL_SUBJECT_FAMILY_RANCH;
        }
        if (GrassLivestockImbalanceWarningRules.DATA_SOURCE_FARMER_HOUSEHOLD.equals(dataSource))
        {
            return LABEL_SUBJECT_FARMER_HOUSEHOLD;
        }
        if (GrassLivestockImbalanceWarningRules.DATA_SOURCE_LARGE_LIVESTOCK_FARMER.equals(dataSource))
        {
            return LABEL_SUBJECT_LARGE_LIVESTOCK_FARMER;
        }
        return dataSource == null ? "" : dataSource;
    }

    /**
     * 核定载畜量 &gt;= 年底存栏绵羊单位为平衡「是」，否则「否」；缺值按「否」。
     */
    public static String resolveIsGrassBalanced(BigDecimal approved, BigDecimal yearEndStock)
    {
        if (approved == null || yearEndStock == null)
        {
            return LABEL_GRASS_BALANCED_NO;
        }
        return approved.compareTo(yearEndStock) >= 0 ? LABEL_GRASS_BALANCED_YES : LABEL_GRASS_BALANCED_NO;
    }

    private static HomeScreenNamedCountItemVo namedCountItem(String code, String label, int count, int total)
    {
        HomeScreenNamedCountItemVo vo = new HomeScreenNamedCountItemVo();
        vo.setItemCode(code);
        vo.setItemLabel(label);
        vo.setCount(count);
        vo.setRatio(total > 0
                ? BreedingIndustryCalculator.ratioPercent(count, total)
                : BigDecimal.ZERO.setScale(1, RoundingMode.HALF_UP));
        return vo;
    }

    private static HomeScreenEnterpriseFinanceItemVo financeItem(String code, String label, BigDecimal amount)
    {
        HomeScreenEnterpriseFinanceItemVo vo = new HomeScreenEnterpriseFinanceItemVo();
        vo.setItemCode(code);
        vo.setItemLabel(label);
        vo.setAmountWan(scaleWan(amount));
        return vo;
    }

    private static BigDecimal scaleWan(BigDecimal amount)
    {
        if (amount == null)
        {
            return BigDecimal.ZERO.setScale(2, RoundingMode.HALF_UP);
        }
        return amount.setScale(2, RoundingMode.HALF_UP);
    }

    private static HomeScreenOutboundSpeciesItemVo outboundSpeciesItem(
            String code, String label, int count, int total, String unit)
    {
        HomeScreenOutboundSpeciesItemVo vo = new HomeScreenOutboundSpeciesItemVo();
        vo.setSpeciesCode(code);
        vo.setSpeciesLabel(label);
        vo.setCount(count);
        vo.setRatio(BreedingIndustryCalculator.ratioPercent(count, total));
        vo.setUnit(unit);
        return vo;
    }

    private static HomeScreenSelfConsumptionItemVo selfConsumptionItem(String code, String label, int count)
    {
        HomeScreenSelfConsumptionItemVo vo = new HomeScreenSelfConsumptionItemVo();
        vo.setItemCode(code);
        vo.setItemLabel(label);
        vo.setCount(count);
        return vo;
    }

    private static HomeScreenYakStructureCategoryVo yakCategoryVo(
            String code, String label, int count, int total, HomeScreenYakStructureDetailVo... details)
    {
        HomeScreenYakStructureCategoryVo vo = new HomeScreenYakStructureCategoryVo();
        vo.setCategoryCode(code);
        vo.setCategoryLabel(label);
        vo.setCount(count);
        vo.setRatio(BreedingIndustryCalculator.ratioPercent(count, total));
        List<HomeScreenYakStructureDetailVo> detailList = new ArrayList<>();
        if (details != null)
        {
            Collections.addAll(detailList, details);
        }
        vo.setDetails(detailList);
        return vo;
    }

    private static HomeScreenYakStructureDetailVo yakDetailVo(
            String code, String label, int count, int total)
    {
        HomeScreenYakStructureDetailVo vo = new HomeScreenYakStructureDetailVo();
        vo.setDetailCode(code);
        vo.setDetailLabel(label);
        vo.setCount(count);
        vo.setRatio(BreedingIndustryCalculator.ratioPercent(count, total));
        return vo;
    }

    private static HomeScreenHerdSpeciesItemVo speciesItemVo(String code, String label, int count, int total)
    {
        HomeScreenHerdSpeciesItemVo vo = new HomeScreenHerdSpeciesItemVo();
        vo.setSpeciesCode(code);
        vo.setSpeciesLabel(label);
        vo.setCount(count);
        vo.setRatio(BreedingIndustryCalculator.ratioPercent(count, total));
        return vo;
    }

    private static HomeScreenAgeBandVo cowAgeBandVo(String code, String label, int count, int total)
    {
        HomeScreenAgeBandVo vo = new HomeScreenAgeBandVo();
        vo.setBandCode(code);
        vo.setBandLabel(label);
        vo.setCount(count);
        vo.setRatio(BreedingIndustryCalculator.ratioPercent(count, total));
        return vo;
    }

    private static int safeInt(Integer value)
    {
        return value != null ? value : 0;
    }

    public static List<HomeScreenAgeBandVo> buildAgeStructureAtDate(
            List<BreedingIndustryYakSnapshot> assets, LocalDate statDate)
    {
        Map<String, Integer> bandCounts = new HashMap<>();
        bandCounts.put(BreedingIndustryRules.BAND_M1_6, 0);
        bandCounts.put(BreedingIndustryRules.BAND_M7_12, 0);
        bandCounts.put(BreedingIndustryRules.BAND_M13_PLUS, 0);
        int total = 0;
        for (BreedingIndustryYakSnapshot asset : assets)
        {
            if (!BreedingIndustryCalculator.countsAtMonthEnd(asset, statDate))
            {
                continue;
            }
            total++;
            String band = BreedingIndustryCalculator.bandCodeOf(
                    BreedingIndustryCalculator.resolveAgeMonths(asset, statDate));
            bandCounts.put(band, bandCounts.get(band) + 1);
        }
        List<BreedingIndustryAgeBandVo> breedingBands =
                BreedingIndustryCalculator.buildAgeStructure(bandCounts, total);
        List<HomeScreenAgeBandVo> result = new ArrayList<>();
        for (BreedingIndustryAgeBandVo band : breedingBands)
        {
            HomeScreenAgeBandVo vo = new HomeScreenAgeBandVo();
            vo.setBandCode(band.getBandCode());
            vo.setBandLabel(band.getBandLabel());
            vo.setCount(band.getCount());
            vo.setRatio(band.getRatio());
            result.add(vo);
        }
        return result;
    }

    /**
     * 牦牛月度出栏趋势（出栏填报表 {@code yak_outbound_count} 全县按月合计）。
     * <p>
     * 以所选年填报数据中的<strong>最大月份 N</strong>为准，输出 1～N 月（按月升序）；
     * 区间内缺月补 0；无任何填报月则返回空列表。
     * </p>
     *
     * @param rows     按月合计行
     * @param statDate 所选年统计截止日（取年份写入 {@code statYear}）
     */
    public static List<HomeScreenOutboundMonthVo> buildOutboundMonthly(
            List<HomeScreenMonthCountDto> rows, LocalDate statDate)
    {
        Map<Integer, Integer> byMonth = new HashMap<>();
        int maxMonth = 0;
        if (rows != null)
        {
            for (HomeScreenMonthCountDto row : rows)
            {
                if (row == null || row.getMonth() == null)
                {
                    continue;
                }
                int month = row.getMonth();
                if (month < 1 || month > 12)
                {
                    continue;
                }
                byMonth.put(month, row.getCount() != null ? row.getCount() : 0);
                if (month > maxMonth)
                {
                    maxMonth = month;
                }
            }
        }
        if (maxMonth <= 0)
        {
            return Collections.emptyList();
        }
        int statYear = statDate != null ? statDate.getYear()
                : LocalDate.now(ZoneId.of(BreedingIndustryRules.ZONE_SHANGHAI)).getYear();
        List<HomeScreenOutboundMonthVo> list = new ArrayList<>(maxMonth);
        for (int month = 1; month <= maxMonth; month++)
        {
            HomeScreenOutboundMonthVo vo = new HomeScreenOutboundMonthVo();
            vo.setStatYear(statYear);
            vo.setMonth(month);
            vo.setOutboundCount(byMonth.getOrDefault(month, 0));
            list.add(vo);
        }
        return list;
    }

    public static HomeScreenGrasslandVo buildGrasslandVo(
            int grasslandTotal, int availableCount, int inUseCount,
            List<BreedingIndustryDegradationStatVo> grouped)
    {
        Map<Integer, Integer> countByLevel = new HashMap<>();
        if (grouped != null)
        {
            for (BreedingIndustryDegradationStatVo row : grouped)
            {
                countByLevel.put(row.getDegradationLevel(), row.getCount());
            }
        }
        List<HomeScreenDegradationStatVo> stats = new ArrayList<>();
        for (int level = GrasslandRules.DEGRADATION_NONE; level <= GrasslandRules.DEGRADATION_EXTREME; level++)
        {
            int count = countByLevel.getOrDefault(level, 0);
            HomeScreenDegradationStatVo stat = new HomeScreenDegradationStatVo();
            stat.setDegradationLevel(level);
            stat.setDegradationLabel(DEGRADATION_LABELS[level]);
            stat.setCount(count);
            stat.setRatio(BreedingIndustryCalculator.ratioPercent(count, grasslandTotal));
            stats.add(stat);
        }
        HomeScreenGrasslandVo vo = new HomeScreenGrasslandVo();
        vo.setAvailableCount(availableCount);
        vo.setInUseCount(inUseCount);
        vo.setDegradationStats(stats);
        return vo;
    }

    public static HomeScreenAppointmentVo buildAppointment(
            List<HomeScreenAppointmentStatDto> annualRows,
            List<HomeScreenAppointmentStatDto> rollingMonthlyRows,
            YearMonth anchor)
    {
        int vet = 0;
        int expert = 0;
        int org = 0;
        if (annualRows != null)
        {
            for (HomeScreenAppointmentStatDto row : annualRows)
            {
                int c = row.getCount() != null ? row.getCount() : 0;
                if (row.getProviderType() != null)
                {
                    if (row.getProviderType() == PROVIDER_VET)
                    {
                        vet = c;
                    }
                    else if (row.getProviderType() == PROVIDER_EXPERT)
                    {
                        expert = c;
                    }
                    else if (row.getProviderType() == PROVIDER_ORG)
                    {
                        org = c;
                    }
                }
            }
        }
        HomeScreenAppointmentVo vo = new HomeScreenAppointmentVo();
        vo.setAnnualVetCount(vet);
        vo.setAnnualExpertCount(expert);
        vo.setAnnualOrgCount(org);
        vo.setMonthlyStacked(buildRolling12MonthAppointmentStacked(rollingMonthlyRows, anchor));
        return vo;
    }

    /**
     * 线下服务预约（畜牧资源）：三项台账人数/机构数不随年变；月度堆叠为所选年 1～库中最大月，缺月补 0。
     *
     * @param vetCount    兽医人员数（biz_medical_resource 004001）
     * @param expertCount 专家人员数（biz_tech_resource 004005）
     * @param orgCount    诊疗机构数（biz_medical_resource 004003）
     */
    public static HomeScreenAppointmentVo buildAppointmentForStatYear(
            int vetCount,
            int expertCount,
            int orgCount,
            List<HomeScreenAppointmentStatDto> monthlyRows,
            int statYear)
    {
        HomeScreenAppointmentVo vo = new HomeScreenAppointmentVo();
        vo.setAnnualVetCount(Math.max(0, vetCount));
        vo.setAnnualExpertCount(Math.max(0, expertCount));
        vo.setAnnualOrgCount(Math.max(0, orgCount));
        vo.setMonthlyStacked(buildStatYearAppointmentStacked(monthlyRows, statYear));
        return vo;
    }

    /**
     * 线下服务预约月度堆叠：所选年 1～最大月。
     */
    public static List<HomeScreenAppointmentMonthVo> buildStatYearAppointmentStacked(
            List<HomeScreenAppointmentStatDto> rows, int statYear)
    {
        Map<Integer, Map<Integer, Integer>> byMonthAndType = new HashMap<>();
        int maxMonth = 0;
        if (rows != null)
        {
            for (HomeScreenAppointmentStatDto row : rows)
            {
                if (row == null || row.getProviderType() == null)
                {
                    continue;
                }
                Integer month = row.getStatMonth() != null ? row.getStatMonth() : row.getMonth();
                if (month == null || month < 1 || month > 12)
                {
                    continue;
                }
                byMonthAndType.computeIfAbsent(month, k -> new HashMap<>())
                        .put(row.getProviderType(), row.getCount() != null ? row.getCount() : 0);
                if (month > maxMonth)
                {
                    maxMonth = month;
                }
            }
        }
        if (maxMonth <= 0)
        {
            return Collections.emptyList();
        }
        List<HomeScreenAppointmentMonthVo> stacked = new ArrayList<>(maxMonth);
        for (int month = 1; month <= maxMonth; month++)
        {
            Map<Integer, Integer> types = byMonthAndType.getOrDefault(month, Collections.emptyMap());
            HomeScreenAppointmentMonthVo point = new HomeScreenAppointmentMonthVo();
            point.setStatYear(statYear);
            point.setMonth(month);
            point.setVetCount(types.getOrDefault(PROVIDER_VET, 0));
            point.setExpertCount(types.getOrDefault(PROVIDER_EXPERT, 0));
            point.setOrgCount(types.getOrDefault(PROVIDER_ORG, 0));
            stacked.add(point);
        }
        return stacked;
    }

    /** 按 {@code biz_subsidy_grant_record.subsidy_type} 汇总金额（万元），无记录时 hasSubsidyData=false。 */
    public static HomeScreenSubsidyVo buildSubsidy(List<HomeScreenSubsidyTypeSumDto> sums, boolean hasSubsidyData)
    {
        Map<Integer, BigDecimal> amountByType = new HashMap<>();
        if (sums != null)
        {
            for (HomeScreenSubsidyTypeSumDto row : sums)
            {
                if (row.getSubsidyType() != null && row.getAmountSum() != null)
                {
                    amountByType.put(row.getSubsidyType(), row.getAmountSum());
                }
            }
        }
        List<HomeScreenSubsidyTypeStatVo> stats = new ArrayList<>();
        stats.add(subsidyTypeStat(1, "农业生产", amountByType.get(1)));
        stats.add(subsidyTypeStat(2, "住房基建", amountByType.get(2)));
        stats.add(subsidyTypeStat(3, "民生保障", amountByType.get(3)));
        HomeScreenSubsidyVo vo = new HomeScreenSubsidyVo();
        vo.setHasSubsidyData(hasSubsidyData);
        vo.setTypeStats(stats);
        return vo;
    }

    public static BigDecimal toAmountWan(BigDecimal amountYuan)
    {
        if (amountYuan == null)
        {
            return BigDecimal.ZERO.setScale(2, RoundingMode.HALF_UP);
        }
        return amountYuan.divide(new BigDecimal("10000"), 2, RoundingMode.HALF_UP);
    }

    public static HomeScreenAgriClassroomVo buildAgriClassroom(int videoCount, int trainingCount)
    {
        HomeScreenAgriClassroomVo vo = new HomeScreenAgriClassroomVo();
        vo.setVideoCourseCount(videoCount);
        vo.setPracticalTrainingCount(trainingCount);
        vo.setTrainingContentTotal(videoCount + trainingCount);
        vo.setHasAgriData(videoCount > 0 || trainingCount > 0);
        return vo;
    }

    public static HomeScreenTrainingTrendVo buildTrainingTrend(
            List<HomeScreenTrainingMonthDto> rollingRows, boolean hasTrainingData, YearMonth anchor)
    {
        List<HomeScreenTrainingMonthVo> monthly = buildRolling12MonthTraining(rollingRows, anchor);
        if (!hasTrainingData)
        {
            for (HomeScreenTrainingMonthVo point : monthly)
            {
                point.setActivityCount(0);
                point.setEnrolledCount(0);
            }
        }
        HomeScreenTrainingTrendVo vo = new HomeScreenTrainingTrendVo();
        vo.setHasTrainingData(hasTrainingData);
        vo.setMonthly(monthly);
        return vo;
    }

    /**
     * 实战培训报名（畜牧资源）：所选年从 1 月到库中最大填报月，缺月补 0；无数据则空序列。
     */
    public static HomeScreenTrainingTrendVo buildTrainingTrendForStatYear(
            List<HomeScreenTrainingMonthDto> rows, int statYear)
    {
        Map<Integer, HomeScreenTrainingMonthDto> byMonth = new HashMap<>();
        int maxMonth = 0;
        if (rows != null)
        {
            for (HomeScreenTrainingMonthDto row : rows)
            {
                if (row == null)
                {
                    continue;
                }
                Integer month = row.getStatMonth() != null ? row.getStatMonth() : row.getMonth();
                if (month == null || month < 1 || month > 12)
                {
                    continue;
                }
                byMonth.put(month, row);
                if (month > maxMonth)
                {
                    maxMonth = month;
                }
            }
        }
        HomeScreenTrainingTrendVo vo = new HomeScreenTrainingTrendVo();
        if (maxMonth <= 0)
        {
            vo.setHasTrainingData(false);
            vo.setMonthly(Collections.emptyList());
            return vo;
        }
        List<HomeScreenTrainingMonthVo> monthly = new ArrayList<>(maxMonth);
        for (int month = 1; month <= maxMonth; month++)
        {
            HomeScreenTrainingMonthDto src = byMonth.get(month);
            HomeScreenTrainingMonthVo point = new HomeScreenTrainingMonthVo();
            point.setStatYear(statYear);
            point.setMonth(month);
            point.setActivityCount(src != null && src.getActivityCount() != null ? src.getActivityCount() : 0);
            point.setEnrolledCount(src != null && src.getEnrolledCount() != null ? src.getEnrolledCount() : 0);
            monthly.add(point);
        }
        vo.setHasTrainingData(true);
        vo.setMonthly(monthly);
        return vo;
    }

    /** 合并各数据源年份并降序，始终包含当前年供前端年份切换。 */
    public static List<Integer> buildAvailableYears(List<Integer>... yearLists)
    {
        Set<Integer> set = new TreeSet<>(Collections.reverseOrder());
        int currentYear = LocalDate.now(ZoneId.of(BreedingIndustryRules.ZONE_SHANGHAI)).getYear();
        set.add(currentYear);
        if (yearLists != null)
        {
            for (List<Integer> list : yearLists)
            {
                if (list != null)
                {
                    set.addAll(list);
                }
            }
        }
        return new ArrayList<>(set);
    }

    public static void enrichStandardTypeNames(List<HomeScreenStandardListItemVo> rows)
    {
        if (rows == null)
        {
            return;
        }
        for (HomeScreenStandardListItemVo row : rows)
        {
            String code = row.getStandardType();
            row.setStandardTypeName(STANDARD_TYPE_NAMES.getOrDefault(code, code));
        }
    }

    private static HomeScreenSubsidyTypeStatVo subsidyTypeStat(int type, String name, BigDecimal amountYuan)
    {
        HomeScreenSubsidyTypeStatVo stat = new HomeScreenSubsidyTypeStatVo();
        stat.setSubsidyType(type);
        stat.setSubsidyTypeName(name);
        stat.setAmountWan(toAmountWan(amountYuan));
        return stat;
    }
}
