ГЛОБАЛЬНЫЙ РЕФАКТОРИНГ АРХИТЕКТУРЫ - Спека exact-маршрута payables на дату: confirmed_balance без эвристического финала
This commit is contained in:
@@ -1,8 +1,10 @@
|
||||
import {
|
||||
import {
|
||||
FEATURE_ASSISTANT_ADDRESS_QUERY_V1,
|
||||
FEATURE_ASSISTANT_ADDRESS_QUERY_LIVE_V1
|
||||
} from "../config";
|
||||
import type {
|
||||
AddressAsOfDateBasis,
|
||||
AddressEvidenceStrength,
|
||||
AddressExecutionResult,
|
||||
AddressFilterSet,
|
||||
AddressIntent,
|
||||
@@ -10,14 +12,19 @@ import type {
|
||||
AddressMatchFailureStage,
|
||||
AddressMcpCallStatus,
|
||||
AddressQueryShapeDetection,
|
||||
AddressResultMode,
|
||||
AddressResponseType,
|
||||
AddressRuntimeReadiness
|
||||
} from "../types/addressQuery";
|
||||
import { buildAddressRecipePlan, selectAddressRecipe } from "./addressRecipeCatalog";
|
||||
import {
|
||||
buildAddressRecipePlan,
|
||||
selectAddressRecipe,
|
||||
type AddressRecipeExecutionPlan
|
||||
} from "./addressRecipeCatalog";
|
||||
import { executeAddressMcpQuery } from "./addressMcpClient";
|
||||
import { runAddressDecomposeStage, type AddressFollowupContext } from "./address_runtime/decomposeStage";
|
||||
import { resolvePrimaryAnchor, refineAnchorFromRows, type AnchorResolutionDebug } from "./address_runtime/resolveStage";
|
||||
import { composeFactualReply, inferReplyType } from "./address_runtime/composeStage";
|
||||
import { composeFactualReply, inferReplyType, type ComposeReplySemantics } from "./address_runtime/composeStage";
|
||||
|
||||
interface NormalizedAddressRow {
|
||||
period: string | null;
|
||||
@@ -36,6 +43,7 @@ interface AddressTryHandleOptions {
|
||||
const ACCOUNT_SCOPE_FIELDS_CHECKED = ["account_dt", "account_kt", "registrator", "analytics"] as const;
|
||||
const ACCOUNT_SCOPE_MATCH_STRATEGY = "account_code_regex_plus_alias_map_v1" as const;
|
||||
const ADDRESS_ANCHOR_RECOVERY_LIMIT = 1000;
|
||||
const ADDRESS_CONFIRMED_PAYABLES_MIN_LIMIT = 200;
|
||||
const COUNTERPARTY_CATALOG_LOOKUP_LIMIT = 1000;
|
||||
const COUNTERPARTY_CATALOG_CACHE_TTL_MS = 120_000;
|
||||
const PARTY_ANCHOR_STOPWORDS = new Set([
|
||||
@@ -742,6 +750,197 @@ function isCounterpartyRiskIntent(intent: AddressIntent): boolean {
|
||||
);
|
||||
}
|
||||
|
||||
function isHeuristicCandidatesIntent(intent: AddressIntent): boolean {
|
||||
return (
|
||||
intent === "list_receivables_counterparties" ||
|
||||
intent === "list_payables_counterparties" ||
|
||||
intent === "list_open_contracts" ||
|
||||
intent === "open_items_by_counterparty_or_contract"
|
||||
);
|
||||
}
|
||||
|
||||
function isConfirmedBalanceIntent(intent: AddressIntent): boolean {
|
||||
return intent === "account_balance_snapshot" || intent === "documents_forming_balance";
|
||||
}
|
||||
|
||||
function resolveAsOfDateBasis(filters: AddressFilterSet): AddressAsOfDateBasis | null {
|
||||
const asOfDate = normalizeAnalysisDateHint(filters.as_of_date);
|
||||
if (asOfDate) {
|
||||
return "explicit_as_of_date";
|
||||
}
|
||||
const periodFrom = normalizeAnalysisDateHint(filters.period_from);
|
||||
const periodTo = normalizeAnalysisDateHint(filters.period_to);
|
||||
if (periodFrom && periodTo) {
|
||||
return "period_range";
|
||||
}
|
||||
if (!periodFrom && periodTo) {
|
||||
return "period_end";
|
||||
}
|
||||
if (periodFrom) {
|
||||
return "period_range";
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function deriveAddressEvidenceStrength(input: {
|
||||
intent: AddressIntent;
|
||||
selectedRecipe: string | null;
|
||||
responseType: AddressResponseType;
|
||||
rowsMatched: number;
|
||||
}): AddressEvidenceStrength | undefined {
|
||||
if (isHeuristicCandidatesIntent(input.intent)) {
|
||||
if (input.rowsMatched <= 0 || input.responseType === "LIMITED_WITH_REASON") {
|
||||
return "weak";
|
||||
}
|
||||
if (input.selectedRecipe === "address_open_items_by_party_or_contract_v1") {
|
||||
return "medium";
|
||||
}
|
||||
return "weak";
|
||||
}
|
||||
if (isConfirmedBalanceIntent(input.intent)) {
|
||||
if (input.rowsMatched > 0) {
|
||||
return "strong";
|
||||
}
|
||||
return input.responseType === "LIMITED_WITH_REASON" ? "weak" : "medium";
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
function resolveRequestedResultMode(intent: AddressIntent, filters: AddressFilterSet): AddressResultMode | undefined {
|
||||
if (isConfirmedBalanceIntent(intent)) {
|
||||
return "confirmed_balance";
|
||||
}
|
||||
if (isHeuristicCandidatesIntent(intent)) {
|
||||
const asOfDateBasis = resolveAsOfDateBasis(filters);
|
||||
if (asOfDateBasis === "explicit_as_of_date" || asOfDateBasis === "period_end" || asOfDateBasis === "period_range") {
|
||||
return "confirmed_balance";
|
||||
}
|
||||
return "heuristic_candidates";
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
function deriveAddressResultSemantics(input: {
|
||||
intent: AddressIntent;
|
||||
selectedRecipe: string | null;
|
||||
filters: AddressFilterSet;
|
||||
responseType: AddressResponseType;
|
||||
rowsMatched: number;
|
||||
}): {
|
||||
requested_result_mode?: AddressResultMode;
|
||||
result_mode?: AddressResultMode;
|
||||
evidence_strength?: AddressEvidenceStrength;
|
||||
balance_confirmed?: boolean;
|
||||
as_of_date_basis?: AddressAsOfDateBasis | null;
|
||||
} {
|
||||
const asOfDateBasis = resolveAsOfDateBasis(input.filters);
|
||||
const requestedResultMode = resolveRequestedResultMode(input.intent, input.filters);
|
||||
if (isHeuristicCandidatesIntent(input.intent)) {
|
||||
return {
|
||||
requested_result_mode: requestedResultMode,
|
||||
result_mode: "heuristic_candidates",
|
||||
evidence_strength: deriveAddressEvidenceStrength(input),
|
||||
balance_confirmed: false,
|
||||
as_of_date_basis: asOfDateBasis
|
||||
};
|
||||
}
|
||||
if (isConfirmedBalanceIntent(input.intent)) {
|
||||
return {
|
||||
requested_result_mode: requestedResultMode,
|
||||
result_mode: "confirmed_balance",
|
||||
evidence_strength: deriveAddressEvidenceStrength(input),
|
||||
balance_confirmed: true,
|
||||
as_of_date_basis: asOfDateBasis ?? "period_end"
|
||||
};
|
||||
}
|
||||
if (requestedResultMode) {
|
||||
return {
|
||||
requested_result_mode: requestedResultMode
|
||||
};
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
type AddressResultSemantics = ReturnType<typeof deriveAddressResultSemantics>;
|
||||
|
||||
function mergeAddressResultSemantics(
|
||||
base: AddressResultSemantics,
|
||||
override: ComposeReplySemantics | undefined
|
||||
): AddressResultSemantics {
|
||||
if (!override) {
|
||||
return base;
|
||||
}
|
||||
return {
|
||||
...base,
|
||||
...(override.result_mode ? { result_mode: override.result_mode } : {}),
|
||||
...(override.evidence_strength ? { evidence_strength: override.evidence_strength } : {}),
|
||||
...(typeof override.balance_confirmed === "boolean" ? { balance_confirmed: override.balance_confirmed } : {})
|
||||
};
|
||||
}
|
||||
|
||||
function withConfirmedBalanceFallbackReason(
|
||||
reasons: string[],
|
||||
requestedResultMode: AddressResultMode | undefined,
|
||||
semantics: ComposeReplySemantics | undefined,
|
||||
baseResultMode?: AddressResultMode
|
||||
): string[] {
|
||||
if (requestedResultMode !== "confirmed_balance") {
|
||||
return reasons;
|
||||
}
|
||||
const effectiveResultMode = semantics?.result_mode ?? baseResultMode;
|
||||
if (effectiveResultMode !== "heuristic_candidates") {
|
||||
return reasons;
|
||||
}
|
||||
if (reasons.includes("confirmed_balance_unavailable_fallback_to_heuristic_candidates")) {
|
||||
return reasons;
|
||||
}
|
||||
return [...reasons, "confirmed_balance_unavailable_fallback_to_heuristic_candidates"];
|
||||
}
|
||||
|
||||
function enforceStrictAccountScopeForIntent(
|
||||
plan: AddressRecipeExecutionPlan,
|
||||
intent: AddressIntent
|
||||
): AddressRecipeExecutionPlan {
|
||||
if (intent !== "list_receivables_counterparties" || plan.account_scope_mode === "strict") {
|
||||
return plan;
|
||||
}
|
||||
return {
|
||||
...plan,
|
||||
account_scope_mode: "strict"
|
||||
};
|
||||
}
|
||||
|
||||
function resolveExecutionFiltersForPayablesConfirmedBalance(
|
||||
filters: AddressFilterSet,
|
||||
analysisDate: string | null
|
||||
): {
|
||||
executionFilters: AddressFilterSet;
|
||||
asOfDerived: string | null;
|
||||
} {
|
||||
const explicitAsOf = normalizeAnalysisDateHint(filters.as_of_date);
|
||||
const periodTo = normalizeAnalysisDateHint(filters.period_to);
|
||||
const derivedAsOf = explicitAsOf ?? periodTo ?? analysisDate ?? null;
|
||||
const executionFilters: AddressFilterSet = {
|
||||
...filters
|
||||
};
|
||||
if (derivedAsOf) {
|
||||
executionFilters.as_of_date = derivedAsOf;
|
||||
}
|
||||
delete executionFilters.period_from;
|
||||
delete executionFilters.period_to;
|
||||
const limit =
|
||||
typeof executionFilters.limit === "number" && Number.isFinite(executionFilters.limit)
|
||||
? Math.max(1, Math.trunc(executionFilters.limit))
|
||||
: null;
|
||||
if (limit === null || limit < ADDRESS_CONFIRMED_PAYABLES_MIN_LIMIT) {
|
||||
executionFilters.limit = Math.max(ADDRESS_CONFIRMED_PAYABLES_MIN_LIMIT, limit ?? 0);
|
||||
}
|
||||
return {
|
||||
executionFilters,
|
||||
asOfDerived: derivedAsOf
|
||||
};
|
||||
}
|
||||
|
||||
function resolveFutureGuardReferenceDate(analysisDate: string | null, filters: AddressFilterSet): string | null {
|
||||
if (analysisDate) {
|
||||
return analysisDate;
|
||||
@@ -1494,6 +1693,20 @@ function buildLimitedExecutionResult(input: {
|
||||
category: AddressLimitedReasonCategory;
|
||||
}): AddressExecutionResult {
|
||||
const accountScopeAudit = input.accountScopeAudit ?? buildDefaultAccountScopeAudit(input.filters);
|
||||
const resultSemantics = deriveAddressResultSemantics({
|
||||
intent: input.intent.intent,
|
||||
selectedRecipe: input.selectedRecipe,
|
||||
filters: input.filters,
|
||||
responseType: "LIMITED_WITH_REASON",
|
||||
rowsMatched: input.rowsMatched
|
||||
});
|
||||
const requestedResultMode = resolveRequestedResultMode(input.intent.intent, input.filters);
|
||||
const reasons = withConfirmedBalanceFallbackReason(
|
||||
input.reasons,
|
||||
requestedResultMode,
|
||||
undefined,
|
||||
resultSemantics.result_mode
|
||||
);
|
||||
return {
|
||||
handled: true,
|
||||
reply_text: composeLimitedReply({
|
||||
@@ -1544,8 +1757,9 @@ function buildLimitedExecutionResult(input: {
|
||||
runtime_readiness: runtimeReadinessForLimitedCategory(input.category),
|
||||
limited_reason_category: input.category,
|
||||
response_type: "LIMITED_WITH_REASON",
|
||||
...resultSemantics,
|
||||
limitations: input.limitations,
|
||||
reasons: input.reasons
|
||||
reasons
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -1579,23 +1793,66 @@ export class AddressQueryService {
|
||||
baseReasons.push("as_of_date_from_analysis_context");
|
||||
}
|
||||
}
|
||||
const requestedResultMode = resolveRequestedResultMode(intent.intent, filters.extracted_filters);
|
||||
const payablesConfirmedExecution =
|
||||
intent.intent === "list_payables_counterparties" && requestedResultMode === "confirmed_balance"
|
||||
? resolveExecutionFiltersForPayablesConfirmedBalance(filters.extracted_filters, analysisDate)
|
||||
: null;
|
||||
const executionFilters = payablesConfirmedExecution?.executionFilters ?? filters.extracted_filters;
|
||||
if (
|
||||
payablesConfirmedExecution?.asOfDerived &&
|
||||
!(typeof filters.extracted_filters.as_of_date === "string" && filters.extracted_filters.as_of_date.trim().length > 0)
|
||||
) {
|
||||
if (!filters.warnings.includes("as_of_date_derived_for_confirmed_payables")) {
|
||||
filters.warnings.push("as_of_date_derived_for_confirmed_payables");
|
||||
}
|
||||
if (!baseReasons.includes("as_of_date_derived_for_confirmed_payables")) {
|
||||
baseReasons.push("as_of_date_derived_for_confirmed_payables");
|
||||
}
|
||||
}
|
||||
const composeOptionsFromFilters = (filterSet: AddressFilterSet) => ({
|
||||
userMessage,
|
||||
periodFrom: typeof filterSet.period_from === "string" ? filterSet.period_from : undefined,
|
||||
periodTo: typeof filterSet.period_to === "string" ? filterSet.period_to : undefined,
|
||||
asOfDate: typeof filterSet.as_of_date === "string" ? filterSet.as_of_date : undefined
|
||||
asOfDate: typeof filterSet.as_of_date === "string" ? filterSet.as_of_date : undefined,
|
||||
requestedResultMode
|
||||
});
|
||||
const futureGuardReferenceDate = resolveFutureGuardReferenceDate(analysisDate, filters.extracted_filters);
|
||||
const futureGuardReferenceDate = resolveFutureGuardReferenceDate(analysisDate, executionFilters);
|
||||
let anchor = resolvePrimaryAnchor(intent.intent, filters.extracted_filters);
|
||||
const debtLifecycleReceivablesScenario =
|
||||
intent.intent === "list_receivables_counterparties" &&
|
||||
Array.isArray(intent.reasons) &&
|
||||
intent.reasons.includes("receivables_debt_lifecycle_signal_detected");
|
||||
const recipeIntent = debtLifecycleReceivablesScenario ? "open_items_by_counterparty_or_contract" : intent.intent;
|
||||
const recipeSelection = selectAddressRecipe(recipeIntent, filters.extracted_filters);
|
||||
const debtLifecyclePayablesScenario =
|
||||
intent.intent === "list_payables_counterparties" &&
|
||||
Array.isArray(intent.reasons) &&
|
||||
(intent.reasons.includes("payables_debt_lifecycle_signal_detected") ||
|
||||
intent.reasons.includes("supplier_tail_risk_signal_detected") ||
|
||||
intent.reasons.includes("payables_signal_detected"));
|
||||
const preferConfirmedBalanceForPayablesLifecycle =
|
||||
debtLifecyclePayablesScenario && requestedResultMode === "confirmed_balance";
|
||||
const recipeIntent = debtLifecycleReceivablesScenario
|
||||
? "open_items_by_counterparty_or_contract"
|
||||
: debtLifecyclePayablesScenario && !preferConfirmedBalanceForPayablesLifecycle
|
||||
? "open_items_by_counterparty_or_contract"
|
||||
: intent.intent;
|
||||
const recipeSelection = selectAddressRecipe(recipeIntent, executionFilters);
|
||||
if (debtLifecycleReceivablesScenario && recipeIntent !== intent.intent) {
|
||||
baseReasons.push("recipe_override_to_open_items_for_receivables_debt_lifecycle");
|
||||
}
|
||||
if (debtLifecyclePayablesScenario && recipeIntent !== intent.intent) {
|
||||
baseReasons.push("recipe_override_to_open_items_for_payables_debt_lifecycle");
|
||||
}
|
||||
if (preferConfirmedBalanceForPayablesLifecycle && !baseReasons.includes("confirmed_balance_attempt_for_payables_debt_lifecycle")) {
|
||||
baseReasons.push("confirmed_balance_attempt_for_payables_debt_lifecycle");
|
||||
}
|
||||
if (
|
||||
requestedResultMode === "confirmed_balance" &&
|
||||
recipeIntent === "open_items_by_counterparty_or_contract" &&
|
||||
!baseReasons.includes("confirmed_balance_unavailable_fallback_to_heuristic_candidates")
|
||||
) {
|
||||
baseReasons.push("confirmed_balance_unavailable_fallback_to_heuristic_candidates");
|
||||
}
|
||||
|
||||
if (intent.intent === "unknown") {
|
||||
return buildLimitedExecutionResult({
|
||||
@@ -1716,19 +1973,27 @@ export class AddressQueryService {
|
||||
}
|
||||
}
|
||||
|
||||
let plan = buildAddressRecipePlan(recipeSelection.selected_recipe, filters.extracted_filters);
|
||||
let effectiveRecipeId = recipeSelection.selected_recipe.recipe_id;
|
||||
let plan = enforceStrictAccountScopeForIntent(
|
||||
buildAddressRecipePlan(recipeSelection.selected_recipe, executionFilters),
|
||||
intent.intent
|
||||
);
|
||||
let mcp = await executeAddressMcpQuery({
|
||||
query: plan.query,
|
||||
limit: plan.limit
|
||||
});
|
||||
if (
|
||||
mcp.error &&
|
||||
recipeSelection.selected_recipe.recipe_id === "address_movements_receivables_v1" &&
|
||||
(plan.recipe.recipe_id === "address_movements_receivables_v1" ||
|
||||
plan.recipe.recipe_id === "address_movements_payables_v1") &&
|
||||
isMissingSubcontoFieldError(mcp.error)
|
||||
) {
|
||||
const fallbackSelection = selectAddressRecipe("open_items_by_counterparty_or_contract", filters.extracted_filters);
|
||||
const fallbackSelection = selectAddressRecipe("open_items_by_counterparty_or_contract", executionFilters);
|
||||
if (fallbackSelection.selected_recipe && fallbackSelection.missing_required_filters.length === 0) {
|
||||
const fallbackPlan = buildAddressRecipePlan(fallbackSelection.selected_recipe, filters.extracted_filters);
|
||||
const fallbackPlan = enforceStrictAccountScopeForIntent(
|
||||
buildAddressRecipePlan(fallbackSelection.selected_recipe, executionFilters),
|
||||
intent.intent
|
||||
);
|
||||
const fallbackMcp = await executeAddressMcpQuery({
|
||||
query: fallbackPlan.query,
|
||||
limit: fallbackPlan.limit
|
||||
@@ -1736,9 +2001,18 @@ export class AddressQueryService {
|
||||
if (!fallbackMcp.error) {
|
||||
plan = fallbackPlan;
|
||||
mcp = fallbackMcp;
|
||||
if (intent.intent === "list_payables_counterparties") {
|
||||
effectiveRecipeId = fallbackSelection.selected_recipe.recipe_id;
|
||||
}
|
||||
if (!baseReasons.includes("mcp_missing_subconto_field_auto_fallback_to_open_items")) {
|
||||
baseReasons.push("mcp_missing_subconto_field_auto_fallback_to_open_items");
|
||||
}
|
||||
if (
|
||||
intent.intent === "list_payables_counterparties" &&
|
||||
!baseReasons.includes("fallback_recipe_switched_to_open_items")
|
||||
) {
|
||||
baseReasons.push("fallback_recipe_switched_to_open_items");
|
||||
}
|
||||
} else {
|
||||
if (!baseReasons.includes("mcp_missing_subconto_field_auto_fallback_failed")) {
|
||||
baseReasons.push("mcp_missing_subconto_field_auto_fallback_failed");
|
||||
@@ -1759,7 +2033,7 @@ export class AddressQueryService {
|
||||
intent,
|
||||
filters: filters.extracted_filters,
|
||||
missingRequiredFilters: [],
|
||||
selectedRecipe: recipeSelection.selected_recipe.recipe_id,
|
||||
selectedRecipe: effectiveRecipeId,
|
||||
accountScopeMode: plan.account_scope_mode,
|
||||
anchor,
|
||||
mcpCallStatus: deriveMcpStageStatus({
|
||||
@@ -1797,10 +2071,10 @@ export class AddressQueryService {
|
||||
anchor = refineAnchorFromRows(anchor, normalizedRows);
|
||||
const filtersForMatching: AddressFilterSet =
|
||||
anchor.anchor_type === "counterparty" && anchor.anchor_value_resolved
|
||||
? { ...filters.extracted_filters, counterparty: anchor.anchor_value_resolved }
|
||||
? { ...executionFilters, counterparty: anchor.anchor_value_resolved }
|
||||
: anchor.anchor_type === "contract" && anchor.anchor_value_resolved
|
||||
? { ...filters.extracted_filters, contract: anchor.anchor_value_resolved }
|
||||
: filters.extracted_filters;
|
||||
? { ...executionFilters, contract: anchor.anchor_value_resolved }
|
||||
: executionFilters;
|
||||
const accountScopeAudit = buildAccountScopeAudit({
|
||||
intent: intent.intent,
|
||||
filters: filtersForMatching,
|
||||
@@ -1849,7 +2123,7 @@ export class AddressQueryService {
|
||||
const recoveredBankRows = applyIntentSpecificFilter("bank_operations_by_contract", filterByAnchors);
|
||||
const recoveredRows = recoveredBankRows.length > 0 ? recoveredBankRows : filterByAnchors;
|
||||
if (recoveredRows.length > 0) {
|
||||
const factual = composeFactualReply(intent.intent, recoveredRows, composeOptionsFromFilters(filters.extracted_filters));
|
||||
const factual = composeFactualReply(intent.intent, recoveredRows, composeOptionsFromFilters(executionFilters));
|
||||
const recoveryReason =
|
||||
recoveredBankRows.length > 0
|
||||
? "contract_docs_recovered_via_bank_fallback"
|
||||
@@ -1872,7 +2146,7 @@ export class AddressQueryService {
|
||||
detected_intent_confidence: intent.confidence,
|
||||
extracted_filters: filters.extracted_filters,
|
||||
missing_required_filters: [],
|
||||
selected_recipe: recipeSelection.selected_recipe.recipe_id,
|
||||
selected_recipe: effectiveRecipeId,
|
||||
mcp_call_status_legacy: toLegacyMcpStatus("matched_non_empty"),
|
||||
account_scope_mode: plan.account_scope_mode,
|
||||
account_scope_fallback_applied: accountScopeFallbackApplied,
|
||||
@@ -1900,8 +2174,22 @@ export class AddressQueryService {
|
||||
runtime_readiness: "LIVE_QUERYABLE_WITH_LIMITS",
|
||||
limited_reason_category: null,
|
||||
response_type: factual.responseType,
|
||||
...mergeAddressResultSemantics(
|
||||
deriveAddressResultSemantics({
|
||||
intent: intent.intent,
|
||||
selectedRecipe: effectiveRecipeId,
|
||||
filters: filters.extracted_filters,
|
||||
responseType: factual.responseType,
|
||||
rowsMatched: recoveredRows.length
|
||||
}),
|
||||
factual.semantics
|
||||
),
|
||||
limitations: [...filters.warnings, recoveryReason],
|
||||
reasons: [...baseReasons, recoveryReason]
|
||||
reasons: withConfirmedBalanceFallbackReason(
|
||||
[...baseReasons, recoveryReason],
|
||||
requestedResultMode,
|
||||
factual.semantics
|
||||
)
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -1915,12 +2203,12 @@ export class AddressQueryService {
|
||||
stageStatus === "raw_rows_received_but_not_materialized")
|
||||
) {
|
||||
const currentLimit =
|
||||
typeof filters.extracted_filters.limit === "number" && Number.isFinite(filters.extracted_filters.limit)
|
||||
? Math.max(1, Math.trunc(filters.extracted_filters.limit))
|
||||
typeof executionFilters.limit === "number" && Number.isFinite(executionFilters.limit)
|
||||
? Math.max(1, Math.trunc(executionFilters.limit))
|
||||
: plan.limit;
|
||||
if (currentLimit < ADDRESS_ANCHOR_RECOVERY_LIMIT) {
|
||||
const expandedLimitFilters: AddressFilterSet = {
|
||||
...filters.extracted_filters,
|
||||
...executionFilters,
|
||||
limit: ADDRESS_ANCHOR_RECOVERY_LIMIT
|
||||
};
|
||||
const expandedSelection = selectAddressRecipe(intent.intent, expandedLimitFilters);
|
||||
@@ -2034,8 +2322,22 @@ export class AddressQueryService {
|
||||
runtime_readiness: "LIVE_QUERYABLE_WITH_LIMITS",
|
||||
limited_reason_category: null,
|
||||
response_type: expandedFactual.responseType,
|
||||
...mergeAddressResultSemantics(
|
||||
deriveAddressResultSemantics({
|
||||
intent: intent.intent,
|
||||
selectedRecipe: expandedSelection.selected_recipe.recipe_id,
|
||||
filters: filters.extracted_filters,
|
||||
responseType: expandedFactual.responseType,
|
||||
rowsMatched: expandedFilteredRows.length
|
||||
}),
|
||||
expandedFactual.semantics
|
||||
),
|
||||
limitations: expandedLimitations,
|
||||
reasons: expandedReasons
|
||||
reasons: withConfirmedBalanceFallbackReason(
|
||||
expandedReasons,
|
||||
requestedResultMode,
|
||||
expandedFactual.semantics
|
||||
)
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -2160,8 +2462,22 @@ export class AddressQueryService {
|
||||
runtime_readiness: "LIVE_QUERYABLE_WITH_LIMITS",
|
||||
limited_reason_category: null,
|
||||
response_type: broadenedFactual.responseType,
|
||||
...mergeAddressResultSemantics(
|
||||
deriveAddressResultSemantics({
|
||||
intent: intent.intent,
|
||||
selectedRecipe: broadenedSelection.selected_recipe.recipe_id,
|
||||
filters: filters.extracted_filters,
|
||||
responseType: broadenedFactual.responseType,
|
||||
rowsMatched: broadenedFilteredRows.length
|
||||
}),
|
||||
broadenedFactual.semantics
|
||||
),
|
||||
limitations: broadenedLimitations,
|
||||
reasons: broadenedReasons
|
||||
reasons: withConfirmedBalanceFallbackReason(
|
||||
broadenedReasons,
|
||||
requestedResultMode,
|
||||
broadenedFactual.semantics
|
||||
)
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -2298,8 +2614,22 @@ export class AddressQueryService {
|
||||
runtime_readiness: "LIVE_QUERYABLE_WITH_LIMITS",
|
||||
limited_reason_category: null,
|
||||
response_type: historicalFactual.responseType,
|
||||
...mergeAddressResultSemantics(
|
||||
deriveAddressResultSemantics({
|
||||
intent: intent.intent,
|
||||
selectedRecipe: historicalSelection.selected_recipe.recipe_id,
|
||||
filters: filters.extracted_filters,
|
||||
responseType: historicalFactual.responseType,
|
||||
rowsMatched: historicalFilteredRows.length
|
||||
}),
|
||||
historicalFactual.semantics
|
||||
),
|
||||
limitations: historicalLimitations,
|
||||
reasons: historicalReasons
|
||||
reasons: withConfirmedBalanceFallbackReason(
|
||||
historicalReasons,
|
||||
requestedResultMode,
|
||||
historicalFactual.semantics
|
||||
)
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -2319,7 +2649,7 @@ export class AddressQueryService {
|
||||
const fallbackFactual = composeFactualReply(
|
||||
intent.intent,
|
||||
documentBankFallbackRows,
|
||||
composeOptionsFromFilters(filters.extracted_filters)
|
||||
composeOptionsFromFilters(executionFilters)
|
||||
);
|
||||
const fallbackPrefix = "По вашему запросу показываю найденные документы и операции в доступном срезе базы.";
|
||||
const fallbackSuggestion =
|
||||
@@ -2342,7 +2672,7 @@ export class AddressQueryService {
|
||||
detected_intent_confidence: intent.confidence,
|
||||
extracted_filters: filters.extracted_filters,
|
||||
missing_required_filters: [],
|
||||
selected_recipe: recipeSelection.selected_recipe.recipe_id,
|
||||
selected_recipe: effectiveRecipeId,
|
||||
mcp_call_status_legacy: "matched_non_empty",
|
||||
account_scope_mode: plan.account_scope_mode,
|
||||
account_scope_fallback_applied: accountScopeFallbackApplied,
|
||||
@@ -2370,8 +2700,22 @@ export class AddressQueryService {
|
||||
runtime_readiness: "LIVE_QUERYABLE_WITH_LIMITS",
|
||||
limited_reason_category: null,
|
||||
response_type: fallbackFactual.responseType,
|
||||
...mergeAddressResultSemantics(
|
||||
deriveAddressResultSemantics({
|
||||
intent: intent.intent,
|
||||
selectedRecipe: effectiveRecipeId,
|
||||
filters: filters.extracted_filters,
|
||||
responseType: fallbackFactual.responseType,
|
||||
rowsMatched: documentBankFallbackRows.length
|
||||
}),
|
||||
fallbackFactual.semantics
|
||||
),
|
||||
limitations: fallbackLimitations,
|
||||
reasons: fallbackReasons
|
||||
reasons: withConfirmedBalanceFallbackReason(
|
||||
fallbackReasons,
|
||||
requestedResultMode,
|
||||
fallbackFactual.semantics
|
||||
)
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -2467,7 +2811,7 @@ export class AddressQueryService {
|
||||
intent,
|
||||
filters: filters.extracted_filters,
|
||||
missingRequiredFilters: [],
|
||||
selectedRecipe: recipeSelection.selected_recipe.recipe_id,
|
||||
selectedRecipe: effectiveRecipeId,
|
||||
accountScopeMode: plan.account_scope_mode,
|
||||
accountScopeFallbackApplied,
|
||||
accountScopeAudit,
|
||||
@@ -2491,7 +2835,17 @@ export class AddressQueryService {
|
||||
});
|
||||
}
|
||||
|
||||
const factual = composeFactualReply(intent.intent, filteredRows, composeOptionsFromFilters(filters.extracted_filters));
|
||||
const factual = composeFactualReply(intent.intent, filteredRows, composeOptionsFromFilters(executionFilters));
|
||||
const factualResultSemantics = mergeAddressResultSemantics(
|
||||
deriveAddressResultSemantics({
|
||||
intent: intent.intent,
|
||||
selectedRecipe: effectiveRecipeId,
|
||||
filters: filters.extracted_filters,
|
||||
responseType: factual.responseType,
|
||||
rowsMatched: filteredRows.length
|
||||
}),
|
||||
factual.semantics
|
||||
);
|
||||
return {
|
||||
handled: true,
|
||||
reply_text: factual.text,
|
||||
@@ -2506,7 +2860,7 @@ export class AddressQueryService {
|
||||
detected_intent_confidence: intent.confidence,
|
||||
extracted_filters: filters.extracted_filters,
|
||||
missing_required_filters: [],
|
||||
selected_recipe: recipeSelection.selected_recipe.recipe_id,
|
||||
selected_recipe: effectiveRecipeId,
|
||||
mcp_call_status_legacy: toLegacyMcpStatus(stageStatus),
|
||||
account_scope_mode: plan.account_scope_mode,
|
||||
account_scope_fallback_applied: accountScopeFallbackApplied,
|
||||
@@ -2534,8 +2888,14 @@ export class AddressQueryService {
|
||||
runtime_readiness: "LIVE_QUERYABLE_WITH_LIMITS",
|
||||
limited_reason_category: null,
|
||||
response_type: factual.responseType,
|
||||
...factualResultSemantics,
|
||||
limitations: filters.warnings,
|
||||
reasons: baseReasons
|
||||
reasons: withConfirmedBalanceFallbackReason(
|
||||
baseReasons,
|
||||
requestedResultMode,
|
||||
factual.semantics,
|
||||
factualResultSemantics.result_mode
|
||||
)
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user