fix(rectification): ask a ready discriminator before dated collect
Family coverage still continues dated collection when nothing can split leftover minutes, but an already-built choice card must not be displaced by spoken finance collect. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -1,5 +1,9 @@
|
||||
# 印度占星 Skill 更新日志
|
||||
|
||||
## 2026-09-05 — 有现成区分卡时先问卡,没有卡才补采集
|
||||
|
||||
生时校正里,家人那题已经覆盖或说「没有」之后,如果引擎已经算出能分开剩余分钟的选择卡,就先问那张卡。只有这一轮拿不出可点的区分卡时,才继续问还没问过的钱、搬家、身体这些带年份的经历。Skill 版本仍是 10.0.14。
|
||||
|
||||
## 2026-09-05 — 家人说没有之后继续收能区分时间的经历
|
||||
|
||||
生时校正里,家里那题答「没有」之后,如果学业、感情、事业已经有带年份的事,会接着问钱、搬家、身体或工作,用来分开还分得开的几个分钟。不会因为已经可以给代表时间就直接结束。Skill 版本仍是 10.0.14。
|
||||
|
||||
@@ -8435,5 +8435,22 @@
|
||||
- 防复发:训练门已开不得跳过未用的 dated collect。`canAdopt` 不得单独构成跳过财务/搬家/健康/职业采集的理由。不得把 BUG-539「家人没有后采用」读成「未问完的经历域也可以结束」。
|
||||
- 相关记录:BUG-519、BUG-539、BUG-544
|
||||
- 复发自:BUG-519(采用早退把未用采集域一并吃掉);BUG-539 把该收口写成非缺陷
|
||||
- 修复版本:`998406ce`
|
||||
- 修正:`998406ce` 把家人之后的 dated 补采集做成无条件优先,现成搬家 A/B 卡被财务口述挤掉。见 BUG-547。
|
||||
|
||||
## BUG-547 | 现成区分卡被口述补采集挤掉
|
||||
|
||||
- 状态:resolved
|
||||
- 首次发现:2026-09-05
|
||||
- 最近更新:2026-09-05
|
||||
- 影响面:`buildMethodFollowupPlan`、`nextDatedCollectFollowup`、精度层 / yearless 区分卡
|
||||
- 用户现象:学业、感情、事业、家人都已有带年份经历,引擎已经给出搬家 A/B 探针,下一问却变成财务口述采集。
|
||||
- 触发条件:经典八法覆盖、精度阶段 `d4_refine`、存在可渲染 `choice_frame` 的搬家探针,同时 `DATED_COLLECT_ORDER` 里还有未覆盖域。
|
||||
- 根因:BUG-546 把家人之后的 `nextDatedCollectFollowup` 从「训练门未开才走」改成无条件执行,且排在精度层搬家卡和 yearless 卡之前。BUG-546 的触发条件本是「没有可用区分题」,实现扩成了「不管有没有区分题都先采集」。
|
||||
- 修复:家人之后若 yearless 或精度层能拿出 `choice_frame`,先问那张区分卡;只有本轮没有任何可渲染区分卡时才走 dated 补采集。`isRemainingEvidenceCollect` 对 `deferAdoption` 的豁免保留。不放宽精确分钟确认门,不改 `DATED_COLLECT_ORDER`。
|
||||
- 验证:`rectification-choice-card` 搬家 A/B 卡用例原样通过;无探针时仍问财务采集;occupation 顺序断言改为 `d2_finance` 并写三栏说明。
|
||||
- 防复发:dated 补采集不得抢在可渲染区分卡之前。没有区分卡时仍必须继续收未问过的带年份域(BUG-546)。
|
||||
- 相关记录:BUG-546、BUG-519
|
||||
- 复发自:BUG-546
|
||||
- 修复版本:待发布
|
||||
|
||||
|
||||
@@ -41,3 +41,46 @@
|
||||
| `page.tsx` | 未改 |
|
||||
|
||||
未做浏览器真人走查。清单:`docs/testing/rectification-family-collect-continue-20260905.md`。旧会话在部署前仍会按旧逻辑结束。
|
||||
|
||||
## BUG-547 · 现成区分卡优先于口述补采集
|
||||
|
||||
工作树:`.worktrees/rectification-family-collect-continue-fix-20260905`
|
||||
分支:`codex/rectification-family-collect-continue-fix-20260905`
|
||||
基线:`origin/staging` @ `309c7424`(含 BUG-546 的 `998406ce`)。未改 `.gitea/workflows/**`、未动迁移、未升级依赖、未 bump Skill(仍 10.0.14)、未改 `page.tsx`。
|
||||
|
||||
| 任务 | 状态 | BUG |
|
||||
| --- | --- | --- |
|
||||
| 补采集让位于可渲染区分卡 | 完成 | BUG-547 |
|
||||
| 无区分卡时仍走 dated 补采集 | 完成 | BUG-546 保留 |
|
||||
|
||||
### 编号
|
||||
|
||||
开工时最大号 BUG-546。本单 **BUG-547**。未占用预留的 BUG-542。
|
||||
|
||||
### 实现取舍
|
||||
|
||||
在 `if (!next)` 链里,家人之后先看 yearless / 精度层能否拿出 `choice_frame`,有卡才问卡;没有卡才 `nextDatedCollectFollowup`。精度层抢在采集前必须训练门已开,避免 `lagna_frame` 借走无关探针、在 holdout 未齐时改成区分题。没有把 dated 分支整段挪到 `d4_refine` 之后,因为精度层在没有探针时也会生成无框 `distinguish` 题,整段后移会把 BUG-546 的八方法财务采集再改回去。
|
||||
|
||||
### 断言变更
|
||||
|
||||
| 项 | 原值 | 新值 | 原因 |
|
||||
| --- | --- | --- | --- |
|
||||
| `career evidence alone still does not cover occupation` 下一问 | `occupation` | `d2_finance` | 家人已覆盖且无区分卡,BUG-546 继续收未用带年份域;职业仍 uncovered |
|
||||
| `training gate open does not mint yearless…` 下一问 | `occupation` | `d2_finance` | yearless 存在/质量卡被丢掉后没有可渲染区分卡,先财务采集 |
|
||||
|
||||
`distinguish follow-up copy forbids competing-chart ranking` 未改断言。新增无探针对照:同样证据集、没有搬家探针时下一问仍是财务采集。
|
||||
|
||||
### 测试
|
||||
|
||||
| 命令 | 结果 |
|
||||
| --- | --- |
|
||||
| choice-card + occupation-coverage-exit | 55/55 |
|
||||
| eight-method + choice-card + occupation | 117/117 |
|
||||
| collect-stall / collect-direction / provisional-adopt / range-offer / adopt-narration / adopt-flow-fix / eight-method / choice-card / occupation / agent-voice | 192/192 |
|
||||
| `npx tsx --test tests/rectification-*.test.ts tests/agent-voice-copy-contract.test.ts` | 895 项;产品失败 0。整包曾出现 1 次 Docker 迁移夹具失败(`v9 agent api migration…`),单测重跑 1/1 通过 |
|
||||
| `tsc --noEmit` | 0 错 |
|
||||
| 改动文件 eslint `--quiet` | 0 error |
|
||||
| `page.tsx` | 未改 |
|
||||
|
||||
未做浏览器真人走查。旧会话在部署前仍会按 `998406ce` 把现成区分卡挤掉。
|
||||
|
||||
|
||||
@@ -53,7 +53,7 @@
|
||||
| `TASK-rectification-quality-probe-dedupe-20260904.md` | `PROGRESS-rectification-quality-probe-dedupe-20260904.md` | 候选区分阶段同一道"上大学"题问两次:`pickProbe` 按种类取第一条质量探针无视 `semantic_key`(第二次答案绑到错的证据);引擎对毕业等学业 kind 也套"高考发挥"模板;同域同分组质量探针不去重 | 已验收通过(浏览器走查欠) | `codex/rectification-quality-probe-dedupe-20260904`(BUG-540~541) |
|
||||
| — | `PROGRESS-rectification-adopt-card-anchor-20260905.md` | 采用卡钉在历史采集题下,最新旁白没有卡;单分钟交付旁白机器腔 | 已验收通过(浏览器走查欠) | `codex/rectification-quality-probe-dedupe-20260904`(BUG-544~545) |
|
||||
| — | `PROGRESS-rectification-family-collect-continue-20260905.md` | 家人答「没有」后直接给结果,未继续收能区分分钟的经历 | 验收未通过(1 项行为回归 + 2 条断言未更新,见修复单) | `codex/rectification-quality-probe-dedupe-20260904`(BUG-546) |
|
||||
| `TASK-rectification-family-collect-continue-fix-20260905.md` | — | `998406ce` 修复单:家人之后的 dated 补采集无条件抢在现成区分卡之前(choice-card A/B 卡用例回归),另两条顺序断言缺三栏说明 | 待执行 | `codex/rectification-family-collect-continue-fix-20260905`(BUG-547 起) |
|
||||
| `TASK-rectification-family-collect-continue-fix-20260905.md` | `PROGRESS-rectification-family-collect-continue-20260905.md` | `998406ce` 修复单:家人之后的 dated 补采集无条件抢在现成区分卡之前(choice-card A/B 卡用例回归),另两条顺序断言缺三栏说明 | 待验收 | `codex/rectification-family-collect-continue-fix-20260905`(BUG-547) |
|
||||
| `TASK-api-not-configured-mislabel-20260904.md` | — | 16 处路由把数据库瞬断(部署切换窗口)兜底翻译成 503「服务尚未配置」;改为仅配置错误用该文案,其余 `service_unavailable`,收敛为共享 helper | 待执行 | `codex/api-not-configured-mislabel-20260904`(BUG-542 起) |
|
||||
| `TASK-rectification-ux-20260902.md` | `PROGRESS-rectification-ux-20260903.md` | 会话面空白假死与交互摩擦 | 已验收 | `d159f08e`(09-03 在新基线重做后合入,BUG-505~509) |
|
||||
|
||||
|
||||
@@ -17,6 +17,15 @@
|
||||
- ✅ 预期:下一句是还没问过的带年份经历。学业/感情/事业都有了的话,应是「钱的方面,还记得哪年收入明显变过、有过大笔支出,或欠过债吗?」不是采用旁白,也不是「再问下去也分不开」。
|
||||
- ❌ 失败:直接出现「我按你说的经历认真分析过了,下面是这次的结果。」或时间卡。
|
||||
|
||||
## 1b. 已有搬家区分卡时不要改口问钱(P0 · BUG-547)
|
||||
|
||||
同一轮里如果已经出现过搬家/离乡的 A/B 卡(「某年前后,有没有明显搬家…」),答完家人「没有」之后:
|
||||
|
||||
- ✅ 预期:下一问仍是那张能分开剩余分钟的选择卡,不是财务口述。
|
||||
- ❌ 失败:改口问「钱的方面,还记得哪年收入明显变过…」。
|
||||
|
||||
没有区分卡、只说「没有」时,仍走第 1 条的财务采集。
|
||||
|
||||
## 2. 这题还没答时不要出时间卡(P0 · 叠 BUG-544)
|
||||
|
||||
家人答「没有」后,财务(或下一域)口述题还在:
|
||||
|
||||
@@ -1974,8 +1974,98 @@ export function buildMethodFollowupPlan(input: {
|
||||
const candidate = followupFromRanked(ranked);
|
||||
return candidate.choice_frame ? candidate : null;
|
||||
};
|
||||
const firstRenderableYearlessFollowup = (): MethodFollowup | null => {
|
||||
for (const ranked of yearlessDiscriminators) {
|
||||
const domain = contrastFollowupDomain(
|
||||
ranked.eventProbe?.domain ?? ranked.contrastProbe?.domain ?? null,
|
||||
);
|
||||
if (declined.has(domain)) continue;
|
||||
const key = rankedDiscriminatorKey(ranked);
|
||||
if (key && askedKeys.has(key)) continue;
|
||||
const candidate = followupFromRanked(ranked);
|
||||
if (candidate.choice_frame) return candidate;
|
||||
}
|
||||
return null;
|
||||
};
|
||||
const precisionStageFollowup = (): MethodFollowup | null => {
|
||||
if (stage === "lagna_frame") {
|
||||
return makeFollowup({
|
||||
method_id: "dasha_events",
|
||||
intent: "distinguish_candidates",
|
||||
ask_theme: "dated_event",
|
||||
domain: null,
|
||||
kind_hint: null,
|
||||
user_prompt_hint: ask(
|
||||
"窗口里本命上升还可能落在两段。按 choice_frame.period 与探针问一件前事是否发生,用来筛这两段。不要问两套盘哪个更像。",
|
||||
"本命上升 / Dasha",
|
||||
),
|
||||
source: "precision_stage",
|
||||
});
|
||||
}
|
||||
if (stage === "d9_refine" && !relationshipCovered && !declined.has("relationship")) {
|
||||
return makeFollowup({
|
||||
method_id: "d9_relationship",
|
||||
intent: "distinguish_candidates",
|
||||
ask_theme: "relationship_style",
|
||||
domain: "relationship",
|
||||
kind_hint: "relationship_change",
|
||||
user_prompt_hint: ask(
|
||||
"关系盘仍会换升。按探针年份问感情这条线的前事是否发生,用来筛窗。不要问两套盘哪个更像。",
|
||||
"D9",
|
||||
"对照 D9 上升类型表只作校时方法。",
|
||||
),
|
||||
source: "precision_stage",
|
||||
});
|
||||
}
|
||||
if (stage === "d10_refine" && !declined.has("career")) {
|
||||
return makeFollowup({
|
||||
method_id: "d10_career",
|
||||
intent: "distinguish_candidates",
|
||||
ask_theme: "career_style",
|
||||
domain: "career",
|
||||
kind_hint: "career_change",
|
||||
user_prompt_hint: ask(
|
||||
"事业盘仍会换升。按探针年份问事业这条线的前事是否发生,用来筛窗。不要问两套盘哪个更像。",
|
||||
"D10",
|
||||
"可用 D10 事业类型表作校时对照。",
|
||||
),
|
||||
source: "precision_stage",
|
||||
});
|
||||
}
|
||||
if ((stage === "d4_refine" || stage === "theme_refine") && !declined.has("relocation")) {
|
||||
return makeFollowup({
|
||||
method_id: "d4_home",
|
||||
intent: "distinguish_candidates",
|
||||
ask_theme: "home_change",
|
||||
domain: "relocation",
|
||||
kind_hint: "home_change",
|
||||
user_prompt_hint: ask(
|
||||
"居所盘仍会换升。按探针年份问搬家或住处变化是否发生,用来筛窗。不要问两套盘哪个更像。",
|
||||
"D4",
|
||||
),
|
||||
source: "precision_stage",
|
||||
});
|
||||
}
|
||||
if (stage === "d5_refine" && !declined.has("education")) {
|
||||
return makeFollowup({
|
||||
method_id: "d5_education",
|
||||
intent: "distinguish_candidates",
|
||||
ask_theme: "education_style",
|
||||
domain: "education",
|
||||
kind_hint: "education_milestone",
|
||||
user_prompt_hint: ask(
|
||||
"成就盘或学业盘仍会换升。按探针年份问学业或考试变化是否发生,用来筛窗。不要问两套盘哪个更像。",
|
||||
"D5 / D24",
|
||||
),
|
||||
source: "precision_stage",
|
||||
});
|
||||
}
|
||||
return null;
|
||||
};
|
||||
let datedCollect: MethodFollowup | null = null;
|
||||
let renderableYearless: MethodFollowup | null = null;
|
||||
if (!next) {
|
||||
const precisionCard = precisionStageFollowup();
|
||||
if (!relationshipCovered && !declined.has("relationship")) {
|
||||
next = sameDomainYearlessCard("relationship") ?? makeFollowup({
|
||||
method_id: "d9_relationship",
|
||||
@@ -2020,6 +2110,16 @@ export function buildMethodFollowupPlan(input: {
|
||||
source: "method_coverage",
|
||||
...collectionYearFields(familyCollect),
|
||||
});
|
||||
} else if ((renderableYearless = firstRenderableYearlessFollowup())) {
|
||||
next = renderableYearless;
|
||||
} else if (
|
||||
// BUG-547: a renderable precision card beats dated collect, but only
|
||||
// after the training gate is open. lagna_frame can borrow an unrelated
|
||||
// probe into choice_frame while holdout is still short.
|
||||
meetsAcceptanceEventQuality(input.evidence)
|
||||
&& precisionCard?.choice_frame
|
||||
) {
|
||||
next = precisionCard;
|
||||
} else if ((datedCollect = nextDatedCollectFollowup(input.evidence, declined))) {
|
||||
next = makeFollowup(datedCollect);
|
||||
} else if (!occupationCovered) {
|
||||
@@ -2063,73 +2163,8 @@ export function buildMethodFollowupPlan(input: {
|
||||
&& !declined.has("relocation"))
|
||||
) {
|
||||
next = makeFollowup(otherCollectFollowup(input.evidence));
|
||||
} else if (stage === "lagna_frame") {
|
||||
next = makeFollowup({
|
||||
method_id: "dasha_events",
|
||||
intent: "distinguish_candidates",
|
||||
ask_theme: "dated_event",
|
||||
domain: null,
|
||||
kind_hint: null,
|
||||
user_prompt_hint: ask(
|
||||
"窗口里本命上升还可能落在两段。按 choice_frame.period 与探针问一件前事是否发生,用来筛这两段。不要问两套盘哪个更像。",
|
||||
"本命上升 / Dasha",
|
||||
),
|
||||
source: "precision_stage",
|
||||
});
|
||||
} else if (stage === "d9_refine" && !relationshipCovered && !declined.has("relationship")) {
|
||||
next = makeFollowup({
|
||||
method_id: "d9_relationship",
|
||||
intent: "distinguish_candidates",
|
||||
ask_theme: "relationship_style",
|
||||
domain: "relationship",
|
||||
kind_hint: "relationship_change",
|
||||
user_prompt_hint: ask(
|
||||
"关系盘仍会换升。按探针年份问感情这条线的前事是否发生,用来筛窗。不要问两套盘哪个更像。",
|
||||
"D9",
|
||||
"对照 D9 上升类型表只作校时方法。",
|
||||
),
|
||||
source: "precision_stage",
|
||||
});
|
||||
} else if (stage === "d10_refine" && !declined.has("career")) {
|
||||
next = makeFollowup({
|
||||
method_id: "d10_career",
|
||||
intent: "distinguish_candidates",
|
||||
ask_theme: "career_style",
|
||||
domain: "career",
|
||||
kind_hint: "career_change",
|
||||
user_prompt_hint: ask(
|
||||
"事业盘仍会换升。按探针年份问事业这条线的前事是否发生,用来筛窗。不要问两套盘哪个更像。",
|
||||
"D10",
|
||||
"可用 D10 事业类型表作校时对照。",
|
||||
),
|
||||
source: "precision_stage",
|
||||
});
|
||||
} else if ((stage === "d4_refine" || stage === "theme_refine") && !declined.has("relocation")) {
|
||||
next = makeFollowup({
|
||||
method_id: "d4_home",
|
||||
intent: "distinguish_candidates",
|
||||
ask_theme: "home_change",
|
||||
domain: "relocation",
|
||||
kind_hint: "home_change",
|
||||
user_prompt_hint: ask(
|
||||
"居所盘仍会换升。按探针年份问搬家或住处变化是否发生,用来筛窗。不要问两套盘哪个更像。",
|
||||
"D4",
|
||||
),
|
||||
source: "precision_stage",
|
||||
});
|
||||
} else if (stage === "d5_refine" && !declined.has("education")) {
|
||||
next = makeFollowup({
|
||||
method_id: "d5_education",
|
||||
intent: "distinguish_candidates",
|
||||
ask_theme: "education_style",
|
||||
domain: "education",
|
||||
kind_hint: "education_milestone",
|
||||
user_prompt_hint: ask(
|
||||
"成就盘或学业盘仍会换升。按探针年份问学业或考试变化是否发生,用来筛窗。不要问两套盘哪个更像。",
|
||||
"D5 / D24",
|
||||
),
|
||||
source: "precision_stage",
|
||||
});
|
||||
} else if (precisionCard) {
|
||||
next = precisionCard;
|
||||
} else {
|
||||
const d9 = input.observations?.find((item) => item.layer === "d9");
|
||||
const d10 = input.observations?.find((item) => item.layer === "d10");
|
||||
|
||||
@@ -751,6 +751,54 @@ test("distinguish follow-up copy forbids competing-chart ranking", () => {
|
||||
assert.match(plan.next_followup?.user_prompt_hint ?? "", /不得发明年份/);
|
||||
});
|
||||
|
||||
test("family coverage without a renderable discriminator still collects the next dated domain", () => {
|
||||
const plan = buildMethodFollowupPlan({
|
||||
evidence: [{
|
||||
status: "confirmed",
|
||||
domain: "education",
|
||||
datePrecision: "year",
|
||||
occurredFrom: "2016-01-01",
|
||||
occurredTo: null,
|
||||
}, {
|
||||
status: "confirmed",
|
||||
domain: "relationship",
|
||||
datePrecision: "year",
|
||||
occurredFrom: "2018-01-01",
|
||||
occurredTo: null,
|
||||
}, {
|
||||
status: "confirmed",
|
||||
domain: "career",
|
||||
datePrecision: "year",
|
||||
occurredFrom: "2019-01-01",
|
||||
occurredTo: null,
|
||||
}, {
|
||||
status: "confirmed",
|
||||
domain: "family",
|
||||
datePrecision: "year",
|
||||
occurredFrom: "2020-01-01",
|
||||
occurredTo: null,
|
||||
}, {
|
||||
status: "confirmed",
|
||||
domain: "occupation",
|
||||
datePrecision: "unknown",
|
||||
occurredFrom: null,
|
||||
occurredTo: null,
|
||||
}, {
|
||||
status: "confirmed",
|
||||
domain: "horary",
|
||||
datePrecision: "day",
|
||||
occurredFrom: "2024-01-01",
|
||||
occurredTo: null,
|
||||
}],
|
||||
precisionStage: "d4_refine",
|
||||
birthDate: "1997-08-08",
|
||||
});
|
||||
assert.equal(plan.next_followup?.method_id, "d2_finance");
|
||||
assert.equal(plan.next_followup?.intent, "collect_method_evidence");
|
||||
assert.equal(plan.next_followup?.choice_frame, null);
|
||||
assert.equal(plan.deferred_followup, null);
|
||||
});
|
||||
|
||||
test("GET A/B card stays visible after coverage when candidates remain tied", () => {
|
||||
const card = projectRectificationChoiceCard({
|
||||
evidence: [{
|
||||
|
||||
@@ -248,7 +248,10 @@ test("career evidence alone still does not cover occupation", () => {
|
||||
contrastPacket: { candidateSetVersion: "04:47-05:15", vargaDifferences: [], probes: [] },
|
||||
});
|
||||
assert.equal(plan.methods.find((item) => item.method_id === "occupation")?.status, "uncovered");
|
||||
assert.equal(plan.next_followup?.method_id, "occupation");
|
||||
// 原值: occupation
|
||||
// 新值: d2_finance
|
||||
// 原因: 家人已覆盖且无区分卡时,BUG-546 继续收未用带年份域;职业仍未覆盖,只是还没轮到
|
||||
assert.equal(plan.next_followup?.method_id, "d2_finance");
|
||||
});
|
||||
|
||||
test("training gate open does not mint yearless existence or quality varga cards", () => {
|
||||
@@ -256,7 +259,10 @@ test("training gate open does not mint yearless existence or quality varga cards
|
||||
assert.equal(plan.methods.find((item) => item.method_id === "occupation")?.status, "uncovered");
|
||||
assert.doesNotMatch(plan.next_followup?.semantic_key ?? "", /^varga\.d(24|7|4|5)\./);
|
||||
assert.equal(plan.dropped_probes.some((item) => item.reason === "yearless_ungrounded_contrast"), true);
|
||||
assert.equal(plan.next_followup?.method_id, "occupation");
|
||||
// 原值: occupation
|
||||
// 新值: d2_finance
|
||||
// 原因: yearless 存在/质量卡被丢掉后没有可渲染区分卡,BUG-546 先收财务采集
|
||||
assert.equal(plan.next_followup?.method_id, "d2_finance");
|
||||
assert.equal(plan.next_followup?.intent, "collect_method_evidence");
|
||||
assert.equal(plan.next_followup?.choice_frame, null);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user