feat(rectification): 出卡加精度门槛,补经历改成系统点名
宽度超过 10 分钟或头名并列时不再出交付卡,改为按大运边界逐条问、 用类型芯片和年/月选择器录入。跳过的线换问法再问一次;答「这类事 都没有过」的不再问。用户说「没有了」仍立刻给目前范围。Skill 10.0.27。 BUG-740~743
This commit is contained in:
@@ -0,0 +1,33 @@
|
||||
/**
|
||||
* Range-delivery precision gate (D1). Width, display-gap, and exact score
|
||||
* tie are independent of scoring / cluster merge.
|
||||
*/
|
||||
|
||||
import { RECTIFICATION_POLICY } from "../../rectification-policy.ts";
|
||||
import { RANGE_DELIVERY_TIE_PERCENT, rangeWidthMinutes } from "../user-copy.ts";
|
||||
|
||||
export function deliveryWidthMinutes(
|
||||
range: readonly [string, string] | null | undefined,
|
||||
): number | null {
|
||||
if (!range?.[0] || !range[1]) return null;
|
||||
if (range[0] === range[1]) return 0;
|
||||
return rangeWidthMinutes(range[0], range[1]);
|
||||
}
|
||||
|
||||
export function probabilityToPercent(value: number | null | undefined): number {
|
||||
if (typeof value !== "number" || !Number.isFinite(value) || value < 0) return 0;
|
||||
const raw = value <= 1 ? value * 100 : value;
|
||||
return Math.min(100, Math.round(raw));
|
||||
}
|
||||
|
||||
export function computePrecisionGateMet(input: {
|
||||
range: readonly [string, string] | null | undefined;
|
||||
topTwoPercents: readonly number[];
|
||||
tiedForFirst: boolean;
|
||||
}): boolean {
|
||||
if (input.tiedForFirst) return false;
|
||||
const width = deliveryWidthMinutes(input.range);
|
||||
if (width == null || width > RECTIFICATION_POLICY.deliveryMaxWidthMinutes) return false;
|
||||
if (input.topTwoPercents.length < 2) return input.topTwoPercents.length === 1;
|
||||
return input.topTwoPercents[0]! - input.topTwoPercents[1]! > RANGE_DELIVERY_TIE_PERCENT;
|
||||
}
|
||||
@@ -231,6 +231,10 @@ export type DecideRectificationInput = Readonly<{
|
||||
windowWidenSuggested?: boolean;
|
||||
refreshExhausted?: boolean;
|
||||
targetedCollectExhausted?: boolean;
|
||||
/** D1: width ≤ 10, top-two display gap > 3, not an exact first-place tie. */
|
||||
precisionGateMet?: boolean;
|
||||
/** Guided window / skip-retry / uncovered-domain pool is empty. */
|
||||
guidedCollectExhausted?: boolean;
|
||||
/** Opening search window from `case.candidateRange`. Omit in helper/unit paths. */
|
||||
openingCandidateRange?: readonly [string, string] | null;
|
||||
/** Unasked D9/D10 style questions remain. */
|
||||
@@ -359,6 +363,16 @@ export function decideRectification(input: DecideRectificationInput): Rectificat
|
||||
&& !probe
|
||||
&& input.targetedCollectExhausted !== false
|
||||
) {
|
||||
if (!mayDeliverOnPrecision(input)) {
|
||||
return collect(
|
||||
separation,
|
||||
holdout,
|
||||
range,
|
||||
probe,
|
||||
waitToNarrowCapability(rangeDeliveryCapability),
|
||||
stopClass.reason,
|
||||
);
|
||||
}
|
||||
return deliverRange(
|
||||
input,
|
||||
separation,
|
||||
@@ -372,7 +386,7 @@ export function decideRectification(input: DecideRectificationInput): Rectificat
|
||||
if (coverageBlocks) {
|
||||
const engineOffers = input.engineCeiling.acceptanceAllowed
|
||||
|| input.engineCeiling.proposeAllowed;
|
||||
const narrowingOpen = stillNeedNarrowing(input);
|
||||
const narrowingOpen = stillNeedNarrowing(input) && !mayDeliverOnPrecision(input);
|
||||
if (
|
||||
stopClass?.kind !== "keep_collecting"
|
||||
&& input.trainingGateOpen !== false
|
||||
@@ -406,17 +420,37 @@ export function decideRectification(input: DecideRectificationInput): Rectificat
|
||||
return holdoutValidation(separation, range, capability);
|
||||
}
|
||||
// coverageBlocks already collected when the training gate is closed.
|
||||
// Dated-pool empty is not delivery until refresh and targeted collect are
|
||||
// exhausted (BUG-654). Personality still does not occupy this slot.
|
||||
// Uncovered collect lines keep collecting only while D1 is unmet
|
||||
// (BUG-654 + precision gate). Personality still does not occupy this slot.
|
||||
// Omitted flags mean the helper/unit path: do not wait. Production
|
||||
// decideFromDossier always passes explicit booleans.
|
||||
if (stillNeedNarrowing(input)) {
|
||||
if (stillNeedNarrowing(input) && !mayDeliverOnPrecision(input)) {
|
||||
return collect(separation, holdout, range, probe, waitToNarrowCapability(capability), stopReason);
|
||||
}
|
||||
if (stopClass?.kind === "exhausted") {
|
||||
if (!mayDeliverOnPrecision(input)) {
|
||||
return collect(
|
||||
separation,
|
||||
holdout,
|
||||
range,
|
||||
probe,
|
||||
waitToNarrowCapability(rangeDeliveryCapability),
|
||||
stopReason,
|
||||
);
|
||||
}
|
||||
return deliverRange(input, separation, holdout, range, "exhausted", rangeDeliveryCapability, stopClass.reason);
|
||||
}
|
||||
if (rangeDeliveryCapability.canAdopt && input.methodCoverageAll) {
|
||||
if (!mayDeliverOnPrecision(input)) {
|
||||
return collect(
|
||||
separation,
|
||||
holdout,
|
||||
range,
|
||||
probe,
|
||||
waitToNarrowCapability(rangeDeliveryCapability),
|
||||
"probe_pool_exhausted",
|
||||
);
|
||||
}
|
||||
return finish("adopt_representative", {
|
||||
input,
|
||||
separation,
|
||||
@@ -427,9 +461,29 @@ export function decideRectification(input: DecideRectificationInput): Rectificat
|
||||
stopReason: "probe_pool_exhausted",
|
||||
});
|
||||
}
|
||||
if (!mayDeliverOnPrecision(input)) {
|
||||
return collect(
|
||||
separation,
|
||||
holdout,
|
||||
range,
|
||||
probe,
|
||||
waitToNarrowCapability(rangeDeliveryCapability),
|
||||
stopReason,
|
||||
);
|
||||
}
|
||||
return deliverRange(input, separation, holdout, range, "offer", rangeDeliveryCapability);
|
||||
}
|
||||
if (stopClass?.kind === "exhausted") {
|
||||
if (!mayDeliverOnPrecision(input)) {
|
||||
return collect(
|
||||
separation,
|
||||
holdout,
|
||||
range,
|
||||
probe,
|
||||
waitToNarrowCapability(rangeDeliveryCapability),
|
||||
stopReason,
|
||||
);
|
||||
}
|
||||
return deliverRange(input, separation, holdout, range, "exhausted", rangeDeliveryCapability, stopClass.reason);
|
||||
}
|
||||
if (input.accepted) {
|
||||
@@ -445,7 +499,7 @@ export function decideRectification(input: DecideRectificationInput): Rectificat
|
||||
capability: rangeDeliveryCapability,
|
||||
});
|
||||
}
|
||||
if (input.datedMethodCollectOpen === true && !input.userStopped) {
|
||||
if (input.datedMethodCollectOpen === true && !input.userStopped && !mayDeliverOnPrecision(input)) {
|
||||
return collect(separation, holdout, range, null, capability, stopReason);
|
||||
}
|
||||
if (confirmationAllowed) {
|
||||
@@ -588,6 +642,20 @@ function stillNeedNarrowing(input: DecideRectificationInput): boolean {
|
||||
return input.refreshExhausted === false || input.targetedCollectExhausted === false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Exhausted / offer delivery is blocked until D1, the user stops, or the
|
||||
* guided pool is empty. Omitted flags keep the helper/unit path delivering.
|
||||
*/
|
||||
export function mayDeliverOnPrecision(input: DecideRectificationInput): boolean {
|
||||
if (input.userStopped === true) return true;
|
||||
if (input.guidedCollectExhausted === true) return true;
|
||||
if (input.precisionGateMet === true) return true;
|
||||
if (input.precisionGateMet === undefined && input.guidedCollectExhausted === undefined) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
export function shouldHoldForTieBreak(
|
||||
input: Pick<
|
||||
DecideRectificationInput,
|
||||
|
||||
Reference in New Issue
Block a user