fix(rectification): share datedPrecision for quarter and range events
Training already counted quarter/range as dated, while the inference ledger mapped them to unknown and left the events unused. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -6429,6 +6429,22 @@
|
|||||||
- 复发自:BUG-403(候选卡、代表时间、可信区间必须共享 inference 投影,但读写口径未钉成同一函数)
|
- 复发自:BUG-403(候选卡、代表时间、可信区间必须共享 inference 投影,但读写口径未钉成同一函数)
|
||||||
- 修复版本:待发布
|
- 修复版本:待发布
|
||||||
|
|
||||||
|
## BUG-424 | quarter/range 精度训练门计入、推断账本却标 unused
|
||||||
|
|
||||||
|
- 状态:resolved
|
||||||
|
- 首次发现:2026-08-28
|
||||||
|
- 最近更新:2026-08-28
|
||||||
|
- 影响面:`datedPrecision`、`buildCaseInferenceState` 的 `asPrecision`
|
||||||
|
- 用户现象:账本允许 `quarter` / `range` 精度。训练门把它们当成带日期事件开门,推断状态却把同一批事件标成 `unknown`/`unused`,既不打分也不进 holdout。
|
||||||
|
- 触发条件:录证据工具写入 `datePrecision: "quarter"` 或 `"range"` 后重建 `inference_state`。
|
||||||
|
- 根因:`evidence-model.datedPrecision` 把 `quarter`/`range` 映射成 `year`,`inference-adapter.asPrecision` 映射成 `unknown`。
|
||||||
|
- 修复:导出同一个 `datedPrecision`,推断适配器也走它。空值仍是 `unknown`。
|
||||||
|
- 验证:`rectification-v9-contracts` 锁定 `quarter`/`range` → `year`。`rectification-credible-range-projection` 锁定 `quarter` 事件 `usage !== "unused"`。
|
||||||
|
- 防复发:训练门与推断账本不得对同一 `datePrecision` 使用两套映射。
|
||||||
|
- 相关记录:BUG-423
|
||||||
|
- 复发自:无
|
||||||
|
- 修复版本:待发布
|
||||||
|
|
||||||
## BUG-410 | 训练已齐仍因家人/职业方法层停在采集,Agent 只确认后截断
|
## BUG-410 | 训练已齐仍因家人/职业方法层停在采集,Agent 只确认后截断
|
||||||
|
|
||||||
- 状态:resolved
|
- 状态:resolved
|
||||||
|
|||||||
@@ -165,7 +165,7 @@ export function datePrecisionRank(precision: string): number {
|
|||||||
return PRECISION_RANK[precision] ?? -1;
|
return PRECISION_RANK[precision] ?? -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
function datedPrecision(value: string): DatedEventInput["precision"] {
|
export function datedPrecision(value: string): DatedEventInput["precision"] {
|
||||||
if (value === "day" || value === "month" || value === "year" || value === "unknown") return value;
|
if (value === "day" || value === "month" || value === "year" || value === "unknown") return value;
|
||||||
return "year";
|
return "year";
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ import { rankActive } from "../core/convergence-evaluator.ts";
|
|||||||
import { rangeFromTimes, unionStillValidRange } from "../core/credible-range.ts";
|
import { rangeFromTimes, unionStillValidRange } from "../core/credible-range.ts";
|
||||||
import { previousInferenceFromReceipt } from "../core/compose-receipt.ts";
|
import { previousInferenceFromReceipt } from "../core/compose-receipt.ts";
|
||||||
import type { AnswerClass, ConflictProbe, InferenceState } from "../core/types.ts";
|
import type { AnswerClass, ConflictProbe, InferenceState } from "../core/types.ts";
|
||||||
|
import { datedPrecision } from "./evidence-model.ts";
|
||||||
import {
|
import {
|
||||||
isHoldoutVerificationQuote,
|
isHoldoutVerificationQuote,
|
||||||
type ChoiceKey,
|
type ChoiceKey,
|
||||||
@@ -28,8 +29,8 @@ function yearFrom(value: string | null | undefined): number | null {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function asPrecision(value: string | null | undefined): EngineEventInput["precision"] {
|
function asPrecision(value: string | null | undefined): EngineEventInput["precision"] {
|
||||||
if (value === "day" || value === "month" || value === "year") return value;
|
if (!value) return "unknown";
|
||||||
return "unknown";
|
return datedPrecision(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function askedProbeKeysFromReceipt(
|
export function askedProbeKeysFromReceipt(
|
||||||
|
|||||||
@@ -196,6 +196,24 @@ test("a corrupted receipt range outside the window still fail-closes", () => {
|
|||||||
assert.equal(projection.credibleRange, null);
|
assert.equal(projection.credibleRange, null);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("quarter-precision evidence stays dated in the inference ledger", () => {
|
||||||
|
const state = buildCaseInferenceState({
|
||||||
|
range: { start_time: "05:00", end_time: "05:10" },
|
||||||
|
candidates: [
|
||||||
|
{ candidateId: "05:00", time: "05:00", relativeSupport: 50 },
|
||||||
|
{ candidateId: "05:10", time: "05:10", relativeSupport: 50 },
|
||||||
|
],
|
||||||
|
evidence: [
|
||||||
|
{ id: "q-career", domain: "career", occurredFrom: "2018-04-01", datePrecision: "quarter" },
|
||||||
|
{ id: "y-edu", domain: "education", occurredFrom: "2016-01-01", datePrecision: "year" },
|
||||||
|
],
|
||||||
|
probes: [],
|
||||||
|
});
|
||||||
|
const quarter = state.events.find((item) => item.id === "q-career");
|
||||||
|
assert.equal(quarter?.precision, "year");
|
||||||
|
assert.notEqual(quarter?.usage, "unused");
|
||||||
|
});
|
||||||
|
|
||||||
test("buildCaseInferenceState receipt is the producer of the projected range", () => {
|
test("buildCaseInferenceState receipt is the producer of the projected range", () => {
|
||||||
const state = buildCaseInferenceState({
|
const state = buildCaseInferenceState({
|
||||||
range: { start_time: "04:55", end_time: "05:02" },
|
range: { start_time: "04:55", end_time: "05:02" },
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ import {
|
|||||||
isEvidenceDomain,
|
isEvidenceDomain,
|
||||||
isEvidenceKind,
|
isEvidenceKind,
|
||||||
isEvidenceStatus,
|
isEvidenceStatus,
|
||||||
|
datedPrecision,
|
||||||
normalizeQuote,
|
normalizeQuote,
|
||||||
quoteIsGroundedInMessage,
|
quoteIsGroundedInMessage,
|
||||||
} from "../src/lib/rectification-agentic/v9/evidence-model.ts";
|
} from "../src/lib/rectification-agentic/v9/evidence-model.ts";
|
||||||
@@ -130,6 +131,10 @@ test("evidence model exposes the full kind/domain/precision/status sets", () =>
|
|||||||
assert.equal(isDatePrecision(precision), true);
|
assert.equal(isDatePrecision(precision), true);
|
||||||
}
|
}
|
||||||
assert.equal(isDatePrecision("exact_minute"), false);
|
assert.equal(isDatePrecision("exact_minute"), false);
|
||||||
|
assert.equal(datedPrecision("quarter"), "year");
|
||||||
|
assert.equal(datedPrecision("range"), "year");
|
||||||
|
assert.equal(datedPrecision("day"), "day");
|
||||||
|
assert.equal(datedPrecision("unknown"), "unknown");
|
||||||
for (const status of ["draft", "pending_confirmation", "confirmed", "superseded", "rejected"]) {
|
for (const status of ["draft", "pending_confirmation", "confirmed", "superseded", "rejected"]) {
|
||||||
assert.equal(isEvidenceStatus(status), true);
|
assert.equal(isEvidenceStatus(status), true);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user