wip(#182): one yoga status table drives the contract and the professional voice

- yoga-status-words.ts:新增 yogaStatusContractTable()(遍历 YOGA_STATUS_MAPPING 生成,
  不手写文字)与 YOGA_STATUS_WORD_CLASSES(拦截检查用的三张词表 + 括号里照抄状态的词)
- references/product_runtime_contract.md:shared-reading 第 7 条与它上面的注释改成
  「按 yogas_detail 每行状态 + {{YOGA_STATUS_TABLE}}」,别的行一字未动
- consultation-condensed-checklist.ts:sharedReadingBlock() 的取值里补 YOGA_STATUS_TABLE
- product-voice.ts:只把「状态词照卡上原词抄」那一行换成按同一张表写
- consult-professional-mode-20261107.test.ts:按任务书改那一处断言(三栏注释)

未完成(下一轮):consultation-answer-fact-guard.ts 的格局状态检查与 9 组测试。

Tests: consult-professional-mode 10/10、consult-basis-candidate-status-1824 12/12 通过。

Co-Authored-By: Grok (agent loop)
This commit is contained in:
jesse-ux
2026-11-09 18:39:56 +08:00
co-authored by Grok (agent loop)
parent 0d114c9ca9
commit ece76a2e8e
5 changed files with 55 additions and 5 deletions
@@ -1,6 +1,7 @@
import type { ConsultationDomain } from "./consultation-domain-registry.ts";
import type { ProfileGender } from "./profile-gender.ts";
import { AFFLICTION_RANGE_RULE, CAREER_FIELD_ASK_RULE, CHECKLIST_ANALYSIS_ONLY_NOTE } from "./consultation-thinking-plan.ts";
import { yogaStatusContractTable } from "./yoga-status-words.ts";
import { productContractBlock } from "./product-runtime-contract.ts";
/**
@@ -68,7 +69,12 @@ export const CONDENSED_SHARED_READING_SOURCE = "consultation shared reading (TAS
* items without their numbers.
*/
function sharedReadingBlock(): string {
return productContractBlock("shared-reading", { AFFLICTION_RANGE_RULE, CHECKLIST_ANALYSIS_ONLY_NOTE });
return productContractBlock("shared-reading", {
AFFLICTION_RANGE_RULE,
CHECKLIST_ANALYSIS_ONLY_NOTE,
// BUG-1825:格局状态表只从 yoga-status-words.ts 取,合同里不手写。
YOGA_STATUS_TABLE: yogaStatusContractTable(),
});
}
export const CONDENSED_SHARED_READING_LINES: readonly string[] = sharedReadingBlock()
.split("\n")
+38
View File
@@ -74,3 +74,41 @@ export function yogaStatus(row: Rec | undefined, options: { notEvaluated?: boole
export function isStructuralObservation(row: Rec): boolean {
return row.claim_kind === "structural_observation" || row.status === "structural_observation";
}
/**
* 她的表渲染成一行合同文字(合同与 product-voice 都用它,不各写一遍)。
* 左边是卡上可能出现的原始值(用「 / 」连接),右边是映射表里的文字;文字不手写。
*/
export function yogaStatusContractTable(): string {
return (Object.keys(YOGA_STATUS_MAPPING) as YogaStatusKey[])
.map((key) => {
const entry = YOGA_STATUS_MAPPING[key];
return `${entry.evidence.join(" / ")} →「${entry.text}」`;
})
.join(";");
}
/** 拦截检查用的三张词表(占星师第七轮回复 §5 P0.2 的口径)。 */
export const YOGA_STATUS_WORD_CLASSES: Readonly<{
affirm: readonly string[];
deny: readonly string[];
exempt: readonly string[];
conditional: readonly string[];
literalStatus: readonly string[];
}> = {
// 肯定:说这条格局已经成立。
affirm: ["已成立", "已经成立", "确认成立", "已形成", "已经形成", "形成了", "成立"],
// 否定:说这条格局不成立 / 落空。
deny: ["不成立", "非成立", "未成立", "没有成立", "并不成立", "落空", "不构成", "未形成", "没有形成", "必要条件未满足", "条件未满足"],
// 排除写法:出现时这一处既不算肯定也不算否定。
exempt: [
"不作成立", "不作为成立", "不作格局成立", "不当作成立", "不当成成立", "不算成立", "不算作成立",
"不写成成立", "不说成成立", "不能说成立", "未列为成立", "没有列为成立", "不视为成立",
"是否成立", "能否成立", "成立与否", "成立条件", "成立的条件", "成立名单",
],
// 条件句里出现这些词时,后面的「成立」不算。
conditional: ["若", "如果", "假如", "一旦", "只有", "才能", "才会"],
// 括号里照抄状态时,这条格局名不检查。
literalStatus: ["candidate", "computed", "候选", "待核实", "检出", "未确认", "待定"],
};
+2 -1
View File
@@ -1,3 +1,4 @@
import { yogaStatusContractTable } from "../lib/yoga-status-words.ts";
import {
DEFAULT_CONSULTATION_ANSWER_MODE,
INTERPRETATION_STATUS_RULE,
@@ -140,7 +141,7 @@ export const professionalConversationVoice = `VISIBLE VOICE
- 数值照卡上和补取结果原样引用,标出处层(D1、D9、卡上 timing 等);卡上没有、补取也没有的,写「未交付」,不从记忆补。
- 受冲信号写实际来源:哪一宫、哪颗星、什么关系(同宫、相照、宫主落 6 / 8 / 12、功能凶星、落陷或燃烧),不报条数,条数只在后台(占星师 2026-11-08 第五轮裁定 6)。
- 点位(A2、A11、AL、UL、Indu Lagna 等)和宫主、行星的星座、宫位、主星,只照卡上那个字段写;同一个点在正文、表格、审计表里只有一个值,不从别的点、宫或 Saham 推出另一个落点。卡上没给的大运层(比如下一个子运)写「未交付」,不拿下一个主运或别的体系顶替。
- 状态词照卡上原词抄:candidate 写「候选」、computed 写「已计算,未验证」、strength_comparison_required 写「待定」;不升格为「成立」「确认」「命中」,标题里也不写「已成立」。
- 格局(Yoga)的状态只按卡上 yogas_detail 那一行写,用这张表:${yogaStatusContractTable()};只出现在 yogas.hits、卡上没有状态行的,按 blocked 写并写明缺什么。不从 hits 推出「成立」,没有完整条件检查不写「不成立」「非成立」「落空」。其他状态词照卡上原词抄:candidate 写「候选」、strength_comparison_required 写「待定」;不升格为「成立」「确认」「命中」,标题里也不写「已成立」。
- 日期精度跟出生时间走:卡上 rectification 没有写明出生时间已校正时,大运、子运、细运和行运的日期都是名义计算边界,几分钟的出生误差就能移动几天;第一次写日期时注明「名义计算边界」,结论和行动建议里的时间写到月或旬,不写「某日起」。
- 用户点名的技法、宫位、分盘每项都写到;用户的写法要求照做:不让排除的,不写排除或圈定某一类的句子;要分明显和可能的,按证据等级分。
- 用户纠正你的判断时,先认下,直说原来哪句读偏了,再只说盘上真正对得上的部分,并重新标等级;不为原判断辩护,也不把原因推到用户身上。
@@ -6,6 +6,7 @@
// read"; sub-skills travel with the router; follow-ups and entries never
// invite a question the chart cannot answer.
import assert from "node:assert/strict";
import { yogaStatusContractTable } from "../src/lib/yoga-status-words.ts";
import { readFileSync } from "node:fs";
import test from "node:test";
@@ -205,7 +206,11 @@ test("claims come from the card, status words are copied, dates carry the birth-
const voice = professionalConversationVoice;
// 原值:「再 Yoga(只写 yogas.hits 里成立的,写成立条件……)」;新值:状态照卡上原词写;原因:占星师 2026-11-07 评测意见(candidate 格局被写成「成立」,原句本身就要求写「成立的」)。
assert.doesNotMatch(voice, /yogas\.hits 里成立的/);
assert.match(voice, /状态词照卡上原词抄:candidate 写「候选」、computed 写「已计算,未验证」、strength_comparison_required 写「待定」;不升格为「成立」「确认」「命中」,标题里也不写「已成立」/);
// 原值:/状态词照卡上原词抄:candidate 写「候选」、computed 写「已计算,未验证」、strength_comparison_required 写「待定」;不升格为「成立」「确认」「命中」,标题里也不写「已成立」/
// 新值:断言新的格局状态行(含映射表整串 + 其他状态词那句原文)
// 原因:BUG-1825:占星师 2026-11-09 第七轮回复 §5 P0.2,格局状态用 yoga-status-words.ts 的同一张映射表
assert.ok(voice.includes(`- 格局(Yoga)的状态只按卡上 yogas_detail 那一行写,用这张表:${yogaStatusContractTable()};`), voice);
assert.match(voice, /其他状态词照卡上原词抄:candidate 写「候选」、strength_comparison_required 写「待定」;不升格为「成立」「确认」「命中」,标题里也不写「已成立」/);
// 原值:无;新值:点位和宫主只照卡上字段写、一个点一个值,卡上没给的大运层写「未交付」;原因:占星师 2026-11-07 评测意见(A11 表格写狮子、正文写金牛/11 宫;三次运行把下一个 Narayana 主运处女写成下一个子运)。
assert.match(voice, /同一个点在正文、表格、审计表里只有一个值/);
assert.match(voice, /卡上没给的大运层(比如下一个子运)写「未交付」,不拿下一个主运或别的体系顶替/);
+2 -2
View File
@@ -192,8 +192,8 @@ evidence_card.backstage records, for the backend only, that global web evidence
5. 不写没有区分度的推论:逆行不等于「反复、打回来」,也不等于「拖得久、拖出来」,推论里不拿逆行当理由(依据里可以列);宫主不在本宫不等于「往外跑」;某星不在某宫不等于「离得远」;空宫不单独下结论。
<!-- 产品规则,不是占星师的方法:S-06 不断言死亡、离婚、疾病诊断、流产、不孕。 -->
6. 不断言死亡、离婚、疾病诊断、流产、不孕。
<!-- 产品规则,不是占星师的方法:S-07 瑜伽只认 yogas.hits。 -->
7. 瑜伽只认 yogas.hits:hits 里的才是这张盘成立的格局;只在 names 里、没进 hits 的是待检规则,不得说成本人的格局。
<!-- 产品规则,不是占星师的方法:S-07 格局状态词。BUG-1825:原值「瑜伽只认 yogas.hits:hits 里的才是这张盘成立的格局;只在 names 里、没进 hits 的是待检规则,不得说成本人的格局。」;新值:按 yogas_detail 每行状态、用 yoga-status-words.ts 的映射表({{YOGA_STATUS_TABLE}});原因:占星师 2026-11-09 第七轮回复 §5 P0.2「使用hits列表不能推断成立;没有完整条件检查不能写落空」,模型与页面用同一张表。 -->
7. 格局只按卡上 yogas_detail 每一行的状态说,状态文字用这张表:{{YOGA_STATUS_TABLE}}。yogas.hits 只是检测器匹配到的规则名,不能据此写「成立」;只在 names 里、没进 hits 的是待检规则,不得说成本人的格局;没有完整条件检查,不写「不成立」「非成立」「落空」。
<!-- 产品规则,不是占星师的方法:S-08 traditional_yogas 只说明力量。 -->
8. `traditional_yogas` 只用来说明力量,不单独下结论,不算受冲,也不抵消受冲。
<!-- 产品规则,不是占星师的方法:S-09 综合判断表受冲条数留在后台。第五轮裁定 6(BUG-1756):原值「只计数,不下结论、不报给用户」;新值:条数留作后台字段、不单独当结论,用户需要理解冲突时写出实际来源;原因:占星师 2026-11-08 第五轮裁定 6「保留后台字段,但用户需要理解冲突时必须展示实际来源」。BUG-1758:原值「留在后台作计数,不单独当结论,也不只报一个条数」;新值:卡上各宫只列来源(指向按事实去重的来源账,每条写输入与作用),条数只在后台引擎字段,回答写来源、不写条数;同源不算独立票(占星师 2026-11-08 验收);原因:r4 盲测念出受冲条数 6/70 → 13/70,「不只报一个条数」被读成可以连条数一起报。 -->