年度经营统计分析增长率算法修改方案.md
9.07 KB
年度经营统计分析增长率算法修改方案
一、需求分析
当前算法
- 增长率 = (本年总数 - 上年总数) / 上年总数 × 100%
- 月均 = 总数 / 12
新需求
- 25年新开门店:增长率 = 0
- 增长率算法改为月均增长平均值:
- 增长率 = (本年月均 - 上年月均) / 上年月均 × 100%
- 24年开业的门店特殊处理:
- 第一个月业绩不计入平均的基数
- 第一个月不计入月份数量
- 例如:大源2024.10月开业,实际业绩取数11月、12月,平均月份数 = 2
二、修改逻辑梳理
2.1 门店开业时间获取
- 从
lq_mdxx表获取门店开业时间字段kysj - 判断门店开业年份:
- 如果
kysj年份 = 2025,则为25年新开门店 - 如果
kysj年份 = 2024,则为24年开业门店
- 如果
2.2 月均计算逻辑
对于24年开业的门店(上年数据):
- 获取开业月份:
openMonth = kysj.Month - 计算有效月份数:
validMonths = 12 - openMonth(排除开业当月) - 计算有效月份数据总和:只统计
openMonth + 1到12月的数据 - 月均 = 有效月份数据总和 / 有效月份数
对于25年新开门店(本年数据):
- 获取开业月份:
openMonth = kysj.Month - 计算有效月份数:
validMonths = 12 - openMonth(排除开业当月) - 计算有效月份数据总和:只统计
openMonth + 1到12月的数据 - 月均 = 有效月份数据总和 / 有效月份数
对于其他门店:
- 月均 = 总数 / 12
2.3 增长率计算逻辑
// 伪代码
if (开业年份 == 2025) {
// 25年新开门店,增长率 = 0
growthRate = "0%";
} else {
// 计算本年月均
decimal currentAvg = CalculateMonthlyAverage(本年数据, 开业时间, 当前年份);
// 计算上年月均
decimal lastAvg = CalculateMonthlyAverage(上年数据, 开业时间, 上年年份);
// 增长率 = (本年月均 - 上年月均) / 上年月均 × 100%
if (lastAvg == 0) {
growthRate = currentAvg > 0 ? "100%" : "0%";
} else {
growthRate = ((currentAvg - lastAvg) / lastAvg * 100).ToString("F2") + "%";
}
}
2.4 月均计算方法
private decimal CalculateMonthlyAverage(
List<LqAnnualSummaryEntity> data,
DateTime? openTime,
int year)
{
if (data == null || !data.Any()) return 0;
// 如果没有开业时间,按12个月计算
if (openTime == null || openTime.Value.Year != year)
{
return data.Sum(x => fieldSelector(x)) / 12;
}
// 如果是开业当年,排除开业当月
int openMonth = openTime.Value.Month;
int validMonthCount = 12 - openMonth;
if (validMonthCount <= 0) return 0;
// 只统计开业月之后的数据
var validData = data.Where(x => x.Month > openMonth).ToList();
decimal total = validData.Sum(x => fieldSelector(x));
return total / validMonthCount;
}
三、需要修改的接口
3.1 月度统计相关接口(使用 GetMonthlyStat 方法)
以下接口都调用 GetMonthlyStat 方法,需要统一修改:
[HttpPost("GetTotalPerformanceStat")]- 业绩月度统计[HttpPost("GetTotalConsumeStat")]- 消耗月度统计[HttpPost("GetHeadCountStat")]- 人头月度统计[HttpPost("GetProjectCountStat")]- 项目数月度统计[HttpPost("GetPersonTimeStat")]- 人次月度统计[HttpPost("GetMonthlyStat")]- 通用月度统计
修改点:
- 修改
GetMonthlyStat方法中的月均计算逻辑(第1017行、1020行) - 修改增长率计算逻辑(第1022行),改为基于月均计算
3.2 事业部汇总统计接口
[HttpPost("GetBusinessUnitSummaryStat")] - 事业部汇总统计(宽表)
修改点:
- 第912-916行:所有增长率计算需要改为基于月均计算
- 需要为每个指标计算月均,然后基于月均计算增长率
3.3 门店指标统计接口
[HttpPost("GetStoreIndicatorsStat")] - 门店指标统计
修改点:
- 调用
GetStoreIndicatorStat方法(第1116行) - 需要修改
GetStoreIndicatorStat方法中的增长率计算逻辑
3.4 事业部指标统计接口
[HttpPost("GetBuIndicatorsStat")] - 事业部指标统计
修改点:
- 调用
GetBuIndicatorStat方法(第1195行) - 需要修改
GetBuIndicatorStat方法中的增长率计算逻辑
3.5 核心方法修改
CalculateGrowthRate方法(第1212行)- 需要重载或修改,支持基于月均的增长率计算
- 需要传入开业时间参数,判断是否为25年新开门店
新增方法:
CalculateMonthlyAverage- 计算月均,考虑开业时间
- 排除开业当月的数据和月份数
新增方法:
GetStoreOpenTime- 批量获取门店开业时间
- 返回
Dictionary<string, DateTime?>格式
四、具体修改步骤
步骤1:新增辅助方法
/// <summary>
/// 批量获取门店开业时间
/// </summary>
private async Task<Dictionary<string, DateTime?>> GetStoreOpenTimesAsync(List<string> storeIds)
{
var stores = await _db.Queryable<LqMdxxEntity>()
.Where(x => storeIds.Contains(x.Id))
.Select(x => new { x.Id, x.Kysj })
.ToListAsync();
return stores.ToDictionary(x => x.Id, x => x.Kysj);
}
/// <summary>
/// 计算月均(考虑开业时间)
/// </summary>
private decimal CalculateMonthlyAverage(
List<LqAnnualSummaryEntity> data,
DateTime? openTime,
int year,
Func<LqAnnualSummaryEntity, decimal> fieldSelector)
{
if (data == null || !data.Any()) return 0;
// 如果没有开业时间,或开业年份不是当前年份,按12个月计算
if (openTime == null || openTime.Value.Year != year)
{
return data.Sum(x => fieldSelector(x)) / 12;
}
// 如果是开业当年,排除开业当月
int openMonth = openTime.Value.Month;
int validMonthCount = 12 - openMonth;
if (validMonthCount <= 0) return 0;
// 只统计开业月之后的数据
var validData = data.Where(x => x.Month > openMonth).ToList();
if (!validData.Any()) return 0;
decimal total = validData.Sum(x => fieldSelector(x));
return total / validMonthCount;
}
/// <summary>
/// 计算增长率(基于月均,考虑开业时间)
/// </summary>
private string CalculateGrowthRateByAverage(
List<LqAnnualSummaryEntity> currentData,
List<LqAnnualSummaryEntity> lastData,
DateTime? openTime,
int currentYear,
Func<LqAnnualSummaryEntity, decimal> fieldSelector)
{
// 25年新开门店,增长率 = 0
if (openTime != null && openTime.Value.Year == 2025)
{
return "0%";
}
// 计算本年月均
decimal currentAvg = CalculateMonthlyAverage(currentData, openTime, currentYear, fieldSelector);
// 计算上年月均
decimal lastAvg = CalculateMonthlyAverage(lastData, openTime, currentYear - 1, fieldSelector);
if (lastAvg == 0) return currentAvg > 0 ? "100%" : "0%";
var rate = (currentAvg - lastAvg) / lastAvg;
return (rate * 100).ToString("F2") + "%";
}
步骤2:修改 GetMonthlyStat 方法
- 在方法开始处获取门店开业时间
- 修改月均计算逻辑(第1017行、1020行)
- 修改增长率计算逻辑(第1022行)
步骤3:修改 GetBusinessUnitSummaryStat 方法
- 在方法开始处获取门店开业时间
- 为每个指标计算月均,然后基于月均计算增长率(第912-916行)
步骤4:修改 GetStoreIndicatorStat 方法
- 在方法开始处获取门店开业时间
- 修改增长率计算逻辑(第1116行)
步骤5:修改 GetBuIndicatorStat 方法
- 在方法开始处获取门店开业时间
- 修改增长率计算逻辑(第1195行)
五、注意事项
- 数据一致性:确保门店开业时间数据准确,如果
kysj为空,按12个月计算 - 边界情况:
- 开业月份 = 12月:有效月份数 = 0,月均 = 0
- 开业月份 = 1月:有效月份数 = 11,排除1月数据
- 性能优化:批量获取门店开业时间,避免在循环中查询数据库
- 向后兼容:保留原有的
CalculateGrowthRate方法,新增基于月均的方法
六、测试用例
测试用例1:25年新开门店
- 门店:测试门店A
- 开业时间:2025-06-01
- 本年业绩:100万(6-12月,7个月)
- 上年业绩:0
- 预期结果:增长率 = 0%
测试用例2:24年开业门店
- 门店:大源店
- 开业时间:2024-10-01
- 本年业绩:200万(全年12个月)
- 上年业绩:50万(11月、12月,2个月)
- 上年月均:50万 / 2 = 25万
- 本年月均:200万 / 12 = 16.67万
- 预期结果:增长率 = (16.67 - 25) / 25 × 100% = -33.32%
测试用例3:正常门店
- 门店:正常门店B
- 开业时间:2023-01-01
- 本年业绩:300万(全年12个月)
- 上年业绩:240万(全年12个月)
- 本年月均:300万 / 12 = 25万
- 上年月均:240万 / 12 = 20万
- 预期结果:增长率 = (25 - 20) / 20 × 100% = 25%