From fd34df9b65c4af5d0fb96807114ee3decf8e0955 Mon Sep 17 00:00:00 2001 From: jesse Date: Wed, 28 Oct 2026 18:39:20 +0800 Subject: [PATCH] fix: rectification recap no longer repeats a date the event already carries (BUG-1496, issue #161) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Staging showed 「记下了:2016-09 2016 年 9 月上大学。」. evidenceRecapLine only recognised an unspaced 「2016年」 as the phrase's own date, so a spaced year or an ISO / slash date got the label in front again. It now recognises `\d{4}\s*年` and `\d{4}[-/.]\d{1,2}`; phrases without a date still get the label. Regression test fails before the fix and passes after. Co-Authored-By: Claude Opus 5.5 --- CHANGELOG.md | 5 +++++ docs/BUG_HISTORY.md | 15 +++++++++++++++ .../rectification-agentic/v9/host-fallback.ts | 7 ++++++- .../tests/rectification-host-fallback.test.ts | 16 ++++++++++++++++ 4 files changed, 42 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 937493a3..858a1b7c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,10 @@ # 印度占星 Skill 更新日志 +## 2026-10-28 — 生时校正复述不再把日期说两遍 + +- 说「2016 年 9 月上大学」这类自带日期的事,回复以前会写成「2016-09 2016 年 9 月上大学」,现在只说一遍。 +- Skill 版本不变(BUG-1496,issue #161)。 + ## 2026-10-28 — 生时校正对话说得更清楚 - 开场那句会说明是按什么时间开始找的(出生记录、你填的大概时间,或你给的时间段)。 diff --git a/docs/BUG_HISTORY.md b/docs/BUG_HISTORY.md index 2e3f58d9..163a3cd6 100644 --- a/docs/BUG_HISTORY.md +++ b/docs/BUG_HISTORY.md @@ -20447,3 +20447,18 @@ - 关联:BUG-1494。 - 复发自:无。 - 修复版本:分支 `codex/upstream-sync9b-20261028`。 + +## BUG-1496 | 生时校正复述把日期说了两遍(「2016-09 2016 年 9 月上大学」) + +- 状态:resolved(代码 + 回归测试;待部署后 staging 复测) +- 首次发现 / 最近更新:2026-10-28 / 2026-10-28(issue #161;issue #148 验收时在 staging 实测发现) +- 影响面:`frontend/src/lib/rectification-agentic/v9/host-fallback.ts::evidenceRecapLine`。所有证据轮复述都经过它:本轮入账复述(`acceptedRecapLines`)、每条回复的入账清单(`recordedEvidenceByTurn`)、收束卡「我说错了,改一条」的事件列表(`fixableEvidenceLines`)。 +- 现象:用户说「2016 年 9 月上大学。」,回复「记下了:2016-09 2016 年 9 月上大学。」 +- 触发条件:事件说法里自带日期,且年份和「年」之间有空格,或写成 ISO / 斜杠日期。 +- 根因:拼复述时,判断「事件说法已带日期」的规则只认不带空格的 `\d{4}年`(BUG-1136 加的拼接规则),漏掉「2016 年」「2016-09」「2016/9」,于是又在前面加了一次日期标签。 +- 修复:改为识别 `\d{4}\s*年` 或 `\d{4}[-/.]\d{1,2}`;事件说法里有这样的日期就只用事件说法,没带日期的照旧加标签。 +- 验证:`tests/rectification-host-fallback.test.ts` 新增「the recap does not repeat a date the event phrase already carries」,修复前红、修复后绿;覆盖带空格年份、不带空格、ISO、斜杠四种写法,以及不带日期、中文标签、非年份数字(「600 分」)三种不变的情况。`rectification-message-cleanup-20261001`、`rectification-grounding-20260927`、`rectification-event-date-bounds-20261002` 同时通过。 +- 防复发:上面那条测试固定了「带空格的年份」这一例。拼复述只在 `evidenceRecapLine` 一处做。 +- 关联:BUG-1136。 +- 复发自:无。 +- 修复版本:分支 `codex/rectification-recap-date-dup-20261028`。 diff --git a/frontend/src/lib/rectification-agentic/v9/host-fallback.ts b/frontend/src/lib/rectification-agentic/v9/host-fallback.ts index 6ea88011..d1352a9f 100644 --- a/frontend/src/lib/rectification-agentic/v9/host-fallback.ts +++ b/frontend/src/lib/rectification-agentic/v9/host-fallback.ts @@ -71,11 +71,16 @@ export function lastCompletedPublicTool( return last; } +const PHRASE_HAS_OWN_DATE = /\d{4}\s*年|\d{4}[-/.]\d{1,2}/; + export function evidenceRecapLine(item: HostFallbackRecap): string { const label = typeof item.display_date_label === "string" ? item.display_date_label.trim() : ""; const phrase = typeof item.event_phrase === "string" ? item.event_phrase.trim() : ""; if (!phrase) return label; - if (/\d{4}年/.test(phrase) || (label.length > 0 && phrase.startsWith(label))) { + // BUG-1496: the phrase already carries its own date, so the label would say + // it twice (「2016-09 2016 年 9 月上大学」). Spaced years and ISO / slash dates + // count too, not only an unspaced 「2016年」. + if (PHRASE_HAS_OWN_DATE.test(phrase) || (label.length > 0 && phrase.startsWith(label))) { return phrase; } // BUG-1136: the recap is now the server's on every evidence turn; a diff --git a/frontend/tests/rectification-host-fallback.test.ts b/frontend/tests/rectification-host-fallback.test.ts index b41d26d9..6caf447c 100644 --- a/frontend/tests/rectification-host-fallback.test.ts +++ b/frontend/tests/rectification-host-fallback.test.ts @@ -104,3 +104,19 @@ test("case snapshot receipts expose host_fallback origin from the durable phase" assert.match(route, /answer_origin: "host_fallback"/); assert.match(route, /answer\.host_fallback/); }); + +// BUG-1496: a phrase that already carries its date must not get the label in +// front again. Staging showed 「记下了:2016-09 2016 年 9 月上大学。」. +test("the recap does not repeat a date the event phrase already carries", async () => { + const { evidenceRecapLine } = await import("../src/lib/rectification-agentic/v9/host-fallback.ts"); + assert.equal(evidenceRecapLine({ display_date_label: "2016-09", event_phrase: "2016 年 9 月上大学" }), "2016 年 9 月上大学"); + assert.equal(evidenceRecapLine({ display_date_label: "2016-09", event_phrase: "2016年9月上大学" }), "2016年9月上大学"); + assert.equal(evidenceRecapLine({ display_date_label: "2024-04-07", event_phrase: "2024-04-07 入职" }), "2024-04-07 入职"); + assert.equal(evidenceRecapLine({ display_date_label: "2016-09", event_phrase: "2016/9 上大学" }), "2016/9 上大学"); + // Unchanged: a phrase without its own date still gets the label. + assert.equal(evidenceRecapLine({ display_date_label: "2016-09", event_phrase: "上大学" }), "2016-09 上大学"); + assert.equal(evidenceRecapLine({ display_date_label: "2016年", event_phrase: "入学" }), "2016年入学"); + assert.equal(evidenceRecapLine({ display_date_label: "2016 年 9 月", event_phrase: "去北京工作" }), "2016 年 9 月去北京工作"); + // A number that is not a year is not a date. + assert.equal(evidenceRecapLine({ display_date_label: "2016-09", event_phrase: "考了 600 分" }), "2016-09 考了 600 分"); +});