Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fd1bd75889 |
@@ -1,11 +1,5 @@
|
||||
# 印度占星 Skill 更新日志
|
||||
|
||||
## 2026-11-12 — 个人报告按当前证据合同写作并单列核验状态
|
||||
|
||||
- 解读开头明确废止历史预测数字和旧分级口径,功能吉凶按事实包选中的版本解释。
|
||||
- 「计算依据」中的核验记录按实际状态显示,默认折叠,导出保留两行。
|
||||
- 本轮沿用当前 Skill 版本(BUG-1861)。
|
||||
|
||||
## 2026-11-12 — 年运报告将 Narayana 列为条件参考
|
||||
|
||||
- 未决种子的具名分支独立列出,窗口来源数及把握等级依据独立证据。
|
||||
|
||||
@@ -25085,45 +25085,3 @@
|
||||
- 防复发:后端验证单来源不成窗、条件事件不能桥接窗口,前端验证同向不抬级和候选说明。
|
||||
- 关联记录:#209、#190、BUG-1601、BUG-1650、BUG-1732、BUG-1830、BUG-1842。
|
||||
- 修复版本:`codex/annual-narayana-conditional-20261112`。
|
||||
|
||||
## BUG-1858 | 报告用途稳定性由一分钟分盘边界检查推定
|
||||
|
||||
- 状态:fixed-pending-verify(待独立验收)。
|
||||
- 首次发现 / 最近更新:2026-11-12 / 2026-11-12。
|
||||
- 影响面:个人报告 `report_evidence_grade_v1` 每类结论的用途稳定性账本。
|
||||
- 现象和触发:事实包报用途未评估,但一分钟内无分盘边界时账本填稳定。应期窗口无分盘依赖也被填稳定。
|
||||
- 根因:`claim_grade` 从分盘边界检查推导用途稳定性。BUG-1851 的既有回归核对分级上限和分盘边界,还把边界值当作用途稳定性,未覆盖两者不一致。
|
||||
- 修复:分别记录用途评估和一分钟边界检查,增加未评估披露,保留必需分盘贴边时的 D 上限。
|
||||
- 验证:合成事实包复现原实现八项误报稳定。Claude 确认同步两条旧断言后,任务书三文件合跑 20 项通过。快速门有 3 项失败,均在 staging 定向复现。见 `docs/tasks/PROGRESS-purpose-stability-20261112.md`。
|
||||
- 防复发:新增回归覆盖未评估、应期窗口、必需分盘贴边及缺失边界。保留旧测试的 D6/D30 上限检查,分别核对 D6 的边界状态和 D30 的披露。
|
||||
- 关联记录:#215、BUG-1851、BUG-1848。
|
||||
- 修复版本:`codex/purpose-stability-20261112`。
|
||||
|
||||
## BUG-1859 | 报告参与状态混用,Badhaka 宫位和宫主落点难以区分
|
||||
|
||||
- 状态:fixed-pending-verify(待独立验收)。
|
||||
- 首次发现 / 最近更新:2026-11-12 / 2026-11-12。
|
||||
- 影响面:个人报告事实包的领域参与行及共享技法,咨询和报告共用的 Badhaka 记录。
|
||||
- 现象:已算出的 Indu 等点只显示未路由;Badhaka 平铺字段容易把阻碍宫写成宫主星的落宫。
|
||||
- 触发条件:报告事实包包含领域不采用的已算结果,或健康参与层读取 Badhaka。
|
||||
- 根因:一个 `state` 同时表示执行与领域使用。BUG-1852 修了引用和描述,但没有分轴;BUG-1853 增加了计算,旧测试只验证平铺字段。
|
||||
- 修复:为每行加计算和解读两轴,保留旧状态并分别汇总缺口。Badhaka 按原来源输出阻碍宫、宫内行星和宫主落点,沿用产品计算待审状态。
|
||||
- 验证:十二上升合成盘、两张公开盘和精度阻塞回归均通过。任务及等级合同 42 项、消费者 10 项通过。详情见 `docs/tasks/PROGRESS-reading-status-axes-badhaka-20261112.md`。
|
||||
- 防复发:检查已算未用、缺真实结果、不适用理由及阻碍原因。断言宫主不在阻碍宫时不列入其宫内行星,落在该宫时列入。
|
||||
- 关联记录:#216、BUG-1848、BUG-1852、BUG-1853。
|
||||
- 修复版本:`codex/reading-status-axes-badhaka-20261112`。
|
||||
|
||||
## BUG-1861 | 个人报告提示仍含旧置信度口径,功能吉凶资料混用
|
||||
|
||||
- 状态:resolved(分支定向回归通过,待独立验收)
|
||||
- 首次发现 / 最近更新:2026-11-12 / 2026-11-12
|
||||
- 影响面:个人报告写作提示、功能吉凶规则溯源与「计算依据」附录。
|
||||
- 用户现象:旧准确率和 A/B/C 置信度口径仍进入个人报告写作提示。附录显示固定核验说明,读者无法辨认两项实际核验状态。
|
||||
- 触发条件:个人报告从事实包生成写作提示并渲染计算依据。
|
||||
- 根因:历史方法原文先于产品合同进入模型上下文。事实包使用 PVR,供模型参考的 BPHS 文档却用上游路径存放产品修订。普通审计表用固定文案替换后台核验状态。
|
||||
- 修复:先载入产品合同的废止公告,报告继续遵循 `report_evidence_grade_v1`。按 Git 原始字节恢复上游 BPHS 并另存产品修订。个人报告附录从已有审计读取两项核验状态,缺失或阻塞显示「未完成」。
|
||||
- 验证:检查提示拼接顺序、上游 SHA256、十二上升土星分类、两张公开盘的 PVR profile、已保存与缺失的核验状态,以及附录折叠和九节正文的渲染边界。详情见 `docs/tasks/PROGRESS-reading-runtime-sources-20261112.md`。
|
||||
- 防复发:`test_functional_profile_split_20261112.py` 锁定原始文件和运行时 profile。`report-reading-runtime-sources-20261112.test.ts` 锁定公告、提示顺序和个人报告附录边界。
|
||||
- 相关记录:BUG-1794(个人报告附录部分回撤)、BUG-1841、BUG-1570、BUG-1590。
|
||||
- 复发自:无。
|
||||
- 修复版本:`codex/reading-runtime-sources-20261112`,待发布。
|
||||
|
||||
@@ -1,27 +0,0 @@
|
||||
# 报告用途稳定性 · 2026-11-12
|
||||
|
||||
Gitea #215,BUG-1858。首次按评论 42245 认领。评论 42285 确认两条断言可同步更新,认领评论 42293。任务书基线为 `1ba0364a7a8c0f964c3e21c21cb304aaa1899375`。
|
||||
|
||||
## 已核实
|
||||
|
||||
- 用完整合成事实复现。所有分盘最近边界大于 60 分钟、事实包用途稳定性为 `not_evaluated` 时,旧账本将八类结论全标为 `stable`。八类上限仍为 C。
|
||||
- 账本从事实包读取用途稳定性,默认 `not_evaluated`。事实包给出 `stable` 时,应期窗口仍记为 `not_evaluated`。分盘边界独立列在 `ledger.boundary_check`,包含规则、状态和原数值。必需 D6 距边界 0.25 分钟时上限仍为 D。
|
||||
- 已检索 `frontend/src` 及后端消费者。前端等级判断读取 `evidence_grades.claims` 的 `cap`。未找到消费本单 `v:` 行的 `stable` 字面值的模块。
|
||||
- 四项新增回归在本地候选上通过。第一项在原实现失败,实际为八项 `stable`。
|
||||
- 拍板前合跑任务书指定的三个文件,19 项通过、1 项失败。原 staging 上两个既有文件 16 项均通过。候选失败是下表第一条。运行在第一条断言中止,第二条已用同一夹具独立核对。
|
||||
|
||||
## 既有断言变更
|
||||
|
||||
两条断言在 `tests/test_report_evidence_grade_20261112.py` 的同一个测试中。Claude 2026-11-12 答复确认可同步更新。
|
||||
|
||||
| 位置 | 原值 | 新值 | 原因 |
|
||||
| --- | --- | --- | --- |
|
||||
| `test_missing_required_layer_or_boundary_varga_gives_d` 的必需 D6 | `ledger.purpose_stability == "boundary_sensitive"` | `ledger.purpose_stability == "not_evaluated"`,在 `boundary_check.rows` 验证 D6 为 `boundary_sensitive`,保留上限 D 断言 | 任务书 #215 第 1、5 条;Claude 2026-11-12 答复。 |
|
||||
| 同测试的仅核对 D30 | `must_disclose == ["v:D30"]` | `must_disclose` 同时包含 `v:D30` 与 `purpose_stability_not_evaluated`,保留上限 C 断言 | 任务书 #215 第 1、5 条;Claude 2026-11-12 答复。 |
|
||||
|
||||
## 最终验证
|
||||
|
||||
- 合入本分支的验证基线为 staging `2f374fff281622f342151a97fa6f6d758de74cc8`,保留了该分支上的 Bug 记录。
|
||||
- 更新既有断言后,任务书指定三文件合跑 20 项通过,四项新增回归均通过。
|
||||
- Python 快速门退出码 1,1,463 项通过、3 项失败、2 项跳过、29 项子测试通过。失败均在 `test_upstream_technique_layers.py`,在同一 staging 提交定向复跑重现。
|
||||
- 仓库隐私检查 62 项通过。
|
||||
@@ -1,55 +0,0 @@
|
||||
# 个人报告解读的运行时资料与核验记录 · 2026-11-12
|
||||
|
||||
Gitea #217,BUG-1861。计划评论 42256,认领评论 42374。任务书基线 `1ba0364a`,合入本分支的验证基线为 staging `c55338f35916587ec9007d0786f370e5c29d0aa9`。
|
||||
|
||||
## 旧原文盘点
|
||||
|
||||
范围为仓库 `SKILL.md` 与 `REPORT_READING_REFERENCES` 的八份文件。下列行号按本轮基线。
|
||||
|
||||
| 文件与标题 | 行 | 原文片段与用途 |
|
||||
| --- | --- | --- |
|
||||
| `SKILL.md`,v6.9.14 核心能力、Yoga 逻辑验证指标 | 29、57–61 | 「Yoga精度Benchmark 100%」、Precision 83.26% / 96.48%、Recall 91.52% / 93.99%、F1 87.19% / 95.22%、8/8。技术样本指标,报告不据此声明预测准确率。 |
|
||||
| `SKILL.md`,Transit Actionable Output | 288 | 「[A]=已验证 / [B]=高概率 / [C]=推断」。旧三级分法。 |
|
||||
| `SKILL.md`,Chara Dasha 能力升级 | 334–338 | 「95.83% ≥ 95%」,「Sign 100%, Dur 91.67%, Overall 95.83%」,「~4.2%差异」。引擎对拍,不是个案命中率。 |
|
||||
| `SKILL.md`,核心分析规则 | 426 | 「若没有主承诺和推运激活,输出置信度不得超过 C」。旧固定上限。 |
|
||||
| `SKILL.md`,精准解盘方法论与资料索引 | 392、604 | 「三级置信度」索引及「[A]已验证 / [B]强推断 / [C]假设」。 |
|
||||
| `SKILL.md`,已验证名人案例 | 727、731–736、738 | 标题与总览「平均93%」,个案列 95%、94%、92%、93%、94%、93%。历史吻合度,报告不据此声明验证或命中。 |
|
||||
| `strict-workflow-router.md`,full-reading-strict | 181 | `Confidence label: A/B/C/insufficient`。 |
|
||||
| 同文件,event-verification-strict | 332–343 | `A: same theme + same timing`、`B: … approximate timing`、`C: symbolic match only`、`Fail`、表格 `A/B/C/fail`。回放质量的旧映射不作报告等级。 |
|
||||
| 同文件,Confidence rules | 409–418 | 表格 A、B、C、Insufficient 与「Upgrade only when independent techniques converge」。报告不采用这组旧分级门槛。 |
|
||||
| `precision-reading-methodology.md`,案例验证 | 42 | 「500个案例验证,达到99%成功率」。 |
|
||||
| 同文件,九层复合方法 | 141 | 「做到第六层,平均85%的预测成功率」。 |
|
||||
| 同文件,验证驱动 | 178 | 「倒推失败率 > 30%」。历史流程阈值,不是本报告结果。 |
|
||||
| 同文件,置信度分级、输出规范及诚实边界 | 226–249 | 「A-已验证 / B-强推断 / C-假设」、「置信度:[A/B/C]」。第 249 行「物理学中90%」「医学中60%」是跨学科举例,不是预测成绩。 |
|
||||
| 同文件,常见教条式失误及纠正 | 269 | 「多系统验证(L4-L9)才能达到85%准确率」。 |
|
||||
| 同文件,实战五步法 | 315、323 | 「匹配度 <60% 则停止」是历史流程阈值。「三级置信度标注」是旧输出。 |
|
||||
| `event_judgment_skeleton.md`,Confidence Mapping 与 Output Contract | 109–125、147 | 「建议映射」A–D 与 `"confidence": "A|B|C|D"`。不是旧 A/B/C 词表,但本报告不按其示例自评分。 |
|
||||
|
||||
`SKILL.md` 第 148 行 A/B/C 是入口路由分类。第 713 行 97.8% 是文档覆盖率。三个专题 `event_judgment_career.md`、`event_judgment_wealth.md`、`event_judgment_marriage.md` 只提证据不足会影响置信度,未给旧等级词表或预测率。`functional-role-profile.md` 与 `interpretation_status_contract.md` 分别记方法和状态,不含此类旧声明。
|
||||
|
||||
## 实施与消费位置
|
||||
|
||||
- 产品合同 G 节新增专供个人报告的前置公告。 `reportReadingPrompt` 在引述原文之前加载公告,原文照常逐份保留和记录哈希。聊天不加载这个块。
|
||||
- 从占星师 `8aaf60991c32a84ac30d6d7b6a62c4f166507bce` Git 对象取原始字节。SHA256 为 `95f5445a197a5f49ebaa08431816f0f3583b0a3c13f7967d3c043cecfe63aa7e`。原产品表按原字节移入具名文件,另加逐行差异表。 `functional_benefics.py` 仅修改两处引用路径。
|
||||
- 从当前 BPHS 代码取十二上升的土星期望值。白羊、巨蟹、狮子、天蝎、射手、双鱼为凶。金牛、天秤、摩羯、水瓶为吉。双子、处女为混合,现有集合将混合列入中性组,歧义注记标为 mixed。
|
||||
- `pl9_full_data_export._append_audit_chapters` 是个人报告审计的统一接入点。 `audit_chapters` 在这里读取已保存的两个审计状态,不复制后台词和说明。空记录或阻塞记未完成,已记录的完成、部分完成或不适用照实写。中文行名为「网上资料核对」「真实案例校准」,英文版独立翻译。 `PersonalReportMarkdownView` 在原计算依据下折叠显示这张表,打印复用原折叠表机制,Markdown 导出保留两行。
|
||||
- 九节的 C 级说明仍表示缺少相符案例。文案为「有星盘依据,还没有对得上的案例」,使核验行名只出现在附录。原作者写作与聊天过滤合同继续生效。
|
||||
|
||||
## 既有断言变更
|
||||
|
||||
| 位置 | 原值 | 新值 | 原因 |
|
||||
| --- | --- | --- | --- |
|
||||
| `test_report_no_web_wording_20261109.py::_assert_clean` | 一条固定核验说明必须是普通审计表最后一行,其余内容禁词 | 独立核验表恰有两条状态行,其余内容沿用禁词断言 | #217 要求两条可读的实际状态。 |
|
||||
| `test_report_audit_chapters_20261107.py` 中文审计 | 固定说明仅一次,正文全篇不出现案例词 | 两条未完成行分别检查,隔离后仍禁止其余内容出现案例词 | 保留词语隔离,按新附录结构验证。 |
|
||||
| `test_report_wording_sanitizer_20261111.py` | 聊天与报告共用同一句固定说明 | 聊天固定文案保持原值,个人报告不显示旧固定句 | 本轮只更改个人报告附录。 |
|
||||
| `report-reading-20261027.test.tsx` C 级 | 「有星盘依据,还没有用真实案例校准过」 | 「有星盘依据,还没有对得上的案例」 | 九节不重复附录中的核验行名,保留 C 级含义。 |
|
||||
|
||||
## 验证
|
||||
|
||||
- 开工基线 `c55338f35916587ec9007d0786f370e5c29d0aa9`。Python 定向 64 项通过,覆盖 BPHS 拆分、两张公开盘的 PVR 事实包、报告审计、禁词范围和聊天提示。盘面、功能吉凶及校正另有 95 项与 3 个子测试通过。隐私守卫 62 项通过。
|
||||
- 前端指定测试 24 项通过,另有聊天核验说明、报告导出与相关消费链 16 项通过。原 6 个提示块逐块校验,去掉维护者注释后的文本与基线一致。
|
||||
- TypeScript 检查通过。Lint 为 0 错误、62 条既有警告。对照 `npm run build` 两边均成功,首页 `/` 保持 `○ Static`。从构建后的首页 HTML 提取 JS 和 CSS 资源,两边各 39 个文件,gzip 后 JS 515860 字节、CSS 90886 字节,均为 0% 变化。
|
||||
- Python 快速门 `--profile quick --skip-frontend-runtime` 退出 1,1463 项通过、2 项跳过、29 个子测试通过。3 项失败与开工基线对照一致,均在 `tests/test_upstream_technique_layers.py`,分别为公开盘固定值、大运注册表与 Kaala 的 PyJHora 对照。失败表现相同,分别为 `KeyError: 'seed'`、Windows `OSError: [Errno 22]`、`KeyError: 'seed'`。
|
||||
- 合入 staging `718bc991368cd3eb33de60e93c22600e80a139f1`(#216)后,验证实际合并树。功能吉凶拆分、两张公开盘、参与状态和报告事实包定向共 41 项通过。合入期间只需解决 Bug 历史的追加冲突,两单记录均保留。前端源文件和测试与上面的验证版本逐字节一致。
|
||||
- 前端全量按 `npm test` 同一 TSX 文件清单运行,每组并发 4。开工基线 3663 项,3636 通过、27 失败。分支 3665 项,3638 通过、27 失败。两组取消与跳过均为 0,退出码均为 1。27 项失败名称完全一致,无缺失断言。新增的 2 项提示与附录测试均通过。
|
||||
- 已验证提示实际拼接及个人报告服务端渲染。真实模型输出与浏览器交互留待独立验收。
|
||||
@@ -1,29 +0,0 @@
|
||||
# 个人报告参与状态与 Badhaka · 2026-11-12
|
||||
|
||||
Gitea #216,BUG-1859。计划评论 42250,认领评论 42345。任务书基线 `1ba0364a`,验证基线为同步后的 staging `c55338f35916587ec9007d0786f370e5c29d0aa9`。
|
||||
|
||||
## 两轴与消费位置
|
||||
|
||||
- 参与行复用 `technique_status_axes.technique_audit_axes` 规范状态词,保留原有 `state`。参与本领域的计算默认是 `executed + primary`。Badhaka 是专项答复 §4 指定的健康补充层,记 `supporting`。KP 已有条件参考规则,存在实际宫头或行星结果时记 `conditional`,只有方法标签时记 `not_computed + not_used`。
|
||||
- 已算出的未路由值记 `executed + not_used`。缺结果且没有失败证据时记 `not_computed + not_used`,不附计算值。明确缺输入、调用失败和验证失败分别记录对应阻碍;精度保留层缺确认分钟,沿用原说明。显式不适用需带范围理由。汇总保留旧路由及精度分类,实际计算缺口分到各自状态。
|
||||
- 全仓检索核对:`consult_card_domain_facts._badhaka` 把产品记录传入咨询和阅读,从 D1 原有行星状态读取尊贵度,缺失或无效上升分别标明原因。 `reading_facts.STRUCTURED_VALUES` 放行两段新记录、来源与 profile。前端 `computedPointsOf` 读取参与 ID、旧 `state` 和 `computed`,上限模块也读旧 `state`。星盘页身份标签来自独立的 `chart_view_points.functional_roles`,没有读取本轮旧字段。
|
||||
- `badhaka_modality_v1` 沿用来源 §二及现有 `badhaka_house`。按落入目标星座记录 `badhaka_sthana.occupants`;`badhakesh` 记录宫主本身的落点和尊贵度。保留产品计算待审状态。
|
||||
|
||||
## 既有断言变更
|
||||
|
||||
| 位置 | 原值 | 新值 | 原因 |
|
||||
| --- | --- | --- | --- |
|
||||
| `test_badhaka_facts_20261112.py` 三种上升 | 比较顶层 `house/sign/lord` | 比较 `badhaka_sthana.house/sign` 与 `badhakesh.planet` | 迁移到任务书规定的宫位与宫主字段,预期值相同。 |
|
||||
| 同测试宫主落点 | 比较 `lord_sign/lord_house` | 比较 `badhakesh.sign/house` | 保留对宫主落点的检查。 |
|
||||
| 同测试宫内行星 | 比较顶层 `occupants` | 比较 `badhaka_sthana.occupants` | 保留对宫内行星的检查。 |
|
||||
| `test_reading_facts_v3_20261112.py` 健康参与层宫位 | `value.house` 属于 7、9、11 | `value.badhaka_sthana.house` 属于 7、9、11 | 只更新读取位置。 |
|
||||
| 同测试星座及宫主 | `value.sign` 和 `value.lord` 均有值 | `value.badhaka_sthana.sign` 和 `value.badhakesh.planet` 均有值 | 只更新读取位置。 |
|
||||
| `test_report_reading_facts.py::test_withheld_varga_houses_stay_out_without_a_confirmed_minute` | D30 行精确等于四个旧字段 | 保留四个字段及精确相等,新增三个要求字段,值分别为 `blocked`、`not_used`、`missing_input` | 每条参与行都必须给两轴,缺确认分钟给具体阻碍。仍禁止附上计算值。 |
|
||||
| `test_report_reading_facts.py::test_reading_facts_are_packet_leaves` | Python 默认带分隔空格的 JSON 长度小于 80,000 | 紧凑 JSON 长度小于 80,000 | 模型输入使用 `JSON.stringify(facts)`。测量实际的紧凑格式,保留阈值和内部字段禁入断言。 |
|
||||
|
||||
## 验证
|
||||
|
||||
- 任务文件和等级合同共 42 项通过。两张公开盘实测分别为第 7 宫双鱼、木星落第 10 宫,以及第 11 宫天蝎、火星落第 9 宫。第一张的宫内行星含月亮。两张盘均按 D1 核对宫主尊贵度,四个未用点均为已算未用。
|
||||
- 报告与咨询消费者共 10 项通过。两条旧合同在 staging 原样通过。新事实用 Python 默认 JSON 计长为 80,710,实际模型使用的紧凑格式为 74,560,仍低于 80,000。
|
||||
- Python 快速门退出码 1,1,463 项通过、3 项失败、2 项跳过、29 项子测试通过。三项失败均在同一 staging 提交的上游技法定向测试重现。
|
||||
- 仓库隐私检查 62 项通过。
|
||||
@@ -1,71 +0,0 @@
|
||||
# TASK:上限账本的用途稳定性不再由一分钟规则冒充(2026-11-12)
|
||||
|
||||
- Gitea:#215
|
||||
- 分支:`codex/purpose-stability-20261112`,基于 staging `1ba0364a7a8c0f964c3e21c21cb304aaa1899375`
|
||||
- Bug 编号:BUG-1858
|
||||
- 验收:Claude(jyotisha-e6)
|
||||
|
||||
不得自行写「产品决定/裁定」。任务书没写到的取舍,一律选「需拍板」。
|
||||
|
||||
不得修改或删除既有断言来让测试通过。确需修改的,在 PROGRESS 里逐条写明原值、新值和原因。
|
||||
|
||||
占星师的引擎和资料(`scripts/jyotish_engine.py`、`scripts/yoga_engine.py`、`references/` 下她仓库已有的文件、`SKILL.md`)一律不改。产品差异只能放进单独的产品文件,并在 `references/product_runtime_contract.md` 公告(项目规则 2026-11-07)。
|
||||
|
||||
占星师回复原文:`docs/tasks/ref-astrologer-acceptance-reply-20261112.md`,由本分支第一个提交带入。下文的「她 §N」都指这份文件。
|
||||
|
||||
## 背景(她 §2 P0-1)
|
||||
|
||||
`scripts/report_render/evidence_grade.py` 为报告解读的每类结论算上限,账本里有一项 `purpose_stability`。问题在于两处结果对不上:
|
||||
|
||||
- 事实包里 `birth_precision.purpose_stability` 是 `not_evaluated`,因为按用途区间的合同还没接入。
|
||||
- 账本里却写 `stable`。
|
||||
|
||||
原因是 `claim_grade` 用 `_varga_stability` 的结果推出 `purpose_stability`:分盘上升最近的换宫距离大于 `CONFIRMED_TOLERANCE_MINUTES = 1.0`,就算 `stable`。Jobs 样例的 8 项全是 `stable`;Obama 样例 7 项 `stable`,1 项 `boundary_sensitive`。
|
||||
|
||||
她的裁定:这个一分钟比较只是在 `birth_record_minute_v1` 规则下检查附近有没有边界,不能证明结论在真实出生区间内稳定。`timing_window` 更不能在没有精度依赖的情况下标成稳定。
|
||||
|
||||
## 要改的文件
|
||||
|
||||
- `scripts/report_render/evidence_grade.py`
|
||||
- 新建 `tests/test_evidence_grade_purpose_stability_20261112.py`
|
||||
- `tests/test_report_evidence_grade_20261112.py`:只在断言写的是旧的 `stable` 时改,并在 PROGRESS 逐条列出
|
||||
- `docs/BUG_HISTORY.md`:只在末尾追加一条
|
||||
- `docs/tasks/PROGRESS-purpose-stability-20261112.md`
|
||||
|
||||
## 要做的
|
||||
|
||||
1. 账本里的 `purpose_stability` 改成直接取事实包的 `birth_precision.purpose_stability`,取不到就填 `not_evaluated`。不能再由分盘边界推出来。
|
||||
2. 分盘边界检查的结果单独记在账本新字段 `boundary_check` 里,形如 `{"policy": "birth_record_minute_v1", "rows": [...]}`。`rows` 保留现有的 `nearest_change_minutes`、`tolerance_minutes` 和状态(`clear` 表示一分钟内没有换宫,另有 `boundary_sensitive`、`not_evaluated`、`missing`)。
|
||||
- `required` / `checked` 里 `v:` 依赖行的 `state`:`stable` 改名为 `clear_at_tolerance`。
|
||||
- 其余状态不变。
|
||||
- 前端读不读这个字符串,你要用 grep 核对 `frontend/src`。如果有读取,只能在 PROGRESS 报告,不许改前端,选需拍板。
|
||||
3. 上限规则不变:
|
||||
- 必需分盘落在边界内(`boundary_sensitive`),照旧给 D;
|
||||
- `not_evaluated` 不会因此把结论降到 D(她:未评估不意味着整份报告一律 D)。
|
||||
4. `timing_window` 的账本里,`purpose_stability` 在任何情况下都不能是 `stable`。
|
||||
5. 新增一个 `must_disclose` 项 `purpose_stability_not_evaluated`:只要 `purpose_stability` 不是 `stable`,每类结论都带上这一项。
|
||||
|
||||
## 不要碰
|
||||
|
||||
- `frontend/**`、`references/**`、`scripts/report_render/reading_facts.py`、`scripts/report_render/birth_precision_margins.py`
|
||||
- 冻结文件
|
||||
|
||||
## 验收标准(写进新测试)
|
||||
|
||||
1. 用合成 facts 测:`minute_confirmed=True`,所有分盘边界都大于 60 分钟,`birth_precision.purpose_stability="not_evaluated"`。此时 8 类结论的账本 `purpose_stability` 全是 `not_evaluated`,上限仍是 C。
|
||||
2. 事实包里给出 `birth_precision.purpose_stability="stable"` 时,`timing_window` 仍不是 `stable`,其他类照抄 `stable`。
|
||||
3. `v:D8` 落在 0.25 分钟内、并且是必需依赖时(自己造一个把 D8 设为必需的场景;做不到就用 D6),该类上限是 D。`boundary_check.rows` 里能看到 0.25。
|
||||
4. 旧代码上,第 1 条必须失败。在 PROGRESS 里记下旧代码的结果。
|
||||
5. `tests/test_report_evidence_grade_20261112.py` 和 `tests/test_reading_facts_v3_20261112.py` 全部通过。
|
||||
|
||||
## 自测命令(仓库根目录)
|
||||
|
||||
```
|
||||
python3 -m pytest -q tests/test_evidence_grade_purpose_stability_20261112.py tests/test_report_evidence_grade_20261112.py tests/test_reading_facts_v3_20261112.py
|
||||
python3 scripts/run_quality_gate.py --profile quick --skip-frontend-runtime
|
||||
```
|
||||
|
||||
## 何时停下来选「需拍板」
|
||||
|
||||
- 前端或别的模块依赖 `stable` 这个字面值。
|
||||
- 只有改事实包结构才能完成。
|
||||
@@ -0,0 +1,112 @@
|
||||
# TASK:报告解读复查补事实核对规则,修正误拦,依据节被拦时给确定性审计(2026-11-12)
|
||||
|
||||
- Gitea:#218
|
||||
- 分支:`codex/reading-postcheck-facts-20261112`,基于 staging `2f374fff281622f342151a97fa6f6d758de74cc8`。#207 已合入(2f374fff)。
|
||||
- Bug 编号:BUG-1860
|
||||
- 验收:Claude(jyotisha-e6)
|
||||
|
||||
不得自行写「产品决定/裁定」。任务书没写到的取舍,一律选「需拍板」。
|
||||
|
||||
不得修改或删除既有断言来让测试通过。确需修改的,在 PROGRESS 里逐条写明原值、新值和原因。
|
||||
|
||||
占星师的引擎和资料(`scripts/jyotish_engine.py`、`scripts/yoga_engine.py`、`references/` 下她仓库已有的文件、`SKILL.md`)一律不改。产品差异只能放进单独的产品文件,并在 `references/product_runtime_contract.md` 公告(项目规则 2026-11-07)。
|
||||
|
||||
占星师回复原文:`docs/tasks/ref-astrologer-acceptance-reply-20261112.md`,由本分支第一个提交带入。下文的「她 §N」都指这份文件。
|
||||
|
||||
## 背景
|
||||
|
||||
报告解读写完后,由 `frontend/src/lib/personal-report-reading.ts` 的 `checkSection` 逐节复查。没通过的那一节会被隐藏,原处显示 `READING_SECTION_WITHHELD_TEXT`。#207 已经补了三条复查:历史回放不给当前建议、双行运范围、形成与未验证。
|
||||
|
||||
她 §3 指出成稿里还有这些错误,要求修正,而不是降到 C 了事:
|
||||
|
||||
1. 「水星掌管第五宫与第十宫」:事实包 `planets.Mercury.owns_houses = [1,10]`,第五宫只是落宫(`d1_house = 5`)。这是宫主和落宫混用。
|
||||
2. 「SAV 总分为337,显示不缺维持力」:337 是固定的标准总和,只能用来校验,不能说明个人强弱。
|
||||
3. 「Badhaka(阻碍星)落在第七宫」:第七宫是阻碍宫,木星是阻碍宫主,两者混成了一个说法。
|
||||
4. Obama 样稿的健康节写了「运动或炎症类风险」:这是没有依据的具体现实风险。
|
||||
|
||||
她 §4 还指出两处问题:
|
||||
|
||||
5. `BANNED_PHRASES` 一刀切。「检测器检出,待核实」是合法的状态说明;「不得预测死亡」这类边界说明也不该被拦。
|
||||
6. 依据节被拦时整节隐藏,行动性结论还在页面上,证据限制却看不到了。
|
||||
|
||||
## 要改的文件
|
||||
|
||||
- `frontend/src/lib/personal-report-reading.ts`:只改复查部分
|
||||
- `frontend/src/lib/personal-report-reading-contract.ts`:只在第 6 条需要时新增类型或文案常量
|
||||
- `frontend/src/components/personal-report/report-reading-section.tsx`、`frontend/src/lib/report-export-blocks.ts`:只为第 6 条
|
||||
- 新建 `frontend/tests/report-reading-postcheck-facts-20261112.test.ts`
|
||||
- `docs/BUG_HISTORY.md`:末尾追加一条
|
||||
- `docs/tasks/PROGRESS-reading-postcheck-facts-20261112.md`
|
||||
|
||||
## 要做的
|
||||
|
||||
每条新检查不通过时只退回那一节,原因加进 `ReadingRejection`。
|
||||
|
||||
1. **`house_lord_mismatch`**
|
||||
- 句子里出现「X 掌管 / 主管 / 管 / 是 第 N 宫(主)」「第 N 宫主 X」「第 N 宫的宫主 X」「X 为第 N、M 宫主」等写法时,按事实包 `planets.<X>.owns_houses` 核对。N 不在其中就退回。
|
||||
- 「第 N 宫与第 M 宫」这类并列写法要逐个核对。
|
||||
- 「X 落 / 落在 / 位于 / 坐 第 N 宫」不是宫主写法,不查。
|
||||
- 行星名用 `PLANETS_ZH`。数字同时支持阿拉伯数字和中文数字(一到十二)。
|
||||
- 分句规则:以「。;!?」分句。宫主词与行星之间如果隔着另一颗行星名,就不算命中。
|
||||
2. **`sav_total_as_strength`**
|
||||
- 句子里出现「337」或「三百三十七」,同时又出现「不缺」「充足」「强」「弱」「维持力」「底气」「力量」中任何一个,退回。
|
||||
- 「校验」「合计」「总和为标准值」这类纯校验写法不退回。
|
||||
3. **`badhaka_role_mix`**
|
||||
- 健康领域的 Badhaka 值里有宫位和宫主。
|
||||
- 退回的情况:句子里出现「阻碍星」,同一分句又出现「第 N 宫」,而 N 是 Badhaka 宫的宫位。例如「阻碍星落在第七宫」。
|
||||
- 不退回的情况:「阻碍宫在第七宫」「阻碍宫主木星在第十宫」。
|
||||
- 字段名先按当前 `badhaka_facts.py` 的 `house` / `lord` 读取。如果 BUG-1859 已经合入,改为读 `badhaka_sthana` / `badhakesh`,两种都要兼容。
|
||||
4. **`health_outcome`**
|
||||
- 健康节和总评节里,命中具体病症、伤害或部位风险词表就退回。
|
||||
- 起始词表:炎症、感染、外伤、受伤、损伤、骨折、手术、出血、发炎、结石、肝、肾、心、肺、胃、血压、血糖、过敏、失眠、运动风险、运动损伤。
|
||||
- 「心」只在「心脏」「心血管」「心律」中算命中;「心理」「心情」「关心」等不算。
|
||||
- 写进 PROGRESS 的内容:完整词表,以及用现有三份公开样稿(下文路径)跑出的误伤列表。
|
||||
5. **`banned_phrase` 收窄**(只改这两个词,其他禁用词不动)
|
||||
- 「检测器」:只有出现在「检测器检出」「检测器识别」之后,并且同一分句含「待核实」「未确认」「候选」,才放行。其余照旧拦。
|
||||
- 「死亡」:同一分句里有「不得 / 不能 / 不会 / 不作 / 不做 / 不预测 / 禁止」中任何一个,并且出现在「死亡」之前,就放行。其余照旧拦。
|
||||
6. **依据节被拦时的确定性审计**
|
||||
- 当 `basis` 节被拦时,原处不再只显示「暂不显示」,而是显示固定生成的最小审计。内容只取自事实包的 `evidence_grades.claims`,不经过模型:
|
||||
- 每类结论一行:中文名、上限等级、必需依赖里缺失或在边界上的项,以及 `must_disclose` 列表;
|
||||
- 一句固定说明:「这一节的模型文字没有通过核对,下面是由计算直接生成的依据清单。」
|
||||
- 中文名和依赖项的中文写法放在 `personal-report-reading-contract.ts` 的常量里。不认识的 id 原样显示,不能编造。
|
||||
- 页面和 MD 导出(`report-export-blocks.ts`)都要换成这份审计。
|
||||
- 其他节被拦时,行为不变。
|
||||
7. 拦截正文里的准确率说法:句子里出现「成功率」「准确率」「命中率」,并且带百分数或「几成」,就退回,原因记为 `accuracy_claim`(她 §4 末条)。
|
||||
|
||||
## 不要碰
|
||||
|
||||
- 现有检查的逻辑,第 5 条除外
|
||||
- `writeReportReading`
|
||||
- `references/**`、`scripts/**`
|
||||
- 冻结文件
|
||||
|
||||
## 验收标准(写进新测试,全部用合成文字)
|
||||
|
||||
1. 第 1 到 5 条和第 7 条,每条至少测两种情况:一个触发、一个合法写法不触发。第 1 条还要测:「水星为第一、十宫主,落第五宫」不触发。
|
||||
2. 第 5 条:「检测器检出,待核实」不拦,「检测器显示」照旧拦;「本报告不得预测死亡」不拦,「有死亡风险」照旧拦。
|
||||
3. 第 6 条:`basis` 被拦时,页面渲染和 MD 导出都出现审计行,其他节不受影响。
|
||||
4. 用 `/workspace/eval-upstream-1107/report-192-acceptance-1112/报告解读层-验收材料-1112.zip` 里三份 `draft-*.raw.json` 与对应的 `facts-*.json` 跑复查:
|
||||
- A-2 的财富节(第 1、2 条)和健康节(第 3 条)必须被拦;
|
||||
- B-1 的健康节(第 4 条)必须被拦;
|
||||
- A-1 的依据节不能再因为「检测器」或「死亡」被拦(如果还因为别的原因被拦,把原因写进 PROGRESS)。
|
||||
- 这一条可以写成一次性脚本,结果写进 PROGRESS,不进测试。zip 里的 facts 不得提交进仓库。
|
||||
5. 已有的复查测试全部通过:`report-reading-20261027`、#207 的测试和 MD 导出测试。
|
||||
|
||||
## 自测命令(在 frontend 目录)
|
||||
|
||||
```
|
||||
npx tsx --test tests/report-reading-postcheck-facts-20261112.test.ts tests/report-reading-postcheck-gaps-20261112.test.ts tests/report-reading-20261027.test.tsx tests/report-md-export-reading-20261111.test.ts
|
||||
npx tsc --noEmit -p .
|
||||
npx eslint .
|
||||
```
|
||||
|
||||
另外在仓库根目录运行 Python 快速质量门:
|
||||
|
||||
```
|
||||
python3 scripts/run_quality_gate.py --profile quick --skip-frontend-runtime
|
||||
```
|
||||
|
||||
## 何时停下来选「需拍板」
|
||||
|
||||
- 第 4 条的词表在三份样稿里误伤了合法的结构描述(例如「第六宫主」),而且不放宽就无法避免。
|
||||
- 第 6 条需要改模型提示或事实包。
|
||||
@@ -1,115 +0,0 @@
|
||||
# TASK:报告解读运行时口径:废止旧成功率与旧 ABC 口径,拆分功能吉凶 profile,附录如实列核验行(2026-11-12)
|
||||
|
||||
- Gitea:#217
|
||||
- 分支:`codex/reading-runtime-sources-20261112`,基于 staging `1ba0364a7a8c0f964c3e21c21cb304aaa1899375`
|
||||
- Bug 编号:BUG-1861
|
||||
- 验收:Claude(jyotisha-e6)
|
||||
|
||||
不得自行写「产品决定/裁定」。任务书没写到的取舍,一律选「需拍板」。
|
||||
|
||||
不得修改或删除既有断言来让测试通过。确需修改的,在 PROGRESS 里逐条写明原值、新值和原因。
|
||||
|
||||
占星师的引擎和资料(`scripts/jyotish_engine.py`、`scripts/yoga_engine.py`、`references/` 下她仓库已有的文件、`SKILL.md`)一律不改。产品差异只能放进单独的产品文件,并在 `references/product_runtime_contract.md` 公告(项目规则 2026-11-07)。
|
||||
|
||||
占星师回复原文:`docs/tasks/ref-astrologer-acceptance-reply-20261112.md`,由本分支第一个提交带入。下文的「她 §N」都指这份文件。
|
||||
|
||||
本任务**允许**修改 `references/product_runtime_contract.md`(产品文件),但只能改下文点名的地方。
|
||||
|
||||
## 背景
|
||||
|
||||
### A. 旧提示原文仍有运行效力(她 §4 末条)
|
||||
|
||||
报告解读的系统提示由 `frontend/src/mastra/report-reading-skill.ts` 拼成:`SKILL.md` 加上 `REPORT_READING_REFERENCES` 里列的文件,最后加产品合同的 report-reading 块。
|
||||
|
||||
其中 `references/precision-reading-methodology.md`「三、九层复合方法」写有「做到第六层,平均85%的预测成功率」,另外好几处还用着旧的 A/B/C 置信度口径,例如:
|
||||
|
||||
- `strict-workflow-router.md` 的 `Confidence label: A/B/C/insufficient` 和 `A/B/C/fail`
|
||||
- `precision-reading-methodology.md` 的 `置信度:[A/B/C]`
|
||||
|
||||
她要求保留原文以便追溯,但必须明确废止它们在运行时的效力,不能只靠末尾的新合同去隐式覆盖。没有验证设计、分母和适用范围,就不得向用户宣称准确率。
|
||||
|
||||
### B. 功能吉凶 profile 混用(她 §4)
|
||||
|
||||
- 事实包 `functional.functional_profile` 选的是 `pvr_table30_v1`。
|
||||
- 提示里加载的 `references/functional-role-profile.md` 是**我们改过的** BPHS 表(`bphs_ch34_general_with_sign_exceptions_v3_166q4`)。它与她 `8aaf6099` 的同名文件有几处不同:
|
||||
- 双子、处女上升的土星 mixed 两行;
|
||||
- 34.20、34.35 两处注释;
|
||||
- 她的「Revision v3」整段被换成了我们的 #163 说明。
|
||||
- 她的裁定:
|
||||
- 按已选的 profile(PVR)给结论。
|
||||
- BPHS 修订作为另一个具名、带版本的 profile,附来源定位、完整差异和十二上升测试,另行送审。
|
||||
- 不接受「只有两个词差异」这种笼统说明。
|
||||
|
||||
### C. 核验行在附录里如实显示(她 §4,产品 2026-11-12 决定)
|
||||
|
||||
她要求真实案例与 MEVG 的状态保留在用户可读的证据附录里。产品 2026-11-12 的决定如下:
|
||||
|
||||
- **个人报告**里可折叠的「计算依据」附录如实列两行:「网上资料核对:未完成」「真实案例校准:未完成」,按实际状态写。
|
||||
- 正文和聊天回答照旧不提(BUG-1794 对正文仍然有效)。
|
||||
- 这属于 BUG-1794 在**附录**范围的部分回撤,要在产品合同里写明。
|
||||
- 年运报告的附录由 #210/#212 负责,本任务不管。
|
||||
|
||||
## 要改的文件
|
||||
|
||||
- `references/product_runtime_contract.md`:
|
||||
- report-reading 块之前的 G 节公告区:新增「运行时废止」一段和「功能吉凶 profile」一段;
|
||||
- BUG-1794 的两处注释(第 14、128 行附近):各追加一句附录例外,并写原值、新值和原因。
|
||||
- 其他段落一律不动。
|
||||
- `references/functional-role-profile.md`:恢复为她 `8aaf6099` 的原文,逐字节一致。原文见 `/workspace/yinduzhanxing`,用 `git -C /workspace/yinduzhanxing show 8aaf6099:references/functional-role-profile.md` 取得。
|
||||
- 新建 `references/product_functional_profile_bphs_v3_166q4.md`:我们当前的版本,加上文件头和差异表(见下)。
|
||||
- `scripts/functional_benefics.py`:`profile_source` 和 `rule_reference` 两处字符串指向新文件,只改字符串。
|
||||
- 「计算依据」附录的生成代码:自己定位,先看 `frontend/src/lib/report-toc-groups.ts`、`frontend/src/components/personal-report/`、`scripts/report_render/audit_chapters.py`;在 PROGRESS 写明改的是哪里。
|
||||
- 新建 `tests/test_functional_profile_split_20261112.py`
|
||||
- 新建 `frontend/tests/report-reading-runtime-sources-20261112.test.ts`
|
||||
- `frontend/tests/report-no-web-wording-20261109.test.ts`:只在它对附录也断言「不出现」时才收窄到正文,并在 PROGRESS 写明原值、新值和原因。
|
||||
- `docs/BUG_HISTORY.md`:末尾追加一条
|
||||
- `docs/tasks/PROGRESS-reading-runtime-sources-20261112.md`
|
||||
|
||||
## 要做的
|
||||
|
||||
1. **盘点。** 在 `SKILL.md` 和 `REPORT_READING_REFERENCES` 列出的每份文件里,找出所有准确率、成功率、命中率的数字说法,以及所有 A/B/C 置信度口径的位置。每处记下文件、标题、行号和原文片段,列进 PROGRESS。
|
||||
- SKILL.md「阶段零 A/B/C 入口路由」是路由分类,不是置信度,不算。其他类似情况也照此分辨,并写明理由。
|
||||
2. **废止段。** 在产品合同的 G 节公告区写「运行时废止」:
|
||||
- 逐条点名第 1 步找到的位置,说明「保留原文以便追溯,运行时无效」。
|
||||
- 置信度只按 `report_evidence_grade_v1` 的 A–D 上限,不得向用户宣称任何准确率或成功率。
|
||||
3. **profile 段。** 在 G 节写明:
|
||||
- 报告的功能吉凶以事实包 `functional.functional_profile`(目前是 `pvr_table30_v1`)为准;
|
||||
- `references/functional-role-profile.md` 是她的 BPHS 原文,只作对照;
|
||||
- 产品修订的 BPHS 表是 `references/product_functional_profile_bphs_v3_166q4.md`,状态为待她审核,不进报告结论。
|
||||
4. **新 profile 文件。** 文件头写清以下内容:
|
||||
- profile id;
|
||||
- 来源:她 11-05 #166 Q4 裁定、BUG-1570、BUG-1590;
|
||||
- 与她 `8aaf6099` 版本的**完整**差异表,逐行列出新增、删除和改写,被替换掉的「Revision v3」段也要列。
|
||||
5. **测试。**
|
||||
- Python 测试:
|
||||
- `references/functional-role-profile.md` 的 sha256 等于她 `8aaf6099` 版本的 sha256(把期望值写死在测试里,并注明来源提交);
|
||||
- 十二个上升都用 `functional_benefics` 的 BPHS profile 求土星分类,双子、处女是 mixed,其余十个上升的结果写死成期望值(期望值取自现有代码,写进 PROGRESS);
|
||||
- 对 Jobs 和 Obama 两张公开盘调用报告事实包(方法参照 `tests/test_reading_facts_v3_20261112.py`),`functional.functional_profile == "pvr_table30_v1"`。
|
||||
- 前端测试:
|
||||
- `buildReportReadingSystemPrompt`(以实际函数名为准)的输出里,含有新的废止段和 profile 段;
|
||||
- 附录渲染出「网上资料核对」「真实案例校准」两行,状态为「未完成」;
|
||||
- 正文区域(解读的九节)仍然不出现这两个词。
|
||||
6. **附录两行。**
|
||||
- 状态取自现有后台记录(回执或审计里的 MEVG / Real Case 行)。没有记录就写「未完成」,不得写成「已完成」或「通过」。
|
||||
- 行名用中文:「网上资料核对」「真实案例校准」。附录里不出现「MEVG」字样。
|
||||
|
||||
## 不要碰
|
||||
|
||||
- `frontend/src/lib/personal-report-reading.ts`(#207 和 BUG-1860 在改)
|
||||
- `scripts/report_render/evidence_grade.py`、`reading_facts.py`
|
||||
- 聊天的提示和回答
|
||||
- 冻结文件
|
||||
- 她的其他 references 文件(只恢复 `functional-role-profile.md` 这一份)
|
||||
|
||||
## 自测命令
|
||||
|
||||
```
|
||||
python3 -m pytest -q tests/test_functional_profile_split_20261112.py tests/test_functional_yogakaraka_kendra_trikona.py tests/test_reading_facts_v3_20261112.py
|
||||
python3 scripts/run_quality_gate.py --profile quick --skip-frontend-runtime
|
||||
cd frontend && npx tsx --test tests/report-reading-runtime-sources-20261112.test.ts tests/report-no-web-wording-20261109.test.ts tests/report-reading-20261027.test.tsx && npx tsc --noEmit -p . && npx eslint .
|
||||
```
|
||||
|
||||
## 何时停下来选「需拍板」
|
||||
|
||||
- 恢复她的 `functional-role-profile.md` 之后,聊天、生时纠正或盘面页的测试失败。说明这些地方实际读的是我们改过的表,先停下来报告。
|
||||
- 找不到「计算依据」附录的单一生成点,或者附录同时用于聊天。
|
||||
@@ -1,85 +0,0 @@
|
||||
# TASK:事实包参与层改用两轴状态,Badhaka 区分宫与宫主并补十二上升测试(2026-11-12)
|
||||
|
||||
- Gitea:#216
|
||||
- 分支:`codex/reading-status-axes-badhaka-20261112`,基于 staging `1ba0364a7a8c0f964c3e21c21cb304aaa1899375`
|
||||
- Bug 编号:BUG-1859
|
||||
- 验收:Claude(jyotisha-e6)
|
||||
|
||||
不得自行写「产品决定/裁定」。任务书没写到的取舍,一律选「需拍板」。
|
||||
|
||||
不得修改或删除既有断言来让测试通过。确需修改的,在 PROGRESS 里逐条写明原值、新值和原因。
|
||||
|
||||
占星师的引擎和资料(`scripts/jyotish_engine.py`、`scripts/yoga_engine.py`、`references/` 下她仓库已有的文件、`SKILL.md`)一律不改。产品差异只能放进单独的产品文件,并在 `references/product_runtime_contract.md` 公告(项目规则 2026-11-07)。
|
||||
|
||||
占星师回复原文:`docs/tasks/ref-astrologer-acceptance-reply-20261112.md`,由本分支第一个提交带入。下文的「她 §N」都指这份文件。
|
||||
|
||||
## 背景
|
||||
|
||||
### 两轴状态(她 §4「状态与可见审计」)
|
||||
|
||||
`scripts/report_render/reading_facts.py` 给每个领域的参与层写一个单轴 `state`,取值有 `executed`、`not_routed`、`not_in_domain_route`、`not_computed`、`blocked` 等。
|
||||
|
||||
- 已经算出、只是本领域不用它下结论的层(Indu、Gulika 等),现在的状态是 `not_in_domain_route`,读起来像是没执行。
|
||||
- 她要求拆成两个轴:`execution_status`(`executed` / `blocked` / `not_applicable` / `not_computed`)和 `interpretation_usage`(`primary` / `supporting` / `conditional` / `not_used`)。
|
||||
- 已算出但没用的层,必须是 `execution_status=executed`、`interpretation_usage=not_used`。
|
||||
- `blocked` 必须写明具体阻碍:缺输入、调用失败或验证失败。
|
||||
- `not_applicable` 必须写明范围或适用条件的理由。
|
||||
|
||||
仓库里已经有词表 `scripts/report_render/technique_status_axes.py`(`CALCULATION_LABELS` / `INTERPRETATION_LABELS`),要复用它,不要另起一套。
|
||||
|
||||
### Badhaka(她 §3、§4)
|
||||
|
||||
产品模块 `scripts/report_render/badhaka_facts.py` 返回的记录用 `house`、`sign`、`lord` 等字段,没有区分「阻碍宫(Badhaka sthana)」和「阻碍宫主(Badhakesh)」。样稿因此写成「阻碍星落在第七宫」。
|
||||
|
||||
她要求下一包附上:实现、profile、十二上升单测,以及星与宫区分的测试。
|
||||
|
||||
## 要改的文件
|
||||
|
||||
- `scripts/report_render/reading_facts.py`:只改参与层行的构造,以及汇总 `blocked_states` 的部分
|
||||
- `scripts/report_render/badhaka_facts.py`
|
||||
- 新建 `tests/test_reading_status_axes_20261112.py`
|
||||
- 新建 `tests/test_badhaka_twelve_ascendants_20261112.py`
|
||||
- `tests/test_badhaka_facts_20261112.py`、`tests/test_reading_facts_v3_20261112.py`:只在字段改名处改,并逐条列进 PROGRESS
|
||||
- `docs/BUG_HISTORY.md`:末尾追加一条
|
||||
- `docs/tasks/PROGRESS-reading-status-axes-badhaka-20261112.md`
|
||||
|
||||
## 要做的
|
||||
|
||||
1. 参与层每一行加两个字段:`execution_status` 和 `interpretation_usage`。
|
||||
- 原有的 `state` 字段保留、取值不变。前端 `computedPointsOf` 和 `evidence_grade.py` 都读它。
|
||||
- 映射规则:
|
||||
- `executed` 且本领域在用 → `executed` + `primary`(或按现有数据写 `supporting`,规则写进 PROGRESS)
|
||||
- `not_in_domain_route` 且有值 → `executed` + `not_used`
|
||||
- `not_routed` 且有值 → `executed` + `not_used`;没有值 → `not_computed` + `not_used`
|
||||
- `not_computed` → `not_computed` + `not_used`
|
||||
- `blocked` 类 → `blocked`,同时带 `blocked_reason`(`missing_input` / `call_failed` / `validation_failed` 之一,按实际原因写)
|
||||
- 任何一行不得出现 `execution_status=not_computed` 却又附有计算值的情况。
|
||||
2. `badhaka_facts()` 的记录改用这些字段名:
|
||||
- `badhaka_sthana`:`{house, sign, occupants}`
|
||||
- `badhakesh`:`{planet, sign, house, dignity}`
|
||||
- `ascendant_sign`、`ascendant_modality`、`source`、`status` 保留
|
||||
- 加 `profile: "badhaka_modality_v1"`,说明「动 → 11、固 → 9、双 → 7,按 badhaka-obstacle-planet-guide.md §二」
|
||||
- 旧字段名不再输出。`reading_facts.STRUCTURED_VALUES["Badhaka"]` 和 `consult_card_domain_facts.py` 里引用它的地方同步改。改之前先 grep 全仓,列进 PROGRESS。
|
||||
3. 十二上升测试:每个上升都用合成行星位置,断言 Badhaka 宫的宫位、星座、宫主。另外断言宫内行星只列落在该星座里的星,宫主自己不算进去(除非宫主恰好落在宫内)。
|
||||
4. 区分测试:造一个宫主不在 Badhaka 宫内的例子,断言 `badhakesh.house` 不等于 `badhaka_sthana.house`,而且 `badhaka_sthana.occupants` 里不含宫主。
|
||||
|
||||
## 不要碰
|
||||
|
||||
- `frontend/**`
|
||||
- `references/**`
|
||||
- `scripts/yoga_engine.py`、`scripts/jyotish_engine.py`(只能调用 `badhaka_house`)
|
||||
- `scripts/report_render/evidence_grade.py`
|
||||
|
||||
## 验收标准
|
||||
|
||||
1. 用 Jobs(1955-02-24 19:15,UTC−8,Raman 岁差,平均交点)和 Obama 两张公开盘生成事实包,健康领域 Badhaka 的结果是:
|
||||
- Jobs:Badhaka 宫为第 7 宫双鱼,宫内有月亮;Badhakesh 为木星,在第 10 宫。
|
||||
- Obama:Badhaka 宫为第 11 宫天蝎;Badhakesh 为火星,在第 9 宫。
|
||||
生成方法参考现有的 `tests/test_reading_facts_v3_20261112.py`。
|
||||
2. 两张盘的 Indu、Gulika、Mandi、Pranapada 行都是 `executed` + `not_used`。
|
||||
3. 新测试、改过的旧测试和 Python 快速质量门全部通过。
|
||||
|
||||
## 何时停下来选「需拍板」
|
||||
|
||||
- 某个领域的「在用 / 辅助」无法从现有数据判断。
|
||||
- 改字段名会影响 `frontend/` 下你不许改的代码。
|
||||
@@ -1518,8 +1518,6 @@ Bhava Bala 保留完整十二宫力量表;Score / Weight 等领域表头不单
|
||||
|
||||
## 报告核对层与独立附录(2026-09-23)
|
||||
|
||||
个人报告「计算依据」内的「核验记录」默认折叠。网上资料核对和真实案例校准各一行,按记录显示状态。导出保留两行,打印时按现有核对表规则展开,打印后恢复展开状态。解读区不显示这两行。
|
||||
|
||||
个人报告保留原 Markdown 阅读、目录隔离与 memo 边界;文后独立渲染服务端分盘和事实表。分盘复用 VedicChartSvg 与 chart-grid/chart-card,不使用 float 或负 margin。事实表默认折叠、可键盘展开,横向溢出仅在表格区域滚动,缺数据不补写。Markdown 表里,只有表头含速度、Speed、度数或黄经的数值表(`.personal-report-table-numeric`)单行并按内容撑宽,放不下时在表自己的框里横滑;作用说明、建议这类整句表在页内换行。打印时所有报告表都是页宽,单元格可以换行,纸上不横滑。Sade Sati 明确缺三轮日期。09-24 reader-actions D2 撤下操作区「下载原始附录」入口;相关组件、路由和 helper 因删除权限阻塞暂留源码,不当作完成清理。不接普通下载 fallback、不增加 spinner。旧缓存不升级、不重算;没有结构化层就不显示占位盘。文案遵守 VOICE:不把可计算写成已验证。
|
||||
|
||||
事实表用中文列名和领域行列呈现,不把引擎路径当行名。主运、分运与小运、SAV 与 BAV、年度位置与 Saham 分为同组子表;缺失的年度行星位置明确说明,不用本命盘替代。机器溯源路径保留在数据中、不渲染。打印或保存 PDF 前自动展开此核对层全部折叠组,完成或取消后恢复原状态;打印解除表格横向滚动裁剪、重复表头,宽 BAV 表仍保留十二星座列。不新增打印入口或等待动画。
|
||||
|
||||
@@ -18,7 +18,6 @@ import {
|
||||
import { reportAiNotice } from "@/lib/ai-generated-notice";
|
||||
import { parseReportFocusBlock, reportTitleByline } from "@/lib/report-focus-block";
|
||||
import { reportChartGrid } from "@/lib/report-chart-grid-rehype";
|
||||
import { bindFactTablePrint } from "@/lib/report-fact-table-print";
|
||||
import { remarkCjkStrong } from "@/lib/remark-cjk-strong";
|
||||
import { NorthIndianChartSvg } from "@/components/personal-report/vedic-chart-svg";
|
||||
import { ReportEdgeJump } from "@/components/personal-report/report-edge-jump";
|
||||
@@ -88,21 +87,6 @@ export function reportBirthLine(markdown: string): string | null {
|
||||
return value && value.length <= 40 ? value : null;
|
||||
}
|
||||
|
||||
function VerificationAppendix({ language, children }: { language: ReportLanguage; children: ReactNode }) {
|
||||
const root = useRef<HTMLDivElement>(null);
|
||||
useEffect(() => {
|
||||
if (root.current) return bindFactTablePrint(root.current, window);
|
||||
}, []);
|
||||
return (
|
||||
<div ref={root} className="personal-report-fact-tables">
|
||||
<details>
|
||||
<summary>{language === "en" ? "Verification" : "核验记录"}</summary>
|
||||
<div className="personal-report-fact-body">{children}</div>
|
||||
</details>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function markdownComponents(
|
||||
headings: readonly LongformHeading[],
|
||||
language: ReportLanguage,
|
||||
@@ -128,14 +112,11 @@ function markdownComponents(
|
||||
return <span>{children}</span>;
|
||||
},
|
||||
img: () => null,
|
||||
table: ({ children }) => {
|
||||
const labels = tableHeaderLabels(children);
|
||||
const table = <div className={reportTableWrapClass(labels)}><table>{children}</table></div>;
|
||||
const verificationHeader = language === "en" ? "Verification item|Status" : "核验项目|状态";
|
||||
return labels.join("|") === verificationHeader
|
||||
? <VerificationAppendix language={language}>{table}</VerificationAppendix>
|
||||
: table;
|
||||
},
|
||||
table: ({ children }) => (
|
||||
<div className={reportTableWrapClass(tableHeaderLabels(children))}>
|
||||
<table>{children}</table>
|
||||
</div>
|
||||
),
|
||||
td: ({ children }) => <td>{renderNakshatraLabel(children)}</td>,
|
||||
h1: ({ children }) => {
|
||||
const title = flattenText(children).trim();
|
||||
|
||||
@@ -143,7 +143,7 @@ export const EVIDENCE_GRADE_LABEL = "证据等级";
|
||||
export const EVIDENCE_GRADE_MEANING: Readonly<Record<EvidenceGrade, string>> = {
|
||||
A: "在明确的案例集里经过较强的独立验证",
|
||||
B: "有对得上的真实案例支持,几种独立方法方向一致",
|
||||
C: "有星盘依据,还没有对得上的案例",
|
||||
C: "有星盘依据,还没有用真实案例校准过",
|
||||
D: "关键依据缺失,暂不能判断",
|
||||
};
|
||||
|
||||
@@ -156,7 +156,7 @@ export function readingSectionView(section: ReportReading["sections"][number]):
|
||||
return { status: "ok", paragraphs: section.paragraphs.map(({ text, grade }) => (grade ? { text, grade } : { text })) };
|
||||
}
|
||||
|
||||
/** 「证据等级 C:有星盘依据,还没有对得上的案例」 */
|
||||
/** 「证据等级 C:有星盘依据,还没有用真实案例校准过」 */
|
||||
export function evidenceGradeTag(grade: EvidenceGrade): string {
|
||||
return `${EVIDENCE_GRADE_LABEL} ${grade}:${EVIDENCE_GRADE_MEANING[grade]}`;
|
||||
}
|
||||
|
||||
@@ -28,7 +28,6 @@ export type ProductContractBlock =
|
||||
| "natal-instructions"
|
||||
| "mevg-blocked-policy"
|
||||
| "shared-reading"
|
||||
| "report-reading-source-notices"
|
||||
| "report-reading";
|
||||
|
||||
let cached: string | null = null;
|
||||
|
||||
@@ -54,11 +54,9 @@ export function reportReadingPrompt(): ReportReadingPrompt {
|
||||
if (!body) throw new Error(`Skill ${jyotishSkillPackage.name} has no method body for the report reading`);
|
||||
const references = REPORT_READING_REFERENCES.map((path) => ({ path, ...read(path) }));
|
||||
const contract = read(PRODUCT_RUNTIME_CONTRACT_FILE);
|
||||
const sourceNotices = productContractBlock("report-reading-source-notices", {}, contract.text);
|
||||
const rules = productContractBlock("report-reading", { CLAIM_TYPES: REPORT_CLAIM_TYPES.join("、") }, contract.text);
|
||||
const rules = productContractBlock("report-reading", { CLAIM_TYPES: REPORT_CLAIM_TYPES.join("、") });
|
||||
const system = [
|
||||
`The jyotish-vedic-astrology skill is loaded for a written report. Its full SKILL.md (the astrologer's upstream skill, verbatim) is quoted below, then the references this report routes to, verbatim. Follow its method, routing and truth boundaries. There are no tools in this call: everything you may use is in this message and the chart facts.`,
|
||||
sourceNotices,
|
||||
REPORT_READING_SKILL_MARKER,
|
||||
body,
|
||||
...references.map((file) => `<reference path="${file.path}">\n${file.text.trim()}\n</reference>`),
|
||||
|
||||
@@ -389,7 +389,7 @@ test("the page keeps a reading only in the stored shape and shows it before the
|
||||
assert.ok(readingAt > zh.indexOf("印度占星星盘报告") && readingAt < basisAt && basisAt < engineAt, zh.slice(0, 400));
|
||||
for (const title of ["总评", "现在这几年", "几条建议", "依据与边界"]) assert.ok(zh.includes(`<h3>${title}</h3>`), title);
|
||||
// #192(产品 2026-11-12 原型方案 B):领域节每段下面一行灰字,写等级和含义;总评等不分级的节没有
|
||||
assert.ok(zh.includes("证据等级 C:有星盘依据,还没有对得上的案例"), "grade line");
|
||||
assert.ok(zh.includes("证据等级 C:有星盘依据,还没有用真实案例校准过"), "grade line");
|
||||
assert.match(zh, /<p class="personal-report-evidence-grade">证据等级 C:/, "a plain line, no tap to open");
|
||||
const withheld = (accept(draft({ love: [`${PARAGRAPH}你注定晚婚。`] })) as { reading: unknown }).reading;
|
||||
const shown = renderToStaticMarkup(<PersonalReportMarkdownView markdown={markdown} reading={withheld as never} language="zh" />);
|
||||
|
||||
@@ -1,56 +0,0 @@
|
||||
import assert from "node:assert/strict";
|
||||
import test from "node:test";
|
||||
import { createElement } from "react";
|
||||
import { renderToStaticMarkup } from "react-dom/server";
|
||||
|
||||
import { PersonalReportMarkdownView } from "../src/components/personal-report/personal-report-markdown-view.tsx";
|
||||
import {
|
||||
EVIDENCE_GRADE_CONTRACT,
|
||||
REPORT_READING_SECTIONS,
|
||||
type ReportReading,
|
||||
} from "../src/lib/personal-report-reading-contract.ts";
|
||||
import { projectOrdinaryReportMarkdown } from "../src/lib/report-public-projection.ts";
|
||||
import { reportReadingPrompt, REPORT_READING_SKILL_MARKER } from "../src/mastra/report-reading-skill.ts";
|
||||
|
||||
test("report writer retires the historic rules before the original references", () => {
|
||||
const { system, skill } = reportReadingPrompt();
|
||||
const noticesAt = system.indexOf("### 运行时废止");
|
||||
const profileAt = system.indexOf("### 功能吉凶 profile");
|
||||
const sourceAt = system.indexOf(REPORT_READING_SKILL_MARKER);
|
||||
assert.ok(noticesAt >= 0 && noticesAt < profileAt && profileAt < sourceAt);
|
||||
const notices = system.slice(noticesAt, sourceAt);
|
||||
assert.match(notices, /保留原文以便追溯,运行时无效/);
|
||||
assert.match(notices, /report_evidence_grade_v1/);
|
||||
assert.match(notices, /不得向用户宣称准确率、成功率或命中率/);
|
||||
assert.match(notices, /functional\.functional_profile.*pvr_table30_v1/);
|
||||
assert.match(notices, /product_functional_profile_bphs_v3_166q4\.md.*待占星师审核/);
|
||||
assert.match(system.slice(sourceAt), /平均85%的预测成功率/);
|
||||
assert.ok(skill.loaded.some(({ path }) => path === "references/functional-role-profile.md"));
|
||||
assert.ok(!skill.loaded.some(({ path }) => path.includes("product_functional_profile_bphs")));
|
||||
});
|
||||
|
||||
test("personal report verification appears in the folded appendix outside all nine reading sections", () => {
|
||||
const reading: ReportReading = {
|
||||
version: 3,
|
||||
language: "zh",
|
||||
contract: EVIDENCE_GRADE_CONTRACT,
|
||||
skill: { name: "fixture", version: "fixture", loaded: [{ path: "SKILL.md", sha256: "0".repeat(64) }] },
|
||||
sections: REPORT_READING_SECTIONS.map((section) => ({
|
||||
...section, status: "ok", paragraphs: [{ text: "先核对盘面材料。", grade: "C" }],
|
||||
})),
|
||||
};
|
||||
const markdown = [
|
||||
"# 个人报告", "", "## 技法审计与证据边界", "", "| 核验项目 | 状态 |", "| ------ | ------ |",
|
||||
"| 网上资料核对 | 未完成 |", "| 真实案例校准 | 未完成 |",
|
||||
].join("\n");
|
||||
const html = renderToStaticMarkup(createElement(PersonalReportMarkdownView, {
|
||||
markdown: projectOrdinaryReportMarkdown(markdown), reading, drawnThrough: 0,
|
||||
}));
|
||||
const body = /<section[^>]+class="personal-report-reading"[^>]*>([\s\S]*?)<\/section>/.exec(html)?.[1];
|
||||
assert.ok(body);
|
||||
assert.equal(body.match(/class="personal-report-reading-part"/g)?.length, 9);
|
||||
assert.doesNotMatch(body, /网上资料核对|真实案例校准/);
|
||||
assert.match(body, /证据等级 C/);
|
||||
assert.match(html, /<details><summary>核验记录<\/summary>[\s\S]*网上资料核对<\/td><td>未完成[\s\S]*真实案例校准<\/td><td>未完成/);
|
||||
assert.doesNotMatch(html, /MEVG/);
|
||||
});
|
||||
@@ -1,6 +1,8 @@
|
||||
# Functional ownership profile v3
|
||||
# Functional ownership profile v3 + #166 Q4
|
||||
|
||||
Profile: `bphs_ch34_general_with_sign_exceptions_v3`.
|
||||
Profile: `bphs_ch34_general_with_sign_exceptions_v3_166q4` (upstream v3 table, i.e. Aries / Scorpio
|
||||
Venus challenging (BUG-1570), plus the #166 Q4 Gemini / Virgo Saturn mixed rows
|
||||
(BUG-1590)). Until 2026-11-07 this table was mislabelled `..._v2`.
|
||||
Source: BPHS chapter 34, general rules 2–17 and ascendant entries 19–44,
|
||||
public translation at https://jyotishvidya.com/ch34.htm (inspected 2026-10-02).
|
||||
This is a named implementation of a translation, not all-lineage agreement.
|
||||
@@ -17,9 +19,11 @@ not automatically malefic. Sun/Moon eighth lordship has the stated exception.
|
||||
Unspecified angular or second-house-only cases remain neutral/mixed.
|
||||
|
||||
Explicit exceptions are recorded in `ROLE_OVERRIDES`: Aries Mars helpful in
|
||||
association (mixed), Venus challenging; Taurus Venus challenging, Sun supportive; Gemini Jupiter
|
||||
challenging; Virgo Jupiter challenging; Libra Venus mixed, Mars challenging;
|
||||
Scorpio Mars mixed, Sun yoga-supporting and Venus challenging; Sagittarius Jupiter/Mercury mixed
|
||||
association (mixed), Venus challenging (34.20); Taurus Venus challenging, Sun
|
||||
supportive; Gemini Jupiter challenging, Saturn mixed (#166 Q4); Virgo Jupiter
|
||||
challenging, Saturn mixed (#166 Q4); Libra Venus
|
||||
mixed, Mars challenging; Scorpio Mars mixed, Sun yoga-supporting, Venus
|
||||
challenging (34.35); Sagittarius Jupiter/Mercury mixed
|
||||
(Mercury's association with Sun is separate); Capricorn Moon challenging;
|
||||
Aquarius Mars challenging, Mercury mixed; Pisces Mercury challenging.
|
||||
The remaining general fallbacks are identified in each planet's `role_basis`;
|
||||
@@ -31,8 +35,9 @@ functional benefic does not guarantee a favorable event. No death or outcome
|
||||
prediction is inferred from this classification. The Yoga engine's natural
|
||||
predicates and active Yoga rule library are not replaced by ownership roles.
|
||||
|
||||
Revision v3 (2026-11-04) restores the explicit Venus entries in ch34.19–22
|
||||
and35–36. This corrects omissions independently of rectification fit. Existing
|
||||
score weights remain unchanged; native event evidence records the functional
|
||||
profile and its weighted/unweighted auxiliary contribution. Historical scores
|
||||
must not be described as recomputed merely because this profile changed.
|
||||
Chart page layout (issue #163): `scripts/chart_view_points/functional_roles.py` lays this
|
||||
profile out one row per body for the 盘面数据 table. It adds no rule: the
|
||||
functional class is the profile's; Rahu/Ketu are `pending` (association not
|
||||
evaluated); Yogakaraka and Badhaka (lord of `yoga_engine.badhaka_house`) are
|
||||
separate labels; a natural benefic owning 4/7/10 and none of 1/5/9 is marked
|
||||
Kendradhipati with `conditions_not_evaluated`.
|
||||
|
||||
@@ -1,73 +0,0 @@
|
||||
# 产品功能吉凶 profile · BPHS v3 + #166 Q4
|
||||
|
||||
- Profile ID:`bphs_ch34_general_with_sign_exceptions_v3_166q4`
|
||||
- 来源:占星师 2026-11-05 #166 Q4,BUG-1570,BUG-1590。
|
||||
- 状态:产品修订,待占星师审核。个人报告以事实包选中的 profile 为准。
|
||||
|
||||
## 与占星师 `8aaf6099` 版本的逐行差异
|
||||
|
||||
行号分别按上游原文和下方产品原文计算。末组改动将上游 Revision v3 整段替换为产品 #163 的星盘页说明。
|
||||
|
||||
| 改动 | 上游原文 | 产品原文 |
|
||||
| --- | --- | --- |
|
||||
| 改写 | 1 <code># Functional ownership profile v3</code> | 1 <code># Functional ownership profile v3 + #166 Q4</code> |
|
||||
| 改写 | 3 <code>Profile: `bphs_ch34_general_with_sign_exceptions_v3`.</code> | 3 <code>Profile: `bphs_ch34_general_with_sign_exceptions_v3_166q4` (upstream v3 table, i.e. Aries / Scorpio</code> |
|
||||
| 新增 | — | 4 <code>Venus challenging (BUG-1570), plus the #166 Q4 Gemini / Virgo Saturn mixed rows</code> |
|
||||
| 新增 | — | 5 <code>(BUG-1590)). Until 2026-11-07 this table was mislabelled `..._v2`.</code> |
|
||||
| 改写 | 20 <code>association (mixed), Venus challenging; Taurus Venus challenging, Sun supportive; Gemini Jupiter</code> | 22 <code>association (mixed), Venus challenging (34.20); Taurus Venus challenging, Sun</code> |
|
||||
| 改写 | 21 <code>challenging; Virgo Jupiter challenging; Libra Venus mixed, Mars challenging;</code> | 23 <code>supportive; Gemini Jupiter challenging, Saturn mixed (#166 Q4); Virgo Jupiter</code> |
|
||||
| 改写 | 22 <code>Scorpio Mars mixed, Sun yoga-supporting and Venus challenging; Sagittarius Jupiter/Mercury mixed</code> | 24 <code>challenging, Saturn mixed (#166 Q4); Libra Venus</code> |
|
||||
| 新增 | — | 25 <code>mixed, Mars challenging; Scorpio Mars mixed, Sun yoga-supporting, Venus</code> |
|
||||
| 新增 | — | 26 <code>challenging (34.35); Sagittarius Jupiter/Mercury mixed</code> |
|
||||
| 改写 | 34 <code>Revision v3 (2026-11-04) restores the explicit Venus entries in ch34.19–22</code> | 38 <code>Chart page layout (issue #163): `scripts/chart_view_points/functional_roles.py` lays this</code> |
|
||||
| 改写 | 35 <code>and35–36. This corrects omissions independently of rectification fit. Existing</code> | 39 <code>profile out one row per body for the 盘面数据 table. It adds no rule: the</code> |
|
||||
| 改写 | 36 <code>score weights remain unchanged; native event evidence records the functional</code> | 40 <code>functional class is the profile's; Rahu/Ketu are `pending` (association not</code> |
|
||||
| 改写 | 37 <code>profile and its weighted/unweighted auxiliary contribution. Historical scores</code> | 41 <code>evaluated); Yogakaraka and Badhaka (lord of `yoga_engine.badhaka_house`) are</code> |
|
||||
| 改写 | 38 <code>must not be described as recomputed merely because this profile changed.</code> | 42 <code>separate labels; a natural benefic owning 4/7/10 and none of 1/5/9 is marked</code> |
|
||||
| 新增 | — | 43 <code>Kendradhipati with `conditions_not_evaluated`.</code> |
|
||||
|
||||
## 分拆前的产品原文
|
||||
|
||||
# Functional ownership profile v3 + #166 Q4
|
||||
|
||||
Profile: `bphs_ch34_general_with_sign_exceptions_v3_166q4` (upstream v3 table, i.e. Aries / Scorpio
|
||||
Venus challenging (BUG-1570), plus the #166 Q4 Gemini / Virgo Saturn mixed rows
|
||||
(BUG-1590)). Until 2026-11-07 this table was mislabelled `..._v2`.
|
||||
Source: BPHS chapter 34, general rules 2–17 and ascendant entries 19–44,
|
||||
public translation at https://jyotishvidya.com/ch34.htm (inspected 2026-10-02).
|
||||
This is a named implementation of a translation, not all-lineage agreement.
|
||||
|
||||
`owned_houses` is geometry. `lagna_lord` is identity. `yogakarakas` is the
|
||||
strict distinct 4/7/10 plus 5/9 lordship definition. The first house remains
|
||||
angular and trinal but cannot count twice. Broader textual yoga-producing
|
||||
associations (including Scorpio Sun/Moon) are not restricted by that field.
|
||||
|
||||
Functional roles use general ownership rules with explicit sign entries
|
||||
taking precedence. Lagna/trinal lords are generally supportive; 3/6/11 and
|
||||
qualified eighth lordship challenging. Sole twelfth lordship is conditional,
|
||||
not automatically malefic. Sun/Moon eighth lordship has the stated exception.
|
||||
Unspecified angular or second-house-only cases remain neutral/mixed.
|
||||
|
||||
Explicit exceptions are recorded in `ROLE_OVERRIDES`: Aries Mars helpful in
|
||||
association (mixed), Venus challenging (34.20); Taurus Venus challenging, Sun
|
||||
supportive; Gemini Jupiter challenging, Saturn mixed (#166 Q4); Virgo Jupiter
|
||||
challenging, Saturn mixed (#166 Q4); Libra Venus
|
||||
mixed, Mars challenging; Scorpio Mars mixed, Sun yoga-supporting, Venus
|
||||
challenging (34.35); Sagittarius Jupiter/Mercury mixed
|
||||
(Mercury's association with Sun is separate); Capricorn Moon challenging;
|
||||
Aquarius Mars challenging, Mercury mixed; Pisces Mercury challenging.
|
||||
The remaining general fallbacks are identified in each planet's `role_basis`;
|
||||
they are not presented as a verbatim complete classical table.
|
||||
|
||||
Natural benefic/malefic status, strength, maraka roles, conjunctions, aspects
|
||||
and period activation are independent layers. Neutral does not mean harmless;
|
||||
functional benefic does not guarantee a favorable event. No death or outcome
|
||||
prediction is inferred from this classification. The Yoga engine's natural
|
||||
predicates and active Yoga rule library are not replaced by ownership roles.
|
||||
|
||||
Chart page layout (issue #163): `scripts/chart_view_points/functional_roles.py` lays this
|
||||
profile out one row per body for the 盘面数据 table. It adds no rule: the
|
||||
functional class is the profile's; Rahu/Ketu are `pending` (association not
|
||||
evaluated); Yogakaraka and Badhaka (lord of `yoga_engine.badhaka_house`) are
|
||||
separate labels; a natural benefic owning 4/7/10 and none of 1/5/9 is marked
|
||||
Kendradhipati with `conditions_not_evaluated`.
|
||||
@@ -11,7 +11,7 @@ SKILL.md 是占星师 skill 的原文(上游 732642856/yinduzhanxing),方
|
||||
- 不给医疗、法律、投资、安全关键指令;不断言死亡、疾病诊断、离婚、妊娠、流产、不孕、孩子性别。
|
||||
<!-- 产品规则,不是占星师的方法:A-04 blocked 原样保留;外部 oracle 未闭环不阻塞有条件解读。 -->
|
||||
- 服务端标为 `blocked` 的计算原样保留为 blocked;外部 oracle 未闭环或体系分歧本身不阻塞有条件的传统解读,按 `references/interpretation_status_contract.md` 记在解读状态轴上。
|
||||
<!-- 产品规则,不是占星师的方法:A-05 联网检索、外部资料、MEVG 与真实案例校准不写进回答。原值:「本轮没有做的联网核验、真实案例校准,照实说明并降低置信度,不假装做过。」;新值:两种模式的回答都不写这几项,置信度照常封顶;原因:产品 2026-11-08 决定(BUG-1794),偏离占星师「缺口不能隐藏」的裁定(第五轮裁定 6 / 8、她的 Q5 之一与 Q6):产品选择不向用户展示;后台保留记录(回执里的技法审计行 MEVG / Global Web Evidence、Real Case Calibration 照旧)。 原值(2026-11-08):核验行只留后台。新值(2026-11-12):个人报告「计算依据」附录按记录展示网上资料核对和真实案例校准两行,个人报告正文与聊天回答照旧不写。原因:产品负责人 2026-11-12 同意在附录落实占星师 §4 的可见核验要求(BUG-1861)。 -->
|
||||
<!-- 产品规则,不是占星师的方法:A-05 联网检索、外部资料、MEVG 与真实案例校准不写进回答。原值:「本轮没有做的联网核验、真实案例校准,照实说明并降低置信度,不假装做过。」;新值:两种模式的回答都不写这几项,置信度照常封顶;原因:产品 2026-11-08 决定(BUG-1794),偏离占星师「缺口不能隐藏」的裁定(第五轮裁定 6 / 8、她的 Q5 之一与 Q6):产品选择不向用户展示;后台保留记录(回执里的技法审计行 MEVG / Global Web Evidence、Real Case Calibration 照旧)。 -->
|
||||
- 回答里不写联网检索、外部资料、MEVG、全网核验或真实案例校准(简明解读、深度解读两种模式都不写,审计表也不列这几行);这几项只记在后台。也不假装做过:不把任何结论写成已验证、已校准或确定。
|
||||
<!-- 产品规则,不是占星师的方法:A-06 Arudha 写法(占星师 2026-11-07 确认「A2 水瓶(主土星,落 2 宫)」有歧义;句式是她给的,字段是产品加的)。 -->
|
||||
- Arudha(A1–A12、AL、UL)的写法:pada 所在星座、pada 所在宫、pada 星座的主星、该主星的本命落宫是四件不同的事,证据卡分别给出 `sign`、`pada_house_from_lagna`(从本命上升按整宫数到 pada 所在星座)、`lord`、`lord_natal_house`。写主星落宫时按占星师要求的句式:「A2 位于水瓶;其主星土星位于本命第 2 宫」。不得写成「A2 水瓶(主土星,落 2 宫)」这类分不清是 pada 落宫还是主星落宫的写法;要说 pada 所在宫时写明「A2 所在宫(从上升数)为第 6 宫」,并且只引卡上的 `pada_house_from_lagna`,不自己数。
|
||||
@@ -125,7 +125,7 @@ Treat the tool result's top-level status and evidence_contract as the authoritat
|
||||
- When status is ready and evidence_contract.answer_policy.can_answer_direction is true, answer the user's actual question directly. Do not begin with infrastructure or confidence disclaimers.
|
||||
<!-- 产品规则,不是占星师的方法:P-17 可选外部交叉验证不可用不是计算失败。 -->
|
||||
- An unavailable optional provider or external cross-check is not a calculation failure. Never call it an internal error.
|
||||
<!-- 产品规则,不是占星师的方法:P-18 不谈 provider 内部词。原值(2):「The gap in external verification and real-case calibration is not hidden: plain mode states it in one short sentence next to the conclusion it limits, and professional mode records it in the Technique Audit Table's four verification rows.」;新值(2):两种模式都不写;原因(2):产品 2026-11-08 决定(BUG-1794),偏离占星师「缺口不能隐藏」的裁定(第五轮裁定 6 / 8、她的 Q5 之一与 Q6):产品选择不向用户展示;后台保留记录(回执里的技法审计行 MEVG / Global Web Evidence、Real Case Calibration 照旧)。第五轮裁定 6(BUG-1757):原值「Do not mention VedAstro, snapshot, fallback, gateway, archive, provider, MEVG, or calibration unless the user explicitly asks about methodology, or the missing layer materially blocks the exact claim they requested.」;新值:provider 内部词照旧不谈,外部核验与真实案例校准的缺口不再隐藏(白话一句短话,专业模式审计表四行);原因:占星师 2026-11-08 第五轮裁定 6「白话模式不主动提 MEVG 缺口:删除;可用一句简短说明,但不能隐藏」。 原值(2026-11-08):核验行只留后台。新值(2026-11-12):个人报告「计算依据」附录按记录展示网上资料核对和真实案例校准两行,个人报告正文与聊天回答照旧不写。原因:产品负责人 2026-11-12 同意在附录落实占星师 §4 的可见核验要求(BUG-1861)。 -->
|
||||
<!-- 产品规则,不是占星师的方法:P-18 不谈 provider 内部词。原值(2):「The gap in external verification and real-case calibration is not hidden: plain mode states it in one short sentence next to the conclusion it limits, and professional mode records it in the Technique Audit Table's four verification rows.」;新值(2):两种模式都不写;原因(2):产品 2026-11-08 决定(BUG-1794),偏离占星师「缺口不能隐藏」的裁定(第五轮裁定 6 / 8、她的 Q5 之一与 Q6):产品选择不向用户展示;后台保留记录(回执里的技法审计行 MEVG / Global Web Evidence、Real Case Calibration 照旧)。第五轮裁定 6(BUG-1757):原值「Do not mention VedAstro, snapshot, fallback, gateway, archive, provider, MEVG, or calibration unless the user explicitly asks about methodology, or the missing layer materially blocks the exact claim they requested.」;新值:provider 内部词照旧不谈,外部核验与真实案例校准的缺口不再隐藏(白话一句短话,专业模式审计表四行);原因:占星师 2026-11-08 第五轮裁定 6「白话模式不主动提 MEVG 缺口:删除;可用一句简短说明,但不能隐藏」。 -->
|
||||
- Do not mention VedAstro, snapshot, fallback, gateway, archive or provider internals unless the user explicitly asks about methodology. In either mode, do not write about web search, external sources, MEVG or real-case calibration, and do not put rows for them in the 深度解读 Technique Audit Table; the backend keeps that record.
|
||||
|
||||
Always:
|
||||
@@ -255,24 +255,6 @@ evidence_card.backstage records, for the backend only, that global web evidence
|
||||
|
||||
**产品计算,待占星师审核(BUG-1853,2026-11-12):Badhaka。** 她的引擎(`8aaf6099`)没有本命 Badhaka 模块,健康领域清单却列了它。产品在 `scripts/report_render/badhaka_facts.py` 里按 `references/badhaka-obstacle-planet-guide.md` §二(与她仓库同名文件逐字相同)从已算好的 D1 位置给出 Badhaka 宫、星座、宫主及其落点、宫内行星,只给位置、不下结论,记录里带 `status: product_computation_pending_astrologer_review`。它不改她的 `jyotish_engine.py`、`yoga_engine.py`(只调用后者的 `badhaka_house`)。报告解读和聊天健康证据卡都读这条记录。她的路由 §7 本身没有列 Badhaka;是否保留为健康必查项、用哪条规则,等她审核。
|
||||
|
||||
<!-- product-block:report-reading-source-notices -->
|
||||
### 运行时废止
|
||||
|
||||
以下位置保留原文以便追溯,运行时无效。它们不得成为个人报告的预测准确率、成功率或证据等级依据。
|
||||
|
||||
- `SKILL.md` 的「v6.9.14 核心能力」第 29 行和「Yoga 逻辑验证指标」第 57–61 行为旧技术评测。`Chara Dasha 能力升级`第 334–338 行为旧引擎对拍。第 727、731–736、738 行为既有案例吻合数字。这些数字均不得用于报告的准确率、成功率、命中率或校准声明。
|
||||
- `SKILL.md` 的「Transit Actionable Output」第 288 行、应期规则第 426 行及「精准解盘方法论」第 604 行的旧置信度口径和固定上限停止生效。第 392 行的索引只用于追溯。
|
||||
- `references/strict-workflow-router.md` 的「full-reading-strict」第 181 行、「event-verification-strict」第 332–343 行和「Confidence rules」第 409–418 行的旧 A/B/C/insufficient、A/B/C/fail 和分级门槛停止生效。
|
||||
- `references/precision-reading-methodology.md` 的「案例验证」第 42 行、「九层复合方法」第 141 行、「常见教条式失误及纠正」第 269 行的预测数字停止生效。「验证驱动」第 178 行和「实战」第 315 行的历史阈值与第 249 行的跨学科举例,不得当成本报告的验证结果或门槛。第 226–245 行与第 323 行的三级标签和示例停止生效。
|
||||
- `references/event_judgment_skeleton.md` 第 109–125 行的旧示例映射只保留方法史。第 147 行的 A–D 字段示例不能给报告自评分。
|
||||
|
||||
结论等级只按 `report_evidence_grade_v1` 提供的 A–D 上限逐条使用。报告不得向用户宣称准确率、成功率或命中率,也不得把旧样例或工程对拍写成本次验证。
|
||||
|
||||
### 功能吉凶 profile
|
||||
|
||||
个人报告以事实包 `functional.functional_profile` 选中的版本为准,当前为 `pvr_table30_v1`。引用的 `references/functional-role-profile.md` 是占星师 `8aaf6099` 的 BPHS 原文,只供对照。产品修订的 `references/product_functional_profile_bphs_v3_166q4.md` 另有具名版本,待占星师审核,不进入报告结论。不要用对照表重判已选 PVR 结果。
|
||||
<!-- /product-block:report-reading-source-notices -->
|
||||
|
||||
<!-- product-block:report-reading -->
|
||||
<!-- 产品规则,不是占星师的方法:R-01 输入边界。 -->
|
||||
你在给一份个人星盘报告写开头的「解读」。用户消息里的盘面事实(report-reading-facts-v3)是引擎已经算好的全部材料,只用这些,不编数据,不另起算法,不凭印象重算尊贵度、宫位或日期。事实里没有的层就当没有。
|
||||
|
||||
@@ -175,8 +175,7 @@ def _planet_base(view: dict, name: str) -> str | None:
|
||||
def _badhaka(view: dict) -> dict[str, Any]:
|
||||
"""BUG-1853: product computation from D1 positions (report_render/badhaka_facts.py).
|
||||
|
||||
The product record separates the obstructing house from its lord's placement
|
||||
and keeps its pending-review status.
|
||||
Her engine has no natal Badhaka module; the record says it is pending her review.
|
||||
"""
|
||||
from report_render.badhaka_facts import badhaka_facts
|
||||
|
||||
@@ -185,13 +184,10 @@ def _badhaka(view: dict) -> dict[str, Any]:
|
||||
for name in PLANETS:
|
||||
base = _planet_base(view, name)
|
||||
if base:
|
||||
placement = dig(view, base)
|
||||
planets[name] = {"sign": placement.get("sign"), "house": placement.get("house"),
|
||||
"dignity": placement.get("status") or placement.get("dignity")}
|
||||
planets[name] = dig(view, base)
|
||||
record = badhaka_facts(asc, planets)
|
||||
if record is None:
|
||||
return {**_gap("core_chart.houses.house_1.cusp_sign"),
|
||||
"blocked_reason": "validation_failed" if asc else "missing_input"}
|
||||
return _gap("core_chart.houses.house_1.cusp_sign")
|
||||
return {"status": "value", "path": "product:report_render.badhaka_facts", "value": record, "check": "product_computation"}
|
||||
|
||||
|
||||
|
||||
@@ -377,7 +377,7 @@ def derive_functional_benefic_malefic(
|
||||
else "BPHS ch34 general ownership; associations not evaluated"
|
||||
for planet in PLANETS
|
||||
}
|
||||
profile_source = "BPHS ch. 34 general ownership rules plus sign exceptions (references/product_functional_profile_bphs_v3_166q4.md)"
|
||||
profile_source = "BPHS ch. 34 general ownership rules plus sign exceptions (references/functional-role-profile.md)"
|
||||
|
||||
prior = []
|
||||
for (sign, planet), (ruling, product_role, note) in PRIOR_RULINGS.items():
|
||||
@@ -408,7 +408,7 @@ def derive_functional_benefic_malefic(
|
||||
"profile_source": profile_source,
|
||||
"lagna_lord": SIGN_LORDS[asc_sign],
|
||||
"yogakaraka_definition": "distinct lordships: one of 4/7/10 and one of 5/9",
|
||||
"rule_reference": "references/product_functional_profile_bphs_v3_166q4.md",
|
||||
"rule_reference": "references/functional-role-profile.md",
|
||||
"profile_is_not": PROFILE_IDS[other],
|
||||
"assertion_label": "parameter_sensitive",
|
||||
"dual_lordship_note": DUAL_LORDSHIP_NOTE,
|
||||
|
||||
@@ -6,8 +6,9 @@ for: the full Technique Audit Table (with the Functional Benefic/Malefic row),
|
||||
the parameter and method differences, the evidence boundaries, and the
|
||||
60-division ledger (20 formal traditional divisions + 40 research D-N rows).
|
||||
|
||||
核验状态只在个人报告计算依据的独立表中显示(BUG-1861)。普通审计行和
|
||||
证据边界继续过滤后台措辞,packet 的 ``technique_audit_table`` 原样保留。
|
||||
产品 2026-11-08(BUG-1810):用户可见文字不出现「联网 / 外部资料 / 外部核验 /
|
||||
MEVG / 真实案例」这类说法。审计行与证据边界里的外部核验字样不再渲染;后台
|
||||
packet 的 ``technique_audit_table`` 原样保留,质量门改为检查后台记录仍在。
|
||||
|
||||
Everything is read from the packet. Statuses are written as words, never as
|
||||
internal codes, so the ordinary projection keeps the chapter.
|
||||
@@ -36,11 +37,13 @@ LEDGER_HEADING = {
|
||||
REQUIRED_ROWS = ("Functional Benefic/Malefic",)
|
||||
# 原值:这三行都渲染,缺哪行就补哪行
|
||||
# 新值:后两行不再渲染(后台 packet 保留);原因:产品负责人 2026-11-08:用户可见文字不出现联网/外部核验字样
|
||||
VERIFICATION_LABELS = {
|
||||
"MEVG / Global Web Evidence": ("网上资料核对", "Online sources check"),
|
||||
"Real Case Calibration": ("真实案例校准", "Real-case calibration"),
|
||||
}
|
||||
HIDDEN_TECHNIQUES = ("MEVG / Global Web Evidence", "Real Case Calibration")
|
||||
NO_WEB_WORDS = ("联网", "外部资料", "外部核验", "MEVG", "真实案例", "Real Case", "Global Web")
|
||||
VERIFICATION_LIMIT_LINE = "本次未完成外部资料与案例核验,候选窗口尚未据此确认。"
|
||||
VERIFICATION_LIMIT_LINE_EN = (
|
||||
"This reading did not complete external-source or real-case verification; "
|
||||
"candidate windows have not been confirmed on that basis."
|
||||
)
|
||||
WEB_WORDING = re.compile("|".join(re.escape(word) for word in NO_WEB_WORDS), re.IGNORECASE)
|
||||
AUDIT_CLAUSE = re.compile(r"[^。;;]+[。;;]?")
|
||||
BODIES = ("Ascendant", "Sun", "Moon", "Mars", "Mercury", "Jupiter", "Venus", "Saturn", "Rahu", "Ketu")
|
||||
@@ -279,8 +282,8 @@ def _audit_rows(packet: dict, language: str) -> list[tuple[str, str, str, str]]:
|
||||
continue
|
||||
name = str(row["technique"])
|
||||
seen.add(name)
|
||||
if name in VERIFICATION_LABELS:
|
||||
# The original audit names and notes stay in the packet.
|
||||
if name in HIDDEN_TECHNIQUES:
|
||||
# 产品负责人 2026-11-08:这两行只在后台记录里保留,不渲染给用户。
|
||||
continue
|
||||
if name == "Functional Benefic/Malefic":
|
||||
note = _functional_note(functional, language) if functional else _clean(row.get("note"), language)
|
||||
@@ -322,28 +325,6 @@ def _table(header: tuple[str, ...], rows: list[tuple[str, ...]]) -> list[str]:
|
||||
return lines
|
||||
|
||||
|
||||
def _verification_table(packet: dict, language: str) -> list[str]:
|
||||
en = language == "en"
|
||||
status_by_technique = {}
|
||||
table = packet.get("technique_audit_table")
|
||||
for row in table if isinstance(table, list) else []:
|
||||
if isinstance(row, dict) and str(row.get("technique")) in VERIFICATION_LABELS:
|
||||
status_by_technique[row["technique"]] = str(row.get("calculation_status") or row.get("status") or "").strip().lower()
|
||||
lines = ["| Verification item | Status |" if en else "| 核验项目 | 状态 |", "| ------ | ------ |"]
|
||||
for technique, labels in VERIFICATION_LABELS.items():
|
||||
state = status_by_technique.get(technique)
|
||||
if state in ("used", "executed", "complete", "completed", "verified", "official_verified"):
|
||||
status = "Completed" if en else "已完成"
|
||||
elif state in ("partial", "partial_verified"):
|
||||
status = "Partially completed" if en else "部分完成"
|
||||
elif state == "not_applicable":
|
||||
status = "Not applicable" if en else "不适用"
|
||||
else:
|
||||
status = "Not completed" if en else "未完成"
|
||||
lines.append(f"| {labels[int(en)]} | {status} |")
|
||||
return lines
|
||||
|
||||
|
||||
def _functional_table(packet: dict, language: str) -> list[str]:
|
||||
functional = _dict(_dict(_dict(packet.get("worksheets")).get("strengths_and_scores")).get("functional_benefic_malefic"))
|
||||
if not functional:
|
||||
@@ -570,8 +551,7 @@ def render_audit_chapters(packet: dict, language: str) -> str:
|
||||
"",
|
||||
*_table(("Technique", "Status", "Source", "Audit note") if en else ("技法", "状态", "依据来源", "审计说明"),
|
||||
_audit_rows(packet, language)),
|
||||
"",
|
||||
*_verification_table(packet, language),
|
||||
f"| — | — | — | {VERIFICATION_LIMIT_LINE_EN if en else VERIFICATION_LIMIT_LINE} |",
|
||||
"",
|
||||
*_functional_table(packet, language),
|
||||
]
|
||||
|
||||
@@ -15,8 +15,6 @@ from yoga_engine import badhaka_house
|
||||
|
||||
SOURCE = "references/badhaka-obstacle-planet-guide.md §二"
|
||||
STATUS = "product_computation_pending_astrologer_review"
|
||||
PROFILE = "badhaka_modality_v1"
|
||||
PROFILE_RULE = "动 → 11、固 → 9、双 → 7,按 badhaka-obstacle-planet-guide.md §二"
|
||||
SIGNS = ("Aries", "Taurus", "Gemini", "Cancer", "Leo", "Virgo", "Libra", "Scorpio",
|
||||
"Sagittarius", "Capricorn", "Aquarius", "Pisces")
|
||||
LORDS = ("Mars", "Venus", "Mercury", "Moon", "Sun", "Mercury", "Venus", "Mars",
|
||||
@@ -25,7 +23,7 @@ MODALITY = {11: "movable", 9: "fixed", 7: "dual"}
|
||||
|
||||
|
||||
def badhaka_facts(asc_sign: Any, planets: dict[str, dict]) -> dict[str, Any] | None:
|
||||
"""Return the obstructing house and its lord's placement separately.
|
||||
"""Badhaka house, sign, lord and the lord's placement, plus occupants.
|
||||
|
||||
``planets`` maps a planet name to its D1 record (``sign``, ``house``, optional ``dignity``).
|
||||
Returns None when the ascendant sign is unknown.
|
||||
@@ -42,15 +40,13 @@ def badhaka_facts(asc_sign: Any, planets: dict[str, dict]) -> dict[str, Any] | N
|
||||
return {
|
||||
"ascendant_sign": asc_sign,
|
||||
"ascendant_modality": MODALITY[house],
|
||||
"badhaka_sthana": {"house": house, "sign": sign, "occupants": occupants},
|
||||
"badhakesh": {
|
||||
"planet": lord,
|
||||
"sign": lord_data.get("sign"),
|
||||
"house": lord_data.get("house"),
|
||||
"dignity": lord_data.get("dignity"),
|
||||
},
|
||||
"house": house,
|
||||
"sign": sign,
|
||||
"lord": lord,
|
||||
"lord_sign": lord_data.get("sign"),
|
||||
"lord_house": lord_data.get("house"),
|
||||
"lord_dignity": lord_data.get("dignity"),
|
||||
"occupants": occupants,
|
||||
"source": SOURCE,
|
||||
"status": STATUS,
|
||||
"profile": PROFILE,
|
||||
"profile_rule": PROFILE_RULE,
|
||||
}
|
||||
|
||||
@@ -33,7 +33,7 @@ GRADE_CONTRACT = "report_evidence_grade_v1"
|
||||
GRADE_ORDER = ("A", "B", "C", "D")
|
||||
# Product policy, versioned: how far the true birth instant may sit from the
|
||||
# recorded one. A minute-confirmed record is taken as +/- 1 minute; an
|
||||
# unconfirmed one has no stated tolerance, so the local boundary is not checked.
|
||||
# unconfirmed one has no stated tolerance, so varga stability is not evaluated.
|
||||
PRECISION_POLICY = "birth_record_minute_v1"
|
||||
CONFIRMED_TOLERANCE_MINUTES = 1.0
|
||||
|
||||
@@ -42,7 +42,7 @@ MISSING_STATES = {"blocked", "withheld", "missing", "calculation_failed", "missi
|
||||
# Dependency kinds:
|
||||
# ("p", domain, id) domain participation row
|
||||
# ("s", id) shared technique row
|
||||
# ("v", code) varga ascendant boundary relative to the birth-time tolerance
|
||||
# ("v", code) varga ascendant stable within the birth-time tolerance
|
||||
# ("t", key) timing / transit / annual block present
|
||||
# Each claim type: route (her router section), section ids it may appear in,
|
||||
# required deps (missing -> D) and checked deps (missing -> disclosed only).
|
||||
@@ -124,7 +124,7 @@ def _shared(facts: dict, item_id: str) -> str:
|
||||
return "missing"
|
||||
|
||||
|
||||
def _varga_boundary(facts: dict, code: str) -> tuple[str, dict[str, Any]]:
|
||||
def _varga_stability(facts: dict, code: str) -> tuple[str, dict[str, Any]]:
|
||||
precision = facts.get("birth_precision") or {}
|
||||
margin = (precision.get("ascendant_margins") or {}).get(code)
|
||||
if not facts.get("minute_confirmed"):
|
||||
@@ -134,7 +134,7 @@ def _varga_boundary(facts: dict, code: str) -> tuple[str, dict[str, Any]]:
|
||||
nearest = min(float(margin.get("before_minutes", 0)), float(margin.get("after_minutes", 0)))
|
||||
detail = {"policy": PRECISION_POLICY, "nearest_change_minutes": nearest,
|
||||
"tolerance_minutes": CONFIRMED_TOLERANCE_MINUTES}
|
||||
return ("clear_at_tolerance" if nearest > CONFIRMED_TOLERANCE_MINUTES else "boundary_sensitive"), detail
|
||||
return ("stable" if nearest > CONFIRMED_TOLERANCE_MINUTES else "boundary_sensitive"), detail
|
||||
|
||||
|
||||
def _block_state(facts: dict, key: str) -> str:
|
||||
@@ -168,7 +168,7 @@ def _evaluate(facts: dict, dep: tuple, sensitive: set[str]) -> dict[str, Any]:
|
||||
if dep[1] in sensitive and state == "executed":
|
||||
state = "parameter_sensitive"
|
||||
elif kind == "v":
|
||||
state, detail = _varga_boundary(facts, dep[1])
|
||||
state, detail = _varga_stability(facts, dep[1])
|
||||
else:
|
||||
state = _block_state(facts, dep[1])
|
||||
return {"id": _dep_id(dep), "state": state, **({"detail": detail} if detail else {})}
|
||||
@@ -189,20 +189,15 @@ def claim_grade(facts: dict, claim_type: str) -> dict[str, Any]:
|
||||
required = [_evaluate(facts, dep, sensitive) for dep in spec["required"]]
|
||||
checked = [_evaluate(facts, dep, sensitive) for dep in spec["checked"]]
|
||||
missing_required = [row["id"] for row in required if _is_missing(row)]
|
||||
must_disclose = [row["id"] for row in checked if _is_missing(row)]
|
||||
missing_checked = [row["id"] for row in checked if _is_missing(row)]
|
||||
sensitive_rows = [row["id"] for row in required + checked if row["state"] == "parameter_sensitive"]
|
||||
boundary_rows = []
|
||||
for row in required + checked:
|
||||
if row["id"].startswith("v:"):
|
||||
details = {key: value for key, value in row["detail"].items() if key != "policy"}
|
||||
state = "clear" if row["state"] == "clear_at_tolerance" else row["state"]
|
||||
boundary_rows.append({"id": row["id"], "state": state, **details})
|
||||
precision = facts.get("birth_precision") or {}
|
||||
purpose = precision.get("purpose_stability") or "not_evaluated"
|
||||
if claim_type == "timing_window" and purpose == "stable":
|
||||
stability = [row for row in required + checked if row["id"].startswith("v:")]
|
||||
if any(row["state"] == "boundary_sensitive" for row in stability):
|
||||
purpose = "boundary_sensitive"
|
||||
elif any(row["state"] == "not_evaluated" for row in stability):
|
||||
purpose = "not_evaluated"
|
||||
if purpose != "stable":
|
||||
must_disclose.append("purpose_stability_not_evaluated")
|
||||
else:
|
||||
purpose = "stable"
|
||||
cap = "D" if missing_required else "C"
|
||||
reasons = (
|
||||
[f"required dependency unavailable: {item}" for item in missing_required]
|
||||
@@ -215,15 +210,14 @@ def claim_grade(facts: dict, claim_type: str) -> dict[str, Any]:
|
||||
"reasons": reasons,
|
||||
"required": required,
|
||||
"checked": checked,
|
||||
"must_disclose": must_disclose,
|
||||
"must_disclose": missing_checked,
|
||||
"parameter_sensitive": sensitive_rows,
|
||||
# Six axes (§5.2) plus the local boundary check.
|
||||
# Six axes (§5.2), raw enums; a missing value never defaults to good.
|
||||
"ledger": {
|
||||
"case_fit": "none",
|
||||
"method_maturity": "source_bounded" if not missing_required else "dependency_missing",
|
||||
"independent_agreement": "not_assessed",
|
||||
"purpose_stability": purpose,
|
||||
"boundary_check": {"policy": PRECISION_POLICY, "rows": boundary_rows},
|
||||
"external_calculation_check": "not_done",
|
||||
"contradiction": [f"{row.get('planet')}: natural {row.get('natural')} / functional {row.get('functional')}"
|
||||
for row in _contradictions(facts)],
|
||||
|
||||
@@ -24,7 +24,6 @@ from typing import Any
|
||||
from .birth_precision_margins import ascendant_margins
|
||||
from .evidence_grade import evidence_grades
|
||||
from .parity_chapters import _VIMSOPAKA_DIGNITY
|
||||
from .technique_status_axes import technique_audit_axes
|
||||
|
||||
try:
|
||||
from scripts.consult_card_domain_facts import (
|
||||
@@ -116,13 +115,7 @@ PARTICIPATION_REASON_CODES = {
|
||||
("general", "D2"): "not_routed",
|
||||
}
|
||||
STRUCTURED_VALUES = {
|
||||
"Badhaka": ("ascendant_sign", "ascendant_modality", "badhaka_sthana", "badhakesh", "source", "status",
|
||||
"profile", "profile_rule"),
|
||||
}
|
||||
PARTICIPATION_BLOCKED_REASONS = {
|
||||
"missing_input": "missing_input", "error": "call_failed",
|
||||
"calculation_failed": "call_failed", "call_failed": "call_failed",
|
||||
"validation_failed": "validation_failed",
|
||||
"Badhaka": ("ascendant_modality", "house", "sign", "lord", "lord_sign", "lord_house", "lord_dignity", "occupants", "status"),
|
||||
}
|
||||
UNUSED_POINT_PATHS = {
|
||||
"Indu": f"{LAGNAS}.Indu_Lagna.sign",
|
||||
@@ -415,31 +408,6 @@ def _unused_value(view: dict, item_id: str, minute_confirmed: bool) -> str | Non
|
||||
return None
|
||||
|
||||
|
||||
def _participation_axes(row: dict[str, Any], item: dict[str, Any], view: dict) -> dict[str, Any]:
|
||||
state, item_id = row["state"], row["id"]
|
||||
if item_id == "kp" and item.get("check") == "label":
|
||||
# The participation label describes the method, even when no KP results were supplied.
|
||||
if not (dig(view, f"{KP}.planets") or dig(view, f"{KP}.houses")):
|
||||
return {"execution_status": "not_computed", "interpretation_usage": "not_used"}
|
||||
if state == "executed" or "value" in row:
|
||||
usage = "not_used"
|
||||
if state == "executed":
|
||||
usage = {"Badhaka": "supporting", "kp": "conditional"}.get(item_id, "primary")
|
||||
axes = technique_audit_axes({}, {"calculation_status": "executed", "interpretation_status": usage})
|
||||
return {"execution_status": axes["calculation_status"], "interpretation_usage": axes["interpretation_status"]}
|
||||
if item.get("status") == "not_applicable" and isinstance(item.get("reason"), str) and item["reason"].strip():
|
||||
return {"execution_status": "not_applicable", "interpretation_usage": "not_used", "reason": item["reason"]}
|
||||
raw_reason = item.get("blocked_reason") or item.get("reason_code") or item.get("status")
|
||||
blocked_reason = (
|
||||
"missing_input" if state == "withheld"
|
||||
else PARTICIPATION_BLOCKED_REASONS.get(raw_reason)
|
||||
or PARTICIPATION_BLOCKED_REASONS.get(row.get("reason_code"))
|
||||
)
|
||||
if blocked_reason:
|
||||
return {"execution_status": "blocked", "interpretation_usage": "not_used", "blocked_reason": blocked_reason}
|
||||
return {"execution_status": "not_computed", "interpretation_usage": "not_used"}
|
||||
|
||||
|
||||
def _participation(rows: list[dict[str, Any]], domain: str, view: dict, minute_confirmed: bool) -> list[dict[str, Any]]:
|
||||
"""Each technique's state for the domain; small values ride along."""
|
||||
out = []
|
||||
@@ -450,7 +418,7 @@ def _participation(rows: list[dict[str, Any]], domain: str, view: dict, minute_c
|
||||
item_id = item["id"]
|
||||
reason = None
|
||||
if status == "blocked":
|
||||
reason = PARTICIPATION_REASON_CODES.get((domain, item_id), "not_computed")
|
||||
reason = PARTICIPATION_REASON_CODES[domain, item_id]
|
||||
elif status != "value" and status != "withheld":
|
||||
reason = "not_computed" # A packet leaf is absent. The card does not report an engine exception.
|
||||
state = {"value": "executed", "withheld": "withheld"}.get(status, "blocked")
|
||||
@@ -471,9 +439,6 @@ def _participation(rows: list[dict[str, Any]], domain: str, view: dict, minute_c
|
||||
row["reason"] = PRECISION_SENTENCE
|
||||
if isinstance(item.get("citation"), str):
|
||||
row["source"] = item["citation"]
|
||||
row.update(_participation_axes(row, item, view))
|
||||
if row["execution_status"] == "not_computed":
|
||||
row.pop("value", None)
|
||||
out.append(row)
|
||||
return out
|
||||
|
||||
@@ -783,15 +748,12 @@ def _blocked_states(
|
||||
) -> dict[str, Any]:
|
||||
labels: dict[str, list[str]] = {
|
||||
"blocked": [], "not_routed": [], "not_in_domain_route": [],
|
||||
"parameter_sensitive": [], "not_applicable": [], "withheld": [], "not_computed": [],
|
||||
"parameter_sensitive": [], "not_applicable": [], "withheld": [],
|
||||
}
|
||||
for name, row in domains.items():
|
||||
for item in [*(row.get("participation") or []), *(shared if name == "general" else [])]:
|
||||
identity = f"{name}:{item['id']}"
|
||||
if item["state"] in UNUSED_REASONS or item["state"] == "withheld":
|
||||
labels[item["state"]].append(identity)
|
||||
if item["execution_status"] in ("blocked", "not_applicable", "not_computed"):
|
||||
labels[item["execution_status"]].append(identity)
|
||||
if item["state"] in labels:
|
||||
labels[item["state"]].append(f"{name}:{item['id']}")
|
||||
for family in families:
|
||||
status = str(family.get("status") or "")
|
||||
if status in BLOCKED_STATES:
|
||||
|
||||
@@ -9,10 +9,9 @@ def test_source_table_for_cancer_leo_gemini():
|
||||
("Gemini", 7, "Sagittarius", "Jupiter")):
|
||||
planets = {lord: {"sign": "Libra", "house": 2, "dignity": "Neutral"}, "Ketu": {"sign": sign, "house": house}}
|
||||
record = badhaka_facts(asc, planets)
|
||||
sthana, badhakesh = record["badhaka_sthana"], record["badhakesh"]
|
||||
assert (sthana["house"], sthana["sign"], badhakesh["planet"]) == (house, sign, lord)
|
||||
assert (badhakesh["sign"], badhakesh["house"]) == ("Libra", 2)
|
||||
assert sthana["occupants"] == ["Ketu"]
|
||||
assert (record["house"], record["sign"], record["lord"]) == (house, sign, lord)
|
||||
assert (record["lord_sign"], record["lord_house"]) == ("Libra", 2)
|
||||
assert record["occupants"] == ["Ketu"]
|
||||
assert record["status"] == STATUS
|
||||
|
||||
|
||||
|
||||
@@ -1,43 +0,0 @@
|
||||
"""The source table's house and lord, with separate D1 placement facts."""
|
||||
|
||||
import pytest
|
||||
|
||||
from scripts.report_render.badhaka_facts import badhaka_facts
|
||||
|
||||
|
||||
@pytest.mark.parametrize(("ascendant", "house", "sign", "lord"), [
|
||||
("Aries", 11, "Aquarius", "Saturn"),
|
||||
("Taurus", 9, "Capricorn", "Saturn"),
|
||||
("Gemini", 7, "Sagittarius", "Jupiter"),
|
||||
("Cancer", 11, "Taurus", "Venus"),
|
||||
("Leo", 9, "Aries", "Mars"),
|
||||
("Virgo", 7, "Pisces", "Jupiter"),
|
||||
("Libra", 11, "Leo", "Sun"),
|
||||
("Scorpio", 9, "Cancer", "Moon"),
|
||||
("Sagittarius", 7, "Gemini", "Mercury"),
|
||||
("Capricorn", 11, "Scorpio", "Mars"),
|
||||
("Aquarius", 9, "Libra", "Venus"),
|
||||
("Pisces", 7, "Virgo", "Mercury"),
|
||||
])
|
||||
def test_badhaka_house_and_lord_from_source_table(ascendant, house, sign, lord):
|
||||
assert ascendant != sign
|
||||
planets = {
|
||||
lord: {"sign": ascendant, "house": 1, "dignity": "Neutral"},
|
||||
"Ketu": {"sign": sign, "house": house},
|
||||
"Rahu": {"sign": ascendant, "house": house},
|
||||
}
|
||||
record = badhaka_facts(ascendant, planets)
|
||||
assert record["badhaka_sthana"] == {"house": house, "sign": sign, "occupants": ["Ketu"]}
|
||||
assert record["badhakesh"] == {"planet": lord, "sign": ascendant, "house": 1, "dignity": "Neutral"}
|
||||
assert record["badhakesh"]["house"] != record["badhaka_sthana"]["house"]
|
||||
assert record["badhakesh"]["planet"] not in record["badhaka_sthana"]["occupants"]
|
||||
assert record["profile"] == "badhaka_modality_v1"
|
||||
assert record["source"].endswith("badhaka-obstacle-planet-guide.md §二")
|
||||
assert record["status"] == "product_computation_pending_astrologer_review"
|
||||
assert {"house", "sign", "lord", "lord_house", "occupants"}.isdisjoint(record)
|
||||
|
||||
|
||||
def test_badhakesh_is_an_occupant_when_it_is_in_the_obstructing_sign():
|
||||
record = badhaka_facts("Gemini", {"Jupiter": {"sign": "Sagittarius", "house": 7}})
|
||||
assert record["badhaka_sthana"]["occupants"] == ["Jupiter"]
|
||||
assert record["badhakesh"]["house"] == record["badhaka_sthana"]["house"] == 7
|
||||
@@ -1,70 +0,0 @@
|
||||
"""Keep the product's minute boundary check separate from purpose stability."""
|
||||
|
||||
from scripts.report_render.evidence_grade import CLAIM_TYPES, claim_grade, evidence_grades
|
||||
from tests.test_report_evidence_grade_20261112 import _facts
|
||||
|
||||
|
||||
def _complete_facts(purpose_stability):
|
||||
facts = _facts()
|
||||
for row in facts["domains"]["health"]["participation"]:
|
||||
if row["id"] == "Badhaka":
|
||||
row["state"] = "executed"
|
||||
precision = facts["birth_precision"]
|
||||
precision["purpose_stability"] = purpose_stability
|
||||
for margin in precision["ascendant_margins"].values():
|
||||
margin.update(before_minutes=90.0, after_minutes=120.0)
|
||||
return facts
|
||||
|
||||
|
||||
def test_clear_minute_boundary_does_not_establish_purpose_stability():
|
||||
claims = evidence_grades(_complete_facts("not_evaluated"))["claims"]
|
||||
assert len(claims) == 8
|
||||
assert {name: claim["ledger"]["purpose_stability"] for name, claim in claims.items()} == {
|
||||
name: "not_evaluated" for name in CLAIM_TYPES
|
||||
}
|
||||
for claim in claims.values():
|
||||
assert claim["cap"] == "C"
|
||||
assert "purpose_stability_not_evaluated" in claim["must_disclose"]
|
||||
character = claims["character_structure"]
|
||||
assert character["required"][0]["state"] == "clear_at_tolerance"
|
||||
assert next(row for row in character["checked"] if row["id"] == "v:D9")["state"] == "clear_at_tolerance"
|
||||
assert character["ledger"]["boundary_check"]["policy"] == "birth_record_minute_v1"
|
||||
assert character["ledger"]["boundary_check"]["rows"][0] == {
|
||||
"id": "v:D1", "state": "clear", "nearest_change_minutes": 90.0, "tolerance_minutes": 1.0,
|
||||
}
|
||||
|
||||
|
||||
def test_explicit_stability_does_not_make_timing_stable():
|
||||
claims = evidence_grades(_complete_facts("stable"))["claims"]
|
||||
for name, claim in claims.items():
|
||||
purpose = "not_evaluated" if name == "timing_window" else "stable"
|
||||
assert claim["ledger"]["purpose_stability"] == purpose
|
||||
assert ("purpose_stability_not_evaluated" in claim["must_disclose"]) == (purpose != "stable")
|
||||
assert claims["timing_window"]["ledger"]["boundary_check"]["rows"] == []
|
||||
|
||||
|
||||
def test_required_boundary_still_caps_the_claim_at_d():
|
||||
facts = _complete_facts("not_evaluated")
|
||||
facts["birth_precision"]["ascendant_margins"]["D6"]["before_minutes"] = 0.25
|
||||
claim = claim_grade(facts, "health_pressure")
|
||||
assert claim["cap"] == "D"
|
||||
assert claim["ledger"]["purpose_stability"] == "not_evaluated"
|
||||
row = next(row for row in claim["ledger"]["boundary_check"]["rows"] if row["id"] == "v:D6")
|
||||
assert row == {
|
||||
"id": "v:D6", "state": "boundary_sensitive", "nearest_change_minutes": 0.25, "tolerance_minutes": 1.0,
|
||||
}
|
||||
|
||||
|
||||
def test_missing_purpose_defaults_to_unassessed_independently_of_boundary_status():
|
||||
facts = _complete_facts(None)
|
||||
del facts["birth_precision"]["ascendant_margins"]["D9"]
|
||||
claim = claim_grade(facts, "character_structure")
|
||||
assert claim["cap"] == "C"
|
||||
assert claim["ledger"]["purpose_stability"] == "not_evaluated"
|
||||
assert claim["must_disclose"] == ["v:D9", "purpose_stability_not_evaluated"]
|
||||
assert claim["ledger"]["boundary_check"]["rows"][-1]["state"] == "missing"
|
||||
|
||||
facts["minute_confirmed"] = False
|
||||
claim = claim_grade(facts, "character_structure")
|
||||
assert claim["cap"] == "C"
|
||||
assert {row["state"] for row in claim["ledger"]["boundary_check"]["rows"]} == {"not_evaluated"}
|
||||
@@ -1,60 +0,0 @@
|
||||
"""Report and product BPHS provenance and the selected profile."""
|
||||
|
||||
from hashlib import sha256
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
from scripts.functional_benefics import derive_functional_benefic_malefic
|
||||
from scripts.report_render.audit_chapters import render_audit_chapters
|
||||
from tests.test_reading_facts_v3_20261112 import _reading_facts, _reading_request, public_report
|
||||
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
PRODUCT_PROFILE_PATH = "references/product_functional_profile_bphs_v3_166q4.md"
|
||||
|
||||
|
||||
def test_upstream_profile_retains_exact_source_bytes():
|
||||
source = ROOT / "references/functional-role-profile.md"
|
||||
# yinduzhanxing 8aaf60991c32a84ac30d6d7b6a62c4f166507bce.
|
||||
assert sha256(source.read_bytes()).hexdigest() == "95f5445a197a5f49ebaa08431816f0f3583b0a3c13f7967d3c043cecfe63aa7e"
|
||||
product = (ROOT / PRODUCT_PROFILE_PATH).read_text(encoding="utf-8")
|
||||
assert "bphs_ch34_general_with_sign_exceptions_v3_166q4" in product
|
||||
assert "Revision v3" in product and "Chart page layout (issue #163)" in product
|
||||
|
||||
|
||||
@pytest.mark.parametrize(("ascendant", "role"), [
|
||||
("Aries", "malefic"), ("Taurus", "benefic"), ("Gemini", "mixed"),
|
||||
("Cancer", "malefic"), ("Leo", "malefic"), ("Virgo", "mixed"),
|
||||
("Libra", "benefic"), ("Scorpio", "malefic"), ("Sagittarius", "malefic"),
|
||||
("Capricorn", "benefic"), ("Aquarius", "benefic"), ("Pisces", "malefic"),
|
||||
])
|
||||
def test_product_bphs_saturn_table_is_unchanged(ascendant, role):
|
||||
result = derive_functional_benefic_malefic(ascendant, functional_profile="bphs_exception_profile")
|
||||
group = "neutral" if role == "mixed" else role
|
||||
assert "Saturn" in result[f"functional_{group}s"]
|
||||
if role == "mixed":
|
||||
assert "mixed" in result["ambiguous_cell_notes"]["Saturn"]["summary_en"]
|
||||
assert result["profile"] == "bphs_ch34_general_with_sign_exceptions_v3_166q4"
|
||||
assert result["rule_reference"] == PRODUCT_PROFILE_PATH
|
||||
assert PRODUCT_PROFILE_PATH in result["profile_source"]
|
||||
|
||||
|
||||
def test_report_functional_profile_follows_packet(public_report):
|
||||
_, body, reading, packet = public_report
|
||||
assert _reading_facts(packet, reading, **_reading_request(body))["functional"]["functional_profile"] == "pvr_table30_v1"
|
||||
|
||||
|
||||
def test_personal_appendix_reports_only_recorded_verification_status():
|
||||
packet = {"technique_audit_table": [
|
||||
{"technique": "MEVG / Global Web Evidence", "status": "blocked", "note": "internal"},
|
||||
{"technique": "Real Case Calibration", "calculation_status": "partial", "status": "executed"},
|
||||
]}
|
||||
zh = render_audit_chapters(packet, "zh")
|
||||
assert "| 网上资料核对 | 未完成 |" in zh
|
||||
assert "| 真实案例校准 | 部分完成 |" in zh
|
||||
assert "MEVG" not in zh and "internal" not in zh
|
||||
packet["technique_audit_table"][0]["status"] = "executed"
|
||||
assert "| 网上资料核对 | 已完成 |" in render_audit_chapters(packet, "zh")
|
||||
fallback = render_audit_chapters({}, "zh")
|
||||
assert "| 网上资料核对 | 未完成 |" in fallback and "| 真实案例校准 | 未完成 |" in fallback
|
||||
@@ -80,8 +80,8 @@ def test_public_report_v3_facts(public_report):
|
||||
# BUG-1853: Badhaka is computed from the ascendant and reaches the health facts with its positions.
|
||||
badhaka = next(row for row in facts["domains"]["health"]["participation"] if row["id"] == "Badhaka")
|
||||
assert badhaka["state"] == "executed"
|
||||
assert badhaka["value"]["badhaka_sthana"]["house"] in (7, 9, 11)
|
||||
assert badhaka["value"]["badhaka_sthana"]["sign"] and badhaka["value"]["badhakesh"]["planet"]
|
||||
assert badhaka["value"]["house"] in (7, 9, 11)
|
||||
assert badhaka["value"]["sign"] and badhaka["value"]["lord"]
|
||||
# Product computation, not her engine (owner rule 2026-11-07): the record says so.
|
||||
assert badhaka["value"]["status"] == "product_computation_pending_astrologer_review"
|
||||
assert dig(packet, "worksheets.advanced_systems.badhaka") is None
|
||||
|
||||
@@ -1,118 +0,0 @@
|
||||
"""Separate participation from computation in the report's facts."""
|
||||
|
||||
import pytest
|
||||
|
||||
from scripts.consult_card_domain_facts import _badhaka
|
||||
from scripts.report_render import reading_facts as rf
|
||||
from scripts.report_render.technique_status_axes import CALCULATION_LABELS, INTERPRETATION_LABELS
|
||||
from tests.test_reading_facts_v3_20261112 import _reading_facts, _reading_request, public_report
|
||||
|
||||
|
||||
def test_unused_results_and_absent_results_keep_distinct_axes():
|
||||
card = [{"id": "Indu", "status": "blocked"}]
|
||||
missing, = rf._participation(card, "wealth", {}, True)
|
||||
assert (missing["state"], missing["execution_status"], missing["interpretation_usage"]) == (
|
||||
"not_in_domain_route", "not_computed", "not_used",
|
||||
)
|
||||
assert "value" not in missing and "blocked_reason" not in missing
|
||||
|
||||
view = {"worksheets": {"divisional_and_special_charts": {
|
||||
"special_lagnas": {"Indu_Lagna": {"sign": "Leo"}},
|
||||
"varga_full": {"D10_Dasamsa": {"Ascendant": {"sign": "Leo"}}},
|
||||
}}}
|
||||
unused, = rf._participation(card, "wealth", view, True)
|
||||
not_routed, = rf._participation([{"id": "D10", "status": "blocked"}], "general", view, True)
|
||||
assert unused["state"] == "not_in_domain_route"
|
||||
assert not_routed["state"] == "not_routed"
|
||||
for row in (unused, not_routed):
|
||||
assert (row["execution_status"], row["interpretation_usage"]) == ("executed", "not_used")
|
||||
assert row["value"] == "Leo" and row["computed"] is True
|
||||
|
||||
|
||||
@pytest.mark.parametrize(("item", "execution", "reason"), [
|
||||
({"status": "withheld"}, "blocked", "missing_input"),
|
||||
({"status": "error"}, "blocked", "call_failed"),
|
||||
({"status": "blocked", "blocked_reason": "validation_failed"}, "blocked", "validation_failed"),
|
||||
({"status": "gap"}, "not_computed", None),
|
||||
({"status": "not_applicable", "reason": "requires a relationship comparison"}, "not_applicable", None),
|
||||
])
|
||||
def test_states_keep_explicit_blocking_and_applicability_reasons(item, execution, reason):
|
||||
row, = rf._participation([{"id": "example", **item}], "general", {}, False)
|
||||
assert (row["execution_status"], row["interpretation_usage"]) == (execution, "not_used")
|
||||
assert row.get("blocked_reason") == reason
|
||||
if execution == "not_applicable":
|
||||
assert row["reason"] == item["reason"]
|
||||
assert "value" not in row
|
||||
|
||||
|
||||
def test_badhaka_missing_ascendant_is_blocked_for_missing_input():
|
||||
row, = rf._participation([{"id": "Badhaka", **_badhaka({})}], "health", {}, True)
|
||||
assert (row["state"], row["execution_status"], row["blocked_reason"]) == ("blocked", "blocked", "missing_input")
|
||||
|
||||
|
||||
def test_kp_method_label_is_not_a_calculation_result():
|
||||
item = {"id": "kp", "status": "value", "check": "label", "value": "conditional method"}
|
||||
missing, = rf._participation([item], "general", {}, True)
|
||||
assert missing["state"] == "executed"
|
||||
assert (missing["execution_status"], missing["interpretation_usage"]) == ("not_computed", "not_used")
|
||||
assert "value" not in missing
|
||||
view = {"worksheets": {"advanced_systems": {"kp": {"planets": {"Moon": {"sub": "Mars"}}}}}}
|
||||
computed, = rf._participation([item], "general", view, True)
|
||||
assert (computed["execution_status"], computed["interpretation_usage"]) == ("executed", "conditional")
|
||||
|
||||
|
||||
def test_summary_keeps_routing_separate_from_calculation_gaps():
|
||||
rows = rf._participation([
|
||||
{"id": "Gulika", "status": "blocked"},
|
||||
{"id": "D6", "status": "withheld"},
|
||||
{"id": "D8", "status": "gap"},
|
||||
], "health", {"worksheets": {"divisional_and_special_charts": {
|
||||
"upagrahas": {"raw": {"Gulika": {"sign_idx": 7}}},
|
||||
}}}, False)
|
||||
transits = {"planets": {"Jupiter": {}}, "double_transit": {"checked_houses": [7]}}
|
||||
result = rf._blocked_states({"health": {"participation": rows}}, [], [], {}, transits, {})
|
||||
assert result["not_in_domain_route"] == ["health:Gulika"]
|
||||
assert result["withheld"] == ["health:D6"]
|
||||
assert result["blocked"] == ["health:D6"]
|
||||
assert result["not_computed"] == ["health:D8"]
|
||||
|
||||
|
||||
def test_public_report_axes_and_badhaka_positions(public_report):
|
||||
case, body, reading, packet = public_report
|
||||
facts = _reading_facts(packet, reading, **_reading_request(body))
|
||||
participation = {name: {row["id"]: row for row in domain["participation"]}
|
||||
for name, domain in facts["domains"].items()}
|
||||
unused = [participation["wealth"]["Indu"]]
|
||||
unused.extend(participation["health"][name] for name in ("Gulika", "Mandi", "Pranapada"))
|
||||
for row in unused:
|
||||
assert row["state"] == "not_in_domain_route"
|
||||
assert (row["execution_status"], row["interpretation_usage"]) == ("executed", "not_used")
|
||||
assert row["value"] and row["computed"] is True
|
||||
|
||||
health = participation["health"]["Badhaka"]
|
||||
assert (health["state"], health["execution_status"], health["interpretation_usage"]) == (
|
||||
"executed", "executed", "supporting",
|
||||
)
|
||||
record = health["value"]
|
||||
sthana, lord = record["badhaka_sthana"], record["badhakesh"]
|
||||
expected = {
|
||||
"steve_jobs_1955_aa": (7, "Pisces", "Jupiter", 10),
|
||||
"barack_obama_1961_aa": (11, "Scorpio", "Mars", 9),
|
||||
}
|
||||
assert (sthana["house"], sthana["sign"], lord["planet"], lord["house"]) == expected[case]
|
||||
assert lord["dignity"] and lord["dignity"] == facts["planets"][lord["planet"]]["d1_dignity"]
|
||||
if case == "steve_jobs_1955_aa":
|
||||
assert "Moon" in sthana["occupants"]
|
||||
assert lord["planet"] not in sthana["occupants"]
|
||||
assert record["profile"] == "badhaka_modality_v1"
|
||||
assert record["status"] == "product_computation_pending_astrologer_review"
|
||||
assert facts["evidence_grades"]["claims"]["health_obstruction"]["cap"] != "D"
|
||||
|
||||
rows = [*facts["shared_techniques"], *(row for domain in participation.values() for row in domain.values())]
|
||||
for row in rows:
|
||||
assert row["execution_status"] in CALCULATION_LABELS.keys() | {"not_computed"}
|
||||
assert row["interpretation_usage"] in INTERPRETATION_LABELS.keys() - {"blocked", "observation_only"}
|
||||
if row["execution_status"] == "not_computed":
|
||||
assert "value" not in row
|
||||
if row["execution_status"] == "blocked":
|
||||
assert row["blocked_reason"] in {"missing_input", "call_failed", "validation_failed"}
|
||||
@@ -72,11 +72,12 @@ def test_audit_chapter_keeps_required_rows_differences_boundaries_and_ledger() -
|
||||
assert "| Functional Benefic/Malefic |" in zh
|
||||
assert "MEVG" not in zh
|
||||
assert "Real Case" not in zh
|
||||
assert "| 网上资料核对 | 未完成 |" in zh
|
||||
case_row = "| 真实案例校准 | 未完成 |"
|
||||
assert zh.count(case_row) == 1
|
||||
assert "外部资料" not in zh
|
||||
assert "真实案例" not in zh.replace(case_row, "", 1)
|
||||
# 原值:全文没有「外部资料」。新值:只放行表尾的固定说明行。
|
||||
# 原因:2026-11-11 固定核验说明(BUG-1837)。
|
||||
limit = "| — | — | — | 本次未完成外部资料与案例核验,候选窗口尚未据此确认。 |"
|
||||
assert zh.count(limit) == 1
|
||||
assert "外部资料" not in zh.replace(limit, "", 1)
|
||||
assert "真实案例" not in zh
|
||||
# Functional vs natural conflicts are stated, not only the role lists.
|
||||
assert "| 土星 | 自然凶 | Yogakaraka | 9, 10 | 冲突,降一级置信度 |" in zh
|
||||
# Parameter-sensitive rows and the Narayana method conflict.
|
||||
|
||||
@@ -69,14 +69,12 @@ def test_missing_required_layer_or_boundary_varga_gives_d():
|
||||
boundary["birth_precision"]["ascendant_margins"]["D6"] = {"before_minutes": 0.4, "after_minutes": 9.0}
|
||||
health = claim_grade(boundary, "health_pressure")
|
||||
assert health["cap"] == "D"
|
||||
assert health["ledger"]["purpose_stability"] == "not_evaluated"
|
||||
d6 = next(row for row in health["ledger"]["boundary_check"]["rows"] if row["id"] == "v:D6")
|
||||
assert d6["state"] == "boundary_sensitive"
|
||||
assert health["ledger"]["purpose_stability"] == "boundary_sensitive"
|
||||
checked_only = _facts()
|
||||
checked_only["birth_precision"]["ascendant_margins"]["D30"] = {"before_minutes": 0.4, "after_minutes": 9.0}
|
||||
disclosed = claim_grade(checked_only, "health_pressure")
|
||||
assert disclosed["cap"] == "C"
|
||||
assert {"v:D30", "purpose_stability_not_evaluated"} <= set(disclosed["must_disclose"])
|
||||
assert disclosed["must_disclose"] == ["v:D30"]
|
||||
|
||||
|
||||
def test_unconfirmed_birth_time_is_not_evaluated_not_assumed_stable():
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
"""Verification wording is confined to the personal-report appendix."""
|
||||
"""产品负责人 2026-11-08(BUG-1810):报告渲染不出现联网/外部核验字样,后台记录保留。
|
||||
|
||||
只有技法审计表末行可显示固定核验说明。表中其他内容不显示联网措辞,后台记录保留。
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
@@ -6,23 +9,27 @@ from itertools import takewhile
|
||||
|
||||
from scripts.full_report_quality_gate import BACKSTAGE_TECHNIQUE_AUDIT_ROWS
|
||||
from scripts.report_render.audit_chapters import (
|
||||
AUDIT_TABLE_HEADING,
|
||||
NO_WEB_WORDS,
|
||||
VERIFICATION_LIMIT_LINE,
|
||||
VERIFICATION_LIMIT_LINE_EN,
|
||||
render_audit_chapters,
|
||||
)
|
||||
|
||||
from tests.test_report_audit_chapters_20261107 import _packet
|
||||
|
||||
def _assert_clean(text: str, language: str, where: str) -> None:
|
||||
header = "| Verification item | Status |" if language == "en" else "| 核验项目 | 状态 |"
|
||||
first, second = (("Online sources check", "Real-case calibration") if language == "en"
|
||||
else ("网上资料核对", "真实案例校准"))
|
||||
lines = text.splitlines()
|
||||
assert lines.count(header) == 1, where
|
||||
start = lines.index(header)
|
||||
table = list(takewhile(lambda line: line.startswith("|"), lines[start:]))
|
||||
assert len(table) == 4, where
|
||||
assert table[2].startswith(f"| {first} | ") and table[3].startswith(f"| {second} | "), where
|
||||
remainder = "\n".join(lines[:start] + lines[start + len(table):]).casefold()
|
||||
# 原值:在全文检查禁词。
|
||||
# 新值:先核对固定说明只出现一次且位于技法审计表末行,再检查其余全文。
|
||||
# 原因:2026-11-11 的说明行及英文版补充(BUG-1837)。
|
||||
limit = VERIFICATION_LIMIT_LINE_EN if language == "en" else VERIFICATION_LIMIT_LINE
|
||||
row = f"| — | — | — | {limit} |"
|
||||
assert text.count(limit) == 1, where
|
||||
_, heading, tail = text.partition(f"\n### {AUDIT_TABLE_HEADING[language]}\n")
|
||||
assert heading, where
|
||||
rows = list(takewhile(lambda line: line.startswith("|"), tail.lstrip("\n").splitlines()))
|
||||
assert rows and rows[-1] == row, where
|
||||
remainder = "\n".join(line for line in text.splitlines() if line != row).casefold()
|
||||
for word in NO_WEB_WORDS:
|
||||
assert word.casefold() not in remainder, f"{where} 里出现了「{word}」"
|
||||
|
||||
|
||||
@@ -54,9 +54,9 @@ def test_reading_facts_are_packet_leaves(chart) -> None:
|
||||
assert [row["lord"] for row in facts["timing"]["next_antardashas"]] == [row["lord"] for row in rows[index + 1:index + 3]]
|
||||
# 原值:'"status"'、'"citation"'、"references/" 一律不进模型输入,总长 < 20000
|
||||
# 新值:内部 leaf 的 path / check / citation 键仍不进;技法状态(executed / blocked / withheld)、
|
||||
# 依据来源(references/ 条目)与证据路径进入契约;写作输入的紧凑 JSON 总长 < 80000
|
||||
# 依据来源(references/ 条目)与证据路径进入契约;总长 < 80000
|
||||
# 原因:产品 2026-11-07 占星师方案 item 10:reading_facts 契约要带证据路径、方法差异和阻塞状态
|
||||
encoded = json.dumps(facts, ensure_ascii=False, separators=(",", ":"))
|
||||
encoded = json.dumps(facts, ensure_ascii=False)
|
||||
for marker in ('"path"', '"check"', '"citation"'):
|
||||
assert marker not in encoded
|
||||
assert len(encoded) < 80_000
|
||||
@@ -135,10 +135,7 @@ def test_withheld_varga_houses_stay_out_without_a_confirmed_minute(chart) -> Non
|
||||
health = facts["domains"]["health"]
|
||||
assert health["varga_charts"]["D30"] == "出生时间精度不够,不参与"
|
||||
d30 = next(row for row in health["participation"] if row["id"] == "D30")
|
||||
assert d30 == {
|
||||
"id": "D30", "state": "withheld", "reason": "出生时间精度不够,不参与", "source": d30["source"],
|
||||
"execution_status": "blocked", "interpretation_usage": "not_used", "blocked_reason": "missing_input",
|
||||
}
|
||||
assert d30 == {"id": "D30", "state": "withheld", "reason": "出生时间精度不够,不参与", "source": d30["source"]}
|
||||
assert facts["special_points"]["Ghati_Lagna"] == "出生时间精度不够,不参与"
|
||||
assert "health:D30" in facts["blocked_states"]["withheld"]
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ import pytest
|
||||
|
||||
from scripts.pl9_full_data_export import _sanitize_pl9_ai_density_markdown
|
||||
from scripts.pl9_reader_export import _sanitize_pl9_public_markdown
|
||||
from scripts.report_render.audit_chapters import STATUS, render_audit_chapters
|
||||
from scripts.report_render.audit_chapters import STATUS, VERIFICATION_LIMIT_LINE, render_audit_chapters
|
||||
from tests.public_chart_reports import PUBLIC_REPORT_CASES, public_chart_reports
|
||||
from tests.test_report_audit_chapters_20261107 import _packet
|
||||
from tests.test_report_no_web_wording_20261109 import _assert_clean
|
||||
@@ -41,8 +41,7 @@ def test_audit_filter_preserves_other_clauses_and_uses_empty_cell_marker() -> No
|
||||
def test_chinese_limit_line_matches_consult_literal() -> None:
|
||||
frontend = Path(__file__).resolve().parents[1] / "frontend/src/lib/consult-answer-basis.ts"
|
||||
match = re.search(r'CONSULT_VERIFICATION_LIMIT_LINE\s*=\s*"([^"]+)"', frontend.read_text(encoding="utf-8"))
|
||||
assert match and match[1] == "本次未完成外部资料与案例核验,候选窗口尚未据此确认。"
|
||||
assert match[1] not in render_audit_chapters(_packet(), "zh")
|
||||
assert match and VERIFICATION_LIMIT_LINE == match[1]
|
||||
|
||||
|
||||
def test_both_reader_paths_translate_status_words_without_changing_field_keys_or_code() -> None:
|
||||
|
||||
Reference in New Issue
Block a user