АДРЕСНЫЙ РЕЖИМ -ADDRESS:Шаг 1 - ЛЛМ ФЕРСТ + feat(address): стабилизация wave1 dynamic resolver контрагентов, follow-up carryover и актуализация docs/tests
This commit is contained in:
@@ -35,6 +35,8 @@ 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 COUNTERPARTY_CATALOG_LOOKUP_LIMIT = 1000;
|
||||
const COUNTERPARTY_CATALOG_CACHE_TTL_MS = 120_000;
|
||||
const PARTY_ANCHOR_STOPWORDS = new Set([
|
||||
"ооо",
|
||||
"ао",
|
||||
@@ -85,6 +87,26 @@ const ACCOUNT_ALIAS_MAP: Record<string, string[]> = {
|
||||
"62": ["покупатель", "покупателями", "расчеты с покупателями"],
|
||||
"76": ["прочие расчеты", "прочими дебиторами и кредиторами"]
|
||||
};
|
||||
const COUNTERPARTY_CATALOG_LOOKUP_QUERY_TEMPLATE = `
|
||||
ВЫБРАТЬ ПЕРВЫЕ __LIMIT__
|
||||
ДАТАВРЕМЯ(2000, 1, 1, 0, 0, 0) КАК Период,
|
||||
ПРЕДСТАВЛЕНИЕ(Контрагенты.Ссылка) КАК Регистратор,
|
||||
"" КАК СчетДт,
|
||||
"" КАК СчетКт,
|
||||
0 КАК Сумма,
|
||||
ПРЕДСТАВЛЕНИЕ(Контрагенты.Ссылка) КАК Контрагент
|
||||
ИЗ
|
||||
Справочник.Контрагенты КАК Контрагенты
|
||||
`;
|
||||
|
||||
interface CounterpartyCatalogResolution {
|
||||
tried: boolean;
|
||||
resolvedValue: string | null;
|
||||
confidence: "high" | "medium" | "low" | null;
|
||||
ambiguityCount: number;
|
||||
}
|
||||
|
||||
let counterpartyCatalogCache: { names: string[]; loadedAt: number } | null = null;
|
||||
|
||||
function parseFiniteNumber(value: unknown): number | null {
|
||||
if (typeof value === "number" && Number.isFinite(value)) {
|
||||
@@ -165,6 +187,28 @@ function tokenizeAnchor(value: string): string[] {
|
||||
.filter((token) => token.length >= 2 && !PARTY_ANCHOR_STOPWORDS.has(token));
|
||||
}
|
||||
|
||||
function anchorTokenVariants(token: string): string[] {
|
||||
const source = String(token ?? "").trim().toLowerCase();
|
||||
if (!source) {
|
||||
return [];
|
||||
}
|
||||
const variants = new Set<string>([source]);
|
||||
if (/^[а-яё]+$/iu.test(source) && source.length >= 4) {
|
||||
const withoutEnding = source.replace(
|
||||
/(?:ами|ями|ого|ему|ому|ыми|ими|иях|ях|ах|ей|ой|ом|ем|ам|ям|ую|юю|ая|яя|ое|ее|ые|ие|ов|ев|ий|ый|ой|е|у|ы|а|я|и|ю)$/iu,
|
||||
""
|
||||
);
|
||||
if (withoutEnding.length >= 3) {
|
||||
variants.add(withoutEnding);
|
||||
}
|
||||
const withoutTrailingVowel = source.replace(/[аеёиоуыэюя]$/iu, "");
|
||||
if (withoutTrailingVowel.length >= 3) {
|
||||
variants.add(withoutTrailingVowel);
|
||||
}
|
||||
}
|
||||
return Array.from(variants);
|
||||
}
|
||||
|
||||
function matchesAnchorText(searchable: string, anchor: string): boolean {
|
||||
const searchableNormalized = normalizeSearchText(searchable);
|
||||
const searchableLatin = transliterateCyrillicToLatin(searchableNormalized);
|
||||
@@ -177,8 +221,11 @@ function matchesAnchorText(searchable: string, anchor: string): boolean {
|
||||
return searchableNormalized.includes(direct) || searchableLatin.includes(transliterateCyrillicToLatin(direct));
|
||||
}
|
||||
return tokens.every((token) => {
|
||||
const tokenLatin = transliterateCyrillicToLatin(token);
|
||||
return searchableNormalized.includes(token) || searchableLatin.includes(tokenLatin);
|
||||
const variants = anchorTokenVariants(token);
|
||||
return variants.some((variant) => {
|
||||
const tokenLatin = transliterateCyrillicToLatin(variant);
|
||||
return searchableNormalized.includes(variant) || searchableLatin.includes(tokenLatin);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@@ -260,6 +307,172 @@ function uniqueStrings(values: string[]): string[] {
|
||||
);
|
||||
}
|
||||
|
||||
function normalizeCounterpartyName(value: string): string {
|
||||
return normalizeSearchText(String(value ?? ""))
|
||||
.replace(/\s+/g, " ")
|
||||
.trim();
|
||||
}
|
||||
|
||||
function extractCounterpartyCatalogNames(rows: Array<Record<string, unknown>>): string[] {
|
||||
return uniqueStrings(
|
||||
rows
|
||||
.map((row) => {
|
||||
const direct =
|
||||
valueAsString(row.Контрагент ?? row.counterparty ?? row.Counterparty).trim() ||
|
||||
valueAsString(row.Регистратор ?? row.registrator ?? row.Registrator).trim();
|
||||
return direct;
|
||||
})
|
||||
.map((value) => value.trim())
|
||||
.filter((value) => value.length >= 2)
|
||||
);
|
||||
}
|
||||
|
||||
function scoreCounterpartyCandidate(name: string, anchor: string): number | null {
|
||||
if (!matchesAnchorText(name, anchor)) {
|
||||
return null;
|
||||
}
|
||||
const normalizedName = normalizeCounterpartyName(name);
|
||||
const normalizedAnchor = normalizeCounterpartyName(anchor);
|
||||
if (!normalizedName || !normalizedAnchor) {
|
||||
return null;
|
||||
}
|
||||
|
||||
let score = 0;
|
||||
if (normalizedName === normalizedAnchor) {
|
||||
score += 10_000;
|
||||
} else if (normalizedName.includes(normalizedAnchor)) {
|
||||
score += 5_000;
|
||||
} else if (normalizedAnchor.includes(normalizedName) && normalizedName.length >= 4) {
|
||||
score += 2_000;
|
||||
}
|
||||
|
||||
const anchorTokens = tokenizeAnchor(anchor);
|
||||
for (const token of anchorTokens) {
|
||||
const variants = anchorTokenVariants(token);
|
||||
let tokenScore = 0;
|
||||
for (const variant of variants) {
|
||||
if (normalizedName.includes(variant)) {
|
||||
tokenScore = Math.max(tokenScore, Math.max(2, variant.length) * 20);
|
||||
}
|
||||
}
|
||||
if (tokenScore === 0) {
|
||||
return null;
|
||||
}
|
||||
score += tokenScore;
|
||||
}
|
||||
|
||||
const lengthPenalty = Math.abs(normalizedName.length - normalizedAnchor.length);
|
||||
score -= lengthPenalty;
|
||||
return score;
|
||||
}
|
||||
|
||||
function shouldAttemptCounterpartyCatalogResolution(intent: AddressIntent, filters: AddressFilterSet): boolean {
|
||||
const counterparty = typeof filters.counterparty === "string" ? filters.counterparty.trim() : "";
|
||||
if (!counterparty || isLikelyLowQualityPartyAnchor(counterparty)) {
|
||||
return false;
|
||||
}
|
||||
return (
|
||||
intent === "list_contracts_by_counterparty" ||
|
||||
intent === "list_documents_by_counterparty" ||
|
||||
intent === "bank_operations_by_counterparty" ||
|
||||
intent === "open_items_by_counterparty_or_contract" ||
|
||||
intent === "list_payables_counterparties" ||
|
||||
intent === "list_receivables_counterparties"
|
||||
);
|
||||
}
|
||||
|
||||
async function resolveCounterpartyViaCatalog(anchorRaw: string): Promise<CounterpartyCatalogResolution> {
|
||||
const requested = String(anchorRaw ?? "").trim();
|
||||
if (!requested || isLikelyLowQualityPartyAnchor(requested)) {
|
||||
return {
|
||||
tried: false,
|
||||
resolvedValue: null,
|
||||
confidence: null,
|
||||
ambiguityCount: 0
|
||||
};
|
||||
}
|
||||
|
||||
const now = Date.now();
|
||||
const cacheFresh =
|
||||
counterpartyCatalogCache !== null && now - counterpartyCatalogCache.loadedAt <= COUNTERPARTY_CATALOG_CACHE_TTL_MS;
|
||||
let names: string[] = cacheFresh ? [...counterpartyCatalogCache!.names] : [];
|
||||
|
||||
if (!cacheFresh) {
|
||||
const mcp = await executeAddressMcpQuery({
|
||||
query: COUNTERPARTY_CATALOG_LOOKUP_QUERY_TEMPLATE.replaceAll("__LIMIT__", String(COUNTERPARTY_CATALOG_LOOKUP_LIMIT)),
|
||||
limit: COUNTERPARTY_CATALOG_LOOKUP_LIMIT
|
||||
});
|
||||
if (!mcp.error) {
|
||||
names = extractCounterpartyCatalogNames(mcp.raw_rows);
|
||||
if (names.length > 0) {
|
||||
counterpartyCatalogCache = {
|
||||
names: [...names],
|
||||
loadedAt: now
|
||||
};
|
||||
}
|
||||
} else if (counterpartyCatalogCache && counterpartyCatalogCache.names.length > 0) {
|
||||
names = [...counterpartyCatalogCache.names];
|
||||
} else {
|
||||
return {
|
||||
tried: true,
|
||||
resolvedValue: null,
|
||||
confidence: null,
|
||||
ambiguityCount: 0
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
if (names.length === 0) {
|
||||
return {
|
||||
tried: true,
|
||||
resolvedValue: null,
|
||||
confidence: null,
|
||||
ambiguityCount: 0
|
||||
};
|
||||
}
|
||||
|
||||
const scored = names
|
||||
.map((name) => {
|
||||
const score = scoreCounterpartyCandidate(name, requested);
|
||||
return score === null ? null : { name, score };
|
||||
})
|
||||
.filter((item): item is { name: string; score: number } => Boolean(item))
|
||||
.sort((a, b) => b.score - a.score || a.name.length - b.name.length || a.name.localeCompare(b.name, "ru"));
|
||||
|
||||
if (scored.length === 0) {
|
||||
return {
|
||||
tried: true,
|
||||
resolvedValue: null,
|
||||
confidence: null,
|
||||
ambiguityCount: 0
|
||||
};
|
||||
}
|
||||
|
||||
const topScore = scored[0].score;
|
||||
const topCandidates = scored.filter((item) => item.score === topScore);
|
||||
const bestCandidate = topCandidates[0];
|
||||
const normalizedRequested = normalizeCounterpartyName(requested);
|
||||
const normalizedBest = normalizeCounterpartyName(bestCandidate.name);
|
||||
const isExact = normalizedBest === normalizedRequested;
|
||||
const isStrongContains = normalizedBest.includes(normalizedRequested);
|
||||
|
||||
if (topCandidates.length > 1 && !isExact && !isStrongContains) {
|
||||
return {
|
||||
tried: true,
|
||||
resolvedValue: null,
|
||||
confidence: "low",
|
||||
ambiguityCount: topCandidates.length - 1
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
tried: true,
|
||||
resolvedValue: bestCandidate.name,
|
||||
confidence: isExact ? "high" : isStrongContains ? "medium" : topCandidates.length === 1 ? "medium" : "low",
|
||||
ambiguityCount: topCandidates.length - 1
|
||||
};
|
||||
}
|
||||
|
||||
function collectAnalyticsStrings(row: Record<string, unknown>): string[] {
|
||||
const fixedKeys = [
|
||||
"СубконтоДт1",
|
||||
@@ -466,10 +679,15 @@ function canAutoBroadenPeriodWindow(intent: AddressIntent, filters: AddressFilte
|
||||
);
|
||||
}
|
||||
|
||||
function invertSort(sort: AddressFilterSet["sort"]): AddressFilterSet["sort"] {
|
||||
return sort === "period_asc" ? "period_desc" : "period_asc";
|
||||
}
|
||||
|
||||
function isAnchorRecoveryIntent(intent: AddressIntent): boolean {
|
||||
return (
|
||||
intent === "list_documents_by_counterparty" ||
|
||||
intent === "bank_operations_by_counterparty" ||
|
||||
intent === "list_contracts_by_counterparty" ||
|
||||
intent === "list_documents_by_contract" ||
|
||||
intent === "bank_operations_by_contract" ||
|
||||
intent === "open_items_by_counterparty_or_contract" ||
|
||||
@@ -923,6 +1141,45 @@ export class AddressQueryService {
|
||||
});
|
||||
}
|
||||
|
||||
const rawCounterpartyAnchor =
|
||||
typeof filters.extracted_filters.counterparty === "string" ? filters.extracted_filters.counterparty.trim() : "";
|
||||
if (shouldAttemptCounterpartyCatalogResolution(intent.intent, filters.extracted_filters)) {
|
||||
const catalogResolution = await resolveCounterpartyViaCatalog(rawCounterpartyAnchor);
|
||||
if (catalogResolution.resolvedValue) {
|
||||
if (normalizeCounterpartyName(rawCounterpartyAnchor) !== normalizeCounterpartyName(catalogResolution.resolvedValue)) {
|
||||
filters.warnings.push("counterparty_anchor_resolved_via_catalog_lookup");
|
||||
}
|
||||
} else if (catalogResolution.tried) {
|
||||
filters.warnings.push(
|
||||
catalogResolution.ambiguityCount > 0
|
||||
? "counterparty_anchor_catalog_lookup_ambiguous"
|
||||
: "counterparty_anchor_catalog_lookup_no_match"
|
||||
);
|
||||
}
|
||||
|
||||
anchor = resolvePrimaryAnchor(intent.intent, filters.extracted_filters);
|
||||
if (anchor.anchor_type === "counterparty") {
|
||||
anchor = {
|
||||
...anchor,
|
||||
anchor_value_raw: rawCounterpartyAnchor || anchor.anchor_value_raw
|
||||
};
|
||||
if (catalogResolution.resolvedValue) {
|
||||
anchor = {
|
||||
...anchor,
|
||||
anchor_value_resolved: catalogResolution.resolvedValue,
|
||||
resolver_confidence: catalogResolution.confidence ?? anchor.resolver_confidence,
|
||||
ambiguity_count: Math.max(anchor.ambiguity_count, catalogResolution.ambiguityCount)
|
||||
};
|
||||
} else if (catalogResolution.ambiguityCount > 0) {
|
||||
anchor = {
|
||||
...anchor,
|
||||
resolver_confidence: "low",
|
||||
ambiguity_count: Math.max(anchor.ambiguity_count, catalogResolution.ambiguityCount)
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const plan = buildAddressRecipePlan(recipeSelection.selected_recipe, filters.extracted_filters);
|
||||
const mcp = await executeAddressMcpQuery({
|
||||
query: plan.query,
|
||||
@@ -1013,7 +1270,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);
|
||||
const factual = composeFactualReply(intent.intent, recoveredRows, { userMessage });
|
||||
const recoveryReason =
|
||||
recoveredBankRows.length > 0
|
||||
? "contract_docs_recovered_via_bank_fallback"
|
||||
@@ -1132,7 +1389,7 @@ export class AddressQueryService {
|
||||
rowsAnchorMatched: expandedRowsByAnchor.length,
|
||||
rowsMatched: expandedFilteredRows.length
|
||||
});
|
||||
const expandedFactual = composeFactualReply(intent.intent, expandedFilteredRows);
|
||||
const expandedFactual = composeFactualReply(intent.intent, expandedFilteredRows, { userMessage });
|
||||
const expandedPrefix = `Период сохранен. Глубина live-выборки автоматически расширена до ${expandedPlan.limit} строк.`;
|
||||
const expandedLimitations = [...filters.warnings, "query_limit_auto_expanded_for_anchor_recovery"];
|
||||
const expandedReasons = [...baseReasons, "query_limit_auto_expanded_for_anchor_recovery"];
|
||||
@@ -1241,7 +1498,7 @@ export class AddressQueryService {
|
||||
});
|
||||
const observedWindow = deriveObservedPeriodWindow(broadenedFilteredRows);
|
||||
const broadenedPrefix = composeAutoBroadenedPeriodPrefix(filters.extracted_filters, observedWindow);
|
||||
const broadenedFactual = composeFactualReply(intent.intent, broadenedFilteredRows);
|
||||
const broadenedFactual = composeFactualReply(intent.intent, broadenedFilteredRows, { userMessage });
|
||||
const broadenedLimitations = [...filters.warnings, "period_window_auto_broadened_to_available_data"];
|
||||
const broadenedReasons = [...baseReasons, "period_window_auto_broadened_to_available_data"];
|
||||
return {
|
||||
@@ -1295,15 +1552,133 @@ export class AddressQueryService {
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
filteredRows.length === 0 &&
|
||||
isDocumentOrBankAnchorIntent(intent.intent) &&
|
||||
!hasExplicitPeriodWindow(filters.extracted_filters) &&
|
||||
(anchor.anchor_type === "counterparty" || anchor.anchor_type === "contract")
|
||||
) {
|
||||
const currentLimit =
|
||||
typeof filters.extracted_filters.limit === "number" && Number.isFinite(filters.extracted_filters.limit)
|
||||
? Math.max(1, Math.trunc(filters.extracted_filters.limit))
|
||||
: plan.limit;
|
||||
const historicalFilters: AddressFilterSet = {
|
||||
...filters.extracted_filters,
|
||||
sort: invertSort(filters.extracted_filters.sort),
|
||||
limit: Math.max(currentLimit, ADDRESS_ANCHOR_RECOVERY_LIMIT)
|
||||
};
|
||||
const historicalSelection = selectAddressRecipe(intent.intent, historicalFilters);
|
||||
if (historicalSelection.selected_recipe && historicalSelection.missing_required_filters.length === 0) {
|
||||
const historicalPlan = buildAddressRecipePlan(historicalSelection.selected_recipe, historicalFilters);
|
||||
const historicalMcp = await executeAddressMcpQuery({
|
||||
query: historicalPlan.query,
|
||||
limit: historicalPlan.limit
|
||||
});
|
||||
if (!historicalMcp.error) {
|
||||
const historicalRawRows = toNormalizedRows(historicalMcp.raw_rows);
|
||||
const historicalScopedRows = applyAccountScopeFilter(historicalRawRows, historicalPlan.account_scope);
|
||||
const historicalAccountScopeFallbackApplied =
|
||||
historicalPlan.account_scope_mode === "preferred" &&
|
||||
historicalPlan.account_scope.length > 0 &&
|
||||
historicalRawRows.length > 0 &&
|
||||
historicalScopedRows.length === 0;
|
||||
const historicalNormalizedRows = historicalAccountScopeFallbackApplied ? historicalRawRows : historicalScopedRows;
|
||||
let historicalAnchor = resolvePrimaryAnchor(intent.intent, historicalFilters);
|
||||
historicalAnchor = refineAnchorFromRows(historicalAnchor, historicalNormalizedRows);
|
||||
const historicalFiltersForMatching: AddressFilterSet =
|
||||
historicalAnchor.anchor_type === "counterparty" && historicalAnchor.anchor_value_resolved
|
||||
? { ...historicalFilters, counterparty: historicalAnchor.anchor_value_resolved }
|
||||
: historicalAnchor.anchor_type === "contract" && historicalAnchor.anchor_value_resolved
|
||||
? { ...historicalFilters, contract: historicalAnchor.anchor_value_resolved }
|
||||
: historicalFilters;
|
||||
const historicalAccountScopeAudit = buildAccountScopeAudit({
|
||||
intent: intent.intent,
|
||||
filters: historicalFiltersForMatching,
|
||||
accountScope: historicalPlan.account_scope,
|
||||
rowsBeforeScope: historicalRawRows.length,
|
||||
rowsAfterScope: historicalNormalizedRows.length
|
||||
});
|
||||
const historicalAnchorFilter = applyAddressFilters(historicalNormalizedRows, historicalFiltersForMatching);
|
||||
const historicalRowsByAnchor = historicalAnchorFilter.rows;
|
||||
const historicalFilteredRows = applyIntentSpecificFilter(intent.intent, historicalRowsByAnchor);
|
||||
if (historicalFilteredRows.length > 0) {
|
||||
const historicalRowDiagnostics = deriveRowStageDiagnostics(
|
||||
historicalMcp.raw_rows,
|
||||
historicalNormalizedRows.length,
|
||||
historicalNormalizedRows.length
|
||||
);
|
||||
const historicalStageStatus = deriveMcpStageStatus({
|
||||
rawRowsReceived: historicalMcp.raw_rows.length,
|
||||
rowsMaterialized: historicalNormalizedRows.length,
|
||||
rowsAnchorMatched: historicalRowsByAnchor.length,
|
||||
rowsMatched: historicalFilteredRows.length
|
||||
});
|
||||
const historicalFactual = composeFactualReply(intent.intent, historicalFilteredRows, { userMessage });
|
||||
const historicalPrefix =
|
||||
"В последних доступных записях якорь не подтвердился; показаны найденные строки по историческому окну.";
|
||||
const historicalLimitations = [...filters.warnings, "historical_window_sort_recovery_applied"];
|
||||
const historicalReasons = [...baseReasons, "historical_window_sort_recovery_applied"];
|
||||
return {
|
||||
handled: true,
|
||||
reply_text: `${historicalPrefix}\n${historicalFactual.text}`,
|
||||
reply_type: inferReplyType(historicalFactual.responseType),
|
||||
response_type: historicalFactual.responseType,
|
||||
debug: {
|
||||
detected_mode: mode.mode,
|
||||
detected_mode_confidence: mode.confidence,
|
||||
query_shape: shape.shape,
|
||||
query_shape_confidence: shape.confidence,
|
||||
detected_intent: intent.intent,
|
||||
detected_intent_confidence: intent.confidence,
|
||||
extracted_filters: filters.extracted_filters,
|
||||
missing_required_filters: [],
|
||||
selected_recipe: historicalSelection.selected_recipe.recipe_id,
|
||||
mcp_call_status_legacy: toLegacyMcpStatus(historicalStageStatus),
|
||||
account_scope_mode: historicalPlan.account_scope_mode,
|
||||
account_scope_fallback_applied: historicalAccountScopeFallbackApplied,
|
||||
anchor_type: historicalAnchor.anchor_type,
|
||||
anchor_value_raw: historicalAnchor.anchor_value_raw,
|
||||
anchor_value_resolved: historicalAnchor.anchor_value_resolved,
|
||||
resolver_confidence: historicalAnchor.resolver_confidence,
|
||||
ambiguity_count: historicalAnchor.ambiguity_count,
|
||||
match_failure_stage: "none",
|
||||
match_failure_reason: null,
|
||||
mcp_call_status: historicalStageStatus,
|
||||
rows_fetched: historicalMcp.fetched_rows,
|
||||
raw_rows_received: historicalMcp.raw_rows.length,
|
||||
rows_after_account_scope: historicalNormalizedRows.length,
|
||||
rows_after_recipe_filter: historicalRowsByAnchor.length,
|
||||
rows_materialized: historicalNormalizedRows.length,
|
||||
rows_matched: historicalFilteredRows.length,
|
||||
raw_row_keys_sample: historicalRowDiagnostics.rawRowKeysSample,
|
||||
materialization_drop_reason: historicalRowDiagnostics.materializationDropReason,
|
||||
account_token_raw: historicalAccountScopeAudit.accountTokenRaw,
|
||||
account_token_normalized: historicalAccountScopeAudit.accountTokenNormalized,
|
||||
account_scope_fields_checked: historicalAccountScopeAudit.accountScopeFieldsChecked,
|
||||
account_scope_match_strategy: historicalAccountScopeAudit.accountScopeMatchStrategy,
|
||||
account_scope_drop_reason: historicalAccountScopeAudit.accountScopeDropReason,
|
||||
runtime_readiness: "LIVE_QUERYABLE_WITH_LIMITS",
|
||||
limited_reason_category: null,
|
||||
response_type: historicalFactual.responseType,
|
||||
limitations: historicalLimitations,
|
||||
reasons: historicalReasons
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
filteredRows.length === 0 &&
|
||||
isDocumentOrBankAnchorIntent(intent.intent) &&
|
||||
normalizedRows.length > 0 &&
|
||||
filterByAnchors.length > 0 &&
|
||||
(stageStatus === "materialized_but_not_anchor_matched" || stageStatus === "materialized_but_filtered_out_by_recipe")
|
||||
) {
|
||||
const documentBankFallbackRows = applyIntentSpecificFilter(intent.intent, normalizedRows);
|
||||
if (documentBankFallbackRows.length > 0) {
|
||||
const fallbackFactual = composeFactualReply(intent.intent, documentBankFallbackRows);
|
||||
const fallbackFactual = composeFactualReply(intent.intent, documentBankFallbackRows, { userMessage });
|
||||
const fallbackLimitations = [...filters.warnings, "anchor_not_matched_fallback_rows"];
|
||||
const fallbackReasons = [...baseReasons, "anchor_not_matched_fallback_rows"];
|
||||
return {
|
||||
@@ -1446,7 +1821,7 @@ export class AddressQueryService {
|
||||
});
|
||||
}
|
||||
|
||||
const factual = composeFactualReply(intent.intent, filteredRows);
|
||||
const factual = composeFactualReply(intent.intent, filteredRows, { userMessage });
|
||||
return {
|
||||
handled: true,
|
||||
reply_text: factual.text,
|
||||
|
||||
Reference in New Issue
Block a user