diff --git a/docs/features/FEAT-MO-001-market-overview/design.md b/docs/features/FEAT-MO-001-market-overview/design.md index 75faaab..32ab030 100644 --- a/docs/features/FEAT-MO-001-market-overview/design.md +++ b/docs/features/FEAT-MO-001-market-overview/design.md @@ -8,7 +8,7 @@ - `breadth`:使用 TDX 指数快照的 `UpHome`、`DownHome` 汇总上证、深证和北证市场宽度;未返回的 `unchanged` 明确为 `null`,不得用总数推算。 - `sectors`:使用 TDX 系统分类中的一级行业(`get_stock_list(market=16)`)和批量日行情,按涨跌幅排序后展示重点强弱板块。 - `funds`:模块名称和口径为“成交额趋势”,使用核心交易所指数日行情的 `Amount` 汇总当前与前一交易日成交额;这不是主力净流入。 -- 交易日对齐:交易日期必须是有效 `YYYYMMDD` 公历日期;指数和一级行业只保留最新交易日截面。成交额还要求日期与金额数组等长且前一交易日早于当前交易日,再按最新有效日期对汇总,避免畸形日期、数组错位或旧市场覆盖新数据。市场宽度额外并行读取一个核心指数日线作为日期参考。 +- 交易日对齐:交易日期必须是有效 `YYYYMMDD` 公历日期;指数和一级行业只有在前一根日期有效且严格早于当前日期时才计算涨跌,随后只保留最新交易日截面。成交额还要求日期与金额数组等长且前一交易日早于当前交易日,再按最新有效日期对汇总,避免畸形日期、数组错位或旧市场覆盖新数据。市场宽度额外并行读取一个核心指数日线作为日期参考。 - 新鲜度:日行情交易日期与 `Asia/Shanghai` 当前日历日期不同即标记 `delayed`;宽度沿用参考指数日期。首版不引入交易日历依赖,休市日保守标延迟并展示实际日期。 - 主力净流入和盘中新闻不在本次 TDX 首屏主链路中,后续通过独立补充数据源适配器接入,并在响应中标记来源与数据质量。 - 页面不把缺失或权限不足的指标降级为模拟值;单个来源失败只影响对应模块。 diff --git a/docs/features/FEAT-MO-001-market-overview/development-summary.md b/docs/features/FEAT-MO-001-market-overview/development-summary.md index 1f2251c..f4cb372 100644 --- a/docs/features/FEAT-MO-001-market-overview/development-summary.md +++ b/docs/features/FEAT-MO-001-market-overview/development-summary.md @@ -30,6 +30,7 @@ - 根据代码 Review 修复畸形 TDX 字段被折算为零、缺市场成交额误报三市、交易日错位跨日求和、空行业目录误报 `fresh`、前端隐藏 `partial/delayed` 警告和零变化使用上涨色等缺陷,并补齐对应回归测试。 - 根据二次 Review 继续修复涨跌家数接受非整数、成交额受市场定义顺序影响、指数/板块跨交易日混排和后端从不生成 `delayed` 的边界;宽度增加核心指数日期参考,不新增基础设施或第三方依赖。 - 根据第三轮独立 Review 修复 8 位畸形日期被误认为有效公历日期、成交额接受倒序日期对与日期/金额数组错位,以及服务测试依赖运行当天的问题。 +- 根据第四轮独立 Review 修复指数/板块未校验前一根 K 线日期的问题;非法、重复或倒序的前一日期不再生成涨跌信号。 ## 未完成与延期项 @@ -39,16 +40,16 @@ ## 关键提交 -- `9a965fa`、`d283ee3`、`6eb34b5`、`c2fa929`、`15a2500`、`27bc3c1`、`8261948`、`ff8ada8`、`49f6be9`、`7e4dcd9` +- `9a965fa`、`d283ee3`、`6eb34b5`、`c2fa929`、`15a2500`、`27bc3c1`、`8261948`、`ff8ada8`、`49f6be9`、`7e4dcd9`、`ae2a7e0` ## 验证证据 - GitNexus 已完成提交前重新索引;上一修复提交的最终 staged 检测映射 66 个符号、21 条概览执行流并标记 critical。风险来自本功能修改核心概览服务、TDX 映射和前端数据面板,已由定向单测、HTTP/OpenAPI、前端状态回归、构建、浏览器视觉验证和 E2E 覆盖;未发现额外业务模块调用方。第三轮修复前又用 MCP 对 `toTradeDate` 和 `mapTurnoverTrend` 执行符号级影响分析,直接调用方仅在市场概览映射与测试范围。 - API 冒烟:`/breadth`、`/sectors`、`/funds` 均返回 `source=tdx,status=fresh`;完整结果和耗时见 `tdx-smoke-report.md`。 - 家庭 LAN 隧道二次复验:五个市场概览接口均为 `fresh`,指数 3 项、板块 8 项、成交额覆盖 3 个市场且交易日期完整;只保存脱敏统计。 -- 日期对齐与延迟状态修复后,后端 60/60、完整全仓门禁和家庭 LAN 五接口当日 `fresh` 复验通过;第三轮日期真实性与测试时钟修复后,后端增加至 63/63,完整全仓门禁等待本修复提交推送后再次执行。 +- 日期对齐与延迟状态修复后,后端 60/60、完整全仓门禁和家庭 LAN 五接口当日 `fresh` 复验通过;第三轮日期真实性与测试时钟修复后,后端增加至 63/63 且完整全仓门禁通过;第四轮相邻日期修复后,后端增加至 67/67,完整全仓门禁等待本修复提交推送后再次执行。 - 前端单测、typecheck、build、后端 TDX/映射/服务/HTTP/OpenAPI 测试通过。 - `pnpm verify:e2e`:4/4 通过(桌面和移动端各覆盖页面与健康契约)。 - 浏览器桌面与 390px 移动视口验证无横向溢出,视角与引擎切换可用。 -- 三轮独立 Review 均已归档:前两轮数据完整性与状态问题已关闭;第三轮发现的日期真实性/顺序和测试时钟问题已按 TDD 修复,等待再次独立确认。 +- 四轮独立 Review 均已归档:前三轮数据完整性、状态、日期真实性/顺序和测试时钟问题已关闭;第四轮发现的前一根日期校验问题已按 TDD 修复,等待再次独立确认。 - 开发 Schema 按用户确认跳过,沿用 `v0.0.10 captured_empty` 追溯边界,不读取生产库。 diff --git a/docs/features/FEAT-MO-001-market-overview/gitnexus/detected-changes.md b/docs/features/FEAT-MO-001-market-overview/gitnexus/detected-changes.md index 1b81c68..f74a3c3 100644 --- a/docs/features/FEAT-MO-001-market-overview/gitnexus/detected-changes.md +++ b/docs/features/FEAT-MO-001-market-overview/gitnexus/detected-changes.md @@ -10,10 +10,10 @@ npx gitnexus detect-changes --repo "$PWD" --scope staged ## 结果 - 索引:已完成最终增量更新。 -- 暂存文件:12 个文件、31 个符号;GitNexus 自动改写的 `AGENTS.md` 已明确排除。 -- 影响执行流:2 条,CLI 风险:medium。 -- 主要执行流:`Candidates → ToTradeDate` 与 `GetFunds → MapTurnoverTrend`;本轮未修改服务状态策略或跨模块契约。 +- 暂存文件:11 个文件、24 个符号;GitNexus 自动改写的 `AGENTS.md` 已明确排除。 +- 影响执行流:3 条,CLI 风险:medium。 +- 主要执行流:`Candidates → ToNumber`、`Candidates → ToTradeDate` 与 `Candidates → MapDirection`;本轮未修改服务状态策略或跨模块契约。 ## 处置 -本轮 medium 来自日期解析和成交额候选过滤的局部映射变更,不是未知跨业务扩散。已用 TDX 映射/服务测试、类型检查和 `git diff --check` 验证;前一提交已覆盖 HTTP/OpenAPI、前端状态、构建、浏览器视觉和 4/4 E2E,未发现用户、消息、数据库、Redis、RocketMQ、Scheduler、Worker、认证或部署调用方。当前环境已使用 GitNexus MCP 与同版本 CLI 交叉确认,没有将工具限制误判为低风险。 +本轮 medium 来自指数/板块相邻日期比较的局部映射变更,不是未知跨业务扩散。已用 TDX 映射/服务测试、类型检查和 `git diff --check` 验证;前一提交已覆盖 HTTP/OpenAPI、前端状态、构建、浏览器视觉和 4/4 E2E,未发现用户、消息、数据库、Redis、RocketMQ、Scheduler、Worker、认证或部署调用方。当前环境已使用 GitNexus MCP 与同版本 CLI 交叉确认,没有将工具限制误判为低风险。 diff --git a/docs/features/FEAT-MO-001-market-overview/prd.md b/docs/features/FEAT-MO-001-market-overview/prd.md index 93f5656..103b9b1 100644 --- a/docs/features/FEAT-MO-001-market-overview/prd.md +++ b/docs/features/FEAT-MO-001-market-overview/prd.md @@ -56,6 +56,8 @@ A 股使用红涨绿跌。成交额统一展示为亿元,比例明确使用百 TDX 交易日期必须通过真实公历日期校验。成交额当前/前一日金额必须与同索引日期对齐,且前一交易日严格早于当前交易日;不满足条件的市场不参与汇总,也不得抢占最新日期截面。 +指数和板块涨跌幅必须同时具备有效且严格递增的相邻日期。前一根日期非法、重复或晚于当前日期时,指数只展示当前价而不生成涨跌信号,板块不进入强弱排序。 + 首个切片不引入交易日历依赖:指数、一级行业和成交额的最新交易日期与 `Asia/Shanghai` 当前日历日期不同即标记 `delayed`;市场宽度快照缺少日期字段,使用同批核心指数日线作为日期参考。周末和法定休市日会保守显示延迟并同时展示实际交易日期,后续接入权威交易日历后再细化“最新交易日”语义。 ## 状态与异常 diff --git a/docs/features/FEAT-MO-001-market-overview/qa/test-results.md b/docs/features/FEAT-MO-001-market-overview/qa/test-results.md index 2483a9e..510dea0 100644 --- a/docs/features/FEAT-MO-001-market-overview/qa/test-results.md +++ b/docs/features/FEAT-MO-001-market-overview/qa/test-results.md @@ -3,7 +3,7 @@ ## 自动化结果 - 前端 Vitest:8 个文件、20 个测试通过。 -- 后端 Vitest:11 个文件、63 个测试通过;API 启动器 Node 测试 2/2 通过,Schema、治理、边界和部署门禁通过。 +- 后端 Vitest:11 个文件、67 个测试通过;API 启动器 Node 测试 2/2 通过,Schema、治理、边界和部署门禁通过。 - `pnpm verify:e2e`:桌面/移动端页面和健康契约共 4 个测试通过。 - `pnpm typecheck`、`pnpm build`、`pnpm format:check`:通过。 - ESLint:0 error,保留 35 条 Vue 风格 warning,不阻断门禁。 @@ -15,4 +15,5 @@ - 家庭 LAN 真实冒烟中,指数返回 3 项、板块返回 8 项、成交额覆盖 3 个市场且交易日期完整;未保存真实价格或成交额数值。 - 修复日期对齐和延迟状态后再次通过家庭 LAN 真实冒烟,五个接口在当日数据下仍为 `fresh` 且无 warning。 - 第三轮 Review 修复后,非法月份/日期、倒序日期对和日期/金额数组错位均被映射层排除;服务测试统一冻结时钟,不依赖执行当天。 +- 第四轮 Review 修复后,指数前一根非法、重复或倒序日期不再生成涨跌幅,板块对应记录不进入强弱排序。 - 桌面默认视口与 390px 移动视口无横向溢出;视角和引擎切换可用。 diff --git a/docs/features/FEAT-MO-001-market-overview/release-notes.md b/docs/features/FEAT-MO-001-market-overview/release-notes.md index 3ae5cf8..bc963fb 100644 --- a/docs/features/FEAT-MO-001-market-overview/release-notes.md +++ b/docs/features/FEAT-MO-001-market-overview/release-notes.md @@ -7,6 +7,7 @@ - 市场宽度、一级行业强弱和成交额趋势接入真实 TDX 数据,并对空数据、部分数据、日期不一致和延迟状态显示明确边界。 - 指数和板块按最新交易日截面对齐,成交额按最新有效交易日期对汇总;后端根据上海当前日历日期实际生成 `delayed`,市场宽度使用同批核心指数日期参考。 - 拒绝非法公历日期、倒序交易日期以及日期/金额数组错位的 TDX 数据,避免异常市场抢占最新截面或产生时间倒置的成交额趋势。 +- 指数和板块仅使用有效且严格递增的相邻 K 线日期计算涨跌,前一日期非法、重复或倒序时不生成市场强弱信号。 - 明确显示 TDX 来源、快照时间、交易日期、市场覆盖数和模块级错误。 ## 明确不包含 diff --git a/docs/features/FEAT-MO-001-market-overview/review/code-review.md b/docs/features/FEAT-MO-001-market-overview/review/code-review.md index 47430aa..780fa94 100644 --- a/docs/features/FEAT-MO-001-market-overview/review/code-review.md +++ b/docs/features/FEAT-MO-001-market-overview/review/code-review.md @@ -64,4 +64,14 @@ 处置:按 TDD 先得到 3 个预期失败,再增加真实公历日期、日期顺序和数组对齐校验;服务测试套件统一冻结上海日期,并由单个延迟用例显式切换。修复后后端 Vitest `63/63`、启动器 `2/2` 和类型检查通过,等待再次独立 Review。 +## 第四轮独立 Review + +- 审查范围:`49f6be9..ae2a7e0` +- 结论:`Ready to merge: No` +- Critical:无。 +- Important:指数和板块只校验当前 K 线日期,前一根日期非法、重复或倒序时仍计算涨跌;最终治理材料仍处于 `pending`,尚未执行 `governance:finish`。 +- Minor:无新增。 + +处置:按 TDD 先得到 4 个预期失败,再要求前一日期有效且严格早于当前日期;无可比前收盘时指数保留当前价但不生成涨跌,板块不进入强弱排序。后端 Vitest 增加至 `67/67`。治理状态保持 `pending` 是有意的门禁顺序:需本修复再次独立 Review 通过后,才能合法写入 `passed` 并运行 `governance:finish`。 + 最终结论:pending diff --git a/docs/features/FEAT-MO-001-market-overview/review/standards-review.md b/docs/features/FEAT-MO-001-market-overview/review/standards-review.md index bb26fa7..3a7051c 100644 --- a/docs/features/FEAT-MO-001-market-overview/review/standards-review.md +++ b/docs/features/FEAT-MO-001-market-overview/review/standards-review.md @@ -23,5 +23,6 @@ - 二次独立 Review 发现的严格整数、日期对齐和真实 `delayed` 语义已按 TDD 修复;后端新增 9 个断言并完成家庭 LAN 当日行情复验。 - 新鲜度暂按上海当前日历日期保守判断,周末和法定休市日可能显示 `delayed`;该残余边界已同步 PRD、设计和测试计划,不伪装成交易日历已接入。 - 第三轮 Review 的日期真实性、先后顺序、数组对齐和测试时钟问题已按 TDD 修复;需求、设计、测试计划、测试结果、发布说明和开发总结已同步。 +- 第四轮 Review 的前一根日期校验问题已按 TDD 修复;治理结论继续保持 `pending`,等待本次代码修复再次独立确认。 最终结论:pending(等待再次独立代码 Review 与完整门禁) diff --git a/docs/features/FEAT-MO-001-market-overview/test-plan.md b/docs/features/FEAT-MO-001-market-overview/test-plan.md index 5359869..ce1831a 100644 --- a/docs/features/FEAT-MO-001-market-overview/test-plan.md +++ b/docs/features/FEAT-MO-001-market-overview/test-plan.md @@ -13,7 +13,7 @@ - 前端 Vitest、typecheck、build:通过。 - 后端 TDX 客户端、映射、服务、HTTP/OpenAPI 测试:通过。 - `curl` 冒烟:`/breadth`、`/sectors`、`/funds` 均返回 `source=tdx,status=fresh`;分别验证 `UpHome/DownHome` 汇总、一级行业排序和成交额单位转换。 -- 异常映射:覆盖畸形涨跌家数字段(小数、指数、十六进制和超出安全整数)、非法公历日期、空一级行业目录、单根板块 K 线、指数/板块混合交易日、成交额缺市场/交易日倒序/日期金额数组错位/负数/首市场落后,以及三市完整性状态。 +- 异常映射:覆盖畸形涨跌家数字段(小数、指数、十六进制和超出安全整数)、当前及前一根非法公历日期、相邻日期重复/倒序、空一级行业目录、单根板块 K 线、指数/板块混合交易日、成交额缺市场/交易日倒序/日期金额数组错位/负数/首市场落后,以及三市完整性状态。 - 后端状态:覆盖指数、市场宽度、一级行业和成交额旧交易日期的真实 `delayed` 分支;休市日保守延迟属于已记录边界。 - 测试时钟:市场概览服务套件统一冻结到上海 `2026-08-18`,单个 `delayed` 用例显式切换目标日期并在用例后恢复,保证测试不会随自然日期变化。 - 前端状态:覆盖宽度、板块和成交额模块的 `partial`/`delayed` 警告、真实交易日期和成交额零变化中性色。 diff --git a/server/apps/api/src/market-overview/tdx/tdx.mapper.test.ts b/server/apps/api/src/market-overview/tdx/tdx.mapper.test.ts index e8d964a..8428ff6 100644 --- a/server/apps/api/src/market-overview/tdx/tdx.mapper.test.ts +++ b/server/apps/api/src/market-overview/tdx/tdx.mapper.test.ts @@ -61,6 +61,40 @@ describe('TDX 行情映射', () => { expect(result[0]).toMatchObject({ price: 2105, changePercent: null, direction: 'flat' }); }); + it.each([ + ['非法公历日期', ['20269999', '20260818']], + ['重复日期', ['20260818', '20260818']], + ['倒序日期', ['20260819', '20260818']], + ])('前一根 K 线为%s时保留指数当前价但不计算涨跌', (_caseName, dates) => { + const result = mapIndexQuotes( + { + ErrorId: '0', + Value: { + '000001.SH': { + Date: dates, + Open: ['100', '200'], + High: ['100', '200'], + Low: ['100', '200'], + Close: ['100', '200'], + Amount: ['1', '1'], + Volume: ['1', '1'], + }, + }, + }, + [{ code: '000001.SH', name: '上证指数' }], + ); + + expect(result).toEqual([ + expect.objectContaining({ + code: '000001.SH', + price: 200, + changePercent: null, + direction: 'flat', + tradeDate: '20260818', + }), + ]); + }); + it('市场宽度映射只汇总可用的上涨和下跌家数', () => { expect( mapBreadthSnapshots([ @@ -206,6 +240,28 @@ describe('TDX 行情映射', () => { ]); }); + it('板块前一根 K 线日期无效时不进入强弱排序', () => { + expect( + mapSectorQuotes( + { + ErrorId: '0', + Value: { + '881001.SH': { + Date: ['20269999', '20260818'], + Open: ['100', '200'], + High: ['100', '200'], + Low: ['100', '200'], + Close: ['100', '200'], + Amount: ['1', '1'], + Volume: ['1', '1'], + }, + }, + }, + [{ code: '881001.SH', name: '畸形日期行业' }], + ), + ).toEqual([]); + }); + it('板块混合交易日时只保留最新交易日,避免把不同日期混排', () => { const series = (dates: string[], close: string[]) => ({ Date: dates, diff --git a/server/apps/api/src/market-overview/tdx/tdx.mapper.ts b/server/apps/api/src/market-overview/tdx/tdx.mapper.ts index 372a3ae..1f504b6 100644 --- a/server/apps/api/src/market-overview/tdx/tdx.mapper.ts +++ b/server/apps/api/src/market-overview/tdx/tdx.mapper.ts @@ -60,9 +60,13 @@ function mapIndexQuote(series: TdxKlineSeries, definition: IndexDefinition): Ind const previousPrice = toNumber(series.Close[lastIndex - 1]); const tradeDate = toTradeDate(series.Date[lastIndex]); if (price === null || !tradeDate) return null; + const previousTradeDate = toTradeDate(series.Date[lastIndex - 1]); const changePercent = - previousPrice === null || previousPrice === 0 + previousPrice === null || + previousPrice === 0 || + previousTradeDate === null || + previousTradeDate >= tradeDate ? null : Math.round(((price - previousPrice) / previousPrice) * 100 * 100) / 100;