АРЧ АП11 - Архитектура после регресса: Архитектура: централизовать anchor и temporal carryover в continuity policy и протянуть их в transition hot path

This commit is contained in:
2026-04-18 21:08:01 +03:00
parent d29fbba214
commit cf17404925
10 changed files with 348 additions and 204 deletions
@@ -5,6 +5,8 @@ exports.readAddressDebugFilters = readAddressDebugFilters;
exports.readAddressDebugItem = readAddressDebugItem;
exports.readAddressDebugOrganization = readAddressDebugOrganization;
exports.readAddressDebugScopedDate = readAddressDebugScopedDate;
exports.readAddressDebugTemporalScope = readAddressDebugTemporalScope;
exports.resolveAddressDebugAnchorContext = resolveAddressDebugAnchorContext;
exports.resolveAddressDebugContextFacts = resolveAddressDebugContextFacts;
exports.buildInventoryRootFrameFromAddressDebug = buildInventoryRootFrameFromAddressDebug;
exports.isGroundedAddressDebug = isGroundedAddressDebug;
@@ -60,6 +62,58 @@ function readAddressDebugScopedDate(debug) {
formatIsoDateForReply(rootFrameContext?.as_of_date) ??
formatIsoDateForReply(extractedFilters?.period_to));
}
function readAddressDebugTemporalScope(debug, toNonEmptyString = fallbackToNonEmptyString) {
const extractedFilters = readAddressDebugFilters(debug);
const rootFrameContext = toRecordObject(debug?.address_root_frame_context);
return {
asOfDate: toNonEmptyString(extractedFilters?.as_of_date) ?? toNonEmptyString(rootFrameContext?.as_of_date),
periodFrom: toNonEmptyString(extractedFilters?.period_from) ?? toNonEmptyString(rootFrameContext?.period_from),
periodTo: toNonEmptyString(extractedFilters?.period_to) ?? toNonEmptyString(rootFrameContext?.period_to)
};
}
function resolveAddressDebugAnchorContext(debug, toNonEmptyString = fallbackToNonEmptyString) {
const explicitAnchorType = toNonEmptyString(debug?.anchor_type);
const explicitAnchorValue = toNonEmptyString(debug?.anchor_value_resolved) ?? toNonEmptyString(debug?.anchor_value_raw);
if (explicitAnchorType || explicitAnchorValue) {
return {
anchorType: explicitAnchorType,
anchorValue: explicitAnchorValue
};
}
const extractedFilters = readAddressDebugFilters(debug);
const item = toNonEmptyString(extractedFilters?.item);
if (item) {
return {
anchorType: "item",
anchorValue: item
};
}
const counterparty = toNonEmptyString(extractedFilters?.counterparty);
if (counterparty) {
return {
anchorType: "counterparty",
anchorValue: counterparty
};
}
const account = toNonEmptyString(extractedFilters?.account);
if (account) {
return {
anchorType: "account",
anchorValue: account
};
}
const contract = toNonEmptyString(extractedFilters?.contract);
if (contract) {
return {
anchorType: "contract",
anchorValue: contract
};
}
return {
anchorType: null,
anchorValue: null
};
}
function resolveAddressDebugContextFacts(debug, toNonEmptyString = fallbackToNonEmptyString) {
return {
item: readAddressDebugItem(debug, toNonEmptyString),
@@ -3,69 +3,6 @@ Object.defineProperty(exports, "__esModule", { value: true });
exports.runAssistantLivingChatRuntime = runAssistantLivingChatRuntime;
const assistantMemoryRecapPolicy_1 = require("./assistantMemoryRecapPolicy");
const assistantContinuityPolicy_1 = require("./assistantContinuityPolicy");
function formatIsoDateForReply(value) {
const source = String(value ?? "").trim();
const match = source.match(/^(\d{4})-(\d{2})-(\d{2})$/);
if (!match) {
return null;
}
return `${match[3]}.${match[2]}.${match[1]}`;
}
function findLastGroundedInventoryAddressDebug(items) {
if (!Array.isArray(items)) {
return null;
}
for (let index = items.length - 1; index >= 0; index -= 1) {
const item = items[index];
if (!item || item.role !== "assistant" || !item.debug || typeof item.debug !== "object") {
continue;
}
const debug = item.debug;
const answerGroundingCheck = debug.answer_grounding_check && typeof debug.answer_grounding_check === "object"
? debug.answer_grounding_check
: null;
const groundingStatus = String(answerGroundingCheck?.status ?? "");
const detectedIntent = String(debug.detected_intent ?? "");
const capabilityId = String(debug.capability_id ?? "");
const rootFrameContext = debug.address_root_frame_context && typeof debug.address_root_frame_context === "object"
? debug.address_root_frame_context
: null;
const rootIntent = String(rootFrameContext?.root_intent ?? "");
const isInventoryContext = detectedIntent === "inventory_on_hand_as_of_date" ||
capabilityId === "confirmed_inventory_on_hand_as_of_date" ||
rootIntent === "inventory_on_hand_as_of_date";
if (groundingStatus === "grounded" && isInventoryContext) {
return debug;
}
}
return null;
}
function findLastAddressDebugWithItem(items) {
if (!Array.isArray(items)) {
return null;
}
for (let index = items.length - 1; index >= 0; index -= 1) {
const item = items[index];
if (!item || item.role !== "assistant" || !item.debug || typeof item.debug !== "object") {
continue;
}
const debug = item.debug;
if (String(debug.execution_lane ?? "") !== "address_query") {
continue;
}
const extractedFilters = debug.extracted_filters && typeof debug.extracted_filters === "object"
? debug.extracted_filters
: null;
const itemLabel = String(extractedFilters?.item ?? "").trim() ||
(String(debug.anchor_type ?? "") === "item"
? String(debug.anchor_value_resolved ?? debug.anchor_value_raw ?? "").trim()
: "");
if (itemLabel) {
return debug;
}
}
return null;
}
function hasPriorAssistantTurn(items) {
if (!Array.isArray(items)) {
return false;
@@ -75,21 +12,6 @@ function hasPriorAssistantTurn(items) {
function buildDeterministicSmalltalkLeadReply() {
return "\u041f\u0440\u0438\u0432\u0435\u0442! \u0412\u0441\u0451 \u043d\u043e\u0440\u043c\u0430\u043b\u044c\u043d\u043e.";
}
function findLastAddressDebug(items) {
if (!Array.isArray(items)) {
return null;
}
for (let index = items.length - 1; index >= 0; index -= 1) {
const item = items[index];
if (!item || item.role !== "assistant" || !item.debug || typeof item.debug !== "object") {
continue;
}
if (String(item.debug.execution_lane ?? "") === "address_query") {
return item.debug;
}
}
return null;
}
function buildInventoryHistoryCapabilityFollowupReply(input) {
const rootFrameContext = input.addressDebug?.address_root_frame_context && typeof input.addressDebug.address_root_frame_context === "object"
? input.addressDebug.address_root_frame_context
@@ -100,8 +22,8 @@ function buildInventoryHistoryCapabilityFollowupReply(input) {
const organization = input.organization ??
input.toNonEmptyString(rootFrameContext?.organization) ??
input.toNonEmptyString(extractedFilters?.organization);
const lastAsOfDate = formatIsoDateForReply(rootFrameContext?.as_of_date) ??
formatIsoDateForReply(extractedFilters?.as_of_date);
const lastAsOfDate = (0, assistantContinuityPolicy_1.formatIsoDateForReply)(rootFrameContext?.as_of_date) ??
(0, assistantContinuityPolicy_1.formatIsoDateForReply)(extractedFilters?.as_of_date);
const organizationPart = organization ? ` по компании «${organization}»` : "";
const referenceLine = lastAsOfDate
? `Да, могу. Сейчас мы уже смотрели складской срез${organizationPart} на ${lastAsOfDate}.`
@@ -132,9 +54,9 @@ function buildAddressMemoryRecapReply(input) {
const organization = input.organization ??
input.toNonEmptyString(extractedFilters?.organization) ??
input.toNonEmptyString(rootFrameContext?.organization);
const scopedDate = formatIsoDateForReply(extractedFilters?.as_of_date) ??
formatIsoDateForReply(rootFrameContext?.as_of_date) ??
formatIsoDateForReply(extractedFilters?.period_to);
const scopedDate = (0, assistantContinuityPolicy_1.formatIsoDateForReply)(extractedFilters?.as_of_date) ??
(0, assistantContinuityPolicy_1.formatIsoDateForReply)(rootFrameContext?.as_of_date) ??
(0, assistantContinuityPolicy_1.formatIsoDateForReply)(extractedFilters?.period_to);
if (item) {
const datePart = scopedDate ? ` в срезе на ${scopedDate}` : "";
const organizationPart = organization ? ` по компании «${organization}»` : "";
@@ -340,6 +340,7 @@ function createAssistantTransitionPolicy(deps) {
mergeKnownOrganizations: deps.mergeKnownOrganizations
});
const continuitySnapshot = organizationAuthority.continuitySnapshot;
const continuityTemporalScope = (0, assistantContinuityPolicy_1.readAddressDebugTemporalScope)(continuitySnapshot.lastGroundedAddressDebug, deps.toNonEmptyString);
const organizationClarificationCandidates = Array.isArray(organizationAuthority.organizationClarificationCandidates)
? organizationAuthority.organizationClarificationCandidates
: [];
@@ -522,13 +523,9 @@ function createAssistantTransitionPolicy(deps) {
followupSelectionMode = "switch_to_suggested_intent";
}
}
let previousAnchorType = deps.toNonEmptyString(previousAddressDebug.anchor_type);
let previousAnchor = deps.toNonEmptyString(previousAddressDebug.anchor_value_resolved) ??
deps.toNonEmptyString(previousAddressDebug.anchor_value_raw) ??
deps.readAddressFilterString(previousAddressDebug, "item") ??
deps.readAddressFilterString(previousAddressDebug, "counterparty") ??
deps.readAddressFilterString(previousAddressDebug, "account") ??
deps.readAddressFilterString(previousAddressDebug, "contract");
const previousAnchorContext = (0, assistantContinuityPolicy_1.resolveAddressDebugAnchorContext)(previousAddressDebug, deps.toNonEmptyString);
let previousAnchorType = previousAnchorContext.anchorType;
let previousAnchor = previousAnchorContext.anchorValue;
const navigationSessionContext = addressNavigationState && typeof addressNavigationState === "object"
? addressNavigationState.session_context && typeof addressNavigationState.session_context === "object"
? addressNavigationState.session_context
@@ -699,8 +696,8 @@ function createAssistantTransitionPolicy(deps) {
}
if (shouldBackfillPreviousDateScopeFromNavigation &&
!deps.toNonEmptyString(previousFilters.as_of_date) &&
(0, assistantContinuityPolicy_1.readAddressDebugFilters)(continuitySnapshot.lastGroundedAddressDebug)?.as_of_date) {
previousFilters.as_of_date = deps.toNonEmptyString((0, assistantContinuityPolicy_1.readAddressDebugFilters)(continuitySnapshot.lastGroundedAddressDebug)?.as_of_date);
continuityTemporalScope.asOfDate) {
previousFilters.as_of_date = continuityTemporalScope.asOfDate;
}
if (shouldBackfillPreviousDateScopeFromNavigation &&
!deps.toNonEmptyString(previousFilters.period_from) &&
@@ -709,8 +706,8 @@ function createAssistantTransitionPolicy(deps) {
}
if (shouldBackfillPreviousDateScopeFromNavigation &&
!deps.toNonEmptyString(previousFilters.period_from) &&
(0, assistantContinuityPolicy_1.readAddressDebugFilters)(continuitySnapshot.lastGroundedAddressDebug)?.period_from) {
previousFilters.period_from = deps.toNonEmptyString((0, assistantContinuityPolicy_1.readAddressDebugFilters)(continuitySnapshot.lastGroundedAddressDebug)?.period_from);
continuityTemporalScope.periodFrom) {
previousFilters.period_from = continuityTemporalScope.periodFrom;
}
if (shouldBackfillPreviousDateScopeFromNavigation &&
!deps.toNonEmptyString(previousFilters.period_to) &&
@@ -719,8 +716,8 @@ function createAssistantTransitionPolicy(deps) {
}
if (shouldBackfillPreviousDateScopeFromNavigation &&
!deps.toNonEmptyString(previousFilters.period_to) &&
(0, assistantContinuityPolicy_1.readAddressDebugFilters)(continuitySnapshot.lastGroundedAddressDebug)?.period_to) {
previousFilters.period_to = deps.toNonEmptyString((0, assistantContinuityPolicy_1.readAddressDebugFilters)(continuitySnapshot.lastGroundedAddressDebug)?.period_to);
continuityTemporalScope.periodTo) {
previousFilters.period_to = continuityTemporalScope.periodTo;
}
const rootContextOnlyPivot = Boolean((deps.isInventorySelectedObjectIntent(sourceIntentHint) || currentFrameKind === "inventory_drilldown") &&
deps.hasForeignAccountingPivotOverInventoryMessage(userMessage, alternateMessage) &&
@@ -33,6 +33,17 @@ export interface AssistantAddressDebugContextFacts {
scopedDate: string | null;
}
export interface AssistantAddressDebugTemporalScope {
asOfDate: string | null;
periodFrom: string | null;
periodTo: string | null;
}
export interface AssistantAddressDebugAnchorContext {
anchorType: string | null;
anchorValue: string | null;
}
export interface AssistantOrganizationAuthorityInput {
sessionItems?: unknown[];
sessionKnownOrganizations?: unknown[];
@@ -124,6 +135,68 @@ export function readAddressDebugScopedDate(debug: Record<string, unknown> | null
);
}
export function readAddressDebugTemporalScope(
debug: Record<string, unknown> | null,
toNonEmptyString: (value: unknown) => string | null = fallbackToNonEmptyString
): AssistantAddressDebugTemporalScope {
const extractedFilters = readAddressDebugFilters(debug);
const rootFrameContext = toRecordObject(debug?.address_root_frame_context);
return {
asOfDate: toNonEmptyString(extractedFilters?.as_of_date) ?? toNonEmptyString(rootFrameContext?.as_of_date),
periodFrom: toNonEmptyString(extractedFilters?.period_from) ?? toNonEmptyString(rootFrameContext?.period_from),
periodTo: toNonEmptyString(extractedFilters?.period_to) ?? toNonEmptyString(rootFrameContext?.period_to)
};
}
export function resolveAddressDebugAnchorContext(
debug: Record<string, unknown> | null,
toNonEmptyString: (value: unknown) => string | null = fallbackToNonEmptyString
): AssistantAddressDebugAnchorContext {
const explicitAnchorType = toNonEmptyString(debug?.anchor_type);
const explicitAnchorValue =
toNonEmptyString(debug?.anchor_value_resolved) ?? toNonEmptyString(debug?.anchor_value_raw);
if (explicitAnchorType || explicitAnchorValue) {
return {
anchorType: explicitAnchorType,
anchorValue: explicitAnchorValue
};
}
const extractedFilters = readAddressDebugFilters(debug);
const item = toNonEmptyString(extractedFilters?.item);
if (item) {
return {
anchorType: "item",
anchorValue: item
};
}
const counterparty = toNonEmptyString(extractedFilters?.counterparty);
if (counterparty) {
return {
anchorType: "counterparty",
anchorValue: counterparty
};
}
const account = toNonEmptyString(extractedFilters?.account);
if (account) {
return {
anchorType: "account",
anchorValue: account
};
}
const contract = toNonEmptyString(extractedFilters?.contract);
if (contract) {
return {
anchorType: "contract",
anchorValue: contract
};
}
return {
anchorType: null,
anchorValue: null
};
}
export function resolveAddressDebugContextFacts(
debug: Record<string, unknown> | null,
toNonEmptyString: (value: unknown) => string | null = fallbackToNonEmptyString
@@ -3,7 +3,7 @@ import {
buildInventoryHistoryCapabilityFollowupReply as buildInventoryHistoryCapabilityFollowupReplyFromPolicy,
resolveAssistantLivingChatMemoryContext
} from "./assistantMemoryRecapPolicy";
import { resolveAssistantOrganizationAuthority } from "./assistantContinuityPolicy";
import { formatIsoDateForReply, resolveAssistantOrganizationAuthority } from "./assistantContinuityPolicy";
export interface AssistantLivingChatSessionScopeInput {
knownOrganizations?: unknown[];
@@ -66,77 +66,6 @@ export interface AssistantLivingChatRuntimeOutput {
debug: Record<string, unknown> | null;
}
function formatIsoDateForReply(value: unknown): string | null {
const source = String(value ?? "").trim();
const match = source.match(/^(\d{4})-(\d{2})-(\d{2})$/);
if (!match) {
return null;
}
return `${match[3]}.${match[2]}.${match[1]}`;
}
function findLastGroundedInventoryAddressDebug(items: unknown[]): Record<string, unknown> | null {
if (!Array.isArray(items)) {
return null;
}
for (let index = items.length - 1; index >= 0; index -= 1) {
const item = items[index] as { role?: string; debug?: Record<string, unknown> } | null;
if (!item || item.role !== "assistant" || !item.debug || typeof item.debug !== "object") {
continue;
}
const debug = item.debug;
const answerGroundingCheck =
debug.answer_grounding_check && typeof debug.answer_grounding_check === "object"
? (debug.answer_grounding_check as Record<string, unknown>)
: null;
const groundingStatus = String(answerGroundingCheck?.status ?? "");
const detectedIntent = String(debug.detected_intent ?? "");
const capabilityId = String(debug.capability_id ?? "");
const rootFrameContext =
debug.address_root_frame_context && typeof debug.address_root_frame_context === "object"
? (debug.address_root_frame_context as Record<string, unknown>)
: null;
const rootIntent = String(rootFrameContext?.root_intent ?? "");
const isInventoryContext =
detectedIntent === "inventory_on_hand_as_of_date" ||
capabilityId === "confirmed_inventory_on_hand_as_of_date" ||
rootIntent === "inventory_on_hand_as_of_date";
if (groundingStatus === "grounded" && isInventoryContext) {
return debug;
}
}
return null;
}
function findLastAddressDebugWithItem(items: unknown[]): Record<string, unknown> | null {
if (!Array.isArray(items)) {
return null;
}
for (let index = items.length - 1; index >= 0; index -= 1) {
const item = items[index] as { role?: string; debug?: Record<string, unknown> } | null;
if (!item || item.role !== "assistant" || !item.debug || typeof item.debug !== "object") {
continue;
}
const debug = item.debug;
if (String(debug.execution_lane ?? "") !== "address_query") {
continue;
}
const extractedFilters =
debug.extracted_filters && typeof debug.extracted_filters === "object"
? (debug.extracted_filters as Record<string, unknown>)
: null;
const itemLabel =
String(extractedFilters?.item ?? "").trim() ||
(String(debug.anchor_type ?? "") === "item"
? String(debug.anchor_value_resolved ?? debug.anchor_value_raw ?? "").trim()
: "");
if (itemLabel) {
return debug;
}
}
return null;
}
function hasPriorAssistantTurn(items: unknown[]): boolean {
if (!Array.isArray(items)) {
return false;
@@ -148,22 +77,6 @@ function buildDeterministicSmalltalkLeadReply(): string {
return "\u041f\u0440\u0438\u0432\u0435\u0442! \u0412\u0441\u0451 \u043d\u043e\u0440\u043c\u0430\u043b\u044c\u043d\u043e.";
}
function findLastAddressDebug(items: unknown[]): Record<string, unknown> | null {
if (!Array.isArray(items)) {
return null;
}
for (let index = items.length - 1; index >= 0; index -= 1) {
const item = items[index] as { role?: string; debug?: Record<string, unknown> } | null;
if (!item || item.role !== "assistant" || !item.debug || typeof item.debug !== "object") {
continue;
}
if (String(item.debug.execution_lane ?? "") === "address_query") {
return item.debug;
}
}
return null;
}
function buildInventoryHistoryCapabilityFollowupReply(input: {
organization: string | null;
addressDebug: Record<string, unknown> | null;
@@ -3,6 +3,8 @@ import {
buildInventoryRootFrameFromAddressDebug,
readAddressDebugFilters,
readAddressDebugItem,
readAddressDebugTemporalScope,
resolveAddressDebugAnchorContext,
resolveAssistantOrganizationAuthority
} from "./assistantContinuityPolicy";
@@ -422,6 +424,10 @@ export function createAssistantTransitionPolicy(deps) {
mergeKnownOrganizations: deps.mergeKnownOrganizations
});
const continuitySnapshot = organizationAuthority.continuitySnapshot;
const continuityTemporalScope = readAddressDebugTemporalScope(
continuitySnapshot.lastGroundedAddressDebug,
deps.toNonEmptyString
);
const organizationClarificationCandidates = Array.isArray(organizationAuthority.organizationClarificationCandidates)
? organizationAuthority.organizationClarificationCandidates
: [];
@@ -652,14 +658,9 @@ export function createAssistantTransitionPolicy(deps) {
followupSelectionMode = "switch_to_suggested_intent";
}
}
let previousAnchorType = deps.toNonEmptyString(previousAddressDebug.anchor_type);
let previousAnchor =
deps.toNonEmptyString(previousAddressDebug.anchor_value_resolved) ??
deps.toNonEmptyString(previousAddressDebug.anchor_value_raw) ??
deps.readAddressFilterString(previousAddressDebug, "item") ??
deps.readAddressFilterString(previousAddressDebug, "counterparty") ??
deps.readAddressFilterString(previousAddressDebug, "account") ??
deps.readAddressFilterString(previousAddressDebug, "contract");
const previousAnchorContext = resolveAddressDebugAnchorContext(previousAddressDebug, deps.toNonEmptyString);
let previousAnchorType = previousAnchorContext.anchorType;
let previousAnchor = previousAnchorContext.anchorValue;
const navigationSessionContext =
addressNavigationState && typeof addressNavigationState === "object"
? addressNavigationState.session_context && typeof addressNavigationState.session_context === "object"
@@ -851,11 +852,9 @@ export function createAssistantTransitionPolicy(deps) {
if (
shouldBackfillPreviousDateScopeFromNavigation &&
!deps.toNonEmptyString(previousFilters.as_of_date) &&
readAddressDebugFilters(continuitySnapshot.lastGroundedAddressDebug)?.as_of_date
continuityTemporalScope.asOfDate
) {
previousFilters.as_of_date = deps.toNonEmptyString(
readAddressDebugFilters(continuitySnapshot.lastGroundedAddressDebug)?.as_of_date
);
previousFilters.as_of_date = continuityTemporalScope.asOfDate;
}
if (
shouldBackfillPreviousDateScopeFromNavigation &&
@@ -867,11 +866,9 @@ export function createAssistantTransitionPolicy(deps) {
if (
shouldBackfillPreviousDateScopeFromNavigation &&
!deps.toNonEmptyString(previousFilters.period_from) &&
readAddressDebugFilters(continuitySnapshot.lastGroundedAddressDebug)?.period_from
continuityTemporalScope.periodFrom
) {
previousFilters.period_from = deps.toNonEmptyString(
readAddressDebugFilters(continuitySnapshot.lastGroundedAddressDebug)?.period_from
);
previousFilters.period_from = continuityTemporalScope.periodFrom;
}
if (
shouldBackfillPreviousDateScopeFromNavigation &&
@@ -883,11 +880,9 @@ export function createAssistantTransitionPolicy(deps) {
if (
shouldBackfillPreviousDateScopeFromNavigation &&
!deps.toNonEmptyString(previousFilters.period_to) &&
readAddressDebugFilters(continuitySnapshot.lastGroundedAddressDebug)?.period_to
continuityTemporalScope.periodTo
) {
previousFilters.period_to = deps.toNonEmptyString(
readAddressDebugFilters(continuitySnapshot.lastGroundedAddressDebug)?.period_to
);
previousFilters.period_to = continuityTemporalScope.periodTo;
}
const rootContextOnlyPivot = Boolean(
(deps.isInventorySelectedObjectIntent(sourceIntentHint) || currentFrameKind === "inventory_drilldown") &&
@@ -1,6 +1,8 @@
import { describe, expect, it } from "vitest";
import {
readAddressDebugTemporalScope,
resolveAddressDebugContextFacts,
resolveAddressDebugAnchorContext,
resolveAssistantOrganizationAuthority
} from "../src/services/assistantContinuityPolicy";
@@ -75,4 +77,27 @@ describe("assistantContinuityPolicy organization authority", () => {
scopedDate: "31.03.2020"
});
});
it("resolves carryover temporal scope and inferred anchor from debug filters when explicit anchor fields are absent", () => {
const debug = {
extracted_filters: {
item: "Рабочая станция",
period_from: "2020-03-01"
},
address_root_frame_context: {
as_of_date: "2020-03-31",
period_to: "2020-03-31"
}
};
expect(readAddressDebugTemporalScope(debug)).toEqual({
asOfDate: "2020-03-31",
periodFrom: "2020-03-01",
periodTo: "2020-03-31"
});
expect(resolveAddressDebugAnchorContext(debug)).toEqual({
anchorType: "item",
anchorValue: "Рабочая станция"
});
});
});
@@ -304,6 +304,41 @@ describe("assistantTransitionPolicy", () => {
expect(carryover?.followupContext?.target_intent).toBe("inventory_on_hand_as_of_date");
});
it("hydrates carryover anchor from shared debug helpers when explicit anchor fields are absent", () => {
const policy = buildPolicy({
findLastAddressAssistantItem: () => ({
text: "Подтвержденный складской срез собран.",
debug: {
detected_intent: "inventory_on_hand_as_of_date",
extracted_filters: {
item: "Рабочая станция",
period_from: "2020-03-01"
},
address_root_frame_context: {
as_of_date: "2020-03-31",
period_to: "2020-03-31"
},
anchor_type: null,
anchor_value_resolved: null
}
}),
hasAddressFollowupContextSignal: () => true,
hasReferentialPointer: () => true,
findRecentInventoryRootFrame: () => null,
findRecentAddressFilterValue: () => null,
resolveAddressIntent: () => ({ intent: "unknown" })
});
const carryover = policy.resolveAddressFollowupCarryoverContext("по этой позиции", [], null, null, null);
expect(carryover?.followupContext?.previous_anchor_type).toBe("item");
expect(carryover?.followupContext?.previous_anchor_value).toBe("Рабочая станция");
expect(carryover?.followupContext?.previous_filters).toMatchObject({
item: "Рабочая станция",
period_from: "2020-03-01"
});
});
it("bridges selected-item purchase provenance into a VAT period follow-up", () => {
const item = "Рабочая станция универсального специалиста";
const policy = buildPolicy({