Skip to content

Commit 89fc2b3

Browse files
committed
Update unneeded slot references
1 parent a517e04 commit 89fc2b3

3 files changed

Lines changed: 64 additions & 57 deletions

File tree

apps/web/src/lib/server/turn-router.ts

Lines changed: 16 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import {
1010
type RoutedTurn,
1111
resolveOperationKind,
1212
routedTurnSchema,
13-
SLOT_COMMITTING_TURN_TYPES,
13+
FIELD_COMMITTING_TURN_TYPES,
1414
type TurnAmbiguity,
1515
type TurnClassifierOutput,
1616
type TurnInterpretation,
@@ -27,7 +27,7 @@ export type TurnRouterInput = TurnRoutingInput;
2727
export type TurnRouterResult = RoutedTurn;
2828

2929
// Required schedule fields per operation kind — mirrors commit-policy internals.
30-
function requiredSlotsForOperation(
30+
function requiredScheduleFieldsForOperation(
3131
operationKind: OperationKind,
3232
): ("day" | "time" | "duration")[] {
3333
switch (operationKind) {
@@ -56,7 +56,7 @@ export async function routeMessageTurn(
5656
});
5757

5858
// Guard: reclassify compound confirmations (confirmation + modification payload)
59-
// so slot extraction runs and the edit is not silently dropped.
59+
// so field extraction runs and the edit is not silently dropped.
6060
// Scoped to active write/proposal context only.
6161
if (classification.turnType === "confirmation") {
6262
const hasActiveProposal = entityRegistry.some(
@@ -77,28 +77,30 @@ export async function routeMessageTurn(
7777
}
7878
}
7979

80-
// Pipeline B: extract slots (conditional)
80+
// Pipeline B: extract fields via the slot extractor (conditional)
8181
const priorOperation = discourseState.pending_write_operation;
8282
const operationKind =
8383
resolveOperationKind({
8484
turnType: classification.turnType,
8585
priorOperationKind: priorOperation?.operationKind,
8686
}) ?? "plan";
8787

88-
let slotExtraction = null;
88+
let fieldExtraction = null;
8989

90-
if (SLOT_COMMITTING_TURN_TYPES.has(classification.turnType)) {
90+
if (FIELD_COMMITTING_TURN_TYPES.has(classification.turnType)) {
9191
const priorScheduleFields =
9292
priorOperation?.resolvedFields.scheduleFields ?? {};
93-
const pendingSlots = requiredSlotsForOperation(operationKind).filter(
94-
(slot) =>
95-
priorScheduleFields[slot as keyof typeof priorScheduleFields] ===
93+
const pendingScheduleFields = requiredScheduleFieldsForOperation(
94+
operationKind,
95+
).filter(
96+
(fieldKey) =>
97+
priorScheduleFields[fieldKey as keyof typeof priorScheduleFields] ===
9698
undefined,
9799
);
98100

99-
slotExtraction = await extractSlots({
101+
fieldExtraction = await extractSlots({
100102
currentTurnText: input.normalizedText,
101-
pendingSlots,
103+
pendingSlots: pendingScheduleFields,
102104
priorResolvedSlots: priorScheduleFields,
103105
conversationContext: deriveConversationContext(input.recentTurns),
104106
});
@@ -107,9 +109,9 @@ export async function routeMessageTurn(
107109
// Policy layer: commit + route
108110
const commitResult = applyCommitPolicy({
109111
turnType: classification.turnType,
110-
extractedValues: slotExtraction?.extractedValues ?? {},
111-
confidence: compactConfidence(slotExtraction?.confidence ?? {}),
112-
unresolvable: slotExtraction?.unresolvable ?? [],
112+
extractedValues: fieldExtraction?.extractedValues ?? {},
113+
confidence: compactConfidence(fieldExtraction?.confidence ?? {}),
114+
unresolvable: fieldExtraction?.unresolvable ?? [],
113115
operationKind,
114116
priorPendingWriteOperation: priorOperation,
115117
...(classification.resolvedEntityIds[0] !== undefined

packages/core/src/commit-policy.test.ts

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ function buildInput(overrides: Partial<CommitPolicyInput>): CommitPolicyInput {
3535
}
3636

3737
describe("applyCommitPolicy", () => {
38-
it("does not commit slots for informational turns", () => {
38+
it("does not commit fields for informational turns", () => {
3939
const result = applyCommitPolicy(
4040
buildInput({
4141
turnType: "informational",
@@ -47,7 +47,7 @@ describe("applyCommitPolicy", () => {
4747
expect(result.resolvedFields.scheduleFields?.time).toBeUndefined();
4848
});
4949

50-
it("does not commit slots for confirmation turns", () => {
50+
it("does not commit fields for confirmation turns", () => {
5151
const result = applyCommitPolicy(
5252
buildInput({
5353
turnType: "confirmation",
@@ -59,7 +59,7 @@ describe("applyCommitPolicy", () => {
5959
expect(result.resolvedFields.scheduleFields?.time).toBeUndefined();
6060
});
6161

62-
it("commits slots above confidence threshold for planning_request", () => {
62+
it("commits fields above confidence threshold for planning_request", () => {
6363
const result = applyCommitPolicy(
6464
buildInput({
6565
turnType: "planning_request",
@@ -114,7 +114,7 @@ describe("applyCommitPolicy", () => {
114114
expect(result.needsClarification).not.toContain("scheduleFields.time");
115115
});
116116

117-
it("routes unresolvable slots to needsClarification", () => {
117+
it("routes unresolvable fields to needsClarification", () => {
118118
const result = applyCommitPolicy(
119119
buildInput({
120120
turnType: "clarification_answer",
@@ -127,7 +127,7 @@ describe("applyCommitPolicy", () => {
127127
expect(result.needsClarification).toContain("scheduleFields.time");
128128
});
129129

130-
it("resets prior slots on operation kind change", () => {
130+
it("resets prior fields on operation kind change", () => {
131131
const result = applyCommitPolicy(
132132
buildInput({
133133
turnType: "planning_request",
@@ -145,7 +145,7 @@ describe("applyCommitPolicy", () => {
145145
expect(result.resolvedFields.scheduleFields?.day).toBe("friday");
146146
});
147147

148-
it("does not reset slots when operation kind is unchanged", () => {
148+
it("does not reset fields when operation kind is unchanged", () => {
149149
const result = applyCommitPolicy(
150150
buildInput({
151151
turnType: "planning_request",
@@ -201,7 +201,7 @@ describe("applyCommitPolicy", () => {
201201
expect(result.resolvedFields.scheduleFields?.time).toEqual(t(17, 0));
202202
});
203203

204-
it("commits slots for edit_request turn type", () => {
204+
it("commits fields for edit_request turn type", () => {
205205
const result = applyCommitPolicy(
206206
buildInput({
207207
turnType: "edit_request",
@@ -228,7 +228,7 @@ describe("applyCommitPolicy", () => {
228228
expect(result.needsClarification).toContain("scheduleFields.time");
229229
});
230230

231-
it("does not flag unresolvable slot that is already resolved from prior turn", () => {
231+
it("does not flag an unresolvable field that is already resolved from prior turn", () => {
232232
const result = applyCommitPolicy(
233233
buildInput({
234234
turnType: "clarification_answer",
@@ -244,7 +244,7 @@ describe("applyCommitPolicy", () => {
244244
expect(result.resolvedFields.scheduleFields?.day).toBe("friday");
245245
});
246246

247-
it("handles unresolvable slots not in extractedValues", () => {
247+
it("handles unresolvable fields not in extractedValues", () => {
248248
const result = applyCommitPolicy(
249249
buildInput({
250250
turnType: "clarification_answer",

packages/core/src/commit-policy.ts

Lines changed: 39 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,13 @@ import type {
88
} from "./index";
99
import { timeSpecsEqual } from "./time-spec";
1010

11-
type ScheduleSlot = "day" | "time" | "duration";
11+
type ScheduleFieldKey = "day" | "time" | "duration";
1212

1313
export type CommitPolicyInput = {
1414
turnType: TurnInterpretation["turnType"];
15-
extractedValues: Partial<Record<ScheduleSlot, unknown>>;
16-
confidence: Partial<Record<ScheduleSlot, number>>;
17-
unresolvable: ScheduleSlot[];
15+
extractedValues: Partial<Record<ScheduleFieldKey, unknown>>;
16+
confidence: Partial<Record<ScheduleFieldKey, number>>;
17+
unresolvable: ScheduleFieldKey[];
1818
operationKind: OperationKind;
1919
priorPendingWriteOperation?: PendingWriteOperation | undefined;
2020
currentTargetEntityId?: string;
@@ -28,7 +28,7 @@ export type CommitPolicyOutput = {
2828
workflowChanged: boolean;
2929
};
3030

31-
export const SLOT_COMMITTING_TURN_TYPES = new Set<
31+
export const FIELD_COMMITTING_TURN_TYPES = new Set<
3232
TurnInterpretation["turnType"]
3333
>(["clarification_answer", "planning_request", "edit_request"]);
3434

@@ -39,7 +39,7 @@ const CORRECTION_THRESHOLD = 0.9;
3939
// Contract derivation lives here rather than as a pre-extraction gate.
4040
function requiredFieldsForOperation(
4141
operationKind: OperationKind,
42-
): ScheduleSlot[] {
42+
): ScheduleFieldKey[] {
4343
switch (operationKind) {
4444
case "plan":
4545
return ["day", "time"];
@@ -77,53 +77,54 @@ export function applyCommitPolicy(
7777
(operationKind !== priorPendingWriteOperation.operationKind ||
7878
targetChanged);
7979

80-
const priorScheduleFields: Partial<Record<ScheduleSlot, unknown>> =
80+
const priorScheduleFields: Partial<Record<ScheduleFieldKey, unknown>> =
8181
operationChanged
8282
? {}
8383
: {
8484
...(priorPendingWriteOperation?.resolvedFields.scheduleFields ?? {}),
8585
};
8686

8787
const needsClarification: string[] = [];
88-
const committedSchedule: Partial<Record<ScheduleSlot, unknown>> = {
88+
const committedScheduleFields: Partial<Record<ScheduleFieldKey, unknown>> = {
8989
...priorScheduleFields,
9090
};
9191

92-
if (SLOT_COMMITTING_TURN_TYPES.has(turnType)) {
93-
const slotKeys = Object.keys(extractedValues) as ScheduleSlot[];
92+
if (FIELD_COMMITTING_TURN_TYPES.has(turnType)) {
93+
const extractedFieldKeys = Object.keys(extractedValues) as ScheduleFieldKey[];
9494

95-
for (const slot of slotKeys) {
96-
const value = extractedValues[slot];
95+
for (const fieldKey of extractedFieldKeys) {
96+
const value = extractedValues[fieldKey];
9797
if (value === undefined) continue;
9898

99-
if (unresolvable.includes(slot)) {
100-
needsClarification.push(`scheduleFields.${slot}`);
99+
if (unresolvable.includes(fieldKey)) {
100+
needsClarification.push(`scheduleFields.${fieldKey}`);
101101
continue;
102102
}
103103

104-
const slotConfidence = confidence[slot] ?? 0;
105-
if (slotConfidence < CONFIDENCE_THRESHOLD) {
106-
needsClarification.push(`scheduleFields.${slot}`);
104+
const fieldConfidence = confidence[fieldKey] ?? 0;
105+
if (fieldConfidence < CONFIDENCE_THRESHOLD) {
106+
needsClarification.push(`scheduleFields.${fieldKey}`);
107107
continue;
108108
}
109109

110-
const priorValue = priorScheduleFields[slot];
110+
const priorValue = priorScheduleFields[fieldKey];
111111
const isCorrection =
112-
priorValue !== undefined && !slotValuesEqual(slot, priorValue, value);
113-
if (isCorrection && slotConfidence < CORRECTION_THRESHOLD) {
114-
needsClarification.push(`scheduleFields.${slot}`);
112+
priorValue !== undefined &&
113+
!scheduleFieldValuesEqual(fieldKey, priorValue, value);
114+
if (isCorrection && fieldConfidence < CORRECTION_THRESHOLD) {
115+
needsClarification.push(`scheduleFields.${fieldKey}`);
115116
continue;
116117
}
117118

118-
committedSchedule[slot] = value;
119+
committedScheduleFields[fieldKey] = value;
119120
}
120121

121-
for (const slot of unresolvable) {
122-
const dotPath = `scheduleFields.${slot}`;
122+
for (const fieldKey of unresolvable) {
123+
const dotPath = `scheduleFields.${fieldKey}`;
123124
if (
124-
!slotKeys.includes(slot) &&
125+
!extractedFieldKeys.includes(fieldKey) &&
125126
!needsClarification.includes(dotPath) &&
126-
committedSchedule[slot] === undefined
127+
committedScheduleFields[fieldKey] === undefined
127128
) {
128129
needsClarification.push(dotPath);
129130
}
@@ -132,15 +133,15 @@ export function applyCommitPolicy(
132133

133134
const resolvedFields: ResolvedFields = {
134135
scheduleFields:
135-
Object.keys(committedSchedule).length > 0
136-
? (committedSchedule as ResolvedFields["scheduleFields"])
136+
Object.keys(committedScheduleFields).length > 0
137+
? (committedScheduleFields as ResolvedFields["scheduleFields"])
137138
: undefined,
138139
};
139140

140-
const requiredSlots = requiredFieldsForOperation(operationKind);
141-
const missingFields = requiredSlots
142-
.filter((slot) => committedSchedule[slot] === undefined)
143-
.map((slot) => `scheduleFields.${slot}`);
141+
const requiredFieldKeys = requiredFieldsForOperation(operationKind);
142+
const missingFields = requiredFieldKeys
143+
.filter((fieldKey) => committedScheduleFields[fieldKey] === undefined)
144+
.map((fieldKey) => `scheduleFields.${fieldKey}`);
144145

145146
// Carry forward the prior target unless this turn introduced a new one.
146147
const resolvedTargetRef: TargetRef = currentTargetEntityId
@@ -156,8 +157,12 @@ export function applyCommitPolicy(
156157
};
157158
}
158159

159-
function slotValuesEqual(slot: ScheduleSlot, a: unknown, b: unknown): boolean {
160-
if (slot === "time" && a && b) {
160+
function scheduleFieldValuesEqual(
161+
fieldKey: ScheduleFieldKey,
162+
a: unknown,
163+
b: unknown,
164+
): boolean {
165+
if (fieldKey === "time" && a && b) {
161166
return timeSpecsEqual(a as TimeSpec, b as TimeSpec);
162167
}
163168
return a === b;

0 commit comments

Comments
 (0)