АРЧ АП11 - Архитектура после регресса: Архитектура: централизовать anchor и temporal carryover в continuity policy и протянуть их в transition hot path
This commit is contained in:
parent
d29fbba214
commit
cf17404925
|
|
@ -341,6 +341,16 @@ Still open after the accepted phase12 replay:
|
||||||
- this matters because deterministic memory-recap and historical-inventory capability replies now depend on the same context interpretation as the rest of continuity policy, rather than on a separate local parser that could drift on root-frame-only turns;
|
- this matters because deterministic memory-recap and historical-inventory capability replies now depend on the same context interpretation as the rest of continuity policy, rather than on a separate local parser that could drift on root-frame-only turns;
|
||||||
- targeted continuity / memory-recap / living-chat tests now protect the root-frame fallback path explicitly;
|
- targeted continuity / memory-recap / living-chat tests now protect the root-frame fallback path explicitly;
|
||||||
- wide saved-session replay `address_truth_harness_phase12_wider_saved_session_pool_live_20260418_rerun6` remains accepted `20/20`, which is the critical proof that this context-helper convergence did not reopen the broader living-chat continuity path.
|
- wide saved-session replay `address_truth_harness_phase12_wider_saved_session_pool_live_20260418_rerun6` remains accepted `20/20`, which is the critical proof that this context-helper convergence did not reopen the broader living-chat continuity path.
|
||||||
|
- the next cleanup pass also removes one more class of false owners from the living-chat adapter itself:
|
||||||
|
- `assistantLivingChatRuntimeAdapter` no longer keeps local dead history scanners for grounded inventory / selected-object / generic address debug lookup that are not part of the active execution path anymore;
|
||||||
|
- this does not change runtime behavior directly, but it reduces the chance that future fixes accidentally revive or patch a stale local owner instead of the shared continuity / memory-recap policy seam;
|
||||||
|
- targeted living-chat adapter tests and backend build remain green after the cleanup, which is the necessary proof that this was a structural owner-reduction pass rather than a hidden behavior change.
|
||||||
|
- the next continuity-authority pass now removes one more local `addressDebug -> carryover anchor/date` parser from the transition hot path:
|
||||||
|
- `assistantContinuityPolicy` now exposes shared helpers for `anchorType/anchorValue` resolution and raw temporal carryover scope (`as_of_date / period_from / period_to`) from grounded `addressDebug`, including root-frame fallback;
|
||||||
|
- `assistantTransitionPolicy` now consumes these helpers instead of rebuilding previous anchor selection from raw `anchor_value_*` and filter fields inline, and instead of reading carryover dates directly from `readAddressDebugFilters(...)` in multiple ad hoc places;
|
||||||
|
- this matters because follow-up carryover is now closer to the same continuity interpretation layer that already owns item / organization / scoped-date facts, rather than keeping a separate transition-local parser for the same runtime evidence;
|
||||||
|
- targeted continuity and transition regressions now protect inferred anchor carryover when explicit `anchor_type` is absent, plus root-frame temporal fallback at the helper layer;
|
||||||
|
- wide saved-session replay `address_truth_harness_phase12_wider_saved_session_pool_live_20260418_rerun7` remains accepted `20/20`, which is the critical proof that this transition-layer convergence did not reopen the broader saved-session path.
|
||||||
|
|
||||||
## Next Execution Slice (2026-04-18)
|
## Next Execution Slice (2026-04-18)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,8 @@ exports.readAddressDebugFilters = readAddressDebugFilters;
|
||||||
exports.readAddressDebugItem = readAddressDebugItem;
|
exports.readAddressDebugItem = readAddressDebugItem;
|
||||||
exports.readAddressDebugOrganization = readAddressDebugOrganization;
|
exports.readAddressDebugOrganization = readAddressDebugOrganization;
|
||||||
exports.readAddressDebugScopedDate = readAddressDebugScopedDate;
|
exports.readAddressDebugScopedDate = readAddressDebugScopedDate;
|
||||||
|
exports.readAddressDebugTemporalScope = readAddressDebugTemporalScope;
|
||||||
|
exports.resolveAddressDebugAnchorContext = resolveAddressDebugAnchorContext;
|
||||||
exports.resolveAddressDebugContextFacts = resolveAddressDebugContextFacts;
|
exports.resolveAddressDebugContextFacts = resolveAddressDebugContextFacts;
|
||||||
exports.buildInventoryRootFrameFromAddressDebug = buildInventoryRootFrameFromAddressDebug;
|
exports.buildInventoryRootFrameFromAddressDebug = buildInventoryRootFrameFromAddressDebug;
|
||||||
exports.isGroundedAddressDebug = isGroundedAddressDebug;
|
exports.isGroundedAddressDebug = isGroundedAddressDebug;
|
||||||
|
|
@ -60,6 +62,58 @@ function readAddressDebugScopedDate(debug) {
|
||||||
formatIsoDateForReply(rootFrameContext?.as_of_date) ??
|
formatIsoDateForReply(rootFrameContext?.as_of_date) ??
|
||||||
formatIsoDateForReply(extractedFilters?.period_to));
|
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) {
|
function resolveAddressDebugContextFacts(debug, toNonEmptyString = fallbackToNonEmptyString) {
|
||||||
return {
|
return {
|
||||||
item: readAddressDebugItem(debug, toNonEmptyString),
|
item: readAddressDebugItem(debug, toNonEmptyString),
|
||||||
|
|
|
||||||
|
|
@ -3,69 +3,6 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
||||||
exports.runAssistantLivingChatRuntime = runAssistantLivingChatRuntime;
|
exports.runAssistantLivingChatRuntime = runAssistantLivingChatRuntime;
|
||||||
const assistantMemoryRecapPolicy_1 = require("./assistantMemoryRecapPolicy");
|
const assistantMemoryRecapPolicy_1 = require("./assistantMemoryRecapPolicy");
|
||||||
const assistantContinuityPolicy_1 = require("./assistantContinuityPolicy");
|
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) {
|
function hasPriorAssistantTurn(items) {
|
||||||
if (!Array.isArray(items)) {
|
if (!Array.isArray(items)) {
|
||||||
return false;
|
return false;
|
||||||
|
|
@ -75,21 +12,6 @@ function hasPriorAssistantTurn(items) {
|
||||||
function buildDeterministicSmalltalkLeadReply() {
|
function buildDeterministicSmalltalkLeadReply() {
|
||||||
return "\u041f\u0440\u0438\u0432\u0435\u0442! \u0412\u0441\u0451 \u043d\u043e\u0440\u043c\u0430\u043b\u044c\u043d\u043e.";
|
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) {
|
function buildInventoryHistoryCapabilityFollowupReply(input) {
|
||||||
const rootFrameContext = input.addressDebug?.address_root_frame_context && typeof input.addressDebug.address_root_frame_context === "object"
|
const rootFrameContext = input.addressDebug?.address_root_frame_context && typeof input.addressDebug.address_root_frame_context === "object"
|
||||||
? input.addressDebug.address_root_frame_context
|
? input.addressDebug.address_root_frame_context
|
||||||
|
|
@ -100,8 +22,8 @@ function buildInventoryHistoryCapabilityFollowupReply(input) {
|
||||||
const organization = input.organization ??
|
const organization = input.organization ??
|
||||||
input.toNonEmptyString(rootFrameContext?.organization) ??
|
input.toNonEmptyString(rootFrameContext?.organization) ??
|
||||||
input.toNonEmptyString(extractedFilters?.organization);
|
input.toNonEmptyString(extractedFilters?.organization);
|
||||||
const lastAsOfDate = formatIsoDateForReply(rootFrameContext?.as_of_date) ??
|
const lastAsOfDate = (0, assistantContinuityPolicy_1.formatIsoDateForReply)(rootFrameContext?.as_of_date) ??
|
||||||
formatIsoDateForReply(extractedFilters?.as_of_date);
|
(0, assistantContinuityPolicy_1.formatIsoDateForReply)(extractedFilters?.as_of_date);
|
||||||
const organizationPart = organization ? ` по компании «${organization}»` : "";
|
const organizationPart = organization ? ` по компании «${organization}»` : "";
|
||||||
const referenceLine = lastAsOfDate
|
const referenceLine = lastAsOfDate
|
||||||
? `Да, могу. Сейчас мы уже смотрели складской срез${organizationPart} на ${lastAsOfDate}.`
|
? `Да, могу. Сейчас мы уже смотрели складской срез${organizationPart} на ${lastAsOfDate}.`
|
||||||
|
|
@ -132,9 +54,9 @@ function buildAddressMemoryRecapReply(input) {
|
||||||
const organization = input.organization ??
|
const organization = input.organization ??
|
||||||
input.toNonEmptyString(extractedFilters?.organization) ??
|
input.toNonEmptyString(extractedFilters?.organization) ??
|
||||||
input.toNonEmptyString(rootFrameContext?.organization);
|
input.toNonEmptyString(rootFrameContext?.organization);
|
||||||
const scopedDate = formatIsoDateForReply(extractedFilters?.as_of_date) ??
|
const scopedDate = (0, assistantContinuityPolicy_1.formatIsoDateForReply)(extractedFilters?.as_of_date) ??
|
||||||
formatIsoDateForReply(rootFrameContext?.as_of_date) ??
|
(0, assistantContinuityPolicy_1.formatIsoDateForReply)(rootFrameContext?.as_of_date) ??
|
||||||
formatIsoDateForReply(extractedFilters?.period_to);
|
(0, assistantContinuityPolicy_1.formatIsoDateForReply)(extractedFilters?.period_to);
|
||||||
if (item) {
|
if (item) {
|
||||||
const datePart = scopedDate ? ` в срезе на ${scopedDate}` : "";
|
const datePart = scopedDate ? ` в срезе на ${scopedDate}` : "";
|
||||||
const organizationPart = organization ? ` по компании «${organization}»` : "";
|
const organizationPart = organization ? ` по компании «${organization}»` : "";
|
||||||
|
|
|
||||||
|
|
@ -340,6 +340,7 @@ function createAssistantTransitionPolicy(deps) {
|
||||||
mergeKnownOrganizations: deps.mergeKnownOrganizations
|
mergeKnownOrganizations: deps.mergeKnownOrganizations
|
||||||
});
|
});
|
||||||
const continuitySnapshot = organizationAuthority.continuitySnapshot;
|
const continuitySnapshot = organizationAuthority.continuitySnapshot;
|
||||||
|
const continuityTemporalScope = (0, assistantContinuityPolicy_1.readAddressDebugTemporalScope)(continuitySnapshot.lastGroundedAddressDebug, deps.toNonEmptyString);
|
||||||
const organizationClarificationCandidates = Array.isArray(organizationAuthority.organizationClarificationCandidates)
|
const organizationClarificationCandidates = Array.isArray(organizationAuthority.organizationClarificationCandidates)
|
||||||
? organizationAuthority.organizationClarificationCandidates
|
? organizationAuthority.organizationClarificationCandidates
|
||||||
: [];
|
: [];
|
||||||
|
|
@ -522,13 +523,9 @@ function createAssistantTransitionPolicy(deps) {
|
||||||
followupSelectionMode = "switch_to_suggested_intent";
|
followupSelectionMode = "switch_to_suggested_intent";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let previousAnchorType = deps.toNonEmptyString(previousAddressDebug.anchor_type);
|
const previousAnchorContext = (0, assistantContinuityPolicy_1.resolveAddressDebugAnchorContext)(previousAddressDebug, deps.toNonEmptyString);
|
||||||
let previousAnchor = deps.toNonEmptyString(previousAddressDebug.anchor_value_resolved) ??
|
let previousAnchorType = previousAnchorContext.anchorType;
|
||||||
deps.toNonEmptyString(previousAddressDebug.anchor_value_raw) ??
|
let previousAnchor = previousAnchorContext.anchorValue;
|
||||||
deps.readAddressFilterString(previousAddressDebug, "item") ??
|
|
||||||
deps.readAddressFilterString(previousAddressDebug, "counterparty") ??
|
|
||||||
deps.readAddressFilterString(previousAddressDebug, "account") ??
|
|
||||||
deps.readAddressFilterString(previousAddressDebug, "contract");
|
|
||||||
const navigationSessionContext = addressNavigationState && typeof addressNavigationState === "object"
|
const navigationSessionContext = addressNavigationState && typeof addressNavigationState === "object"
|
||||||
? addressNavigationState.session_context && typeof addressNavigationState.session_context === "object"
|
? addressNavigationState.session_context && typeof addressNavigationState.session_context === "object"
|
||||||
? addressNavigationState.session_context
|
? addressNavigationState.session_context
|
||||||
|
|
@ -699,8 +696,8 @@ function createAssistantTransitionPolicy(deps) {
|
||||||
}
|
}
|
||||||
if (shouldBackfillPreviousDateScopeFromNavigation &&
|
if (shouldBackfillPreviousDateScopeFromNavigation &&
|
||||||
!deps.toNonEmptyString(previousFilters.as_of_date) &&
|
!deps.toNonEmptyString(previousFilters.as_of_date) &&
|
||||||
(0, assistantContinuityPolicy_1.readAddressDebugFilters)(continuitySnapshot.lastGroundedAddressDebug)?.as_of_date) {
|
continuityTemporalScope.asOfDate) {
|
||||||
previousFilters.as_of_date = deps.toNonEmptyString((0, assistantContinuityPolicy_1.readAddressDebugFilters)(continuitySnapshot.lastGroundedAddressDebug)?.as_of_date);
|
previousFilters.as_of_date = continuityTemporalScope.asOfDate;
|
||||||
}
|
}
|
||||||
if (shouldBackfillPreviousDateScopeFromNavigation &&
|
if (shouldBackfillPreviousDateScopeFromNavigation &&
|
||||||
!deps.toNonEmptyString(previousFilters.period_from) &&
|
!deps.toNonEmptyString(previousFilters.period_from) &&
|
||||||
|
|
@ -709,8 +706,8 @@ function createAssistantTransitionPolicy(deps) {
|
||||||
}
|
}
|
||||||
if (shouldBackfillPreviousDateScopeFromNavigation &&
|
if (shouldBackfillPreviousDateScopeFromNavigation &&
|
||||||
!deps.toNonEmptyString(previousFilters.period_from) &&
|
!deps.toNonEmptyString(previousFilters.period_from) &&
|
||||||
(0, assistantContinuityPolicy_1.readAddressDebugFilters)(continuitySnapshot.lastGroundedAddressDebug)?.period_from) {
|
continuityTemporalScope.periodFrom) {
|
||||||
previousFilters.period_from = deps.toNonEmptyString((0, assistantContinuityPolicy_1.readAddressDebugFilters)(continuitySnapshot.lastGroundedAddressDebug)?.period_from);
|
previousFilters.period_from = continuityTemporalScope.periodFrom;
|
||||||
}
|
}
|
||||||
if (shouldBackfillPreviousDateScopeFromNavigation &&
|
if (shouldBackfillPreviousDateScopeFromNavigation &&
|
||||||
!deps.toNonEmptyString(previousFilters.period_to) &&
|
!deps.toNonEmptyString(previousFilters.period_to) &&
|
||||||
|
|
@ -719,8 +716,8 @@ function createAssistantTransitionPolicy(deps) {
|
||||||
}
|
}
|
||||||
if (shouldBackfillPreviousDateScopeFromNavigation &&
|
if (shouldBackfillPreviousDateScopeFromNavigation &&
|
||||||
!deps.toNonEmptyString(previousFilters.period_to) &&
|
!deps.toNonEmptyString(previousFilters.period_to) &&
|
||||||
(0, assistantContinuityPolicy_1.readAddressDebugFilters)(continuitySnapshot.lastGroundedAddressDebug)?.period_to) {
|
continuityTemporalScope.periodTo) {
|
||||||
previousFilters.period_to = deps.toNonEmptyString((0, assistantContinuityPolicy_1.readAddressDebugFilters)(continuitySnapshot.lastGroundedAddressDebug)?.period_to);
|
previousFilters.period_to = continuityTemporalScope.periodTo;
|
||||||
}
|
}
|
||||||
const rootContextOnlyPivot = Boolean((deps.isInventorySelectedObjectIntent(sourceIntentHint) || currentFrameKind === "inventory_drilldown") &&
|
const rootContextOnlyPivot = Boolean((deps.isInventorySelectedObjectIntent(sourceIntentHint) || currentFrameKind === "inventory_drilldown") &&
|
||||||
deps.hasForeignAccountingPivotOverInventoryMessage(userMessage, alternateMessage) &&
|
deps.hasForeignAccountingPivotOverInventoryMessage(userMessage, alternateMessage) &&
|
||||||
|
|
|
||||||
|
|
@ -33,6 +33,17 @@ export interface AssistantAddressDebugContextFacts {
|
||||||
scopedDate: string | null;
|
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 {
|
export interface AssistantOrganizationAuthorityInput {
|
||||||
sessionItems?: unknown[];
|
sessionItems?: unknown[];
|
||||||
sessionKnownOrganizations?: 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(
|
export function resolveAddressDebugContextFacts(
|
||||||
debug: Record<string, unknown> | null,
|
debug: Record<string, unknown> | null,
|
||||||
toNonEmptyString: (value: unknown) => string | null = fallbackToNonEmptyString
|
toNonEmptyString: (value: unknown) => string | null = fallbackToNonEmptyString
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@ import {
|
||||||
buildInventoryHistoryCapabilityFollowupReply as buildInventoryHistoryCapabilityFollowupReplyFromPolicy,
|
buildInventoryHistoryCapabilityFollowupReply as buildInventoryHistoryCapabilityFollowupReplyFromPolicy,
|
||||||
resolveAssistantLivingChatMemoryContext
|
resolveAssistantLivingChatMemoryContext
|
||||||
} from "./assistantMemoryRecapPolicy";
|
} from "./assistantMemoryRecapPolicy";
|
||||||
import { resolveAssistantOrganizationAuthority } from "./assistantContinuityPolicy";
|
import { formatIsoDateForReply, resolveAssistantOrganizationAuthority } from "./assistantContinuityPolicy";
|
||||||
|
|
||||||
export interface AssistantLivingChatSessionScopeInput {
|
export interface AssistantLivingChatSessionScopeInput {
|
||||||
knownOrganizations?: unknown[];
|
knownOrganizations?: unknown[];
|
||||||
|
|
@ -66,77 +66,6 @@ export interface AssistantLivingChatRuntimeOutput {
|
||||||
debug: Record<string, unknown> | null;
|
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 {
|
function hasPriorAssistantTurn(items: unknown[]): boolean {
|
||||||
if (!Array.isArray(items)) {
|
if (!Array.isArray(items)) {
|
||||||
return false;
|
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.";
|
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: {
|
function buildInventoryHistoryCapabilityFollowupReply(input: {
|
||||||
organization: string | null;
|
organization: string | null;
|
||||||
addressDebug: Record<string, unknown> | null;
|
addressDebug: Record<string, unknown> | null;
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,8 @@ import {
|
||||||
buildInventoryRootFrameFromAddressDebug,
|
buildInventoryRootFrameFromAddressDebug,
|
||||||
readAddressDebugFilters,
|
readAddressDebugFilters,
|
||||||
readAddressDebugItem,
|
readAddressDebugItem,
|
||||||
|
readAddressDebugTemporalScope,
|
||||||
|
resolveAddressDebugAnchorContext,
|
||||||
resolveAssistantOrganizationAuthority
|
resolveAssistantOrganizationAuthority
|
||||||
} from "./assistantContinuityPolicy";
|
} from "./assistantContinuityPolicy";
|
||||||
|
|
||||||
|
|
@ -422,6 +424,10 @@ export function createAssistantTransitionPolicy(deps) {
|
||||||
mergeKnownOrganizations: deps.mergeKnownOrganizations
|
mergeKnownOrganizations: deps.mergeKnownOrganizations
|
||||||
});
|
});
|
||||||
const continuitySnapshot = organizationAuthority.continuitySnapshot;
|
const continuitySnapshot = organizationAuthority.continuitySnapshot;
|
||||||
|
const continuityTemporalScope = readAddressDebugTemporalScope(
|
||||||
|
continuitySnapshot.lastGroundedAddressDebug,
|
||||||
|
deps.toNonEmptyString
|
||||||
|
);
|
||||||
const organizationClarificationCandidates = Array.isArray(organizationAuthority.organizationClarificationCandidates)
|
const organizationClarificationCandidates = Array.isArray(organizationAuthority.organizationClarificationCandidates)
|
||||||
? organizationAuthority.organizationClarificationCandidates
|
? organizationAuthority.organizationClarificationCandidates
|
||||||
: [];
|
: [];
|
||||||
|
|
@ -652,14 +658,9 @@ export function createAssistantTransitionPolicy(deps) {
|
||||||
followupSelectionMode = "switch_to_suggested_intent";
|
followupSelectionMode = "switch_to_suggested_intent";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let previousAnchorType = deps.toNonEmptyString(previousAddressDebug.anchor_type);
|
const previousAnchorContext = resolveAddressDebugAnchorContext(previousAddressDebug, deps.toNonEmptyString);
|
||||||
let previousAnchor =
|
let previousAnchorType = previousAnchorContext.anchorType;
|
||||||
deps.toNonEmptyString(previousAddressDebug.anchor_value_resolved) ??
|
let previousAnchor = previousAnchorContext.anchorValue;
|
||||||
deps.toNonEmptyString(previousAddressDebug.anchor_value_raw) ??
|
|
||||||
deps.readAddressFilterString(previousAddressDebug, "item") ??
|
|
||||||
deps.readAddressFilterString(previousAddressDebug, "counterparty") ??
|
|
||||||
deps.readAddressFilterString(previousAddressDebug, "account") ??
|
|
||||||
deps.readAddressFilterString(previousAddressDebug, "contract");
|
|
||||||
const navigationSessionContext =
|
const navigationSessionContext =
|
||||||
addressNavigationState && typeof addressNavigationState === "object"
|
addressNavigationState && typeof addressNavigationState === "object"
|
||||||
? addressNavigationState.session_context && typeof addressNavigationState.session_context === "object"
|
? addressNavigationState.session_context && typeof addressNavigationState.session_context === "object"
|
||||||
|
|
@ -851,11 +852,9 @@ export function createAssistantTransitionPolicy(deps) {
|
||||||
if (
|
if (
|
||||||
shouldBackfillPreviousDateScopeFromNavigation &&
|
shouldBackfillPreviousDateScopeFromNavigation &&
|
||||||
!deps.toNonEmptyString(previousFilters.as_of_date) &&
|
!deps.toNonEmptyString(previousFilters.as_of_date) &&
|
||||||
readAddressDebugFilters(continuitySnapshot.lastGroundedAddressDebug)?.as_of_date
|
continuityTemporalScope.asOfDate
|
||||||
) {
|
) {
|
||||||
previousFilters.as_of_date = deps.toNonEmptyString(
|
previousFilters.as_of_date = continuityTemporalScope.asOfDate;
|
||||||
readAddressDebugFilters(continuitySnapshot.lastGroundedAddressDebug)?.as_of_date
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
if (
|
if (
|
||||||
shouldBackfillPreviousDateScopeFromNavigation &&
|
shouldBackfillPreviousDateScopeFromNavigation &&
|
||||||
|
|
@ -867,11 +866,9 @@ export function createAssistantTransitionPolicy(deps) {
|
||||||
if (
|
if (
|
||||||
shouldBackfillPreviousDateScopeFromNavigation &&
|
shouldBackfillPreviousDateScopeFromNavigation &&
|
||||||
!deps.toNonEmptyString(previousFilters.period_from) &&
|
!deps.toNonEmptyString(previousFilters.period_from) &&
|
||||||
readAddressDebugFilters(continuitySnapshot.lastGroundedAddressDebug)?.period_from
|
continuityTemporalScope.periodFrom
|
||||||
) {
|
) {
|
||||||
previousFilters.period_from = deps.toNonEmptyString(
|
previousFilters.period_from = continuityTemporalScope.periodFrom;
|
||||||
readAddressDebugFilters(continuitySnapshot.lastGroundedAddressDebug)?.period_from
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
if (
|
if (
|
||||||
shouldBackfillPreviousDateScopeFromNavigation &&
|
shouldBackfillPreviousDateScopeFromNavigation &&
|
||||||
|
|
@ -883,11 +880,9 @@ export function createAssistantTransitionPolicy(deps) {
|
||||||
if (
|
if (
|
||||||
shouldBackfillPreviousDateScopeFromNavigation &&
|
shouldBackfillPreviousDateScopeFromNavigation &&
|
||||||
!deps.toNonEmptyString(previousFilters.period_to) &&
|
!deps.toNonEmptyString(previousFilters.period_to) &&
|
||||||
readAddressDebugFilters(continuitySnapshot.lastGroundedAddressDebug)?.period_to
|
continuityTemporalScope.periodTo
|
||||||
) {
|
) {
|
||||||
previousFilters.period_to = deps.toNonEmptyString(
|
previousFilters.period_to = continuityTemporalScope.periodTo;
|
||||||
readAddressDebugFilters(continuitySnapshot.lastGroundedAddressDebug)?.period_to
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
const rootContextOnlyPivot = Boolean(
|
const rootContextOnlyPivot = Boolean(
|
||||||
(deps.isInventorySelectedObjectIntent(sourceIntentHint) || currentFrameKind === "inventory_drilldown") &&
|
(deps.isInventorySelectedObjectIntent(sourceIntentHint) || currentFrameKind === "inventory_drilldown") &&
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,8 @@
|
||||||
import { describe, expect, it } from "vitest";
|
import { describe, expect, it } from "vitest";
|
||||||
import {
|
import {
|
||||||
|
readAddressDebugTemporalScope,
|
||||||
resolveAddressDebugContextFacts,
|
resolveAddressDebugContextFacts,
|
||||||
|
resolveAddressDebugAnchorContext,
|
||||||
resolveAssistantOrganizationAuthority
|
resolveAssistantOrganizationAuthority
|
||||||
} from "../src/services/assistantContinuityPolicy";
|
} from "../src/services/assistantContinuityPolicy";
|
||||||
|
|
||||||
|
|
@ -75,4 +77,27 @@ describe("assistantContinuityPolicy organization authority", () => {
|
||||||
scopedDate: "31.03.2020"
|
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");
|
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", () => {
|
it("bridges selected-item purchase provenance into a VAT period follow-up", () => {
|
||||||
const item = "Рабочая станция универсального специалиста";
|
const item = "Рабочая станция универсального специалиста";
|
||||||
const policy = buildPolicy({
|
const policy = buildPolicy({
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,120 @@
|
||||||
|
{
|
||||||
|
"suite_id": "assistant_saved_session_runtime_job-0Xii7XMUFP",
|
||||||
|
"suite_version": "0.1.0",
|
||||||
|
"schema_version": "assistant_saved_session_runtime_v0_1",
|
||||||
|
"title": "БОЛЬШОЙ ОБЩИЙ Ручная сессия 16.04.2026, 21:26:06",
|
||||||
|
"scenario_count": 1,
|
||||||
|
"case_ids": [
|
||||||
|
"SAVED-001"
|
||||||
|
],
|
||||||
|
"cases": [
|
||||||
|
{
|
||||||
|
"case_id": "SAVED-001",
|
||||||
|
"scenario_tag": "saved_user_sessions_runtime",
|
||||||
|
"title": "БОЛЬШОЙ ОБЩИЙ Ручная сессия 16.04.2026, 21:26:06",
|
||||||
|
"question_type": "followup",
|
||||||
|
"broadness_level": "medium",
|
||||||
|
"turns": [
|
||||||
|
{
|
||||||
|
"user_message": "приветик - че как там дела"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"user_message": "расскажи что можешь интересного"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"user_message": "кайф - что там на складе по остаткам?"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"user_message": "АЛЬТЕРНАТИВА"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"user_message": "а исторические остатки на другие даты умеешь?"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"user_message": "давай на июль 2017"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"user_message": "март 2016"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"user_message": "По выбранному объекту \"Рабочая станция универсального специалиста (индивидуальное изготовление)\": где взяли это?"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"user_message": "а кому продали?"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"user_message": "у тебя написано кто контрагент: рабочая станция - это ошибка?"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"user_message": "ндс можешь прикинуть на дату покупки рабочей станции?"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"user_message": "а какой ндс мы должны сгрузить на март 2020?"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"user_message": "прикинь какой ндс нам надо заплатить на февраль 2017"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"user_message": "кто у нас самый доходный клиент за все время"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"user_message": "кто нам должен денег на май 2017"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"user_message": "а какой ндс мы должны примерно заплатить за этот период?"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"user_message": "мы должны комуто денег на сегодня?"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"user_message": "а нам?"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"user_message": "какой у нас самый доходный год"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"user_message": "а за 2017 мы скок заработали?"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"user_message": "сколько вообще денег мы заработали за все время?"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"user_message": "ты умеешь считать дельту по договорам?"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"user_message": "по чепурнову покажи все доки"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"user_message": "а по свк"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"user_message": "а сейчас у нас есть что на складе?"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"user_message": "что нам отгружал чепурнов? какой товар или услугу?"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"user_message": "какие остатки на складе на сегодня"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"user_message": "остатки на март 2016"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"user_message": "хвосты покажи по счету 60 на август 2022"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"user_message": "Есть ли остатки товара, которые закупались очень давно"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"user_message": "Какие конкретно номенклатуры формируют остаток по складу на май 2020"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"user_message": "а по Альтернативе Плюс сколько лет активности в базе 1С?"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"user_message": "Как ты оценишь деятельность компании?"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue