Этап 4 / Волна 16: смысловая изоляция РБП и ОС, фиксы лайв-ответов и добивка экспорта
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
// @ts-nocheck
|
||||
// @ts-nocheck
|
||||
import * as nanoid_1 from "nanoid";
|
||||
import * as stage1Contracts_1 from "../types/stage1Contracts";
|
||||
import * as config_1 from "../config";
|
||||
@@ -41,6 +41,30 @@ function extractFragments(normalized) {
|
||||
const source = normalized;
|
||||
return Array.isArray(source.fragments) ? source.fragments : [];
|
||||
}
|
||||
function hasExplicitPeriodAnchorFromNormalized(normalized) {
|
||||
const fragments = extractFragments(normalized);
|
||||
const explicitPeriodPattern = /(?:\b20\d{2}(?:[-./](?:0?[1-9]|1[0-2]))?(?:[-./](?:0?[1-9]|[12]\d|3[01]))?\b|\b(?:0?[1-9]|[12]\d|3[01])[./-](?:0?[1-9]|1[0-2])[./-](?:\d{2}|\d{4})\b|\b(?:январ[ьяе]|феврал[ьяе]|март[ае]?|апрел[ьяе]|ма[йея]|июн[ьяе]|июл[ьяе]|август[ае]?|сентябр[ьяе]|октябр[ьяе]|ноябр[ьяе]|декабр[ьяе]|january|february|march|april|may|june|july|august|september|october|november|december)\b)/i;
|
||||
for (const item of fragments) {
|
||||
if (!item || typeof item !== "object") {
|
||||
continue;
|
||||
}
|
||||
const fragment = item;
|
||||
const timeScope = fragment.time_scope && typeof fragment.time_scope === "object" ? fragment.time_scope : null;
|
||||
if (timeScope) {
|
||||
const type = String(timeScope.type ?? "").trim().toLowerCase();
|
||||
const value = String(timeScope.value ?? "").trim();
|
||||
const confidence = String(timeScope.confidence ?? "").trim().toLowerCase();
|
||||
if ((type === "explicit" || type === "range") && value.length > 0 && confidence !== "low") {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
const rawText = `${typeof fragment.raw_fragment_text === "string" ? fragment.raw_fragment_text : ""} ${typeof fragment.normalized_fragment_text === "string" ? fragment.normalized_fragment_text : ""}`;
|
||||
if (explicitPeriodPattern.test(rawText)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
function extractExecutionState(normalized) {
|
||||
const fragments = extractFragments(normalized);
|
||||
return fragments.map((item) => {
|
||||
@@ -205,7 +229,7 @@ function extractAccountTokens(text) {
|
||||
return Array.from(explicitAccounts);
|
||||
}
|
||||
const spans = collectDateSpans(lower);
|
||||
const hasAccountingLexeme = /(?:\bсчет(?:а|у|ом|ов)?\b|\bсч\.?\b|\baccount(?:s)?\b|\bschet(?:a|u|om|ov)?\b|оплат|расчет|аванс|долг|settlement|payment|счет|СЃС‡\.?)/iu.test(lower);
|
||||
const hasAccountingLexeme = /(?:\bсчет(?:а|у|ом|ов)?\b|\bсч\.?\b|\baccount(?:s)?\b|\bschet(?:a|u|om|ov)?\b|оплат|расчет|аванс|долг|settlement|payment)/iu.test(lower);
|
||||
if (!hasAccountingLexeme) {
|
||||
return [];
|
||||
}
|
||||
@@ -394,9 +418,9 @@ function buildSkippedResult(item) {
|
||||
why_included: [],
|
||||
selection_reason: [mapNoRouteReason(item.no_route_reason)],
|
||||
risk_factors: [],
|
||||
business_interpretation: ["Данный фрагмент не был выполнен из-за no-route решения."],
|
||||
business_interpretation: ["Данный фрагмент не был выполнен из-за no-route решения."],
|
||||
confidence: "low",
|
||||
limitations: ["Фрагмент требует уточнения или отсутствует поддерживаемый маршрут."],
|
||||
limitations: ["Фрагмент требует уточнения или отсутствует поддерживаемый маршрут."],
|
||||
errors: []
|
||||
});
|
||||
}
|
||||
@@ -643,28 +667,28 @@ function checkGrounding(userMessage, requirements, coverage, retrievalResults) {
|
||||
const reasons = [];
|
||||
if (!routeSubjectMatch) {
|
||||
status = "route_mismatch_blocked";
|
||||
reasons.push(`Не подтверждены критичные предметные токены запроса: ${missingCriticalTokens.join(", ")}`);
|
||||
reasons.push(`Не подтверждены критичные предметные токены запроса: ${missingCriticalTokens.join(", ")}`);
|
||||
}
|
||||
else if (accountOnlyMismatchRecoverable) {
|
||||
status = "partial";
|
||||
reasons.push(`Рчет-токены не подтверждены напрямую (${missingCriticalTokens.join(", ")}), но есть релевантная опора для ограниченного вывода.`);
|
||||
reasons.push(`Счет-токены не подтверждены напрямую (${missingCriticalTokens.join(", ")}), но есть релевантная опора для ограниченного вывода.`);
|
||||
}
|
||||
else if (coverage.requirements_covered === 0) {
|
||||
status = "no_grounded_answer";
|
||||
reasons.push("Ни одно требование не получило подтвержденного покрытия.");
|
||||
reasons.push("Ни одно требование не получило подтвержденного покрытия.");
|
||||
}
|
||||
else if (coverage.requirements_uncovered.length > 0 ||
|
||||
coverage.requirements_partially_covered.length > 0 ||
|
||||
coverage.clarification_needed_for.length > 0 ||
|
||||
coverage.out_of_scope_requirements.length > 0) {
|
||||
status = "partial";
|
||||
reasons.push("Вопрос покрыт частично: есть непокрытые или требующие уточнения требования.");
|
||||
reasons.push("Вопрос покрыт частично: есть непокрытые или требующие уточнения требования.");
|
||||
}
|
||||
if (whyIncludedSummary.length === 0) {
|
||||
reasons.push("Нет explainable-сигналов why_included в результатах выборки.");
|
||||
reasons.push("Нет explainable-сигналов why_included в результатах выборки.");
|
||||
}
|
||||
if (missingSubjectTokens.length > 0 && missingCriticalTokens.length === 0) {
|
||||
reasons.push(`Часть контекстных токенов не подтверждена напрямую: ${missingSubjectTokens.join(", ")}`);
|
||||
reasons.push(`Часть контекстных токенов не подтверждена напрямую: ${missingSubjectTokens.join(", ")}`);
|
||||
}
|
||||
const missingRequirements = [
|
||||
...coverage.requirements_uncovered,
|
||||
@@ -727,10 +751,10 @@ function buildAnswerStructureV11(input) {
|
||||
})), 8);
|
||||
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 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}.`)
|
||||
...input.coverageReport.requirements_uncovered.map((item) => `Проверить непокрытое требование ${item}.`),
|
||||
...input.coverageReport.requirements_partially_covered.map((item) => `Доуточнить частично покрытое требование ${item}.`)
|
||||
], 6);
|
||||
const mechanismStatus = mechanismNotes.length === 0
|
||||
? "unresolved"
|
||||
@@ -773,7 +797,8 @@ const FOLLOWUP_ROUTE_HINTS = new Set(["store_canonical", "store_feature_risk", "
|
||||
const FOLLOWUP_ACTIVE_DOMAIN_ROUTE_MAP = {
|
||||
settlements_60_62: "hybrid_store_plus_live",
|
||||
vat_document_register_book: "hybrid_store_plus_live",
|
||||
month_close_costs_20_44: "hybrid_store_plus_live"
|
||||
month_close_costs_20_44: "hybrid_store_plus_live",
|
||||
fixed_asset_amortization: "hybrid_store_plus_live"
|
||||
};
|
||||
const FOLLOWUP_BUSINESS_CONTEXT_MAX = 320;
|
||||
const FOLLOWUP_SUBJECT_MAX = 160;
|
||||
@@ -786,17 +811,17 @@ function hasAccountingSignal(text) {
|
||||
if (/(?:^|[\s,;:])\d{2}(?:\.\d{2})?(?=$|[\s,.;:])/i.test(lower)) {
|
||||
return true;
|
||||
}
|
||||
return /(РїСЂРѕРІРѕРґРє|документ|реализац|поступлен|взаиморасчет|сальдо|остатк|счет|РЅРґСЃ|амортиз|СЂР±Рї|РѕСЃ|контрагент|поставщик|покупател|оплат|банк|выписк|склад|товар|материал|проводк|документ|реализац|поступлен|взаиморасчет|сальдо|остатк|счет|счёт|ндс|амортиз|рбп|контрагент|поставщик|покупател|оплат|банк|выписк|склад|товар|материал|закрыти|период|postavshchik|kontragent|schet|schetu|period|counterparty|supplier|invoice|posting|ledger|account|anomaly|risk)/i.test(lower);
|
||||
return /(проводк|документ|реализац|поступлен|взаиморасчет|сальдо|остатк|счет|счёт|ндс|амортиз|рбп|ос|контрагент|поставщик|покупател|оплат|банк|выписк|склад|товар|материал|закрыти|период|postavshchik|kontragent|schet|schetu|period|counterparty|supplier|invoice|posting|ledger|account|anomaly|risk)/i.test(lower);
|
||||
}
|
||||
function hasFollowupMarker(text) {
|
||||
const compact = compactWhitespace(text.toLowerCase());
|
||||
return /^(Рё|Р° еще|Р° ещё|еще|ещё|добав|уточн|продолж|также|и|а если|а еще|а ещё|еще|ещё|добав|уточн|продолж|также|plus|also|dobav|utochn|prodolzh)/i.test(compact);
|
||||
return /^(и|а еще|а ещё|еще|ещё|добав|уточн|продолж|также|а если|plus|also|dobav|utochn|prodolzh)/i.test(compact);
|
||||
}
|
||||
function hasReferentialPointer(text) {
|
||||
return /(РїРѕ этому|РїРѕ тому|это Р¶Рµ|этой|этим|тому|по этому|по тому|это же|этой|этим|этому|из этого|в этом|тот же|same thing|that one|po etomu|po tomu)/i.test(text.toLowerCase());
|
||||
return /(по этому|по тому|это же|этой|этим|этому|из этого|в этом|тот же|same thing|that one|po etomu|po tomu)/i.test(text.toLowerCase());
|
||||
}
|
||||
function hasSmallTalkSignal(text) {
|
||||
return /(привет|как дела|спасибо|привет|как дела|спасибо|благодарю|thanks|thank you|hello|hi)\b/i.test(text.toLowerCase());
|
||||
return /(привет|как дела|спасибо|благодарю|thanks|thank you|hello|hi)\b/i.test(text.toLowerCase());
|
||||
}
|
||||
function countTokens(text) {
|
||||
return compactWhitespace(text)
|
||||
@@ -840,12 +865,17 @@ function inferP0DomainFromMessage(text) {
|
||||
const hasVatAccount = accountTokens.some((token) => /^(?:19|68)(?:\.|$)/.test(token));
|
||||
const hasSettlementAccount = accountTokens.some((token) => /^(?:51|60|62|76)(?:\.|$)/.test(token));
|
||||
const hasMonthCloseAccount = accountTokens.some((token) => /^(?:97|2\d|3\d|4[0-4])(?:\.|$)/.test(token));
|
||||
const vatLexical = /(?:ндс|vat|счет[\s-]?фактур|сч[её]т[\s-]?фактур|книг[аи]\s+(?:покуп|продаж)|налогов)/i.test(lower);
|
||||
const hasFixedAssetAccount = accountTokens.some((token) => /^(?:01|02|08)(?:\.|$)/.test(token));
|
||||
const vatLexical = /(?:ндс|vat|сч[её]т[\s-]?фактур|книг[аи]\s+(?:покуп|продаж)|налогов)/i.test(lower);
|
||||
const settlementLexical = /(?:долг|аванс|зач[её]т|взаимозач|расч[её]т|оплат|платеж|платёж|постав|покупател)/i.test(lower);
|
||||
const monthCloseLexical = /(?:закрыти[ея]\s+месяц|закрытие счетов|регламентн|косвенн|затрат|распределени|рбп|амортиз|финансовых результат)/i.test(lower);
|
||||
const monthCloseLexical = /(?:закрыти[ея]\s+месяц|закрытие\s+счетов|регламентн|косвенн|затрат|распределени|рбп|финансовых\s+результат)/i.test(lower);
|
||||
const fixedAssetLexical = /(?:основн(?:ые|ых)?\s+сред|(?:^|[^a-zа-яё])ос(?:$|[^a-zа-яё])|амортиз|depreciat|fixed\s*asset)/i.test(lower);
|
||||
if (hasVatAccount || vatLexical) {
|
||||
return "vat_document_register_book";
|
||||
}
|
||||
if (fixedAssetLexical || hasFixedAssetAccount) {
|
||||
return "fixed_asset_amortization";
|
||||
}
|
||||
if (monthCloseLexical || hasMonthCloseAccount) {
|
||||
return "month_close_costs_20_44";
|
||||
}
|
||||
@@ -1018,12 +1048,12 @@ function buildFollowupStateBinding(input) {
|
||||
const shouldAugmentQuestion = Boolean(subject) && (followupMarker || referentialPointer || !strongSignal);
|
||||
let normalizedQuestion = userMessage;
|
||||
if (shouldAugmentQuestion) {
|
||||
const appendParts = [`Фокус текущего разбора: ${subject}`];
|
||||
const appendParts = [`Фокус текущего разбора: ${subject}`];
|
||||
if (input.investigationState.focus.primary_accounts.length > 0 && !/\b\d{2}(?:\.\d{2})?\b/.test(userMessage)) {
|
||||
appendParts.push(`Счета фокуса: ${input.investigationState.focus.primary_accounts.join(", ")}`);
|
||||
appendParts.push(`Счета фокуса: ${input.investigationState.focus.primary_accounts.join(", ")}`);
|
||||
}
|
||||
if (periodHintFromState && !hasPeriodLiteral(userMessage)) {
|
||||
appendParts.push(`Период фокуса: ${periodHintFromState}`);
|
||||
appendParts.push(`Период фокуса: ${periodHintFromState}`);
|
||||
}
|
||||
const appendBlock = withCappedLength(compactWhitespace(appendParts.join("; ")), FOLLOWUP_QUESTION_APPEND_MAX);
|
||||
normalizedQuestion = `${userMessage}\n${appendBlock}`.trim();
|
||||
@@ -1187,6 +1217,9 @@ export class AssistantService {
|
||||
: null;
|
||||
const questionTypeClass = (0, questionTypeResolver_1.resolveQuestionType)(userMessage);
|
||||
const companyAnchors = (0, companyAnchorResolver_1.resolveCompanyAnchors)(userMessage);
|
||||
const hasPeriodInCompanyAnchors = (Array.isArray(companyAnchors?.dates) && companyAnchors.dates.some((item) => String(item ?? "").trim().length > 0)) ||
|
||||
(Array.isArray(companyAnchors?.periods) && companyAnchors.periods.some((item) => String(item ?? "").trim().length > 0));
|
||||
const normalizationPeriodExplicit = hasExplicitPeriodAnchorFromNormalized(normalized.normalized) || hasPeriodInCompanyAnchors;
|
||||
const composition = (0, answerComposer_1.composeAssistantAnswer)({
|
||||
userMessage,
|
||||
routeSummary: normalized.route_hint_summary,
|
||||
@@ -1197,15 +1230,21 @@ export class AssistantService {
|
||||
focusDomainHint,
|
||||
questionTypeHint: questionTypeClass,
|
||||
companyAnchors,
|
||||
normalizationPeriodExplicit,
|
||||
enableAnswerPolicyV11: config_1.FEATURE_ASSISTANT_ANSWER_POLICY_V11,
|
||||
enableProblemCentricAnswerV1: config_1.FEATURE_ASSISTANT_PROBLEM_CENTRIC_ANSWER_V1,
|
||||
enableLifecycleAnswerV1: config_1.FEATURE_ASSISTANT_LIFECYCLE_ANSWER_V1
|
||||
});
|
||||
const safeAssistantReplyBase = (0, answerComposer_1.sanitizeAssistantReplyForUserFacing)(composition.assistant_reply);
|
||||
const safeAssistantReply = String(safeAssistantReplyBase ?? "")
|
||||
.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();
|
||||
const answerStructureV11 = config_1.FEATURE_ASSISTANT_CONTRACTS_V11
|
||||
? config_1.FEATURE_ASSISTANT_ANSWER_POLICY_V11 && composition.answer_structure_v11
|
||||
? composition.answer_structure_v11
|
||||
: buildAnswerStructureV11({
|
||||
assistantReply: composition.assistant_reply,
|
||||
assistantReply: safeAssistantReply,
|
||||
coverageReport: coverageEvaluation.coverage,
|
||||
groundingCheck,
|
||||
retrievalResults
|
||||
@@ -1266,7 +1305,7 @@ export class AssistantService {
|
||||
message_id: `msg-${(0, nanoid_1.nanoid)(10)}`,
|
||||
session_id: sessionId,
|
||||
role: "assistant",
|
||||
text: composition.assistant_reply,
|
||||
text: safeAssistantReply,
|
||||
reply_type: composition.reply_type,
|
||||
created_at: new Date().toISOString(),
|
||||
trace_id: normalized.trace_id,
|
||||
@@ -1326,7 +1365,7 @@ export class AssistantService {
|
||||
answer_structure_v11: answerStructureV11,
|
||||
investigation_state_snapshot: investigationStateSnapshot,
|
||||
fallback_type: composition.fallback_type,
|
||||
assistant_reply: composition.assistant_reply,
|
||||
assistant_reply: safeAssistantReply,
|
||||
reply_type: composition.reply_type,
|
||||
trace_id: normalized.trace_id
|
||||
}
|
||||
@@ -1334,7 +1373,7 @@ export class AssistantService {
|
||||
return {
|
||||
ok: true,
|
||||
session_id: sessionId,
|
||||
assistant_reply: composition.assistant_reply,
|
||||
assistant_reply: safeAssistantReply,
|
||||
reply_type: composition.reply_type,
|
||||
conversation_item: assistantItem,
|
||||
debug,
|
||||
@@ -1343,3 +1382,4 @@ export class AssistantService {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user