ГЛОБАЛЬНЫЙ РЕФАКТОРИНГ АРХИТЕКТУРЫ - Рефакторинг этапов 2.12.23: декомпозиция deep-turn пайплайна ассистента в runtime-адаптеры

This commit is contained in:
2026-04-10 19:22:40 +03:00
parent 19dbaff741
commit 80d108e506
116 changed files with 13949 additions and 2039 deletions
+1
View File
@@ -797,6 +797,7 @@ function buildRunSummary(run) {
llm_provider: llmProvider,
model,
use_mock: toBooleanSafe(run.report.use_mock),
analysis_date: toStringSafe(run.report.analysis_date),
prompt_version: toStringSafe(run.report.prompt_version),
schema_version: toStringSafe(run.report.schema_version),
suite_id: toStringSafe(run.report.suite_id),
+31 -2
View File
@@ -90,7 +90,32 @@ function normalizeCaseIds(value) {
.filter((item) => item.length > 0);
return normalized.length > 0 ? normalized : undefined;
}
function normalizeAnalysisDate(value) {
if (typeof value !== "string") {
return undefined;
}
const trimmed = value.trim();
const match = trimmed.match(/^(\d{4})-(\d{2})-(\d{2})$/);
if (!match) {
return undefined;
}
const year = Number(match[1]);
const month = Number(match[2]);
const day = Number(match[3]);
if (!Number.isFinite(year) || !Number.isFinite(month) || !Number.isFinite(day)) {
return undefined;
}
const candidate = new Date(Date.UTC(year, month - 1, day));
if (candidate.getUTCFullYear() !== year ||
candidate.getUTCMonth() + 1 !== month ||
candidate.getUTCDate() !== day) {
return undefined;
}
return `${match[1]}-${match[2]}-${match[3]}`;
}
function buildEvalPayloadFromBody(body) {
const analysisDate = normalizeAnalysisDate(body.analysis_date) ??
normalizeAnalysisDate(body.analysisDate);
return {
normalizeConfig: (body.normalizeConfig ?? {}),
caseIds: normalizeCaseIds(body.caseIds),
@@ -103,7 +128,8 @@ function buildEvalPayloadFromBody(body) {
? body.compare_with_report_file
: typeof body.comparisonBaselineReportFile === "string"
? body.comparisonBaselineReportFile
: undefined
: undefined,
analysisDate
};
}
function resolveReadablePath(inputPath) {
@@ -245,6 +271,7 @@ function snapshotJob(job) {
eval_target: job.eval_target,
run_id: job.run_id,
case_set_file: job.case_set_file,
analysis_date: job.analysis_date,
total_cases: job.total_cases,
completed_cases: job.completed_cases,
error: job.error,
@@ -258,7 +285,8 @@ function snapshotJob(job) {
: toRecord(job.report.metrics) && typeof toRecord(job.report.metrics)?.score_index === "number"
? Number(toRecord(job.report.metrics)?.score_index)
: null,
cases_total: typeof job.report.cases_total === "number" ? Number(job.report.cases_total) : null
cases_total: typeof job.report.cases_total === "number" ? Number(job.report.cases_total) : null,
analysis_date: toStringSafe(job.report.analysis_date) ?? job.analysis_date
}
: null
};
@@ -310,6 +338,7 @@ function buildEvalRouter(services) {
eval_target: payload.evalTarget,
run_id: runId,
case_set_file: runtimeCaseSetFile,
analysis_date: payload.analysisDate ?? null,
total_cases: caseSeeds.length,
completed_cases: 0,
cases: caseSeeds.map((item) => ({
@@ -427,8 +427,20 @@ function extractLooseByAnchorValue(text) {
}
const lowered = token.toLowerCase();
const stopWords = new Set([
"какой",
"какая",
"какие",
"каких",
"каким",
"какими",
"каком",
"кто",
"что",
"мы",
"видим",
"контрагенту",
"контрагента",
"контрагентам",
"контре",
"компании",
"компанию",
@@ -436,10 +448,14 @@ function extractLooseByAnchorValue(text) {
"организацию",
"поставщику",
"поставщика",
"поставщикам",
"клиенту",
"клиента",
"клиентам",
"покупателю",
"покупателя",
"покупателям",
"заказчикам",
"партнеру",
"партнера",
"договору",
@@ -552,6 +568,9 @@ function isLikelyCounterpartyToken(rawToken) {
"какая",
"какое",
"каких",
"каким",
"какими",
"каком",
"какому",
"какую",
"кто",
@@ -566,6 +585,8 @@ function isLikelyCounterpartyToken(rawToken) {
"чья",
"чей",
"чью",
"мы",
"видим",
"самый",
"самая",
"самое",
@@ -620,10 +641,23 @@ function isLikelyCounterpartyToken(rawToken) {
"контрагент",
"контрагенту",
"контрагента",
"контрагентам",
"компания",
"компании",
"организация",
"организации",
"поставщикам",
"клиентам",
"покупателям",
"заказчикам",
"аванс",
"авансы",
"проблемный",
"проблемные",
"проблемным",
"закрытия",
"закрыть",
"закрыты",
"год",
"года",
"г",
@@ -727,7 +761,7 @@ function isLowQualityCounterpartyAnchorValue(rawValue) {
if (tokens.length === 0) {
return true;
}
const questionCue = /(?:кто|что|какой|какая|какие|какого|сколько|где|когда|почему|зачем|which|who|what|how\s+many)/iu.test(value) ||
const questionCue = /(?:кто|что|какой|какая|какие|какого|каких|каким|какими|каком|сколько|где|когда|почему|зачем|which|who|what|how\s+many)/iu.test(value) ||
/[?]/u.test(String(rawValue ?? ""));
const rankingCue = /(?:больше|меньше|сам(?:ый|ая|ое|ые)|крупн|жирн|максим|миним)/iu.test(value);
const paymentCue = /(?:плат(?:ит|ят|еж|ёж|ежн|ежей|ежа)|денег|деньг|money|payment)/iu.test(value);
@@ -604,6 +604,10 @@ function hasLifecycleSegmentationSignal(text) {
return /(?:вперв|нов(?:ые|ых|ые\s+контрагент|ые\s+клиент|ые\s+заказчик)|исчез|ушед|ушл|пропал|отвал|только\s+один\s+раз|ровно\s+один\s+раз|однораз|дольше\s+всех|долгожив|самые\s+старые|старые\s+по\s+сотрудничеству|регуляр|эпизодич|разов(?:ые|ой|ые\s+поставщик)|давно\s+не\s+использ|неиспольз|потом\s+перестал)/iu.test(text);
}
function hasCounterpartyActivityLifecycleSignal(text) {
const hasPaymentRiskLexeme = /(?:не\s+плат(?:ит|ят|ил|или)|без\s+оплат|оплат(?:ы|а)?\s+нет|нет\s+оплат|задерж(?:ива|к)|просроч|долг|задолж)/iu.test(text);
if (hasPaymentRiskLexeme) {
return false;
}
if ((hasDocumentSignal(text) || hasBankOperationSignal(text)) && !hasLifecycleSegmentationSignal(text)) {
return false;
}
@@ -678,6 +682,7 @@ function hasCustomerRevenueAndPaymentsSignal(text) {
/(?:топ|top|сам(?:ый|ая|ое|ые)|крупн|мален|жирн|мелк|больше\s+всего|чаще\s+всего|наибольш|максимальн|минимальн)/iu.test(text);
const asksRevenueTotal = /(?:сколько|скока|скок).*(?:денег|выручк|доход|заработ|оборот)/iu.test(text);
const asksOverallTurnover = /(?:общ(?:ий|ие|ая)\s+оборот|общ(?:ая|ий)\s+выручк|total\s+turnover|turnover\s+total)/iu.test(text);
const asksMajorShare = /(?:основн(?:ую|ая|ые|ой)\s+част|больш(?:ую|ая|ие)\s+част|львин(?:ая|ую)\s+дол[яю]|ключев(?:ую|ая)\s+част)/iu.test(text);
const asksValue = /(?:доходн|выручк|приход|поступлен|входящ|зачислен|оплат|плат(?:еж|ёж|ежн|ежей|ежа|ит|ят)|деньг|денег|заработ|оборот|чек|сделк|бюджет|занес|занёс|принес|принёс|revenue|inflow|deal|turnover)/iu.test(text);
const asksRankOrTop = /(?:топ|top|сам(?:ый|ая|ое|ые)|крупн|мален|жирн|мелк|больше\s+всего|чаще\s+всего|наибольш|максимальн)/iu.test(text);
const asksCountOnly = /(?:сколько|скока|скок)\s+/iu.test(text) && !asksValue;
@@ -702,6 +707,9 @@ function hasCustomerRevenueAndPaymentsSignal(text) {
if (asksCounterpartySource && asksValue) {
return true;
}
if (!hasFuzzySupplierLexeme && (asksCustomerGroup || hasCounterpartyLexeme) && asksMajorShare && asksValue) {
return true;
}
if (!hasFuzzySupplierLexeme && asksIncomingFlow && asksRankOrTop) {
return true;
}
@@ -801,6 +809,58 @@ function hasOpenContractsListSignal(text) {
}
return true;
}
function hasSupplierTailRiskSignal(text) {
const hasSupplier = /(?:поставщик|supplier|vendor)/iu.test(text);
const hasTail = /(?:хвост|висят|незакрыт|задолж|долг|просроч)/iu.test(text);
const hasRisk = /(?:систематич|регулярн|проблем|тревог|не\s+разов|больше\s+похож)/iu.test(text);
const hasPeriodCue = /(?:на\s+конец\s+(?:месяц|период)|конец\s+месяц|пару\s+месяц|несколько\s+месяц)/iu.test(text);
return hasSupplier && hasTail && (hasRisk || hasPeriodCue);
}
function hasReceivablesLatencyRiskSignal(text) {
const hasBuyer = /(?:покупател|клиент|заказчик|customer|buyer)/iu.test(text);
const hasCounterparty = /(?:контрагент|counterparty|partner)/iu.test(text);
const hasPayment = /(?:оплат|платеж|платёж|payment)/iu.test(text);
const hasShipment = /(?:отправк|отгруз|реализ|shipment|delivery)/iu.test(text);
const hasDelay = /(?:длинн|долг|просроч|задерж|висят|тревог|too\s+long|late)/iu.test(text);
const hasNonPayment = /(?:не\s+плат(?:ит|ят|ил|или)|без\s+оплат|оплат(?:ы|а)?\s+нет|нет\s+оплат|неоплач)/iu.test(text);
const hasPeriodOrRiskCue = /(?:за\s+текущ|на\s+конец|тревог|просроч|задерж|долг|длинн)/iu.test(text);
const hasBetweenShipmentAndPayment = /между[\s\S]{0,80}(?:отправк|отгруз|реализ)[\s\S]{0,80}(?:оплат|платеж|платёж|payment)/iu.test(text);
if (hasBuyer && hasPayment && ((hasShipment && hasDelay) || hasBetweenShipmentAndPayment)) {
return true;
}
return (hasBuyer || hasCounterparty) && hasNonPayment && hasPeriodOrRiskCue;
}
function hasSettlementGapSignal(text) {
const hasPayment = /(?:платеж|платёж|оплат|списани|поступлен|payment)/iu.test(text);
const hasDocument = /(?:док(?:и|умент|ументы|ументов)|docs?|documents?)/iu.test(text);
const hasAdvance = /(?:аванс|предоплат)/iu.test(text);
const hasNoDocumentForClosing = /(?:нет|без)\s+(?:док(?:и|умент|ументы|ументов)|закрывающ)/iu.test(text) &&
/(?:закрыти|взаиморасч|акт)/iu.test(text);
const hasNoDocumentForClosingReversed = /(?:док(?:и|умент|ументы|ументов)|закрывающ)[\s\S]{0,48}(?:нет|без)/iu.test(text) &&
/(?:закрыти|взаиморасч|акт)/iu.test(text);
const hasNoPayments = /(?:нет|без)\s+(?:оплат|платеж|платёж|payment)/iu.test(text) ||
/(?:оплат|платеж|платёж|payment)\s+нет/iu.test(text);
const hasDocsWithoutPayments = hasDocument && hasNoPayments;
const hasPaymentsWithoutClosingDocs = hasPayment && (hasNoDocumentForClosing || hasNoDocumentForClosingReversed);
const hasUnclosedAdvanceGap = hasAdvance &&
(/(?:не\s+закрыт|незакрыт|долго\s+не\s+закрыт|давно\s+не\s+закрыт)/iu.test(text) ||
hasNoDocumentForClosing ||
hasNoDocumentForClosingReversed);
return hasPaymentsWithoutClosingDocs || hasDocsWithoutPayments || hasUnclosedAdvanceGap;
}
function hasReconciliationMismatchSignal(text) {
const hasCounterparty = /(?:контрагент|поставщик|клиент|покупател|customer|supplier|counterparty)/iu.test(text);
const hasReconciliationLexeme = /(?:акт(?:а|ом|ах)?\s+свер(?:к|ок)|свер(?:к|ок))/iu.test(text);
const hasMismatchLexeme = /(?:не\s+совпад|несовпад|расхожд|расход|не\s+сход|несход|разъех|разниц|не\s+бь[её]т)/iu.test(text);
const hasBalanceLexeme = /(?:сальд|остат|баланс|saldo|balance)/iu.test(text);
const hasLookupVerb = /(?:покажи|выведи|найд[иь]|show|list)/iu.test(text);
const hasInterrogativeLookup = /(?:по\s+каким|у\s+кого|какие|какой|кто|где)/iu.test(text);
return (hasCounterparty &&
hasReconciliationLexeme &&
hasMismatchLexeme &&
hasBalanceLexeme &&
(hasLookupVerb || hasInterrogativeLookup));
}
function isLikelyCounterpartyToken(rawToken) {
const token = String(rawToken ?? "").trim().toLowerCase();
if (!token || token.length < 2) {
@@ -1115,6 +1175,34 @@ function resolveAddressIntent(userMessage) {
reasons: ["payables_signal_detected"]
};
}
if (hasSettlementGapSignal(text)) {
return {
intent: "list_open_contracts",
confidence: "medium",
reasons: ["settlement_gap_signal_detected"]
};
}
if (hasReconciliationMismatchSignal(text)) {
return {
intent: "list_open_contracts",
confidence: "medium",
reasons: ["reconciliation_mismatch_signal_detected"]
};
}
if (hasReceivablesLatencyRiskSignal(text)) {
return {
intent: "list_receivables_counterparties",
confidence: "medium",
reasons: ["receivables_payment_lag_signal_detected"]
};
}
if (hasSupplierTailRiskSignal(text)) {
return {
intent: "list_payables_counterparties",
confidence: "medium",
reasons: ["supplier_tail_risk_signal_detected"]
};
}
if (hasDocumentsFormingBalanceSignal(text) && hasDocumentsFormingBalanceAccountAnchor(text)) {
return {
intent: "documents_forming_balance",
@@ -1137,7 +1225,7 @@ function resolveAddressIntent(userMessage) {
};
}
if (hasAny(text, OPEN_ITEMS_HINTS) &&
(text.includes("контраг") || text.includes("договор") || text.includes("контракт") || text.includes("counterparty") || text.includes("contract"))) {
/(?:контраг|договор|контракт|counterparty|contract|покупател|клиент|заказчик|customer|client|buyer|supplier|поставщик)/iu.test(text)) {
return {
intent: "open_items_by_counterparty_or_contract",
confidence: "medium",
+105 -12
View File
@@ -86,6 +86,33 @@ function parseFiniteNumber(value) {
}
return null;
}
function normalizeAnalysisDateHint(value) {
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) {
if (value === null || value === undefined) {
return "";
@@ -665,6 +692,50 @@ function runtimeReadinessForLimitedCategory(category) {
}
return "UNKNOWN";
}
function normalizeLimitedReason(reason) {
let normalized = String(reason ?? "").trim();
if (!normalized) {
return "не хватает подтвержденных данных для уверенного вывода";
}
const replacements = [
[/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) {
let normalized = String(nextStep ?? "").trim();
if (!normalized) {
return "";
}
const replacements = [
[/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();
}
function rowHasNonEmptyField(row, keys) {
return keys.some((key) => String(row[key] ?? "").trim().length > 0);
}
@@ -766,20 +837,27 @@ function toLegacyMcpStatus(status) {
}
function composeLimitedReply(category, reason, nextStep) {
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");
}
@@ -842,7 +920,22 @@ 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) => ({
userMessage,
periodFrom: typeof filterSet.period_from === "string" ? filterSet.period_from : undefined,
@@ -863,8 +956,8 @@ class AddressQueryService {
rowsFetched: 0,
rowsMatched: 0,
category: "unsupported",
reasonText: "intent пока не поддержан в address V1",
nextStep: "переформулируйте вопрос как адресный lookup по счету/контрагенту/договору",
reasonText: "сценарий пока вне поддерживаемого контура текущего адресного режима",
nextStep: "могу проверить близкие сценарии: документы/платежи по контрагенту, договоры или остаток по счету",
limitations: ["intent_not_supported_in_v1"],
reasons: baseReasons
});
@@ -903,8 +996,8 @@ 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]
});
@@ -1172,7 +1172,7 @@ function composeFactualReply(intent, rows, options = {}) {
if (intent === "list_open_contracts") {
const contracts = contractCandidatesFromRows(rows);
const lines = [
"Собраны кандидаты по незакрытым договорным позициям (по live движениям 60/62/76).",
"Проверил потенциальные разрывы во взаиморасчетах (платежи без закрытия и документы без оплат).",
`Строк движения: ${rows.length}.`,
`Договорных кандидатов: ${contracts.length}.`
];
@@ -1188,6 +1188,34 @@ function composeFactualReply(intent, rows, options = {}) {
text: lines.join("\n")
};
}
if (intent === "list_payables_counterparties") {
const lines = [
"Проверил поставщиков с признаками незакрытых хвостов по взаиморасчетам (контур 60/76).",
`Строк в выборке: ${rows.length}.`,
...(rows.length > 0
? ["Ниже примеры строк для ручной проверки."]
: ["Явных признаков системной задолженности по доступному срезу не найдено."]),
...formatTopRows(rows, 6)
];
return {
responseType: "FACTUAL_LIST",
text: lines.join("\n")
};
}
if (intent === "list_receivables_counterparties") {
const lines = [
"Проверил покупателей с признаками затянутой оплаты (контур 62/76).",
`Строк в выборке: ${rows.length}.`,
...(rows.length > 0
? ["Ниже примеры строк, которые стоит проверить в первую очередь."]
: ["Явных признаков затяжной дебиторки по доступному срезу не найдено."]),
...formatTopRows(rows, 6)
];
return {
responseType: "FACTUAL_LIST",
text: lines.join("\n")
};
}
if (intent === "open_items_by_counterparty_or_contract") {
const lines = [
"Собраны открытые позиции по указанному фильтру (контрагент/договор).",
@@ -1279,12 +1307,7 @@ function composeFactualReply(intent, rows, options = {}) {
text: lines.join("\n")
};
}
const title = intent === "list_payables_counterparties"
? "Срез обязательств (payables) собран по движениям с account scope 60/76."
: intent === "list_receivables_counterparties"
? "Срез требований (receivables) собран по движениям с account scope 62/76."
: "Срез адресного запроса собран.";
const lines = [title, `Строк отобрано: ${rows.length}.`, ...formatTopRows(rows, 6)];
const lines = ["Срез адресного запроса собран.", `Строк отобрано: ${rows.length}.`, ...formatTopRows(rows, 6)];
return {
responseType: "FACTUAL_LIST",
text: lines.join("\n")
@@ -0,0 +1,107 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.buildAssistantAnswerStructureV11 = buildAssistantAnswerStructureV11;
const config_1 = require("../config");
const stage1Contracts_1 = require("../types/stage1Contracts");
const EVIDENCE_LIMITATION_REASON_CODE_SET = new Set([
"snapshot_only",
"heuristic_inference",
"missing_mechanism",
"weak_source_mapping",
"insufficient_detail",
"unknown"
]);
function summarizeUnique(values, limit = 6) {
return Array.from(new Set(values.map((item) => String(item ?? "").trim()).filter(Boolean))).slice(0, limit);
}
function isEvidenceLimitationReasonCode(value) {
return EVIDENCE_LIMITATION_REASON_CODE_SET.has(value);
}
function firstNonEmptyLine(text) {
const line = String(text ?? "")
.split("\n")
.map((item) => item.trim())
.find((item) => item.length > 0);
return (line ?? String(text ?? "")).slice(0, 220);
}
function buildClaimEvidenceLinks(retrievalResults) {
const byClaim = new Map();
for (const result of retrievalResults) {
for (const evidence of result.evidence) {
const claimRef = String(evidence.claim_ref ?? "").trim();
if (!claimRef) {
continue;
}
const evidenceId = String(evidence.evidence_id ?? "").trim();
if (!evidenceId) {
continue;
}
const current = byClaim.get(claimRef) ?? [];
current.push(evidenceId);
byClaim.set(claimRef, current);
}
}
return Array.from(byClaim.entries())
.slice(0, 10)
.map(([claimRef, evidenceIds]) => ({
claim_ref: claimRef,
evidence_ids: summarizeUnique(evidenceIds, 10)
}));
}
function buildAssistantAnswerStructureV11(input) {
const evidenceIds = summarizeUnique(input.retrievalResults.flatMap((item) => item.evidence.map((evidence) => evidence.evidence_id)), 10);
const mechanismNotes = summarizeUnique(input.retrievalResults.flatMap((item) => item.evidence
.map((evidence) => evidence.mechanism_note)
.filter((note) => typeof note === "string" && note.trim().length > 0)), 6);
const sourceRefs = summarizeUnique(input.retrievalResults.flatMap((item) => item.evidence
.map((evidence) => evidence.source_ref?.canonical_ref)
.filter((value) => typeof value === "string" && value.trim().length > 0)), 8);
const limitationReasonCodes = summarizeUnique(input.retrievalResults.flatMap((item) => item.evidence.flatMap((evidence) => {
const code = evidence.limitation?.reason_code;
return typeof code === "string" && code.trim().length > 0 ? [code] : [];
})), 8).filter(isEvidenceLimitationReasonCode);
const claimEvidenceLinks = buildClaimEvidenceLinks(input.retrievalResults);
const limitations = summarizeUnique([...input.retrievalResults.flatMap((item) => item.limitations), ...input.groundingCheck.reasons], 8);
const clarificationQuestions = input.coverageReport.clarification_needed_for.map((item) => `Уточните требование ${item}.`);
const recommendedActions = summarizeUnique([
...input.coverageReport.requirements_uncovered.map((item) => `Проверить непокрытое требование ${item}.`),
...input.coverageReport.requirements_partially_covered.map((item) => `Доуточнить частично покрытое требование ${item}.`)
], 6);
const mechanismStatus = mechanismNotes.length === 0
? "unresolved"
: limitationReasonCodes.includes("missing_mechanism") || limitationReasonCodes.includes("heuristic_inference")
? "limited"
: "grounded";
const enableEvidenceEnrichment = input.options?.enableEvidenceEnrichment ?? config_1.FEATURE_ASSISTANT_EVIDENCE_ENRICHMENT_V1;
return {
schema_version: stage1Contracts_1.ANSWER_STRUCTURE_SCHEMA_VERSION,
answer_summary: firstNonEmptyLine(input.assistantReply),
direct_answer: input.assistantReply,
mechanism_block: {
status: mechanismStatus,
mechanism_notes: mechanismNotes,
limitation_reason_codes: limitationReasonCodes
},
evidence_block: {
evidence_ids: evidenceIds,
source_refs: sourceRefs,
mechanism_notes: mechanismNotes,
coverage_note: input.coverageReport.requirements_total === input.coverageReport.requirements_covered
? "coverage_full_or_near_full"
: "coverage_partial_or_limited",
...(enableEvidenceEnrichment && claimEvidenceLinks.length > 0
? {
claim_evidence_links: claimEvidenceLinks
}
: {})
},
uncertainty_block: {
open_uncertainties: input.groundingCheck.missing_requirements,
limitations
},
next_step_block: {
recommended_actions: recommendedActions,
clarification_questions: clarificationQuestions
}
};
}
@@ -0,0 +1,41 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.assembleAssistantContractsBundleV1 = assembleAssistantContractsBundleV1;
const assistantOrchestrationContracts_1 = require("./assistantOrchestrationContracts");
function assembleAssistantContractsBundleV1(input) {
const queryFrameContractV1 = (0, assistantOrchestrationContracts_1.buildAssistantQueryFrameContractV1)({
userMessage: input.userMessage,
normalizedQuestion: input.normalizedQuestion,
normalized: input.normalized,
routeSummary: input.routeSummary,
droppedIntentSegments: input.droppedIntentSegments,
analysisContext: input.analysisContext
});
const executionPlanContractV1 = (0, assistantOrchestrationContracts_1.buildAssistantExecutionPlanContractV1)({
executionPlan: input.executionPlan,
requirements: input.requirements
});
const outcomeClassV1 = (0, assistantOrchestrationContracts_1.classifyAssistantOutcomeClassV1)({
replyType: input.replyType,
coverageReport: input.coverageReport,
grounding: input.grounding,
retrievalResults: input.retrievalResults
});
const coverageContractV1 = (0, assistantOrchestrationContracts_1.buildAssistantCoverageContractV1)({
coverageReport: input.coverageReport,
grounding: input.grounding,
outcomeClass: outcomeClassV1
});
return {
queryFrameContractV1,
executionPlanContractV1,
outcomeClassV1,
coverageContractV1,
assistantOrchestrationContractsV1: {
query_frame: queryFrameContractV1,
execution_plan: executionPlanContractV1,
evidence_bundle: input.evidenceBundleContractV1,
coverage: coverageContractV1
}
};
}
@@ -0,0 +1,344 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.extractRequirementsForRoute = extractRequirementsForRoute;
exports.evaluateCoverageForRequirements = evaluateCoverageForRequirements;
exports.checkGroundingForRequirements = checkGroundingForRequirements;
function summarizeUnique(values, limit = 6) {
return Array.from(new Set(values.map((item) => String(item ?? "").trim()).filter(Boolean))).slice(0, limit);
}
const SUBJECT_TOKEN_RULES = {
nds: {
critical: true,
patterns: [
"vat",
"accumulationregister",
"ндс",
"книгипокупок",
"книгипродаж",
"налогнадобавленнуюстоимость"
]
},
os: {
critical: true,
patterns: ["fixed_asset", "fixedasset", "основн", "амортиз"]
},
saldo: {
critical: true,
patterns: ["balance", "saldo", "сальдо", "остат"]
},
counterparty: {
critical: false,
patterns: [
"counterparty",
"supplier",
"buyer",
"counterparty_id",
"journal_counterparty",
"document_has_counterparty",
"контрагент",
"поставщик",
"покупател"
],
routes: ["hybrid_store_plus_live", "store_feature_risk", "store_canonical"]
},
document: {
critical: false,
patterns: [
"document",
"recorder",
"journal",
"document_refs_count",
"recorded_by_document",
"journal_refers_to_document",
"документ"
],
routes: ["hybrid_store_plus_live", "store_feature_risk", "store_canonical", "live_mcp_drilldown"]
},
anomaly: {
critical: false,
patterns: [
"risk",
"risk_score",
"unknown_link_count",
"zero_guid",
"navigation_links",
"missing_counterparty_link",
"аномал",
"риск"
],
routes: ["store_feature_risk", "batch_refresh_then_store"]
},
chain: {
critical: false,
patterns: ["chain", "cross_entity_chain", "relation_types", "operations_count", "matched_counterparties", "цепоч"],
routes: ["hybrid_store_plus_live"]
}
};
function hasRegexMatch(corpus, pattern) {
try {
return pattern.test(corpus);
}
catch {
return false;
}
}
function evaluateSubjectTokenMatch(token, corpus, executedRoutes) {
if (token.startsWith("account_")) {
const account = token.slice("account_".length).trim();
if (!account) {
return { matched: false, critical: true };
}
const escaped = account.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
const accountPattern = new RegExp(`(^|[^0-9])${escaped}([^0-9]|$)`, "i");
return { matched: hasRegexMatch(corpus, accountPattern), critical: true };
}
const rule = SUBJECT_TOKEN_RULES[token];
if (rule) {
const byPattern = rule.patterns.some((pattern) => corpus.includes(pattern));
const byRoute = Array.isArray(rule.routes) ? rule.routes.some((route) => executedRoutes.has(route)) : false;
return { matched: byPattern || byRoute, critical: rule.critical };
}
return { matched: corpus.includes(token), critical: false };
}
function evidenceCountForRequirement(requirementId, result) {
const evidence = Array.isArray(result.evidence) ? result.evidence : [];
if (evidence.length === 0) {
return 0;
}
const tagged = evidence.filter((item) => {
const claimRef = typeof item?.claim_ref === "string" ? item.claim_ref : "";
return claimRef.toLowerCase() === `requirement:${String(requirementId).toLowerCase()}`;
}).length;
if (tagged > 0) {
return tagged;
}
if (Array.isArray(result.requirement_ids) &&
result.requirement_ids.length === 1 &&
result.requirement_ids[0] === requirementId) {
return evidence.length;
}
return 0;
}
function hasSubstantiveCoverageForRequirement(requirementId, result) {
const evidenceCount = evidenceCountForRequirement(requirementId, result);
if (evidenceCount > 0) {
return true;
}
const problemUnitsCount = Array.isArray(result.problem_units) ? result.problem_units.length : 0;
const candidateEvidenceCount = Array.isArray(result.candidate_evidence) ? result.candidate_evidence.length : 0;
if (problemUnitsCount > 0 || candidateEvidenceCount > 0) {
if (Array.isArray(result.requirement_ids) &&
result.requirement_ids.length === 1 &&
result.requirement_ids[0] === requirementId) {
return true;
}
}
return false;
}
function extractRequirementsForRoute(input) {
const byFragment = new Map();
const requirements = [];
const pushRequirement = (item) => {
const subjectTokens = input.extractSubjectTokens(item.requirement_text);
requirements.push({
requirement_id: item.requirement_id,
source_fragment_id: item.source_fragment_id,
requirement_text: item.requirement_text,
subject_tokens: subjectTokens,
status: item.status,
route: item.route
});
if (item.source_fragment_id) {
const current = byFragment.get(item.source_fragment_id) ?? [];
current.push(item.requirement_id);
byFragment.set(item.source_fragment_id, current);
}
};
if (!input.routeSummary) {
pushRequirement({
requirement_id: "R1",
source_fragment_id: null,
requirement_text: input.userMessage,
status: "clarification_needed",
route: null
});
return { requirements, byFragment };
}
if (input.routeSummary.mode === "legacy_v1") {
pushRequirement({
requirement_id: "R1",
source_fragment_id: "F1",
requirement_text: input.userMessage,
status: "covered",
route: input.routeSummary.route_hint
});
return { requirements, byFragment };
}
input.routeSummary.decisions.forEach((decision, index) => {
const requirementId = `R${index + 1}`;
const text = input.fragmentTextById.get(decision.fragment_id) ?? input.userMessage;
let status = "covered";
if (decision.route === "no_route") {
if (decision.no_route_reason === "out_of_scope") {
status = "out_of_scope";
}
else if (decision.no_route_reason === "insufficient_specificity") {
status = "clarification_needed";
}
else {
status = "uncovered";
}
}
pushRequirement({
requirement_id: requirementId,
source_fragment_id: decision.fragment_id,
requirement_text: text,
status,
route: decision.route === "no_route" ? null : decision.route
});
});
return { requirements, byFragment };
}
function evaluateCoverageForRequirements(requirements, retrievalResults) {
const statusByRequirement = new Map();
for (const result of retrievalResults) {
for (const requirementId of result.requirement_ids) {
const list = statusByRequirement.get(requirementId) ?? [];
list.push({
status: result.status,
substantive: hasSubstantiveCoverageForRequirement(requirementId, result)
});
statusByRequirement.set(requirementId, list);
}
}
const resolvedRequirements = requirements.map((requirement) => {
if (requirement.status === "out_of_scope" || requirement.status === "clarification_needed") {
return requirement;
}
const states = statusByRequirement.get(requirement.requirement_id) ?? [];
if (states.length === 0) {
return { ...requirement, status: "uncovered" };
}
const hasAnySubstantive = states.some((item) => item.substantive);
if (!hasAnySubstantive) {
return { ...requirement, status: "uncovered" };
}
const hasOk = states.some((item) => item.status === "ok");
const hasPartial = states.some((item) => item.status === "partial");
const hasEmpty = states.some((item) => item.status === "empty");
const hasError = states.some((item) => item.status === "error");
const hasWeakOk = states.some((item) => item.status === "ok" && !item.substantive);
const hasSubstantiveOk = states.some((item) => item.status === "ok" && item.substantive);
const hasSubstantivePartial = states.some((item) => item.status === "partial" && item.substantive);
if (hasSubstantiveOk && !hasSubstantivePartial && !hasWeakOk && !hasEmpty && !hasError) {
return { ...requirement, status: "covered" };
}
if (hasSubstantiveOk || hasSubstantivePartial || hasOk || hasPartial) {
return { ...requirement, status: "partially_covered" };
}
return { ...requirement, status: "uncovered" };
});
const requirementsCovered = resolvedRequirements.filter((item) => item.status === "covered").length;
const requirementsUncovered = resolvedRequirements
.filter((item) => item.status === "uncovered")
.map((item) => item.requirement_id);
const requirementsPartiallyCovered = resolvedRequirements
.filter((item) => item.status === "partially_covered")
.map((item) => item.requirement_id);
const clarificationNeededFor = resolvedRequirements
.filter((item) => item.status === "clarification_needed")
.map((item) => item.requirement_id);
const outOfScopeRequirements = resolvedRequirements
.filter((item) => item.status === "out_of_scope")
.map((item) => item.requirement_id);
return {
requirements: resolvedRequirements,
coverage: {
requirements_total: resolvedRequirements.length,
requirements_covered: requirementsCovered,
requirements_uncovered: requirementsUncovered,
requirements_partially_covered: requirementsPartiallyCovered,
clarification_needed_for: clarificationNeededFor,
out_of_scope_requirements: outOfScopeRequirements
}
};
}
function checkGroundingForRequirements(input) {
const whyIncludedSummary = summarizeUnique(input.retrievalResults.flatMap((item) => item.why_included));
const selectionReasonSummary = summarizeUnique(input.retrievalResults.flatMap((item) => item.selection_reason));
const hasMaterialResults = input.retrievalResults.some((item) => item.status === "ok" || item.status === "partial");
const subjectTokens = input.extractSubjectTokens(input.userMessage);
const executedRoutes = new Set(input.retrievalResults
.filter((item) => item.status !== "error")
.map((item) => item.route)
.filter(Boolean));
const retrievalCorpus = JSON.stringify(input.retrievalResults.map((item) => ({
route: item.route,
result_type: item.result_type,
summary: item.summary,
items: item.items,
evidence: item.evidence,
why_included: item.why_included,
selection_reason: item.selection_reason,
risk_factors: item.risk_factors,
business_interpretation: item.business_interpretation
}))).toLowerCase();
const missingSubjectTokens = [];
const missingCriticalTokens = [];
for (const token of subjectTokens) {
const match = evaluateSubjectTokenMatch(token, retrievalCorpus, executedRoutes);
if (!match.matched) {
missingSubjectTokens.push(token);
if (match.critical) {
missingCriticalTokens.push(token);
}
}
}
const onlyAccountCriticalMissing = missingCriticalTokens.length > 0 && missingCriticalTokens.every((token) => token.startsWith("account_"));
const accountOnlyMismatchRecoverable = hasMaterialResults &&
input.coverage.requirements_covered > 0 &&
onlyAccountCriticalMissing &&
(whyIncludedSummary.length > 0 || selectionReasonSummary.length > 0);
const routeSubjectMatch = !hasMaterialResults || missingCriticalTokens.length === 0 || accountOnlyMismatchRecoverable;
let status = "grounded";
const reasons = [];
if (!routeSubjectMatch) {
status = "route_mismatch_blocked";
reasons.push(`Ключевые ориентиры вопроса не подтверждены в найденных данных: ${missingCriticalTokens.join(", ")}`);
}
else if (accountOnlyMismatchRecoverable) {
status = "partial";
reasons.push(`Часть счетных ориентиров не подтвердилась напрямую (${missingCriticalTokens.join(", ")}), но есть опора для ограниченного вывода.`);
}
else if (input.coverage.requirements_covered === 0) {
status = "no_grounded_answer";
reasons.push("Ни одно требование не получило подтвержденного покрытия.");
}
else if (input.coverage.requirements_uncovered.length > 0 ||
input.coverage.requirements_partially_covered.length > 0 ||
input.coverage.clarification_needed_for.length > 0 ||
input.coverage.out_of_scope_requirements.length > 0) {
status = "partial";
reasons.push("Вопрос покрыт частично: есть непокрытые или требующие уточнения требования.");
}
if (whyIncludedSummary.length === 0) {
reasons.push("В текущей выборке не хватает явных подтверждений, почему записи попали в ответ.");
}
if (missingSubjectTokens.length > 0 && missingCriticalTokens.length === 0) {
reasons.push(`Часть контекста вопроса не подтверждена напрямую в найденных данных: ${missingSubjectTokens.join(", ")}`);
}
const missingRequirements = [
...input.coverage.requirements_uncovered,
...input.coverage.requirements_partially_covered,
...input.coverage.clarification_needed_for,
...input.coverage.out_of_scope_requirements
];
return {
status,
route_subject_match: routeSubjectMatch,
missing_requirements: missingRequirements,
reasons,
why_included_summary: whyIncludedSummary,
selection_reason_summary: selectionReasonSummary
};
}
+56 -22
View File
@@ -141,6 +141,29 @@ function formatIsoDateUtc(date) {
const day = String(date.getUTCDate()).padStart(2, "0");
return `${year}-${month}-${day}`;
}
function normalizeIsoDate(value) {
if (typeof value !== "string") {
return null;
}
const trimmed = value.trim();
const match = trimmed.match(/^(\d{4})-(\d{2})-(\d{2})$/);
if (!match) {
return null;
}
const year = Number(match[1]);
const month = Number(match[2]);
const day = Number(match[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 `${match[1]}-${match[2]}-${match[3]}`;
}
function monthEndFromIso(isoDate) {
const match = String(isoDate ?? "").match(/^(\d{4})-(\d{2})-(\d{2})$/);
if (!match) {
@@ -198,14 +221,25 @@ function hasRbpSignal(text) {
function hasFixedAssetAmortizationSignal(text) {
return /(?:амортиз|основн(?:ые|ых)?\s+сред|(?:^|[^a-zа-яё])ос(?:$|[^a-zа-яё])|depreciat|fixed\s*asset|account\s*0[12]|счет\s*0[12])/i.test(String(text ?? "").toLowerCase());
}
function buildLiveMcpCallPlan(route, fragmentText) {
function buildLiveMcpCallPlan(route, fragmentText, temporalHint) {
const semanticProfile = buildSemanticRetrievalProfile(fragmentText);
const preferredDomainHint = (0, investigationState_1.inferP0DomainFromMessage)(fragmentText);
const periodScope = inferPeriodScope(fragmentText);
const primaryFrom = periodScope.from ?? "2020-07-01";
const primaryTo = periodScope.to ?? monthEndFromIso(primaryFrom) ?? "2020-07-31";
const carryFrom = shiftIsoDate(primaryFrom, -31) ?? primaryFrom;
const carryTo = shiftIsoDate(primaryTo, 31) ?? primaryTo;
const hintedAsOfDate = normalizeIsoDate(temporalHint?.as_of_date);
const hintedPeriodFrom = normalizeIsoDate(temporalHint?.period_from);
const hintedPeriodTo = normalizeIsoDate(temporalHint?.period_to);
const primaryFrom = periodScope.from ?? hintedPeriodFrom ?? hintedAsOfDate;
const primaryTo = periodScope.to ??
hintedPeriodTo ??
(!periodScope.from && !hintedPeriodFrom && hintedAsOfDate ? hintedAsOfDate : primaryFrom ? monthEndFromIso(primaryFrom) ?? primaryFrom : null);
const carryFrom = primaryFrom ? shiftIsoDate(primaryFrom, -31) ?? primaryFrom : null;
const carryTo = primaryTo ? shiftIsoDate(primaryTo, 31) ?? primaryTo : null;
const buildPrimaryQuery = (limit) => primaryFrom && primaryTo
? buildLiveRangeQuery(primaryFrom, primaryTo, limit)
: MCP_LIVE_MOVEMENTS_QUERY_TEMPLATE.replace("__LIMIT__", String(limit));
const buildCarryQuery = (limit) => carryFrom && carryTo
? buildLiveRangeQuery(carryFrom, carryTo, limit)
: buildPrimaryQuery(limit);
const faClaim = preferredDomainHint === "fixed_asset_amortization" ||
hasFixedAssetAmortizationSignal(fragmentText) ||
semanticProfile.query_subject === "fixed_asset_card_mismatch" ||
@@ -219,7 +253,7 @@ function buildLiveMcpCallPlan(route, fragmentText) {
{
call_id: "find_amortization_documents_in_period",
purpose: "seed_amortization_documents",
query: buildLiveRangeQuery(primaryFrom, primaryTo, CLAIM_BOUND_PRIMARY_LIVE_LIMIT),
query: buildPrimaryQuery(CLAIM_BOUND_PRIMARY_LIVE_LIMIT),
limit: CLAIM_BOUND_PRIMARY_LIVE_LIMIT,
required_for_claim: true,
account_scope_override: ["01", "02", "08"]
@@ -227,7 +261,7 @@ function buildLiveMcpCallPlan(route, fragmentText) {
{
call_id: "find_fixed_asset_movements_accounts_01_02",
purpose: "collect_fa_object_movements",
query: buildLiveRangeQuery(primaryFrom, primaryTo, CLAIM_BOUND_PRIMARY_LIVE_LIMIT),
query: buildPrimaryQuery(CLAIM_BOUND_PRIMARY_LIVE_LIMIT),
limit: CLAIM_BOUND_PRIMARY_LIVE_LIMIT,
required_for_claim: true,
account_scope_override: ["01", "02", "08"]
@@ -235,7 +269,7 @@ function buildLiveMcpCallPlan(route, fragmentText) {
{
call_id: "find_fixed_asset_cards_expected_for_period",
purpose: "build_expected_fa_set",
query: buildLiveRangeQuery(carryFrom, primaryTo, CLAIM_BOUND_CARRY_WINDOW_LIVE_LIMIT),
query: buildCarryQuery(CLAIM_BOUND_CARRY_WINDOW_LIVE_LIMIT),
limit: CLAIM_BOUND_CARRY_WINDOW_LIVE_LIMIT,
required_for_claim: true,
account_scope_override: ["01", "02", "08"]
@@ -243,7 +277,7 @@ function buildLiveMcpCallPlan(route, fragmentText) {
{
call_id: "match_expected_vs_actual_fa_coverage",
purpose: "compare_expected_vs_actual_fa_coverage",
query: buildLiveRangeQuery(carryFrom, carryTo, CLAIM_BOUND_CARRY_WINDOW_LIVE_LIMIT),
query: buildCarryQuery(CLAIM_BOUND_CARRY_WINDOW_LIVE_LIMIT),
limit: CLAIM_BOUND_CARRY_WINDOW_LIVE_LIMIT,
required_for_claim: true,
account_scope_override: ["01", "02", "08"]
@@ -265,7 +299,7 @@ function buildLiveMcpCallPlan(route, fragmentText) {
{
call_id: "find_vat_source_documents_in_period",
purpose: "seed_vat_source_documents",
query: buildLiveRangeQuery(primaryFrom, primaryTo, CLAIM_BOUND_PRIMARY_LIVE_LIMIT),
query: buildPrimaryQuery(CLAIM_BOUND_PRIMARY_LIVE_LIMIT),
limit: CLAIM_BOUND_PRIMARY_LIVE_LIMIT,
required_for_claim: true,
account_scope_override: ["19", "68"]
@@ -273,7 +307,7 @@ function buildLiveMcpCallPlan(route, fragmentText) {
{
call_id: "find_vat_invoice_links_in_period",
purpose: "collect_invoice_links",
query: buildLiveRangeQuery(primaryFrom, primaryTo, CLAIM_BOUND_PRIMARY_LIVE_LIMIT),
query: buildPrimaryQuery(CLAIM_BOUND_PRIMARY_LIVE_LIMIT),
limit: CLAIM_BOUND_PRIMARY_LIVE_LIMIT,
required_for_claim: true,
account_scope_override: ["19", "68"]
@@ -281,7 +315,7 @@ function buildLiveMcpCallPlan(route, fragmentText) {
{
call_id: "find_vat_register_entries_in_period",
purpose: "collect_vat_register_entries",
query: buildLiveRangeQuery(primaryFrom, primaryTo, CLAIM_BOUND_PRIMARY_LIVE_LIMIT),
query: buildPrimaryQuery(CLAIM_BOUND_PRIMARY_LIVE_LIMIT),
limit: CLAIM_BOUND_PRIMARY_LIVE_LIMIT,
required_for_claim: true,
account_scope_override: ["19", "68"]
@@ -289,7 +323,7 @@ function buildLiveMcpCallPlan(route, fragmentText) {
{
call_id: "find_vat_book_entries_in_period",
purpose: "collect_vat_book_entries",
query: buildLiveRangeQuery(carryFrom, carryTo, CLAIM_BOUND_CARRY_WINDOW_LIVE_LIMIT),
query: buildCarryQuery(CLAIM_BOUND_CARRY_WINDOW_LIVE_LIMIT),
limit: CLAIM_BOUND_CARRY_WINDOW_LIVE_LIMIT,
required_for_claim: true,
account_scope_override: ["19", "68"]
@@ -326,7 +360,7 @@ function buildLiveMcpCallPlan(route, fragmentText) {
{
call_id: "find_rbp_writeoff_documents_in_period",
purpose: "seed_writeoff_documents",
query: buildLiveRangeQuery(primaryFrom, primaryTo, CLAIM_BOUND_PRIMARY_LIVE_LIMIT),
query: buildPrimaryQuery(CLAIM_BOUND_PRIMARY_LIVE_LIMIT),
limit: CLAIM_BOUND_PRIMARY_LIVE_LIMIT,
required_for_claim: true,
account_scope_override: ["97", "20", "25", "26", "44"]
@@ -334,7 +368,7 @@ function buildLiveMcpCallPlan(route, fragmentText) {
{
call_id: "find_rbp_object_movements_account_97",
purpose: "collect_rbp_object_movements",
query: buildLiveRangeQuery(primaryFrom, primaryTo, CLAIM_BOUND_PRIMARY_LIVE_LIMIT),
query: buildPrimaryQuery(CLAIM_BOUND_PRIMARY_LIVE_LIMIT),
limit: CLAIM_BOUND_PRIMARY_LIVE_LIMIT,
required_for_claim: true,
account_scope_override: ["97"]
@@ -342,7 +376,7 @@ function buildLiveMcpCallPlan(route, fragmentText) {
{
call_id: "find_month_close_entries_linked_to_rbp",
purpose: "link_month_close_to_rbp",
query: buildLiveRangeQuery(primaryFrom, primaryTo, CLAIM_BOUND_PRIMARY_LIVE_LIMIT),
query: buildPrimaryQuery(CLAIM_BOUND_PRIMARY_LIVE_LIMIT),
limit: CLAIM_BOUND_PRIMARY_LIVE_LIMIT,
required_for_claim: true,
account_scope_override: ["97", "20", "25", "26", "44"]
@@ -350,7 +384,7 @@ function buildLiveMcpCallPlan(route, fragmentText) {
{
call_id: "compute_end_period_residual_by_rbp_object",
purpose: "collect_residual_tail_signals",
query: buildLiveRangeQuery(carryFrom, carryTo, CLAIM_BOUND_CARRY_WINDOW_LIVE_LIMIT),
query: buildCarryQuery(CLAIM_BOUND_CARRY_WINDOW_LIVE_LIMIT),
limit: CLAIM_BOUND_CARRY_WINDOW_LIVE_LIMIT,
required_for_claim: true,
account_scope_override: ["97", "20", "25", "26", "44"]
@@ -1410,7 +1444,7 @@ function buildSemanticRetrievalProfile(fragmentText) {
pushMany(entityTypes, ["document", "tax_entry", "posting"]);
pushMany(relationPatterns, ["invoice_to_vat", "document_to_posting"]);
}
if (/ос|основн(ые|ых)\s+сред|(?:^|[^a-zа-яё])ос(?:$|[^a-zа-яё])|основн(ые|ых|ым)?\s+средств|fixed asset|amort|амортиз|амортиз/i.test(lower) ||
if (/основн(ые|ых)\s+сред|(?:^|[^a-zа-яё])ос(?:$|[^a-zа-яё])|основн(ые|ых|ым)?\s+средств|fixed asset|amort|амортиз|амортиз/i.test(lower) ||
hasFixedAssetAccountScope) {
pushMany(domainScope, ["fixed_assets"]);
pushMany(documentTypes, ["fixed_asset_card", "fixed_asset_acceptance", "depreciation_document"]);
@@ -2243,7 +2277,7 @@ class AssistantDataLayer {
}
return enforceBroadQueryGuards(route, fragmentText, result);
}
async executeRouteRuntime(route, fragmentText) {
async executeRouteRuntime(route, fragmentText, options) {
const base = this.executeRoute(route, fragmentText);
if (!config_1.FEATURE_ASSISTANT_MCP_RUNTIME_V1) {
return base;
@@ -2251,7 +2285,7 @@ class AssistantDataLayer {
if (route !== "hybrid_store_plus_live" && route !== "live_mcp_drilldown") {
return base;
}
const liveOverlay = await this.fetchLiveMcpOverlay(route, fragmentText);
const liveOverlay = await this.fetchLiveMcpOverlay(route, fragmentText, options?.temporalHint);
return this.mergeWithLiveOverlay(base, liveOverlay);
}
cloneRawResult(base) {
@@ -2301,9 +2335,9 @@ class AssistantDataLayer {
}
return merged;
}
async fetchLiveMcpOverlay(route, fragmentText) {
async fetchLiveMcpOverlay(route, fragmentText, temporalHint) {
const endpoint = this.buildMcpUrl("/api/execute_query");
const livePlan = buildLiveMcpCallPlan(route, fragmentText);
const livePlan = buildLiveMcpCallPlan(route, fragmentText, temporalHint);
const explicitAccountScope = extractAccountScopeFromText(fragmentText);
const accountScope = livePlan.claim_type === "prove_fixed_asset_amortization_coverage"
? ["01", "02", "08"]
@@ -0,0 +1,92 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.buildDeepAnalysisDebugPayload = buildDeepAnalysisDebugPayload;
function toAnalysisContext(input) {
if (!input.active) {
return null;
}
return {
as_of_date: input.as_of_date,
period_from: input.period_from,
period_to: input.period_to,
source: input.source,
snapshot_mode: input.snapshot_mode
};
}
function buildDeepAnalysisDebugPayload(input) {
const analysisContext = toAnalysisContext(input.runtimeAnalysisContext);
return {
trace_id: input.traceId,
prompt_version: input.promptVersion,
schema_version: input.schemaVersion,
fallback_type: input.fallbackType,
route_summary: input.routeSummary,
fragments: input.fragments,
requirements_extracted: input.requirementsExtracted,
coverage_report: input.coverageReport,
routes: input.routes,
retrieval_status: input.retrievalStatus,
retrieval_results: input.retrievalResults,
answer_grounding_check: input.groundingCheck,
dropped_intent_segments: input.droppedIntentSegments,
question_type_class: input.questionTypeClass,
company_anchors: input.companyAnchors,
analysis_context_applied: input.runtimeAnalysisContext.active,
analysis_context: analysisContext,
business_scope_raw: input.businessScopeResolution.business_scope_raw,
business_scope_resolved: input.businessScopeResolution.business_scope_resolved,
company_grounding_applied: input.businessScopeResolution.company_grounding_applied,
scope_resolution_reason: input.businessScopeResolution.scope_resolution_reason,
company_scope_resolution_reason: input.businessScopeResolution.scope_resolution_reason,
raw_time_anchor: input.temporalGuard.raw_time_anchor,
raw_time_scope: input.temporalGuard.raw_time_scope,
resolved_time_anchor: input.temporalGuard.resolved_time_anchor,
resolved_primary_period: input.temporalGuard.resolved_primary_period,
effective_primary_period: input.temporalGuard.effective_primary_period,
temporal_guard_input: input.temporalGuard.temporal_guard_input,
temporal_alignment_status: input.temporalGuard.temporal_alignment_status,
temporal_resolution_source: input.temporalGuard.temporal_resolution_source,
temporal_guard_basis: input.temporalGuard.temporal_guard_basis,
temporal_guard_applied: input.temporalGuard.temporal_guard_applied,
temporal_guard_outcome: input.temporalGuard.temporal_guard_outcome,
temporal_guard: input.temporalGuard,
raw_numeric_tokens: input.polarityAudit.raw_numeric_tokens,
classified_numeric_tokens: input.polarityAudit.classified_numeric_tokens,
rejected_as_non_accounts: input.polarityAudit.rejected_as_non_accounts,
resolved_account_anchors: input.polarityAudit.resolved_account_anchors,
domain_polarity_guard: input.polarityAudit,
claim_anchor_audit: input.claimAnchorAudit,
settlement_role: input.claimAnchorAudit.settlement_role ?? null,
settlement_role_resolution_reason: input.claimAnchorAudit.settlement_role_resolution_reason ?? [],
polarity_resolution_status: input.claimAnchorAudit.polarity_resolution_status ?? "not_applicable",
targeted_evidence_acquisition: input.targetedEvidenceAudit,
evidence_admissibility_gate: input.evidenceAdmissibilityGateAudit,
...(input.rbpLiveRouteAudit ? { rbp_live_route_audit: input.rbpLiveRouteAudit } : {}),
...(input.faLiveRouteAudit ? { fa_live_route_audit: input.faLiveRouteAudit } : {}),
eligibility_time_basis: input.groundedAnswerEligibilityGuard.eligibility_time_basis,
grounded_answer_eligibility_guard: input.groundedAnswerEligibilityGuard,
...(input.followupStateUsage ? { followup_state_usage: input.followupStateUsage } : {}),
problem_centric_answer_applied: input.compositionDebug.problem_centric_answer_applied ?? false,
problem_units_used_count: input.compositionDebug.problem_units_used_count ?? 0,
problem_answer_mode: input.compositionDebug.problem_answer_mode ?? "stage1_policy_v11",
...(Array.isArray(input.compositionDebug.problem_unit_ids_used) && input.compositionDebug.problem_unit_ids_used.length > 0
? {
problem_unit_ids_used: input.compositionDebug.problem_unit_ids_used
}
: {}),
address_llm_predecompose_attempted: Boolean(input.addressRuntimeMetaForDeep?.attempted),
address_llm_predecompose_applied: Boolean(input.addressRuntimeMetaForDeep?.applied),
address_llm_predecompose_reason: input.addressRuntimeMetaForDeep?.reason ?? null,
address_llm_predecompose_provider: input.addressRuntimeMetaForDeep?.provider ?? null,
address_fallback_rule_hit: input.addressRuntimeMetaForDeep?.fallbackRuleHit ?? null,
address_tool_gate_decision: input.addressRuntimeMetaForDeep?.toolGateDecision ?? null,
address_tool_gate_reason: input.addressRuntimeMetaForDeep?.toolGateReason ?? null,
address_llm_predecompose_contract: input.addressRuntimeMetaForDeep?.predecomposeContract ?? null,
orchestration_contract_v1: input.addressRuntimeMetaForDeep?.orchestrationContract ?? null,
assistant_outcome_class_v1: input.outcomeClassV1,
assistant_orchestration_contracts_v1: input.assistantOrchestrationContractsV1,
answer_structure_v11: input.answerStructureV11,
investigation_state_snapshot: input.investigationStateSnapshot,
normalized: input.normalizedPayload
};
}
@@ -0,0 +1,40 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.buildDeepAnswerArtifacts = buildDeepAnswerArtifacts;
exports.buildAssistantConversationItem = buildAssistantConversationItem;
const assistantAnswerPackageBuilder_1 = require("./assistantAnswerPackageBuilder");
function stripTechnicalTail(text) {
return String(text ?? "")
.replace(/(?:^|\n)\s*#{0,6}\s*(?:debug_payload_json|technical_breakdown_json)\b[\s\S]*$/gi, "")
.replace(/\b(?:debug_payload_json|technical_breakdown_json)\b[\s\S]*$/gi, "")
.trim();
}
function buildDeepAnswerArtifacts(input) {
const safeAssistantReply = stripTechnicalTail(input.safeAssistantReplyBase);
const answerStructureV11 = input.featureContractsV11
? input.featureAnswerPolicyV11 && input.compositionAnswerStructureV11
? input.compositionAnswerStructureV11
: (0, assistantAnswerPackageBuilder_1.buildAssistantAnswerStructureV11)({
assistantReply: safeAssistantReply,
coverageReport: input.coverageReport,
groundingCheck: input.groundingCheck,
retrievalResults: input.retrievalResults
})
: null;
return {
safeAssistantReply,
answerStructureV11
};
}
function buildAssistantConversationItem(input) {
return {
message_id: input.messageId,
session_id: input.sessionId,
role: "assistant",
text: input.text,
reply_type: input.replyType,
created_at: new Date().toISOString(),
trace_id: input.traceId,
debug: input.debug
};
}
@@ -0,0 +1,40 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.buildAssistantDeepTurnComposition = buildAssistantDeepTurnComposition;
const questionTypeResolver_1 = require("./questionTypeResolver");
const answerComposer_1 = require("./answerComposer");
function buildAssistantDeepTurnComposition(input) {
const resolveQuestionTypeSafe = input.resolveQuestionTypeFn ?? questionTypeResolver_1.resolveQuestionType;
const composeAssistantAnswerSafe = input.composeAssistantAnswerFn ?? answerComposer_1.composeAssistantAnswer;
const followupApplied = Boolean(input.followupUsage?.applied);
const focusDomainHint = followupApplied
? input.investigationState?.followup_context?.active_domain ?? input.investigationState?.focus.domain ?? null
: null;
const questionTypeClass = resolveQuestionTypeSafe(input.userMessage);
const companyAnchorSet = input.companyAnchors;
const hasPeriodInCompanyAnchors = (Array.isArray(companyAnchorSet?.dates) && companyAnchorSet.dates.some((item) => String(item ?? "").trim().length > 0)) ||
(Array.isArray(companyAnchorSet?.periods) && companyAnchorSet.periods.some((item) => String(item ?? "").trim().length > 0));
const normalizationPeriodExplicit = input.hasExplicitPeriodAnchor(input.normalizedPayload) || hasPeriodInCompanyAnchors;
const composition = composeAssistantAnswerSafe({
userMessage: input.userMessage,
routeSummary: input.routeSummary,
retrievalResults: input.retrievalResults,
requirements: input.requirements,
coverageReport: input.coverageReport,
groundingCheck: input.groundingCheck,
focusDomainHint,
questionTypeHint: questionTypeClass,
companyAnchors: input.companyAnchors,
normalizationPeriodExplicit,
enableAnswerPolicyV11: input.featureAnswerPolicyV11,
enableProblemCentricAnswerV1: input.featureProblemCentricAnswerV1,
enableLifecycleAnswerV1: input.featureLifecycleAnswerV1
});
return {
focusDomainHint,
questionTypeClass,
hasPeriodInCompanyAnchors,
normalizationPeriodExplicit,
composition
};
}
@@ -0,0 +1,72 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.buildAssistantDeepTurnRuntimeContext = buildAssistantDeepTurnRuntimeContext;
const KNOWN_P0_DOMAINS = new Set([
"settlements_60_62",
"vat_document_register_book",
"month_close_costs_20_44",
"fixed_asset_amortization"
]);
function toAnalysisContext(runtimeAnalysisContext) {
if (!runtimeAnalysisContext.active) {
return null;
}
return {
as_of_date: runtimeAnalysisContext.as_of_date,
period_from: runtimeAnalysisContext.period_from,
period_to: runtimeAnalysisContext.period_to,
source: runtimeAnalysisContext.source
};
}
function buildAssistantDeepTurnRuntimeContext(input) {
const companyAnchors = input.resolveCompanyAnchors(input.userMessage);
const initialBusinessScopeResolution = input.resolveBusinessScopeAlignment({
userMessage: input.userMessage,
companyAnchors,
normalized: input.normalizedPayload,
routeSummary: input.routeSummary
});
const inferredDomainByMessage = input.inferP0DomainFromMessage(input.userMessage);
const focusDomainForGuards = inferredDomainByMessage && KNOWN_P0_DOMAINS.has(inferredDomainByMessage) ? inferredDomainByMessage : null;
const analysisContext = toAnalysisContext(input.runtimeAnalysisContext);
const temporalGuard = input.resolveTemporalGuard({
userMessage: input.userMessage,
normalized: input.normalizedPayload,
companyAnchors,
analysisContext
});
const domainPolarityGuardInitial = input.resolveDomainPolarityGuard({
userMessage: input.userMessage,
companyAnchors,
focusDomainHint: focusDomainForGuards
});
const claimAnchorAudit = input.resolveClaimBoundAnchors({
userMessage: input.userMessage,
companyAnchors,
focusDomainHint: focusDomainForGuards,
primaryPeriod: temporalGuard.effective_primary_period ?? temporalGuard.primary_period_window
});
const businessScopeResolution = input.resolveBusinessScopeFromLiveContext({
current: initialBusinessScopeResolution,
temporalGuard,
claimType: claimAnchorAudit.claim_type,
focusDomainHint: focusDomainForGuards,
userMessage: input.userMessage,
companyAnchors,
followupApplied: Boolean(input.followupUsage?.applied)
});
const resolvedRouteSummary = businessScopeResolution.route_summary_resolved;
const liveTemporalHint = toAnalysisContext(input.runtimeAnalysisContext);
return {
companyAnchors,
initialBusinessScopeResolution,
inferredDomainByMessage,
focusDomainForGuards,
temporalGuard,
domainPolarityGuardInitial,
claimAnchorAudit,
businessScopeResolution,
resolvedRouteSummary,
liveTemporalHint
};
}
@@ -0,0 +1,49 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.runAssistantDeepTurnGroundingRuntime = runAssistantDeepTurnGroundingRuntime;
const assistantOrchestrationRuntimeAdapter_1 = require("./assistantOrchestrationRuntimeAdapter");
const assistantDeepTurnGuardRuntimeAdapter_1 = require("./assistantDeepTurnGuardRuntimeAdapter");
function runAssistantDeepTurnGroundingRuntime(input) {
const runCoverageGroundingPipelineSafe = input.runCoverageGroundingPipelineFn ?? assistantOrchestrationRuntimeAdapter_1.runAssistantCoverageGroundingPipeline;
const applyGroundingEligibilitySafe = input.applyGroundingEligibilityFn ??
((payload) => (0, assistantDeepTurnGuardRuntimeAdapter_1.applyAssistantDeepTurnGroundingEligibility)(payload));
const rbpLiveRouteAudit = input.collectRbpLiveRouteAudit({
claimType: input.claimType,
retrievalResults: input.retrievalResults,
planAudit: input.rbpPlanAudit
});
const faLiveRouteAudit = input.collectFaLiveRouteAudit({
claimType: input.claimType,
retrievalResults: input.retrievalResults,
planAudit: input.faPlanAudit
});
const orchestrationRuntime = runCoverageGroundingPipelineSafe({
routeSummary: input.routeSummary,
normalized: input.normalizedPayload,
userMessage: input.userMessage,
retrievalResults: input.retrievalResults,
requirementExtraction: input.requirementExtraction,
extractRequirements: input.extractRequirements,
evaluateCoverage: input.evaluateCoverage,
checkGrounding: input.checkGrounding
});
const coverageEvaluation = orchestrationRuntime.coverageEvaluation;
const groundingCheckBase = orchestrationRuntime.groundingCheckBase;
const groundingEligibilityRuntime = applyGroundingEligibilitySafe({
groundingCheckBase,
temporalGuard: input.temporalGuard,
polarityAudit: input.polarityAudit,
evidenceAudit: input.evidenceAudit,
claimAnchorAudit: input.claimAnchorAudit,
targetedEvidenceHitRate: input.targetedEvidenceHitRate,
businessScopeResolved: input.businessScopeResolved
});
return {
rbpLiveRouteAudit,
faLiveRouteAudit,
coverageEvaluation,
groundingCheckBase,
groundedAnswerEligibilityGuard: groundingEligibilityRuntime.groundedAnswerEligibilityGuard,
groundingCheck: groundingEligibilityRuntime.groundingCheck
};
}
@@ -0,0 +1,51 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.applyAssistantDeepTurnRetrievalGuards = applyAssistantDeepTurnRetrievalGuards;
exports.applyAssistantDeepTurnGroundingEligibility = applyAssistantDeepTurnGroundingEligibility;
const assistantClaimBoundEvidence_1 = require("./assistantClaimBoundEvidence");
const assistantRuntimeGuards_1 = require("./assistantRuntimeGuards");
function applyAssistantDeepTurnRetrievalGuards(input) {
const applyDomainPolarityGuardSafe = input.applyDomainPolarityGuardFn ?? assistantRuntimeGuards_1.applyDomainPolarityGuardToRetrievalResults;
const applyTargetedEvidenceSafe = input.applyTargetedEvidenceFn ?? assistantClaimBoundEvidence_1.applyTargetedEvidenceAcquisition;
const applyEvidenceAdmissibilityGateSafe = input.applyEvidenceAdmissibilityGateFn ?? assistantRuntimeGuards_1.applyEvidenceAdmissibilityGate;
const polarityGuardResult = applyDomainPolarityGuardSafe({
retrievalResults: input.retrievalResults,
guard: input.domainPolarityGuardInitial
});
const targetedEvidenceResult = applyTargetedEvidenceSafe({
retrievalResults: polarityGuardResult.retrievalResults,
claimAudit: input.claimAnchorAudit
});
const evidenceGateResult = applyEvidenceAdmissibilityGateSafe({
retrievalResults: targetedEvidenceResult.retrievalResults,
temporal: input.temporalGuard,
focusDomainHint: input.focusDomainForGuards,
polarity: polarityGuardResult.audit.polarity,
companyAnchors: input.companyAnchors,
userMessage: input.userMessage
});
return {
retrievalResults: evidenceGateResult.retrievalResults,
polarityGuardResult,
targetedEvidenceResult,
evidenceGateResult
};
}
function applyAssistantDeepTurnGroundingEligibility(input) {
const evaluateGroundedAnswerEligibilitySafe = input.evaluateGroundedAnswerEligibilityFn ?? assistantRuntimeGuards_1.evaluateGroundedAnswerEligibility;
const applyEligibilityToGroundingCheckSafe = input.applyEligibilityToGroundingCheckFn ??
((groundingCheck, eligibility) => (0, assistantRuntimeGuards_1.applyEligibilityToGroundingCheck)(groundingCheck, eligibility));
const groundedAnswerEligibilityGuard = evaluateGroundedAnswerEligibilitySafe({
temporal: input.temporalGuard,
polarity: input.polarityAudit,
evidence: input.evidenceAudit,
claimAnchors: input.claimAnchorAudit,
targetedEvidenceHitRate: input.targetedEvidenceHitRate,
businessScopeResolved: input.businessScopeResolved
});
const groundingCheck = applyEligibilityToGroundingCheckSafe(input.groundingCheckBase, groundedAnswerEligibilityGuard);
return {
groundedAnswerEligibilityGuard,
groundingCheck
};
}
@@ -0,0 +1,19 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.buildAssistantDeepTurnPackagingInput = buildAssistantDeepTurnPackagingInput;
function buildAssistantDeepTurnPackagingInput(args) {
return {
...args,
routesForDebug: Array.isArray(args.routesForDebug) ? args.routesForDebug : [],
followupStateUsage: args.followupStateUsage ?? null,
composition: {
reply_type: args.composition.reply_type,
fallback_type: args.composition.fallback_type,
answer_structure_v11: args.composition.answer_structure_v11 ?? null,
problem_centric_answer_applied: args.composition.problem_centric_answer_applied ?? false,
problem_units_used_count: args.composition.problem_units_used_count ?? 0,
problem_answer_mode: args.composition.problem_answer_mode ?? "stage1_policy_v11",
problem_unit_ids_used: Array.isArray(args.composition.problem_unit_ids_used) ? args.composition.problem_unit_ids_used : []
}
};
}
@@ -0,0 +1,139 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.assembleAssistantDeepTurnPackaging = assembleAssistantDeepTurnPackaging;
const assistantEvidenceBundleAssembler_1 = require("./assistantEvidenceBundleAssembler");
const assistantContractsBundleAssembler_1 = require("./assistantContractsBundleAssembler");
const assistantDeepResponseAssembler_1 = require("./assistantDeepResponseAssembler");
const assistantDebugPayloadAssembler_1 = require("./assistantDebugPayloadAssembler");
const assistantMessageLogAssembler_1 = require("./assistantMessageLogAssembler");
function assembleAssistantDeepTurnPackaging(input) {
const normalizedPayload = (input.normalized.normalized ?? null);
const normalizedFragments = Array.isArray(normalizedPayload?.["fragments"]) ? normalizedPayload?.["fragments"] : [];
const evidenceBundleAssembly = (0, assistantEvidenceBundleAssembler_1.assembleAssistantEvidenceBundle)({
retrievalCalls: input.retrievalCalls,
retrievalResults: input.retrievalResults
});
const contractsBundleV1 = (0, assistantContractsBundleAssembler_1.assembleAssistantContractsBundleV1)({
userMessage: input.userMessage,
normalizedQuestion: input.normalizedQuestion,
normalized: input.normalized.normalized,
routeSummary: input.routeSummary,
droppedIntentSegments: input.droppedIntentSegments,
analysisContext: input.analysisContextForContract,
executionPlan: input.executionPlan,
requirements: input.requirementExtractionRequirements,
evidenceBundleContractV1: evidenceBundleAssembly.evidenceBundleContractV1,
replyType: input.composition.reply_type,
coverageReport: input.coverageReport,
grounding: input.groundingCheck,
retrievalResults: input.retrievalResults
});
const deepAnswerArtifacts = (0, assistantDeepResponseAssembler_1.buildDeepAnswerArtifacts)({
safeAssistantReplyBase: input.safeAssistantReplyBase,
featureContractsV11: input.featureContractsV11,
featureAnswerPolicyV11: input.featureAnswerPolicyV11,
compositionAnswerStructureV11: input.composition.answer_structure_v11 ?? null,
coverageReport: input.coverageReport,
groundingCheck: input.groundingCheck,
retrievalResults: input.retrievalResults
});
const debug = (0, assistantDebugPayloadAssembler_1.buildDeepAnalysisDebugPayload)({
traceId: input.normalized.trace_id,
promptVersion: input.normalized.prompt_version,
schemaVersion: input.normalized.schema_version,
fallbackType: input.composition.fallback_type,
routeSummary: input.routeSummary,
fragments: normalizedFragments,
requirementsExtracted: input.coverageEvaluationRequirements,
coverageReport: input.coverageReport,
routes: input.routesForDebug,
retrievalStatus: evidenceBundleAssembly.retrievalStatus,
retrievalResults: input.retrievalResults,
groundingCheck: input.groundingCheck,
droppedIntentSegments: input.droppedIntentSegments,
questionTypeClass: input.questionTypeClass,
companyAnchors: input.companyAnchors,
runtimeAnalysisContext: input.runtimeAnalysisContext,
businessScopeResolution: input.businessScopeResolution,
temporalGuard: input.temporalGuard,
polarityAudit: input.polarityAudit,
claimAnchorAudit: input.claimAnchorAudit,
targetedEvidenceAudit: input.targetedEvidenceAudit,
evidenceAdmissibilityGateAudit: input.evidenceAdmissibilityGateAudit,
rbpLiveRouteAudit: input.rbpLiveRouteAudit,
faLiveRouteAudit: input.faLiveRouteAudit,
groundedAnswerEligibilityGuard: input.groundedAnswerEligibilityGuard,
followupStateUsage: input.followupStateUsage,
compositionDebug: {
problem_centric_answer_applied: input.composition.problem_centric_answer_applied ?? false,
problem_units_used_count: input.composition.problem_units_used_count ?? 0,
problem_answer_mode: input.composition.problem_answer_mode ?? "stage1_policy_v11",
problem_unit_ids_used: Array.isArray(input.composition.problem_unit_ids_used) ? input.composition.problem_unit_ids_used : []
},
addressRuntimeMetaForDeep: input.addressRuntimeMetaForDeep,
outcomeClassV1: contractsBundleV1.outcomeClassV1,
assistantOrchestrationContractsV1: contractsBundleV1.assistantOrchestrationContractsV1,
answerStructureV11: deepAnswerArtifacts.answerStructureV11,
investigationStateSnapshot: input.investigationStateSnapshot,
normalizedPayload: normalizedPayload
});
const assistantItem = (0, assistantDeepResponseAssembler_1.buildAssistantConversationItem)({
messageId: input.messageId,
sessionId: input.sessionId,
text: deepAnswerArtifacts.safeAssistantReply,
replyType: input.composition.reply_type,
traceId: input.normalized.trace_id,
debug: debug
});
const deepAnalysisLogDetails = (0, assistantMessageLogAssembler_1.buildDeepAnalysisProcessedLogDetails)({
sessionId: input.sessionId,
messageId: input.messageId,
userMessage: input.userMessage,
normalizerOutput: input.normalized.normalized,
executionPlan: input.executionPlan,
resolvedExecutionState: input.resolvedExecutionState,
routes: input.routesForDebug,
retrievalCalls: input.retrievalCalls,
retrievalResultsRaw: input.retrievalResultsRaw,
retrievalResultsNormalized: input.retrievalResults,
requirementsExtracted: input.coverageEvaluationRequirements,
coverageReport: input.coverageReport,
groundingCheck: input.groundingCheck,
replyType: input.composition.reply_type,
droppedIntentSegments: input.droppedIntentSegments,
questionTypeClass: input.questionTypeClass,
companyAnchors: input.companyAnchors,
runtimeAnalysisContext: input.runtimeAnalysisContext,
businessScopeResolution: input.businessScopeResolution,
temporalGuard: input.temporalGuard,
polarityAudit: input.polarityAudit,
claimAnchorAudit: input.claimAnchorAudit,
targetedEvidenceAudit: input.targetedEvidenceAudit,
evidenceAdmissibilityGateAudit: input.evidenceAdmissibilityGateAudit,
rbpLiveRouteAudit: input.rbpLiveRouteAudit,
faLiveRouteAudit: input.faLiveRouteAudit,
groundedAnswerEligibilityGuard: input.groundedAnswerEligibilityGuard,
followupStateUsage: input.followupStateUsage,
compositionDebug: {
problem_centric_answer_applied: input.composition.problem_centric_answer_applied ?? false,
problem_units_used_count: input.composition.problem_units_used_count ?? 0,
problem_answer_mode: input.composition.problem_answer_mode ?? "stage1_policy_v11",
problem_unit_ids_used: Array.isArray(input.composition.problem_unit_ids_used) ? input.composition.problem_unit_ids_used : [],
fallback_type: input.composition.fallback_type
},
outcomeClassV1: contractsBundleV1.outcomeClassV1,
assistantOrchestrationContractsV1: contractsBundleV1.assistantOrchestrationContractsV1,
answerStructureV11: deepAnswerArtifacts.answerStructureV11,
investigationStateSnapshot: input.investigationStateSnapshot,
assistantReply: deepAnswerArtifacts.safeAssistantReply,
traceId: input.normalized.trace_id
});
return {
evidenceBundleAssembly,
contractsBundleV1,
deepAnswerArtifacts,
debug,
assistantItem,
deepAnalysisLogDetails
};
}
@@ -0,0 +1,106 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.runAssistantDeepTurnPackagingRuntime = runAssistantDeepTurnPackagingRuntime;
const nanoid_1 = require("nanoid");
const assistantDeepTurnInputBuilder_1 = require("./assistantDeepTurnInputBuilder");
const assistantDeepTurnPackaging_1 = require("./assistantDeepTurnPackaging");
const assistantDeepTurnPrePackagingContext_1 = require("./assistantDeepTurnPrePackagingContext");
const assistantInvestigationStateRuntimeAdapter_1 = require("./assistantInvestigationStateRuntimeAdapter");
function runAssistantDeepTurnPackagingRuntime(input) {
const buildPrePackagingContextSafe = input.buildPrePackagingContextFn ?? assistantDeepTurnPrePackagingContext_1.buildAssistantDeepTurnPrePackagingContext;
const buildInvestigationStateSnapshotSafe = input.buildInvestigationStateSnapshotFn ?? assistantInvestigationStateRuntimeAdapter_1.buildAssistantInvestigationStateSnapshot;
const persistInvestigationStateSnapshotSafe = input.persistInvestigationStateSnapshotFn ?? assistantInvestigationStateRuntimeAdapter_1.persistAssistantInvestigationStateSnapshot;
const buildDeepTurnPackagingInputSafe = input.buildDeepTurnPackagingInputFn ?? assistantDeepTurnInputBuilder_1.buildAssistantDeepTurnPackagingInput;
const assembleDeepTurnPackagingSafe = input.assembleDeepTurnPackagingFn ?? assistantDeepTurnPackaging_1.assembleAssistantDeepTurnPackaging;
const deepTurnPrePackagingContext = buildPrePackagingContextSafe({
normalizedPayload: input.normalized.normalized,
routeSummary: input.routeSummary,
runtimeAnalysisContext: input.runtimeAnalysisContext,
assistantReply: input.composition.assistant_reply,
extractDroppedIntentSegments: input.extractDroppedIntentSegments,
buildDebugRoutes: input.buildDebugRoutes,
extractExecutionState: input.extractExecutionState,
sanitizeReply: input.sanitizeReply
});
const investigationStateSnapshot = buildInvestigationStateSnapshotSafe({
featureEnabled: input.featureInvestigationStateV1,
previousState: input.previousInvestigationState,
timestamp: input.nowIso ? input.nowIso() : new Date().toISOString(),
questionId: input.questionId,
userMessage: input.userMessage,
routeSummary: input.routeSummary,
requirements: input.coverageEvaluationRequirements,
coverageReport: input.coverageReport,
retrievalResults: input.retrievalResults,
replyType: input.composition.reply_type,
followupApplied: input.followupApplied
});
persistInvestigationStateSnapshotSafe({
featureEnabled: input.featureInvestigationStateV1,
sessionId: input.sessionId,
snapshot: investigationStateSnapshot,
persist: input.persistInvestigationState
});
const messageId = input.messageIdFactory ? input.messageIdFactory() : `msg-${(0, nanoid_1.nanoid)(10)}`;
const deepTurnPackagingInput = buildDeepTurnPackagingInputSafe({
sessionId: input.sessionId,
messageId,
userMessage: input.userMessage,
normalized: input.normalized,
normalizedQuestion: input.normalizedQuestion,
routeSummary: input.routeSummary,
droppedIntentSegments: deepTurnPrePackagingContext.droppedIntentSegments,
analysisContextForContract: deepTurnPrePackagingContext.analysisContextForContract,
executionPlan: input.executionPlan,
requirementExtractionRequirements: input.requirementExtractionRequirements,
coverageEvaluationRequirements: input.coverageEvaluationRequirements,
coverageReport: input.coverageReport,
groundingCheck: input.groundingCheck,
retrievalCalls: input.retrievalCalls,
retrievalResultsRaw: input.retrievalResultsRaw,
retrievalResults: input.retrievalResults,
routesForDebug: deepTurnPrePackagingContext.routesForDebug,
resolvedExecutionState: deepTurnPrePackagingContext.resolvedExecutionState,
questionTypeClass: input.questionTypeClass,
companyAnchors: input.companyAnchors,
runtimeAnalysisContext: input.runtimeAnalysisContext,
businessScopeResolution: input.businessScopeResolution,
temporalGuard: input.temporalGuard,
polarityAudit: input.polarityAudit,
claimAnchorAudit: input.claimAnchorAudit,
targetedEvidenceAudit: input.targetedEvidenceAudit,
evidenceAdmissibilityGateAudit: input.evidenceAdmissibilityGateAudit,
rbpLiveRouteAudit: input.rbpLiveRouteAudit,
faLiveRouteAudit: input.faLiveRouteAudit,
groundedAnswerEligibilityGuard: input.groundedAnswerEligibilityGuard,
followupStateUsage: input.followupStateUsage,
composition: {
reply_type: input.composition.reply_type,
fallback_type: input.composition.fallback_type,
answer_structure_v11: input.composition.answer_structure_v11,
problem_centric_answer_applied: input.composition.problem_centric_answer_applied,
problem_units_used_count: input.composition.problem_units_used_count,
problem_answer_mode: input.composition.problem_answer_mode,
problem_unit_ids_used: input.composition.problem_unit_ids_used
},
safeAssistantReplyBase: deepTurnPrePackagingContext.safeAssistantReplyBase,
featureContractsV11: input.featureContractsV11,
featureAnswerPolicyV11: input.featureAnswerPolicyV11,
investigationStateSnapshot,
addressRuntimeMetaForDeep: input.addressRuntimeMetaForDeep
});
const deepTurnPackaging = assembleDeepTurnPackagingSafe(deepTurnPackagingInput);
return {
messageId,
investigationStateSnapshot,
droppedIntentSegments: deepTurnPrePackagingContext.droppedIntentSegments,
analysisContextForContract: deepTurnPrePackagingContext.analysisContextForContract,
routesForDebug: deepTurnPrePackagingContext.routesForDebug,
resolvedExecutionState: deepTurnPrePackagingContext.resolvedExecutionState,
safeAssistantReplyBase: deepTurnPrePackagingContext.safeAssistantReplyBase,
safeAssistantReply: deepTurnPackaging.deepAnswerArtifacts.safeAssistantReply,
debug: deepTurnPackaging.debug,
assistantItem: deepTurnPackaging.assistantItem,
deepAnalysisLogDetails: deepTurnPackaging.deepAnalysisLogDetails
};
}
@@ -0,0 +1,27 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.buildAssistantDeepTurnExecutionPlan = buildAssistantDeepTurnExecutionPlan;
function buildAssistantDeepTurnExecutionPlan(input) {
const requirementExtraction = input.extractRequirements(input.routeSummary, input.normalizedPayload, input.userMessage);
let executionPlan = input.toExecutionPlan(input.routeSummary, input.normalizedPayload, input.userMessage, requirementExtraction.byFragment);
const rbpRoutePlanEnforcement = input.enforceRbpLiveRoutePlan({
executionPlan,
claimType: input.claimType,
temporalGuard: input.temporalGuard
});
executionPlan = rbpRoutePlanEnforcement.executionPlan;
const faRoutePlanEnforcement = input.enforceFaLiveRoutePlan({
executionPlan,
claimType: input.claimType,
temporalGuard: input.temporalGuard
});
executionPlan = faRoutePlanEnforcement.executionPlan;
executionPlan = input.applyTemporalHintToExecutionPlan(executionPlan, input.temporalGuard);
executionPlan = input.applyPolarityHintToExecutionPlan(executionPlan, input.domainPolarityGuardInitial);
return {
requirementExtraction,
executionPlan,
rbpRoutePlanEnforcement,
faRoutePlanEnforcement
};
}
@@ -0,0 +1,20 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.buildAssistantDeepTurnPrePackagingContext = buildAssistantDeepTurnPrePackagingContext;
function buildAssistantDeepTurnPrePackagingContext(input) {
return {
droppedIntentSegments: input.extractDroppedIntentSegments(input.normalizedPayload),
analysisContextForContract: input.runtimeAnalysisContext.active
? {
as_of_date: input.runtimeAnalysisContext.as_of_date,
period_from: input.runtimeAnalysisContext.period_from,
period_to: input.runtimeAnalysisContext.period_to,
source: input.runtimeAnalysisContext.source,
snapshot_mode: input.runtimeAnalysisContext.snapshot_mode
}
: null,
routesForDebug: input.buildDebugRoutes(input.routeSummary),
resolvedExecutionState: input.extractExecutionState(input.normalizedPayload),
safeAssistantReplyBase: input.sanitizeReply(input.assistantReply, "Нужны уточнения для надежного ответа.")
};
}
@@ -0,0 +1,14 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.buildAssistantDeepTurnSuccessResponse = buildAssistantDeepTurnSuccessResponse;
function buildAssistantDeepTurnSuccessResponse(input) {
return {
ok: true,
session_id: input.sessionId,
assistant_reply: input.assistantReply,
reply_type: input.replyType,
conversation_item: input.conversationItem,
debug: input.debug,
conversation: input.conversation
};
}
@@ -0,0 +1,78 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.executeAssistantDeepTurnRetrievalPlan = executeAssistantDeepTurnRetrievalPlan;
const retrievalResultNormalizer_1 = require("./retrievalResultNormalizer");
function buildRouteExecutorErrorRawResult(route, message) {
return {
status: "error",
result_type: "summary",
items: [],
summary: {
route
},
evidence: [],
why_included: [],
selection_reason: [],
risk_factors: [],
business_interpretation: [],
confidence: "low",
limitations: ["Route executor failed."],
errors: [message]
};
}
async function executeAssistantDeepTurnRetrievalPlan(input) {
const normalizeRetrievalResultSafe = input.normalizeRetrievalResultFn ?? retrievalResultNormalizer_1.normalizeRetrievalResult;
const retrievalCalls = [];
const retrievalResultsRaw = [];
const retrievalResults = [];
for (const planItem of input.executionPlan) {
if (!planItem.should_execute) {
retrievalCalls.push({
fragment_id: planItem.fragment_id,
requirement_ids: planItem.requirement_ids,
route: planItem.route,
status: "skipped",
query_text: planItem.fragment_text,
reason: input.mapNoRouteReason(planItem.no_route_reason)
});
retrievalResults.push(input.buildSkippedResult(planItem));
continue;
}
retrievalCalls.push({
fragment_id: planItem.fragment_id,
requirement_ids: planItem.requirement_ids,
route: planItem.route,
status: "executed",
query_text: planItem.fragment_text,
reason: null
});
try {
const raw = await input.executeRouteRuntime(planItem.route, planItem.fragment_text, {
temporalHint: input.liveTemporalHint
});
retrievalResultsRaw.push({
fragment_id: planItem.fragment_id,
route: planItem.route,
raw_result: raw
});
retrievalResults.push(normalizeRetrievalResultSafe(planItem.fragment_id, planItem.requirement_ids, planItem.route, raw));
}
catch (error) {
const message = error instanceof Error ? error.message : String(error);
retrievalCalls[retrievalCalls.length - 1].status = "failed";
retrievalCalls[retrievalCalls.length - 1].reason = message;
const rawError = buildRouteExecutorErrorRawResult(planItem.route, message);
retrievalResultsRaw.push({
fragment_id: planItem.fragment_id,
route: planItem.route,
raw_result: rawError
});
retrievalResults.push(normalizeRetrievalResultSafe(planItem.fragment_id, planItem.requirement_ids, planItem.route, rawError));
}
}
return {
retrievalCalls,
retrievalResultsRaw,
retrievalResults
};
}
@@ -0,0 +1,23 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.assembleAssistantEvidenceBundle = assembleAssistantEvidenceBundle;
const assistantOrchestrationContracts_1 = require("./assistantOrchestrationContracts");
function buildRetrievalStatus(retrievalResults) {
return retrievalResults.map((item) => ({
fragment_id: item.fragment_id,
requirement_ids: item.requirement_ids,
route: item.route,
status: item.status,
result_type: item.result_type
}));
}
function assembleAssistantEvidenceBundle(input) {
const retrievalResults = Array.isArray(input.retrievalResults) ? input.retrievalResults : [];
return {
evidenceBundleContractV1: (0, assistantOrchestrationContracts_1.buildAssistantEvidenceBundleContractV1)({
retrievalCalls: Array.isArray(input.retrievalCalls) ? input.retrievalCalls : [],
retrievalResults
}),
retrievalStatus: buildRetrievalStatus(retrievalResults)
};
}
@@ -0,0 +1,29 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.buildAssistantInvestigationStateSnapshot = buildAssistantInvestigationStateSnapshot;
exports.persistAssistantInvestigationStateSnapshot = persistAssistantInvestigationStateSnapshot;
const investigationState_1 = require("./investigationState");
function buildAssistantInvestigationStateSnapshot(input) {
if (!input.featureEnabled || !input.previousState) {
return null;
}
return (0, investigationState_1.updateInvestigationState)({
previous: input.previousState,
timestamp: input.timestamp,
questionId: input.questionId,
userMessage: input.userMessage,
routeSummary: input.routeSummary,
requirements: input.requirements,
coverageReport: input.coverageReport,
retrievalResults: input.retrievalResults,
replyType: input.replyType,
followupApplied: input.followupApplied
});
}
function persistAssistantInvestigationStateSnapshot(input) {
if (!input.featureEnabled || !input.snapshot) {
return false;
}
input.persist(input.sessionId, input.snapshot);
return true;
}
@@ -0,0 +1,102 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.buildDeepAnalysisProcessedLogDetails = buildDeepAnalysisProcessedLogDetails;
function toAnalysisContext(input) {
if (!input.active) {
return null;
}
return {
as_of_date: input.as_of_date,
period_from: input.period_from,
period_to: input.period_to,
source: input.source,
snapshot_mode: input.snapshot_mode
};
}
function resolveCoverageStatus(coverageReport) {
return coverageReport.requirements_total === coverageReport.requirements_covered &&
coverageReport.requirements_uncovered.length === 0 &&
coverageReport.requirements_partially_covered.length === 0
? "full"
: "partial_or_limited";
}
function buildDeepAnalysisProcessedLogDetails(input) {
const analysisContext = toAnalysisContext(input.runtimeAnalysisContext);
return {
session_id: input.sessionId,
message_id: input.messageId,
user_message: input.userMessage,
normalizer_output: input.normalizerOutput,
execution_plan: input.executionPlan,
resolved_execution_state: input.resolvedExecutionState,
routes: input.routes,
retrieval_calls: input.retrievalCalls,
retrieval_results_raw: input.retrievalResultsRaw,
retrieval_results_normalized: input.retrievalResultsNormalized,
requirements_extracted: input.requirementsExtracted,
requirements_total: input.coverageReport.requirements_total,
requirements_covered: input.coverageReport.requirements_covered,
requirements_uncovered: input.coverageReport.requirements_uncovered,
coverage_status: resolveCoverageStatus(input.coverageReport),
answer_grounding_status: input.groundingCheck.status,
reply_semantic_type: input.replyType,
why_included_summary: input.groundingCheck.why_included_summary,
selection_reason_summary: input.groundingCheck.selection_reason_summary,
route_subject_match: input.groundingCheck.route_subject_match,
clarification_target: input.coverageReport.clarification_needed_for,
dropped_intent_segments: input.droppedIntentSegments,
question_type_class: input.questionTypeClass,
company_anchors: input.companyAnchors,
analysis_context_applied: input.runtimeAnalysisContext.active,
analysis_context: analysisContext,
business_scope_raw: input.businessScopeResolution.business_scope_raw,
business_scope_resolved: input.businessScopeResolution.business_scope_resolved,
company_grounding_applied: input.businessScopeResolution.company_grounding_applied,
scope_resolution_reason: input.businessScopeResolution.scope_resolution_reason,
company_scope_resolution_reason: input.businessScopeResolution.scope_resolution_reason,
raw_time_anchor: input.temporalGuard.raw_time_anchor,
raw_time_scope: input.temporalGuard.raw_time_scope,
resolved_time_anchor: input.temporalGuard.resolved_time_anchor,
resolved_primary_period: input.temporalGuard.resolved_primary_period,
effective_primary_period: input.temporalGuard.effective_primary_period,
temporal_guard_input: input.temporalGuard.temporal_guard_input,
temporal_alignment_status: input.temporalGuard.temporal_alignment_status,
temporal_resolution_source: input.temporalGuard.temporal_resolution_source,
temporal_guard_basis: input.temporalGuard.temporal_guard_basis,
temporal_guard_applied: input.temporalGuard.temporal_guard_applied,
temporal_guard_outcome: input.temporalGuard.temporal_guard_outcome,
temporal_guard: input.temporalGuard,
raw_numeric_tokens: input.polarityAudit.raw_numeric_tokens,
classified_numeric_tokens: input.polarityAudit.classified_numeric_tokens,
rejected_as_non_accounts: input.polarityAudit.rejected_as_non_accounts,
resolved_account_anchors: input.polarityAudit.resolved_account_anchors,
domain_polarity_guard: input.polarityAudit,
claim_anchor_audit: input.claimAnchorAudit,
settlement_role: input.claimAnchorAudit.settlement_role ?? null,
settlement_role_resolution_reason: input.claimAnchorAudit.settlement_role_resolution_reason ?? [],
polarity_resolution_status: input.claimAnchorAudit.polarity_resolution_status ?? "not_applicable",
targeted_evidence_acquisition: input.targetedEvidenceAudit,
evidence_admissibility_gate: input.evidenceAdmissibilityGateAudit,
...(input.rbpLiveRouteAudit ? { rbp_live_route_audit: input.rbpLiveRouteAudit } : {}),
...(input.faLiveRouteAudit ? { fa_live_route_audit: input.faLiveRouteAudit } : {}),
eligibility_time_basis: input.groundedAnswerEligibilityGuard.eligibility_time_basis,
grounded_answer_eligibility_guard: input.groundedAnswerEligibilityGuard,
...(input.followupStateUsage ? { followup_state_usage: input.followupStateUsage } : {}),
problem_centric_answer_applied: input.compositionDebug.problem_centric_answer_applied ?? false,
problem_units_used_count: input.compositionDebug.problem_units_used_count ?? 0,
problem_answer_mode: input.compositionDebug.problem_answer_mode ?? "stage1_policy_v11",
...(Array.isArray(input.compositionDebug.problem_unit_ids_used) && input.compositionDebug.problem_unit_ids_used.length > 0
? {
problem_unit_ids_used: input.compositionDebug.problem_unit_ids_used
}
: {}),
assistant_outcome_class_v1: input.outcomeClassV1,
assistant_orchestration_contracts_v1: input.assistantOrchestrationContractsV1,
answer_structure_v11: input.answerStructureV11,
investigation_state_snapshot: input.investigationStateSnapshot,
fallback_type: input.compositionDebug.fallback_type,
assistant_reply: input.assistantReply,
reply_type: input.replyType,
trace_id: input.traceId
};
}
@@ -0,0 +1,148 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.classifyAssistantOutcomeClassV1 = classifyAssistantOutcomeClassV1;
exports.buildAssistantQueryFrameContractV1 = buildAssistantQueryFrameContractV1;
exports.buildAssistantExecutionPlanContractV1 = buildAssistantExecutionPlanContractV1;
exports.buildAssistantEvidenceBundleContractV1 = buildAssistantEvidenceBundleContractV1;
exports.buildAssistantCoverageContractV1 = buildAssistantCoverageContractV1;
function normalizeSnapshotMode(value) {
const token = String(value ?? "").trim();
if (token === "force_snapshot" || token === "force_live") {
return token;
}
return "auto";
}
function extractFragmentsTotal(normalized) {
if (!normalized || typeof normalized !== "object") {
return 0;
}
const source = normalized;
const fragments = source.fragments;
return Array.isArray(fragments) ? fragments.length : 0;
}
function collectEvidenceTotals(retrievalResults) {
let evidenceTotal = 0;
const sourceRefs = new Set();
let limitationTotal = 0;
let errorTotal = 0;
for (const result of retrievalResults) {
evidenceTotal += Array.isArray(result.evidence) ? result.evidence.length : 0;
limitationTotal += Array.isArray(result.limitations) ? result.limitations.length : 0;
errorTotal += Array.isArray(result.errors) ? result.errors.length : 0;
for (const evidence of result.evidence ?? []) {
const ref = String(evidence?.source_ref?.canonical_ref ?? "").trim();
if (ref) {
sourceRefs.add(ref);
}
}
}
return {
evidence_total: evidenceTotal,
source_refs_total: sourceRefs.size,
limitation_total: limitationTotal,
error_total: errorTotal
};
}
function classifyAssistantOutcomeClassV1(input) {
const replyType = input.replyType;
const grounding = input.grounding;
const coverage = input.coverageReport;
const hasOnlyErrors = input.retrievalResults.length > 0 &&
input.retrievalResults.every((item) => item.status === "error");
if (replyType === "backend_error" || hasOnlyErrors) {
return "BLOCKED_BY_TOOLING";
}
if (grounding.status === "route_mismatch_blocked" || replyType === "route_mismatch_blocked") {
return "MISROUTED";
}
if (replyType === "clarification_required" || coverage.clarification_needed_for.length > 0) {
return "BLOCKED_BY_AMBIGUITY";
}
if (replyType === "out_of_scope") {
return "BLOCKED_BY_AMBIGUITY";
}
const fullCoverage = coverage.requirements_total > 0 &&
coverage.requirements_total === coverage.requirements_covered &&
coverage.requirements_uncovered.length === 0 &&
coverage.requirements_partially_covered.length === 0 &&
coverage.clarification_needed_for.length === 0 &&
coverage.out_of_scope_requirements.length === 0;
if (fullCoverage && grounding.status === "grounded") {
return "FULLY_ANSWERED";
}
const hasAnyCoverage = coverage.requirements_covered > 0 ||
coverage.requirements_partially_covered.length > 0 ||
grounding.status === "partial";
if (hasAnyCoverage) {
return "PARTIALLY_ANSWERED";
}
const missingRequirementSignal = grounding.missing_requirements.length > 0 ||
coverage.requirements_uncovered.length > 0 ||
coverage.requirements_total > 0;
const possibleBindingFailure = replyType === "no_grounded_answer" &&
missingRequirementSignal &&
grounding.route_subject_match;
if (possibleBindingFailure) {
return "FAILED_TO_BIND_ENTITIES";
}
return "BLOCKED_BY_MISSING_DATA";
}
function buildAssistantQueryFrameContractV1(input) {
const analysis = input.analysisContext
? {
as_of_date: input.analysisContext.as_of_date ?? null,
period_from: input.analysisContext.period_from ?? null,
period_to: input.analysisContext.period_to ?? null,
source: input.analysisContext.source ?? null,
snapshot_mode: normalizeSnapshotMode(input.analysisContext.snapshot_mode)
}
: null;
return {
schema_version: "assistant_query_frame_v1",
original_user_question: String(input.userMessage ?? ""),
normalized_question: String(input.normalizedQuestion ?? ""),
route_summary_mode: input.routeSummary?.mode ?? "none",
fragments_total: extractFragmentsTotal(input.normalized),
dropped_intent_segments: Array.isArray(input.droppedIntentSegments) ? [...input.droppedIntentSegments] : [],
analysis_context: analysis
};
}
function buildAssistantExecutionPlanContractV1(input) {
return {
schema_version: "assistant_execution_plan_v1",
steps: (Array.isArray(input.executionPlan) ? input.executionPlan : []).map((item) => ({
fragment_id: String(item.fragment_id ?? ""),
route: String(item.route ?? ""),
should_execute: Boolean(item.should_execute),
requirement_ids: Array.isArray(item.requirement_ids) ? [...item.requirement_ids] : [],
no_route_reason: item.no_route_reason ?? null,
clarification_reason: item.clarification_reason ?? null
})),
requirements_total: Array.isArray(input.requirements) ? input.requirements.length : 0
};
}
function buildAssistantEvidenceBundleContractV1(input) {
const retrievalResults = Array.isArray(input.retrievalResults) ? input.retrievalResults : [];
const breakdown = {
ok: retrievalResults.filter((item) => item.status === "ok").length,
partial: retrievalResults.filter((item) => item.status === "partial").length,
empty: retrievalResults.filter((item) => item.status === "empty").length,
error: retrievalResults.filter((item) => item.status === "error").length
};
const totals = collectEvidenceTotals(retrievalResults);
return {
schema_version: "assistant_evidence_bundle_v1",
retrieval_calls_total: Array.isArray(input.retrievalCalls) ? input.retrievalCalls.length : 0,
retrieval_results_total: retrievalResults.length,
retrieval_status_breakdown: breakdown,
...totals
};
}
function buildAssistantCoverageContractV1(input) {
return {
schema_version: "assistant_coverage_contract_v1",
coverage_report: input.coverageReport,
grounding: input.grounding,
outcome_class: input.outcomeClass
};
}
@@ -0,0 +1,13 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.runAssistantCoverageGroundingPipeline = runAssistantCoverageGroundingPipeline;
function runAssistantCoverageGroundingPipeline(input) {
const requirementExtraction = input.requirementExtraction ?? input.extractRequirements(input.routeSummary, input.normalized, input.userMessage);
const coverageEvaluation = input.evaluateCoverage(requirementExtraction.requirements, input.retrievalResults);
const groundingCheckBase = input.checkGrounding(input.userMessage, coverageEvaluation.requirements, coverageEvaluation.coverage, input.retrievalResults);
return {
requirementExtraction,
coverageEvaluation,
groundingCheckBase
};
}
@@ -0,0 +1,106 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.buildFragmentTextById = buildFragmentTextById;
exports.buildExecutionPlanFromRoute = buildExecutionPlanFromRoute;
exports.buildDebugRoutesFromRoute = buildDebugRoutesFromRoute;
function escapeRegex(value) {
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
}
function enrichFragmentTextWithHints(fragment, text) {
const baseText = String(text ?? "").trim();
const accountHints = Array.isArray(fragment.account_hints)
? Array.from(new Set(fragment.account_hints.map((item) => String(item ?? "").trim()).filter((item) => item.length > 0)))
: [];
if (accountHints.length === 0) {
return baseText;
}
const hasAccountInText = accountHints.some((account) => new RegExp(`\\b${escapeRegex(account)}\\b`, "i").test(baseText));
if (hasAccountInText) {
return baseText;
}
return `${baseText}, по счету ${accountHints.join(", ")}`;
}
function buildFragmentTextById(fragments) {
const result = new Map();
for (const item of fragments) {
if (!item || typeof item !== "object") {
continue;
}
const fragment = item;
const fragmentId = typeof fragment.fragment_id === "string" ? fragment.fragment_id : "";
if (!fragmentId) {
continue;
}
const text = (typeof fragment.raw_fragment_text === "string" && fragment.raw_fragment_text.trim()) ||
(typeof fragment.normalized_fragment_text === "string" && fragment.normalized_fragment_text.trim()) ||
"";
result.set(fragmentId, enrichFragmentTextWithHints(fragment, text));
}
return result;
}
function buildExecutionPlanFromRoute(input) {
if (!input.routeSummary) {
return [];
}
if (input.routeSummary.mode === "legacy_v1") {
return [
{
fragment_id: "F1",
requirement_ids: input.requirementByFragment.get("F1") ?? ["R1"],
route: input.routeSummary.route_hint,
should_execute: true,
fragment_text: input.userMessage,
no_route_reason: null,
clarification_reason: null
}
];
}
return input.routeSummary.decisions.map((decision) => {
const text = input.fragmentTextById.get(decision.fragment_id) ?? input.userMessage;
if (decision.route === "no_route") {
return {
fragment_id: decision.fragment_id,
requirement_ids: input.requirementByFragment.get(decision.fragment_id) ?? [],
route: "no_route",
should_execute: false,
fragment_text: text,
no_route_reason: decision.no_route_reason ?? null,
clarification_reason: decision.clarification_reason ?? null
};
}
return {
fragment_id: decision.fragment_id,
requirement_ids: input.requirementByFragment.get(decision.fragment_id) ?? [],
route: decision.route,
should_execute: true,
fragment_text: text,
no_route_reason: null,
clarification_reason: decision.clarification_reason ?? null
};
});
}
function buildDebugRoutesFromRoute(input) {
if (!input.routeSummary) {
return [];
}
if (input.routeSummary.mode === "legacy_v1") {
return [
{
fragment_id: "F1",
route: input.routeSummary.route_hint,
reason: input.resolveLegacyRouteReason(input.routeSummary.route_hint),
confidence: input.routeSummary.confidence,
intent_class: input.routeSummary.intent_class
}
];
}
return input.routeSummary.decisions.map((decision) => ({
fragment_id: decision.fragment_id,
route: decision.route,
reason: decision.reason,
route_status: decision.route_status ?? null,
no_route_reason: decision.no_route_reason ?? null,
clarification_reason: decision.clarification_reason ?? null,
execution_readiness: decision.execution_readiness ?? null
}));
}
@@ -584,7 +584,78 @@ function toTemporalGuardInput(window, fallback) {
const value = String(fallback ?? "").trim();
return value || null;
}
function normalizeIsoDate(value) {
if (typeof value !== "string") {
return null;
}
const trimmed = value.trim();
const match = trimmed.match(/^(\d{4})-(\d{2})-(\d{2})$/);
if (!match) {
return null;
}
const year = Number(match[1]);
const month = Number(match[2]);
const day = Number(match[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 `${match[1]}-${match[2]}-${match[3]}`;
}
function normalizeTemporalWindow(input) {
const asOfDate = normalizeIsoDate(input.asOfDate);
if (asOfDate) {
return {
from: asOfDate,
to: asOfDate,
granularity: "day"
};
}
const from = normalizeIsoDate(input.periodFrom);
const to = normalizeIsoDate(input.periodTo);
if (!from || !to) {
return null;
}
return {
from,
to,
granularity: from === to ? "day" : "month"
};
}
function resolveTemporalGuard(input) {
const analysisWindow = normalizeTemporalWindow({
asOfDate: input.analysisContext?.as_of_date,
periodFrom: input.analysisContext?.period_from,
periodTo: input.analysisContext?.period_to
});
if (analysisWindow) {
const source = String(input.analysisContext?.source ?? "").trim() || "analysis_context";
const guardInput = toTemporalGuardInput(analysisWindow, analysisWindow.from);
return {
raw_time_anchor: analysisWindow.from,
raw_time_scope: guardInput,
resolved_time_anchor: analysisWindow.granularity === "day" ? analysisWindow.from : null,
resolved_primary_period: analysisWindow,
effective_primary_period: analysisWindow,
temporal_guard_input: guardInput,
temporal_alignment_status: "aligned",
temporal_resolution_source: source,
temporal_guard_basis: "raw_time_scope_unlocked",
temporal_guard_applied: false,
temporal_guard_outcome: "passed",
primary_period_window: null,
allowed_context_window: null,
controlled_temporal_expansion_enabled: false,
context_expansion_reasons_allowed: ["prehistory", "carryover", "post_period_closure", "long_running_contract_context"],
normalized_anchor_drift_detected: false,
reason_codes: ["analysis_context_applied"]
};
}
const rawAnchorText = collectRawTemporalAnchorText(input.userMessage, input.companyAnchors);
const julyAnchor = resolveJulyAnchor(rawAnchorText);
const normalizedAnchor = normalizedAnchorFromFragments(input.normalized);
@@ -654,9 +725,14 @@ function applyTemporalHintToExecutionPlan(executionPlan, temporal) {
return executionPlan;
}
const primaryWindow = temporal.effective_primary_period ?? temporal.primary_period_window;
const periodLabel = primaryWindow
? `${primaryWindow.from}..${primaryWindow.to}`
: temporal.resolved_time_anchor
? temporal.resolved_time_anchor
: "active_period";
const hint = primaryWindow?.granularity === "day" && temporal.resolved_time_anchor
? `primary period ${temporal.resolved_time_anchor}; controlled temporal expansion only for linked entities`
: `primary period July 2020 (${primaryWindow?.from ?? JULY_WINDOW.from}..${primaryWindow?.to ?? JULY_WINDOW.to}); controlled temporal expansion only for linked entities`;
: `primary period ${periodLabel}; controlled temporal expansion only for linked entities`;
return executionPlan.map((item) => {
if (!item.should_execute) {
return item;
@@ -1319,15 +1395,15 @@ function applyEligibilityToGroundingCheck(groundingCheck, eligibility) {
? "no_grounded_answer"
: "partial";
const reasonMap = {
admissible_evidence_count_zero: "Недостаточно допустимого evidence для обоснованного ответа.",
critical_domain_or_account_contradiction: "Есть критическое противоречие по domain/account scope.",
temporal_guard_failed_out_of_snapshot_window: "Temporal anchor вышел за окно company snapshot (июль 2020).",
temporal_guard_ambiguous_limited: "Temporal anchor не разрешен надежно в пределах company snapshot.",
business_scope_generic_unresolved: "Business scope остался generic и не подтвержден как company-specific для доказательного ответа.",
polarity_guard_limited_unresolved_polarity: "Не удалось надежно определить supplier/customer polarity.",
polarity_guard_blocked_conflict: "Обнаружен конфликт supplier/customer polarity в retrieval-контуре.",
claim_anchor_coverage_insufficient: "Недостаточно покрытия required anchors для claim-bound grounding.",
targeted_evidence_hit_rate_zero: "Targeted evidence acquisition не дал допустимых попаданий по claim target path."
admissible_evidence_count_zero: "Недостаточно подтвержденных данных для уверенного ответа.",
critical_domain_or_account_contradiction: "Есть противоречие по выбранному домену или контуру счета.",
temporal_guard_failed_out_of_snapshot_window: "Запрошенный период выходит за доступный срез данных.",
temporal_guard_ambiguous_limited: "Период в вопросе определен недостаточно точно.",
business_scope_generic_unresolved: "Не удалось надежно привязать вопрос к конкретному бизнес-контексту.",
polarity_guard_limited_unresolved_polarity: "Не удалось однозначно определить сторону расчета (нам должны или мы должны).",
polarity_guard_blocked_conflict: "В данных есть конфликт по стороне расчета.",
claim_anchor_coverage_insufficient: "Не хватает ключевых ориентиров в вопросе (период, объект или контрагент).",
targeted_evidence_hit_rate_zero: "Не хватило целевых подтверждений по выбранному сценарию."
};
const reasons = [
...(Array.isArray(groundingCheck.reasons) ? groundingCheck.reasons : []),
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,24 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.commitAssistantTurnAndLog = commitAssistantTurnAndLog;
function commitAssistantTurnAndLog(input) {
input.appendItem(input.sessionId, input.assistantItem);
const currentSession = input.getSession(input.sessionId);
if (currentSession) {
input.persistSession(currentSession);
}
const conversation = input.cloneConversation(currentSession?.items ?? []);
input.logEvent({
timestamp: (input.nowIso ?? (() => new Date().toISOString()))(),
level: "info",
service: "assistant_loop",
message: "assistant_message_processed",
sessionId: input.sessionId,
eventType: input.eventType,
details: input.logDetails
});
return {
currentSession,
conversation
};
}
+64
View File
@@ -169,6 +169,29 @@ function parseRawQuestions(rawQuestions) {
.filter(Boolean);
return byLine.length > 0 ? byLine : [text];
}
function normalizeAnalysisDate(value) {
if (typeof value !== "string") {
return null;
}
const trimmed = value.trim();
const match = trimmed.match(/^(\d{4})-(\d{2})-(\d{2})$/);
if (!match) {
return null;
}
const year = Number(match[1]);
const month = Number(match[2]);
const day = Number(match[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 `${match[1]}-${match[2]}-${match[3]}`;
}
function executionReadinessOf(fragment) {
return "execution_readiness" in fragment ? fragment.execution_readiness : "executable";
}
@@ -759,6 +782,13 @@ class EvalService {
...payload.normalizeConfig,
userQuestion: item.raw_question,
context: {
period_hint: payload.analysisDate ?? undefined,
analysis_context: payload.analysisDate
? {
as_of_date: payload.analysisDate,
source: "eval_analysis_date"
}
: undefined,
eval_label: runId,
case_id: item.case_id,
eval_mode: payload.mode
@@ -1553,6 +1583,7 @@ class EvalService {
const suite = parseAssistantSuiteFile(payload.caseSetFile);
const suiteCases = suite.cases.filter((item) => !payload.caseIds || payload.caseIds.includes(item.case_id));
const runId = typeof payload.runId === "string" && payload.runId.trim().length > 0 ? payload.runId.trim() : `assistant-stage1-${(0, nanoid_1.nanoid)(10)}`;
const analysisDate = normalizeAnalysisDate(payload.analysisDate);
const assistantService = new assistantService_1.AssistantService(this.normalizerService, new assistantSessionStore_1.AssistantSessionStore());
const diagnostics = [];
let requestsTotal = 0;
@@ -1579,6 +1610,15 @@ class EvalService {
developerPrompt: payload.normalizeConfig.developerPrompt,
domainPrompt: payload.normalizeConfig.domainPrompt,
fewShotExamples: payload.normalizeConfig.fewShotExamples,
context: analysisDate
? {
period_hint: analysisDate,
analysis_context: {
as_of_date: analysisDate,
source: "eval_analysis_date"
}
}
: undefined,
useMock: payload.useMock
}));
turnResponses.push(response);
@@ -1820,6 +1860,7 @@ class EvalService {
eval_target: "assistant_stage1",
mode: payload.mode,
use_mock: Boolean(payload.useMock),
analysis_date: analysisDate,
prompt_version: payload.normalizeConfig.promptVersion ?? null,
suite_id: suite.suite_id,
suite_version: suite.suite_version,
@@ -1887,6 +1928,7 @@ class EvalService {
const suite = parseAssistantStage2SuiteFile(payload.caseSetFile);
const suiteCases = suite.cases.filter((item) => !payload.caseIds || payload.caseIds.includes(item.case_id));
const runId = typeof payload.runId === "string" && payload.runId.trim().length > 0 ? payload.runId.trim() : `assistant-stage2-${(0, nanoid_1.nanoid)(10)}`;
const analysisDate = normalizeAnalysisDate(payload.analysisDate);
const assistantService = new assistantService_1.AssistantService(this.normalizerService, new assistantSessionStore_1.AssistantSessionStore());
const diagnostics = [];
let requestsTotal = 0;
@@ -1915,6 +1957,15 @@ class EvalService {
developerPrompt: payload.normalizeConfig.developerPrompt,
domainPrompt: payload.normalizeConfig.domainPrompt,
fewShotExamples: payload.normalizeConfig.fewShotExamples,
context: analysisDate
? {
period_hint: analysisDate,
analysis_context: {
as_of_date: analysisDate,
source: "eval_analysis_date"
}
}
: undefined,
useMock: payload.useMock
}));
turnResponses.push(response);
@@ -2090,6 +2141,7 @@ class EvalService {
eval_target: "assistant_stage2",
mode: payload.mode,
use_mock: Boolean(payload.useMock),
analysis_date: analysisDate,
prompt_version: payload.normalizeConfig.promptVersion ?? null,
suite_id: suite.suite_id,
suite_version: suite.suite_version,
@@ -2172,6 +2224,7 @@ class EvalService {
async run(payload) {
const mode = payload.mode ?? "standard";
const evalTarget = payload.evalTarget ?? "normalizer";
const analysisDate = normalizeAnalysisDate(payload.analysisDate);
if (evalTarget === "assistant_stage1") {
return this.runAssistantStage1({
normalizeConfig: payload.normalizeConfig,
@@ -2180,6 +2233,7 @@ class EvalService {
mode,
caseSetFile: payload.caseSetFile,
compareWithReportFile: payload.compareWithReportFile,
analysisDate: analysisDate ?? undefined,
runId: payload.runId
});
}
@@ -2191,6 +2245,7 @@ class EvalService {
mode,
caseSetFile: payload.caseSetFile,
compareWithReportFile: payload.compareWithReportFile,
analysisDate: analysisDate ?? undefined,
runId: payload.runId
});
}
@@ -2231,6 +2286,7 @@ class EvalService {
return this.runV2({
...payload,
mode,
analysisDate: analysisDate ?? undefined,
cases: filtered
});
}
@@ -2256,6 +2312,13 @@ class EvalService {
...payload.normalizeConfig,
userQuestion: item.raw_question,
context: {
period_hint: analysisDate ?? undefined,
analysis_context: analysisDate
? {
as_of_date: analysisDate,
source: "eval_analysis_date"
}
: undefined,
expected_route: item.expected.route_hint,
eval_label: runId,
case_id: item.case_id,
@@ -2366,6 +2429,7 @@ class EvalService {
timestamp: new Date().toISOString(),
mode,
use_mock: Boolean(payload.useMock),
analysis_date: analysisDate,
prompt_version: payload.normalizeConfig.promptVersion ?? null,
dataset: {
source: payload.caseSetFile ? "file" : "data/eval_cases/*.json",
@@ -99,7 +99,7 @@ function resolveQuestionType(input) {
if (bestType !== "unknown") {
return bestType;
}
if (/[?пјџ]/u.test(text)) {
if (/(?:\bwhy\b|почему|из-?за\s+чего|в\s+ч(?:е|ё)м\s+причина)/iu.test(text)) {
return "why_breaks";
}
return "unknown";