diff --git a/docs/tasks/README.md b/docs/tasks/README.md index 8c91fbe7..728f5e60 100644 --- a/docs/tasks/README.md +++ b/docs/tasks/README.md @@ -221,7 +221,9 @@ | `TASK-rectification-house-lord-gochara-research-20260913.md` | `PROGRESS-rectification-house-lord-gochara-research-20260913.md` | 研究单:宫主触发与木星/土星过运(合冲本命宫主、罗睺紧密合、年精度、用于 block 选上升)四种放宽,20 例公开 AA 离线量 block 层与 minute 层两组指标;引擎里已有宫主/功能吉凶/受控过运,只量缺的四条 | 待验收(无收益,关闭;不立实现单) | `codex/rectification-house-lord-gochara-research-20260913` | -| `TASK-qizheng-native-chart-20260915.md` | `PROGRESS-qizheng-native-chart-20260915.md` | 接入原生七政四余排盘:vendored `stem-branch` 0.8.0(Apache-2.0)归档 + API 镜像 Node runtime + 适配器(计都派别与宿度坐标系参数化、boundary 按实测重写)+ `/api/qizheng` 薄注册。实证三条:四柱时柱按 UTC 算(BUG-700,本轮不修不调用)、`ketuMode` 写死未暴露(BUG-701)、boundary 把空神煞与未闭合庙旺说成已生成(BUG-702)。前端第五个 Tab 另出单,串行在本单之后 | 待领取 | — | +| `TASK-qizheng-native-chart-20260915.md` | `PROGRESS-qizheng-native-chart-20260915.md` | **后端单(独占 `scripts/jyotish_api_server.py`)**:vendored `stem-branch` 0.8.0(Apache-2.0)归档 + API 镜像 Node runtime + 七政适配器(计都派别与宿度坐标系参数化、boundary 按实测重写)+ 三个只读端点 `/api/qizheng`、`/api/western`、`/api/ephemeris_events`。实证三条:四柱时柱按 UTC 算(BUG-700,本轮不修不调用)、`ketuMode` 写死未暴露(BUG-701)、boundary 把空神煞与未闭合庙旺说成已生成(BUG-702)。BUG 段 700–703 | 待领取 | — | +| `TASK-chart-page-20260915.md` | `PROGRESS-chart-page-20260915.md` | **前端单(独占 `app-sidebar.tsx`,同时加星盘与星历两个入口)**:P0 只读星盘页,五个 Tab(星盘 / 基础信息 / 大运 / 西洋盘 / 七政四余),中宫排盘参数卡,三套坐标系各自标注且禁止互相换算。不扣点不调模型不出 spinner;不碰 `page.tsx`(1951/2000);**不搬 `vedic-chart-svg.tsx`**(rectification-board 也在用)。BUG 段 704–706 | 待领取 | — | +| `TASK-ephemeris-page-20260915.md` | `PROGRESS-ephemeris-page-20260915.md` | **前端单**:P1 星历页,今日五要素 + 当日行运(相对本命宫位)+ 未来九十天换座与停滞,底部「带这天去提问」出口。页面不得出现任何运势判断。含实证缺陷:panchanga 写死 Lahiri 与账户 Raman 分裂(BUG-707,只标注不修)。侧边栏入口由 chart-page 单交付。BUG 段 707–709 | 待领取 | — | ## 命名与归档 diff --git a/docs/tasks/TASK-chart-page-20260915.md b/docs/tasks/TASK-chart-page-20260915.md new file mode 100644 index 00000000..c214e8a6 --- /dev/null +++ b/docs/tasks/TASK-chart-page-20260915.md @@ -0,0 +1,151 @@ +# TASK-chart-page-20260915 · P0 星盘事实页(五个 Tab) + +## 基线 + +- 代码基线:`origin/staging` = **`2d7698ea`**(文档树基线 `b3ccef4c`,纯文档提交,代码未变)。 +- 并行关系:本单与 `TASK-qizheng-native-chart-20260915`(后端)、`TASK-ephemeris-page-20260915`(前端星历页)**同期并行**。三份单没有共享代码文件,见「文件归属」。 +- 原型:会话内已交付可点原型(五个 Tab 的完整形态、中宫排盘参数卡、三套坐标系边界文案、桌面 1440 与手机 390 两套)。**原型里的数据全部是实跑引擎输出**,不是编的;实现时以本单文字为准,原型作为视觉与信息层级参照。 +- Skill 版本 `6.9.16`,本单**不 bump**(不新增解读口径,只把已算出的事实露出来)。 + +## 为什么要有这一页 + +当前产品里,用户填完出生资料**看不见自己的盘**: + +- `VedicChartSvg` 在 `frontend/src/components/personal-report/vedic-chart-svg.tsx`,只被三处引用——`personal-report-document-view.tsx`、`personal-report-markdown-view.tsx`(用其中的 `NorthIndianChartSvg`)、`rectification-board.tsx`。也就是说,盘面只在**已生成报告**或**校正进行中**才出现。 +- 侧边栏「星盘资料」是 `ChartLibraryPanel`(`frontend/src/components/chart-library-panel.tsx`),只管出生资料的增删改查,**不画盘**。 +- 结果:第一次看见自己的盘必须先花点数、等模型。 + +而引擎侧 `/api/chart`、`/api/dasha/chara`、`/api/nakshatra_full` 早就在;星宿与 Pada 在 `scripts/dasha_analyzer.py` 已经算出来。**这一页不新增任何计算,只是把已有结果摆出来。** + +## 决策记录 + +产品负责人在 2026-09-15 的对话中授权: + +1. 新建一个**不消耗点数、不调模型、打开即有**的只读星盘页。 +2. Tab 顺序定为 **星盘 / 基础信息 / 大运 / 西洋盘 / 七政四余**。 +3. **体系分在 Tab 这一层**;分盘 chip(D1–D30)只在印度体系内部使用。不得把西洋盘或七政四余做成第 13 个 chip。 +4. **盘的中宫放「排盘参数卡」**:钟表时间与校正状态、出生地与经纬、岁差与交点模式、上升与月亮星宿、当前大运分运、引擎名。手机上放不下,只留岁差两行,完整参数改成盘下方一张卡。 +5. **三套坐标系必须各自在中宫写明自己是什么,并显式点名不能互相换算**,也不得把两边结论叠加成「双重印证」。 +6. 「传统象征」词条层可以做,但必须带边界句「是词条式释义,不是对你个人的判断」,且**不得出现任何运势判断**。 + +### 本单推翻的一处早期说法 + +立单核对时发现:`VedicChartSvg` 并非「只被 personal-report 引用」,`rectification-board.tsx` 也在用。**因此本单不搬这个文件。** 新页面直接从现有路径 `@/components/personal-report/vedic-chart-svg` 引入。搬家会同时碰校正热点文件,与并行的校正轮次冲突;若将来要搬,单独一轮只做搬家。 + +## 硬红线 + +1. `frontend/src/app/page.tsx` 在 `origin/staging` 上是 **1951 行**,AGENTS.md §6 定了 2000 行上限且「不得再增长」。**本页必须是独立 route,一行都不许加进 `page.tsx`。** +2. **不得搬动 `vedic-chart-svg.tsx`**,见上。也不得改动它的导出签名——三个既有调用点必须零改动。 +3. **不得扣点、不得调模型。** 新增的 BFF 路由**不许 import** `@/lib/consultation-billing` 或任何 mastra agent。这一页出现 spinner / 骨架 / 「正在加载」即为不通过(AGENTS.md §6:揭幕后不得出现加载动画,流式生成中除外)。 +4. `./node_modules/.bin/tsc --noEmit` 通过;`npm run lint` **0 error**。 +5. `next build` 后 `/` 仍是 `○ Static`;首屏 gzip 变化在 ±2% 内,超出要在进度记录里给出原因。**新 route 自身不要求 Static**(它读账户数据)。 +6. 测试总数不得低于开工时 `origin/staging` 的实测。改任何既有断言必须写「原值 / 新值 / 原因」三栏。 +7. 不改数据库结构;本单不得顺带动迁移。 +8. 改 UI 的同一提交内更新 `frontend/DESIGN.md`;新文案先对照 `frontend/docs/VOICE.md`。 +9. **一行后端代码都不许改**,特别是 `scripts/jyotish_api_server.py`——它归 `TASK-qizheng-native-chart-20260915` 独占。 + +## 文件归属(并行前提) + +**本单拥有**: + +- `frontend/src/app/chart/**`(新 route) +- `frontend/src/app/api/chart-view/**`(新 BFF) +- `frontend/src/components/chart-page/**`(新组件目录) +- **`frontend/src/components/app-sidebar.tsx`** —— 本单独占。**本单同时加「星盘」与「星历」两个入口**,星历页那一单不碰这个文件。 +- `frontend/src/lib/chart-view-*.ts` + +**本单不许碰**:`scripts/**`、`deploy/**`、`vendor/**`、`frontend/src/app/ephemeris/**`、`frontend/src/app/api/ephemeris/**`、`frontend/src/lib/ephemeris-*.ts`、`frontend/src/components/personal-report/**`、`frontend/src/components/rectification-*`。 + +**三方共享、只许追加各自小节**:`docs/BUG_HISTORY.md`、`CHANGELOG.md`、`frontend/DESIGN.md`。合入顺序 **qizheng → chart-page → ephemeris-page**;rebase 时按各自小节重放,不得覆盖对方。 + +## 任务分解 + +### 任务 1 · BFF 路由 `frontend/src/app/api/chart-view/route.ts` + +按既有路由的写法(参照 `frontend/src/app/api/daily-starlanguage/route.ts` 的骨架:`export const runtime = "nodejs"`、`maxDuration`、`jyotishApiBase = process.env.JYOTISH_API_BASE ?? "http://127.0.0.1:5200"`、`createServerSupabaseClient`、`ACCOUNT_BIRTH_SELECT` + `globalBirthProfileFromAccountRow`、`consumeUserRequestRateLimit`),但: + +- **不 import** `consultation-billing`、不 import `@/mastra`。 +- 出生资料只从**服务端自有资料**读,不接受前端传入的出生参数(沿用 `server-owned-birth-profile` 的既有边界)。 +- 未登录返回 `401`。 +- 上游调用:`/api/chart`(主)+ `/api/dasha/chara`(Chara 大运)。 + - **坑,必须照做**:`/api/dasha` 单独调会忽略月亮黄经、回默认 Ketu balance;大运要从 `/api/chart` 响应的 `dasha` 段取。`/api/dasha/chara` 与 `/api/varga_full` 必须把 `planets`、`ascendant`、`houses` 一起传进去,否则返回 `planets must include longitude data`。 +- 西洋盘与七政四余走 `/api/western`、`/api/qizheng`(由 qizheng 单交付)。**这两个端点未上线时,本路由对应字段返回 `unavailable`,不得抛错、不得让整页失败。** +- 响应是一个窄合同(新增 `frontend/src/lib/chart-view-contract.ts`,zod 校验),只含页面要用的字段,不透传引擎全量响应。 + +**验收标准** + +- 新增 `frontend/tests/chart-view-route.test.ts`:未登录 401;资料不全返回结构化提示而非 500;`/api/western` 或 `/api/qizheng` 不可用时该字段为 `unavailable` 且其余 Tab 正常;响应通过 zod 校验。 +- fixture 必须来自**真实引擎响应**(golden),不得手造形状(AGENTS.md §7.4)。 +- 全程零扣点:测试里断言未调用任何计费路径。 + +### 任务 2 · 页面与五个 Tab `frontend/src/app/chart/` + +页面结构按原型:页眉(眉标「直接计算 · 打开即有 · 不消耗点数」+ 标题 + 出生资料一行)→ 五个 Tab → 内容区。 + +- **星盘**:D1–D30 分盘 chip(一行可换行)+ 盘面 + 右侧行星位置表(星体 / 星座与度数 / 宫位 / 状态)。盘面复用 `VedicChartSvg`(**从现有路径引入,不搬文件**)。 +- **基础信息**:每个星体一张卡——D1 落座与宫位、本命星宿 + 第几足 + 星宿主、D9 落座、传统象征一行(灰字 + 顶部边界卡)。 +- **大运**:Vimshottari 与 Chara 两条轨**并列**,当前段高亮。底部固定一句:只有两条同时指向同一段时间才算证据,单轨命中降一级置信度。 + - Chara 的正式名称是 **Chara Dasha(kn_rao 变体)**,引擎 `method` 字段就这么写的,**不要写成 Narayana**。 + - 分运(antardasha)若从引擎拿不到,可以按标准比例从大运边界推,但**必须在界面上注明「按标准比例推出,不是引擎单独返回的字段」**。 +- **西洋盘**:圆盘(外圈黄道十二宫、Placidus 不等宽宫位、上升在左、度数逆时针)+ 元素模式分布 + 主要相位表。右栏第一张必须是边界卡。 +- **七政四余**:十二地支宫方格(巳午未申在上,寅丑子亥在下)+ 人事宫 + 所辖宿 + 十一曜宿度表。必须出现三条口径:宿度自角宿起算、计都派别、庙旺未闭合。 + +**中宫排盘参数卡**:桌面放六行;**手机上盘宽只有约 318px,中宫塞不下**,只留岁差两行,完整参数改成盘下方一张卡。原型里这两种形态都画了。 + +**验收标准** + +- `tsc --noEmit` 0 错;`npm run lint` 0 error。 +- 新增组件测试:五个 Tab 都能切换;`unavailable` 的 Tab 渲染静态说明而不是报错;手机宽度下中宫只有两行且完整参数卡出现在盘下方。 +- 视觉:`frontend/DESIGN.md` 追加「只读星盘页」小节,写清 Tab 体系边界、中宫参数卡、三套坐标系的各自标注。 +- 文案对照 `frontend/docs/VOICE.md`。 +- **触摸目标 ≥ 44px**;正文 ≥ 14px,12–13px 只给短标签与元信息(DESIGN.md §3)。盘内 SVG 文字按缩放后实际像素计,不得低于 12px。 + +### 任务 3 · 侧边栏入口(本单独占) + +- `frontend/src/components/app-sidebar.tsx`(367 行)在「新建对话」之后、「我的报告」之前新增两个入口:**星盘**、**星历**。 +- 两个入口都要有折叠态图标(DESIGN.md「Sidebar shell · Collapsed content」要求折叠时仍可达)。 +- 当前项使用既有的白玻璃面 + 2px 深棕标记,不得新造一套选中态。 +- **星历入口现在会指向一个尚未存在的 route**(由 `TASK-ephemeris-page-20260915` 交付)。本单先建一个最小占位页 `frontend/src/app/ephemeris/page.tsx`?**不。** 本单**不创建** ephemeris 目录(归属对方)。做法:星历入口在对方 route 落地前指向 `/ephemeris`,本地开发会 404,这是预期的;在进度记录里写明,并在 PR 描述里注明合入顺序。 + +**验收标准** + +- 侧边栏三态(桌面展开 288px / 平板折叠 64px / 手机抽屉)下两个入口都可达。 +- `frontend/tests/touch-target-contract.test.ts` 通过。 +- 不得改动既有入口的顺序与文案。 + +## 让步顺序 + +1. 先保证 任务 1 + 任务 2 的前三个 Tab(星盘 / 基础信息 / 大运)。这三个不依赖任何新端点,**可以完全独立交付**。 +2. 西洋盘、七政四余两个 Tab 可以只落 `unavailable` 形态,等对方端点上线再点亮。 +3. 「传统象征」词条层可以延后;延后时基础信息卡只保留事实三行。 +4. **不可让步**:不碰 `page.tsx`、不搬 `vedic-chart-svg.tsx`、不扣点不调模型、无 spinner、三套坐标系的边界标注、`/` 仍 Static。 + +## 开工前置命令 + +```bash +cd /workspace/Jyotisha +git status -sb +git fetch origin --prune +git worktree add -b codex/chart-page-20260915 .worktrees/chart-page-20260915 origin/staging +cd .worktrees/chart-page-20260915/frontend +npm ci +./node_modules/.bin/tsc --noEmit # 记下开工基线 +npm run lint +npm test 2>&1 | tail -20 # 记下测试总数,验收时不得低于此数 +npx next build 2>&1 | grep -E "^[│├└]| / " | head # 记下 `/` 的 Static 标记与首屏 gzip +wc -l src/app/page.tsx # 应为 1951 +``` + +无 Docker 时 `npm run test:db` 的失败清单要与基线逐条比对,写进 `BLOCKED.md`,不得写成「通过」。 + +## BUG 编号起点 + +`origin/staging` 上 `docs/BUG_HISTORY.md` 当前最大号 **BUG-699**。三份并行单预分配:qizheng 单 700–703、**本单 704–706**、ephemeris-page 单 707–709。本单是新功能,正常情况下**不需要开 BUG 号**;只有实现中发现既有缺陷才用,用满要通知另外两单整体后移。 + +## 进度与记录 + +- 进度记录:`docs/tasks/PROGRESS-chart-page-20260915.md`,本单状态板一行。 +- 环境缺口写 `BLOCKED.md`(预期:无 Docker → `test:db` 阻塞;无登录态与 Chrome → 浏览器级走查留给 `docs/testing/`)。 +- 真人验收清单写进 `docs/testing/`:五个 Tab 切换、手机宽度中宫两行、折叠侧边栏两个入口可达、未登录跳转。 +- `CHANGELOG.md` 写用户可感知的变化,注明 Skill 版本**未 bump**。 +- 索引:`docs/tasks/README.md` 追加本单。 diff --git a/docs/tasks/TASK-ephemeris-page-20260915.md b/docs/tasks/TASK-ephemeris-page-20260915.md new file mode 100644 index 00000000..9bec069a --- /dev/null +++ b/docs/tasks/TASK-ephemeris-page-20260915.md @@ -0,0 +1,157 @@ +# TASK-ephemeris-page-20260915 · P1 星历页 + +## 基线 + +- 代码基线:`origin/staging` = **`2d7698ea`**(文档树基线 `b3ccef4c`,纯文档提交,代码未变)。 +- 并行关系:本单与 `TASK-qizheng-native-chart-20260915`(后端)、`TASK-chart-page-20260915`(前端星盘页)**同期并行**。三份单没有共享代码文件,见「文件归属」。 +- 原型:会话内已交付可点原型(桌面 1440 与手机 390 两套,日期箭头可点,三天数据全部实跑)。实现以本单文字为准,原型作为视觉与信息层级参照。 +- Skill 版本 `6.9.16`,本单**不 bump**。 + +## 为什么要有这一页 + +引擎侧已经算得出、但产品层一条都没露的东西: + +| 层 | 引擎有没有 | 现在露没露 | +| --- | --- | --- | +| Panchanga 五要素 | 有,`/api/panchanga_range` | **没露**,只作为模型的输入 | +| 行运位置 | 有,`/api/chart` 传任意日期即可 | **没露** | +| 换座与顺逆停滞 | 需要新端点(由 qizheng 单交付 `/api/ephemeris_events`) | 从来没做 | +| 「今日」 | 有,`/api/daily-starlanguage` | 露了,但**是模型生成的三句话**,45 秒超时、烧点数 | + +也就是说,连「今天的五要素是什么」这种纯查表的东西,现在都要让模型去说一遍。这一页把它们直接摆出来:**秒开、零点数、不调模型**。 + +## 决策记录 + +产品负责人在 2026-09-15 的对话中授权: + +1. 新建一个**不消耗点数、不调模型**的只读星历页。 +2. 内容三段:**今日五要素**(Panchanga)、**当日行运**(含相对本命上升的宫位)、**未来九十天换座与停滞**。 +3. 日期可前后切换。 +4. **这一页是天象本身,不是对用户的判断**。页面不得出现任何运势结论。 +5. 页面底部固定一个出口:**「带这天去提问」**,把当前日期带进对话。免费层的职责是把人送进对话,不是死胡同。 +6. 不做竞品那种「每月取一个采样点 + 套模板」的年度报告——我们已经有真报告,加一个糊出来的月历只会和它打架。 + +## 硬红线 + +1. `frontend/src/app/page.tsx` 在 `origin/staging` 上 **1951 行**,上限 2000 且不得再增长。**本页必须是独立 route。** +2. **不得扣点、不得调模型。** 新增 BFF **不许 import** `@/lib/consultation-billing`、不许 import `@/mastra`。这一页出现 spinner / 骨架 / 「正在加载」即为不通过。 +3. **不得做任何运势解释。** 事件列表只写「某星 进入 某座」「某星 停滞转顺 · 度数」,不得附加一个字的含义。Yoga / Karana 的吉凶字段来自引擎,可以照搬标签,但**不得展开成建议**。 +4. **不得做三套坐标系之间的换算或叠加。** +5. `tsc --noEmit` 通过;`npm run lint` **0 error**;`next build` 后 `/` 仍 `○ Static`;首屏 gzip ±2%。 +6. 测试总数不得低于开工时实测;改既有断言要写「原值 / 新值 / 原因」三栏。 +7. 不改数据库结构、不动迁移。 +8. 改 UI 的同一提交内更新 `frontend/DESIGN.md`;文案对照 `frontend/docs/VOICE.md`。 +9. **一行后端代码都不许改**(`scripts/jyotish_api_server.py` 归 qizheng 单独占);**不许改 `frontend/src/components/app-sidebar.tsx`**(归 chart-page 单独占)。 + +## 文件归属(并行前提) + +**本单拥有**:`frontend/src/app/ephemeris/**`、`frontend/src/app/api/ephemeris/**`、`frontend/src/components/ephemeris/**`、`frontend/src/lib/ephemeris-*.ts`。 + +**本单不许碰**:`scripts/**`、`deploy/**`、`vendor/**`、`frontend/src/components/app-sidebar.tsx`、`frontend/src/app/chart/**`、`frontend/src/app/api/chart-view/**`、`frontend/src/components/personal-report/**`、`frontend/src/components/rectification-*`。 + +**三方共享、只许追加各自小节**:`docs/BUG_HISTORY.md`、`CHANGELOG.md`、`frontend/DESIGN.md`。合入顺序 **qizheng → chart-page → ephemeris-page**;本单最后合,rebase 时按各自小节重放,不得覆盖对方。 + +**侧边栏入口由 `TASK-chart-page-20260915` 交付**(它会同时加「星盘」「星历」两个入口)。本单只交付 `/ephemeris` 这个 route 本身;在对方合入前,页面通过直接访问 URL 验收。 + +## 任务分解 + +### 任务 1 · BFF 路由 `frontend/src/app/api/ephemeris/route.ts` + +骨架参照 `frontend/src/app/api/daily-starlanguage/route.ts`(`runtime = "nodejs"`、`maxDuration`、`jyotishApiBase`、`createServerSupabaseClient`、`ACCOUNT_BIRTH_SELECT` + `globalBirthProfileFromAccountRow`、`consumeUserRequestRateLimit`),但**不 import** 计费与 mastra。 + +- 入参:目标日期(默认今天,按用户时区)。出生资料只从服务端自有资料读。 +- 上游调用: + - `/api/panchanga_range`(五要素) + - `/api/chart`(目标日期的行运位置;宫位由「行运星座号 − 本命上升星座号」推出) + - `/api/ephemeris_events`(未来九十天换座与停滞,**由 qizheng 单交付**) +- **端点未上线的降级**:`/api/ephemeris_events` 不可用时该字段返回 `unavailable`,页面渲染静态说明,**不得抛错、不得让整页失败**。 +- 响应窄合同 + zod 校验,新增 `frontend/src/lib/ephemeris-contract.ts`。 +- 未登录返回 `401`。未设置出生资料时,五要素仍可返回(它与本命盘无关),行运的「相对本命」字段置空并注明。 + +**验收标准** + +- 新增 `frontend/tests/ephemeris-route.test.ts`:未登录 401;无出生资料时五要素仍返回、相对本命为空;`ephemeris_events` 不可用时降级;响应通过 zod 校验。 +- fixture 来自**真实引擎响应**(golden),不得手造形状。 +- 断言未触发任何计费路径。 + +### 任务 2 · 页面 `frontend/src/app/ephemeris/` + +三段,顺序固定: + +1. **日期条**:前一天 / 后一天 / 「今天」。三个都是 ≥44px 的真按钮。 +2. **今日五要素**:Vara、Tithi、Nakshatra(含第几足)、Yoga、Karana。桌面五张卡一行,手机改成一张卡里的五行(标签左、值右)。 +3. **当日行运**:九个星体的星座与度数、相对本命宫位、顺逆。桌面表格,手机堆叠行。 +4. **未来九十天**:按日期升序的事件列表,只写事实。 + +底部一张卡:一句「这一页是天象本身,不是对你的判断」+ 主按钮「带这天去提问」(把当前日期带进新对话)。 + +**验收标准** + +- `tsc --noEmit` 0 错;`npm run lint` 0 error。 +- 组件测试:日期前后切换会换掉五要素与行运;`unavailable` 时事件段渲染静态说明;手机宽度下五要素为纵向一张卡。 +- 正文 ≥ 14px,12–13px 只给短标签与元信息;触摸目标 ≥ 44px。 +- `frontend/DESIGN.md` 追加「星历页」小节。 +- 文案对照 `frontend/docs/VOICE.md`;**逐条检查不含运势判断**。 + +### 任务 3 · 岁差口径标注(BUG-707) + +**立单时实跑发现的真实缺陷,本单必须在界面上诚实标出来:** + +`/api/panchanga_range` 的响应里 `report.calculation_policy.panchanga` 写的是 +`"SwissEph Lahiri at sunrise-relative reference time"`,而账户默认岁差自 `80102459`(2026-08-20)起已经是 **Raman**,`/api/chart` 也确实按 Raman 算。 + +后果:**同一个用户,在星盘页看到的是 Raman 盘,在星历页看到的五要素却是 Lahiri 口径。** + +复现: +``` +POST /api/panchanga_range {"start_date":"2026-09-14","end_date":"2026-09-16","lat":31.19,"lon":121.44,"tz":8,"ayanamsa":"raman"} +读 report.calculation_policy.panchanga +``` + +**本单的处置(不要越界)**: + +- **不修**。统一到 Raman 还是明确声明 panchanga 永远用 Lahiri,是产品决策,另出单。 +- 五要素卡片下方**必须**出现一行说明:这一段按 Lahiri 计算,与星盘页的岁差设置不同。措辞对照 `frontend/docs/VOICE.md`,不得写成故障或道歉。 +- 行运段用的是 `/api/chart`,走账户岁差,**这一段不要加这句**——否则会误导。 +- 在 `docs/BUG_HISTORY.md` 记 **BUG-707**,状态 `investigating`:写清现象、复现、影响面(星盘页 vs 星历页口径分裂)。**不要猜根因、不要顺手改默认值。** + - qizheng 单也记了同一现象(BUG-703 `investigating`)。开工时若发现对方已经记了,**关联它、不要重复开号**,把本单的编号让出来。 + +**验收标准** + +- 五要素段出现该说明,行运段不出现。 +- `docs/BUG_HISTORY.md` 有对应记录,或明确关联 BUG-703。 + +## 让步顺序 + +1. 先保证 任务 1 + 任务 2 的前三段(日期条 / 五要素 / 当日行运)。这三段不依赖新端点,**可以完全独立交付**。 +2. 「未来九十天」可以只落 `unavailable` 形态,等 `/api/ephemeris_events` 上线再点亮。 +3. 「带这天去提问」若接对话的链路复杂,可以先做成跳到新对话并预填日期文本。 +4. **不可让步**:不碰 `page.tsx`、不碰 sidebar、不扣点不调模型、无 spinner、页面无运势判断、任务 3 的岁差标注。 + +## 开工前置命令 + +```bash +cd /workspace/Jyotisha +git status -sb +git fetch origin --prune +git worktree add -b codex/ephemeris-page-20260915 .worktrees/ephemeris-page-20260915 origin/staging +cd .worktrees/ephemeris-page-20260915/frontend +npm ci +./node_modules/.bin/tsc --noEmit +npm run lint +npm test 2>&1 | tail -20 # 记下测试总数 +npx next build 2>&1 | grep -E "^[│├└]| / " | head # 记下 `/` 的 Static 标记与首屏 gzip +wc -l src/app/page.tsx # 应为 1951 +``` + +## BUG 编号起点 + +`origin/staging` 上当前最大号 **BUG-699**。三份并行单预分配:qizheng 单 700–703、chart-page 单 704–706、**本单 707–709**。任务 3 用 **BUG-707**(若 qizheng 单已记 BUG-703,改为关联,本单不另开号)。 + +## 进度与记录 + +- 进度记录:`docs/tasks/PROGRESS-ephemeris-page-20260915.md`,本单状态板一行。 +- 环境缺口写 `BLOCKED.md`。 +- 真人验收清单写进 `docs/testing/`:日期前后切换、五要素随日期变化、事件列表降级形态、「带这天去提问」真的把日期带进了对话。 +- `CHANGELOG.md` 写用户可感知的变化,注明 Skill 版本**未 bump**。 +- 索引:`docs/tasks/README.md` 追加本单。 diff --git a/docs/tasks/TASK-qizheng-native-chart-20260915.md b/docs/tasks/TASK-qizheng-native-chart-20260915.md index fbe05e34..124c3f0a 100644 --- a/docs/tasks/TASK-qizheng-native-chart-20260915.md +++ b/docs/tasks/TASK-qizheng-native-chart-20260915.md @@ -1,4 +1,6 @@ -# TASK-qizheng-native-chart-20260915 · 接入原生七政四余排盘 +# TASK-qizheng-native-chart-20260915 · 接入原生七政四余排盘,并开出三个只读计算端点 + +> **2026-09-15 修订(立单当天)**:本单扩了范围。原版只做 `/api/qizheng`;核对后发现 P0 星盘页与 P1 星历页还各缺一个后端端点,而三者都要改 `scripts/jyotish_api_server.py`。为了让后续两份前端单能真并行,**把三个端点的注册全部收进本单**,由本单独占该文件的写权。两份前端单一行后端代码都不碰。 ## 基线 @@ -116,9 +118,23 @@ node CLI --date "1990-04-09T13:24:00" --lat 31.19 --lng 121.44 --pillars - **本单做**:vendored 归档、镜像 Node runtime、适配器、只读接口、测试、Bug 记录。 -**本单不做**:前端界面。原因是前端的「七政四余」是**星盘页的第五个 Tab**,而那个星盘页(P0 事实层:星盘 / 基础信息 / 大运 / 西洋盘 / 七政四余)本身还没有立单。两件事必须串行:先有星盘页,再往上挂 Tab。前端单由 Claude 另出,依赖本单的 `/api/qizheng` 响应合同。 +**本单不做**:任何前端文件。 -原型(含五个 Tab 的完整形态、中宫排盘参数卡、三套坐标系的边界文案)已经画好,前端单会引用它。 +**并行与文件归属**。同期还有两份前端单,三份单之间**没有任何共享代码文件**,可以并行开工: + +| 文件 / 目录 | 归属 | +| --- | --- | +| `vendor/**`、`NOTICE`、`deploy/**` | 本单 | +| `scripts/qizheng_chart_engine.py`、`scripts/ephemeris_events.py` | 本单 | +| **`scripts/jyotish_api_server.py`** | **本单独占**。两份前端单一行都不许改 | +| `tests/**`(本单新增的三个文件) | 本单 | +| `frontend/src/app/chart/**`、`frontend/src/app/api/chart-view/**`、`frontend/src/components/app-sidebar.tsx` | `TASK-chart-page-20260915` | +| `frontend/src/app/ephemeris/**`、`frontend/src/app/api/ephemeris/**`、`frontend/src/lib/ephemeris-*.ts` | `TASK-ephemeris-page-20260915` | +| `docs/BUG_HISTORY.md`、`CHANGELOG.md`、`frontend/DESIGN.md` | 三方都写,**只许追加各自小节**,合入顺序 本单 → chart-page → ephemeris-page | + +前端对本单的依赖是**运行时依赖,不是代码依赖**:端点没上线时,前端对应区块渲染静态说明,端点合入后自然点亮。 + +原型(五个 Tab 的完整形态、中宫排盘参数卡、三套坐标系的边界文案)已经画好,两份前端单都会引用它。 ## 任务分解 @@ -160,17 +176,38 @@ node CLI --date "1990-04-09T13:24:00" --lat 31.19 --lng 121.44 --pillars - - `.venv/bin/python -m pytest tests/test_qizheng_chart_engine.py` fail=0。 - `.venv/bin/python scripts/run_quality_gate.py --profile quick` 通过。 -### 任务 3 · `/api/qizheng` 薄注册 +### 任务 3 · 三个只读端点薄注册(本单独占 `scripts/jyotish_api_server.py` 写权) - 在 `scripts/jyotish_api_server.py` 的路由分支处新增一条 `elif path == '/api/qizheng':`。参照点:`origin/staging` 上 `elif path == '/api/chart':` 在 **3497 行**。 - handler 用既有的 `_load_local_module('qizheng_chart_engine')` 模式(该函数已存在,见 137 / 413 / 1009 行的用法),函数体 **≤ 8 行**,只做加载、调用、把领域异常翻成 `BadRequest`。 -- **行数硬上限**:`origin/staging` 上该文件是 **11313 行**,契约上限是 **11363**(`tests/test_api_server_growth_contract.py`,baseline 11063 + 300 余量)。**本轮对该文件的净增不得超过 12 行**,只剩 50 行总余额。 +- **行数硬上限**:`origin/staging` 上该文件是 **11313 行**,契约上限是 **11363**(`tests/test_api_server_growth_contract.py`,baseline 11063 + 300 余量),**总余额只有 50 行**。三个端点加起来,本轮对该文件的净增 **不得超过 32 行**。超了就把 handler 再压薄,不要动契约。 - 端点是只读计算,按既有重计算端点的口径纳入 `JYOTISH_HEAVY_COMPUTE_CONCURRENCY` 限流(默认 2,饱和 429)。CLI 子进程 20 秒超时要与限流口径对得上。 +#### 3b · `/api/western`(回归黄道本命盘) + +前端「西洋盘」Tab 需要,但**当前没有任何路由暴露它**:`scripts/western_chart_engine.py` 的 `build_tropical_natal_chart` 只在 `/api/consult` 内部被 `_western_evidence_packet_from_body` 间接调用(见 `jyotish_api_server.py:2205`)。 + +- 新增 `elif path == '/api/western':`,薄注册直接调 `build_tropical_natal_chart`(**不要**走 `build_tropical_western_evidence_packet`,那条带 `route_packet` 是咨询链概念)。 +- `house_system` 从请求读,默认 `P`(Placidus),回写进响应。 +- 响应必须显式带坐标系标注(`zodiac: "tropical"` 引擎已有,再加一条人类可读的边界句),并写明与恒星黄道的岁差差值。 +- handler 函数体 ≤ 8 行。 + +#### 3c · `/api/ephemeris_events`(区间换座与停滞) + +前端「未来九十天」需要。`/api/transit` 返回的是**对本命的触发点**(`transit_trigger.search_all_transit_triggers`,见 `jyotish_api_server.py:9947` 一带),**不是换座与顺逆停滞**,不能复用。 + +- 新增模块 `scripts/ephemeris_events.py`(**计算主体放这里,不进 api_server**):给定起止日期、岁差、交点模式,逐日扫描并返回 `ingress`(进入星座)与 `station`(停滞转顺 / 转逆)两类事件。 +- 区间上限与既有过境端点对齐(`transit search range must be <= 730 days`,见 `jyotish_api_server.py:9929`),超限返回 400。 +- 岁差必须按请求参数走,**不得写死**(附录里那条 panchanga 写死 Lahiri 的教训就在这)。 +- 新增 `elif path == '/api/ephemeris_events':`,handler 函数体 ≤ 8 行。 +- 参考实现口径(立单时用 pyswisseph 实跑过,`1990` 那组资料 2026-09-15 起 90 天、Raman、六星体得到 **14 条**事件):逐日取黄经与速度,星座号变化记 ingress,速度符号翻转记 station。 + **验收标准** - `.venv/bin/python -m pytest tests/test_api_server_growth_contract.py` 通过。 - 新增 `tests/test_qizheng_api_productization.py`(上游同名文件可参考,但断言按本仓合同重写):正常请求返回 11 曜 12 宫;缺 `lat` / `lon` 返回 400 而非 500;node 不可用时返回结构化错误。 +- 新增 `tests/test_readonly_chart_endpoints.py`:`/api/western` 返回十一项行星 + 十二宫 cusp + 相位 + 元素模式分布,`house_system` 可覆盖并回写;`/api/ephemeris_events` 在 90 天窗口返回按日期升序的事件列表,两类事件都有,超过 730 天返回 400,岁差参数生效(同一区间 Raman 与 Lahiri 的结果不相同)。 +- 新增 `tests/test_ephemeris_events.py`:模块级单测,golden 来自真实引擎输出。 - `curl -s -X POST http://127.0.0.1:5200/api/qizheng -d '{"year":1990,"month":4,"day":9,"hour":13,"minute":24,"lat":31.19,"lon":121.44,"tz":8}'` 的输出贴进进度记录(脱敏不需要,示例资料是虚构的)。 ### 任务 4 · Bug 记录 @@ -181,16 +218,19 @@ node CLI --date "1990-04-09T13:24:00" --lat 31.19 --lng 121.44 --pillars - - **BUG-701**|计都派别(`ketuMode: apogee`)未暴露为参数。状态 `resolved`(任务 2 参数化后)。 - **BUG-702**|适配器 boundary 文案把空的神煞与未闭合的庙旺说成已生成。状态 `resolved`(任务 2 改写后)。 +- **BUG-703**|`/api/panchanga_range` 岁差写死 Lahiri,与账户默认 Raman 不一致。状态 `investigating`(本轮只记录,不修,见附录)。 + 不得把姓名、出生资料、邮箱、用户/案例 ID、Cookie、JWT、密钥、完整请求体或模型原文写进去(AGENTS.md §5.6)。 ## 让步顺序 资源不够时按这个顺序砍,**不得自行调整**: -1. 先保证 任务 1 + 任务 2 + 任务 4。没有镜像 Node 与适配器,这一单等于没做。 +1. 先保证 任务 1 + 任务 2 + 任务 3a + 任务 4。没有镜像 Node 与适配器,这一单等于没做。 2. 任务 3 的限流接入可以延后,但路由与行数上限不能延后。 -3. `raw_engine_output` 的审计字段可以先不落地,改为进度记录里说明。 -4. **不可让步**:许可证归档(Apache-2.0 §4)、`vendor/**` 进 gated-paths、庙旺不得进结论、`--pillars` 不得调用、api_server 行数上限。 +3. 3b 与 3c 若来不及,可以拆到本单的第二次推送,但**必须仍由本单交付**——不得让前端单去改 `scripts/jyotish_api_server.py`。延后时要在进度记录里写明,并通知两份前端单:对应区块先渲染「该数据源尚未上线」。 +4. `raw_engine_output` 的审计字段可以先不落地,改为进度记录里说明。 +5. **不可让步**:许可证归档(Apache-2.0 §4)、`vendor/**` 进 gated-paths、庙旺不得进结论、`--pillars` 不得调用、api_server 行数上限。 ## 开工前置命令 @@ -221,11 +261,19 @@ done ## BUG 编号起点 -`origin/staging` 上 `docs/BUG_HISTORY.md` 的当前最大号是 **BUG-699**。本单从 **BUG-700** 起,开工时必须重新核对一次(可能已被别的会话占用)。 +`origin/staging` 上 `docs/BUG_HISTORY.md` 的当前最大号是 **BUG-699**。 + +三份并行单**预分配编号段**,各自不得越界;开工时仍要重新核对当前最大号(可能已被别的会话占用,占用了就整体后移并通知另外两单): + +| 单 | 编号段 | +| --- | --- | +| 本单(七政 + 只读端点) | **BUG-700 ~ 703** | +| `TASK-chart-page-20260915` | BUG-704 ~ 706 | +| `TASK-ephemeris-page-20260915` | BUG-707 ~ 709 | ## 附:本轮发现但不在本单范围 -立单期间实跑我们自己的引擎时发现一条与七政无关的口径不一致,**不要在本单修**,但请在本单的 Bug 记录里补一条 `investigating` 占位,避免丢失: +立单期间实跑我们自己的引擎时发现一条与七政无关的口径不一致,**不要在本单修**,但请在本单的 Bug 记录里记成 **BUG-703 `investigating`**,避免丢失: - **panchanga 岁差与账户设置不一致**。`/api/panchanga_range` 的响应里 `calculation_policy.panchanga` 写着 `"SwissEph Lahiri at sunrise-relative reference time"`,而账户默认岁差自 `80102459`(2026-08-20)起已是 **Raman**,且 `/api/chart` 确实按 Raman 算。同一个用户在星盘页看到的是 Raman 盘,在星历/今日路径看到的五要素却是 Lahiri 口径。 - 复现:`POST /api/panchanga_range {"start_date":"2026-09-14","end_date":"2026-09-16","lat":31.19,"lon":121.44,"tz":8,"ayanamsa":"raman"}`,读 `report.calculation_policy.panchanga`。