АДРЕСНЫЙ РЕЖИМ - M2.3c тюнинг резолвинга и фильтров адресных запросов, поэтапная диагностика и аудит фильтрации по счету
This commit is contained in:
@@ -58,22 +58,38 @@ function parseRowsFromTextTable(source: string): Array<Record<string, unknown>>
|
||||
}
|
||||
|
||||
const rows: Array<Record<string, unknown>> = [];
|
||||
const parseCsvLine = (line: string): string[] => {
|
||||
const values: string[] = [];
|
||||
let current = "";
|
||||
let inQuotes = false;
|
||||
for (let index = 0; index < line.length; index += 1) {
|
||||
const char = line[index];
|
||||
if (char === '"') {
|
||||
if (inQuotes && line[index + 1] === '"') {
|
||||
current += '"';
|
||||
index += 1;
|
||||
continue;
|
||||
}
|
||||
inQuotes = !inQuotes;
|
||||
continue;
|
||||
}
|
||||
if (char === "," && !inQuotes) {
|
||||
values.push(current.trim());
|
||||
current = "";
|
||||
continue;
|
||||
}
|
||||
current += char;
|
||||
}
|
||||
values.push(current.trim());
|
||||
return values;
|
||||
};
|
||||
const lines = body
|
||||
.split("\n")
|
||||
.map((line) => line.trim())
|
||||
.filter(Boolean);
|
||||
|
||||
for (const line of lines) {
|
||||
const values: string[] = [];
|
||||
const matcher = /"([^"]*)"|([^,]+)/g;
|
||||
let match: RegExpExecArray | null = null;
|
||||
while ((match = matcher.exec(line)) !== null) {
|
||||
const raw = match[1] !== undefined ? match[1] : match[2];
|
||||
const value = String(raw ?? "").trim();
|
||||
if (value.length > 0) {
|
||||
values.push(value);
|
||||
}
|
||||
}
|
||||
const values = parseCsvLine(line);
|
||||
|
||||
if (values.length === 0) {
|
||||
continue;
|
||||
|
||||
@@ -7,6 +7,7 @@ import type {
|
||||
AddressFilterSet,
|
||||
AddressIntent,
|
||||
AddressLimitedReasonCategory,
|
||||
AddressMatchFailureStage,
|
||||
AddressMcpCallStatus,
|
||||
AddressQueryShapeDetection,
|
||||
AddressResponseType,
|
||||
@@ -28,6 +29,30 @@ interface NormalizedAddressRow {
|
||||
analytics: string[];
|
||||
}
|
||||
|
||||
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 PARTY_ANCHOR_STOPWORDS = new Set([
|
||||
"ооо",
|
||||
"ао",
|
||||
"зао",
|
||||
"ип",
|
||||
"llc",
|
||||
"ltd",
|
||||
"company",
|
||||
"компания",
|
||||
"контрагент",
|
||||
"counterparty",
|
||||
"по",
|
||||
"by"
|
||||
]);
|
||||
const ACCOUNT_ALIAS_MAP: Record<string, string[]> = {
|
||||
"51": ["расчетный счет", "расчетные счета", "bank account"],
|
||||
"52": ["валютный счет", "валютные счета", "currency account"],
|
||||
"60": ["поставщик", "поставщиками", "подрядчиками", "расчеты с поставщиками"],
|
||||
"62": ["покупатель", "покупателями", "расчеты с покупателями"],
|
||||
"76": ["прочие расчеты", "прочими дебиторами и кредиторами"]
|
||||
};
|
||||
|
||||
function parseFiniteNumber(value: unknown): number | null {
|
||||
if (typeof value === "number" && Number.isFinite(value)) {
|
||||
return value;
|
||||
@@ -48,12 +73,124 @@ function valueAsString(value: unknown): string {
|
||||
return String(value);
|
||||
}
|
||||
|
||||
function normalizeToken(value: string): string {
|
||||
return String(value ?? "").trim().toLowerCase();
|
||||
function transliterateCyrillicToLatin(value: string): string {
|
||||
const map: Record<string, string> = {
|
||||
а: "a",
|
||||
б: "b",
|
||||
в: "v",
|
||||
г: "g",
|
||||
д: "d",
|
||||
е: "e",
|
||||
ё: "e",
|
||||
ж: "zh",
|
||||
з: "z",
|
||||
и: "i",
|
||||
й: "y",
|
||||
к: "k",
|
||||
л: "l",
|
||||
м: "m",
|
||||
н: "n",
|
||||
о: "o",
|
||||
п: "p",
|
||||
р: "r",
|
||||
с: "s",
|
||||
т: "t",
|
||||
у: "u",
|
||||
ф: "f",
|
||||
х: "h",
|
||||
ц: "ts",
|
||||
ч: "ch",
|
||||
ш: "sh",
|
||||
щ: "sch",
|
||||
ъ: "",
|
||||
ы: "y",
|
||||
ь: "",
|
||||
э: "e",
|
||||
ю: "yu",
|
||||
я: "ya"
|
||||
};
|
||||
let out = "";
|
||||
for (const char of String(value ?? "").toLowerCase()) {
|
||||
out += map[char] ?? char;
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
function escapeRegExp(value: string): string {
|
||||
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
||||
function normalizeSearchText(value: string): string {
|
||||
return String(value ?? "")
|
||||
.toLowerCase()
|
||||
.replace(/ё/g, "е")
|
||||
.replace(/[^a-zа-я0-9]+/gi, " ")
|
||||
.replace(/\s+/g, " ")
|
||||
.trim();
|
||||
}
|
||||
|
||||
function tokenizeAnchor(value: string): string[] {
|
||||
return normalizeSearchText(value)
|
||||
.split(" ")
|
||||
.map((token) => token.trim())
|
||||
.filter((token) => token.length >= 2 && !PARTY_ANCHOR_STOPWORDS.has(token));
|
||||
}
|
||||
|
||||
function matchesAnchorText(searchable: string, anchor: string): boolean {
|
||||
const searchableNormalized = normalizeSearchText(searchable);
|
||||
const searchableLatin = transliterateCyrillicToLatin(searchableNormalized);
|
||||
const tokens = tokenizeAnchor(anchor);
|
||||
if (tokens.length === 0) {
|
||||
const direct = normalizeSearchText(anchor);
|
||||
if (!direct) {
|
||||
return false;
|
||||
}
|
||||
return searchableNormalized.includes(direct) || searchableLatin.includes(transliterateCyrillicToLatin(direct));
|
||||
}
|
||||
return tokens.every((token) => {
|
||||
const tokenLatin = transliterateCyrillicToLatin(token);
|
||||
return searchableNormalized.includes(token) || searchableLatin.includes(tokenLatin);
|
||||
});
|
||||
}
|
||||
|
||||
function normalizeAccountToken(value: string): string {
|
||||
const source = String(value ?? "").trim().replace(",", ".");
|
||||
const match = source.match(/(\d{2})(?:\.(\d{1,2}))?/);
|
||||
if (!match) {
|
||||
return source.toLowerCase();
|
||||
}
|
||||
const base = match[1];
|
||||
if (!match[2]) {
|
||||
return base;
|
||||
}
|
||||
const sub = String(Number(match[2]));
|
||||
return `${base}.${sub}`;
|
||||
}
|
||||
|
||||
function extractAccountTokens(searchable: string): string[] {
|
||||
const result: string[] = [];
|
||||
const matcher = /\b(\d{2})(?:[.,](\d{1,2}))?\b/g;
|
||||
let hit: RegExpExecArray | null = null;
|
||||
while ((hit = matcher.exec(searchable)) !== null) {
|
||||
const base = hit[1];
|
||||
const sub = hit[2] ? String(Number(hit[2])) : null;
|
||||
result.push(sub ? `${base}.${sub}` : base);
|
||||
}
|
||||
return uniqueStrings(result);
|
||||
}
|
||||
|
||||
function accountTokenMatches(requestedToken: string, candidateToken: string): boolean {
|
||||
const requested = normalizeAccountToken(requestedToken);
|
||||
const candidate = normalizeAccountToken(candidateToken);
|
||||
if (requested === candidate) {
|
||||
return true;
|
||||
}
|
||||
if (!requested.includes(".")) {
|
||||
return candidate.startsWith(`${requested}.`) || candidate === requested;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function baseAccountCode(value: string): string | null {
|
||||
const normalized = normalizeAccountToken(value);
|
||||
const match = normalized.match(/^(\d{2})/);
|
||||
return match ? match[1] : null;
|
||||
}
|
||||
|
||||
function uniqueStrings(values: string[]): string[] {
|
||||
@@ -147,13 +284,27 @@ function rowMatchesAnyAccount(row: NormalizedAddressRow, accountScope: string[])
|
||||
return true;
|
||||
}
|
||||
const searchable = [row.account_dt ?? "", row.account_kt ?? "", row.registrator, ...row.analytics].join(" ");
|
||||
const extractedTokens = extractAccountTokens(searchable);
|
||||
const normalizedSearch = normalizeSearchText(searchable);
|
||||
const translitSearch = transliterateCyrillicToLatin(normalizedSearch);
|
||||
return accountScope.some((account) => {
|
||||
const normalized = String(account ?? "").trim();
|
||||
if (!normalized) {
|
||||
const normalizedRequested = normalizeAccountToken(String(account ?? "").trim());
|
||||
if (!normalizedRequested) {
|
||||
return false;
|
||||
}
|
||||
const matcher = new RegExp(`\\b${escapeRegExp(normalized)}(?:\\.\\d{1,2})?\\b`, "i");
|
||||
return matcher.test(searchable);
|
||||
if (extractedTokens.some((candidate) => accountTokenMatches(normalizedRequested, candidate))) {
|
||||
return true;
|
||||
}
|
||||
const base = baseAccountCode(normalizedRequested);
|
||||
if (!base) {
|
||||
return false;
|
||||
}
|
||||
const aliases = ACCOUNT_ALIAS_MAP[base] ?? [];
|
||||
return aliases.some((alias) => {
|
||||
const normalizedAlias = normalizeSearchText(alias);
|
||||
const aliasLatin = transliterateCyrillicToLatin(normalizedAlias);
|
||||
return normalizedSearch.includes(normalizedAlias) || translitSearch.includes(aliasLatin);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@@ -164,30 +315,55 @@ function applyAccountScopeFilter(rows: NormalizedAddressRow[], accountScope: str
|
||||
return rows.filter((row) => rowMatchesAnyAccount(row, accountScope));
|
||||
}
|
||||
|
||||
function applyAddressFilters(rows: NormalizedAddressRow[], filters: AddressFilterSet): NormalizedAddressRow[] {
|
||||
interface AnchorFilterResult {
|
||||
rows: NormalizedAddressRow[];
|
||||
mismatchReason: string | null;
|
||||
}
|
||||
|
||||
function applyAddressFilters(rows: NormalizedAddressRow[], filters: AddressFilterSet): AnchorFilterResult {
|
||||
let filtered = [...rows];
|
||||
let mismatchReason: string | null = null;
|
||||
|
||||
if (filters.account && String(filters.account).trim()) {
|
||||
const scopedAccount = String(filters.account).trim();
|
||||
const before = filtered.length;
|
||||
filtered = filtered.filter((row) => rowMatchesAnyAccount(row, [scopedAccount]));
|
||||
if (before > 0 && filtered.length === 0 && mismatchReason === null) {
|
||||
mismatchReason = "account_anchor_not_matched_in_materialized_rows";
|
||||
}
|
||||
}
|
||||
|
||||
if (filters.counterparty && String(filters.counterparty).trim()) {
|
||||
const needle = normalizeToken(String(filters.counterparty));
|
||||
filtered = filtered.filter((row) => rowSearchableText(row).includes(needle));
|
||||
const needle = String(filters.counterparty);
|
||||
const before = filtered.length;
|
||||
filtered = filtered.filter((row) => matchesAnchorText(rowSearchableText(row), needle));
|
||||
if (before > 0 && filtered.length === 0 && mismatchReason === null) {
|
||||
mismatchReason = "counterparty_anchor_not_matched_in_materialized_rows";
|
||||
}
|
||||
}
|
||||
|
||||
if (filters.contract && String(filters.contract).trim()) {
|
||||
const needle = normalizeToken(String(filters.contract));
|
||||
filtered = filtered.filter((row) => rowSearchableText(row).includes(needle));
|
||||
const needle = String(filters.contract);
|
||||
const before = filtered.length;
|
||||
filtered = filtered.filter((row) => matchesAnchorText(rowSearchableText(row), needle));
|
||||
if (before > 0 && filtered.length === 0 && mismatchReason === null) {
|
||||
mismatchReason = "contract_anchor_not_matched_in_materialized_rows";
|
||||
}
|
||||
}
|
||||
|
||||
if (filters.document_ref && String(filters.document_ref).trim()) {
|
||||
const needle = normalizeToken(String(filters.document_ref));
|
||||
filtered = filtered.filter((row) => rowSearchableText(row).includes(needle));
|
||||
const needle = String(filters.document_ref);
|
||||
const before = filtered.length;
|
||||
filtered = filtered.filter((row) => matchesAnchorText(rowSearchableText(row), needle));
|
||||
if (before > 0 && filtered.length === 0 && mismatchReason === null) {
|
||||
mismatchReason = "document_ref_anchor_not_matched_in_materialized_rows";
|
||||
}
|
||||
}
|
||||
|
||||
return filtered;
|
||||
return {
|
||||
rows: filtered,
|
||||
mismatchReason
|
||||
};
|
||||
}
|
||||
|
||||
function applyIntentSpecificFilter(intent: AddressIntent, rows: NormalizedAddressRow[]): NormalizedAddressRow[] {
|
||||
@@ -261,6 +437,18 @@ interface RowStageDiagnostics {
|
||||
| "unknown_row_shape";
|
||||
}
|
||||
|
||||
interface AccountScopeAuditDebug {
|
||||
accountTokenRaw: string | null;
|
||||
accountTokenNormalized: string | null;
|
||||
accountScopeFieldsChecked: string[];
|
||||
accountScopeMatchStrategy: "account_code_regex_plus_alias_map_v1";
|
||||
accountScopeDropReason:
|
||||
| "not_applicable"
|
||||
| "no_account_scope_requested"
|
||||
| "no_rows_after_scope_filter"
|
||||
| "rows_remaining_after_scope_filter";
|
||||
}
|
||||
|
||||
function rowHasNonEmptyField(row: Record<string, unknown>, keys: string[]): boolean {
|
||||
return keys.some((key) => String(row[key] ?? "").trim().length > 0);
|
||||
}
|
||||
@@ -303,11 +491,63 @@ function deriveRowStageDiagnostics(
|
||||
return { rawRowKeysSample, materializationDropReason: "unknown_row_shape" };
|
||||
}
|
||||
|
||||
function isAccountIntent(intent: AddressIntent): boolean {
|
||||
return intent === "account_balance_snapshot" || intent === "documents_forming_balance";
|
||||
}
|
||||
|
||||
function buildDefaultAccountScopeAudit(filters: AddressFilterSet): AccountScopeAuditDebug {
|
||||
const tokenRaw = typeof filters.account === "string" && filters.account.trim().length > 0 ? filters.account.trim() : null;
|
||||
return {
|
||||
accountTokenRaw: tokenRaw,
|
||||
accountTokenNormalized: tokenRaw ? normalizeAccountToken(tokenRaw) : null,
|
||||
accountScopeFieldsChecked: [...ACCOUNT_SCOPE_FIELDS_CHECKED],
|
||||
accountScopeMatchStrategy: ACCOUNT_SCOPE_MATCH_STRATEGY,
|
||||
accountScopeDropReason: "not_applicable"
|
||||
};
|
||||
}
|
||||
|
||||
function buildAccountScopeAudit(input: {
|
||||
intent: AddressIntent;
|
||||
filters: AddressFilterSet;
|
||||
accountScope: string[];
|
||||
rowsBeforeScope: number;
|
||||
rowsAfterScope: number;
|
||||
}): AccountScopeAuditDebug {
|
||||
const tokenRaw = typeof input.filters.account === "string" && input.filters.account.trim().length > 0 ? input.filters.account.trim() : null;
|
||||
const tokenNormalized = tokenRaw ? normalizeAccountToken(tokenRaw) : null;
|
||||
if (!isAccountIntent(input.intent)) {
|
||||
return {
|
||||
accountTokenRaw: tokenRaw,
|
||||
accountTokenNormalized: tokenNormalized,
|
||||
accountScopeFieldsChecked: [...ACCOUNT_SCOPE_FIELDS_CHECKED],
|
||||
accountScopeMatchStrategy: ACCOUNT_SCOPE_MATCH_STRATEGY,
|
||||
accountScopeDropReason: "not_applicable"
|
||||
};
|
||||
}
|
||||
if (input.accountScope.length === 0) {
|
||||
return {
|
||||
accountTokenRaw: tokenRaw,
|
||||
accountTokenNormalized: tokenNormalized,
|
||||
accountScopeFieldsChecked: [...ACCOUNT_SCOPE_FIELDS_CHECKED],
|
||||
accountScopeMatchStrategy: ACCOUNT_SCOPE_MATCH_STRATEGY,
|
||||
accountScopeDropReason: "no_account_scope_requested"
|
||||
};
|
||||
}
|
||||
return {
|
||||
accountTokenRaw: tokenRaw,
|
||||
accountTokenNormalized: tokenNormalized,
|
||||
accountScopeFieldsChecked: [...ACCOUNT_SCOPE_FIELDS_CHECKED],
|
||||
accountScopeMatchStrategy: ACCOUNT_SCOPE_MATCH_STRATEGY,
|
||||
accountScopeDropReason: input.rowsBeforeScope > 0 && input.rowsAfterScope === 0 ? "no_rows_after_scope_filter" : "rows_remaining_after_scope_filter"
|
||||
};
|
||||
}
|
||||
|
||||
function deriveMcpStageStatus(input: {
|
||||
skipped?: boolean;
|
||||
errored?: boolean;
|
||||
rawRowsReceived: number;
|
||||
rowsMaterialized: number;
|
||||
rowsAnchorMatched: number;
|
||||
rowsMatched: number;
|
||||
}): AddressMcpCallStatus {
|
||||
if (input.skipped) {
|
||||
@@ -322,12 +562,24 @@ function deriveMcpStageStatus(input: {
|
||||
if (input.rowsMaterialized === 0) {
|
||||
return "raw_rows_received_but_not_materialized";
|
||||
}
|
||||
if (input.rowsAnchorMatched === 0) {
|
||||
return "materialized_but_not_anchor_matched";
|
||||
}
|
||||
if (input.rowsMatched === 0) {
|
||||
return "materialized_but_not_matched";
|
||||
return "materialized_but_filtered_out_by_recipe";
|
||||
}
|
||||
return "matched_non_empty";
|
||||
}
|
||||
|
||||
function toLegacyMcpStatus(
|
||||
status: AddressMcpCallStatus
|
||||
): "skipped" | "error" | "no_raw_rows" | "raw_rows_received_but_not_materialized" | "materialized_but_not_matched" | "matched_non_empty" {
|
||||
if (status === "materialized_but_not_anchor_matched" || status === "materialized_but_filtered_out_by_recipe") {
|
||||
return "materialized_but_not_matched";
|
||||
}
|
||||
return status;
|
||||
}
|
||||
|
||||
function resolvePrimaryAnchor(intent: AddressIntent, filters: AddressFilterSet): AnchorResolutionDebug {
|
||||
const account = typeof filters.account === "string" ? filters.account.trim() : "";
|
||||
const counterparty = typeof filters.counterparty === "string" ? filters.counterparty.trim() : "";
|
||||
@@ -385,6 +637,42 @@ function resolvePrimaryAnchor(intent: AddressIntent, filters: AddressFilterSet):
|
||||
};
|
||||
}
|
||||
|
||||
function refineAnchorFromRows(anchor: AnchorResolutionDebug, rows: NormalizedAddressRow[]): AnchorResolutionDebug {
|
||||
if (rows.length === 0) {
|
||||
return anchor;
|
||||
}
|
||||
if (anchor.anchor_type !== "counterparty" && anchor.anchor_type !== "contract") {
|
||||
return anchor;
|
||||
}
|
||||
const needleRaw = String(anchor.anchor_value_raw ?? "").trim();
|
||||
if (!needleRaw) {
|
||||
return anchor;
|
||||
}
|
||||
const candidates = uniqueStrings(
|
||||
rows
|
||||
.flatMap((row) => row.analytics)
|
||||
.map((value) => value.trim())
|
||||
.filter((value) => value.length >= 2 && matchesAnchorText(value, needleRaw))
|
||||
);
|
||||
if (candidates.length === 0) {
|
||||
return anchor;
|
||||
}
|
||||
if (candidates.length === 1) {
|
||||
return {
|
||||
...anchor,
|
||||
anchor_value_resolved: candidates[0],
|
||||
resolver_confidence: anchor.resolver_confidence === "high" ? "high" : "medium",
|
||||
ambiguity_count: 0
|
||||
};
|
||||
}
|
||||
return {
|
||||
...anchor,
|
||||
anchor_value_resolved: candidates[0],
|
||||
resolver_confidence: "low",
|
||||
ambiguity_count: candidates.length - 1
|
||||
};
|
||||
}
|
||||
|
||||
function composeLimitedReply(category: AddressLimitedReasonCategory, reason: string, nextStep?: string): string {
|
||||
const heading =
|
||||
category === "empty_match"
|
||||
@@ -415,7 +703,10 @@ function buildLimitedExecutionResult(input: {
|
||||
selectedRecipe: string | null;
|
||||
accountScopeMode?: "strict" | "preferred";
|
||||
accountScopeFallbackApplied?: boolean;
|
||||
accountScopeAudit?: AccountScopeAuditDebug;
|
||||
anchor?: AnchorResolutionDebug;
|
||||
matchFailureStage?: AddressMatchFailureStage;
|
||||
matchFailureReason?: string | null;
|
||||
mcpCallStatus: AddressMcpCallStatus;
|
||||
rowsFetched: number;
|
||||
rawRowsReceived?: number;
|
||||
@@ -437,6 +728,7 @@ function buildLimitedExecutionResult(input: {
|
||||
nextStep?: string;
|
||||
category: AddressLimitedReasonCategory;
|
||||
}): AddressExecutionResult {
|
||||
const accountScopeAudit = input.accountScopeAudit ?? buildDefaultAccountScopeAudit(input.filters);
|
||||
return {
|
||||
handled: true,
|
||||
reply_text: composeLimitedReply(input.category, input.reasonText, input.nextStep),
|
||||
@@ -452,6 +744,7 @@ function buildLimitedExecutionResult(input: {
|
||||
extracted_filters: input.filters,
|
||||
missing_required_filters: input.missingRequiredFilters,
|
||||
selected_recipe: input.selectedRecipe,
|
||||
mcp_call_status_legacy: toLegacyMcpStatus(input.mcpCallStatus),
|
||||
account_scope_mode: input.accountScopeMode ?? "strict",
|
||||
account_scope_fallback_applied: input.accountScopeFallbackApplied ?? false,
|
||||
anchor_type: input.anchor?.anchor_type ?? null,
|
||||
@@ -459,6 +752,8 @@ function buildLimitedExecutionResult(input: {
|
||||
anchor_value_resolved: input.anchor?.anchor_value_resolved ?? null,
|
||||
resolver_confidence: input.anchor?.resolver_confidence ?? null,
|
||||
ambiguity_count: input.anchor?.ambiguity_count ?? 0,
|
||||
match_failure_stage: input.matchFailureStage ?? "none",
|
||||
match_failure_reason: input.matchFailureReason ?? null,
|
||||
mcp_call_status: input.mcpCallStatus,
|
||||
rows_fetched: input.rowsFetched,
|
||||
raw_rows_received: input.rawRowsReceived ?? input.rowsFetched,
|
||||
@@ -468,6 +763,11 @@ function buildLimitedExecutionResult(input: {
|
||||
rows_matched: input.rowsMatched,
|
||||
raw_row_keys_sample: input.rawRowKeysSample ?? [],
|
||||
materialization_drop_reason: input.materializationDropReason ?? "none",
|
||||
account_token_raw: accountScopeAudit.accountTokenRaw,
|
||||
account_token_normalized: accountScopeAudit.accountTokenNormalized,
|
||||
account_scope_fields_checked: accountScopeAudit.accountScopeFieldsChecked,
|
||||
account_scope_match_strategy: accountScopeAudit.accountScopeMatchStrategy,
|
||||
account_scope_drop_reason: accountScopeAudit.accountScopeDropReason,
|
||||
runtime_readiness: runtimeReadinessForLimitedCategory(input.category),
|
||||
limited_reason_category: input.category,
|
||||
response_type: "LIMITED_WITH_REASON",
|
||||
@@ -605,7 +905,7 @@ export class AddressQueryService {
|
||||
|
||||
const intent = resolveAddressIntent(userMessage);
|
||||
const filters = extractAddressFilters(userMessage, intent.intent);
|
||||
const anchor = resolvePrimaryAnchor(intent.intent, filters.extracted_filters);
|
||||
let anchor = resolvePrimaryAnchor(intent.intent, filters.extracted_filters);
|
||||
const recipeSelection = selectAddressRecipe(intent.intent, filters.extracted_filters);
|
||||
const baseReasons = [...mode.reasons, ...shape.reasons, ...intent.reasons];
|
||||
|
||||
@@ -720,6 +1020,7 @@ export class AddressQueryService {
|
||||
});
|
||||
|
||||
if (mcp.error) {
|
||||
const errorScopeAudit = buildDefaultAccountScopeAudit(filters.extracted_filters);
|
||||
return buildLimitedExecutionResult({
|
||||
mode,
|
||||
shape,
|
||||
@@ -733,8 +1034,10 @@ export class AddressQueryService {
|
||||
errored: true,
|
||||
rawRowsReceived: mcp.raw_rows.length,
|
||||
rowsMaterialized: 0,
|
||||
rowsAnchorMatched: 0,
|
||||
rowsMatched: 0
|
||||
}),
|
||||
accountScopeAudit: errorScopeAudit,
|
||||
rowsFetched: mcp.fetched_rows,
|
||||
rawRowsReceived: mcp.raw_rows.length,
|
||||
rowsAfterAccountScope: mcp.rows.length,
|
||||
@@ -759,16 +1062,44 @@ export class AddressQueryService {
|
||||
normalizedRawRows.length > 0 &&
|
||||
scopedRows.length === 0;
|
||||
const normalizedRows = accountScopeFallbackApplied ? normalizedRawRows : scopedRows;
|
||||
const filterByAnchors = applyAddressFilters(normalizedRows, filters.extracted_filters);
|
||||
anchor = refineAnchorFromRows(anchor, normalizedRows);
|
||||
const filtersForMatching: AddressFilterSet =
|
||||
anchor.anchor_type === "counterparty" && anchor.anchor_value_resolved
|
||||
? { ...filters.extracted_filters, counterparty: anchor.anchor_value_resolved }
|
||||
: anchor.anchor_type === "contract" && anchor.anchor_value_resolved
|
||||
? { ...filters.extracted_filters, contract: anchor.anchor_value_resolved }
|
||||
: filters.extracted_filters;
|
||||
const accountScopeAudit = buildAccountScopeAudit({
|
||||
intent: intent.intent,
|
||||
filters: filtersForMatching,
|
||||
accountScope: plan.account_scope,
|
||||
rowsBeforeScope: normalizedRawRows.length,
|
||||
rowsAfterScope: normalizedRows.length
|
||||
});
|
||||
const anchorFilter = applyAddressFilters(normalizedRows, filtersForMatching);
|
||||
const filterByAnchors = anchorFilter.rows;
|
||||
const filteredRows = applyIntentSpecificFilter(intent.intent, filterByAnchors);
|
||||
const rowDiagnostics = deriveRowStageDiagnostics(mcp.raw_rows, normalizedRows.length, normalizedRows.length);
|
||||
const stageStatus = deriveMcpStageStatus({
|
||||
rawRowsReceived: mcp.raw_rows.length,
|
||||
rowsMaterialized: normalizedRows.length,
|
||||
rowsAnchorMatched: filterByAnchors.length,
|
||||
rowsMatched: filteredRows.length
|
||||
});
|
||||
const matchFailureStage: AddressMatchFailureStage =
|
||||
stageStatus === "materialized_but_not_anchor_matched"
|
||||
? "materialized_but_not_anchor_matched"
|
||||
: stageStatus === "materialized_but_filtered_out_by_recipe"
|
||||
? "materialized_but_filtered_out_by_recipe"
|
||||
: "none";
|
||||
const matchFailureReason =
|
||||
matchFailureStage === "materialized_but_not_anchor_matched"
|
||||
? anchorFilter.mismatchReason ?? "anchor_not_matched_after_materialization"
|
||||
: matchFailureStage === "materialized_but_filtered_out_by_recipe"
|
||||
? "rows_filtered_out_by_intent_recipe_after_anchor_match"
|
||||
: null;
|
||||
|
||||
if (intent.intent === "list_open_contracts" && contractCandidatesFromRows(filteredRows).length === 0) {
|
||||
if (intent.intent === "list_open_contracts" && filteredRows.length > 0 && contractCandidatesFromRows(filteredRows).length === 0) {
|
||||
return buildLimitedExecutionResult({
|
||||
mode,
|
||||
shape,
|
||||
@@ -778,7 +1109,10 @@ export class AddressQueryService {
|
||||
selectedRecipe: recipeSelection.selected_recipe.recipe_id,
|
||||
accountScopeMode: plan.account_scope_mode,
|
||||
accountScopeFallbackApplied,
|
||||
accountScopeAudit,
|
||||
anchor,
|
||||
matchFailureStage,
|
||||
matchFailureReason,
|
||||
mcpCallStatus: stageStatus,
|
||||
rowsFetched: mcp.fetched_rows,
|
||||
rawRowsReceived: mcp.raw_rows.length,
|
||||
@@ -803,6 +1137,38 @@ export class AddressQueryService {
|
||||
hadBaseRows &&
|
||||
hadAnchorMatchedRows &&
|
||||
(intent.intent === "list_documents_by_counterparty" || intent.intent === "bank_operations_by_counterparty");
|
||||
const isAnchorMismatch = stageStatus === "materialized_but_not_anchor_matched";
|
||||
const isRecipeFilteredOut = stageStatus === "materialized_but_filtered_out_by_recipe";
|
||||
const category: AddressLimitedReasonCategory = isAnchorMismatch
|
||||
? "missing_anchor"
|
||||
: isRecipeFilteredOut
|
||||
? "recipe_visibility_gap"
|
||||
: isVisibilityGapCandidate
|
||||
? "recipe_visibility_gap"
|
||||
: "empty_match";
|
||||
const reasonText = isAnchorMismatch
|
||||
? "якорь контрагента/договора не найден в материализованных live-строках"
|
||||
: isRecipeFilteredOut
|
||||
? "строки по якорю найдены, но отфильтрованы intent-specific recipe"
|
||||
: isVisibilityGapCandidate
|
||||
? "в текущем live recipe нет достаточной document/bank видимости после фильтрации"
|
||||
: "по выбранным фильтрам в live-выборке нет строк";
|
||||
const nextStep = isAnchorMismatch
|
||||
? "уточните контрагента точным именем или добавьте ИНН/договор"
|
||||
: isRecipeFilteredOut
|
||||
? "сузьте период, уточните контрагента или документный тип"
|
||||
: isVisibilityGapCandidate
|
||||
? "нужен специализированный recipe для document/bank контуров или более точный документный anchor"
|
||||
: "уточните период, контрагента, договор или снимите часть фильтров";
|
||||
const limitations = isAnchorMismatch
|
||||
? ["anchor_not_matched_after_materialization"]
|
||||
: isRecipeFilteredOut
|
||||
? ["rows_filtered_out_by_recipe_after_anchor_match"]
|
||||
: [
|
||||
isVisibilityGapCandidate
|
||||
? "document_or_bank_visibility_gap_after_base_filter"
|
||||
: "no_rows_after_recipe_and_scope_filter"
|
||||
];
|
||||
return buildLimitedExecutionResult({
|
||||
mode,
|
||||
shape,
|
||||
@@ -812,7 +1178,10 @@ export class AddressQueryService {
|
||||
selectedRecipe: recipeSelection.selected_recipe.recipe_id,
|
||||
accountScopeMode: plan.account_scope_mode,
|
||||
accountScopeFallbackApplied,
|
||||
accountScopeAudit,
|
||||
anchor,
|
||||
matchFailureStage,
|
||||
matchFailureReason,
|
||||
mcpCallStatus: stageStatus,
|
||||
rowsFetched: mcp.fetched_rows,
|
||||
rawRowsReceived: mcp.raw_rows.length,
|
||||
@@ -822,18 +1191,10 @@ export class AddressQueryService {
|
||||
rowsMatched: 0,
|
||||
rawRowKeysSample: rowDiagnostics.rawRowKeysSample,
|
||||
materializationDropReason: rowDiagnostics.materializationDropReason,
|
||||
category: isVisibilityGapCandidate ? "recipe_visibility_gap" : "empty_match",
|
||||
reasonText: isVisibilityGapCandidate
|
||||
? "в текущем live recipe нет достаточной document/bank видимости после фильтрации"
|
||||
: "по выбранным фильтрам в live-выборке нет строк",
|
||||
nextStep: isVisibilityGapCandidate
|
||||
? "нужен специализированный recipe для document/bank контуров или более точный документный anchor"
|
||||
: "уточните период, контрагента, договор или снимите часть фильтров",
|
||||
limitations: [
|
||||
isVisibilityGapCandidate
|
||||
? "document_or_bank_visibility_gap_after_base_filter"
|
||||
: "no_rows_after_recipe_and_scope_filter"
|
||||
],
|
||||
category,
|
||||
reasonText,
|
||||
nextStep,
|
||||
limitations,
|
||||
reasons: baseReasons
|
||||
});
|
||||
}
|
||||
@@ -854,6 +1215,7 @@ export class AddressQueryService {
|
||||
extracted_filters: filters.extracted_filters,
|
||||
missing_required_filters: [],
|
||||
selected_recipe: recipeSelection.selected_recipe.recipe_id,
|
||||
mcp_call_status_legacy: toLegacyMcpStatus(stageStatus),
|
||||
account_scope_mode: plan.account_scope_mode,
|
||||
account_scope_fallback_applied: accountScopeFallbackApplied,
|
||||
anchor_type: anchor.anchor_type,
|
||||
@@ -861,6 +1223,8 @@ export class AddressQueryService {
|
||||
anchor_value_resolved: anchor.anchor_value_resolved,
|
||||
resolver_confidence: anchor.resolver_confidence,
|
||||
ambiguity_count: anchor.ambiguity_count,
|
||||
match_failure_stage: "none",
|
||||
match_failure_reason: null,
|
||||
mcp_call_status: stageStatus,
|
||||
rows_fetched: mcp.fetched_rows,
|
||||
raw_rows_received: mcp.raw_rows.length,
|
||||
@@ -870,6 +1234,11 @@ export class AddressQueryService {
|
||||
rows_matched: filteredRows.length,
|
||||
raw_row_keys_sample: rowDiagnostics.rawRowKeysSample,
|
||||
materialization_drop_reason: rowDiagnostics.materializationDropReason,
|
||||
account_token_raw: accountScopeAudit.accountTokenRaw,
|
||||
account_token_normalized: accountScopeAudit.accountTokenNormalized,
|
||||
account_scope_fields_checked: accountScopeAudit.accountScopeFieldsChecked,
|
||||
account_scope_match_strategy: accountScopeAudit.accountScopeMatchStrategy,
|
||||
account_scope_drop_reason: accountScopeAudit.accountScopeDropReason,
|
||||
runtime_readiness: "LIVE_QUERYABLE_WITH_LIMITS",
|
||||
limited_reason_category: null,
|
||||
response_type: factual.responseType,
|
||||
|
||||
@@ -19,6 +19,32 @@ __WHERE_CLAUSE__
|
||||
Движения.Период УБЫВ
|
||||
`;
|
||||
|
||||
const BANK_DOCS_QUERY_TEMPLATE = `
|
||||
ВЫБРАТЬ ПЕРВЫЕ __LIMIT__
|
||||
БанкСписание.Дата КАК Период,
|
||||
ПРЕДСТАВЛЕНИЕ(БанкСписание.Ссылка) КАК Регистратор,
|
||||
"" КАК СчетДт,
|
||||
"" КАК СчетКт,
|
||||
БанкСписание.СуммаДокумента КАК Сумма,
|
||||
ПРЕДСТАВЛЕНИЕ(БанкСписание.Контрагент) КАК Контрагент
|
||||
ИЗ
|
||||
Документ.СписаниеСРасчетногоСчета КАК БанкСписание
|
||||
__WHERE_OUT__
|
||||
ОБЪЕДИНИТЬ ВСЕ
|
||||
ВЫБРАТЬ ПЕРВЫЕ __LIMIT__
|
||||
БанкПоступление.Дата КАК Период,
|
||||
ПРЕДСТАВЛЕНИЕ(БанкПоступление.Ссылка) КАК Регистратор,
|
||||
"" КАК СчетДт,
|
||||
"" КАК СчетКт,
|
||||
БанкПоступление.СуммаДокумента КАК Сумма,
|
||||
ПРЕДСТАВЛЕНИЕ(БанкПоступление.Контрагент) КАК Контрагент
|
||||
ИЗ
|
||||
Документ.ПоступлениеНаРасчетныйСчет КАК БанкПоступление
|
||||
__WHERE_IN__
|
||||
УПОРЯДОЧИТЬ ПО
|
||||
Период УБЫВ
|
||||
`;
|
||||
|
||||
const BASE_RECIPES: AddressRecipeDefinition[] = [
|
||||
{
|
||||
recipe_id: "address_movements_payables_v1",
|
||||
@@ -68,7 +94,8 @@ const BASE_RECIPES: AddressRecipeDefinition[] = [
|
||||
optional_filters: ["period_from", "period_to", "as_of_date", "organization", "limit", "sort"],
|
||||
default_limit: 100,
|
||||
account_scope: ["60", "62", "76", "51", "52"],
|
||||
account_scope_mode: "preferred"
|
||||
account_scope_mode: "preferred",
|
||||
query_template: "bank_docs"
|
||||
},
|
||||
{
|
||||
recipe_id: "address_bank_operations_by_counterparty_v1",
|
||||
@@ -78,7 +105,8 @@ const BASE_RECIPES: AddressRecipeDefinition[] = [
|
||||
optional_filters: ["period_from", "period_to", "as_of_date", "organization", "limit", "sort"],
|
||||
default_limit: 100,
|
||||
account_scope: ["51", "52"],
|
||||
account_scope_mode: "preferred"
|
||||
account_scope_mode: "preferred",
|
||||
query_template: "bank_docs"
|
||||
},
|
||||
{
|
||||
recipe_id: "address_documents_forming_balance_v1",
|
||||
@@ -125,7 +153,7 @@ function toDateTimeExpr(isoDate: string, endOfDay: boolean): string | null {
|
||||
return `ДАТАВРЕМЯ(${year}, ${month}, ${day}, ${hour}, ${minute}, ${second})`;
|
||||
}
|
||||
|
||||
function buildWhereClause(filters: AddressFilterSet): string {
|
||||
function buildWhereClause(filters: AddressFilterSet, fieldPath: string): string {
|
||||
const periodFromExpr =
|
||||
typeof filters.period_from === "string" && filters.period_from.trim().length > 0
|
||||
? toDateTimeExpr(filters.period_from, false)
|
||||
@@ -140,16 +168,16 @@ function buildWhereClause(filters: AddressFilterSet): string {
|
||||
: null;
|
||||
|
||||
if (periodFromExpr && periodToExpr) {
|
||||
return `ГДЕ\n Движения.Период МЕЖДУ ${periodFromExpr} И ${periodToExpr}`;
|
||||
return `ГДЕ\n ${fieldPath} МЕЖДУ ${periodFromExpr} И ${periodToExpr}`;
|
||||
}
|
||||
if (periodFromExpr) {
|
||||
return `ГДЕ\n Движения.Период >= ${periodFromExpr}`;
|
||||
return `ГДЕ\n ${fieldPath} >= ${periodFromExpr}`;
|
||||
}
|
||||
if (periodToExpr) {
|
||||
return `ГДЕ\n Движения.Период <= ${periodToExpr}`;
|
||||
return `ГДЕ\n ${fieldPath} <= ${periodToExpr}`;
|
||||
}
|
||||
if (asOfExpr) {
|
||||
return `ГДЕ\n Движения.Период <= ${asOfExpr}`;
|
||||
return `ГДЕ\n ${fieldPath} <= ${asOfExpr}`;
|
||||
}
|
||||
|
||||
return "";
|
||||
@@ -194,11 +222,16 @@ export function buildAddressRecipePlan(
|
||||
: [];
|
||||
const accountScopeMode = recipe.account_scope_mode ?? "strict";
|
||||
|
||||
const whereClause = buildWhereClause(filters);
|
||||
const query = MOVEMENTS_QUERY_TEMPLATE.replace("__LIMIT__", String(resolvedLimit)).replace(
|
||||
"__WHERE_CLAUSE__",
|
||||
whereClause
|
||||
);
|
||||
const query =
|
||||
recipe.query_template === "bank_docs"
|
||||
? BANK_DOCS_QUERY_TEMPLATE
|
||||
.replaceAll("__LIMIT__", String(resolvedLimit))
|
||||
.replace("__WHERE_OUT__", buildWhereClause(filters, "БанкСписание.Дата"))
|
||||
.replace("__WHERE_IN__", buildWhereClause(filters, "БанкПоступление.Дата"))
|
||||
: MOVEMENTS_QUERY_TEMPLATE.replace("__LIMIT__", String(resolvedLimit)).replace(
|
||||
"__WHERE_CLAUSE__",
|
||||
buildWhereClause(filters, "Движения.Период")
|
||||
);
|
||||
|
||||
return {
|
||||
recipe,
|
||||
|
||||
@@ -1725,6 +1725,7 @@ function buildAddressDebugPayload(addressDebug) {
|
||||
extracted_filters: addressDebug.extracted_filters,
|
||||
missing_required_filters: addressDebug.missing_required_filters,
|
||||
selected_recipe: addressDebug.selected_recipe,
|
||||
mcp_call_status_legacy: addressDebug.mcp_call_status_legacy,
|
||||
account_scope_mode: addressDebug.account_scope_mode,
|
||||
account_scope_fallback_applied: addressDebug.account_scope_fallback_applied,
|
||||
anchor_type: addressDebug.anchor_type,
|
||||
@@ -1732,6 +1733,8 @@ function buildAddressDebugPayload(addressDebug) {
|
||||
anchor_value_resolved: addressDebug.anchor_value_resolved,
|
||||
resolver_confidence: addressDebug.resolver_confidence,
|
||||
ambiguity_count: addressDebug.ambiguity_count,
|
||||
match_failure_stage: addressDebug.match_failure_stage,
|
||||
match_failure_reason: addressDebug.match_failure_reason,
|
||||
mcp_call_status: addressDebug.mcp_call_status,
|
||||
rows_fetched: addressDebug.rows_fetched,
|
||||
raw_rows_received: addressDebug.raw_rows_received,
|
||||
@@ -1741,6 +1744,11 @@ function buildAddressDebugPayload(addressDebug) {
|
||||
rows_matched: addressDebug.rows_matched,
|
||||
raw_row_keys_sample: addressDebug.raw_row_keys_sample,
|
||||
materialization_drop_reason: addressDebug.materialization_drop_reason,
|
||||
account_token_raw: addressDebug.account_token_raw,
|
||||
account_token_normalized: addressDebug.account_token_normalized,
|
||||
account_scope_fields_checked: addressDebug.account_scope_fields_checked,
|
||||
account_scope_match_strategy: addressDebug.account_scope_match_strategy,
|
||||
account_scope_drop_reason: addressDebug.account_scope_drop_reason,
|
||||
runtime_readiness: addressDebug.runtime_readiness,
|
||||
limited_reason_category: addressDebug.limited_reason_category,
|
||||
response_type: addressDebug.response_type,
|
||||
@@ -1817,10 +1825,13 @@ export class AssistantService {
|
||||
detected_intent: addressLane.debug.detected_intent,
|
||||
extracted_filters: addressLane.debug.extracted_filters,
|
||||
selected_recipe: addressLane.debug.selected_recipe,
|
||||
mcp_call_status_legacy: addressLane.debug.mcp_call_status_legacy,
|
||||
account_scope_mode: addressLane.debug.account_scope_mode,
|
||||
account_scope_fallback_applied: addressLane.debug.account_scope_fallback_applied,
|
||||
anchor_type: addressLane.debug.anchor_type,
|
||||
resolver_confidence: addressLane.debug.resolver_confidence,
|
||||
match_failure_stage: addressLane.debug.match_failure_stage,
|
||||
match_failure_reason: addressLane.debug.match_failure_reason,
|
||||
mcp_call_status: addressLane.debug.mcp_call_status,
|
||||
rows_fetched: addressLane.debug.rows_fetched,
|
||||
raw_rows_received: addressLane.debug.raw_rows_received,
|
||||
@@ -1829,6 +1840,11 @@ export class AssistantService {
|
||||
rows_materialized: addressLane.debug.rows_materialized,
|
||||
rows_matched: addressLane.debug.rows_matched,
|
||||
materialization_drop_reason: addressLane.debug.materialization_drop_reason,
|
||||
account_token_raw: addressLane.debug.account_token_raw,
|
||||
account_token_normalized: addressLane.debug.account_token_normalized,
|
||||
account_scope_fields_checked: addressLane.debug.account_scope_fields_checked,
|
||||
account_scope_match_strategy: addressLane.debug.account_scope_match_strategy,
|
||||
account_scope_drop_reason: addressLane.debug.account_scope_drop_reason,
|
||||
runtime_readiness: addressLane.debug.runtime_readiness,
|
||||
limited_reason_category: addressLane.debug.limited_reason_category,
|
||||
response_type: addressLane.debug.response_type,
|
||||
|
||||
@@ -42,11 +42,18 @@ export type AddressMcpCallStatus =
|
||||
| "error"
|
||||
| "no_raw_rows"
|
||||
| "raw_rows_received_but_not_materialized"
|
||||
| "materialized_but_not_anchor_matched"
|
||||
| "materialized_but_filtered_out_by_recipe"
|
||||
| "materialized_but_not_matched"
|
||||
| "matched_non_empty";
|
||||
|
||||
export type AddressAccountScopeMode = "strict" | "preferred";
|
||||
|
||||
export type AddressMatchFailureStage =
|
||||
| "none"
|
||||
| "materialized_but_not_anchor_matched"
|
||||
| "materialized_but_filtered_out_by_recipe";
|
||||
|
||||
export interface AddressModeDetection {
|
||||
mode: AddressQuestionMode;
|
||||
confidence: "high" | "medium" | "low";
|
||||
@@ -90,6 +97,7 @@ export interface AddressRecipeDefinition {
|
||||
recipe_id: string;
|
||||
intent: Exclude<AddressIntent, "unknown">;
|
||||
purpose: string;
|
||||
query_template?: "movements" | "bank_docs";
|
||||
required_filters: Array<keyof AddressFilterSet>;
|
||||
optional_filters: Array<keyof AddressFilterSet>;
|
||||
default_limit: number;
|
||||
@@ -120,6 +128,7 @@ export interface AddressExecutionDebug {
|
||||
extracted_filters: AddressFilterSet;
|
||||
missing_required_filters: string[];
|
||||
selected_recipe: string | null;
|
||||
mcp_call_status_legacy: Exclude<AddressMcpCallStatus, "materialized_but_not_anchor_matched" | "materialized_but_filtered_out_by_recipe">;
|
||||
account_scope_mode: AddressAccountScopeMode;
|
||||
account_scope_fallback_applied: boolean;
|
||||
anchor_type: "account" | "counterparty" | "contract" | "document_ref" | "unknown" | null;
|
||||
@@ -127,6 +136,8 @@ export interface AddressExecutionDebug {
|
||||
anchor_value_resolved: string | null;
|
||||
resolver_confidence: "high" | "medium" | "low" | null;
|
||||
ambiguity_count: number;
|
||||
match_failure_stage: AddressMatchFailureStage;
|
||||
match_failure_reason: string | null;
|
||||
mcp_call_status: AddressMcpCallStatus;
|
||||
rows_fetched: number;
|
||||
raw_rows_received: number;
|
||||
@@ -142,6 +153,15 @@ export interface AddressExecutionDebug {
|
||||
| "missing_period_field"
|
||||
| "missing_registrator_field"
|
||||
| "unknown_row_shape";
|
||||
account_token_raw: string | null;
|
||||
account_token_normalized: string | null;
|
||||
account_scope_fields_checked: string[];
|
||||
account_scope_match_strategy: "account_code_regex_plus_alias_map_v1";
|
||||
account_scope_drop_reason:
|
||||
| "not_applicable"
|
||||
| "no_account_scope_requested"
|
||||
| "no_rows_after_scope_filter"
|
||||
| "rows_remaining_after_scope_filter";
|
||||
runtime_readiness: AddressRuntimeReadiness;
|
||||
limited_reason_category: AddressLimitedReasonCategory | null;
|
||||
response_type: AddressResponseType;
|
||||
|
||||
@@ -329,6 +329,7 @@ export interface AssistantDebugPayload {
|
||||
extracted_filters?: Record<string, unknown>;
|
||||
missing_required_filters?: string[];
|
||||
selected_recipe?: string | null;
|
||||
mcp_call_status_legacy?: "skipped" | "error" | "no_raw_rows" | "raw_rows_received_but_not_materialized" | "materialized_but_not_matched" | "matched_non_empty";
|
||||
account_scope_mode?: "strict" | "preferred";
|
||||
account_scope_fallback_applied?: boolean;
|
||||
anchor_type?: "account" | "counterparty" | "contract" | "document_ref" | "unknown" | null;
|
||||
@@ -336,11 +337,15 @@ export interface AssistantDebugPayload {
|
||||
anchor_value_resolved?: string | null;
|
||||
resolver_confidence?: "high" | "medium" | "low" | null;
|
||||
ambiguity_count?: number;
|
||||
match_failure_stage?: "none" | "materialized_but_not_anchor_matched" | "materialized_but_filtered_out_by_recipe";
|
||||
match_failure_reason?: string | null;
|
||||
mcp_call_status?:
|
||||
| "skipped"
|
||||
| "error"
|
||||
| "no_raw_rows"
|
||||
| "raw_rows_received_but_not_materialized"
|
||||
| "materialized_but_not_anchor_matched"
|
||||
| "materialized_but_filtered_out_by_recipe"
|
||||
| "materialized_but_not_matched"
|
||||
| "matched_non_empty";
|
||||
rows_fetched?: number;
|
||||
@@ -357,6 +362,11 @@ export interface AssistantDebugPayload {
|
||||
| "missing_period_field"
|
||||
| "missing_registrator_field"
|
||||
| "unknown_row_shape";
|
||||
account_token_raw?: string | null;
|
||||
account_token_normalized?: string | null;
|
||||
account_scope_fields_checked?: string[];
|
||||
account_scope_match_strategy?: "account_code_regex_plus_alias_map_v1";
|
||||
account_scope_drop_reason?: "not_applicable" | "no_account_scope_requested" | "no_rows_after_scope_filter" | "rows_remaining_after_scope_filter";
|
||||
runtime_readiness?: "LIVE_QUERYABLE" | "LIVE_QUERYABLE_WITH_LIMITS" | "REQUIRES_SPECIALIZED_RECIPE" | "DEEP_ONLY" | "UNKNOWN";
|
||||
limited_reason_category?: "empty_match" | "missing_anchor" | "recipe_visibility_gap" | "execution_error" | "unsupported" | null;
|
||||
response_type?: "FACTUAL_LIST" | "FACTUAL_SUMMARY" | "LIMITED_WITH_REASON";
|
||||
|
||||
Reference in New Issue
Block a user