ГЛОБАЛЬНЫЙ РЕФАКТОРИНГ АРХИТЕКТУРЫ - Рефакторинг этапов 2.12.23: декомпозиция deep-turn пайплайна ассистента в runtime-адаптеры
This commit is contained in:
@@ -30,6 +30,7 @@ interface NormalizedAddressRow {
|
||||
|
||||
interface AddressTryHandleOptions {
|
||||
followupContext?: AddressFollowupContext | null;
|
||||
analysisDateHint?: string | null;
|
||||
}
|
||||
|
||||
const ACCOUNT_SCOPE_FIELDS_CHECKED = ["account_dt", "account_kt", "registrator", "analytics"] as const;
|
||||
@@ -121,6 +122,36 @@ function parseFiniteNumber(value: unknown): number | null {
|
||||
return null;
|
||||
}
|
||||
|
||||
function normalizeAnalysisDateHint(value: unknown): string | null {
|
||||
if (typeof value !== "string") {
|
||||
return null;
|
||||
}
|
||||
const trimmed = value.trim();
|
||||
if (!trimmed) {
|
||||
return null;
|
||||
}
|
||||
const strictDate = trimmed.match(/^(\d{4})-(\d{2})-(\d{2})$/);
|
||||
const isoPrefix = strictDate ?? trimmed.match(/^(\d{4})-(\d{2})-(\d{2})T/i);
|
||||
if (!isoPrefix) {
|
||||
return null;
|
||||
}
|
||||
const year = Number(isoPrefix[1]);
|
||||
const month = Number(isoPrefix[2]);
|
||||
const day = Number(isoPrefix[3]);
|
||||
if (!Number.isFinite(year) || !Number.isFinite(month) || !Number.isFinite(day)) {
|
||||
return null;
|
||||
}
|
||||
const candidate = new Date(Date.UTC(year, month - 1, day));
|
||||
if (
|
||||
candidate.getUTCFullYear() !== year ||
|
||||
candidate.getUTCMonth() + 1 !== month ||
|
||||
candidate.getUTCDate() !== day
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
return `${isoPrefix[1]}-${isoPrefix[2]}-${isoPrefix[3]}`;
|
||||
}
|
||||
|
||||
function valueAsString(value: unknown): string {
|
||||
if (value === null || value === undefined) {
|
||||
return "";
|
||||
@@ -788,6 +819,58 @@ function runtimeReadinessForLimitedCategory(category: AddressLimitedReasonCatego
|
||||
return "UNKNOWN";
|
||||
}
|
||||
|
||||
function normalizeLimitedReason(reason: string): string {
|
||||
let normalized = String(reason ?? "").trim();
|
||||
if (!normalized) {
|
||||
return "не хватает подтвержденных данных для уверенного вывода";
|
||||
}
|
||||
|
||||
const replacements: Array<[RegExp, string]> = [
|
||||
[/address_query\s*v?1/giu, "текущий адресный режим"],
|
||||
[/address\s*v1/giu, "текущий адресный режим"],
|
||||
[/intent-specific\s+recipe/giu, "встроенный фильтр сценария"],
|
||||
[/live\s+recipe/giu, "текущий сценарий выборки"],
|
||||
[/materialized\s+live-строках/giu, "доступном срезе данных"],
|
||||
[/live-выборке/giu, "выборке данных"],
|
||||
[/live-данных/giu, "данных"],
|
||||
[/deep-analysis/giu, "режим расширенной проверки"],
|
||||
[/\blookup\b/giu, "поиск"],
|
||||
[/\bintent\b/giu, "сценария"],
|
||||
[/\brecipe\b/giu, "шаблон выборки"],
|
||||
[/\byakor\b/giu, "ориентир"],
|
||||
[/\banchor\b/giu, "ориентир"],
|
||||
[/\s+/gu, " "]
|
||||
];
|
||||
|
||||
for (const [pattern, value] of replacements) {
|
||||
normalized = normalized.replace(pattern, value);
|
||||
}
|
||||
|
||||
return normalized.trim();
|
||||
}
|
||||
|
||||
function normalizeLimitedNextStep(nextStep: string): string {
|
||||
let normalized = String(nextStep ?? "").trim();
|
||||
if (!normalized) {
|
||||
return "";
|
||||
}
|
||||
|
||||
const replacements: Array<[RegExp, string]> = [
|
||||
[/address_query\s*v?1/giu, "текущий адресный режим"],
|
||||
[/deep-analysis/giu, "режим расширенной проверки"],
|
||||
[/\bP0 intent\b/giu, "поддерживаемый сценарий"],
|
||||
[/\bintent\b/giu, "сценарий"],
|
||||
[/\blookup\b/giu, "поиск"],
|
||||
[/\s+/gu, " "]
|
||||
];
|
||||
|
||||
for (const [pattern, value] of replacements) {
|
||||
normalized = normalized.replace(pattern, value);
|
||||
}
|
||||
|
||||
return normalized.trim();
|
||||
}
|
||||
|
||||
interface RowStageDiagnostics {
|
||||
rawRowKeysSample: string[];
|
||||
materializationDropReason:
|
||||
@@ -945,20 +1028,28 @@ function toLegacyMcpStatus(
|
||||
function composeLimitedReply(category: AddressLimitedReasonCategory, reason: string, nextStep?: string): string {
|
||||
const heading =
|
||||
category === "empty_match"
|
||||
? "В live-данных по текущему фильтру записи не найдены."
|
||||
? "По текущим условиям в доступном срезе данных совпадений не нашлось."
|
||||
: category === "missing_anchor"
|
||||
? "Для точного адресного поиска не хватает обязательного якоря."
|
||||
? "Чтобы ответить надежно, нужен более точный ориентир в запросе."
|
||||
: category === "recipe_visibility_gap"
|
||||
? "Текущий live recipe не дает нужную видимость данных для этого сценария."
|
||||
? "Запрос понятен, но текущий режим не дает нужной детализации."
|
||||
: category === "unsupported"
|
||||
? "Этот запрос не подходит под address_query V1."
|
||||
: "Не удалось выполнить адресный live-запрос в V1.";
|
||||
? "Сейчас этот тип вопроса вне поддерживаемого контура адресного режима."
|
||||
: "Не удалось завершить проверку в адресном режиме.";
|
||||
const reasonLine =
|
||||
category === "unsupported"
|
||||
? "Коротко: этот сценарий пока не поддержан в текущем адресном контуре."
|
||||
: category === "missing_anchor"
|
||||
? "Коротко: в запросе не хватает конкретного ориентира (контрагент, договор или период)."
|
||||
: category === "recipe_visibility_gap"
|
||||
? "Коротко: для уверенного ответа нужен более специализированный сценарий выборки."
|
||||
: `Коротко: ${normalizeLimitedReason(reason)}.`;
|
||||
const lines = [
|
||||
heading,
|
||||
`Причина: ${reason}.`
|
||||
reasonLine
|
||||
];
|
||||
if (nextStep) {
|
||||
lines.push(`Что нужно уточнить: ${nextStep}.`);
|
||||
lines.push(`Что можно сделать дальше: ${normalizeLimitedNextStep(nextStep)}.`);
|
||||
}
|
||||
return lines.join("\n");
|
||||
}
|
||||
@@ -1057,7 +1148,24 @@ export class AddressQueryService {
|
||||
if (!decompose) {
|
||||
return null;
|
||||
}
|
||||
const { mode, shape, intent, filters, baseReasons } = decompose;
|
||||
const { mode, shape, intent, filters } = decompose;
|
||||
const baseReasons = [...decompose.baseReasons];
|
||||
const analysisDate = normalizeAnalysisDateHint(options.analysisDateHint);
|
||||
if (analysisDate) {
|
||||
const hasTemporalFilter = Boolean(
|
||||
(typeof filters.extracted_filters.period_from === "string" && filters.extracted_filters.period_from.trim().length > 0) ||
|
||||
(typeof filters.extracted_filters.period_to === "string" && filters.extracted_filters.period_to.trim().length > 0) ||
|
||||
(typeof filters.extracted_filters.as_of_date === "string" && filters.extracted_filters.as_of_date.trim().length > 0)
|
||||
);
|
||||
if (!hasTemporalFilter) {
|
||||
filters.extracted_filters = {
|
||||
...filters.extracted_filters,
|
||||
as_of_date: analysisDate
|
||||
};
|
||||
filters.warnings = [...new Set([...(filters.warnings ?? []), "as_of_date_from_analysis_context"])];
|
||||
baseReasons.push("as_of_date_from_analysis_context");
|
||||
}
|
||||
}
|
||||
const composeOptionsFromFilters = (filterSet: AddressFilterSet) => ({
|
||||
userMessage,
|
||||
periodFrom: typeof filterSet.period_from === "string" ? filterSet.period_from : undefined,
|
||||
@@ -1079,8 +1187,8 @@ export class AddressQueryService {
|
||||
rowsFetched: 0,
|
||||
rowsMatched: 0,
|
||||
category: "unsupported",
|
||||
reasonText: "intent пока не поддержан в address V1",
|
||||
nextStep: "переформулируйте вопрос как адресный lookup по счету/контрагенту/договору",
|
||||
reasonText: "сценарий пока вне поддерживаемого контура текущего адресного режима",
|
||||
nextStep: "могу проверить близкие сценарии: документы/платежи по контрагенту, договоры или остаток по счету",
|
||||
limitations: ["intent_not_supported_in_v1"],
|
||||
reasons: baseReasons
|
||||
});
|
||||
@@ -1123,8 +1231,8 @@ export class AddressQueryService {
|
||||
rowsFetched: 0,
|
||||
rowsMatched: 0,
|
||||
category: "recipe_visibility_gap",
|
||||
reasonText: "для intent пока нет recipe в address V1",
|
||||
nextStep: "выберите поддерживаемый P0 intent или переключите запрос в deep-analysis",
|
||||
reasonText: "для этого сценария пока нет готового шаблона выборки в текущем режиме",
|
||||
nextStep: "можно выбрать близкий поддерживаемый сценарий или переключить запрос в режим расширенной проверки",
|
||||
limitations: ["recipe_not_available"],
|
||||
reasons: [...baseReasons, ...recipeSelection.selection_reason]
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user