Files
Neo-ZQYY/.kiro/specs/business-day-cutoff/requirements.md

14 KiB
Raw Blame History

需求文档业务日分割点机制Business Day Cutoff

简介

引入"业务日分割点"机制,将全系统的统计时间口径从自然日/自然周/自然月切换为以可配置的小时值(默认 08:00为分割点的营业日/营业周/营业月。影响范围覆盖配置层、共享包、ETL 层ODS→DWD→DWS、后端 API 层、前端展示层(管理后台、小程序)及数据库层。

术语表

  • Business_Day_Cutoff营业日分割点一个整数小时值023定义一个"业务日"的起始时刻。默认值为 8即 08:00
  • Business_Date:营业日,从当天 Business_Day_Cutoff 时刻到次日 Business_Day_Cutoff 时刻的时间段所归属的日期。Business_Day_Cutoff 之前的时间戳归属前一天。
  • Business_Week:营业周,从周一 Business_Day_Cutoff 到次周一 Business_Day_Cutoff 的时间段。
  • Business_Month营业月从当月1日 Business_Day_Cutoff 到次月1日 Business_Day_Cutoff 的时间段。
  • Shared_DateTime_Utilspackages/shared/src/neozqyy_shared/datetime_utils.py,跨子系统共享的时间工具模块。
  • AppConfigETL 配置管理器(apps/etl/connectors/feiqiu/config/settings.py),通过 AppConfig.load() 加载配置。
  • Backend_Config:后端配置模块(apps/backend/app/config.py),从 .env 加载环境变量。
  • DWS_TaskDWS 层聚合任务,从 DWD 事实表读取数据并按时间维度聚合写入 DWS 汇总表。
  • biz_date_sql_exprShared_DateTime_Utils 中生成 PostgreSQL 营业日归属 SQL 表达式的函数。
  • stat_dateDWS 汇总表中的统计日期字段,存储的是 Business_Date 而非自然日期。
  • Admin_Web:管理后台前端(apps/admin-web/React + Vite + Ant Design
  • Miniprogram:微信小程序前端(apps/miniprogram/)。

需求

需求 1环境变量配置

用户故事: 作为运维人员,我希望通过 .env 环境变量配置营业日分割点小时值,以便在不修改代码的情况下调整统计时间口径。

验收标准

  1. THE Root_Env SHALL 定义 BUSINESS_DAY_START_HOUR 环境变量,值为 023 的整数,默认值为 8
  2. THE Env_Template SHALL 同步包含 BUSINESS_DAY_START_HOUR 的定义及注释说明(日/周/月统计的分割语义)
  3. WHEN BUSINESS_DAY_START_HOUR 的值不在 023 范围内时, THEN THE AppConfig SHALL 在加载阶段抛出 SystemExit 错误并给出明确提示
  4. WHEN BUSINESS_DAY_START_HOUR 环境变量缺失时, THE AppConfig SHALL 使用默认值 8

需求 2共享时间工具函数

用户故事: 作为开发者,我希望有一组经过充分测试的共享时间工具函数,以便所有子系统使用统一的营业日归属逻辑。

验收标准

  1. THE Shared_DateTime_Utils SHALL 提供 business_date(dt, day_start_hour) 函数,将任意时间戳归属到对应的 Business_Date
  2. THE Shared_DateTime_Utils SHALL 提供 business_month(dt, day_start_hour) 函数,将任意时间戳归属到对应的 Business_Month 首日
  3. THE Shared_DateTime_Utils SHALL 提供 business_week_monday(dt, day_start_hour) 函数,将任意时间戳归属到对应的 Business_Week 的周一日期
  4. THE Shared_DateTime_Utils SHALL 提供 biz_date_sql_expr(col, day_start_hour) 函数,生成 PostgreSQL 营业日归属 SQL 表达式(形如 DATE(col - INTERVAL 'N hours')
  5. WHEN day_start_hour 参数未传入时, THE Shared_DateTime_Utils SHALL 使用 DEFAULT_BUSINESS_DAY_START_HOUR(值为 8作为默认值
  6. THE Shared_DateTime_Utils SHALL 提供 business_day_range(biz_date, day_start_hour) 函数,返回给定 Business_Date 对应的精确时间戳范围 (start_dt, end_dt),即 (biz_date 当天 day_start_hour:00, biz_date 次日 day_start_hour:00)
  7. THE Shared_DateTime_Utils SHALL 提供 business_week_range(week_monday, day_start_hour) 函数,返回给定 Business_Week 周一对应的精确时间戳范围
  8. THE Shared_DateTime_Utils SHALL 提供 business_month_range(month_first, day_start_hour) 函数,返回给定 Business_Month 首日对应的精确时间戳范围
  9. FOR ALL 合法的 datetime 输入, business_date 的输出 SHALL 满足:business_day_range(business_date(dt, h), h)[0] <= dt < business_day_range(business_date(dt, h), h)[1](往返一致性)
  10. FOR ALL 合法的 datetime 输入, business_month(dt, h) SHALL 等于 business_date(dt, h).replace(day=1)(月归属与日归属一致性)
  11. FOR ALL 合法的 datetime 输入, business_week_monday(dt, h) SHALL 等于 business_date(dt, h) - timedelta(days=business_date(dt, h).weekday())(周归属与日归属一致性)

需求 3ETL 配置层集成

用户故事: 作为 ETL 开发者,我希望 AppConfig 正确加载并传播 BUSINESS_DAY_START_HOUR,以便 ETL 任务能获取到配置的分割点值。

验收标准

  1. THE AppConfig SHALL 在 app.business_day_start_hour 路径下存储 BUSINESS_DAY_START_HOUR 的整数值
  2. THE Env_Parser SHALL 将环境变量 BUSINESS_DAY_START_HOUR 映射到 app.business_day_start_hour 配置路径
  3. THE AppConfig_Defaults SHALL 将 app.business_day_start_hour 的默认值设为 8
  4. WHEN AppConfig 加载完成后, THE AppConfig SHALL 通过 cfg.get("app.business_day_start_hour") 返回正确的整数值

需求 4ETL DWS 层聚合逻辑

用户故事: 作为数据分析师,我希望 DWS 层的所有日度/周度/月度聚合统计都基于营业日口径,以便统计结果与门店实际营业周期一致。

验收标准

  1. WHEN DWS_Task 从 DWD 表提取数据时, THE DWS_Task SHALL 使用 biz_date_sql_expr 替代 DATE() 进行日期归属计算
  2. WHEN DWS_Task 按日聚合时, THE DWS_Task SHALL 使用 DATE(timestamp_col - INTERVAL 'N hours') 作为 stat_date 的分组依据,其中 N 为 Business_Day_Cutoff
  3. WHEN DWS_Task 按月聚合时, THE DWS_Task SHALL 使用 Business_Month 口径当月1日 cutoff 到次月1日 cutoff
  4. WHEN DWS_Task 按周聚合时, THE DWS_Task SHALL 使用 Business_Week 口径(周一 cutoff 到次周一 cutoff
  5. THE BaseDwsTask.iter_dwd_rows SHALL 使用 biz_date_sql_expr 替代 DATE() 进行日期过滤
  6. THE BaseDwsTask.get_time_window_range SHALL 返回基于 Business_Date 口径的时间范围
  7. WHILE ETL 任务运行期间, THE DWS_Task SHALL 从 AppConfig 读取 app.business_day_start_hour 值,禁止硬编码

需求 5受影响的 DWS 任务全面排查

用户故事: 作为项目负责人,我希望所有使用 DATE() 进行时间归属的 DWS 任务都被排查并改造,确保无遗漏。

验收标准

  1. THE FinanceBaseTask SHALL 将所有 DATE(pay_time) 替换为 biz_date_sql_expr("pay_time", cutoff_hour) 生成的表达式
  2. THE FinanceDailyTask SHALL 使用 Business_Date 口径提取和聚合结账单、团购核销、充值等数据
  3. THE FinanceRechargeTask SHALL 将 DATE(pay_time) 替换为营业日归属表达式
  4. THE FinanceDiscountTask SHALL 使用 Business_Date 口径聚合优惠明细
  5. THE FinanceIncomeTask SHALL 使用 Business_Date 口径聚合收入结构
  6. THE AssistantDailyTask SHALL 使用 Business_Date 口径聚合助教日度明细
  7. THE AssistantOrderContributionTask SHALL 将 DATE(pay_time) 替换为营业日归属表达式
  8. THE AssistantCustomerTask SHALL 将 DATE(start_use_time) 替换为营业日归属表达式
  9. THE AssistantMonthlyTask SHALL 使用 Business_Month 口径聚合助教月度汇总
  10. THE AssistantFinanceTask SHALL 使用 Business_Date 口径聚合助教财务分析
  11. THE MemberVisitTask SHALL 将 DATE(pay_time)DATE(start_use_time)DATE(ledger_end_time) 替换为营业日归属表达式
  12. THE MemberConsumptionTask SHALL 将 DATE(pay_time)DATE(create_time) 替换为营业日归属表达式
  13. THE GoodsStockDailyTask SHALL 将 DATE(fetched_at) 替换为营业日归属表达式
  14. THE GoodsStockWeeklyTask SHALL 使用 Business_Week 口径聚合库存周报
  15. THE GoodsStockMonthlyTask SHALL 使用 Business_Month 口径聚合库存月报
  16. THE SpendingPowerIndexTask SHALL 将 DATE(pay_time) 替换为营业日归属表达式
  17. THE MemberIndexBase SHALL 将 DATE(pay_time) 替换为营业日归属表达式
  18. THE MvRefreshTask SHALL 确保物化视图刷新的时间过滤条件使用 Business_Date 口径

需求 6后端 API 层时间范围计算

用户故事: 作为后端开发者,我希望后端 API 在处理"今日/本周/本月"等时间范围查询时使用营业日口径,以便前端展示的数据与 DWS 统计一致。

验收标准

  1. THE Backend_Config SHALL 加载 BUSINESS_DAY_START_HOUR 环境变量并暴露为模块级常量
  2. WHEN 后端 API 需要计算"今日"时间范围时, THE Backend SHALL 使用 business_day_range 函数计算从当天 cutoff 到次日 cutoff 的时间戳范围
  3. WHEN 后端 API 需要计算"本周"时间范围时, THE Backend SHALL 使用 business_week_range 函数计算从本周一 cutoff 到次周一 cutoff 的时间戳范围
  4. WHEN 后端 API 需要计算"本月"时间范围时, THE Backend SHALL 使用 business_month_range 函数计算从本月1日 cutoff 到次月1日 cutoff 的时间戳范围
  5. THE Backend SHALL 从 Shared_DateTime_Utils 导入时间工具函数,禁止在后端重复实现营业日逻辑

需求 7前端展示层适配

用户故事: 作为前端开发者,我希望管理后台和小程序在展示日期选择器和统计数据时,能正确反映营业日口径,避免用户困惑。

验收标准

  1. WHEN Admin_Web 展示日期选择器时, THE Admin_Web SHALL 在日期选择器旁标注营业日口径说明(如"营业日08:00 起"
  2. WHEN Admin_Web 展示"今日统计"时, THE Admin_Web SHALL 显示的时间范围为当天 cutoff 到次日 cutoff
  3. WHEN Miniprogram 展示统计数据时, THE Miniprogram SHALL 使用后端 API 返回的基于营业日口径的数据
  4. THE Admin_Web SHALL 通过后端 API 获取 BUSINESS_DAY_START_HOUR 配置值,禁止前端硬编码
  5. IF 后端 API 返回的 BUSINESS_DAY_START_HOUR 配置值不可用, THEN THE Admin_Web SHALL 使用默认值 8 并在控制台输出警告

需求 8后端配置查询 API

用户故事: 作为前端开发者,我希望有一个 API 端点能返回当前的营业日分割点配置,以便前端动态获取并展示。

验收标准

  1. THE Backend SHALL 提供一个 API 端点返回当前 BUSINESS_DAY_START_HOUR 的值
  2. WHEN 前端请求该端点时, THE Backend SHALL 返回包含 business_day_start_hour 字段的 JSON 响应
  3. THE Backend SHALL 确保该端点的响应值与 ETL 层使用的 BUSINESS_DAY_START_HOUR 值一致(均来源于同一 .env 配置)

需求 9数据库层适配

用户故事: 作为 DBA我希望数据库中的物化视图和 SQL 函数使用营业日口径,以便直接查询数据库时也能获得正确的统计结果。

验收标准

  1. WHEN 物化视图使用 date_truncCURRENT_DATE 进行时间过滤时, THE Migration_Script SHALL 将其替换为基于 Business_Day_Cutoff 的表达式
  2. THE Migration_Script SHALL 提供一个 PostgreSQL 函数 biz_date(timestamptz, int) 用于在 SQL 中直接计算营业日归属
  3. WHEN 迁移脚本执行后, THE Database SHALL 确保所有物化视图的时间过滤条件使用营业日口径
  4. THE Migration_Script SHALL 使用日期前缀命名(如 2026-03-XX__add_biz_date_function.sql),遵循项目迁移脚本规范

需求 10数据迁移与历史数据兼容

用户故事: 作为运维人员,我希望引入营业日机制后,历史数据能被正确重算,确保统计连续性。

验收标准

  1. THE Migration_Plan SHALL 提供 DWS 历史数据重算脚本,按营业日口径重新聚合所有受影响的 DWS 表
  2. WHEN 历史数据重算执行时, THE Rebuild_Script SHALL 使用与正式 ETL 任务相同的 Business_Day_Cutoff 配置值
  3. THE Migration_Plan SHALL 记录重算前后的数据行数对比,用于验证重算正确性
  4. IF 重算过程中发生错误, THEN THE Rebuild_Script SHALL 回滚到重算前的状态并记录错误日志

需求 11属性测试覆盖

用户故事: 作为测试工程师,我希望营业日归属逻辑有完整的属性测试覆盖,确保边界条件和不变量得到验证。

验收标准

  1. THE Property_Test SHALL 验证 business_date 的往返一致性:对任意 datetime dtbusiness_day_range(business_date(dt, h), h) 的范围包含 dt
  2. THE Property_Test SHALL 验证 business_monthbusiness_date 的一致性:business_month(dt, h) == business_date(dt, h).replace(day=1)
  3. THE Property_Test SHALL 验证 business_week_mondaybusiness_date 的一致性:business_week_monday(dt, h).weekday() == 0(结果始终为周一)
  4. THE Property_Test SHALL 验证 biz_date_sql_expr 的幂等性:对同一输入参数多次调用返回相同结果
  5. THE Property_Test SHALL 验证边界条件cutoff 时刻恰好在分割点上的时间戳归属当天,分割点前一秒归属前一天
  6. THE Property_Test SHALL 验证 business_day_range 返回的范围恰好为 24 小时
  7. THE Property_Test SHALL 验证 business_week_range 返回的范围恰好为 7 天168 小时)
  8. THE Property_Test SHALL 使用 hypothesis 库生成随机 datetime 和 day_start_hour023进行测试
  9. FOR ALL day_start_hour023, THE Property_Test SHALL 验证 business_date 函数的单调性:若 dt1 < dt2 且两者在同一 Business_Date 范围内,则 business_date(dt1, h) == business_date(dt2, h)

需求 12运维脚本中的 DATE() 排查

用户故事: 作为运维人员,我希望 scripts/ops/ 和 ETL scripts/ 中使用 DATE() 的运维脚本也被排查,确保调试和排查工具与正式统计口径一致。

验收标准

  1. THE Ops_Scripts SHALL 排查 scripts/ops/export_bug_report.py 中的 DATE(trash_time)DATE(create_time)DATE(start_use_time) 调用,评估是否需要替换为营业日归属表达式
  2. THE Ops_Scripts SHALL 排查 scripts/ops/etl_consistency_check.py 中的日期比较逻辑
  3. THE ETL_Scripts SHALL 排查 apps/etl/connectors/feiqiu/scripts/debug/debug_blackbox.py 中的 ::date 类型转换
  4. THE ETL_Scripts SHALL 排查 apps/etl/connectors/feiqiu/scripts/run_update.py 中的 .date() 调用和 datetime.combine 逻辑
  5. WHEN 运维脚本用于与 DWS 数据对比验证时, THE Ops_Scripts SHALL 使用与 DWS 任务相同的营业日归属逻辑