Архитектура: закрыть phase17 clarification-resume и обновить readiness-статус перед расширением доменов

This commit is contained in:
2026-04-19 17:17:09 +03:00
parent af15e21bf6
commit 0e098cde38
18 changed files with 842 additions and 87 deletions
@@ -1933,6 +1933,7 @@ function applyPreExecutionOrganizationScopeGrounding(input: {
sameNormalizedOrganizationScope(input.filters.organization ?? null, activeOrganization) &&
typeof input.filters.counterparty === "string" &&
(isLikelyLowQualityPartyAnchor(input.filters.counterparty) ||
sameOrganizationEntityReference(input.filters.counterparty, resolvedOrganizationFromMessage ?? activeOrganization) ||
isQuestionFragmentPartyAnchor(input.filters.counterparty))
) {
delete input.filters.counterparty;
@@ -21,6 +21,7 @@ import {
hasInventorySaleCue,
hasInventorySupplierCue
} from "../inventoryLifecycleCueHelpers";
import { normalizeOrganizationScopeSearchText, organizationsLikelySameEntity } from "../assistantOrganizationMatcher";
import { applyAddressLlmSemanticHintsToExtraction } from "./semanticHintOverlay";
import type { AddressLlmSemanticHints } from "../../types/addressQuery";
@@ -393,6 +394,28 @@ function isInventoryLifecycleHistoryIntent(intent: AddressIntent | undefined): b
);
}
function shouldSuppressInventoryCounterpartyAlias(
intent: AddressIntent,
counterparty: string | null,
organization: string | null
): boolean {
if (intent !== "inventory_on_hand_as_of_date") {
return false;
}
if (!counterparty || !organization) {
return false;
}
if (organizationsLikelySameEntity(counterparty, organization)) {
return true;
}
const counterpartyNorm = normalizeOrganizationScopeSearchText(counterparty);
const organizationNorm = normalizeOrganizationScopeSearchText(organization);
if (!counterpartyNorm || !organizationNorm) {
return false;
}
return organizationNorm.includes(counterpartyNorm) || counterpartyNorm.includes(organizationNorm);
}
function buildInventoryRootFollowupContext(
followupContext: AddressFollowupContext | null
): AddressFollowupContext | null {
@@ -820,12 +843,29 @@ function mergeFollowupFilters(
const allTimeRequested = hasAllTimeHint(userMessage);
const sameDateRequested = hasSameDateHint(userMessage) || hasSameDatePrepositionHint(userMessage);
const samePeriodRequested = hasSamePeriodHint(userMessage);
const hasFollowupSignal = hasAddressFollowupContextSignal(userMessage);
const hasExplicitPeriodInMessage = hasExplicitPeriodLiteral(userMessage);
const hasExplicitCurrentDateInMessage = hasExplicitCurrentDateHint(userMessage);
const explicitQuotedItem = extractSelectedObjectItemFromFollowupText(userMessage);
if (!toNonEmptyString(merged.organization) && previousOrganization) {
merged.organization = previousOrganization;
reasons.push("organization_from_followup_context");
}
if (
intent === "inventory_on_hand_as_of_date" &&
followupContext.previous_intent === "inventory_on_hand_as_of_date" &&
followupContext.target_intent === "inventory_on_hand_as_of_date" &&
followupContext.previous_anchor_type === "organization" &&
!hasFollowupSignal &&
!hasExplicitPeriodInMessage &&
!hasExplicitCurrentDateInMessage &&
toNonEmptyString(merged.counterparty)
) {
delete merged.counterparty;
reasons.push("counterparty_cleared_from_organization_clarification_selection");
}
if (
intent === "list_documents_by_counterparty" ||
intent === "bank_operations_by_counterparty" ||
@@ -835,11 +875,33 @@ function mergeFollowupFilters(
previousCounterparty ??
(followupContext.previous_anchor_type === "counterparty" ? previousAnchorValue : null);
const currentCounterparty = toNonEmptyString(merged.counterparty);
const organizationReference =
toNonEmptyString(merged.organization) ??
previousOrganization ??
(followupContext.previous_anchor_type === "organization" ? previousAnchorValue : null);
const currentCounterpartyLooksLikeOrganization = shouldSuppressInventoryCounterpartyAlias(
intent,
currentCounterparty,
organizationReference
);
const inheritedCounterpartyLooksLikeOrganization = shouldSuppressInventoryCounterpartyAlias(
intent,
inheritedCounterparty,
organizationReference
);
if (!currentCounterparty && inheritedCounterpartyLooksLikeOrganization) {
reasons.push("counterparty_cleared_as_organization_scope_alias");
}
if (currentCounterpartyLooksLikeOrganization) {
delete merged.counterparty;
reasons.push("counterparty_cleared_as_organization_scope_alias");
}
const shouldInheritCounterparty =
!currentCounterparty ||
(Boolean(inheritedCounterparty) &&
isLowQualityCounterpartyAnchor(currentCounterparty) &&
!isLowQualityCounterpartyAnchor(inheritedCounterparty));
!inheritedCounterpartyLooksLikeOrganization &&
(!currentCounterparty ||
(Boolean(inheritedCounterparty) &&
isLowQualityCounterpartyAnchor(currentCounterparty) &&
!isLowQualityCounterpartyAnchor(inheritedCounterparty)));
if (inheritedCounterparty && shouldInheritCounterparty) {
merged.counterparty = inheritedCounterparty;
reasons.push(currentCounterparty ? "counterparty_replaced_from_followup_context" : "counterparty_from_followup_context");
@@ -925,7 +987,7 @@ function mergeFollowupFilters(
intent === "vat_payable_forecast" ||
intent === "vat_liability_confirmed_for_tax_period"
) {
const hasFollowupSignalForConfirmed = hasAddressFollowupContextSignal(userMessage);
const hasFollowupSignalForConfirmed = hasFollowupSignal;
const inheritedContract = previousContract ?? (followupContext.previous_anchor_type === "contract" ? previousAnchorValue : null);
const currentContract = toNonEmptyString(merged.contract);
const shouldInheritContract =
@@ -1189,9 +1251,6 @@ function mergeFollowupFilters(
}
}
const hasFollowupSignal = hasAddressFollowupContextSignal(userMessage);
const hasExplicitPeriodInMessage = hasExplicitPeriodLiteral(userMessage);
const hasExplicitCurrentDateInMessage = hasExplicitCurrentDateHint(userMessage);
const inventoryLifecycleHistoryIntent = isInventoryLifecycleHistoryIntent(intent);
const asOfPrimaryIntent =
intent === "account_balance_snapshot" ||
@@ -1209,6 +1268,19 @@ function mergeFollowupFilters(
intent === "vat_payable_confirmed_as_of_date";
const currentHasPeriod = hasExplicitPeriodWindow(merged);
const previousHasPeriod = hasExplicitPeriodWindow(previous);
const currentCounterpartyExplicit = toNonEmptyString(merged.counterparty);
const currentContractExplicit = toNonEmptyString(merged.contract);
const currentItemExplicit = toNonEmptyString(merged.item);
const currentAccountExplicit = toNonEmptyString(merged.account);
const shouldSuppressGenericPeriodCarryover =
(Boolean(currentCounterpartyExplicit) &&
!isLowQualityCounterpartyAnchor(currentCounterpartyExplicit) &&
currentCounterpartyExplicit !== previousCounterparty) ||
(Boolean(currentContractExplicit) &&
!isLowQualityContractAnchor(currentContractExplicit) &&
currentContractExplicit !== previousContract) ||
(Boolean(currentItemExplicit) && currentItemExplicit !== previousItem) ||
(Boolean(currentAccountExplicit) && currentAccountExplicit !== previousAccount);
const vatRelativeMonthFollowup =
relativeMonthFromFollowupYear &&
(intent === "vat_payable_confirmed_as_of_date" ||
@@ -1254,7 +1326,8 @@ function mergeFollowupFilters(
hasFollowupSignal &&
!hasExplicitPeriodInMessage &&
!inventoryLifecycleHistoryIntent &&
!vatRelativeMonthFollowup
!vatRelativeMonthFollowup &&
!shouldSuppressGenericPeriodCarryover
) {
if (previousPeriodFrom) {
merged.period_from = previousPeriodFrom;
@@ -1295,6 +1368,18 @@ function mergeFollowupFilters(
}
}
const finalOrganizationReference =
toNonEmptyString(merged.organization) ??
previousOrganization ??
(followupContext.previous_anchor_type === "organization" ? previousAnchorValue : null);
const finalCounterparty = toNonEmptyString(merged.counterparty);
if (shouldSuppressInventoryCounterpartyAlias(intent, finalCounterparty, finalOrganizationReference)) {
delete merged.counterparty;
if (!reasons.includes("counterparty_cleared_as_organization_scope_alias")) {
reasons.push("counterparty_cleared_as_organization_scope_alias");
}
}
return { filters: merged, reasons };
}
@@ -409,10 +409,12 @@ export function createAssistantRoutePolicy(deps) {
const lastOrganizationClarificationDebug = findLastOrganizationClarificationAddressDebug(sessionItems);
const organizationClarificationCandidates = organizationAuthority.organizationClarificationCandidates;
const organizationClarificationSelectionFromScope = organizationAuthority.organizationClarificationSelectionFromScope;
const organizationClarificationSelection = resolveOrganizationSelectionFromMessage(rawUserMessage, organizationClarificationCandidates) ??
const explicitOrganizationClarificationSelection = resolveOrganizationSelectionFromMessage(rawUserMessage, organizationClarificationCandidates) ??
resolveOrganizationSelectionFromMessage(repairedRawUserMessage, organizationClarificationCandidates) ??
resolveOrganizationSelectionFromMessage(effectiveAddressUserMessage, organizationClarificationCandidates) ??
resolveOrganizationSelectionFromMessage(repairedEffectiveAddressUserMessage, organizationClarificationCandidates) ??
null;
const organizationClarificationSelection = explicitOrganizationClarificationSelection ??
(organizationClarificationSelectionFromScope &&
organizationClarificationCandidates.some((candidate) => normalizeOrganizationScopeValue(candidate) === organizationClarificationSelectionFromScope)
? organizationClarificationSelectionFromScope
@@ -523,11 +525,12 @@ export function createAssistantRoutePolicy(deps) {
hasShortInventoryObjectFollowupSignal(repairedEffectiveAddressUserMessage)));
const organizationClarificationContinuationDetected = Boolean((followupContext || continuitySnapshot.hasGroundedAddressContext) &&
lastOrganizationClarificationDebug &&
organizationClarificationSelection &&
explicitOrganizationClarificationSelection &&
!dataScopeMetaQuery &&
!capabilityMetaQuery &&
!dataRetrievalSignal);
const organizationScopeSwitchDetected = Boolean(organizationClarificationSelection &&
const organizationScopeSwitchDetected = Boolean(explicitOrganizationClarificationSelection &&
!organizationClarificationContinuationDetected &&
!dataScopeMetaQuery &&
!capabilityMetaQuery &&
(shouldEmitOrganizationSelectionReply(rawUserMessage, organizationClarificationSelection) ||
@@ -419,7 +419,10 @@ export function createAssistantTransitionPolicy(deps) {
const hasOrganizationClarificationContinuation = Boolean(
lastOrganizationClarificationDebug && organizationClarificationSelection
);
const followupOffer = previousAddressDebug ? deps.buildAddressFollowupOffer(previousAddressDebug) : null;
const carryoverSourceDebug =
previousAddressDebug ??
(hasOrganizationClarificationContinuation ? lastOrganizationClarificationDebug : null);
const followupOffer = carryoverSourceDebug ? deps.buildAddressFollowupOffer(carryoverSourceDebug) : null;
const hasImplicitContinuationSignal =
Boolean(previousAddressDebug) &&
Boolean(followupOffer?.enabled) &&
@@ -427,7 +430,7 @@ export function createAssistantTransitionPolicy(deps) {
(deps.toNonEmptyString(alternateMessage)
? deps.isImplicitAddressContinuationByLlm(alternateMessage, llmPreDecomposeMeta)
: false));
const sourceIntentHint = deps.toNonEmptyString(previousAddressDebug?.detected_intent);
const sourceIntentHint = deps.toNonEmptyString(carryoverSourceDebug?.detected_intent);
const navigationFocusObjectHint =
addressNavigationState &&
typeof addressNavigationState === "object" &&
@@ -560,10 +563,10 @@ export function createAssistantTransitionPolicy(deps) {
) {
return null;
}
if (!previousAddressDebug) {
if (!carryoverSourceDebug) {
return null;
}
const sourceIntent = deps.toNonEmptyString(previousAddressDebug.detected_intent);
const sourceIntent = deps.toNonEmptyString(carryoverSourceDebug.detected_intent);
const llmExplicitIntent = deps.toNonEmptyString(llmPreDecomposeMeta?.predecomposeContract?.intent);
const llmSelectedObjectScopeDetected =
llmPreDecomposeMeta?.predecomposeContract?.semantics?.selected_object_scope_detected === true;
@@ -637,7 +640,7 @@ export function createAssistantTransitionPolicy(deps) {
followupSelectionMode = "switch_to_suggested_intent";
}
}
const previousAnchorContext = resolveAddressDebugAnchorContext(previousAddressDebug, deps.toNonEmptyString);
const previousAnchorContext = resolveAddressDebugAnchorContext(carryoverSourceDebug, deps.toNonEmptyString);
let previousAnchorType = previousAnchorContext.anchorType;
let previousAnchor = previousAnchorContext.anchorValue;
const navigationSessionContext =
@@ -723,7 +726,7 @@ export function createAssistantTransitionPolicy(deps) {
);
let resolvedCounterpartyFromDisplay = false;
let displayedEntityTargetIntent = null;
let previousFilters = resolveAddressDebugCarryoverFilters(previousAddressDebug, deps.toNonEmptyString);
let previousFilters = resolveAddressDebugCarryoverFilters(carryoverSourceDebug, deps.toNonEmptyString);
const shouldBackfillHistoricalPartyAnchors =
sourceIntentHint === "list_contracts_by_counterparty" ||
sourceIntentHint === "list_documents_by_counterparty" ||
@@ -754,12 +757,12 @@ export function createAssistantTransitionPolicy(deps) {
if (inventoryPurchaseDateVatBridge) {
const purchaseBridgeItem =
previousAddressItem &&
deps.toNonEmptyString(previousAddressDebug?.detected_intent) === "inventory_purchase_provenance_for_item"
deps.toNonEmptyString(carryoverSourceDebug?.detected_intent) === "inventory_purchase_provenance_for_item"
? previousAddressItem
: findRecentInventoryPurchaseProvenanceItem(
items,
deps.toNonEmptyString(navigationFocusObjectLabel) ??
readAddressDebugItem(previousAddressDebug, deps.toNonEmptyString) ??
readAddressDebugItem(carryoverSourceDebug, deps.toNonEmptyString) ??
deps.toNonEmptyString(previousFilters.item)
) ?? previousAddressItem;
const purchaseBridgeWindow = extractPurchaseDateBridgeWindow(purchaseBridgeItem, addressNavigationState);