diff --git a/docs/BUG_HISTORY.md b/docs/BUG_HISTORY.md index 70645c5b..6bb16234 100644 --- a/docs/BUG_HISTORY.md +++ b/docs/BUG_HISTORY.md @@ -14562,3 +14562,18 @@ - 防复发:守卫测试锁住「本人出生资料至少有一个编辑入口,且走共享的 `saveSelfProfile`」;取代某个入口的改动必须在任务书里写明新入口。 - 相关记录:BUG-1030/1039(星盘档案)、BUG-018(账户出生资料声明与状态同写)。 - 修复版本:分支 `codex/self-edit-avatar-menu-20260928`,未部署。 + +## BUG-1082 | 天空封面底部的出生城市对中国出生地一律印不出来 + +- 状态:resolved(代码与回归测试;真机待部署后按 `docs/testing/birth-sky-cover-checklist.md` 城市条目复核) +- 首次发现 / 最近更新:2026-09-29 / 2026-09-29 +- 影响面:星盘页「那一刻的天空」图片、报告封面、首次建盘过场底部的 caption(`TASK-birth-sky-polish-20260928` P2,staging `adbb2054` 起)。 +- 用户现象:中国出生地只印「日期 时刻」,没有「· 城市」。 +- 触发条件:出生地来自中国地点库(存储标签形如 `中国 · 省 · 市 · 区`)。 +- 根因:`lib/birth-sky/caption.ts` `birthSkyCity` 从第一段开始按「省 / 直辖市」识别,而存储标签由 `geoapify-location-service.ts` 生成、第一段固定是「中国」,于是每次都判为「取不到可靠城市」。测试夹具手写成不带「中国」的 `省 · 市 · 区`,与真实存储格式不一致,所以单测与样张都显示了城市。执行方在下一单(`TASK-self-edit-avatar-menu-20260928`)读 `people-archive-view.ts` 时发现;Claude 验收 polish 时也只看了夹具样张,未对照真实标签格式。 +- 修复:识别前先去掉开头的「中国」段,其余规则不变。 +- 验证:`frontend/tests/birth-sky-caption.test.ts` 新增「stored China labels lead with 「中国 · 」」一条(省市区、省市、直辖市、只到省、只有「中国」、完整 caption);`birth-sky-caption` / `birth-sky-route` / `birth-sky-draw` 22 pass。 +- 防复发:解析地点标签的测试夹具必须取自 `geoapify-location-service.ts` 的真实生成格式(AGENTS §7.4 golden 原则);新增的地名解析先对照 `people-archive-view.ts` `peopleCityLabel` 已有做法。 +- 相关记录:BUG-1080 +- 复发自:无 +- 修复版本:`codex/self-edit-avatar-menu-20260928` 第 4 个提交 diff --git a/frontend/src/lib/birth-sky/caption.ts b/frontend/src/lib/birth-sky/caption.ts index 1e1a9519..065fda59 100644 --- a/frontend/src/lib/birth-sky/caption.ts +++ b/frontend/src/lib/birth-sky/caption.ts @@ -26,6 +26,7 @@ const HAN = /[㐀-鿿]/; /** * City from a stored birth place label. Rules, most specific first: * + * 0. A leading 「中国」 segment (how stored labels start) is dropped first. * 1. Municipalities and SARs (`北京市 · 朝阳区`, `香港特别行政区`) → `北京`, `香港`. * 2. Chinese labels `省 · 市 · 区` → the second segment when it ends in 「市」, * without the 「市」 (`河北省 · 邯郸市 · 峰峰矿区` → `邯郸`). Prefectures that are @@ -36,7 +37,9 @@ const HAN = /[㐀-鿿]/; */ export function birthSkyCity(label: string | null | undefined): string | null { if (typeof label !== "string") return null; - const segments = label.split(SEPARATOR).map((segment) => segment.trim()).filter(Boolean); + const all = label.split(SEPARATOR).map((segment) => segment.trim()).filter(Boolean); + // Stored China labels lead with 「中国 · 」 (geoapify-location-service); the rules below start at the province (BUG-1082). + const segments = all[0] === "中国" ? all.slice(1) : all; if (segments.length === 0) return null; const [first, second, third] = segments; if (CITY_PROVINCES[first]) return CITY_PROVINCES[first]; diff --git a/frontend/tests/birth-sky-caption.test.ts b/frontend/tests/birth-sky-caption.test.ts index 793d8e1f..4302ec34 100644 --- a/frontend/tests/birth-sky-caption.test.ts +++ b/frontend/tests/birth-sky-caption.test.ts @@ -12,6 +12,17 @@ test("China 省 · 市 · 区 labels give the prefecture city without 「市」" assert.equal(birthSkyCity("广西壮族自治区 · 桂林市 · 秀峰区"), "桂林"); }); +// BUG-1082: stored labels are built as 「中国 · 省 · 市 · 区」 (geoapify-location-service.ts); +// the fixtures above lacked the prefix, so real Chinese birthplaces printed no city. +test("stored China labels lead with 「中国 · 」 and still give the city", () => { + assert.equal(birthSkyCity("中国 · 河北省 · 邯郸市 · 峰峰矿区"), "邯郸"); + assert.equal(birthSkyCity("中国 · 浙江省 · 杭州市"), "杭州"); + assert.equal(birthSkyCity("中国 · 北京市 · 北京市 · 朝阳区"), "北京"); + assert.equal(birthSkyCity("中国 · 浙江省"), null); + assert.equal(birthSkyCity("中国"), null); + assert.equal(birthSkyCaption({ date: "1994-05-18", time: "07:40", placeLabel: "中国 · 浙江省 · 杭州市 · 西湖区" }), "1994年5月18日 07:40 · 杭州"); +}); + test("municipalities and SARs use their own name", () => { assert.equal(birthSkyCity("北京市 · 朝阳区"), "北京"); assert.equal(birthSkyCity("上海市"), "上海");