Этап 4 / Волна 16: смысловая изоляция РБП и ОС, фиксы лайв-ответов и добивка экспорта
This commit is contained in:
parent
6123fafd5b
commit
7eb1410501
|
|
@ -1,5 +1,6 @@
|
|||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.sanitizeAssistantReplyForUserFacing = sanitizeAssistantReplyForUserFacing;
|
||||
exports.composeAssistantAnswer = composeAssistantAnswer;
|
||||
function fallbackFromSummary(routeSummary) {
|
||||
if (!routeSummary || routeSummary.mode !== "deterministic_v2") {
|
||||
|
|
@ -207,6 +208,10 @@ const HUMAN_SIGNAL_MAP = {
|
|||
amount_independent_risk: "Проблема не выглядит случайной суммовой погрешностью.",
|
||||
wrong_document_type: "Есть признак неверного типа закрывающего документа.",
|
||||
fixed_asset_card_mismatch: "Есть несоответствие между карточкой ОС, документом движения и начислением.",
|
||||
contradictory_asset_state: "Состояние объекта ОС выглядит противоречивым по текущей опоре.",
|
||||
disposed: "Есть признак выбытия объекта ОС в цепочке состояния.",
|
||||
invalid_document_or_posting_transition: "Переход состояния ОС не подтвержден документами и проводками.",
|
||||
asset_card_to_depreciation: "Переход от карточки ОС к начислению амортизации подтвержден не полностью.",
|
||||
supplier_tail_analysis: "Есть признаки незавершенного расчетного контура по поставщикам.",
|
||||
cross_entity_breakage: "Есть разрыв между связанными объектами в одной цепочке.",
|
||||
deferred_expense_to_writeoff: "Ожидаемая цепочка списания РБП выглядит незавершенной.",
|
||||
|
|
@ -576,8 +581,13 @@ function stripSyntheticPlaceholders(value) {
|
|||
.trim();
|
||||
}
|
||||
function sanitizeUserFacingReply(value) {
|
||||
const withoutDebugBlocks = String(value ?? "")
|
||||
const raw = String(value ?? "");
|
||||
const hardCutMatch = raw.match(/(?:^|\n)\s*#{0,6}\s*(?:debug_payload_json|technical_breakdown_json)\b/i);
|
||||
const preCut = hardCutMatch ? raw.slice(0, hardCutMatch.index) : raw;
|
||||
const withoutDebugBlocks = preCut
|
||||
.replace(/###\s*debug_payload_json[\s\S]*?(?:```[\s\S]*?```|$)/gi, "")
|
||||
.replace(/###\s*technical_breakdown_json[\s\S]*?(?:```[\s\S]*?```|$)/gi, "")
|
||||
.replace(/(?:^|\n)\s*#{0,6}\s*(?:debug_payload_json|technical_breakdown_json)\b[\s\S]*$/gi, "")
|
||||
.replace(/```json[\s\S]*?```/gi, "");
|
||||
const normalized = scrubRawTechnicalRefs(withoutDebugBlocks).replace(/[ \t]+\n/g, "\n");
|
||||
const cleanedLines = normalized
|
||||
|
|
@ -1174,7 +1184,7 @@ function buildProblemCentricActions(input) {
|
|||
}
|
||||
}
|
||||
if (input.missingAnchors.period && input.mode !== "clarification_required") {
|
||||
actions.push("Уточните период проверки (например, 2020-06), чтобы подтвердить незавершенное списание без лишнего шума.");
|
||||
actions.push("Уточните период проверки (например, июль 2020), чтобы подтвердить незавершенное списание без лишнего шума.");
|
||||
}
|
||||
if (input.mode === "clarification_required") {
|
||||
if (input.missingAnchors.period) {
|
||||
|
|
@ -1202,7 +1212,7 @@ function buildProblemCentricClarifications(input) {
|
|||
const questions = [];
|
||||
const unitTypes = new Set(input.units.map((item) => item.problem_unit_type));
|
||||
if (input.missingAnchors.period) {
|
||||
questions.push("Уточните период (например, 2020-06), в котором нужно проверить проблемный кластер.");
|
||||
questions.push("Уточните период (например, июль 2020), в котором нужно проверить проблемный кластер.");
|
||||
}
|
||||
if (input.missingAnchors.account) {
|
||||
questions.push("Уточните счет или связку счетов (например, 51/60), где вы ожидаете дефект.");
|
||||
|
|
@ -1338,6 +1348,14 @@ function asRecordObject(value) {
|
|||
return value;
|
||||
}
|
||||
const EXPLICIT_PERIOD_ANCHOR_PATTERN = /(?:\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;
|
||||
function hasPeriodAnchorInCompanyAnchors(anchors) {
|
||||
if (!anchors) {
|
||||
return false;
|
||||
}
|
||||
const dates = Array.isArray(anchors.dates) ? anchors.dates : [];
|
||||
const periods = Array.isArray(anchors.periods) ? anchors.periods : [];
|
||||
return dates.some((item) => String(item ?? "").trim().length > 0) || periods.some((item) => String(item ?? "").trim().length > 0);
|
||||
}
|
||||
function hasPeriodAnchorInRetrieval(results) {
|
||||
for (const result of results) {
|
||||
const summary = asRecordObject(result.summary);
|
||||
|
|
@ -1378,9 +1396,12 @@ function hasAccountAnchorInRetrieval(results) {
|
|||
}
|
||||
return false;
|
||||
}
|
||||
function detectMissingAnchors(userMessage, retrievalResults = []) {
|
||||
function detectMissingAnchors(userMessage, retrievalResults = [], options) {
|
||||
const lower = String(userMessage ?? "").toLowerCase();
|
||||
const hasPeriod = EXPLICIT_PERIOD_ANCHOR_PATTERN.test(lower) || hasPeriodAnchorInRetrieval(retrievalResults);
|
||||
const hasPeriod = EXPLICIT_PERIOD_ANCHOR_PATTERN.test(lower) ||
|
||||
hasPeriodAnchorInRetrieval(retrievalResults) ||
|
||||
Boolean(options?.normalizationPeriodExplicit) ||
|
||||
hasPeriodAnchorInCompanyAnchors(options?.companyAnchors);
|
||||
const hasAccount = /(?:\bсчет\b|\baccount\b|\bschet\b|\b(?:0[1-9]|[1-9]\d)(?:\.\d{2})?\b|\b(?:60|62)\.\d{2}\s*\/\s*(?:60|62)\.\d{2}\b)/i.test(lower) || hasAccountAnchorInRetrieval(retrievalResults);
|
||||
const hasDocumentOrObject = /(?:документ|invoice|guid|object|obj|#\d+|\b№\s*[a-zа-я0-9-]+\b|\bid\b|\bref\b|dokument|doc)/i.test(lower);
|
||||
const hasCounterparty = /(?:контрагент|supplier|buyer|customer|kontragent|postavsh|pokupatel|договор|contract)/i.test(lower);
|
||||
|
|
@ -1400,7 +1421,7 @@ function buildClarificationQuestions(input) {
|
|||
return questions;
|
||||
}
|
||||
if (input.missingAnchors.period) {
|
||||
questions.push("Уточните период проверки (например, 2020-06).");
|
||||
questions.push("Уточните период проверки (например, июль 2020).");
|
||||
}
|
||||
if (input.missingAnchors.account) {
|
||||
questions.push("Уточните счет или группу счетов (например, 19, 60, 62).");
|
||||
|
|
@ -1798,8 +1819,8 @@ function inferP0NarrativeDomain(units) {
|
|||
return "vat_document_register_book";
|
||||
}
|
||||
if (hasCloseAccount ||
|
||||
units.some((unit) => ["period_close", "deferred_expense", "fixed_asset"].includes(String(unit.lifecycle_domain ?? ""))) ||
|
||||
units.some((unit) => unit.problem_unit_type === "period_risk_cluster" || unit.problem_unit_type === "lifecycle_anomaly_node")) {
|
||||
units.some((unit) => ["period_close", "deferred_expense"].includes(String(unit.lifecycle_domain ?? ""))) ||
|
||||
units.some((unit) => unit.problem_unit_type === "period_risk_cluster")) {
|
||||
return "month_close_costs_20_44";
|
||||
}
|
||||
return null;
|
||||
|
|
@ -1842,8 +1863,7 @@ function p0NarrativeDomainFromHint(value) {
|
|||
}
|
||||
if (normalized.includes("month_close_costs_20_44") ||
|
||||
normalized.includes("period_close") ||
|
||||
normalized.includes("deferred_expense") ||
|
||||
normalized.includes("fixed_asset")) {
|
||||
normalized.includes("deferred_expense")) {
|
||||
return "month_close_costs_20_44";
|
||||
}
|
||||
return null;
|
||||
|
|
@ -2014,7 +2034,21 @@ function evaluateP0DomainEvidenceGrounding(results, focusDomain) {
|
|||
const topClass = classify(top);
|
||||
const hasAnyPrimary = substantive.some((item) => classify(item).inDomain);
|
||||
const hasForeignPrimary = topClass.foreignDomains.length > 0 && !topClass.inDomain;
|
||||
const blocked = hasForeignPrimary && !hasAnyPrimary && !hasControlledCrossDomainHandoffInResult(top);
|
||||
const topAccounts = collectResultAccounts(top);
|
||||
const topDomains = collectResultDomains(top);
|
||||
const topRelations = collectResultRelations(top);
|
||||
const vatPrimarySignals = topAccounts.filter((item) => isVatAccountToken(item)).length +
|
||||
topDomains.filter((item) => isVatDomainToken(item)).length +
|
||||
topRelations.filter((item) => /invoice_to_vat|source_doc_present|invoice_linked|register_to_book|book_entry_generated|deduction_posted|vat_/i.test(item)).length;
|
||||
const vatForeignSignals = topAccounts.filter((item) => isSettlementAccountToken(item) || isCloseCostsAccountToken(item)).length +
|
||||
topDomains.filter((item) => isForeignToVatDomainToken(item)).length +
|
||||
topRelations.filter((item) => /payment_to_settlement|statement_to_document|deferred_expense_to_writeoff|close_operation|allocation|period_close|fixed_asset/i.test(item)).length;
|
||||
const vatContaminatedPrimary = focusDomain === "vat_document_register_book" &&
|
||||
topClass.inDomain &&
|
||||
topClass.foreignDomains.length > 0 &&
|
||||
vatForeignSignals > Math.max(1, vatPrimarySignals) &&
|
||||
!hasControlledCrossDomainHandoffInResult(top);
|
||||
const blocked = (hasForeignPrimary && !hasAnyPrimary && !hasControlledCrossDomainHandoffInResult(top)) || vatContaminatedPrimary;
|
||||
return {
|
||||
has_primary: hasAnyPrimary,
|
||||
has_foreign_primary: hasForeignPrimary,
|
||||
|
|
@ -2038,21 +2072,35 @@ function hasStrongNarrativeDomainSignalInText(userMessage, domain) {
|
|||
}
|
||||
if (domain === "month_close_costs_20_44") {
|
||||
return (accountTokens.some((item) => isCloseCostsAccountToken(item)) ||
|
||||
/(закрыти[ея]\s+месяц|закрытие\s+счетов|регламентн|косвенн|затрат|распределени|рбп|амортиз|финансовых\s+результат|month\s*close|period\s*close|close\s+operation)/i.test(text));
|
||||
/(закрыти[ея]\s+месяц|закрытие\s+счетов|регламентн|косвенн|затрат|распределени|рбп|финансовых\s+результат|month\s*close|period\s*close|close\s+operation)/i.test(text));
|
||||
}
|
||||
return false;
|
||||
}
|
||||
function hasFixedAssetAmortizationSignalInText(userMessage) {
|
||||
const text = String(userMessage ?? "").toLowerCase();
|
||||
const explicitFixedAssetAccountMention = /(?:сч(?:е|ё)т(?:а|у|ом|ов)?\s*(?:№|#|:)?\s*0[12](?:\.\d{1,2})?|\b0[12]\s*\/\s*0[12]\b)/iu.test(text);
|
||||
return (explicitFixedAssetAccountMention ||
|
||||
/(основн(ые|ых|ым)?\s+средств|(?:^|[^a-zа-яё])ос(?:$|[^a-zа-яё])|амортиз|depreciat|fixed\s*asset)/i.test(text));
|
||||
}
|
||||
function hasExplicitMonthCloseSignalInText(userMessage) {
|
||||
const text = String(userMessage ?? "").toLowerCase();
|
||||
return /(закрыти[ея]\s+месяц|закрытие\s+счетов|регламентн|косвенн|затрат|распределени|рбп|финансовых\s+результат|month\s*close|period\s*close|close\s+operation)/i.test(text);
|
||||
}
|
||||
function inferP0FocusNarrativeDomain(userMessage, results, units, focusDomainHint) {
|
||||
const fromHint = p0NarrativeDomainFromHint(focusDomainHint);
|
||||
const fromMessage = inferNarrativeDomainFromText(userMessage);
|
||||
const strongFromMessage = Boolean(fromMessage && hasStrongNarrativeDomainSignalInText(userMessage, fromMessage));
|
||||
const fromDomainGuard = inferP0NarrativeDomainFromDomainGuards(results);
|
||||
const fixedAssetOnlySignal = hasFixedAssetAmortizationSignalInText(userMessage) && !hasExplicitMonthCloseSignalInText(userMessage);
|
||||
if (fromHint && fromMessage && fromHint !== fromMessage) {
|
||||
return strongFromMessage ? fromMessage : fromHint;
|
||||
}
|
||||
if (fromHint) {
|
||||
return fromHint;
|
||||
}
|
||||
if (fromDomainGuard === "month_close_costs_20_44" && fixedAssetOnlySignal) {
|
||||
return null;
|
||||
}
|
||||
if (fromDomainGuard && fromMessage && fromDomainGuard !== fromMessage) {
|
||||
return strongFromMessage ? fromMessage : fromDomainGuard;
|
||||
}
|
||||
|
|
@ -2333,6 +2381,7 @@ function buildProblemCentricAnswerStructure(input) {
|
|||
], 10);
|
||||
const openUncertainties = uniqueStrings([
|
||||
...input.groundingCheck.missing_requirements,
|
||||
...(input.domainLockMiss ? ["primary_domain_evidence_not_confirmed"] : []),
|
||||
...(input.missingAnchors.period ? ["missing_anchor:period"] : []),
|
||||
...(input.mode === "clarification_required" && input.missingAnchors.account ? ["missing_anchor:account"] : []),
|
||||
...(input.mode === "clarification_required" && input.missingAnchors.documentOrObject
|
||||
|
|
@ -2415,6 +2464,8 @@ function limitationReasonToUserText(code) {
|
|||
function inferNarrativeDomainFromText(value) {
|
||||
const text = String(value ?? "").toLowerCase();
|
||||
const accountTokens = extractAccountNumbersFromNarrativeText(text);
|
||||
const fixedAssetSignal = hasFixedAssetAmortizationSignalInText(text);
|
||||
const explicitMonthCloseSignal = hasExplicitMonthCloseSignalInText(text);
|
||||
let settlementScore = 0;
|
||||
let vatScore = 0;
|
||||
let monthCloseScore = 0;
|
||||
|
|
@ -2436,9 +2487,12 @@ function inferNarrativeDomainFromText(value) {
|
|||
if (/(ндс|vat|сч[её]т(?:а|у|ом|е)?[-\s]?фактур(?:а|ы|е|у|ой)?|книг[аи]|регистр|вычет|налогов(?:ый|ого)?\s+эффект)/i.test(text)) {
|
||||
vatScore += 3;
|
||||
}
|
||||
if (/(закрыти[ея]\s+месяц|закрытие\s+счетов|регламентн|косвенн|затрат|распределени|рбп|амортиз|финансовых\s+результат|month\s*close|period\s*close|close\s+operation)/i.test(text)) {
|
||||
if (explicitMonthCloseSignal) {
|
||||
monthCloseScore += 3;
|
||||
}
|
||||
if (fixedAssetSignal && !explicitMonthCloseSignal && settlementScore === 0 && vatScore === 0) {
|
||||
return null;
|
||||
}
|
||||
const maxScore = Math.max(settlementScore, vatScore, monthCloseScore);
|
||||
if (maxScore <= 0) {
|
||||
return null;
|
||||
|
|
@ -2487,9 +2541,42 @@ function buildShortSectionLine(structure) {
|
|||
}
|
||||
return incomplete ? "Проблема подтверждается частично на текущей опоре." : "Проблема подтверждена на текущей опоре.";
|
||||
}
|
||||
function humanizeCompositeDirectAnswer(value) {
|
||||
const raw = String(value ?? "").trim();
|
||||
if (!raw) {
|
||||
return null;
|
||||
}
|
||||
const tokenPattern = /\b[a-z][a-z0-9_:-]{2,}\b/gi;
|
||||
const tokenMappings = uniqueStrings(Array.from(raw.matchAll(tokenPattern))
|
||||
.map((match) => humanizeTechnicalToken(String(match?.[0] ?? "")))
|
||||
.filter((item) => Boolean(item))
|
||||
.map((item) => ensureSentence(item)), 4);
|
||||
const residualRaw = raw
|
||||
.replace(tokenPattern, " ")
|
||||
.replace(/[()]/g, " ")
|
||||
.replace(/\s*[;:]\s*/g, " ")
|
||||
.replace(/\s{2,}/g, " ")
|
||||
.trim();
|
||||
const residualText = sanitizeUserText(residualRaw);
|
||||
const lines = [...tokenMappings];
|
||||
if (residualText && !hasUserFacingLeakage(residualText)) {
|
||||
lines.push(ensureSentence(residualText));
|
||||
}
|
||||
const compact = dedupeNarrativeLines(lines, 3);
|
||||
if (compact.length === 0) {
|
||||
return null;
|
||||
}
|
||||
return compact.join(" ");
|
||||
}
|
||||
function buildBrokenSectionLines(structure) {
|
||||
const direct = sanitizeUserText(structure.direct_answer);
|
||||
if (direct) {
|
||||
if (/\b[a-z]+_[a-z0-9_:-]+\b/i.test(direct)) {
|
||||
const compositeHumanized = humanizeCompositeDirectAnswer(direct);
|
||||
if (compositeHumanized) {
|
||||
return [compositeHumanized];
|
||||
}
|
||||
}
|
||||
const mapped = mapDefectTokenToNarrative(direct) ?? humanizeTechnicalToken(direct);
|
||||
if (mapped) {
|
||||
return [ensureSentence(mapped)];
|
||||
|
|
@ -2501,17 +2588,37 @@ function buildBrokenSectionLines(structure) {
|
|||
}
|
||||
return ["Есть признаки нарушения в связанной цепочке документов и проводок."];
|
||||
}
|
||||
function buildWhySectionLines(structure) {
|
||||
function buildWhySectionLines(structure, context) {
|
||||
const noteLines = dedupeNarrativeLines(structure.mechanism_block.mechanism_notes
|
||||
.map((item) => sanitizeSupportLine(item))
|
||||
.filter((item) => Boolean(item))
|
||||
.map((item) => mapDefectTokenToNarrative(item) ?? humanizeTechnicalToken(item) ?? item), 4);
|
||||
const domain = context?.focusDomain ?? inferNarrativeDomainFromText(sanitizeUserText(structure.direct_answer) ?? "");
|
||||
const mechanismCorpus = `${structure.direct_answer} ${structure.mechanism_block.mechanism_notes.join(" ")} ${structure.evidence_block.mechanism_notes.join(" ")}`;
|
||||
const fixedAssetContextSignal = hasFixedAssetContextSignal(context);
|
||||
const fixedAssetSignal = fixedAssetContextSignal ||
|
||||
((context?.focusDomain ?? null) !== "settlements_60_62" && hasFixedAssetSignalInStructure(structure, context));
|
||||
const rbpSignal = hasRbpContextSignal(context) || hasRbpSignalInText(mechanismCorpus);
|
||||
const lines = [...noteLines];
|
||||
if (structure.mechanism_block.status === "grounded") {
|
||||
lines.push("Признак проблемы повторяется в связанных документах и проводках.");
|
||||
}
|
||||
else if (structure.mechanism_block.status === "limited") {
|
||||
lines.push("Часть ожидаемой цепочки подтверждена, но ключевой переход закрытия не подтвержден.");
|
||||
if (domain === "vat_document_register_book") {
|
||||
lines.push("Часть НДС-цепочки подтверждена, но один или несколько переходов документ -> счет-фактура -> регистр -> книга не подтверждены.");
|
||||
}
|
||||
else if (fixedAssetSignal) {
|
||||
lines.push("По ОС часть переходов к начислению амортизации подтверждена не полностью, поэтому есть риск пропуска отдельных объектов.");
|
||||
}
|
||||
else if (rbpSignal) {
|
||||
lines.push("По РБП часть списаний к концу периода подтверждена не полностью, поэтому остаток может сохраняться дольше ожидаемого.");
|
||||
}
|
||||
else if (domain === "month_close_costs_20_44") {
|
||||
lines.push("Часть шагов закрытия периода подтверждена, но ключевой переход распределения/закрытия не подтвержден.");
|
||||
}
|
||||
else {
|
||||
lines.push("Часть ожидаемой цепочки подтверждена, но ключевой переход не подтвержден.");
|
||||
}
|
||||
}
|
||||
else {
|
||||
lines.push("Сигнал проблемы есть, но механизм подтвержден не полностью.");
|
||||
|
|
@ -2554,7 +2661,7 @@ function buildCoverageSplitLines(structure, questionType = "unknown") {
|
|||
}
|
||||
return dedupeNarrativeLines(lines, 3);
|
||||
}
|
||||
function buildEvidenceSectionLines(structure, questionType = "unknown") {
|
||||
function buildEvidenceSectionLines(structure, questionType = "unknown", context) {
|
||||
const evidenceCount = Array.isArray(structure.evidence_block.evidence_ids) ? structure.evidence_block.evidence_ids.length : 0;
|
||||
const sourceCount = Array.isArray(structure.evidence_block.source_refs) ? structure.evidence_block.source_refs.length : 0;
|
||||
const claimLinks = Array.isArray(structure.evidence_block.claim_evidence_links)
|
||||
|
|
@ -2566,15 +2673,43 @@ function buildEvidenceSectionLines(structure, questionType = "unknown") {
|
|||
structure.evidence_block.coverage_note === "coverage_partial_or_limited";
|
||||
const lines = [];
|
||||
const coverageSplitLines = buildCoverageSplitLines(structure, questionType);
|
||||
const domain = context?.focusDomain ?? inferNarrativeDomainFromText(sanitizeUserText(structure.direct_answer) ?? "");
|
||||
const evidenceCorpus = `${structure.direct_answer} ${structure.mechanism_block.mechanism_notes.join(" ")} ${structure.evidence_block.mechanism_notes.join(" ")}`;
|
||||
const fixedAssetContextSignal = hasFixedAssetContextSignal(context);
|
||||
const fixedAssetSignal = fixedAssetContextSignal ||
|
||||
((context?.focusDomain ?? null) !== "settlements_60_62" && hasFixedAssetSignalInStructure(structure, context));
|
||||
const rbpSignal = hasRbpContextSignal(context) || hasRbpSignalInText(evidenceCorpus);
|
||||
if (questionType === "what_is_it_grounded_on") {
|
||||
if (domain === "vat_document_register_book") {
|
||||
lines.push("Основание собрано по НДС-цепочке: документ, счет-фактура, регистр НДС и запись книги.");
|
||||
}
|
||||
else if (fixedAssetSignal) {
|
||||
lines.push("Основание собрано по ОС: карточка объекта, параметры амортизации, начисление и движения по 01/02.");
|
||||
}
|
||||
else if (rbpSignal) {
|
||||
lines.push("Основание собрано по РБП: объект списания, документ списания и остаток на конец периода.");
|
||||
}
|
||||
else {
|
||||
lines.push("Основание вывода перечислено по подтвержденным документам, регистрам и проводкам.");
|
||||
}
|
||||
}
|
||||
else if (questionType === "prove_or_guess") {
|
||||
lines.push("Основание разделено на подтвержденную часть и зону гипотез.");
|
||||
}
|
||||
else if (questionType === "which_chains_are_complete_vs_incomplete") {
|
||||
if (domain === "vat_document_register_book") {
|
||||
lines.push("Опора собрана по звеньям НДС-цепочки, чтобы разделить полные и неполные переходы.");
|
||||
}
|
||||
else if (rbpSignal) {
|
||||
lines.push("Опора собрана по РБП-цепочке, чтобы разделить подтвержденное и неподтвержденное списание.");
|
||||
}
|
||||
else if (fixedAssetSignal) {
|
||||
lines.push("Опора собрана по ОС-цепочке, чтобы разделить подтвержденные и неподтвержденные начисления амортизации.");
|
||||
}
|
||||
else {
|
||||
lines.push("Опора собрана так, чтобы разделить цепочки на полные и неполные.");
|
||||
}
|
||||
}
|
||||
if (evidenceCount > 0) {
|
||||
lines.push(`Вывод опирается на ${evidenceCount} подтвержденных наблюдений в текущем срезе.`);
|
||||
}
|
||||
|
|
@ -2584,11 +2719,25 @@ function buildEvidenceSectionLines(structure, questionType = "unknown") {
|
|||
if (claimLinks > 0) {
|
||||
lines.push("Есть связка между основным выводом и подтверждающими записями.");
|
||||
}
|
||||
if (structure.evidence_block.coverage_note === "coverage_partial_or_limited") {
|
||||
if (structure.evidence_block.coverage_note === "coverage_partial_or_limited" || reliabilityLimited) {
|
||||
if (domain === "vat_document_register_book") {
|
||||
lines.push("Опора частичная: по НДС-цепочке не подтверждены одно или несколько звеньев.");
|
||||
}
|
||||
else if (fixedAssetSignal) {
|
||||
lines.push("Опора частичная: не по всем объектам ОС подтверждено попадание в начисление амортизации.");
|
||||
}
|
||||
else if (rbpSignal) {
|
||||
lines.push("Опора частичная: не по всем объектам РБП подтверждено списание к концу периода.");
|
||||
}
|
||||
else if (structure.evidence_block.coverage_note === "coverage_partial_or_limited") {
|
||||
lines.push("Опора частичная: часть требований покрыта не полностью.");
|
||||
}
|
||||
else if (evidenceCount > 0) {
|
||||
lines.push(reliabilityLimited ? "Опора есть, но достаточна только для предварительного вывода." : "Опора достаточна для первичного вывода.");
|
||||
lines.push("Опора есть, но достаточна только для предварительного вывода.");
|
||||
}
|
||||
}
|
||||
else if (evidenceCount > 0) {
|
||||
lines.push("Опора достаточна для первичного вывода.");
|
||||
}
|
||||
if (lines.length === 0) {
|
||||
lines.push("Использована доступная выборка документов и проводок в текущем snapshot.");
|
||||
|
|
@ -2616,6 +2765,123 @@ function buildDefaultChecksByDomain(domain) {
|
|||
}
|
||||
return ["Проверьте связку документов и проводок по проблемному участку в указанном периоде."];
|
||||
}
|
||||
function hasFixedAssetAnchorContext(context) {
|
||||
if (!context) {
|
||||
return false;
|
||||
}
|
||||
const corpus = [...context.anchors.present, ...context.anchors.used].join(" ").toLowerCase();
|
||||
return /(?:doc_type:amortization|account:0[12]|амортиз|основн|(?:^|[^a-zа-яё])ос(?:$|[^a-zа-яё])|fixed\s*asset|depreciat)/i.test(corpus);
|
||||
}
|
||||
function hasFixedAssetContextSignal(context) {
|
||||
if (!context) {
|
||||
return false;
|
||||
}
|
||||
const corpus = [...context.anchors.present, ...context.anchors.used, context.userMessage ?? ""].join(" ").toLowerCase();
|
||||
return (hasFixedAssetAnchorContext(context) ||
|
||||
hasFixedAssetAmortizationSignalInText(corpus) ||
|
||||
/(?:\bос\b|основн(?:ые|ых)?\s+средств|амортиз|сч(?:е|ё)т\s*0[12])/i.test(corpus));
|
||||
}
|
||||
function hasRbpAnchorContext(context) {
|
||||
if (!context) {
|
||||
return false;
|
||||
}
|
||||
const corpus = [...context.anchors.present, ...context.anchors.used].join(" ").toLowerCase();
|
||||
return /(?:\brbp(?:[_\s-]?writeoff)?\b|рбп|deferred[_\s-]?expense(?:[_\s-]?to[_\s-]?writeoff)?|doc_type:(?:deferred|rbp_writeoff)|счет\s*97|account:97)/i.test(corpus);
|
||||
}
|
||||
function hasRbpContextSignal(context) {
|
||||
if (!context) {
|
||||
return false;
|
||||
}
|
||||
const corpus = [...context.anchors.present, ...context.anchors.used, context.userMessage ?? ""].join(" ");
|
||||
return hasRbpAnchorContext(context) || hasRbpSignalInText(corpus);
|
||||
}
|
||||
function hasRbpSignalInText(value) {
|
||||
const text = String(value ?? "").toLowerCase();
|
||||
return /(?:\brbp(?:[_\s-]?writeoff)?\b|рбп|deferred[_\s-]?expense(?:[_\s-]?to[_\s-]?writeoff)?|счет\s*97|списани[ея]\s+рбп|остат(ок|ки)\s+рбп)/i.test(text);
|
||||
}
|
||||
function hasFixedAssetSignalInStructure(structure, context) {
|
||||
const corpus = [
|
||||
structure.direct_answer,
|
||||
...structure.mechanism_block.mechanism_notes,
|
||||
...structure.evidence_block.mechanism_notes,
|
||||
...(structure.evidence_block.source_refs ?? []),
|
||||
...(structure.evidence_block.evidence_ids ?? []),
|
||||
...(context?.anchors.present ?? []),
|
||||
...(context?.anchors.used ?? [])
|
||||
]
|
||||
.filter(Boolean)
|
||||
.join(" ");
|
||||
if (hasFixedAssetAnchorContext(context) || hasFixedAssetAmortizationSignalInText(corpus)) {
|
||||
return true;
|
||||
}
|
||||
return /(?:asset_card_to_depreciation|fixed_asset|fixed_assets|амортиз|основн(?:ые|ых)?\s+средств|сч(?:е|ё)т\s*0[12]|\b0[12](?:\.\d{2})?\b)/i.test(corpus);
|
||||
}
|
||||
function buildFixedAssetChecksByQuestionType(questionType) {
|
||||
if (questionType === "what_to_check_first") {
|
||||
return [
|
||||
"Проверьте по каждому объекту ОС карточку и параметр амортизации (способ, срок, дата начала начисления).",
|
||||
"Сверьте ввод в эксплуатацию и попадание объекта в набор начисления амортизации за нужный период.",
|
||||
"Подтвердите начисление по объектам проводками и регистром амортизации."
|
||||
];
|
||||
}
|
||||
if (questionType === "prove_or_guess") {
|
||||
return [
|
||||
"Разделите доказанные и предположительные участки по цепочке ОС: принятие -> ввод -> начисление амортизации.",
|
||||
"Проверьте, какие объекты отсутствуют в наборе начисления или имеют некорректные параметры амортизации."
|
||||
];
|
||||
}
|
||||
if (questionType === "where_break_is") {
|
||||
return [
|
||||
"Локализуйте разрыв в цепочке ОС: карточка объекта -> ввод в эксплуатацию -> начисление амортизации.",
|
||||
"Сверьте, на каком шаге пропадает подтверждение по конкретным объектам."
|
||||
];
|
||||
}
|
||||
if (questionType === "what_is_it_grounded_on") {
|
||||
return [
|
||||
"Перечислите основание: карточка ОС, документ ввода в эксплуатацию, запись регистра амортизации, проводки по начислению."
|
||||
];
|
||||
}
|
||||
return [
|
||||
"Проверьте ОС-контур: объект ОС -> ввод в эксплуатацию -> начисление амортизации по счетам 01/02.",
|
||||
"Сверьте параметр амортизации и наличие начисления по каждому объекту ОС в периоде."
|
||||
];
|
||||
}
|
||||
function buildRbpChecksByQuestionType(questionType) {
|
||||
if (questionType === "what_to_check_first") {
|
||||
return [
|
||||
"Проверьте список объектов РБП, которые должны были списаться к концу периода.",
|
||||
"Сверьте документ списания РБП и движение по счету 97 по каждому объекту.",
|
||||
"Проверьте остаток РБП после списания и причину, если часть суммы остается активной."
|
||||
];
|
||||
}
|
||||
if (questionType === "prove_or_guess") {
|
||||
return [
|
||||
"Разделите по РБП доказанное и гипотезу: где списание подтверждено, а где есть только косвенные признаки.",
|
||||
"Проверьте, для каких объектов РБП нет подтверждения списания на конец периода."
|
||||
];
|
||||
}
|
||||
if (questionType === "where_break_is") {
|
||||
return [
|
||||
"Локализуйте разрыв в РБП-цепочке: объект РБП -> документ списания -> движение по счету 97.",
|
||||
"Проверьте, на каком шаге исчезает подтверждение списания."
|
||||
];
|
||||
}
|
||||
if (questionType === "what_is_it_grounded_on") {
|
||||
return [
|
||||
"Перечислите основание по РБП: объект, документ списания, движение по счету 97, остаток на конец периода."
|
||||
];
|
||||
}
|
||||
if (questionType === "which_chains_are_complete_vs_incomplete") {
|
||||
return [
|
||||
"Разделите РБП-цепочки на: списание подтверждено, подтверждено частично, не подтверждено.",
|
||||
"Проверьте, где к концу периода остается РБП без подтвержденного списания."
|
||||
];
|
||||
}
|
||||
return [
|
||||
"Проверьте РБП-контур: объект РБП -> документ списания -> движение по счету 97.",
|
||||
"Сверьте остаток РБП на конец периода и причину, если часть суммы не списана."
|
||||
];
|
||||
}
|
||||
function buildQuestionTypeDomainChecks(questionType, domain) {
|
||||
if (questionType === "what_to_check_first") {
|
||||
if (domain === "settlements_60_62") {
|
||||
|
|
@ -2734,7 +3000,18 @@ function buildChecksSectionLines(structure, context) {
|
|||
const broken = sanitizeUserText(structure.direct_answer) ?? "";
|
||||
const domain = context?.focusDomain ?? inferNarrativeDomainFromText(broken);
|
||||
const questionType = context?.questionType ?? "unknown";
|
||||
const domainFallback = buildQuestionTypeDomainChecks(questionType, domain);
|
||||
const effectiveQuestionType = questionType === "unknown" ? "what_to_check_first" : questionType;
|
||||
const fixedAssetMechanismSignal = hasFixedAssetAmortizationSignalInText(`${structure.direct_answer} ${structure.mechanism_block.mechanism_notes.join(" ")} ${structure.evidence_block.mechanism_notes.join(" ")}`);
|
||||
const domainAndEvidenceCorpus = `${broken} ${structure.mechanism_block.mechanism_notes.join(" ")} ${structure.evidence_block.mechanism_notes.join(" ")}`;
|
||||
const fixedAssetContextSignal = hasFixedAssetContextSignal(context);
|
||||
const fixedAssetCase = fixedAssetContextSignal ||
|
||||
(domain !== "settlements_60_62" && (hasFixedAssetSignalInStructure(structure, context) || fixedAssetMechanismSignal));
|
||||
const rbpCase = hasRbpContextSignal(context) || hasRbpSignalInText(domainAndEvidenceCorpus);
|
||||
const domainFallback = fixedAssetCase
|
||||
? buildFixedAssetChecksByQuestionType(effectiveQuestionType)
|
||||
: rbpCase
|
||||
? buildRbpChecksByQuestionType(effectiveQuestionType)
|
||||
: buildQuestionTypeDomainChecks(questionType, domain);
|
||||
const hasMissingPeriod = structure.uncertainty_block.open_uncertainties.some((item) => /missing_anchor:period/i.test(String(item ?? "")));
|
||||
const lines = [];
|
||||
if (questionType === "what_to_check_first") {
|
||||
|
|
@ -2764,18 +3041,21 @@ function buildChecksSectionLines(structure, context) {
|
|||
}
|
||||
}
|
||||
}
|
||||
const filteredLines = fixedAssetCase || rbpCase
|
||||
? lines.filter((item) => !/проверьте связку документов и проводок по проблемному участку/i.test(item))
|
||||
: lines;
|
||||
if (hasMissingPeriod) {
|
||||
if (questionType === "what_to_check_first") {
|
||||
lines.push("Уточните период, если он не зафиксирован в исходной формулировке вопроса.");
|
||||
filteredLines.push("Уточните период, если он не зафиксирован в исходной формулировке вопроса.");
|
||||
}
|
||||
else if (domain === "settlements_60_62" && lines.length > 0) {
|
||||
lines.push("Уточните период проверки, чтобы подтвердить проблему без лишнего шума.");
|
||||
else if (domain === "settlements_60_62" && filteredLines.length > 0) {
|
||||
filteredLines.push("Уточните период проверки, чтобы подтвердить проблему без лишнего шума.");
|
||||
}
|
||||
else {
|
||||
lines.unshift("Уточните период проверки, чтобы подтвердить проблему без лишнего шума.");
|
||||
filteredLines.unshift("Уточните период проверки, чтобы подтвердить проблему без лишнего шума.");
|
||||
}
|
||||
}
|
||||
return dedupeNarrativeLines(lines, questionType === "what_to_check_first" ? 3 : 5);
|
||||
return dedupeNarrativeLines(filteredLines, questionType === "what_to_check_first" ? 3 : 5);
|
||||
}
|
||||
function humanizeLimitationToken(value) {
|
||||
const raw = String(value ?? "").trim();
|
||||
|
|
@ -2877,6 +3157,15 @@ function buildQuestionTypeShortLine(context) {
|
|||
return "\u0412\u044b\u0432\u043e\u0434 \u0440\u0430\u0437\u0434\u0435\u043b\u0435\u043d \u043d\u0430 \u0434\u043e\u043a\u0430\u0437\u0430\u043d\u043d\u0443\u044e \u0447\u0430\u0441\u0442\u044c \u0438 \u0433\u0438\u043f\u043e\u0442\u0435\u0437\u0443.";
|
||||
}
|
||||
if (context.questionType === "what_is_it_grounded_on") {
|
||||
if (hasRbpContextSignal(context)) {
|
||||
return "Ниже перечислены основания вывода по РБП: списание, остаток и подтверждение на конец периода.";
|
||||
}
|
||||
if (hasFixedAssetAnchorContext(context)) {
|
||||
return "Ниже перечислены основания вывода по ОС/амортизации по данным учета.";
|
||||
}
|
||||
if (context.focusDomain === "vat_document_register_book") {
|
||||
return "Ниже перечислены основания вывода по НДС-цепочке по данным учета.";
|
||||
}
|
||||
return "\u041d\u0438\u0436\u0435 \u043f\u0435\u0440\u0435\u0447\u0438\u0441\u043b\u0435\u043d\u044b \u043e\u0441\u043d\u043e\u0432\u0430\u043d\u0438\u044f \u0432\u044b\u0432\u043e\u0434\u0430 \u043f\u043e \u0434\u0430\u043d\u043d\u044b\u043c \u0443\u0447\u0435\u0442\u0430.";
|
||||
}
|
||||
if (context.questionType === "which_chains_are_complete_vs_incomplete") {
|
||||
|
|
@ -2895,8 +3184,14 @@ function buildQuestionTypeShortLine(context) {
|
|||
if (context.focusDomain === "month_close_costs_20_44") {
|
||||
return "Наиболее вероятная причина: цепочка распределения затрат и закрытия месяца подтверждена не полностью.";
|
||||
}
|
||||
if (hasFixedAssetAnchorContext(context)) {
|
||||
return "Наиболее вероятная причина: по ОС часть переходов от параметров амортизации к начислению подтверждена не полностью.";
|
||||
}
|
||||
return "Наиболее вероятный механизм проблемы подтвержден частично и требует первичной проверки.";
|
||||
}
|
||||
if (context.questionType === "unknown" && hasFixedAssetAnchorContext(context)) {
|
||||
return "Риск неполного начисления амортизации подтвержден частично и требует проверки по объектам ОС.";
|
||||
}
|
||||
return null;
|
||||
}
|
||||
function buildQuestionTypeBrokenLine(context) {
|
||||
|
|
@ -2925,18 +3220,45 @@ function buildQuestionTypeWhyLine(context) {
|
|||
return "\u0426\u0435\u043f\u043e\u0447\u043a\u0438 \u0440\u0430\u0437\u0434\u0435\u043b\u0435\u043d\u044b \u043d\u0430 \u0437\u0430\u0432\u0435\u0440\u0448\u0435\u043d\u043d\u044b\u0435 \u0438 \u043d\u0435\u0437\u0430\u0432\u0435\u0440\u0448\u0435\u043d\u043d\u044b\u0435 \u043f\u043e \u0442\u0435\u043a\u0443\u0449\u0435\u0439 \u043e\u043f\u043e\u0440\u0435.";
|
||||
}
|
||||
if (context.questionType === "what_is_it_grounded_on") {
|
||||
if (hasRbpContextSignal(context)) {
|
||||
return "Фокус ответа по РБП: подтверждение списания и остатка на конец периода, а не общий close-narrative.";
|
||||
}
|
||||
if (hasFixedAssetAnchorContext(context)) {
|
||||
return "Фокус ответа по ОС: подтверждение попадания объектов в начисление амортизации.";
|
||||
}
|
||||
if (context.focusDomain === "vat_document_register_book") {
|
||||
return "Фокус ответа по НДС: подтверждение переходов между документом, счетом-фактурой, регистром и книгой.";
|
||||
}
|
||||
return "\u0424\u043e\u043a\u0443\u0441 \u043e\u0442\u0432\u0435\u0442\u0430 \u0441\u043c\u0435\u0449\u0435\u043d \u0432 \u0434\u043e\u043a\u0430\u0437\u0430\u0442\u0435\u043b\u044c\u043d\u044b\u0435 \u0438\u0441\u0442\u043e\u0447\u043d\u0438\u043a\u0438, \u0430 \u043d\u0435 \u0432 \u043e\u0431\u0449\u0438\u0439 narrative.";
|
||||
}
|
||||
return null;
|
||||
}
|
||||
function buildQuestionTypeEvidenceLine(context) {
|
||||
if (context.questionType === "what_is_it_grounded_on") {
|
||||
if (hasRbpContextSignal(context)) {
|
||||
return "Опора перечислена по РБП-объектам, документам списания и остаткам на конец периода.";
|
||||
}
|
||||
if (hasFixedAssetAnchorContext(context)) {
|
||||
return "Опора перечислена по ОС-объектам, параметрам амортизации и движениям начисления.";
|
||||
}
|
||||
if (context.focusDomain === "vat_document_register_book") {
|
||||
return "Опора перечислена по НДС-звеньям: документ, счет-фактура, регистр и книга.";
|
||||
}
|
||||
return "\u0412 \u044d\u0442\u043e\u043c \u043e\u0442\u0432\u0435\u0442\u0435 \u0432 \u043f\u0440\u0438\u043e\u0440\u0438\u0442\u0435\u0442\u0435 \u043f\u043e\u043a\u0430\u0437\u0430\u043d\u044b \u0438\u043c\u0435\u043d\u043d\u043e \u043e\u0441\u043d\u043e\u0432\u0430\u043d\u0438\u044f \u0432\u044b\u0432\u043e\u0434\u0430.";
|
||||
}
|
||||
if (context.questionType === "prove_or_guess") {
|
||||
return "\u0421\u0438\u043b\u0430 \u0432\u044b\u0432\u043e\u0434\u0430 \u043e\u0446\u0435\u043d\u0435\u043d\u0430 \u043f\u043e \u043f\u0440\u044f\u043c\u043e\u0439 \u043e\u043f\u043e\u0440\u0435, \u0430 \u043d\u0435 \u043f\u043e \u0434\u043e\u0433\u0430\u0434\u043a\u0430\u043c.";
|
||||
}
|
||||
if (context.questionType === "which_chains_are_complete_vs_incomplete") {
|
||||
if (context.focusDomain === "vat_document_register_book") {
|
||||
return "Опора собрана по НДС-звеньям, чтобы разделить полные и неполные переходы.";
|
||||
}
|
||||
if (hasRbpContextSignal(context)) {
|
||||
return "Опора собрана по РБП-цепочке, чтобы разделить подтвержденное и неподтвержденное списание.";
|
||||
}
|
||||
if (hasFixedAssetAnchorContext(context)) {
|
||||
return "Опора собрана по ОС-цепочке, чтобы разделить подтвержденные и неподтвержденные начисления амортизации.";
|
||||
}
|
||||
return "\u041e\u043f\u043e\u0440\u0430 \u0441\u043e\u0431\u0440\u0430\u043d\u0430 \u0442\u0430\u043a, \u0447\u0442\u043e\u0431\u044b \u0447\u0435\u0441\u0442\u043d\u043e \u0440\u0430\u0437\u0434\u0435\u043b\u0438\u0442\u044c \u043f\u043e\u043b\u043d\u044b\u0435 \u0438 \u043d\u0435\u043f\u043e\u043b\u043d\u044b\u0435 \u0446\u0435\u043f\u043e\u0447\u043a\u0438.";
|
||||
}
|
||||
return null;
|
||||
|
|
@ -2958,9 +3280,27 @@ function buildQuestionTypeCheckLine(context) {
|
|||
return "\u041f\u0435\u0440\u0432\u044b\u043c \u0434\u0435\u0439\u0441\u0442\u0432\u0438\u0435\u043c \u043e\u0442\u0434\u0435\u043b\u0438\u0442\u0435 \u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442\u0430\u043b\u044c\u043d\u043e \u043f\u043e\u0434\u0442\u0432\u0435\u0440\u0436\u0434\u0435\u043d\u043d\u044b\u0435 \u0444\u0430\u043a\u0442\u044b \u043e\u0442 \u0433\u0438\u043f\u043e\u0442\u0435\u0437.";
|
||||
}
|
||||
if (context.questionType === "what_is_it_grounded_on") {
|
||||
if (hasRbpContextSignal(context)) {
|
||||
return "Сначала перечислите по РБП: объект, документ списания и остаток после списания на конец периода.";
|
||||
}
|
||||
if (hasFixedAssetAnchorContext(context)) {
|
||||
return "Сначала перечислите по ОС: объект, параметры амортизации и подтверждение начисления за период.";
|
||||
}
|
||||
if (context.focusDomain === "vat_document_register_book") {
|
||||
return "Сначала перечислите по НДС: документ, счет-фактуру, запись регистра и запись книги.";
|
||||
}
|
||||
return "\u0421\u043d\u0430\u0447\u0430\u043b\u0430 \u043f\u0435\u0440\u0435\u0447\u0438\u0441\u043b\u0438\u0442\u0435 \u043e\u043f\u043e\u0440\u043d\u044b\u0435 \u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442\u044b \u0438 \u0440\u0435\u0433\u0438\u0441\u0442\u0440\u044b, \u0437\u0430\u0442\u0435\u043c \u043f\u043e\u0434\u0442\u0432\u0435\u0440\u0436\u0434\u0430\u044e\u0449\u0438\u0435 \u043f\u0440\u043e\u0432\u043e\u0434\u043a\u0438.";
|
||||
}
|
||||
if (context.questionType === "which_chains_are_complete_vs_incomplete") {
|
||||
if (context.focusDomain === "vat_document_register_book") {
|
||||
return "Сначала разложите НДС-цепочку по шагам: документ -> счет-фактура -> регистр -> книга.";
|
||||
}
|
||||
if (hasRbpContextSignal(context)) {
|
||||
return "Сначала разложите РБП-цепочку на подтвержденное списание, частичное и неподтвержденное.";
|
||||
}
|
||||
if (hasFixedAssetAnchorContext(context)) {
|
||||
return "Сначала разложите ОС-цепочку на подтвержденное начисление, частичное и неподтвержденное.";
|
||||
}
|
||||
return "\u0421\u043d\u0430\u0447\u0430\u043b\u0430 \u0440\u0430\u0437\u043b\u043e\u0436\u0438\u0442\u0435 \u0446\u0435\u043f\u043e\u0447\u043a\u0438 \u043d\u0430 \u043f\u043e\u043b\u043d\u044b\u0435, \u0447\u0430\u0441\u0442\u0438\u0447\u043d\u043e \u043f\u043e\u043b\u043d\u044b\u0435 \u0438 \u043d\u0435\u043f\u043e\u0434\u0442\u0432\u0435\u0440\u0436\u0434\u0435\u043d\u043d\u044b\u0435.";
|
||||
}
|
||||
return null;
|
||||
|
|
@ -3001,15 +3341,101 @@ function applyQuestionTypeAndAnchorPolicy(input) {
|
|||
limitationLines: nextLimitations
|
||||
};
|
||||
}
|
||||
const RBP_WORDING_PATTERN = /(?:\bрбп\b|deferred[_\s-]?expense|сч(?:е|ё)т\s*97|объект\w*\s+рбп|списани[ея]\s+рбп|остат(?:ок|ки)\s+рбп|документ\s+списани[яе])/iu;
|
||||
const FA_WORDING_PATTERN = /(?:\bос\b|основн(?:ые|ых)?\s+средств|амортиз|сч(?:е|ё)т\s*0[12]|01\/02|карточк\w*\s+ос|объект\w*\s+ос|ввод\w*\s+в\s+эксплуатац|fixed\s*asset|depreciat)/iu;
|
||||
function hasRbpWordingPhrase(value) {
|
||||
return RBP_WORDING_PATTERN.test(String(value ?? ""));
|
||||
}
|
||||
function hasFaWordingPhrase(value) {
|
||||
return FA_WORDING_PATTERN.test(String(value ?? ""));
|
||||
}
|
||||
function resolveDomainWordingMode(structure, context) {
|
||||
if (!context) {
|
||||
return "neutral";
|
||||
}
|
||||
const userMessage = String(context.userMessage ?? "");
|
||||
const explicitRbpFromMessage = hasRbpSignalInText(userMessage);
|
||||
const explicitFaFromMessage = hasFixedAssetAmortizationSignalInText(userMessage);
|
||||
if (explicitRbpFromMessage && !explicitFaFromMessage) {
|
||||
return "rbp";
|
||||
}
|
||||
if (explicitFaFromMessage && !explicitRbpFromMessage) {
|
||||
return "fa_amortization";
|
||||
}
|
||||
const anchorRbp = hasRbpAnchorContext(context);
|
||||
const anchorFa = hasFixedAssetAnchorContext(context);
|
||||
if (anchorRbp && !anchorFa) {
|
||||
return "rbp";
|
||||
}
|
||||
if (anchorFa && !anchorRbp) {
|
||||
return "fa_amortization";
|
||||
}
|
||||
const structureCorpus = [
|
||||
structure.direct_answer,
|
||||
...structure.mechanism_block.mechanism_notes,
|
||||
...structure.evidence_block.mechanism_notes,
|
||||
...(structure.evidence_block.source_refs ?? []),
|
||||
...(context.anchors.present ?? []),
|
||||
...(context.anchors.used ?? [])
|
||||
]
|
||||
.filter(Boolean)
|
||||
.join(" ");
|
||||
const structureRbp = hasRbpSignalInText(structureCorpus);
|
||||
const structureFa = hasFixedAssetAmortizationSignalInText(structureCorpus);
|
||||
const rbpScore = [explicitRbpFromMessage, anchorRbp, structureRbp].filter(Boolean).length;
|
||||
const faScore = [explicitFaFromMessage, anchorFa, structureFa].filter(Boolean).length;
|
||||
if (rbpScore > faScore) {
|
||||
return "rbp";
|
||||
}
|
||||
if (faScore > rbpScore) {
|
||||
return "fa_amortization";
|
||||
}
|
||||
return "neutral";
|
||||
}
|
||||
function enforceDomainWordingIsolation(payload, structure, context) {
|
||||
const mode = resolveDomainWordingMode(structure, context);
|
||||
if (mode === "neutral" || !context) {
|
||||
return payload;
|
||||
}
|
||||
const effectiveQuestionType = context.questionType === "unknown" ? "what_to_check_first" : context.questionType;
|
||||
const isForbidden = mode === "rbp" ? hasFaWordingPhrase : hasRbpWordingPhrase;
|
||||
const filterLines = (lines) => lines.filter((line) => !isForbidden(line));
|
||||
const shortFallback = mode === "rbp"
|
||||
? "Признаки по РБП подтверждены частично и требуют проверки списания к концу периода."
|
||||
: "Риск неполного начисления амортизации по объектам ОС подтвержден частично.";
|
||||
const whyFallback = mode === "rbp"
|
||||
? ["По РБП часть списаний к концу периода подтверждена не полностью, поэтому остаток может сохраняться дольше ожидаемого."]
|
||||
: ["По ОС часть переходов к начислению амортизации подтверждена не полностью, поэтому есть риск пропуска отдельных объектов."];
|
||||
const evidenceFallback = mode === "rbp"
|
||||
? ["Основание собрано по РБП: объект списания, документ списания и остаток на конец периода."]
|
||||
: ["Основание собрано по ОС: карточка объекта, параметры амортизации, начисление и движения по 01/02."];
|
||||
const checkFallback = mode === "rbp"
|
||||
? buildRbpChecksByQuestionType(effectiveQuestionType).slice(0, 2)
|
||||
: buildFixedAssetChecksByQuestionType(effectiveQuestionType).slice(0, 2);
|
||||
const filteredShort = isForbidden(payload.shortLine) ? shortFallback : payload.shortLine;
|
||||
const filteredBroken = dedupeNarrativeLines(filterLines(payload.brokenLines), 4);
|
||||
const filteredWhy = dedupeNarrativeLines([...filterLines(payload.whyLines), ...(filterLines(payload.whyLines).length === 0 ? whyFallback : [])], 4);
|
||||
const filteredEvidence = dedupeNarrativeLines([...filterLines(payload.evidenceLines), ...(filterLines(payload.evidenceLines).length === 0 ? evidenceFallback : [])], 7);
|
||||
const filteredChecks = dedupeNarrativeLines([...filterLines(payload.checkLines), ...(filterLines(payload.checkLines).length === 0 ? checkFallback : [])], effectiveQuestionType === "what_to_check_first" ? 3 : 5);
|
||||
const filteredLimitations = dedupeNarrativeLines(filterLines(payload.limitationLines), 6);
|
||||
return {
|
||||
shortLine: ensureSentence(filteredShort),
|
||||
brokenLines: filteredBroken.length > 0 ? filteredBroken : payload.brokenLines,
|
||||
whyLines: filteredWhy.length > 0 ? filteredWhy : whyFallback,
|
||||
evidenceLines: filteredEvidence.length > 0 ? filteredEvidence : evidenceFallback,
|
||||
checkLines: filteredChecks.length > 0 ? filteredChecks : checkFallback,
|
||||
limitationLines: filteredLimitations.length > 0 ? filteredLimitations : payload.limitationLines
|
||||
};
|
||||
}
|
||||
function renderPolicyReply(structure, context) {
|
||||
const questionType = context?.questionType ?? "unknown";
|
||||
const shortLine = ensureSentence(buildShortSectionLine(structure));
|
||||
const brokenLines = buildBrokenSectionLines(structure);
|
||||
const whyLines = buildWhySectionLines(structure);
|
||||
const evidenceLines = buildEvidenceSectionLines(structure, questionType);
|
||||
const whyLines = buildWhySectionLines(structure, context);
|
||||
const evidenceLines = buildEvidenceSectionLines(structure, questionType, context);
|
||||
const checkLines = buildChecksSectionLines(structure, context);
|
||||
const limitationLines = buildLimitationsSectionLines(structure);
|
||||
const enriched = context
|
||||
const enrichedBase = context
|
||||
? applyQuestionTypeAndAnchorPolicy({
|
||||
shortLine,
|
||||
brokenLines,
|
||||
|
|
@ -3027,6 +3453,7 @@ function renderPolicyReply(structure, context) {
|
|||
checkLines,
|
||||
limitationLines
|
||||
};
|
||||
const enriched = enforceDomainWordingIsolation(enrichedBase, structure, context);
|
||||
return sanitizeUserFacingReply([
|
||||
`Коротко: ${enriched.shortLine}`,
|
||||
`Что сломано:\n${formatList(enriched.brokenLines)}`,
|
||||
|
|
@ -3117,7 +3544,10 @@ function composeAssistantAnswerV11(input) {
|
|||
reply_type: "clarification_required"
|
||||
}
|
||||
: decision;
|
||||
const missingAnchors = detectMissingAnchors(input.userMessage, input.retrievalResults);
|
||||
const missingAnchors = detectMissingAnchors(input.userMessage, input.retrievalResults, {
|
||||
normalizationPeriodExplicit: Boolean(input.normalizationPeriodExplicit),
|
||||
companyAnchors: input.companyAnchors ?? null
|
||||
});
|
||||
const hasProblemWeakSignal = policySignals.narrowing_strength !== "strong" ||
|
||||
policySignals.minimum_evidence_failed ||
|
||||
limitationReasonCodes.includes("missing_mechanism") ||
|
||||
|
|
@ -3158,7 +3588,8 @@ function composeAssistantAnswerV11(input) {
|
|||
assistant_reply: renderPolicyReply(problemCentricStructure, {
|
||||
questionType,
|
||||
focusDomain: focusNarrativeDomain,
|
||||
anchors: anchorUsage
|
||||
anchors: anchorUsage,
|
||||
userMessage: input.userMessage
|
||||
}),
|
||||
fallback_type: guardedDecision.fallback_type,
|
||||
reply_type: guardedDecision.reply_type,
|
||||
|
|
@ -3262,7 +3693,8 @@ function composeAssistantAnswerV11(input) {
|
|||
assistant_reply: renderPolicyReply(answerStructure, {
|
||||
questionType,
|
||||
focusDomain: focusNarrativeDomain,
|
||||
anchors: anchorUsage
|
||||
anchors: anchorUsage,
|
||||
userMessage: input.userMessage
|
||||
}),
|
||||
fallback_type: guardedDecision.fallback_type,
|
||||
reply_type: guardedDecision.reply_type,
|
||||
|
|
@ -3309,6 +3741,9 @@ function composeExplainableAnswer(input, scopeLabel) {
|
|||
.filter(Boolean)
|
||||
.join("\n\n"));
|
||||
}
|
||||
function sanitizeAssistantReplyForUserFacing(value) {
|
||||
return sanitizeUserFacingReply(value);
|
||||
}
|
||||
function composeAssistantAnswer(input) {
|
||||
if (input.enableAnswerPolicyV11) {
|
||||
return composeAssistantAnswerV11(input);
|
||||
|
|
|
|||
|
|
@ -1242,11 +1242,17 @@ function cardResolutionScore(card, fragmentText, profile) {
|
|||
return 0;
|
||||
}
|
||||
const hasVatSoftAnchor = card.id === "vat_document_register_book" && hasStrongVatDomainSignal(fragmentText, profile);
|
||||
const hasHardAnchor = accountMatches.length > 0 || markerHit || hasVatSoftAnchor;
|
||||
const hasMonthCloseSignal = card.id === "month_close_costs_20_44" && hasStrongMonthCloseSignal(fragmentText, profile);
|
||||
const fixedAssetOnlySignal = card.id === "month_close_costs_20_44" && hasFixedAssetSignal(fragmentText, profile) && !hasMonthCloseSignal && accountMatches.length === 0;
|
||||
if (fixedAssetOnlySignal) {
|
||||
return 0;
|
||||
}
|
||||
const markerWeight = card.id === "month_close_costs_20_44" ? hasMonthCloseSignal : markerHit;
|
||||
const hasHardAnchor = accountMatches.length > 0 || markerWeight || hasVatSoftAnchor;
|
||||
if (!hasHardAnchor) {
|
||||
return 0;
|
||||
}
|
||||
return accountMatches.length * 4 + domainMatches.length * 3 + (markerHit ? 2 : 0);
|
||||
return accountMatches.length * 4 + domainMatches.length * 3 + (markerWeight ? 2 : 0);
|
||||
}
|
||||
function hasStrongVatDomainSignal(fragmentText, profile) {
|
||||
const text = String(fragmentText ?? "");
|
||||
|
|
@ -1256,6 +1262,19 @@ function hasStrongVatDomainSignal(fragmentText, profile) {
|
|||
profile.domain_scope.some((domain) => domain === "vat" || domain === "taxes") ||
|
||||
profile.relation_patterns.some((pattern) => ["invoice_to_vat", "register_to_book", "book_entry_generated", "deduction_posted"].includes(pattern)));
|
||||
}
|
||||
function hasStrongMonthCloseSignal(fragmentText, profile) {
|
||||
const text = String(fragmentText ?? "");
|
||||
const hasMonthCloseLexicalAnchor = /(?:закрыти[ея]\s+месяц|закрыт[а-яё]*\s+период|закрытие\s+счетов|регламентн|косвенн|затрат|распределени|рбп|month\s*close|period\s*close|close\s+operation)/iu.test(text);
|
||||
return (hasMonthCloseLexicalAnchor ||
|
||||
profile.account_scope.some((account) => CLOSE_COST_ACCOUNTS.includes(account)) ||
|
||||
profile.domain_scope.some((domain) => domain === "period_close" || domain === "deferred_expense") ||
|
||||
profile.relation_patterns.some((pattern) => ["deferred_expense_to_writeoff", "close_operation", "allocation_rules_resolved", "residuals_zero_or_explained"].includes(pattern)));
|
||||
}
|
||||
function hasFixedAssetSignal(fragmentText, profile) {
|
||||
const text = String(fragmentText ?? "");
|
||||
return (/(?:основн(ые|ых|ым)?\s+средств|(?:^|[^a-zа-яё])ос(?:$|[^a-zа-яё])|амортиз|depreciat|fixed\s*asset)/iu.test(text) ||
|
||||
profile.account_scope.some((account) => account === "01" || account === "02"));
|
||||
}
|
||||
function hasStrongSettlementAccountSignal(profile) {
|
||||
return profile.account_scope.some((account) => account === "51" || account === "60" || account === "62" || account === "76");
|
||||
}
|
||||
|
|
@ -1302,6 +1321,19 @@ function hasSettlementRecoverySignal(signals) {
|
|||
const hasSettlementDocument = signals.document_types.some((item) => ["bank_statement", "payment_order", "settlement_document", "supplier_receipt", "sales_document"].includes(item));
|
||||
return hasSettlementAccount || hasSettlementDomain || hasSettlementRelation || hasSettlementDocument;
|
||||
}
|
||||
function isVatAllowedAccountContext(account) {
|
||||
const normalized = String(account ?? "").trim();
|
||||
return normalized === "19" || normalized === "68";
|
||||
}
|
||||
function isVatAllowedDocumentContext(documentType) {
|
||||
return /(?:invoice|vat_document|purchase_book|sales_book|tax_entry|supplier_receipt|sales_document|register)/i.test(String(documentType ?? ""));
|
||||
}
|
||||
function isVatAllowedRelationPattern(pattern) {
|
||||
return /(?:invoice_to_vat|register_to_book|book_entry_generated|deduction_posted|document_to_posting|contract_to_documents|source_doc_present|invoice_linked)/i.test(String(pattern ?? ""));
|
||||
}
|
||||
function isVatAllowedGraphDomain(domain) {
|
||||
return /(?:vat_flow)/i.test(String(domain ?? ""));
|
||||
}
|
||||
function collectSourceRecords(data, sources) {
|
||||
const items = [];
|
||||
for (const source of sources) {
|
||||
|
|
@ -2423,6 +2455,9 @@ class AssistantDataLayer {
|
|||
group.relations.set(relation, (group.relations.get(relation) ?? 0) + 1);
|
||||
}
|
||||
for (const account of evaluation.signals.account_context) {
|
||||
if (domainCard?.id === "vat_document_register_book" && !isVatAllowedAccountContext(account)) {
|
||||
continue;
|
||||
}
|
||||
if (semanticProfile.account_scope.length === 0 || semanticProfile.account_scope.includes(account)) {
|
||||
group.account_context.add(account);
|
||||
}
|
||||
|
|
@ -2432,6 +2467,9 @@ class AssistantDataLayer {
|
|||
!["bank_statement", "payment_order", "settlement_document", "supplier_receipt", "sales_document", "manual_operation"].includes(item)) {
|
||||
continue;
|
||||
}
|
||||
if (domainCard?.id === "vat_document_register_book" && !isVatAllowedDocumentContext(item)) {
|
||||
continue;
|
||||
}
|
||||
group.document_context.add(item);
|
||||
}
|
||||
for (const item of evaluation.signals.relation_patterns) {
|
||||
|
|
@ -2439,6 +2477,9 @@ class AssistantDataLayer {
|
|||
!["payment_to_settlement", "statement_to_document", "contract_to_documents", "document_to_posting"].includes(item)) {
|
||||
continue;
|
||||
}
|
||||
if (domainCard?.id === "vat_document_register_book" && !isVatAllowedRelationPattern(item)) {
|
||||
continue;
|
||||
}
|
||||
group.relation_pattern_hits.add(item);
|
||||
}
|
||||
for (const item of evaluation.signals.anomaly_patterns) {
|
||||
|
|
@ -2457,6 +2498,9 @@ class AssistantDataLayer {
|
|||
!["bank_settlement", "customer_settlement"].includes(domain)) {
|
||||
continue;
|
||||
}
|
||||
if (domainCard?.id === "vat_document_register_book" && !isVatAllowedGraphDomain(domain)) {
|
||||
continue;
|
||||
}
|
||||
group.graph_domain_scope.add(domain);
|
||||
}
|
||||
for (const reason of evaluation.match_reasons.slice(0, 4)) {
|
||||
|
|
@ -2471,15 +2515,23 @@ class AssistantDataLayer {
|
|||
const unknownLinks = Number(record.unknown_link_count ?? 0);
|
||||
const sampleAccountContext = domainCard?.id === "settlements_60_62"
|
||||
? evaluation.signals.account_context.filter((item) => ["51", "60", "62", "76"].includes(item))
|
||||
: domainCard?.id === "vat_document_register_book"
|
||||
? evaluation.signals.account_context.filter((item) => isVatAllowedAccountContext(item))
|
||||
: evaluation.signals.account_context;
|
||||
const sampleDocumentContext = domainCard?.id === "settlements_60_62"
|
||||
? evaluation.signals.document_types.filter((item) => ["bank_statement", "payment_order", "settlement_document", "supplier_receipt", "sales_document", "manual_operation"].includes(item))
|
||||
: domainCard?.id === "vat_document_register_book"
|
||||
? evaluation.signals.document_types.filter((item) => isVatAllowedDocumentContext(item))
|
||||
: evaluation.signals.document_types;
|
||||
const sampleRelationPatterns = domainCard?.id === "settlements_60_62"
|
||||
? evaluation.signals.relation_patterns.filter((item) => ["payment_to_settlement", "statement_to_document", "contract_to_documents", "document_to_posting"].includes(item))
|
||||
: domainCard?.id === "vat_document_register_book"
|
||||
? evaluation.signals.relation_patterns.filter((item) => isVatAllowedRelationPattern(item))
|
||||
: evaluation.signals.relation_patterns;
|
||||
const sampleGraphDomainScope = domainCard?.id === "settlements_60_62"
|
||||
? evaluation.graph_domain_scope.filter((item) => ["bank_settlement", "customer_settlement"].includes(item))
|
||||
: domainCard?.id === "vat_document_register_book"
|
||||
? evaluation.graph_domain_scope.filter((item) => isVatAllowedGraphDomain(item))
|
||||
: evaluation.graph_domain_scope;
|
||||
group.samples.push({
|
||||
source_entity: record.source_entity,
|
||||
|
|
|
|||
|
|
@ -79,6 +79,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) => {
|
||||
|
|
@ -243,7 +267,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 [];
|
||||
}
|
||||
|
|
@ -432,9 +456,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: []
|
||||
});
|
||||
}
|
||||
|
|
@ -681,28 +705,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,
|
||||
|
|
@ -765,10 +789,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"
|
||||
|
|
@ -811,7 +835,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;
|
||||
|
|
@ -824,17 +849,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)
|
||||
|
|
@ -878,12 +903,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";
|
||||
}
|
||||
|
|
@ -1056,12 +1086,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();
|
||||
|
|
@ -1225,6 +1255,9 @@ 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,
|
||||
|
|
@ -1235,15 +1268,21 @@ 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
|
||||
|
|
@ -1304,7 +1343,7 @@ 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,
|
||||
|
|
@ -1364,7 +1403,7 @@ 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
|
||||
}
|
||||
|
|
@ -1372,7 +1411,7 @@ 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,
|
||||
|
|
|
|||
|
|
@ -91,6 +91,10 @@ function isVatAccount(value) {
|
|||
const prefix = normalizeAccountPrefix(value);
|
||||
return prefix === "19" || prefix === "68";
|
||||
}
|
||||
function isFixedAssetAccount(value) {
|
||||
const prefix = normalizeAccountPrefix(value);
|
||||
return prefix === "01" || prefix === "02" || prefix === "08";
|
||||
}
|
||||
function isCloseCostsAccount(value) {
|
||||
const prefix = normalizeAccountPrefix(value);
|
||||
if (!prefix) {
|
||||
|
|
@ -100,22 +104,34 @@ function isCloseCostsAccount(value) {
|
|||
return (account >= 20 && account <= 44) || prefix === "97";
|
||||
}
|
||||
function inferFollowupActiveDomain(input) {
|
||||
const corpus = `${input.userMessage} ${input.previous.focus.active_query_subject ?? ""}`.toLowerCase();
|
||||
const messageCorpus = String(input.userMessage ?? "").toLowerCase();
|
||||
const contextualCorpus = `${messageCorpus} ${input.previous.focus.active_query_subject ?? ""}`.toLowerCase();
|
||||
const hasFixedAssetLexicalSignal = /(?:амортиз|основн(ые|ых|ым)?\s+средств|(?:^|[^a-zа-яё])ос(?:$|[^a-zа-яё])|объект[а-яё]*\s+ос|fixed\s*asset|depreciat)/i.test(messageCorpus);
|
||||
const hasFixedAssetAccountSignal = input.focusAccounts.some((item) => isFixedAssetAccount(item)) &&
|
||||
/(?:сч[её]т(?:а|у|ом|е)?\s*(?:01|02|08)|(?:01|02|08)(?:\.\d{2})?\s*\/\s*(?:01|02|08)(?:\.\d{2})?|\b0[128](?:\.\d{2})?\b)/i.test(messageCorpus);
|
||||
if (hasFixedAssetLexicalSignal || hasFixedAssetAccountSignal) {
|
||||
return "fixed_asset_amortization";
|
||||
}
|
||||
const hasSettlementSignal = input.focusAccounts.some((item) => isSettlementAccount(item)) ||
|
||||
/(60(?:\.\d{2})?|62(?:\.\d{2})?|оплат|расчет|расч[её]т|зачет|зач[её]т|аванс|долг|поставщ|покупат|settlement|payment|supplier|customer)/i.test(corpus);
|
||||
/(?:60(?:\.\d{2})?|62(?:\.\d{2})?|оплат|расч[её]т|зач[её]т|аванс|долг|поставщ|покупат|settlement|payment|supplier|customer)/i.test(messageCorpus);
|
||||
if (hasSettlementSignal) {
|
||||
return "settlements_60_62";
|
||||
}
|
||||
const hasVatSignal = input.focusAccounts.some((item) => isVatAccount(item)) ||
|
||||
/(ндс|счет[\s-]?фактур|сч[её]т[\s-]?фактур|книг[аи]|vat|invoice|book|register)/i.test(corpus);
|
||||
/(?:ндс|сч[её]т[\s-]?фактур|книг[аи]|vat|invoice|book|register)/i.test(messageCorpus);
|
||||
if (hasVatSignal) {
|
||||
return "vat_document_register_book";
|
||||
}
|
||||
const hasCloseSignal = input.focusAccounts.some((item) => isCloseCostsAccount(item)) ||
|
||||
/(закрыти|закрытие|месяц|затрат|распредел|списан|period\s*close|month\s*close|allocation|residual|cost)/i.test(corpus);
|
||||
/(?:закрыти|месяц|затрат|распредел|списан|period\s*close|month\s*close|allocation|residual|cost)/i.test(messageCorpus);
|
||||
if (hasCloseSignal) {
|
||||
return "month_close_costs_20_44";
|
||||
}
|
||||
if (/(?:60(?:\.\d{2})?|62(?:\.\d{2})?|оплат|расч[её]т|аванс|долг|settlement|payment)/i.test(contextualCorpus) &&
|
||||
(input.previous.followup_context?.active_domain === "settlements_60_62" ||
|
||||
input.previous.focus.domain === "settlements_60_62")) {
|
||||
return "settlements_60_62";
|
||||
}
|
||||
const routeDomain = deriveDomain(input.routeSummary);
|
||||
if (routeDomain && routeDomain !== "no_route") {
|
||||
return routeDomain;
|
||||
|
|
@ -321,7 +337,7 @@ function updateInvestigationState(input) {
|
|||
const uncoveredRequirementIds = collectUncoveredRequirementIds(input.coverageReport);
|
||||
const activeDomain = inferFollowupActiveDomain({
|
||||
userMessage: input.userMessage,
|
||||
focusAccounts: mergedFocusAccounts,
|
||||
focusAccounts: focusFromMessage,
|
||||
routeSummary: input.routeSummary,
|
||||
previous
|
||||
});
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ interface ComposeAnswerInput {
|
|||
focusDomainHint?: string | null;
|
||||
questionTypeHint?: QuestionTypeClass | null;
|
||||
companyAnchors?: CompanyAnchorSet | null;
|
||||
normalizationPeriodExplicit?: boolean;
|
||||
enableAnswerPolicyV11?: boolean;
|
||||
enableProblemCentricAnswerV1?: boolean;
|
||||
enableLifecycleAnswerV1?: boolean;
|
||||
|
|
@ -61,6 +62,7 @@ interface AnswerRenderContext {
|
|||
questionType: QuestionTypeClass;
|
||||
focusDomain: P0NarrativeDomain;
|
||||
anchors: CompanyAnchorUsage;
|
||||
userMessage?: string;
|
||||
}
|
||||
|
||||
function withUniquePush(target: string[], value: string): void {
|
||||
|
|
@ -259,6 +261,10 @@ const HUMAN_SIGNAL_MAP: Record<string, string> = {
|
|||
amount_independent_risk: "Проблема не выглядит случайной суммовой погрешностью.",
|
||||
wrong_document_type: "Есть признак неверного типа закрывающего документа.",
|
||||
fixed_asset_card_mismatch: "Есть несоответствие между карточкой ОС, документом движения и начислением.",
|
||||
contradictory_asset_state: "Состояние объекта ОС выглядит противоречивым по текущей опоре.",
|
||||
disposed: "Есть признак выбытия объекта ОС в цепочке состояния.",
|
||||
invalid_document_or_posting_transition: "Переход состояния ОС не подтвержден документами и проводками.",
|
||||
asset_card_to_depreciation: "Переход от карточки ОС к начислению амортизации подтвержден не полностью.",
|
||||
supplier_tail_analysis: "Есть признаки незавершенного расчетного контура по поставщикам.",
|
||||
cross_entity_breakage: "Есть разрыв между связанными объектами в одной цепочке.",
|
||||
deferred_expense_to_writeoff: "Ожидаемая цепочка списания РБП выглядит незавершенной.",
|
||||
|
|
@ -674,8 +680,13 @@ function stripSyntheticPlaceholders(value: string): string {
|
|||
}
|
||||
|
||||
function sanitizeUserFacingReply(value: string): string {
|
||||
const withoutDebugBlocks = String(value ?? "")
|
||||
const raw = String(value ?? "");
|
||||
const hardCutMatch = raw.match(/(?:^|\n)\s*#{0,6}\s*(?:debug_payload_json|technical_breakdown_json)\b/i);
|
||||
const preCut = hardCutMatch ? raw.slice(0, hardCutMatch.index) : raw;
|
||||
const withoutDebugBlocks = preCut
|
||||
.replace(/###\s*debug_payload_json[\s\S]*?(?:```[\s\S]*?```|$)/gi, "")
|
||||
.replace(/###\s*technical_breakdown_json[\s\S]*?(?:```[\s\S]*?```|$)/gi, "")
|
||||
.replace(/(?:^|\n)\s*#{0,6}\s*(?:debug_payload_json|technical_breakdown_json)\b[\s\S]*$/gi, "")
|
||||
.replace(/```json[\s\S]*?```/gi, "");
|
||||
const normalized = scrubRawTechnicalRefs(withoutDebugBlocks).replace(/[ \t]+\n/g, "\n");
|
||||
const cleanedLines = normalized
|
||||
|
|
@ -1384,7 +1395,7 @@ function buildProblemCentricActions(input: {
|
|||
}
|
||||
|
||||
if (input.missingAnchors.period && input.mode !== "clarification_required") {
|
||||
actions.push("Уточните период проверки (например, 2020-06), чтобы подтвердить незавершенное списание без лишнего шума.");
|
||||
actions.push("Уточните период проверки (например, июль 2020), чтобы подтвердить незавершенное списание без лишнего шума.");
|
||||
}
|
||||
|
||||
if (input.mode === "clarification_required") {
|
||||
|
|
@ -1423,7 +1434,7 @@ function buildProblemCentricClarifications(input: {
|
|||
const unitTypes = new Set(input.units.map((item) => item.problem_unit_type));
|
||||
|
||||
if (input.missingAnchors.period) {
|
||||
questions.push("Уточните период (например, 2020-06), в котором нужно проверить проблемный кластер.");
|
||||
questions.push("Уточните период (например, июль 2020), в котором нужно проверить проблемный кластер.");
|
||||
}
|
||||
if (input.missingAnchors.account) {
|
||||
questions.push("Уточните счет или связку счетов (например, 51/60), где вы ожидаете дефект.");
|
||||
|
|
@ -1564,6 +1575,15 @@ function asRecordObject(value: unknown): Record<string, unknown> | null {
|
|||
const EXPLICIT_PERIOD_ANCHOR_PATTERN =
|
||||
/(?:\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;
|
||||
|
||||
function hasPeriodAnchorInCompanyAnchors(anchors: CompanyAnchorSet | null | undefined): boolean {
|
||||
if (!anchors) {
|
||||
return false;
|
||||
}
|
||||
const dates = Array.isArray(anchors.dates) ? anchors.dates : [];
|
||||
const periods = Array.isArray(anchors.periods) ? anchors.periods : [];
|
||||
return dates.some((item) => String(item ?? "").trim().length > 0) || periods.some((item) => String(item ?? "").trim().length > 0);
|
||||
}
|
||||
|
||||
function hasPeriodAnchorInRetrieval(results: UnifiedRetrievalResult[]): boolean {
|
||||
for (const result of results) {
|
||||
const summary = asRecordObject(result.summary);
|
||||
|
|
@ -1606,9 +1626,20 @@ function hasAccountAnchorInRetrieval(results: UnifiedRetrievalResult[]): boolean
|
|||
return false;
|
||||
}
|
||||
|
||||
function detectMissingAnchors(userMessage: string, retrievalResults: UnifiedRetrievalResult[] = []): MissingAnchors {
|
||||
function detectMissingAnchors(
|
||||
userMessage: string,
|
||||
retrievalResults: UnifiedRetrievalResult[] = [],
|
||||
options?: {
|
||||
normalizationPeriodExplicit?: boolean;
|
||||
companyAnchors?: CompanyAnchorSet | null;
|
||||
}
|
||||
): MissingAnchors {
|
||||
const lower = String(userMessage ?? "").toLowerCase();
|
||||
const hasPeriod = EXPLICIT_PERIOD_ANCHOR_PATTERN.test(lower) || hasPeriodAnchorInRetrieval(retrievalResults);
|
||||
const hasPeriod =
|
||||
EXPLICIT_PERIOD_ANCHOR_PATTERN.test(lower) ||
|
||||
hasPeriodAnchorInRetrieval(retrievalResults) ||
|
||||
Boolean(options?.normalizationPeriodExplicit) ||
|
||||
hasPeriodAnchorInCompanyAnchors(options?.companyAnchors);
|
||||
const hasAccount =
|
||||
/(?:\bсчет\b|\baccount\b|\bschet\b|\b(?:0[1-9]|[1-9]\d)(?:\.\d{2})?\b|\b(?:60|62)\.\d{2}\s*\/\s*(?:60|62)\.\d{2}\b)/i.test(
|
||||
lower
|
||||
|
|
@ -1640,7 +1671,7 @@ function buildClarificationQuestions(input: {
|
|||
}
|
||||
|
||||
if (input.missingAnchors.period) {
|
||||
questions.push("Уточните период проверки (например, 2020-06).");
|
||||
questions.push("Уточните период проверки (например, июль 2020).");
|
||||
}
|
||||
if (input.missingAnchors.account) {
|
||||
questions.push("Уточните счет или группу счетов (например, 19, 60, 62).");
|
||||
|
|
@ -2106,8 +2137,8 @@ function inferP0NarrativeDomain(units: ProblemUnit[]): P0NarrativeDomain {
|
|||
}
|
||||
if (
|
||||
hasCloseAccount ||
|
||||
units.some((unit) => ["period_close", "deferred_expense", "fixed_asset"].includes(String(unit.lifecycle_domain ?? ""))) ||
|
||||
units.some((unit) => unit.problem_unit_type === "period_risk_cluster" || unit.problem_unit_type === "lifecycle_anomaly_node")
|
||||
units.some((unit) => ["period_close", "deferred_expense"].includes(String(unit.lifecycle_domain ?? ""))) ||
|
||||
units.some((unit) => unit.problem_unit_type === "period_risk_cluster")
|
||||
) {
|
||||
return "month_close_costs_20_44";
|
||||
}
|
||||
|
|
@ -2158,8 +2189,7 @@ function p0NarrativeDomainFromHint(value: string | null | undefined): P0Narrativ
|
|||
if (
|
||||
normalized.includes("month_close_costs_20_44") ||
|
||||
normalized.includes("period_close") ||
|
||||
normalized.includes("deferred_expense") ||
|
||||
normalized.includes("fixed_asset")
|
||||
normalized.includes("deferred_expense")
|
||||
) {
|
||||
return "month_close_costs_20_44";
|
||||
}
|
||||
|
|
@ -2370,7 +2400,31 @@ function evaluateP0DomainEvidenceGrounding(
|
|||
const topClass = classify(top);
|
||||
const hasAnyPrimary = substantive.some((item) => classify(item).inDomain);
|
||||
const hasForeignPrimary = topClass.foreignDomains.length > 0 && !topClass.inDomain;
|
||||
const blocked = hasForeignPrimary && !hasAnyPrimary && !hasControlledCrossDomainHandoffInResult(top);
|
||||
const topAccounts = collectResultAccounts(top);
|
||||
const topDomains = collectResultDomains(top);
|
||||
const topRelations = collectResultRelations(top);
|
||||
const vatPrimarySignals =
|
||||
topAccounts.filter((item) => isVatAccountToken(item)).length +
|
||||
topDomains.filter((item) => isVatDomainToken(item)).length +
|
||||
topRelations.filter((item) =>
|
||||
/invoice_to_vat|source_doc_present|invoice_linked|register_to_book|book_entry_generated|deduction_posted|vat_/i.test(item)
|
||||
).length;
|
||||
const vatForeignSignals =
|
||||
topAccounts.filter((item) => isSettlementAccountToken(item) || isCloseCostsAccountToken(item)).length +
|
||||
topDomains.filter((item) => isForeignToVatDomainToken(item)).length +
|
||||
topRelations.filter((item) =>
|
||||
/payment_to_settlement|statement_to_document|deferred_expense_to_writeoff|close_operation|allocation|period_close|fixed_asset/i.test(
|
||||
item
|
||||
)
|
||||
).length;
|
||||
const vatContaminatedPrimary =
|
||||
focusDomain === "vat_document_register_book" &&
|
||||
topClass.inDomain &&
|
||||
topClass.foreignDomains.length > 0 &&
|
||||
vatForeignSignals > Math.max(1, vatPrimarySignals) &&
|
||||
!hasControlledCrossDomainHandoffInResult(top);
|
||||
const blocked =
|
||||
(hasForeignPrimary && !hasAnyPrimary && !hasControlledCrossDomainHandoffInResult(top)) || vatContaminatedPrimary;
|
||||
|
||||
return {
|
||||
has_primary: hasAnyPrimary,
|
||||
|
|
@ -2403,7 +2457,7 @@ function hasStrongNarrativeDomainSignalInText(userMessage: string, domain: P0Nar
|
|||
if (domain === "month_close_costs_20_44") {
|
||||
return (
|
||||
accountTokens.some((item) => isCloseCostsAccountToken(item)) ||
|
||||
/(закрыти[ея]\s+месяц|закрытие\s+счетов|регламентн|косвенн|затрат|распределени|рбп|амортиз|финансовых\s+результат|month\s*close|period\s*close|close\s+operation)/i.test(
|
||||
/(закрыти[ея]\s+месяц|закрытие\s+счетов|регламентн|косвенн|затрат|распределени|рбп|финансовых\s+результат|month\s*close|period\s*close|close\s+operation)/i.test(
|
||||
text
|
||||
)
|
||||
);
|
||||
|
|
@ -2411,6 +2465,23 @@ function hasStrongNarrativeDomainSignalInText(userMessage: string, domain: P0Nar
|
|||
return false;
|
||||
}
|
||||
|
||||
function hasFixedAssetAmortizationSignalInText(userMessage: string): boolean {
|
||||
const text = String(userMessage ?? "").toLowerCase();
|
||||
const explicitFixedAssetAccountMention =
|
||||
/(?:сч(?:е|ё)т(?:а|у|ом|ов)?\s*(?:№|#|:)?\s*0[12](?:\.\d{1,2})?|\b0[12]\s*\/\s*0[12]\b)/iu.test(text);
|
||||
return (
|
||||
explicitFixedAssetAccountMention ||
|
||||
/(основн(ые|ых|ым)?\s+средств|(?:^|[^a-zа-яё])ос(?:$|[^a-zа-яё])|амортиз|depreciat|fixed\s*asset)/i.test(text)
|
||||
);
|
||||
}
|
||||
|
||||
function hasExplicitMonthCloseSignalInText(userMessage: string): boolean {
|
||||
const text = String(userMessage ?? "").toLowerCase();
|
||||
return /(закрыти[ея]\s+месяц|закрытие\s+счетов|регламентн|косвенн|затрат|распределени|рбп|финансовых\s+результат|month\s*close|period\s*close|close\s+operation)/i.test(
|
||||
text
|
||||
);
|
||||
}
|
||||
|
||||
function inferP0FocusNarrativeDomain(
|
||||
userMessage: string,
|
||||
results: UnifiedRetrievalResult[],
|
||||
|
|
@ -2421,12 +2492,16 @@ function inferP0FocusNarrativeDomain(
|
|||
const fromMessage = inferNarrativeDomainFromText(userMessage);
|
||||
const strongFromMessage = Boolean(fromMessage && hasStrongNarrativeDomainSignalInText(userMessage, fromMessage));
|
||||
const fromDomainGuard = inferP0NarrativeDomainFromDomainGuards(results);
|
||||
const fixedAssetOnlySignal = hasFixedAssetAmortizationSignalInText(userMessage) && !hasExplicitMonthCloseSignalInText(userMessage);
|
||||
if (fromHint && fromMessage && fromHint !== fromMessage) {
|
||||
return strongFromMessage ? fromMessage : fromHint;
|
||||
}
|
||||
if (fromHint) {
|
||||
return fromHint;
|
||||
}
|
||||
if (fromDomainGuard === "month_close_costs_20_44" && fixedAssetOnlySignal) {
|
||||
return null;
|
||||
}
|
||||
if (fromDomainGuard && fromMessage && fromDomainGuard !== fromMessage) {
|
||||
return strongFromMessage ? fromMessage : fromDomainGuard;
|
||||
}
|
||||
|
|
@ -2787,6 +2862,7 @@ function buildProblemCentricAnswerStructure(input: {
|
|||
const openUncertainties = uniqueStrings(
|
||||
[
|
||||
...input.groundingCheck.missing_requirements,
|
||||
...(input.domainLockMiss ? ["primary_domain_evidence_not_confirmed"] : []),
|
||||
...(input.missingAnchors.period ? ["missing_anchor:period"] : []),
|
||||
...(input.mode === "clarification_required" && input.missingAnchors.account ? ["missing_anchor:account"] : []),
|
||||
...(input.mode === "clarification_required" && input.missingAnchors.documentOrObject
|
||||
|
|
@ -2870,6 +2946,8 @@ function limitationReasonToUserText(code: EvidenceLimitationReasonCode): string
|
|||
function inferNarrativeDomainFromText(value: string): P0NarrativeDomain {
|
||||
const text = String(value ?? "").toLowerCase();
|
||||
const accountTokens = extractAccountNumbersFromNarrativeText(text);
|
||||
const fixedAssetSignal = hasFixedAssetAmortizationSignalInText(text);
|
||||
const explicitMonthCloseSignal = hasExplicitMonthCloseSignalInText(text);
|
||||
|
||||
let settlementScore = 0;
|
||||
let vatScore = 0;
|
||||
|
|
@ -2898,14 +2976,14 @@ function inferNarrativeDomainFromText(value: string): P0NarrativeDomain {
|
|||
) {
|
||||
vatScore += 3;
|
||||
}
|
||||
if (
|
||||
/(закрыти[ея]\s+месяц|закрытие\s+счетов|регламентн|косвенн|затрат|распределени|рбп|амортиз|финансовых\s+результат|month\s*close|period\s*close|close\s+operation)/i.test(
|
||||
text
|
||||
)
|
||||
) {
|
||||
if (explicitMonthCloseSignal) {
|
||||
monthCloseScore += 3;
|
||||
}
|
||||
|
||||
if (fixedAssetSignal && !explicitMonthCloseSignal && settlementScore === 0 && vatScore === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const maxScore = Math.max(settlementScore, vatScore, monthCloseScore);
|
||||
if (maxScore <= 0) {
|
||||
return null;
|
||||
|
|
@ -2960,9 +3038,50 @@ function buildShortSectionLine(structure: AnswerStructureV11): string {
|
|||
return incomplete ? "Проблема подтверждается частично на текущей опоре." : "Проблема подтверждена на текущей опоре.";
|
||||
}
|
||||
|
||||
function humanizeCompositeDirectAnswer(value: string): string | null {
|
||||
const raw = String(value ?? "").trim();
|
||||
if (!raw) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const tokenPattern = /\b[a-z][a-z0-9_:-]{2,}\b/gi;
|
||||
const tokenMappings = uniqueStrings(
|
||||
Array.from(raw.matchAll(tokenPattern))
|
||||
.map((match) => humanizeTechnicalToken(String(match?.[0] ?? "")))
|
||||
.filter((item): item is string => Boolean(item))
|
||||
.map((item) => ensureSentence(item)),
|
||||
4
|
||||
);
|
||||
|
||||
const residualRaw = raw
|
||||
.replace(tokenPattern, " ")
|
||||
.replace(/[()]/g, " ")
|
||||
.replace(/\s*[;:]\s*/g, " ")
|
||||
.replace(/\s{2,}/g, " ")
|
||||
.trim();
|
||||
const residualText = sanitizeUserText(residualRaw);
|
||||
|
||||
const lines: string[] = [...tokenMappings];
|
||||
if (residualText && !hasUserFacingLeakage(residualText)) {
|
||||
lines.push(ensureSentence(residualText));
|
||||
}
|
||||
|
||||
const compact = dedupeNarrativeLines(lines, 3);
|
||||
if (compact.length === 0) {
|
||||
return null;
|
||||
}
|
||||
return compact.join(" ");
|
||||
}
|
||||
|
||||
function buildBrokenSectionLines(structure: AnswerStructureV11): string[] {
|
||||
const direct = sanitizeUserText(structure.direct_answer);
|
||||
if (direct) {
|
||||
if (/\b[a-z]+_[a-z0-9_:-]+\b/i.test(direct)) {
|
||||
const compositeHumanized = humanizeCompositeDirectAnswer(direct);
|
||||
if (compositeHumanized) {
|
||||
return [compositeHumanized];
|
||||
}
|
||||
}
|
||||
const mapped = mapDefectTokenToNarrative(direct) ?? humanizeTechnicalToken(direct);
|
||||
if (mapped) {
|
||||
return [ensureSentence(mapped)];
|
||||
|
|
@ -2975,7 +3094,7 @@ function buildBrokenSectionLines(structure: AnswerStructureV11): string[] {
|
|||
return ["Есть признаки нарушения в связанной цепочке документов и проводок."];
|
||||
}
|
||||
|
||||
function buildWhySectionLines(structure: AnswerStructureV11): string[] {
|
||||
function buildWhySectionLines(structure: AnswerStructureV11, context?: AnswerRenderContext): string[] {
|
||||
const noteLines = dedupeNarrativeLines(
|
||||
structure.mechanism_block.mechanism_notes
|
||||
.map((item) => sanitizeSupportLine(item))
|
||||
|
|
@ -2984,11 +3103,31 @@ function buildWhySectionLines(structure: AnswerStructureV11): string[] {
|
|||
4
|
||||
);
|
||||
|
||||
const domain = context?.focusDomain ?? inferNarrativeDomainFromText(sanitizeUserText(structure.direct_answer) ?? "");
|
||||
const mechanismCorpus = `${structure.direct_answer} ${structure.mechanism_block.mechanism_notes.join(" ")} ${structure.evidence_block.mechanism_notes.join(
|
||||
" "
|
||||
)}`;
|
||||
const fixedAssetContextSignal = hasFixedAssetContextSignal(context);
|
||||
const fixedAssetSignal =
|
||||
fixedAssetContextSignal ||
|
||||
((context?.focusDomain ?? null) !== "settlements_60_62" && hasFixedAssetSignalInStructure(structure, context));
|
||||
const rbpSignal = hasRbpContextSignal(context) || hasRbpSignalInText(mechanismCorpus);
|
||||
|
||||
const lines: string[] = [...noteLines];
|
||||
if (structure.mechanism_block.status === "grounded") {
|
||||
lines.push("Признак проблемы повторяется в связанных документах и проводках.");
|
||||
} else if (structure.mechanism_block.status === "limited") {
|
||||
lines.push("Часть ожидаемой цепочки подтверждена, но ключевой переход закрытия не подтвержден.");
|
||||
if (domain === "vat_document_register_book") {
|
||||
lines.push("Часть НДС-цепочки подтверждена, но один или несколько переходов документ -> счет-фактура -> регистр -> книга не подтверждены.");
|
||||
} else if (fixedAssetSignal) {
|
||||
lines.push("По ОС часть переходов к начислению амортизации подтверждена не полностью, поэтому есть риск пропуска отдельных объектов.");
|
||||
} else if (rbpSignal) {
|
||||
lines.push("По РБП часть списаний к концу периода подтверждена не полностью, поэтому остаток может сохраняться дольше ожидаемого.");
|
||||
} else if (domain === "month_close_costs_20_44") {
|
||||
lines.push("Часть шагов закрытия периода подтверждена, но ключевой переход распределения/закрытия не подтвержден.");
|
||||
} else {
|
||||
lines.push("Часть ожидаемой цепочки подтверждена, но ключевой переход не подтвержден.");
|
||||
}
|
||||
} else {
|
||||
lines.push("Сигнал проблемы есть, но механизм подтвержден не полностью.");
|
||||
}
|
||||
|
|
@ -3044,7 +3183,8 @@ function buildCoverageSplitLines(
|
|||
|
||||
function buildEvidenceSectionLines(
|
||||
structure: AnswerStructureV11,
|
||||
questionType: QuestionTypeClass = "unknown"
|
||||
questionType: QuestionTypeClass = "unknown",
|
||||
context?: AnswerRenderContext
|
||||
): string[] {
|
||||
const evidenceCount = Array.isArray(structure.evidence_block.evidence_ids) ? structure.evidence_block.evidence_ids.length : 0;
|
||||
const sourceCount = Array.isArray(structure.evidence_block.source_refs) ? structure.evidence_block.source_refs.length : 0;
|
||||
|
|
@ -3058,14 +3198,39 @@ function buildEvidenceSectionLines(
|
|||
structure.evidence_block.coverage_note === "coverage_partial_or_limited";
|
||||
const lines: string[] = [];
|
||||
const coverageSplitLines = buildCoverageSplitLines(structure, questionType);
|
||||
const domain = context?.focusDomain ?? inferNarrativeDomainFromText(sanitizeUserText(structure.direct_answer) ?? "");
|
||||
const evidenceCorpus = `${structure.direct_answer} ${structure.mechanism_block.mechanism_notes.join(" ")} ${structure.evidence_block.mechanism_notes.join(
|
||||
" "
|
||||
)}`;
|
||||
const fixedAssetContextSignal = hasFixedAssetContextSignal(context);
|
||||
const fixedAssetSignal =
|
||||
fixedAssetContextSignal ||
|
||||
((context?.focusDomain ?? null) !== "settlements_60_62" && hasFixedAssetSignalInStructure(structure, context));
|
||||
const rbpSignal = hasRbpContextSignal(context) || hasRbpSignalInText(evidenceCorpus);
|
||||
|
||||
if (questionType === "what_is_it_grounded_on") {
|
||||
if (domain === "vat_document_register_book") {
|
||||
lines.push("Основание собрано по НДС-цепочке: документ, счет-фактура, регистр НДС и запись книги.");
|
||||
} else if (fixedAssetSignal) {
|
||||
lines.push("Основание собрано по ОС: карточка объекта, параметры амортизации, начисление и движения по 01/02.");
|
||||
} else if (rbpSignal) {
|
||||
lines.push("Основание собрано по РБП: объект списания, документ списания и остаток на конец периода.");
|
||||
} else {
|
||||
lines.push("Основание вывода перечислено по подтвержденным документам, регистрам и проводкам.");
|
||||
}
|
||||
} else if (questionType === "prove_or_guess") {
|
||||
lines.push("Основание разделено на подтвержденную часть и зону гипотез.");
|
||||
} else if (questionType === "which_chains_are_complete_vs_incomplete") {
|
||||
if (domain === "vat_document_register_book") {
|
||||
lines.push("Опора собрана по звеньям НДС-цепочки, чтобы разделить полные и неполные переходы.");
|
||||
} else if (rbpSignal) {
|
||||
lines.push("Опора собрана по РБП-цепочке, чтобы разделить подтвержденное и неподтвержденное списание.");
|
||||
} else if (fixedAssetSignal) {
|
||||
lines.push("Опора собрана по ОС-цепочке, чтобы разделить подтвержденные и неподтвержденные начисления амортизации.");
|
||||
} else {
|
||||
lines.push("Опора собрана так, чтобы разделить цепочки на полные и неполные.");
|
||||
}
|
||||
}
|
||||
|
||||
if (evidenceCount > 0) {
|
||||
lines.push(`Вывод опирается на ${evidenceCount} подтвержденных наблюдений в текущем срезе.`);
|
||||
|
|
@ -3076,10 +3241,20 @@ function buildEvidenceSectionLines(
|
|||
if (claimLinks > 0) {
|
||||
lines.push("Есть связка между основным выводом и подтверждающими записями.");
|
||||
}
|
||||
if (structure.evidence_block.coverage_note === "coverage_partial_or_limited") {
|
||||
if (structure.evidence_block.coverage_note === "coverage_partial_or_limited" || reliabilityLimited) {
|
||||
if (domain === "vat_document_register_book") {
|
||||
lines.push("Опора частичная: по НДС-цепочке не подтверждены одно или несколько звеньев.");
|
||||
} else if (fixedAssetSignal) {
|
||||
lines.push("Опора частичная: не по всем объектам ОС подтверждено попадание в начисление амортизации.");
|
||||
} else if (rbpSignal) {
|
||||
lines.push("Опора частичная: не по всем объектам РБП подтверждено списание к концу периода.");
|
||||
} else if (structure.evidence_block.coverage_note === "coverage_partial_or_limited") {
|
||||
lines.push("Опора частичная: часть требований покрыта не полностью.");
|
||||
} else if (evidenceCount > 0) {
|
||||
lines.push(reliabilityLimited ? "Опора есть, но достаточна только для предварительного вывода." : "Опора достаточна для первичного вывода.");
|
||||
lines.push("Опора есть, но достаточна только для предварительного вывода.");
|
||||
}
|
||||
} else if (evidenceCount > 0) {
|
||||
lines.push("Опора достаточна для первичного вывода.");
|
||||
}
|
||||
|
||||
if (lines.length === 0) {
|
||||
|
|
@ -3110,6 +3285,143 @@ function buildDefaultChecksByDomain(domain: P0NarrativeDomain): string[] {
|
|||
return ["Проверьте связку документов и проводок по проблемному участку в указанном периоде."];
|
||||
}
|
||||
|
||||
function hasFixedAssetAnchorContext(context?: AnswerRenderContext): boolean {
|
||||
if (!context) {
|
||||
return false;
|
||||
}
|
||||
const corpus = [...context.anchors.present, ...context.anchors.used].join(" ").toLowerCase();
|
||||
return /(?:doc_type:amortization|account:0[12]|амортиз|основн|(?:^|[^a-zа-яё])ос(?:$|[^a-zа-яё])|fixed\s*asset|depreciat)/i.test(
|
||||
corpus
|
||||
);
|
||||
}
|
||||
|
||||
function hasFixedAssetContextSignal(context?: AnswerRenderContext): boolean {
|
||||
if (!context) {
|
||||
return false;
|
||||
}
|
||||
const corpus = [...context.anchors.present, ...context.anchors.used, context.userMessage ?? ""].join(" ").toLowerCase();
|
||||
return (
|
||||
hasFixedAssetAnchorContext(context) ||
|
||||
hasFixedAssetAmortizationSignalInText(corpus) ||
|
||||
/(?:\bос\b|основн(?:ые|ых)?\s+средств|амортиз|сч(?:е|ё)т\s*0[12])/i.test(corpus)
|
||||
);
|
||||
}
|
||||
|
||||
function hasRbpAnchorContext(context?: AnswerRenderContext): boolean {
|
||||
if (!context) {
|
||||
return false;
|
||||
}
|
||||
const corpus = [...context.anchors.present, ...context.anchors.used].join(" ").toLowerCase();
|
||||
return /(?:\brbp(?:[_\s-]?writeoff)?\b|рбп|deferred[_\s-]?expense(?:[_\s-]?to[_\s-]?writeoff)?|doc_type:(?:deferred|rbp_writeoff)|счет\s*97|account:97)/i.test(
|
||||
corpus
|
||||
);
|
||||
}
|
||||
|
||||
function hasRbpContextSignal(context?: AnswerRenderContext): boolean {
|
||||
if (!context) {
|
||||
return false;
|
||||
}
|
||||
const corpus = [...context.anchors.present, ...context.anchors.used, context.userMessage ?? ""].join(" ");
|
||||
return hasRbpAnchorContext(context) || hasRbpSignalInText(corpus);
|
||||
}
|
||||
|
||||
function hasRbpSignalInText(value: string): boolean {
|
||||
const text = String(value ?? "").toLowerCase();
|
||||
return /(?:\brbp(?:[_\s-]?writeoff)?\b|рбп|deferred[_\s-]?expense(?:[_\s-]?to[_\s-]?writeoff)?|счет\s*97|списани[ея]\s+рбп|остат(ок|ки)\s+рбп)/i.test(
|
||||
text
|
||||
);
|
||||
}
|
||||
|
||||
function hasFixedAssetSignalInStructure(structure: AnswerStructureV11, context?: AnswerRenderContext): boolean {
|
||||
const corpus = [
|
||||
structure.direct_answer,
|
||||
...structure.mechanism_block.mechanism_notes,
|
||||
...structure.evidence_block.mechanism_notes,
|
||||
...(structure.evidence_block.source_refs ?? []),
|
||||
...(structure.evidence_block.evidence_ids ?? []),
|
||||
...(context?.anchors.present ?? []),
|
||||
...(context?.anchors.used ?? [])
|
||||
]
|
||||
.filter(Boolean)
|
||||
.join(" ");
|
||||
|
||||
if (hasFixedAssetAnchorContext(context) || hasFixedAssetAmortizationSignalInText(corpus)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return /(?:asset_card_to_depreciation|fixed_asset|fixed_assets|амортиз|основн(?:ые|ых)?\s+средств|сч(?:е|ё)т\s*0[12]|\b0[12](?:\.\d{2})?\b)/i.test(
|
||||
corpus
|
||||
);
|
||||
}
|
||||
|
||||
function buildFixedAssetChecksByQuestionType(questionType: QuestionTypeClass): string[] {
|
||||
if (questionType === "what_to_check_first") {
|
||||
return [
|
||||
"Проверьте по каждому объекту ОС карточку и параметр амортизации (способ, срок, дата начала начисления).",
|
||||
"Сверьте ввод в эксплуатацию и попадание объекта в набор начисления амортизации за нужный период.",
|
||||
"Подтвердите начисление по объектам проводками и регистром амортизации."
|
||||
];
|
||||
}
|
||||
if (questionType === "prove_or_guess") {
|
||||
return [
|
||||
"Разделите доказанные и предположительные участки по цепочке ОС: принятие -> ввод -> начисление амортизации.",
|
||||
"Проверьте, какие объекты отсутствуют в наборе начисления или имеют некорректные параметры амортизации."
|
||||
];
|
||||
}
|
||||
if (questionType === "where_break_is") {
|
||||
return [
|
||||
"Локализуйте разрыв в цепочке ОС: карточка объекта -> ввод в эксплуатацию -> начисление амортизации.",
|
||||
"Сверьте, на каком шаге пропадает подтверждение по конкретным объектам."
|
||||
];
|
||||
}
|
||||
if (questionType === "what_is_it_grounded_on") {
|
||||
return [
|
||||
"Перечислите основание: карточка ОС, документ ввода в эксплуатацию, запись регистра амортизации, проводки по начислению."
|
||||
];
|
||||
}
|
||||
return [
|
||||
"Проверьте ОС-контур: объект ОС -> ввод в эксплуатацию -> начисление амортизации по счетам 01/02.",
|
||||
"Сверьте параметр амортизации и наличие начисления по каждому объекту ОС в периоде."
|
||||
];
|
||||
}
|
||||
|
||||
function buildRbpChecksByQuestionType(questionType: QuestionTypeClass): string[] {
|
||||
if (questionType === "what_to_check_first") {
|
||||
return [
|
||||
"Проверьте список объектов РБП, которые должны были списаться к концу периода.",
|
||||
"Сверьте документ списания РБП и движение по счету 97 по каждому объекту.",
|
||||
"Проверьте остаток РБП после списания и причину, если часть суммы остается активной."
|
||||
];
|
||||
}
|
||||
if (questionType === "prove_or_guess") {
|
||||
return [
|
||||
"Разделите по РБП доказанное и гипотезу: где списание подтверждено, а где есть только косвенные признаки.",
|
||||
"Проверьте, для каких объектов РБП нет подтверждения списания на конец периода."
|
||||
];
|
||||
}
|
||||
if (questionType === "where_break_is") {
|
||||
return [
|
||||
"Локализуйте разрыв в РБП-цепочке: объект РБП -> документ списания -> движение по счету 97.",
|
||||
"Проверьте, на каком шаге исчезает подтверждение списания."
|
||||
];
|
||||
}
|
||||
if (questionType === "what_is_it_grounded_on") {
|
||||
return [
|
||||
"Перечислите основание по РБП: объект, документ списания, движение по счету 97, остаток на конец периода."
|
||||
];
|
||||
}
|
||||
if (questionType === "which_chains_are_complete_vs_incomplete") {
|
||||
return [
|
||||
"Разделите РБП-цепочки на: списание подтверждено, подтверждено частично, не подтверждено.",
|
||||
"Проверьте, где к концу периода остается РБП без подтвержденного списания."
|
||||
];
|
||||
}
|
||||
return [
|
||||
"Проверьте РБП-контур: объект РБП -> документ списания -> движение по счету 97.",
|
||||
"Сверьте остаток РБП на конец периода и причину, если часть суммы не списана."
|
||||
];
|
||||
}
|
||||
|
||||
function buildQuestionTypeDomainChecks(questionType: QuestionTypeClass, domain: P0NarrativeDomain): string[] {
|
||||
if (questionType === "what_to_check_first") {
|
||||
if (domain === "settlements_60_62") {
|
||||
|
|
@ -3238,7 +3550,23 @@ function buildChecksSectionLines(structure: AnswerStructureV11, context?: Answer
|
|||
const broken = sanitizeUserText(structure.direct_answer) ?? "";
|
||||
const domain = context?.focusDomain ?? inferNarrativeDomainFromText(broken);
|
||||
const questionType = context?.questionType ?? "unknown";
|
||||
const domainFallback = buildQuestionTypeDomainChecks(questionType, domain);
|
||||
const effectiveQuestionType: QuestionTypeClass = questionType === "unknown" ? "what_to_check_first" : questionType;
|
||||
const fixedAssetMechanismSignal = hasFixedAssetAmortizationSignalInText(
|
||||
`${structure.direct_answer} ${structure.mechanism_block.mechanism_notes.join(" ")} ${structure.evidence_block.mechanism_notes.join(" ")}`
|
||||
);
|
||||
const domainAndEvidenceCorpus = `${broken} ${structure.mechanism_block.mechanism_notes.join(" ")} ${structure.evidence_block.mechanism_notes.join(
|
||||
" "
|
||||
)}`;
|
||||
const fixedAssetContextSignal = hasFixedAssetContextSignal(context);
|
||||
const fixedAssetCase =
|
||||
fixedAssetContextSignal ||
|
||||
(domain !== "settlements_60_62" && (hasFixedAssetSignalInStructure(structure, context) || fixedAssetMechanismSignal));
|
||||
const rbpCase = hasRbpContextSignal(context) || hasRbpSignalInText(domainAndEvidenceCorpus);
|
||||
const domainFallback = fixedAssetCase
|
||||
? buildFixedAssetChecksByQuestionType(effectiveQuestionType)
|
||||
: rbpCase
|
||||
? buildRbpChecksByQuestionType(effectiveQuestionType)
|
||||
: buildQuestionTypeDomainChecks(questionType, domain);
|
||||
const hasMissingPeriod = structure.uncertainty_block.open_uncertainties.some((item) =>
|
||||
/missing_anchor:period/i.test(String(item ?? ""))
|
||||
);
|
||||
|
|
@ -3267,16 +3595,21 @@ function buildChecksSectionLines(structure: AnswerStructureV11, context?: Answer
|
|||
}
|
||||
}
|
||||
}
|
||||
const filteredLines =
|
||||
fixedAssetCase || rbpCase
|
||||
? lines.filter((item) => !/проверьте связку документов и проводок по проблемному участку/i.test(item))
|
||||
: lines;
|
||||
|
||||
if (hasMissingPeriod) {
|
||||
if (questionType === "what_to_check_first") {
|
||||
lines.push("Уточните период, если он не зафиксирован в исходной формулировке вопроса.");
|
||||
} else if (domain === "settlements_60_62" && lines.length > 0) {
|
||||
lines.push("Уточните период проверки, чтобы подтвердить проблему без лишнего шума.");
|
||||
filteredLines.push("Уточните период, если он не зафиксирован в исходной формулировке вопроса.");
|
||||
} else if (domain === "settlements_60_62" && filteredLines.length > 0) {
|
||||
filteredLines.push("Уточните период проверки, чтобы подтвердить проблему без лишнего шума.");
|
||||
} else {
|
||||
lines.unshift("Уточните период проверки, чтобы подтвердить проблему без лишнего шума.");
|
||||
filteredLines.unshift("Уточните период проверки, чтобы подтвердить проблему без лишнего шума.");
|
||||
}
|
||||
}
|
||||
return dedupeNarrativeLines(lines, questionType === "what_to_check_first" ? 3 : 5);
|
||||
return dedupeNarrativeLines(filteredLines, questionType === "what_to_check_first" ? 3 : 5);
|
||||
}
|
||||
|
||||
function humanizeLimitationToken(value: string): string | null {
|
||||
|
|
@ -3366,6 +3699,15 @@ function buildQuestionTypeShortLine(context: AnswerRenderContext): string | null
|
|||
return "\u0412\u044b\u0432\u043e\u0434 \u0440\u0430\u0437\u0434\u0435\u043b\u0435\u043d \u043d\u0430 \u0434\u043e\u043a\u0430\u0437\u0430\u043d\u043d\u0443\u044e \u0447\u0430\u0441\u0442\u044c \u0438 \u0433\u0438\u043f\u043e\u0442\u0435\u0437\u0443.";
|
||||
}
|
||||
if (context.questionType === "what_is_it_grounded_on") {
|
||||
if (hasRbpContextSignal(context)) {
|
||||
return "Ниже перечислены основания вывода по РБП: списание, остаток и подтверждение на конец периода.";
|
||||
}
|
||||
if (hasFixedAssetAnchorContext(context)) {
|
||||
return "Ниже перечислены основания вывода по ОС/амортизации по данным учета.";
|
||||
}
|
||||
if (context.focusDomain === "vat_document_register_book") {
|
||||
return "Ниже перечислены основания вывода по НДС-цепочке по данным учета.";
|
||||
}
|
||||
return "\u041d\u0438\u0436\u0435 \u043f\u0435\u0440\u0435\u0447\u0438\u0441\u043b\u0435\u043d\u044b \u043e\u0441\u043d\u043e\u0432\u0430\u043d\u0438\u044f \u0432\u044b\u0432\u043e\u0434\u0430 \u043f\u043e \u0434\u0430\u043d\u043d\u044b\u043c \u0443\u0447\u0435\u0442\u0430.";
|
||||
}
|
||||
if (context.questionType === "which_chains_are_complete_vs_incomplete") {
|
||||
|
|
@ -3384,8 +3726,14 @@ function buildQuestionTypeShortLine(context: AnswerRenderContext): string | null
|
|||
if (context.focusDomain === "month_close_costs_20_44") {
|
||||
return "Наиболее вероятная причина: цепочка распределения затрат и закрытия месяца подтверждена не полностью.";
|
||||
}
|
||||
if (hasFixedAssetAnchorContext(context)) {
|
||||
return "Наиболее вероятная причина: по ОС часть переходов от параметров амортизации к начислению подтверждена не полностью.";
|
||||
}
|
||||
return "Наиболее вероятный механизм проблемы подтвержден частично и требует первичной проверки.";
|
||||
}
|
||||
if (context.questionType === "unknown" && hasFixedAssetAnchorContext(context)) {
|
||||
return "Риск неполного начисления амортизации подтвержден частично и требует проверки по объектам ОС.";
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
|
|
@ -3416,6 +3764,15 @@ function buildQuestionTypeWhyLine(context: AnswerRenderContext): string | null {
|
|||
return "\u0426\u0435\u043f\u043e\u0447\u043a\u0438 \u0440\u0430\u0437\u0434\u0435\u043b\u0435\u043d\u044b \u043d\u0430 \u0437\u0430\u0432\u0435\u0440\u0448\u0435\u043d\u043d\u044b\u0435 \u0438 \u043d\u0435\u0437\u0430\u0432\u0435\u0440\u0448\u0435\u043d\u043d\u044b\u0435 \u043f\u043e \u0442\u0435\u043a\u0443\u0449\u0435\u0439 \u043e\u043f\u043e\u0440\u0435.";
|
||||
}
|
||||
if (context.questionType === "what_is_it_grounded_on") {
|
||||
if (hasRbpContextSignal(context)) {
|
||||
return "Фокус ответа по РБП: подтверждение списания и остатка на конец периода, а не общий close-narrative.";
|
||||
}
|
||||
if (hasFixedAssetAnchorContext(context)) {
|
||||
return "Фокус ответа по ОС: подтверждение попадания объектов в начисление амортизации.";
|
||||
}
|
||||
if (context.focusDomain === "vat_document_register_book") {
|
||||
return "Фокус ответа по НДС: подтверждение переходов между документом, счетом-фактурой, регистром и книгой.";
|
||||
}
|
||||
return "\u0424\u043e\u043a\u0443\u0441 \u043e\u0442\u0432\u0435\u0442\u0430 \u0441\u043c\u0435\u0449\u0435\u043d \u0432 \u0434\u043e\u043a\u0430\u0437\u0430\u0442\u0435\u043b\u044c\u043d\u044b\u0435 \u0438\u0441\u0442\u043e\u0447\u043d\u0438\u043a\u0438, \u0430 \u043d\u0435 \u0432 \u043e\u0431\u0449\u0438\u0439 narrative.";
|
||||
}
|
||||
return null;
|
||||
|
|
@ -3423,12 +3780,30 @@ function buildQuestionTypeWhyLine(context: AnswerRenderContext): string | null {
|
|||
|
||||
function buildQuestionTypeEvidenceLine(context: AnswerRenderContext): string | null {
|
||||
if (context.questionType === "what_is_it_grounded_on") {
|
||||
if (hasRbpContextSignal(context)) {
|
||||
return "Опора перечислена по РБП-объектам, документам списания и остаткам на конец периода.";
|
||||
}
|
||||
if (hasFixedAssetAnchorContext(context)) {
|
||||
return "Опора перечислена по ОС-объектам, параметрам амортизации и движениям начисления.";
|
||||
}
|
||||
if (context.focusDomain === "vat_document_register_book") {
|
||||
return "Опора перечислена по НДС-звеньям: документ, счет-фактура, регистр и книга.";
|
||||
}
|
||||
return "\u0412 \u044d\u0442\u043e\u043c \u043e\u0442\u0432\u0435\u0442\u0435 \u0432 \u043f\u0440\u0438\u043e\u0440\u0438\u0442\u0435\u0442\u0435 \u043f\u043e\u043a\u0430\u0437\u0430\u043d\u044b \u0438\u043c\u0435\u043d\u043d\u043e \u043e\u0441\u043d\u043e\u0432\u0430\u043d\u0438\u044f \u0432\u044b\u0432\u043e\u0434\u0430.";
|
||||
}
|
||||
if (context.questionType === "prove_or_guess") {
|
||||
return "\u0421\u0438\u043b\u0430 \u0432\u044b\u0432\u043e\u0434\u0430 \u043e\u0446\u0435\u043d\u0435\u043d\u0430 \u043f\u043e \u043f\u0440\u044f\u043c\u043e\u0439 \u043e\u043f\u043e\u0440\u0435, \u0430 \u043d\u0435 \u043f\u043e \u0434\u043e\u0433\u0430\u0434\u043a\u0430\u043c.";
|
||||
}
|
||||
if (context.questionType === "which_chains_are_complete_vs_incomplete") {
|
||||
if (context.focusDomain === "vat_document_register_book") {
|
||||
return "Опора собрана по НДС-звеньям, чтобы разделить полные и неполные переходы.";
|
||||
}
|
||||
if (hasRbpContextSignal(context)) {
|
||||
return "Опора собрана по РБП-цепочке, чтобы разделить подтвержденное и неподтвержденное списание.";
|
||||
}
|
||||
if (hasFixedAssetAnchorContext(context)) {
|
||||
return "Опора собрана по ОС-цепочке, чтобы разделить подтвержденные и неподтвержденные начисления амортизации.";
|
||||
}
|
||||
return "\u041e\u043f\u043e\u0440\u0430 \u0441\u043e\u0431\u0440\u0430\u043d\u0430 \u0442\u0430\u043a, \u0447\u0442\u043e\u0431\u044b \u0447\u0435\u0441\u0442\u043d\u043e \u0440\u0430\u0437\u0434\u0435\u043b\u0438\u0442\u044c \u043f\u043e\u043b\u043d\u044b\u0435 \u0438 \u043d\u0435\u043f\u043e\u043b\u043d\u044b\u0435 \u0446\u0435\u043f\u043e\u0447\u043a\u0438.";
|
||||
}
|
||||
return null;
|
||||
|
|
@ -3452,9 +3827,27 @@ function buildQuestionTypeCheckLine(context: AnswerRenderContext): string | null
|
|||
return "\u041f\u0435\u0440\u0432\u044b\u043c \u0434\u0435\u0439\u0441\u0442\u0432\u0438\u0435\u043c \u043e\u0442\u0434\u0435\u043b\u0438\u0442\u0435 \u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442\u0430\u043b\u044c\u043d\u043e \u043f\u043e\u0434\u0442\u0432\u0435\u0440\u0436\u0434\u0435\u043d\u043d\u044b\u0435 \u0444\u0430\u043a\u0442\u044b \u043e\u0442 \u0433\u0438\u043f\u043e\u0442\u0435\u0437.";
|
||||
}
|
||||
if (context.questionType === "what_is_it_grounded_on") {
|
||||
if (hasRbpContextSignal(context)) {
|
||||
return "Сначала перечислите по РБП: объект, документ списания и остаток после списания на конец периода.";
|
||||
}
|
||||
if (hasFixedAssetAnchorContext(context)) {
|
||||
return "Сначала перечислите по ОС: объект, параметры амортизации и подтверждение начисления за период.";
|
||||
}
|
||||
if (context.focusDomain === "vat_document_register_book") {
|
||||
return "Сначала перечислите по НДС: документ, счет-фактуру, запись регистра и запись книги.";
|
||||
}
|
||||
return "\u0421\u043d\u0430\u0447\u0430\u043b\u0430 \u043f\u0435\u0440\u0435\u0447\u0438\u0441\u043b\u0438\u0442\u0435 \u043e\u043f\u043e\u0440\u043d\u044b\u0435 \u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442\u044b \u0438 \u0440\u0435\u0433\u0438\u0441\u0442\u0440\u044b, \u0437\u0430\u0442\u0435\u043c \u043f\u043e\u0434\u0442\u0432\u0435\u0440\u0436\u0434\u0430\u044e\u0449\u0438\u0435 \u043f\u0440\u043e\u0432\u043e\u0434\u043a\u0438.";
|
||||
}
|
||||
if (context.questionType === "which_chains_are_complete_vs_incomplete") {
|
||||
if (context.focusDomain === "vat_document_register_book") {
|
||||
return "Сначала разложите НДС-цепочку по шагам: документ -> счет-фактура -> регистр -> книга.";
|
||||
}
|
||||
if (hasRbpContextSignal(context)) {
|
||||
return "Сначала разложите РБП-цепочку на подтвержденное списание, частичное и неподтвержденное.";
|
||||
}
|
||||
if (hasFixedAssetAnchorContext(context)) {
|
||||
return "Сначала разложите ОС-цепочку на подтвержденное начисление, частичное и неподтвержденное.";
|
||||
}
|
||||
return "\u0421\u043d\u0430\u0447\u0430\u043b\u0430 \u0440\u0430\u0437\u043b\u043e\u0436\u0438\u0442\u0435 \u0446\u0435\u043f\u043e\u0447\u043a\u0438 \u043d\u0430 \u043f\u043e\u043b\u043d\u044b\u0435, \u0447\u0430\u0441\u0442\u0438\u0447\u043d\u043e \u043f\u043e\u043b\u043d\u044b\u0435 \u0438 \u043d\u0435\u043f\u043e\u0434\u0442\u0432\u0435\u0440\u0436\u0434\u0435\u043d\u043d\u044b\u0435.";
|
||||
}
|
||||
return null;
|
||||
|
|
@ -3539,15 +3932,152 @@ function applyQuestionTypeAndAnchorPolicy(input: {
|
|||
};
|
||||
}
|
||||
|
||||
type DomainWordingMode = "neutral" | "rbp" | "fa_amortization";
|
||||
|
||||
const RBP_WORDING_PATTERN =
|
||||
/(?:\bрбп\b|deferred[_\s-]?expense|сч(?:е|ё)т\s*97|объект\w*\s+рбп|списани[ея]\s+рбп|остат(?:ок|ки)\s+рбп|документ\s+списани[яе])/iu;
|
||||
const FA_WORDING_PATTERN =
|
||||
/(?:\bос\b|основн(?:ые|ых)?\s+средств|амортиз|сч(?:е|ё)т\s*0[12]|01\/02|карточк\w*\s+ос|объект\w*\s+ос|ввод\w*\s+в\s+эксплуатац|fixed\s*asset|depreciat)/iu;
|
||||
|
||||
function hasRbpWordingPhrase(value: string): boolean {
|
||||
return RBP_WORDING_PATTERN.test(String(value ?? ""));
|
||||
}
|
||||
|
||||
function hasFaWordingPhrase(value: string): boolean {
|
||||
return FA_WORDING_PATTERN.test(String(value ?? ""));
|
||||
}
|
||||
|
||||
function resolveDomainWordingMode(structure: AnswerStructureV11, context?: AnswerRenderContext): DomainWordingMode {
|
||||
if (!context) {
|
||||
return "neutral";
|
||||
}
|
||||
|
||||
const userMessage = String(context.userMessage ?? "");
|
||||
const explicitRbpFromMessage = hasRbpSignalInText(userMessage);
|
||||
const explicitFaFromMessage = hasFixedAssetAmortizationSignalInText(userMessage);
|
||||
|
||||
if (explicitRbpFromMessage && !explicitFaFromMessage) {
|
||||
return "rbp";
|
||||
}
|
||||
if (explicitFaFromMessage && !explicitRbpFromMessage) {
|
||||
return "fa_amortization";
|
||||
}
|
||||
|
||||
const anchorRbp = hasRbpAnchorContext(context);
|
||||
const anchorFa = hasFixedAssetAnchorContext(context);
|
||||
|
||||
if (anchorRbp && !anchorFa) {
|
||||
return "rbp";
|
||||
}
|
||||
if (anchorFa && !anchorRbp) {
|
||||
return "fa_amortization";
|
||||
}
|
||||
|
||||
const structureCorpus = [
|
||||
structure.direct_answer,
|
||||
...structure.mechanism_block.mechanism_notes,
|
||||
...structure.evidence_block.mechanism_notes,
|
||||
...(structure.evidence_block.source_refs ?? []),
|
||||
...(context.anchors.present ?? []),
|
||||
...(context.anchors.used ?? [])
|
||||
]
|
||||
.filter(Boolean)
|
||||
.join(" ");
|
||||
const structureRbp = hasRbpSignalInText(structureCorpus);
|
||||
const structureFa = hasFixedAssetAmortizationSignalInText(structureCorpus);
|
||||
|
||||
const rbpScore = [explicitRbpFromMessage, anchorRbp, structureRbp].filter(Boolean).length;
|
||||
const faScore = [explicitFaFromMessage, anchorFa, structureFa].filter(Boolean).length;
|
||||
if (rbpScore > faScore) {
|
||||
return "rbp";
|
||||
}
|
||||
if (faScore > rbpScore) {
|
||||
return "fa_amortization";
|
||||
}
|
||||
|
||||
return "neutral";
|
||||
}
|
||||
|
||||
function enforceDomainWordingIsolation(
|
||||
payload: {
|
||||
shortLine: string;
|
||||
brokenLines: string[];
|
||||
whyLines: string[];
|
||||
evidenceLines: string[];
|
||||
checkLines: string[];
|
||||
limitationLines: string[];
|
||||
},
|
||||
structure: AnswerStructureV11,
|
||||
context?: AnswerRenderContext
|
||||
): {
|
||||
shortLine: string;
|
||||
brokenLines: string[];
|
||||
whyLines: string[];
|
||||
evidenceLines: string[];
|
||||
checkLines: string[];
|
||||
limitationLines: string[];
|
||||
} {
|
||||
const mode = resolveDomainWordingMode(structure, context);
|
||||
if (mode === "neutral" || !context) {
|
||||
return payload;
|
||||
}
|
||||
|
||||
const effectiveQuestionType: QuestionTypeClass = context.questionType === "unknown" ? "what_to_check_first" : context.questionType;
|
||||
const isForbidden = mode === "rbp" ? hasFaWordingPhrase : hasRbpWordingPhrase;
|
||||
const filterLines = (lines: string[]): string[] => lines.filter((line) => !isForbidden(line));
|
||||
|
||||
const shortFallback =
|
||||
mode === "rbp"
|
||||
? "Признаки по РБП подтверждены частично и требуют проверки списания к концу периода."
|
||||
: "Риск неполного начисления амортизации по объектам ОС подтвержден частично.";
|
||||
const whyFallback =
|
||||
mode === "rbp"
|
||||
? ["По РБП часть списаний к концу периода подтверждена не полностью, поэтому остаток может сохраняться дольше ожидаемого."]
|
||||
: ["По ОС часть переходов к начислению амортизации подтверждена не полностью, поэтому есть риск пропуска отдельных объектов."];
|
||||
const evidenceFallback =
|
||||
mode === "rbp"
|
||||
? ["Основание собрано по РБП: объект списания, документ списания и остаток на конец периода."]
|
||||
: ["Основание собрано по ОС: карточка объекта, параметры амортизации, начисление и движения по 01/02."];
|
||||
const checkFallback =
|
||||
mode === "rbp"
|
||||
? buildRbpChecksByQuestionType(effectiveQuestionType).slice(0, 2)
|
||||
: buildFixedAssetChecksByQuestionType(effectiveQuestionType).slice(0, 2);
|
||||
|
||||
const filteredShort = isForbidden(payload.shortLine) ? shortFallback : payload.shortLine;
|
||||
const filteredBroken = dedupeNarrativeLines(filterLines(payload.brokenLines), 4);
|
||||
const filteredWhy = dedupeNarrativeLines(
|
||||
[...filterLines(payload.whyLines), ...(filterLines(payload.whyLines).length === 0 ? whyFallback : [])],
|
||||
4
|
||||
);
|
||||
const filteredEvidence = dedupeNarrativeLines(
|
||||
[...filterLines(payload.evidenceLines), ...(filterLines(payload.evidenceLines).length === 0 ? evidenceFallback : [])],
|
||||
7
|
||||
);
|
||||
const filteredChecks = dedupeNarrativeLines(
|
||||
[...filterLines(payload.checkLines), ...(filterLines(payload.checkLines).length === 0 ? checkFallback : [])],
|
||||
effectiveQuestionType === "what_to_check_first" ? 3 : 5
|
||||
);
|
||||
const filteredLimitations = dedupeNarrativeLines(filterLines(payload.limitationLines), 6);
|
||||
|
||||
return {
|
||||
shortLine: ensureSentence(filteredShort),
|
||||
brokenLines: filteredBroken.length > 0 ? filteredBroken : payload.brokenLines,
|
||||
whyLines: filteredWhy.length > 0 ? filteredWhy : whyFallback,
|
||||
evidenceLines: filteredEvidence.length > 0 ? filteredEvidence : evidenceFallback,
|
||||
checkLines: filteredChecks.length > 0 ? filteredChecks : checkFallback,
|
||||
limitationLines: filteredLimitations.length > 0 ? filteredLimitations : payload.limitationLines
|
||||
};
|
||||
}
|
||||
|
||||
function renderPolicyReply(structure: AnswerStructureV11, context?: AnswerRenderContext): string {
|
||||
const questionType = context?.questionType ?? "unknown";
|
||||
const shortLine = ensureSentence(buildShortSectionLine(structure));
|
||||
const brokenLines = buildBrokenSectionLines(structure);
|
||||
const whyLines = buildWhySectionLines(structure);
|
||||
const evidenceLines = buildEvidenceSectionLines(structure, questionType);
|
||||
const whyLines = buildWhySectionLines(structure, context);
|
||||
const evidenceLines = buildEvidenceSectionLines(structure, questionType, context);
|
||||
const checkLines = buildChecksSectionLines(structure, context);
|
||||
const limitationLines = buildLimitationsSectionLines(structure);
|
||||
const enriched = context
|
||||
const enrichedBase = context
|
||||
? applyQuestionTypeAndAnchorPolicy({
|
||||
shortLine,
|
||||
brokenLines,
|
||||
|
|
@ -3565,6 +4095,7 @@ function renderPolicyReply(structure: AnswerStructureV11, context?: AnswerRender
|
|||
checkLines,
|
||||
limitationLines
|
||||
};
|
||||
const enriched = enforceDomainWordingIsolation(enrichedBase, structure, context);
|
||||
|
||||
return sanitizeUserFacingReply(
|
||||
[
|
||||
|
|
@ -3684,7 +4215,10 @@ function composeAssistantAnswerV11(input: ComposeAnswerInput): ComposeAnswerOutp
|
|||
}
|
||||
: decision;
|
||||
|
||||
const missingAnchors = detectMissingAnchors(input.userMessage, input.retrievalResults);
|
||||
const missingAnchors = detectMissingAnchors(input.userMessage, input.retrievalResults, {
|
||||
normalizationPeriodExplicit: Boolean(input.normalizationPeriodExplicit),
|
||||
companyAnchors: input.companyAnchors ?? null
|
||||
});
|
||||
const hasProblemWeakSignal =
|
||||
policySignals.narrowing_strength !== "strong" ||
|
||||
policySignals.minimum_evidence_failed ||
|
||||
|
|
@ -3732,7 +4266,8 @@ function composeAssistantAnswerV11(input: ComposeAnswerInput): ComposeAnswerOutp
|
|||
assistant_reply: renderPolicyReply(problemCentricStructure, {
|
||||
questionType,
|
||||
focusDomain: focusNarrativeDomain,
|
||||
anchors: anchorUsage
|
||||
anchors: anchorUsage,
|
||||
userMessage: input.userMessage
|
||||
}),
|
||||
fallback_type: guardedDecision.fallback_type,
|
||||
reply_type: guardedDecision.reply_type,
|
||||
|
|
@ -3851,7 +4386,8 @@ function composeAssistantAnswerV11(input: ComposeAnswerInput): ComposeAnswerOutp
|
|||
assistant_reply: renderPolicyReply(answerStructure, {
|
||||
questionType,
|
||||
focusDomain: focusNarrativeDomain,
|
||||
anchors: anchorUsage
|
||||
anchors: anchorUsage,
|
||||
userMessage: input.userMessage
|
||||
}),
|
||||
fallback_type: guardedDecision.fallback_type,
|
||||
reply_type: guardedDecision.reply_type,
|
||||
|
|
@ -3908,6 +4444,10 @@ function composeExplainableAnswer(input: ComposeAnswerInput, scopeLabel: "full"
|
|||
);
|
||||
}
|
||||
|
||||
export function sanitizeAssistantReplyForUserFacing(value: string): string {
|
||||
return sanitizeUserFacingReply(value);
|
||||
}
|
||||
|
||||
export function composeAssistantAnswer(input: ComposeAnswerInput): ComposeAnswerOutput {
|
||||
if (input.enableAnswerPolicyV11) {
|
||||
return composeAssistantAnswerV11(input);
|
||||
|
|
|
|||
|
|
@ -1636,12 +1636,19 @@ function cardResolutionScore(card: P0DomainCard, fragmentText: string, profile:
|
|||
}
|
||||
|
||||
const hasVatSoftAnchor = card.id === "vat_document_register_book" && hasStrongVatDomainSignal(fragmentText, profile);
|
||||
const hasHardAnchor = accountMatches.length > 0 || markerHit || hasVatSoftAnchor;
|
||||
const hasMonthCloseSignal = card.id === "month_close_costs_20_44" && hasStrongMonthCloseSignal(fragmentText, profile);
|
||||
const fixedAssetOnlySignal =
|
||||
card.id === "month_close_costs_20_44" && hasFixedAssetSignal(fragmentText, profile) && !hasMonthCloseSignal && accountMatches.length === 0;
|
||||
if (fixedAssetOnlySignal) {
|
||||
return 0;
|
||||
}
|
||||
const markerWeight = card.id === "month_close_costs_20_44" ? hasMonthCloseSignal : markerHit;
|
||||
const hasHardAnchor = accountMatches.length > 0 || markerWeight || hasVatSoftAnchor;
|
||||
if (!hasHardAnchor) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return accountMatches.length * 4 + domainMatches.length * 3 + (markerHit ? 2 : 0);
|
||||
return accountMatches.length * 4 + domainMatches.length * 3 + (markerWeight ? 2 : 0);
|
||||
}
|
||||
|
||||
function hasStrongVatDomainSignal(fragmentText: string, profile: SemanticRetrievalProfile): boolean {
|
||||
|
|
@ -1660,6 +1667,30 @@ function hasStrongVatDomainSignal(fragmentText: string, profile: SemanticRetriev
|
|||
);
|
||||
}
|
||||
|
||||
function hasStrongMonthCloseSignal(fragmentText: string, profile: SemanticRetrievalProfile): boolean {
|
||||
const text = String(fragmentText ?? "");
|
||||
const hasMonthCloseLexicalAnchor =
|
||||
/(?:закрыти[ея]\s+месяц|закрыт[а-яё]*\s+период|закрытие\s+счетов|регламентн|косвенн|затрат|распределени|рбп|month\s*close|period\s*close|close\s+operation)/iu.test(
|
||||
text
|
||||
);
|
||||
return (
|
||||
hasMonthCloseLexicalAnchor ||
|
||||
profile.account_scope.some((account) => CLOSE_COST_ACCOUNTS.includes(account)) ||
|
||||
profile.domain_scope.some((domain) => domain === "period_close" || domain === "deferred_expense") ||
|
||||
profile.relation_patterns.some((pattern) =>
|
||||
["deferred_expense_to_writeoff", "close_operation", "allocation_rules_resolved", "residuals_zero_or_explained"].includes(pattern)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
function hasFixedAssetSignal(fragmentText: string, profile: SemanticRetrievalProfile): boolean {
|
||||
const text = String(fragmentText ?? "");
|
||||
return (
|
||||
/(?:основн(ые|ых|ым)?\s+средств|(?:^|[^a-zа-яё])ос(?:$|[^a-zа-яё])|амортиз|depreciat|fixed\s*asset)/iu.test(text) ||
|
||||
profile.account_scope.some((account) => account === "01" || account === "02")
|
||||
);
|
||||
}
|
||||
|
||||
function hasStrongSettlementAccountSignal(profile: SemanticRetrievalProfile): boolean {
|
||||
return profile.account_scope.some((account) => account === "51" || account === "60" || account === "62" || account === "76");
|
||||
}
|
||||
|
|
@ -1726,6 +1757,27 @@ function hasSettlementRecoverySignal(signals: RecordSemanticSignals): boolean {
|
|||
return hasSettlementAccount || hasSettlementDomain || hasSettlementRelation || hasSettlementDocument;
|
||||
}
|
||||
|
||||
function isVatAllowedAccountContext(account: string): boolean {
|
||||
const normalized = String(account ?? "").trim();
|
||||
return normalized === "19" || normalized === "68";
|
||||
}
|
||||
|
||||
function isVatAllowedDocumentContext(documentType: string): boolean {
|
||||
return /(?:invoice|vat_document|purchase_book|sales_book|tax_entry|supplier_receipt|sales_document|register)/i.test(
|
||||
String(documentType ?? "")
|
||||
);
|
||||
}
|
||||
|
||||
function isVatAllowedRelationPattern(pattern: string): boolean {
|
||||
return /(?:invoice_to_vat|register_to_book|book_entry_generated|deduction_posted|document_to_posting|contract_to_documents|source_doc_present|invoice_linked)/i.test(
|
||||
String(pattern ?? "")
|
||||
);
|
||||
}
|
||||
|
||||
function isVatAllowedGraphDomain(domain: string): boolean {
|
||||
return /(?:vat_flow)/i.test(String(domain ?? ""));
|
||||
}
|
||||
|
||||
function collectSourceRecords(data: DatasetBundle, sources: DatasetSourceName[]): SourceScopedRecord[] {
|
||||
const items: SourceScopedRecord[] = [];
|
||||
for (const source of sources) {
|
||||
|
|
@ -2995,6 +3047,9 @@ export class AssistantDataLayer {
|
|||
group.relations.set(relation, (group.relations.get(relation) ?? 0) + 1);
|
||||
}
|
||||
for (const account of evaluation.signals.account_context) {
|
||||
if (domainCard?.id === "vat_document_register_book" && !isVatAllowedAccountContext(account)) {
|
||||
continue;
|
||||
}
|
||||
if (semanticProfile.account_scope.length === 0 || semanticProfile.account_scope.includes(account)) {
|
||||
group.account_context.add(account);
|
||||
}
|
||||
|
|
@ -3006,6 +3061,9 @@ export class AssistantDataLayer {
|
|||
) {
|
||||
continue;
|
||||
}
|
||||
if (domainCard?.id === "vat_document_register_book" && !isVatAllowedDocumentContext(item)) {
|
||||
continue;
|
||||
}
|
||||
group.document_context.add(item);
|
||||
}
|
||||
for (const item of evaluation.signals.relation_patterns) {
|
||||
|
|
@ -3015,6 +3073,9 @@ export class AssistantDataLayer {
|
|||
) {
|
||||
continue;
|
||||
}
|
||||
if (domainCard?.id === "vat_document_register_book" && !isVatAllowedRelationPattern(item)) {
|
||||
continue;
|
||||
}
|
||||
group.relation_pattern_hits.add(item);
|
||||
}
|
||||
for (const item of evaluation.signals.anomaly_patterns) {
|
||||
|
|
@ -3035,6 +3096,9 @@ export class AssistantDataLayer {
|
|||
) {
|
||||
continue;
|
||||
}
|
||||
if (domainCard?.id === "vat_document_register_book" && !isVatAllowedGraphDomain(domain)) {
|
||||
continue;
|
||||
}
|
||||
group.graph_domain_scope.add(domain);
|
||||
}
|
||||
for (const reason of evaluation.match_reasons.slice(0, 4)) {
|
||||
|
|
@ -3050,22 +3114,30 @@ export class AssistantDataLayer {
|
|||
const sampleAccountContext =
|
||||
domainCard?.id === "settlements_60_62"
|
||||
? evaluation.signals.account_context.filter((item) => ["51", "60", "62", "76"].includes(item))
|
||||
: domainCard?.id === "vat_document_register_book"
|
||||
? evaluation.signals.account_context.filter((item) => isVatAllowedAccountContext(item))
|
||||
: evaluation.signals.account_context;
|
||||
const sampleDocumentContext =
|
||||
domainCard?.id === "settlements_60_62"
|
||||
? evaluation.signals.document_types.filter((item) =>
|
||||
["bank_statement", "payment_order", "settlement_document", "supplier_receipt", "sales_document", "manual_operation"].includes(item)
|
||||
)
|
||||
: domainCard?.id === "vat_document_register_book"
|
||||
? evaluation.signals.document_types.filter((item) => isVatAllowedDocumentContext(item))
|
||||
: evaluation.signals.document_types;
|
||||
const sampleRelationPatterns =
|
||||
domainCard?.id === "settlements_60_62"
|
||||
? evaluation.signals.relation_patterns.filter((item) =>
|
||||
["payment_to_settlement", "statement_to_document", "contract_to_documents", "document_to_posting"].includes(item)
|
||||
)
|
||||
: domainCard?.id === "vat_document_register_book"
|
||||
? evaluation.signals.relation_patterns.filter((item) => isVatAllowedRelationPattern(item))
|
||||
: evaluation.signals.relation_patterns;
|
||||
const sampleGraphDomainScope =
|
||||
domainCard?.id === "settlements_60_62"
|
||||
? evaluation.graph_domain_scope.filter((item) => ["bank_settlement", "customer_settlement"].includes(item))
|
||||
: domainCard?.id === "vat_document_register_book"
|
||||
? evaluation.graph_domain_scope.filter((item) => isVatAllowedGraphDomain(item))
|
||||
: evaluation.graph_domain_scope;
|
||||
group.samples.push({
|
||||
source_entity: record.source_entity,
|
||||
|
|
|
|||
|
|
@ -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 {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import type {
|
||||
import type {
|
||||
AssistantRequirement,
|
||||
RequirementCoverageReport,
|
||||
UnifiedRetrievalResult
|
||||
|
|
@ -146,6 +146,11 @@ function isVatAccount(value: string): boolean {
|
|||
return prefix === "19" || prefix === "68";
|
||||
}
|
||||
|
||||
function isFixedAssetAccount(value: string): boolean {
|
||||
const prefix = normalizeAccountPrefix(value);
|
||||
return prefix === "01" || prefix === "02" || prefix === "08";
|
||||
}
|
||||
|
||||
function isCloseCostsAccount(value: string): boolean {
|
||||
const prefix = normalizeAccountPrefix(value);
|
||||
if (!prefix) {
|
||||
|
|
@ -161,11 +166,26 @@ function inferFollowupActiveDomain(input: {
|
|||
routeSummary: RouteHintSummary | null;
|
||||
previous: InvestigationStateWithProblemUnits;
|
||||
}): string | null {
|
||||
const corpus = `${input.userMessage} ${input.previous.focus.active_query_subject ?? ""}`.toLowerCase();
|
||||
const messageCorpus = String(input.userMessage ?? "").toLowerCase();
|
||||
const contextualCorpus = `${messageCorpus} ${input.previous.focus.active_query_subject ?? ""}`.toLowerCase();
|
||||
|
||||
const hasFixedAssetLexicalSignal =
|
||||
/(?:амортиз|основн(ые|ых|ым)?\s+средств|(?:^|[^a-zа-яё])ос(?:$|[^a-zа-яё])|объект[а-яё]*\s+ос|fixed\s*asset|depreciat)/i.test(
|
||||
messageCorpus
|
||||
);
|
||||
const hasFixedAssetAccountSignal =
|
||||
input.focusAccounts.some((item) => isFixedAssetAccount(item)) &&
|
||||
/(?:сч[её]т(?:а|у|ом|е)?\s*(?:01|02|08)|(?:01|02|08)(?:\.\d{2})?\s*\/\s*(?:01|02|08)(?:\.\d{2})?|\b0[128](?:\.\d{2})?\b)/i.test(
|
||||
messageCorpus
|
||||
);
|
||||
if (hasFixedAssetLexicalSignal || hasFixedAssetAccountSignal) {
|
||||
return "fixed_asset_amortization";
|
||||
}
|
||||
|
||||
const hasSettlementSignal =
|
||||
input.focusAccounts.some((item) => isSettlementAccount(item)) ||
|
||||
/(60(?:\.\d{2})?|62(?:\.\d{2})?|оплат|расчет|расч[её]т|зачет|зач[её]т|аванс|долг|поставщ|покупат|settlement|payment|supplier|customer)/i.test(
|
||||
corpus
|
||||
/(?:60(?:\.\d{2})?|62(?:\.\d{2})?|оплат|расч[её]т|зач[её]т|аванс|долг|поставщ|покупат|settlement|payment|supplier|customer)/i.test(
|
||||
messageCorpus
|
||||
);
|
||||
if (hasSettlementSignal) {
|
||||
return "settlements_60_62";
|
||||
|
|
@ -173,18 +193,26 @@ function inferFollowupActiveDomain(input: {
|
|||
|
||||
const hasVatSignal =
|
||||
input.focusAccounts.some((item) => isVatAccount(item)) ||
|
||||
/(ндс|счет[\s-]?фактур|сч[её]т[\s-]?фактур|книг[аи]|vat|invoice|book|register)/i.test(corpus);
|
||||
/(?:ндс|сч[её]т[\s-]?фактур|книг[аи]|vat|invoice|book|register)/i.test(messageCorpus);
|
||||
if (hasVatSignal) {
|
||||
return "vat_document_register_book";
|
||||
}
|
||||
|
||||
const hasCloseSignal =
|
||||
input.focusAccounts.some((item) => isCloseCostsAccount(item)) ||
|
||||
/(закрыти|закрытие|месяц|затрат|распредел|списан|period\s*close|month\s*close|allocation|residual|cost)/i.test(corpus);
|
||||
/(?:закрыти|месяц|затрат|распредел|списан|period\s*close|month\s*close|allocation|residual|cost)/i.test(messageCorpus);
|
||||
if (hasCloseSignal) {
|
||||
return "month_close_costs_20_44";
|
||||
}
|
||||
|
||||
if (
|
||||
/(?:60(?:\.\d{2})?|62(?:\.\d{2})?|оплат|расч[её]т|аванс|долг|settlement|payment)/i.test(contextualCorpus) &&
|
||||
(input.previous.followup_context?.active_domain === "settlements_60_62" ||
|
||||
input.previous.focus.domain === "settlements_60_62")
|
||||
) {
|
||||
return "settlements_60_62";
|
||||
}
|
||||
|
||||
const routeDomain = deriveDomain(input.routeSummary);
|
||||
if (routeDomain && routeDomain !== "no_route") {
|
||||
return routeDomain;
|
||||
|
|
@ -450,7 +478,7 @@ export function updateInvestigationState(input: UpdateInvestigationStateInput):
|
|||
const uncoveredRequirementIds = collectUncoveredRequirementIds(input.coverageReport);
|
||||
const activeDomain = inferFollowupActiveDomain({
|
||||
userMessage: input.userMessage,
|
||||
focusAccounts: mergedFocusAccounts,
|
||||
focusAccounts: focusFromMessage,
|
||||
routeSummary: input.routeSummary,
|
||||
previous
|
||||
});
|
||||
|
|
@ -498,3 +526,4 @@ export function updateInvestigationState(input: UpdateInvestigationStateInput):
|
|||
: {})
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -147,7 +147,7 @@ describe("assistant mode API", () => {
|
|||
});
|
||||
|
||||
expect(response.status).toBe(200);
|
||||
expect(["partial_coverage", "route_mismatch_blocked", "factual_with_explanation"]).toContain(
|
||||
expect(["partial_coverage", "clarification_required", "route_mismatch_blocked", "factual_with_explanation"]).toContain(
|
||||
String(response.body.reply_type)
|
||||
);
|
||||
expect(["partial", "grounded", "route_mismatch_blocked"]).toContain(
|
||||
|
|
|
|||
|
|
@ -194,6 +194,43 @@ describe.sequential("assistant follow-up state binding", () => {
|
|||
expect(second.body.debug?.investigation_state_snapshot?.turn_index).toBe(2);
|
||||
});
|
||||
|
||||
it("rebinds follow-up domain away from settlements on fixed-asset amortization query", async () => {
|
||||
const app = await createAppWithFlags({
|
||||
state: "1",
|
||||
binding: "1",
|
||||
problemUnits: "1",
|
||||
continuity: "1",
|
||||
answerPolicy: "1",
|
||||
problemCentric: "1"
|
||||
});
|
||||
const sessionId = `asst-wave16-fa-domain-${Date.now()}`;
|
||||
|
||||
const first = await request(app).post("/api/assistant/message").send({
|
||||
session_id: sessionId,
|
||||
useMock: true,
|
||||
promptVersion: "normalizer_v2_0_2",
|
||||
user_message: "Почему деньги ушли, а долг по 60.01/62.02 остался?"
|
||||
});
|
||||
expect(first.status).toBe(200);
|
||||
expect(first.body.debug?.investigation_state_snapshot?.followup_context?.active_domain).toBe("settlements_60_62");
|
||||
|
||||
const second = await request(app).post("/api/assistant/message").send({
|
||||
session_id: sessionId,
|
||||
useMock: true,
|
||||
promptVersion: "normalizer_v2_0_2",
|
||||
user_message:
|
||||
"Полно ли начислена амортизация по объектам ОС за июль? Проверь по 01/02, нет ли пропущенных объектов."
|
||||
});
|
||||
|
||||
expect(second.status).toBe(200);
|
||||
const activeDomain = String(second.body.debug?.investigation_state_snapshot?.followup_context?.active_domain ?? "");
|
||||
expect(activeDomain).not.toBe("settlements_60_62");
|
||||
expect(activeDomain).toMatch(/fixed_asset_amortization|month_close_costs_20_44|no_route|hybrid_store_plus_live|fixed_asset/i);
|
||||
|
||||
const settlementActions = second.body.debug?.investigation_state_snapshot?.followup_context?.settlement_next_actions;
|
||||
expect(Array.isArray(settlementActions) ? settlementActions.length : 0).toBe(0);
|
||||
});
|
||||
|
||||
it("keeps UTF-8 follow-up period refinement in-scope with soft continuity hints", async () => {
|
||||
const app = await createAppWithFlags({
|
||||
state: "1",
|
||||
|
|
|
|||
|
|
@ -0,0 +1,546 @@
|
|||
import { describe, expect, it } from "vitest";
|
||||
import { composeAssistantAnswer } from "../src/services/answerComposer";
|
||||
import { resolveCompanyAnchors } from "../src/services/companyAnchorResolver";
|
||||
import type { AnswerGroundingCheck, RequirementCoverageReport, UnifiedRetrievalResult } from "../src/types/assistant";
|
||||
import type { ProblemUnit } from "../src/types/stage2ProblemUnits";
|
||||
|
||||
function buildRouteSummary() {
|
||||
return {
|
||||
mode: "deterministic_v2" as const,
|
||||
message_in_scope: true,
|
||||
scope_confidence: "high" as const,
|
||||
planner: {
|
||||
total_fragments: 1,
|
||||
in_scope_fragments: 1,
|
||||
out_of_scope_fragments: 0,
|
||||
discarded_fragments: 0,
|
||||
contains_multiple_tasks: false
|
||||
},
|
||||
decisions: [],
|
||||
fallback: {
|
||||
type: "none" as const,
|
||||
message: null
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function buildCoverage(input?: Partial<RequirementCoverageReport>): RequirementCoverageReport {
|
||||
return {
|
||||
requirements_total: 1,
|
||||
requirements_covered: 1,
|
||||
requirements_uncovered: [],
|
||||
requirements_partially_covered: [],
|
||||
clarification_needed_for: [],
|
||||
out_of_scope_requirements: [],
|
||||
...input
|
||||
};
|
||||
}
|
||||
|
||||
function buildGrounding(input?: Partial<AnswerGroundingCheck>): AnswerGroundingCheck {
|
||||
return {
|
||||
status: "partial",
|
||||
route_subject_match: true,
|
||||
missing_requirements: [],
|
||||
reasons: [],
|
||||
why_included_summary: ["wave16-live"],
|
||||
selection_reason_summary: ["wave16-live"],
|
||||
...input
|
||||
};
|
||||
}
|
||||
|
||||
function buildProblemUnit(input?: Partial<ProblemUnit>): ProblemUnit {
|
||||
return {
|
||||
schema_version: "problem_unit_v0_1",
|
||||
problem_unit_id: input?.problem_unit_id ?? "pu-live-1",
|
||||
problem_unit_type: input?.problem_unit_type ?? "cross_branch_inconsistency_cluster",
|
||||
title: input?.title ?? "Live corrective test unit",
|
||||
mechanism_summary: input?.mechanism_summary ?? "Mechanism candidate: invoice_to_vat.",
|
||||
business_defect_class: input?.business_defect_class ?? "invoice_to_vat",
|
||||
severity: input?.severity ?? {
|
||||
score: 0.61,
|
||||
grade: "medium"
|
||||
},
|
||||
confidence: input?.confidence ?? {
|
||||
score: 0.58,
|
||||
grade: "medium"
|
||||
},
|
||||
lifecycle_domain: input?.lifecycle_domain ?? "vat_flow",
|
||||
affected_entities: input?.affected_entities ?? ["Document:DOC-1"],
|
||||
affected_documents: input?.affected_documents ?? ["Document:DOC-1"],
|
||||
affected_postings: input?.affected_postings ?? ["Posting:POST-1"],
|
||||
affected_accounts: input?.affected_accounts ?? ["19"],
|
||||
affected_counterparties: input?.affected_counterparties ?? ["Counterparty:CP-1"],
|
||||
affected_contracts: input?.affected_contracts ?? ["Contract:CTR-1"],
|
||||
failed_expected_edge: input?.failed_expected_edge ?? "invoice_to_vat",
|
||||
period_impact: input?.period_impact ?? {
|
||||
is_period_sensitive: true,
|
||||
impact_class: "close_risk"
|
||||
},
|
||||
evidence_pack: input?.evidence_pack ?? ["ev-1"],
|
||||
entity_backlinks: input?.entity_backlinks ?? [{ entity: "Document", id: "DOC-1" }],
|
||||
snapshot_limitations: input?.snapshot_limitations ?? []
|
||||
};
|
||||
}
|
||||
|
||||
function buildRetrieval(input?: Partial<UnifiedRetrievalResult>): UnifiedRetrievalResult {
|
||||
return {
|
||||
fragment_id: "F1",
|
||||
requirement_ids: ["R1"],
|
||||
route: "hybrid_store_plus_live",
|
||||
status: "ok",
|
||||
result_type: "chain",
|
||||
items: [
|
||||
{
|
||||
source_entity: "Document",
|
||||
source_id: "DOC-1",
|
||||
display_name: "Документ",
|
||||
account_context: ["19"],
|
||||
document_context: ["invoice", "vat_document"],
|
||||
relation_pattern_hits: ["invoice_to_vat", "document_to_posting"],
|
||||
graph_domain_scope: ["vat_flow"],
|
||||
period: "2020-07"
|
||||
}
|
||||
],
|
||||
summary: {
|
||||
semantic_profile: {
|
||||
account_scope: ["19"],
|
||||
domain_scope: ["vat", "taxes"],
|
||||
relation_patterns: ["invoice_to_vat", "document_to_posting"],
|
||||
period_scope: {
|
||||
from: "2020-07-01",
|
||||
to: "2020-07-31",
|
||||
granularity: "month"
|
||||
}
|
||||
},
|
||||
domain_purity_guard: {
|
||||
domain_card_id: "vat_document_register_book"
|
||||
},
|
||||
broad_query_detected: false,
|
||||
broad_result_flag: false,
|
||||
minimum_evidence_failed: false,
|
||||
narrowing_strength: "strong"
|
||||
},
|
||||
evidence: [
|
||||
{
|
||||
evidence_id: "ev-1",
|
||||
claim_ref: "requirement:R1",
|
||||
source_type: "retrieval_item",
|
||||
source_ref: {
|
||||
schema_version: "evidence_source_ref_v1",
|
||||
namespace: "snapshot_2020_07",
|
||||
entity: "document",
|
||||
id: "DOC-1",
|
||||
period: "2020-07",
|
||||
canonical_ref: "evidence_source_ref_v1|snapshot_2020_07|document|doc-1|2020-07"
|
||||
},
|
||||
pointer: {
|
||||
fragment_id: "F1",
|
||||
route: "hybrid_store_plus_live",
|
||||
source: {
|
||||
namespace: "snapshot_2020_07",
|
||||
entity: "document",
|
||||
id: "DOC-1",
|
||||
period: "2020-07"
|
||||
},
|
||||
locator: {
|
||||
field_path: "risk_score",
|
||||
item_index: 0
|
||||
}
|
||||
},
|
||||
evidence_kind: "mechanism_link",
|
||||
mechanism_note: "invoice_to_vat",
|
||||
confidence: "medium",
|
||||
limitation: null,
|
||||
payload: {
|
||||
value: 1
|
||||
}
|
||||
}
|
||||
],
|
||||
candidate_evidence: [],
|
||||
problem_units: [buildProblemUnit()],
|
||||
problem_unit_summary: {
|
||||
schema_version: "problem_unit_summary_v0_1",
|
||||
units_total: 1,
|
||||
duplicate_collapses: 0,
|
||||
unit_types: ["cross_branch_inconsistency_cluster"],
|
||||
type_distribution: {
|
||||
cross_branch_inconsistency_cluster: 1
|
||||
},
|
||||
severity_distribution: {
|
||||
low: 0,
|
||||
medium: 1,
|
||||
high: 0
|
||||
},
|
||||
confidence_distribution: {
|
||||
low: 0,
|
||||
medium: 1,
|
||||
high: 0
|
||||
},
|
||||
primary_unit_type: "cross_branch_inconsistency_cluster"
|
||||
},
|
||||
why_included: ["wave16-live"],
|
||||
selection_reason: ["wave16-live"],
|
||||
risk_factors: ["cross_branch_inconsistency"],
|
||||
business_interpretation: ["wave16-live"],
|
||||
confidence: "medium",
|
||||
limitations: [],
|
||||
errors: [],
|
||||
...input
|
||||
};
|
||||
}
|
||||
|
||||
describe("wave16 live corrective pass regressions", () => {
|
||||
it("removes leaked debug payload scaffolding from user-facing reply", () => {
|
||||
const output = composeAssistantAnswer({
|
||||
userMessage: "Проверь НДС цепочку в июле.",
|
||||
routeSummary: buildRouteSummary(),
|
||||
retrievalResults: [
|
||||
buildRetrieval({
|
||||
selection_reason: [
|
||||
"### debug_payload_json\n```json\n{\"trace_id\":\"abc\",\"route_summary\":{\"mode\":\"x\"}}\n```"
|
||||
]
|
||||
})
|
||||
],
|
||||
requirements: [
|
||||
{
|
||||
requirement_id: "R1",
|
||||
source_fragment_id: "F1",
|
||||
requirement_text: "Проверка НДС",
|
||||
subject_tokens: [],
|
||||
status: "covered",
|
||||
route: "hybrid_store_plus_live"
|
||||
}
|
||||
],
|
||||
coverageReport: buildCoverage(),
|
||||
groundingCheck: buildGrounding({ status: "grounded" }),
|
||||
focusDomainHint: "vat_document_register_book",
|
||||
questionTypeHint: "why_breaks",
|
||||
enableAnswerPolicyV11: true,
|
||||
enableProblemCentricAnswerV1: true,
|
||||
enableLifecycleAnswerV1: true
|
||||
});
|
||||
|
||||
expect(output.assistant_reply).not.toMatch(/debug_payload_json|technical_breakdown_json|trace_id|route_summary/i);
|
||||
});
|
||||
|
||||
it("does not claim missing period when normalization already extracted explicit period", () => {
|
||||
const output = composeAssistantAnswer({
|
||||
userMessage: "Рошибка кодировки", // emulates noisy text from live channel
|
||||
routeSummary: buildRouteSummary(),
|
||||
retrievalResults: [
|
||||
buildRetrieval({
|
||||
summary: {
|
||||
semantic_profile: {
|
||||
account_scope: ["19"],
|
||||
domain_scope: ["vat", "taxes"],
|
||||
relation_patterns: ["invoice_to_vat"]
|
||||
},
|
||||
domain_purity_guard: {
|
||||
domain_card_id: "vat_document_register_book"
|
||||
},
|
||||
broad_query_detected: true,
|
||||
broad_result_flag: false,
|
||||
minimum_evidence_failed: false,
|
||||
narrowing_strength: "weak"
|
||||
}
|
||||
})
|
||||
],
|
||||
requirements: [
|
||||
{
|
||||
requirement_id: "R1",
|
||||
source_fragment_id: "F1",
|
||||
requirement_text: "Проверка периода",
|
||||
subject_tokens: [],
|
||||
status: "covered",
|
||||
route: "hybrid_store_plus_live"
|
||||
}
|
||||
],
|
||||
coverageReport: buildCoverage({
|
||||
requirements_covered: 0,
|
||||
requirements_partially_covered: ["R1"],
|
||||
clarification_needed_for: ["R1"]
|
||||
}),
|
||||
groundingCheck: buildGrounding({
|
||||
status: "partial",
|
||||
reasons: ["Mechanism is unresolved for part of the evidence."]
|
||||
}),
|
||||
focusDomainHint: "vat_document_register_book",
|
||||
questionTypeHint: "which_chains_are_complete_vs_incomplete",
|
||||
normalizationPeriodExplicit: true,
|
||||
enableAnswerPolicyV11: true,
|
||||
enableProblemCentricAnswerV1: true,
|
||||
enableLifecycleAnswerV1: true
|
||||
});
|
||||
|
||||
expect(output.assistant_reply).not.toMatch(/период в запросе не указан/i);
|
||||
expect(output.assistant_reply).not.toMatch(/уточните период проверки/i);
|
||||
});
|
||||
|
||||
it("blocks VAT primary synthesis when top evidence is cross-domain polluted", () => {
|
||||
const polluted = buildRetrieval({
|
||||
items: [
|
||||
{
|
||||
source_entity: "Document",
|
||||
source_id: "DOC-1",
|
||||
display_name: "Документ",
|
||||
account_context: ["25", "20", "19"],
|
||||
document_context: ["invoice", "vat_document", "deferred_expense_document"],
|
||||
relation_pattern_hits: ["invoice_to_vat", "deferred_expense_to_writeoff"],
|
||||
graph_domain_scope: ["vat_flow", "deferred_expense", "period_close", "bank_settlement", "fixed_asset"],
|
||||
period: "2020-07"
|
||||
}
|
||||
],
|
||||
summary: {
|
||||
semantic_profile: {
|
||||
account_scope: ["19"],
|
||||
domain_scope: ["vat", "taxes"],
|
||||
relation_patterns: ["invoice_to_vat", "deferred_expense_to_writeoff"],
|
||||
period_scope: {
|
||||
from: "2020-07-01",
|
||||
to: "2020-07-31",
|
||||
granularity: "month"
|
||||
}
|
||||
},
|
||||
domain_purity_guard: {
|
||||
domain_card_id: "vat_document_register_book"
|
||||
},
|
||||
broad_query_detected: false,
|
||||
broad_result_flag: false,
|
||||
minimum_evidence_failed: false,
|
||||
narrowing_strength: "strong"
|
||||
}
|
||||
});
|
||||
|
||||
const output = composeAssistantAnswer({
|
||||
userMessage: "Проверь НДС-цепочку: документ -> счет-фактура -> регистр -> книга.",
|
||||
routeSummary: buildRouteSummary(),
|
||||
retrievalResults: [polluted],
|
||||
requirements: [
|
||||
{
|
||||
requirement_id: "R1",
|
||||
source_fragment_id: "F1",
|
||||
requirement_text: "Проверка НДС",
|
||||
subject_tokens: [],
|
||||
status: "covered",
|
||||
route: "hybrid_store_plus_live"
|
||||
}
|
||||
],
|
||||
coverageReport: buildCoverage(),
|
||||
groundingCheck: buildGrounding({ status: "grounded" }),
|
||||
focusDomainHint: "vat_document_register_book",
|
||||
questionTypeHint: "why_breaks",
|
||||
enableAnswerPolicyV11: true,
|
||||
enableProblemCentricAnswerV1: true,
|
||||
enableLifecycleAnswerV1: true
|
||||
});
|
||||
|
||||
expect(output.reply_type).toBe("clarification_required");
|
||||
expect(output.answer_structure_v11?.uncertainty_block.open_uncertainties).toContain("primary_domain_evidence_not_confirmed");
|
||||
});
|
||||
|
||||
it("uses VAT-specific partial-coverage wording instead of generic chain template", () => {
|
||||
const output = composeAssistantAnswer({
|
||||
userMessage:
|
||||
"13 июля поступление, 15 июля реализация. НДС-цепочка по этим движениям полная или есть выпадение?",
|
||||
routeSummary: buildRouteSummary(),
|
||||
retrievalResults: [
|
||||
buildRetrieval({
|
||||
summary: {
|
||||
semantic_profile: {
|
||||
account_scope: ["19", "68"],
|
||||
domain_scope: ["vat", "taxes"],
|
||||
relation_patterns: ["invoice_to_vat", "document_to_posting"],
|
||||
period_scope: {
|
||||
from: "2020-07-01",
|
||||
to: "2020-07-31",
|
||||
granularity: "month"
|
||||
}
|
||||
},
|
||||
domain_purity_guard: {
|
||||
domain_card_id: "vat_document_register_book"
|
||||
},
|
||||
broad_query_detected: false,
|
||||
broad_result_flag: false,
|
||||
minimum_evidence_failed: false,
|
||||
narrowing_strength: "strong"
|
||||
}
|
||||
})
|
||||
],
|
||||
requirements: [
|
||||
{
|
||||
requirement_id: "R1",
|
||||
source_fragment_id: "F1",
|
||||
requirement_text: "Проверка полноты НДС-цепочки",
|
||||
subject_tokens: [],
|
||||
status: "covered",
|
||||
route: "hybrid_store_plus_live"
|
||||
}
|
||||
],
|
||||
coverageReport: buildCoverage({ requirements_covered: 0, requirements_partially_covered: ["R1"] }),
|
||||
groundingCheck: buildGrounding({ status: "partial" }),
|
||||
focusDomainHint: "vat_document_register_book",
|
||||
questionTypeHint: "which_chains_are_complete_vs_incomplete",
|
||||
normalizationPeriodExplicit: true,
|
||||
enableAnswerPolicyV11: true,
|
||||
enableProblemCentricAnswerV1: true,
|
||||
enableLifecycleAnswerV1: true
|
||||
});
|
||||
|
||||
expect(output.assistant_reply).toMatch(/НДС-цепочк|НДС-звеньям|документ -> счет-фактура -> регистр -> книга/i);
|
||||
expect(output.assistant_reply).not.toMatch(/ключевой переход закрытия/i);
|
||||
});
|
||||
|
||||
it("renders RBP answer in RBP language with RBP-first checks", () => {
|
||||
const output = composeAssistantAnswer({
|
||||
userMessage:
|
||||
"31 июля прошло Списание РБП за июль. Есть ли признаки, что часть РБП к концу июля живет дольше ожидаемого?",
|
||||
routeSummary: buildRouteSummary(),
|
||||
retrievalResults: [
|
||||
buildRetrieval({
|
||||
items: [
|
||||
{
|
||||
source_entity: "Document",
|
||||
source_id: "DOC-RBP-1",
|
||||
display_name: "Списание РБП",
|
||||
account_context: ["97"],
|
||||
document_context: ["deferred_expense_document"],
|
||||
relation_pattern_hits: ["deferred_expense_to_writeoff", "document_to_posting", "asset_card_to_depreciation"],
|
||||
graph_domain_scope: ["deferred_expense", "period_close", "fixed_asset"],
|
||||
period: "2020-07"
|
||||
}
|
||||
],
|
||||
summary: {
|
||||
semantic_profile: {
|
||||
account_scope: ["97", "01"],
|
||||
domain_scope: ["deferred_expense", "period_close"],
|
||||
relation_patterns: ["deferred_expense_to_writeoff", "document_to_posting", "asset_card_to_depreciation"],
|
||||
period_scope: {
|
||||
from: "2020-07-01",
|
||||
to: "2020-07-31",
|
||||
granularity: "month"
|
||||
}
|
||||
},
|
||||
domain_purity_guard: {
|
||||
domain_card_id: "month_close_costs_20_44"
|
||||
},
|
||||
broad_query_detected: false,
|
||||
broad_result_flag: false,
|
||||
minimum_evidence_failed: false,
|
||||
narrowing_strength: "strong"
|
||||
}
|
||||
})
|
||||
],
|
||||
requirements: [
|
||||
{
|
||||
requirement_id: "R1",
|
||||
source_fragment_id: "F1",
|
||||
requirement_text: "Проверка списания РБП",
|
||||
subject_tokens: [],
|
||||
status: "covered",
|
||||
route: "hybrid_store_plus_live"
|
||||
}
|
||||
],
|
||||
coverageReport: buildCoverage({ requirements_covered: 0, requirements_partially_covered: ["R1"] }),
|
||||
groundingCheck: buildGrounding({ status: "partial" }),
|
||||
questionTypeHint: "what_is_it_grounded_on",
|
||||
companyAnchors: resolveCompanyAnchors(
|
||||
"31 июля прошло Списание РБП за июль. Есть ли признаки, что часть РБП к концу июля живет дольше ожидаемого?"
|
||||
),
|
||||
normalizationPeriodExplicit: true,
|
||||
enableAnswerPolicyV11: true,
|
||||
enableProblemCentricAnswerV1: true,
|
||||
enableLifecycleAnswerV1: true
|
||||
});
|
||||
|
||||
expect(output.assistant_reply).toMatch(/РБП|списани[ея]\s+РБП|счет\s*97/i);
|
||||
expect(output.assistant_reply).toMatch(/документ списания|остаток/i);
|
||||
expect(output.assistant_reply).not.toMatch(/амортиз|объект\w*\s+ОС|01\/02|сч[её]т\s*0[12]/i);
|
||||
expect(output.assistant_reply).not.toMatch(/отдельн\w*\s+проверк\w*\s+расчетн\w*\s+связк/i);
|
||||
});
|
||||
|
||||
it("does not collapse fixed-asset amortization question into month-close primary narrative", () => {
|
||||
const unit = buildProblemUnit({
|
||||
problem_unit_id: "pu-fa-1",
|
||||
problem_unit_type: "lifecycle_anomaly_node",
|
||||
lifecycle_domain: "fixed_asset",
|
||||
affected_accounts: ["01", "02"],
|
||||
mechanism_summary: "Mechanism candidate: asset_card_to_depreciation.",
|
||||
business_defect_class: "asset_card_to_depreciation",
|
||||
failed_expected_edge: "asset_card_to_depreciation"
|
||||
});
|
||||
|
||||
const output = composeAssistantAnswer({
|
||||
userMessage: "Полно ли начислена амортизация по всем объектам ОС за июль?",
|
||||
routeSummary: buildRouteSummary(),
|
||||
retrievalResults: [
|
||||
buildRetrieval({
|
||||
problem_units: [unit],
|
||||
problem_unit_summary: {
|
||||
schema_version: "problem_unit_summary_v0_1",
|
||||
units_total: 1,
|
||||
duplicate_collapses: 0,
|
||||
unit_types: ["lifecycle_anomaly_node"],
|
||||
type_distribution: {
|
||||
lifecycle_anomaly_node: 1
|
||||
},
|
||||
severity_distribution: {
|
||||
low: 0,
|
||||
medium: 1,
|
||||
high: 0
|
||||
},
|
||||
confidence_distribution: {
|
||||
low: 0,
|
||||
medium: 1,
|
||||
high: 0
|
||||
},
|
||||
primary_unit_type: "lifecycle_anomaly_node"
|
||||
},
|
||||
summary: {
|
||||
semantic_profile: {
|
||||
account_scope: ["01", "02"],
|
||||
domain_scope: ["fixed_assets"],
|
||||
relation_patterns: ["asset_card_to_depreciation", "deferred_expense_to_writeoff"],
|
||||
period_scope: {
|
||||
from: "2020-07-01",
|
||||
to: "2020-07-31",
|
||||
granularity: "month"
|
||||
}
|
||||
},
|
||||
domain_purity_guard: {
|
||||
domain_card_id: "month_close_costs_20_44"
|
||||
},
|
||||
broad_query_detected: false,
|
||||
broad_result_flag: false,
|
||||
minimum_evidence_failed: false,
|
||||
narrowing_strength: "strong"
|
||||
}
|
||||
})
|
||||
],
|
||||
requirements: [
|
||||
{
|
||||
requirement_id: "R1",
|
||||
source_fragment_id: "F1",
|
||||
requirement_text: "Проверка амортизации",
|
||||
subject_tokens: [],
|
||||
status: "covered",
|
||||
route: "hybrid_store_plus_live"
|
||||
}
|
||||
],
|
||||
coverageReport: buildCoverage({ requirements_covered: 0, requirements_partially_covered: ["R1"] }),
|
||||
groundingCheck: buildGrounding({ status: "partial" }),
|
||||
questionTypeHint: "why_breaks",
|
||||
companyAnchors: resolveCompanyAnchors("Полно ли начислена амортизация по всем объектам ОС за июль?"),
|
||||
normalizationPeriodExplicit: true,
|
||||
enableAnswerPolicyV11: true,
|
||||
enableProblemCentricAnswerV1: true,
|
||||
enableLifecycleAnswerV1: true
|
||||
});
|
||||
|
||||
expect(output.assistant_reply).not.toMatch(/цепочка распределения затрат и закрытия месяца/i);
|
||||
expect(output.assistant_reply).toMatch(/карточк[аеи] ОС|амортизац/i);
|
||||
expect(output.assistant_reply).not.toMatch(/contradictory_asset_state|invalid_document_or_posting_transition|\bdisposed\b/i);
|
||||
expect(output.assistant_reply).not.toMatch(/Проверьте связку документов и проводок по проблемному участку/i);
|
||||
expect(output.assistant_reply).toMatch(/объект\w*\s+ОС|параметр\w*\s+амортиз|01\/02|счет\w*\s*0[12]/i);
|
||||
expect(output.assistant_reply).not.toMatch(/РБП|сч[её]т\s*97|документ\s+списани[яе]|остат(ок|ки)\s+РБП|списани[ея]\s+РБП/i);
|
||||
});
|
||||
});
|
||||
|
|
@ -0,0 +1,111 @@
|
|||
{
|
||||
"run_id": "eval-0E46_gT0ds",
|
||||
"timestamp": "2026-03-28T14:52:43.266Z",
|
||||
"mode": "single-pass-strict",
|
||||
"use_mock": true,
|
||||
"prompt_version": "normalizer_v2_0_2",
|
||||
"schema_version": "v2_0_2",
|
||||
"dataset": {
|
||||
"source": "inline_raw_questions",
|
||||
"file": null,
|
||||
"raw_questions_count": 2
|
||||
},
|
||||
"cases_total": 2,
|
||||
"metrics": {
|
||||
"schema_validation_pass_rate": 100,
|
||||
"scope_detection_accuracy": null,
|
||||
"scope_in_scope_rate": 100,
|
||||
"multi_intent_detected_rate": 0,
|
||||
"clarification_required_rate": 0,
|
||||
"avg_fragments_per_message": 1,
|
||||
"out_of_scope_fragment_rate": 0,
|
||||
"routed_fragment_rate": 100,
|
||||
"no_route_fragment_rate": 0,
|
||||
"route_resolution_accuracy": null,
|
||||
"no_route_precision": null,
|
||||
"false_no_route_rate": null,
|
||||
"execution_state_consistency_rate": 100,
|
||||
"executable_with_soft_assumptions_rate": 100,
|
||||
"soft_assumption_used_fragment_rate": 100,
|
||||
"clarification_precision": null,
|
||||
"clarification_recall": null,
|
||||
"false_clarification_rate": null
|
||||
},
|
||||
"budget": {
|
||||
"requests_total": 0,
|
||||
"retries_used": 0
|
||||
},
|
||||
"clarification_eval": {
|
||||
"labeled_cases": 0,
|
||||
"true_positive": 0,
|
||||
"false_positive": 0,
|
||||
"false_negative": 0
|
||||
},
|
||||
"route_eval": {
|
||||
"labeled_cases": 0,
|
||||
"correct_cases": 0,
|
||||
"expected_routed_cases": 0,
|
||||
"no_route_true_positive": 0,
|
||||
"no_route_false_positive": 0
|
||||
},
|
||||
"scope_eval": {
|
||||
"labeled_cases": 0,
|
||||
"correct_cases": 0
|
||||
},
|
||||
"execution_state_eval": {
|
||||
"checks_total": 2,
|
||||
"checks_passed": 2
|
||||
},
|
||||
"route_distribution": {
|
||||
"hybrid_store_plus_live": 2
|
||||
},
|
||||
"fallback_distribution": {
|
||||
"none": 2
|
||||
},
|
||||
"results": [
|
||||
{
|
||||
"case_id": "BQ-001",
|
||||
"raw_question": "Проверь счет 60 за июнь 2020",
|
||||
"validation_passed": true,
|
||||
"message_in_scope": true,
|
||||
"scope_confidence": "high",
|
||||
"contains_multiple_tasks": false,
|
||||
"fragments_total": 1,
|
||||
"in_scope_fragments": 1,
|
||||
"out_of_scope_fragments": 0,
|
||||
"unclear_fragments": 0,
|
||||
"fallback_type": "none",
|
||||
"predicted_route_status": "routed",
|
||||
"expected_route_status": null,
|
||||
"predicted_no_route_reason": null,
|
||||
"expected_no_route_reason": null,
|
||||
"predicted_clarification_required": false,
|
||||
"expected_clarification_required": null,
|
||||
"executable_with_soft_assumptions_fragments": 1,
|
||||
"trace_id": "j4C5JaGsdTX31w",
|
||||
"request_count_for_case": 0
|
||||
},
|
||||
{
|
||||
"case_id": "BQ-002",
|
||||
"raw_question": "Покажи риски по счету 97",
|
||||
"validation_passed": true,
|
||||
"message_in_scope": true,
|
||||
"scope_confidence": "high",
|
||||
"contains_multiple_tasks": false,
|
||||
"fragments_total": 1,
|
||||
"in_scope_fragments": 1,
|
||||
"out_of_scope_fragments": 0,
|
||||
"unclear_fragments": 0,
|
||||
"fallback_type": "none",
|
||||
"predicted_route_status": "routed",
|
||||
"expected_route_status": null,
|
||||
"predicted_no_route_reason": null,
|
||||
"expected_no_route_reason": null,
|
||||
"predicted_clarification_required": false,
|
||||
"expected_clarification_required": null,
|
||||
"executable_with_soft_assumptions_fragments": 1,
|
||||
"trace_id": "EiQ_YNd0Z7R8dD",
|
||||
"request_count_for_case": 0
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -0,0 +1,111 @@
|
|||
{
|
||||
"run_id": "eval-0yLbBfl8QU",
|
||||
"timestamp": "2026-03-28T14:16:11.152Z",
|
||||
"mode": "single-pass-strict",
|
||||
"use_mock": true,
|
||||
"prompt_version": "normalizer_v2_0_2",
|
||||
"schema_version": "v2_0_2",
|
||||
"dataset": {
|
||||
"source": "inline_raw_questions",
|
||||
"file": null,
|
||||
"raw_questions_count": 2
|
||||
},
|
||||
"cases_total": 2,
|
||||
"metrics": {
|
||||
"schema_validation_pass_rate": 100,
|
||||
"scope_detection_accuracy": null,
|
||||
"scope_in_scope_rate": 100,
|
||||
"multi_intent_detected_rate": 0,
|
||||
"clarification_required_rate": 0,
|
||||
"avg_fragments_per_message": 1,
|
||||
"out_of_scope_fragment_rate": 0,
|
||||
"routed_fragment_rate": 100,
|
||||
"no_route_fragment_rate": 0,
|
||||
"route_resolution_accuracy": null,
|
||||
"no_route_precision": null,
|
||||
"false_no_route_rate": null,
|
||||
"execution_state_consistency_rate": 100,
|
||||
"executable_with_soft_assumptions_rate": 100,
|
||||
"soft_assumption_used_fragment_rate": 100,
|
||||
"clarification_precision": null,
|
||||
"clarification_recall": null,
|
||||
"false_clarification_rate": null
|
||||
},
|
||||
"budget": {
|
||||
"requests_total": 0,
|
||||
"retries_used": 0
|
||||
},
|
||||
"clarification_eval": {
|
||||
"labeled_cases": 0,
|
||||
"true_positive": 0,
|
||||
"false_positive": 0,
|
||||
"false_negative": 0
|
||||
},
|
||||
"route_eval": {
|
||||
"labeled_cases": 0,
|
||||
"correct_cases": 0,
|
||||
"expected_routed_cases": 0,
|
||||
"no_route_true_positive": 0,
|
||||
"no_route_false_positive": 0
|
||||
},
|
||||
"scope_eval": {
|
||||
"labeled_cases": 0,
|
||||
"correct_cases": 0
|
||||
},
|
||||
"execution_state_eval": {
|
||||
"checks_total": 2,
|
||||
"checks_passed": 2
|
||||
},
|
||||
"route_distribution": {
|
||||
"hybrid_store_plus_live": 2
|
||||
},
|
||||
"fallback_distribution": {
|
||||
"none": 2
|
||||
},
|
||||
"results": [
|
||||
{
|
||||
"case_id": "BQ-001",
|
||||
"raw_question": "Проверь счет 60 за июнь 2020",
|
||||
"validation_passed": true,
|
||||
"message_in_scope": true,
|
||||
"scope_confidence": "high",
|
||||
"contains_multiple_tasks": false,
|
||||
"fragments_total": 1,
|
||||
"in_scope_fragments": 1,
|
||||
"out_of_scope_fragments": 0,
|
||||
"unclear_fragments": 0,
|
||||
"fallback_type": "none",
|
||||
"predicted_route_status": "routed",
|
||||
"expected_route_status": null,
|
||||
"predicted_no_route_reason": null,
|
||||
"expected_no_route_reason": null,
|
||||
"predicted_clarification_required": false,
|
||||
"expected_clarification_required": null,
|
||||
"executable_with_soft_assumptions_fragments": 1,
|
||||
"trace_id": "zeDz4lWmOU-sXS",
|
||||
"request_count_for_case": 0
|
||||
},
|
||||
{
|
||||
"case_id": "BQ-002",
|
||||
"raw_question": "Покажи риски по счету 97",
|
||||
"validation_passed": true,
|
||||
"message_in_scope": true,
|
||||
"scope_confidence": "high",
|
||||
"contains_multiple_tasks": false,
|
||||
"fragments_total": 1,
|
||||
"in_scope_fragments": 1,
|
||||
"out_of_scope_fragments": 0,
|
||||
"unclear_fragments": 0,
|
||||
"fallback_type": "none",
|
||||
"predicted_route_status": "routed",
|
||||
"expected_route_status": null,
|
||||
"predicted_no_route_reason": null,
|
||||
"expected_no_route_reason": null,
|
||||
"predicted_clarification_required": false,
|
||||
"expected_clarification_required": null,
|
||||
"executable_with_soft_assumptions_fragments": 1,
|
||||
"trace_id": "Igp7ZjWw22GURw",
|
||||
"request_count_for_case": 0
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -0,0 +1,111 @@
|
|||
{
|
||||
"run_id": "eval-1yuAQTgSrA",
|
||||
"timestamp": "2026-03-28T14:52:43.542Z",
|
||||
"mode": "single-pass-strict",
|
||||
"use_mock": true,
|
||||
"prompt_version": "normalizer_v2_0_2",
|
||||
"schema_version": "v2_0_2",
|
||||
"dataset": {
|
||||
"source": "inline_raw_questions",
|
||||
"file": null,
|
||||
"raw_questions_count": 2
|
||||
},
|
||||
"cases_total": 2,
|
||||
"metrics": {
|
||||
"schema_validation_pass_rate": 100,
|
||||
"scope_detection_accuracy": null,
|
||||
"scope_in_scope_rate": 100,
|
||||
"multi_intent_detected_rate": 0,
|
||||
"clarification_required_rate": 0,
|
||||
"avg_fragments_per_message": 1,
|
||||
"out_of_scope_fragment_rate": 0,
|
||||
"routed_fragment_rate": 100,
|
||||
"no_route_fragment_rate": 0,
|
||||
"route_resolution_accuracy": null,
|
||||
"no_route_precision": null,
|
||||
"false_no_route_rate": null,
|
||||
"execution_state_consistency_rate": 100,
|
||||
"executable_with_soft_assumptions_rate": 100,
|
||||
"soft_assumption_used_fragment_rate": 100,
|
||||
"clarification_precision": null,
|
||||
"clarification_recall": null,
|
||||
"false_clarification_rate": null
|
||||
},
|
||||
"budget": {
|
||||
"requests_total": 0,
|
||||
"retries_used": 0
|
||||
},
|
||||
"clarification_eval": {
|
||||
"labeled_cases": 0,
|
||||
"true_positive": 0,
|
||||
"false_positive": 0,
|
||||
"false_negative": 0
|
||||
},
|
||||
"route_eval": {
|
||||
"labeled_cases": 0,
|
||||
"correct_cases": 0,
|
||||
"expected_routed_cases": 0,
|
||||
"no_route_true_positive": 0,
|
||||
"no_route_false_positive": 0
|
||||
},
|
||||
"scope_eval": {
|
||||
"labeled_cases": 0,
|
||||
"correct_cases": 0
|
||||
},
|
||||
"execution_state_eval": {
|
||||
"checks_total": 2,
|
||||
"checks_passed": 2
|
||||
},
|
||||
"route_distribution": {
|
||||
"hybrid_store_plus_live": 2
|
||||
},
|
||||
"fallback_distribution": {
|
||||
"none": 2
|
||||
},
|
||||
"results": [
|
||||
{
|
||||
"case_id": "BQ-001",
|
||||
"raw_question": "Проверь счет 60 за июнь 2020",
|
||||
"validation_passed": true,
|
||||
"message_in_scope": true,
|
||||
"scope_confidence": "high",
|
||||
"contains_multiple_tasks": false,
|
||||
"fragments_total": 1,
|
||||
"in_scope_fragments": 1,
|
||||
"out_of_scope_fragments": 0,
|
||||
"unclear_fragments": 0,
|
||||
"fallback_type": "none",
|
||||
"predicted_route_status": "routed",
|
||||
"expected_route_status": null,
|
||||
"predicted_no_route_reason": null,
|
||||
"expected_no_route_reason": null,
|
||||
"predicted_clarification_required": false,
|
||||
"expected_clarification_required": null,
|
||||
"executable_with_soft_assumptions_fragments": 1,
|
||||
"trace_id": "dFofCt0krmlNxt",
|
||||
"request_count_for_case": 0
|
||||
},
|
||||
{
|
||||
"case_id": "BQ-002",
|
||||
"raw_question": "Покажи риски по НДС и по закрытию",
|
||||
"validation_passed": true,
|
||||
"message_in_scope": true,
|
||||
"scope_confidence": "high",
|
||||
"contains_multiple_tasks": false,
|
||||
"fragments_total": 1,
|
||||
"in_scope_fragments": 1,
|
||||
"out_of_scope_fragments": 0,
|
||||
"unclear_fragments": 0,
|
||||
"fallback_type": "none",
|
||||
"predicted_route_status": "routed",
|
||||
"expected_route_status": null,
|
||||
"predicted_no_route_reason": null,
|
||||
"expected_no_route_reason": null,
|
||||
"predicted_clarification_required": false,
|
||||
"expected_clarification_required": null,
|
||||
"executable_with_soft_assumptions_fragments": 1,
|
||||
"trace_id": "yuuGwyY-66-Acz",
|
||||
"request_count_for_case": 0
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -0,0 +1,111 @@
|
|||
{
|
||||
"run_id": "eval-3lDLPY889T",
|
||||
"timestamp": "2026-03-28T14:17:31.550Z",
|
||||
"mode": "single-pass-strict",
|
||||
"use_mock": true,
|
||||
"prompt_version": "normalizer_v2_0_2",
|
||||
"schema_version": "v2_0_2",
|
||||
"dataset": {
|
||||
"source": "inline_raw_questions",
|
||||
"file": null,
|
||||
"raw_questions_count": 2
|
||||
},
|
||||
"cases_total": 2,
|
||||
"metrics": {
|
||||
"schema_validation_pass_rate": 100,
|
||||
"scope_detection_accuracy": null,
|
||||
"scope_in_scope_rate": 100,
|
||||
"multi_intent_detected_rate": 0,
|
||||
"clarification_required_rate": 0,
|
||||
"avg_fragments_per_message": 1,
|
||||
"out_of_scope_fragment_rate": 0,
|
||||
"routed_fragment_rate": 100,
|
||||
"no_route_fragment_rate": 0,
|
||||
"route_resolution_accuracy": null,
|
||||
"no_route_precision": null,
|
||||
"false_no_route_rate": null,
|
||||
"execution_state_consistency_rate": 100,
|
||||
"executable_with_soft_assumptions_rate": 100,
|
||||
"soft_assumption_used_fragment_rate": 100,
|
||||
"clarification_precision": null,
|
||||
"clarification_recall": null,
|
||||
"false_clarification_rate": null
|
||||
},
|
||||
"budget": {
|
||||
"requests_total": 0,
|
||||
"retries_used": 0
|
||||
},
|
||||
"clarification_eval": {
|
||||
"labeled_cases": 0,
|
||||
"true_positive": 0,
|
||||
"false_positive": 0,
|
||||
"false_negative": 0
|
||||
},
|
||||
"route_eval": {
|
||||
"labeled_cases": 0,
|
||||
"correct_cases": 0,
|
||||
"expected_routed_cases": 0,
|
||||
"no_route_true_positive": 0,
|
||||
"no_route_false_positive": 0
|
||||
},
|
||||
"scope_eval": {
|
||||
"labeled_cases": 0,
|
||||
"correct_cases": 0
|
||||
},
|
||||
"execution_state_eval": {
|
||||
"checks_total": 2,
|
||||
"checks_passed": 2
|
||||
},
|
||||
"route_distribution": {
|
||||
"hybrid_store_plus_live": 2
|
||||
},
|
||||
"fallback_distribution": {
|
||||
"none": 2
|
||||
},
|
||||
"results": [
|
||||
{
|
||||
"case_id": "BQ-001",
|
||||
"raw_question": "Проверь счет 60 за июнь 2020",
|
||||
"validation_passed": true,
|
||||
"message_in_scope": true,
|
||||
"scope_confidence": "high",
|
||||
"contains_multiple_tasks": false,
|
||||
"fragments_total": 1,
|
||||
"in_scope_fragments": 1,
|
||||
"out_of_scope_fragments": 0,
|
||||
"unclear_fragments": 0,
|
||||
"fallback_type": "none",
|
||||
"predicted_route_status": "routed",
|
||||
"expected_route_status": null,
|
||||
"predicted_no_route_reason": null,
|
||||
"expected_no_route_reason": null,
|
||||
"predicted_clarification_required": false,
|
||||
"expected_clarification_required": null,
|
||||
"executable_with_soft_assumptions_fragments": 1,
|
||||
"trace_id": "QsfgBqSscHBfPL",
|
||||
"request_count_for_case": 0
|
||||
},
|
||||
{
|
||||
"case_id": "BQ-002",
|
||||
"raw_question": "Покажи риски по НДС и по закрытию",
|
||||
"validation_passed": true,
|
||||
"message_in_scope": true,
|
||||
"scope_confidence": "high",
|
||||
"contains_multiple_tasks": false,
|
||||
"fragments_total": 1,
|
||||
"in_scope_fragments": 1,
|
||||
"out_of_scope_fragments": 0,
|
||||
"unclear_fragments": 0,
|
||||
"fallback_type": "none",
|
||||
"predicted_route_status": "routed",
|
||||
"expected_route_status": null,
|
||||
"predicted_no_route_reason": null,
|
||||
"expected_no_route_reason": null,
|
||||
"predicted_clarification_required": false,
|
||||
"expected_clarification_required": null,
|
||||
"executable_with_soft_assumptions_fragments": 1,
|
||||
"trace_id": "lrWC9XpjJMN6AA",
|
||||
"request_count_for_case": 0
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -0,0 +1,111 @@
|
|||
{
|
||||
"run_id": "eval-48cua6GzNX",
|
||||
"timestamp": "2026-03-28T17:57:44.019Z",
|
||||
"mode": "single-pass-strict",
|
||||
"use_mock": true,
|
||||
"prompt_version": "normalizer_v2_0_2",
|
||||
"schema_version": "v2_0_2",
|
||||
"dataset": {
|
||||
"source": "inline_raw_questions",
|
||||
"file": null,
|
||||
"raw_questions_count": 2
|
||||
},
|
||||
"cases_total": 2,
|
||||
"metrics": {
|
||||
"schema_validation_pass_rate": 100,
|
||||
"scope_detection_accuracy": null,
|
||||
"scope_in_scope_rate": 100,
|
||||
"multi_intent_detected_rate": 0,
|
||||
"clarification_required_rate": 0,
|
||||
"avg_fragments_per_message": 1,
|
||||
"out_of_scope_fragment_rate": 0,
|
||||
"routed_fragment_rate": 100,
|
||||
"no_route_fragment_rate": 0,
|
||||
"route_resolution_accuracy": null,
|
||||
"no_route_precision": null,
|
||||
"false_no_route_rate": null,
|
||||
"execution_state_consistency_rate": 100,
|
||||
"executable_with_soft_assumptions_rate": 100,
|
||||
"soft_assumption_used_fragment_rate": 100,
|
||||
"clarification_precision": null,
|
||||
"clarification_recall": null,
|
||||
"false_clarification_rate": null
|
||||
},
|
||||
"budget": {
|
||||
"requests_total": 0,
|
||||
"retries_used": 0
|
||||
},
|
||||
"clarification_eval": {
|
||||
"labeled_cases": 0,
|
||||
"true_positive": 0,
|
||||
"false_positive": 0,
|
||||
"false_negative": 0
|
||||
},
|
||||
"route_eval": {
|
||||
"labeled_cases": 0,
|
||||
"correct_cases": 0,
|
||||
"expected_routed_cases": 0,
|
||||
"no_route_true_positive": 0,
|
||||
"no_route_false_positive": 0
|
||||
},
|
||||
"scope_eval": {
|
||||
"labeled_cases": 0,
|
||||
"correct_cases": 0
|
||||
},
|
||||
"execution_state_eval": {
|
||||
"checks_total": 2,
|
||||
"checks_passed": 2
|
||||
},
|
||||
"route_distribution": {
|
||||
"hybrid_store_plus_live": 2
|
||||
},
|
||||
"fallback_distribution": {
|
||||
"none": 2
|
||||
},
|
||||
"results": [
|
||||
{
|
||||
"case_id": "BQ-001",
|
||||
"raw_question": "Проверь счет 60 за июнь 2020",
|
||||
"validation_passed": true,
|
||||
"message_in_scope": true,
|
||||
"scope_confidence": "high",
|
||||
"contains_multiple_tasks": false,
|
||||
"fragments_total": 1,
|
||||
"in_scope_fragments": 1,
|
||||
"out_of_scope_fragments": 0,
|
||||
"unclear_fragments": 0,
|
||||
"fallback_type": "none",
|
||||
"predicted_route_status": "routed",
|
||||
"expected_route_status": null,
|
||||
"predicted_no_route_reason": null,
|
||||
"expected_no_route_reason": null,
|
||||
"predicted_clarification_required": false,
|
||||
"expected_clarification_required": null,
|
||||
"executable_with_soft_assumptions_fragments": 1,
|
||||
"trace_id": "60LijB0t8hI6Rv",
|
||||
"request_count_for_case": 0
|
||||
},
|
||||
{
|
||||
"case_id": "BQ-002",
|
||||
"raw_question": "Покажи риски по НДС и по закрытию",
|
||||
"validation_passed": true,
|
||||
"message_in_scope": true,
|
||||
"scope_confidence": "high",
|
||||
"contains_multiple_tasks": false,
|
||||
"fragments_total": 1,
|
||||
"in_scope_fragments": 1,
|
||||
"out_of_scope_fragments": 0,
|
||||
"unclear_fragments": 0,
|
||||
"fallback_type": "none",
|
||||
"predicted_route_status": "routed",
|
||||
"expected_route_status": null,
|
||||
"predicted_no_route_reason": null,
|
||||
"expected_no_route_reason": null,
|
||||
"predicted_clarification_required": false,
|
||||
"expected_clarification_required": null,
|
||||
"executable_with_soft_assumptions_fragments": 1,
|
||||
"trace_id": "tpPYPTRrOcZlBe",
|
||||
"request_count_for_case": 0
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -0,0 +1,137 @@
|
|||
{
|
||||
"run_id": "eval-LbmAxVpEYt",
|
||||
"timestamp": "2026-03-28T14:17:29.773Z",
|
||||
"mode": "single-pass-strict",
|
||||
"use_mock": true,
|
||||
"prompt_version": "normalizer_v2_0_2",
|
||||
"schema_version": "v2_0_2",
|
||||
"dataset": {
|
||||
"source": "inline_raw_questions",
|
||||
"file": null,
|
||||
"raw_questions_count": 3
|
||||
},
|
||||
"cases_total": 3,
|
||||
"metrics": {
|
||||
"schema_validation_pass_rate": 100,
|
||||
"scope_detection_accuracy": null,
|
||||
"scope_in_scope_rate": 33.33,
|
||||
"multi_intent_detected_rate": 0,
|
||||
"clarification_required_rate": 0,
|
||||
"avg_fragments_per_message": 1,
|
||||
"out_of_scope_fragment_rate": 33.33,
|
||||
"routed_fragment_rate": 66.67,
|
||||
"no_route_fragment_rate": 33.33,
|
||||
"route_resolution_accuracy": null,
|
||||
"no_route_precision": null,
|
||||
"false_no_route_rate": null,
|
||||
"execution_state_consistency_rate": 66.67,
|
||||
"executable_with_soft_assumptions_rate": 100,
|
||||
"soft_assumption_used_fragment_rate": 100,
|
||||
"clarification_precision": null,
|
||||
"clarification_recall": null,
|
||||
"false_clarification_rate": null
|
||||
},
|
||||
"budget": {
|
||||
"requests_total": 0,
|
||||
"retries_used": 0
|
||||
},
|
||||
"clarification_eval": {
|
||||
"labeled_cases": 0,
|
||||
"true_positive": 0,
|
||||
"false_positive": 0,
|
||||
"false_negative": 0
|
||||
},
|
||||
"route_eval": {
|
||||
"labeled_cases": 0,
|
||||
"correct_cases": 0,
|
||||
"expected_routed_cases": 0,
|
||||
"no_route_true_positive": 0,
|
||||
"no_route_false_positive": 0
|
||||
},
|
||||
"scope_eval": {
|
||||
"labeled_cases": 0,
|
||||
"correct_cases": 0
|
||||
},
|
||||
"execution_state_eval": {
|
||||
"checks_total": 3,
|
||||
"checks_passed": 2
|
||||
},
|
||||
"route_distribution": {
|
||||
"hybrid_store_plus_live": 1,
|
||||
"no_route": 1,
|
||||
"batch_refresh_then_store": 1
|
||||
},
|
||||
"fallback_distribution": {
|
||||
"none": 1,
|
||||
"out_of_scope": 1,
|
||||
"clarification": 1
|
||||
},
|
||||
"results": [
|
||||
{
|
||||
"case_id": "BQ-001",
|
||||
"raw_question": "Проверь хвосты по поставщикам и разложи цепочку",
|
||||
"validation_passed": true,
|
||||
"message_in_scope": true,
|
||||
"scope_confidence": "high",
|
||||
"contains_multiple_tasks": false,
|
||||
"fragments_total": 1,
|
||||
"in_scope_fragments": 1,
|
||||
"out_of_scope_fragments": 0,
|
||||
"unclear_fragments": 0,
|
||||
"fallback_type": "none",
|
||||
"predicted_route_status": "routed",
|
||||
"expected_route_status": null,
|
||||
"predicted_no_route_reason": null,
|
||||
"expected_no_route_reason": null,
|
||||
"predicted_clarification_required": false,
|
||||
"expected_clarification_required": null,
|
||||
"executable_with_soft_assumptions_fragments": 1,
|
||||
"trace_id": "THNkvQLVaISlq3",
|
||||
"request_count_for_case": 0
|
||||
},
|
||||
{
|
||||
"case_id": "BQ-002",
|
||||
"raw_question": "Как вообще по ФСБУ",
|
||||
"validation_passed": true,
|
||||
"message_in_scope": false,
|
||||
"scope_confidence": "low",
|
||||
"contains_multiple_tasks": false,
|
||||
"fragments_total": 1,
|
||||
"in_scope_fragments": 0,
|
||||
"out_of_scope_fragments": 1,
|
||||
"unclear_fragments": 0,
|
||||
"fallback_type": "out_of_scope",
|
||||
"predicted_route_status": "no_route",
|
||||
"expected_route_status": null,
|
||||
"predicted_no_route_reason": "out_of_scope",
|
||||
"expected_no_route_reason": null,
|
||||
"predicted_clarification_required": false,
|
||||
"expected_clarification_required": null,
|
||||
"executable_with_soft_assumptions_fragments": 0,
|
||||
"trace_id": "0kU7UfIyaxB3TA",
|
||||
"request_count_for_case": 0
|
||||
},
|
||||
{
|
||||
"case_id": "BQ-003",
|
||||
"raw_question": "Покажи топ рисков за июнь 2020",
|
||||
"validation_passed": true,
|
||||
"message_in_scope": false,
|
||||
"scope_confidence": "low",
|
||||
"contains_multiple_tasks": false,
|
||||
"fragments_total": 1,
|
||||
"in_scope_fragments": 0,
|
||||
"out_of_scope_fragments": 0,
|
||||
"unclear_fragments": 1,
|
||||
"fallback_type": "clarification",
|
||||
"predicted_route_status": "routed",
|
||||
"expected_route_status": null,
|
||||
"predicted_no_route_reason": null,
|
||||
"expected_no_route_reason": null,
|
||||
"predicted_clarification_required": false,
|
||||
"expected_clarification_required": null,
|
||||
"executable_with_soft_assumptions_fragments": 0,
|
||||
"trace_id": "ZCKIUbbI9qTMwd",
|
||||
"request_count_for_case": 0
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -0,0 +1,137 @@
|
|||
{
|
||||
"run_id": "eval-LpNMUq6e64",
|
||||
"timestamp": "2026-03-28T14:52:41.774Z",
|
||||
"mode": "single-pass-strict",
|
||||
"use_mock": true,
|
||||
"prompt_version": "normalizer_v2_0_2",
|
||||
"schema_version": "v2_0_2",
|
||||
"dataset": {
|
||||
"source": "inline_raw_questions",
|
||||
"file": null,
|
||||
"raw_questions_count": 3
|
||||
},
|
||||
"cases_total": 3,
|
||||
"metrics": {
|
||||
"schema_validation_pass_rate": 100,
|
||||
"scope_detection_accuracy": null,
|
||||
"scope_in_scope_rate": 33.33,
|
||||
"multi_intent_detected_rate": 0,
|
||||
"clarification_required_rate": 0,
|
||||
"avg_fragments_per_message": 1,
|
||||
"out_of_scope_fragment_rate": 33.33,
|
||||
"routed_fragment_rate": 66.67,
|
||||
"no_route_fragment_rate": 33.33,
|
||||
"route_resolution_accuracy": null,
|
||||
"no_route_precision": null,
|
||||
"false_no_route_rate": null,
|
||||
"execution_state_consistency_rate": 66.67,
|
||||
"executable_with_soft_assumptions_rate": 100,
|
||||
"soft_assumption_used_fragment_rate": 100,
|
||||
"clarification_precision": null,
|
||||
"clarification_recall": null,
|
||||
"false_clarification_rate": null
|
||||
},
|
||||
"budget": {
|
||||
"requests_total": 0,
|
||||
"retries_used": 0
|
||||
},
|
||||
"clarification_eval": {
|
||||
"labeled_cases": 0,
|
||||
"true_positive": 0,
|
||||
"false_positive": 0,
|
||||
"false_negative": 0
|
||||
},
|
||||
"route_eval": {
|
||||
"labeled_cases": 0,
|
||||
"correct_cases": 0,
|
||||
"expected_routed_cases": 0,
|
||||
"no_route_true_positive": 0,
|
||||
"no_route_false_positive": 0
|
||||
},
|
||||
"scope_eval": {
|
||||
"labeled_cases": 0,
|
||||
"correct_cases": 0
|
||||
},
|
||||
"execution_state_eval": {
|
||||
"checks_total": 3,
|
||||
"checks_passed": 2
|
||||
},
|
||||
"route_distribution": {
|
||||
"hybrid_store_plus_live": 1,
|
||||
"no_route": 1,
|
||||
"batch_refresh_then_store": 1
|
||||
},
|
||||
"fallback_distribution": {
|
||||
"none": 1,
|
||||
"out_of_scope": 1,
|
||||
"clarification": 1
|
||||
},
|
||||
"results": [
|
||||
{
|
||||
"case_id": "BQ-001",
|
||||
"raw_question": "Проверь хвосты по поставщикам и разложи цепочку",
|
||||
"validation_passed": true,
|
||||
"message_in_scope": true,
|
||||
"scope_confidence": "high",
|
||||
"contains_multiple_tasks": false,
|
||||
"fragments_total": 1,
|
||||
"in_scope_fragments": 1,
|
||||
"out_of_scope_fragments": 0,
|
||||
"unclear_fragments": 0,
|
||||
"fallback_type": "none",
|
||||
"predicted_route_status": "routed",
|
||||
"expected_route_status": null,
|
||||
"predicted_no_route_reason": null,
|
||||
"expected_no_route_reason": null,
|
||||
"predicted_clarification_required": false,
|
||||
"expected_clarification_required": null,
|
||||
"executable_with_soft_assumptions_fragments": 1,
|
||||
"trace_id": "jjxZOJBxrjPdsX",
|
||||
"request_count_for_case": 0
|
||||
},
|
||||
{
|
||||
"case_id": "BQ-002",
|
||||
"raw_question": "Как вообще по ФСБУ",
|
||||
"validation_passed": true,
|
||||
"message_in_scope": false,
|
||||
"scope_confidence": "low",
|
||||
"contains_multiple_tasks": false,
|
||||
"fragments_total": 1,
|
||||
"in_scope_fragments": 0,
|
||||
"out_of_scope_fragments": 1,
|
||||
"unclear_fragments": 0,
|
||||
"fallback_type": "out_of_scope",
|
||||
"predicted_route_status": "no_route",
|
||||
"expected_route_status": null,
|
||||
"predicted_no_route_reason": "out_of_scope",
|
||||
"expected_no_route_reason": null,
|
||||
"predicted_clarification_required": false,
|
||||
"expected_clarification_required": null,
|
||||
"executable_with_soft_assumptions_fragments": 0,
|
||||
"trace_id": "bYY62dikNsLECZ",
|
||||
"request_count_for_case": 0
|
||||
},
|
||||
{
|
||||
"case_id": "BQ-003",
|
||||
"raw_question": "Покажи топ рисков за июнь 2020",
|
||||
"validation_passed": true,
|
||||
"message_in_scope": false,
|
||||
"scope_confidence": "low",
|
||||
"contains_multiple_tasks": false,
|
||||
"fragments_total": 1,
|
||||
"in_scope_fragments": 0,
|
||||
"out_of_scope_fragments": 0,
|
||||
"unclear_fragments": 1,
|
||||
"fallback_type": "clarification",
|
||||
"predicted_route_status": "routed",
|
||||
"expected_route_status": null,
|
||||
"predicted_no_route_reason": null,
|
||||
"expected_no_route_reason": null,
|
||||
"predicted_clarification_required": false,
|
||||
"expected_clarification_required": null,
|
||||
"executable_with_soft_assumptions_fragments": 0,
|
||||
"trace_id": "baezDULblEFYkL",
|
||||
"request_count_for_case": 0
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -0,0 +1,111 @@
|
|||
{
|
||||
"run_id": "eval-Mk-Ep58vC0",
|
||||
"timestamp": "2026-03-28T17:57:43.813Z",
|
||||
"mode": "single-pass-strict",
|
||||
"use_mock": true,
|
||||
"prompt_version": "normalizer_v2_0_2",
|
||||
"schema_version": "v2_0_2",
|
||||
"dataset": {
|
||||
"source": "inline_raw_questions",
|
||||
"file": null,
|
||||
"raw_questions_count": 2
|
||||
},
|
||||
"cases_total": 2,
|
||||
"metrics": {
|
||||
"schema_validation_pass_rate": 100,
|
||||
"scope_detection_accuracy": null,
|
||||
"scope_in_scope_rate": 100,
|
||||
"multi_intent_detected_rate": 0,
|
||||
"clarification_required_rate": 0,
|
||||
"avg_fragments_per_message": 1,
|
||||
"out_of_scope_fragment_rate": 0,
|
||||
"routed_fragment_rate": 100,
|
||||
"no_route_fragment_rate": 0,
|
||||
"route_resolution_accuracy": null,
|
||||
"no_route_precision": null,
|
||||
"false_no_route_rate": null,
|
||||
"execution_state_consistency_rate": 100,
|
||||
"executable_with_soft_assumptions_rate": 100,
|
||||
"soft_assumption_used_fragment_rate": 100,
|
||||
"clarification_precision": null,
|
||||
"clarification_recall": null,
|
||||
"false_clarification_rate": null
|
||||
},
|
||||
"budget": {
|
||||
"requests_total": 0,
|
||||
"retries_used": 0
|
||||
},
|
||||
"clarification_eval": {
|
||||
"labeled_cases": 0,
|
||||
"true_positive": 0,
|
||||
"false_positive": 0,
|
||||
"false_negative": 0
|
||||
},
|
||||
"route_eval": {
|
||||
"labeled_cases": 0,
|
||||
"correct_cases": 0,
|
||||
"expected_routed_cases": 0,
|
||||
"no_route_true_positive": 0,
|
||||
"no_route_false_positive": 0
|
||||
},
|
||||
"scope_eval": {
|
||||
"labeled_cases": 0,
|
||||
"correct_cases": 0
|
||||
},
|
||||
"execution_state_eval": {
|
||||
"checks_total": 2,
|
||||
"checks_passed": 2
|
||||
},
|
||||
"route_distribution": {
|
||||
"hybrid_store_plus_live": 2
|
||||
},
|
||||
"fallback_distribution": {
|
||||
"none": 2
|
||||
},
|
||||
"results": [
|
||||
{
|
||||
"case_id": "BQ-001",
|
||||
"raw_question": "Проверь счет 60 за июнь 2020",
|
||||
"validation_passed": true,
|
||||
"message_in_scope": true,
|
||||
"scope_confidence": "high",
|
||||
"contains_multiple_tasks": false,
|
||||
"fragments_total": 1,
|
||||
"in_scope_fragments": 1,
|
||||
"out_of_scope_fragments": 0,
|
||||
"unclear_fragments": 0,
|
||||
"fallback_type": "none",
|
||||
"predicted_route_status": "routed",
|
||||
"expected_route_status": null,
|
||||
"predicted_no_route_reason": null,
|
||||
"expected_no_route_reason": null,
|
||||
"predicted_clarification_required": false,
|
||||
"expected_clarification_required": null,
|
||||
"executable_with_soft_assumptions_fragments": 1,
|
||||
"trace_id": "p_blMcrrCin_H_",
|
||||
"request_count_for_case": 0
|
||||
},
|
||||
{
|
||||
"case_id": "BQ-002",
|
||||
"raw_question": "Покажи риски по счету 97",
|
||||
"validation_passed": true,
|
||||
"message_in_scope": true,
|
||||
"scope_confidence": "high",
|
||||
"contains_multiple_tasks": false,
|
||||
"fragments_total": 1,
|
||||
"in_scope_fragments": 1,
|
||||
"out_of_scope_fragments": 0,
|
||||
"unclear_fragments": 0,
|
||||
"fallback_type": "none",
|
||||
"predicted_route_status": "routed",
|
||||
"expected_route_status": null,
|
||||
"predicted_no_route_reason": null,
|
||||
"expected_no_route_reason": null,
|
||||
"predicted_clarification_required": false,
|
||||
"expected_clarification_required": null,
|
||||
"executable_with_soft_assumptions_fragments": 1,
|
||||
"trace_id": "esnlDt05yjx_03",
|
||||
"request_count_for_case": 0
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -0,0 +1,137 @@
|
|||
{
|
||||
"run_id": "eval-N-nY96elpX",
|
||||
"timestamp": "2026-03-28T14:23:36.202Z",
|
||||
"mode": "single-pass-strict",
|
||||
"use_mock": true,
|
||||
"prompt_version": "normalizer_v2_0_2",
|
||||
"schema_version": "v2_0_2",
|
||||
"dataset": {
|
||||
"source": "inline_raw_questions",
|
||||
"file": null,
|
||||
"raw_questions_count": 3
|
||||
},
|
||||
"cases_total": 3,
|
||||
"metrics": {
|
||||
"schema_validation_pass_rate": 100,
|
||||
"scope_detection_accuracy": null,
|
||||
"scope_in_scope_rate": 33.33,
|
||||
"multi_intent_detected_rate": 0,
|
||||
"clarification_required_rate": 0,
|
||||
"avg_fragments_per_message": 1,
|
||||
"out_of_scope_fragment_rate": 33.33,
|
||||
"routed_fragment_rate": 66.67,
|
||||
"no_route_fragment_rate": 33.33,
|
||||
"route_resolution_accuracy": null,
|
||||
"no_route_precision": null,
|
||||
"false_no_route_rate": null,
|
||||
"execution_state_consistency_rate": 66.67,
|
||||
"executable_with_soft_assumptions_rate": 100,
|
||||
"soft_assumption_used_fragment_rate": 100,
|
||||
"clarification_precision": null,
|
||||
"clarification_recall": null,
|
||||
"false_clarification_rate": null
|
||||
},
|
||||
"budget": {
|
||||
"requests_total": 0,
|
||||
"retries_used": 0
|
||||
},
|
||||
"clarification_eval": {
|
||||
"labeled_cases": 0,
|
||||
"true_positive": 0,
|
||||
"false_positive": 0,
|
||||
"false_negative": 0
|
||||
},
|
||||
"route_eval": {
|
||||
"labeled_cases": 0,
|
||||
"correct_cases": 0,
|
||||
"expected_routed_cases": 0,
|
||||
"no_route_true_positive": 0,
|
||||
"no_route_false_positive": 0
|
||||
},
|
||||
"scope_eval": {
|
||||
"labeled_cases": 0,
|
||||
"correct_cases": 0
|
||||
},
|
||||
"execution_state_eval": {
|
||||
"checks_total": 3,
|
||||
"checks_passed": 2
|
||||
},
|
||||
"route_distribution": {
|
||||
"hybrid_store_plus_live": 1,
|
||||
"no_route": 1,
|
||||
"batch_refresh_then_store": 1
|
||||
},
|
||||
"fallback_distribution": {
|
||||
"none": 1,
|
||||
"out_of_scope": 1,
|
||||
"clarification": 1
|
||||
},
|
||||
"results": [
|
||||
{
|
||||
"case_id": "BQ-001",
|
||||
"raw_question": "Проверь хвосты по поставщикам и разложи цепочку",
|
||||
"validation_passed": true,
|
||||
"message_in_scope": true,
|
||||
"scope_confidence": "high",
|
||||
"contains_multiple_tasks": false,
|
||||
"fragments_total": 1,
|
||||
"in_scope_fragments": 1,
|
||||
"out_of_scope_fragments": 0,
|
||||
"unclear_fragments": 0,
|
||||
"fallback_type": "none",
|
||||
"predicted_route_status": "routed",
|
||||
"expected_route_status": null,
|
||||
"predicted_no_route_reason": null,
|
||||
"expected_no_route_reason": null,
|
||||
"predicted_clarification_required": false,
|
||||
"expected_clarification_required": null,
|
||||
"executable_with_soft_assumptions_fragments": 1,
|
||||
"trace_id": "7mDZkne0GBSe62",
|
||||
"request_count_for_case": 0
|
||||
},
|
||||
{
|
||||
"case_id": "BQ-002",
|
||||
"raw_question": "Как вообще по ФСБУ",
|
||||
"validation_passed": true,
|
||||
"message_in_scope": false,
|
||||
"scope_confidence": "low",
|
||||
"contains_multiple_tasks": false,
|
||||
"fragments_total": 1,
|
||||
"in_scope_fragments": 0,
|
||||
"out_of_scope_fragments": 1,
|
||||
"unclear_fragments": 0,
|
||||
"fallback_type": "out_of_scope",
|
||||
"predicted_route_status": "no_route",
|
||||
"expected_route_status": null,
|
||||
"predicted_no_route_reason": "out_of_scope",
|
||||
"expected_no_route_reason": null,
|
||||
"predicted_clarification_required": false,
|
||||
"expected_clarification_required": null,
|
||||
"executable_with_soft_assumptions_fragments": 0,
|
||||
"trace_id": "wAVEOezciqQrtm",
|
||||
"request_count_for_case": 0
|
||||
},
|
||||
{
|
||||
"case_id": "BQ-003",
|
||||
"raw_question": "Покажи топ рисков за июнь 2020",
|
||||
"validation_passed": true,
|
||||
"message_in_scope": false,
|
||||
"scope_confidence": "low",
|
||||
"contains_multiple_tasks": false,
|
||||
"fragments_total": 1,
|
||||
"in_scope_fragments": 0,
|
||||
"out_of_scope_fragments": 0,
|
||||
"unclear_fragments": 1,
|
||||
"fallback_type": "clarification",
|
||||
"predicted_route_status": "routed",
|
||||
"expected_route_status": null,
|
||||
"predicted_no_route_reason": null,
|
||||
"expected_no_route_reason": null,
|
||||
"predicted_clarification_required": false,
|
||||
"expected_clarification_required": null,
|
||||
"executable_with_soft_assumptions_fragments": 0,
|
||||
"trace_id": "t5shV55ZPr8wir",
|
||||
"request_count_for_case": 0
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -0,0 +1,111 @@
|
|||
{
|
||||
"run_id": "eval-S-kDKKK4xO",
|
||||
"timestamp": "2026-03-28T14:17:31.390Z",
|
||||
"mode": "single-pass-strict",
|
||||
"use_mock": true,
|
||||
"prompt_version": "normalizer_v2_0_2",
|
||||
"schema_version": "v2_0_2",
|
||||
"dataset": {
|
||||
"source": "inline_raw_questions",
|
||||
"file": null,
|
||||
"raw_questions_count": 2
|
||||
},
|
||||
"cases_total": 2,
|
||||
"metrics": {
|
||||
"schema_validation_pass_rate": 100,
|
||||
"scope_detection_accuracy": null,
|
||||
"scope_in_scope_rate": 100,
|
||||
"multi_intent_detected_rate": 0,
|
||||
"clarification_required_rate": 0,
|
||||
"avg_fragments_per_message": 1,
|
||||
"out_of_scope_fragment_rate": 0,
|
||||
"routed_fragment_rate": 100,
|
||||
"no_route_fragment_rate": 0,
|
||||
"route_resolution_accuracy": null,
|
||||
"no_route_precision": null,
|
||||
"false_no_route_rate": null,
|
||||
"execution_state_consistency_rate": 100,
|
||||
"executable_with_soft_assumptions_rate": 100,
|
||||
"soft_assumption_used_fragment_rate": 100,
|
||||
"clarification_precision": null,
|
||||
"clarification_recall": null,
|
||||
"false_clarification_rate": null
|
||||
},
|
||||
"budget": {
|
||||
"requests_total": 0,
|
||||
"retries_used": 0
|
||||
},
|
||||
"clarification_eval": {
|
||||
"labeled_cases": 0,
|
||||
"true_positive": 0,
|
||||
"false_positive": 0,
|
||||
"false_negative": 0
|
||||
},
|
||||
"route_eval": {
|
||||
"labeled_cases": 0,
|
||||
"correct_cases": 0,
|
||||
"expected_routed_cases": 0,
|
||||
"no_route_true_positive": 0,
|
||||
"no_route_false_positive": 0
|
||||
},
|
||||
"scope_eval": {
|
||||
"labeled_cases": 0,
|
||||
"correct_cases": 0
|
||||
},
|
||||
"execution_state_eval": {
|
||||
"checks_total": 2,
|
||||
"checks_passed": 2
|
||||
},
|
||||
"route_distribution": {
|
||||
"hybrid_store_plus_live": 2
|
||||
},
|
||||
"fallback_distribution": {
|
||||
"none": 2
|
||||
},
|
||||
"results": [
|
||||
{
|
||||
"case_id": "BQ-001",
|
||||
"raw_question": "Проверь счет 60 за июнь 2020",
|
||||
"validation_passed": true,
|
||||
"message_in_scope": true,
|
||||
"scope_confidence": "high",
|
||||
"contains_multiple_tasks": false,
|
||||
"fragments_total": 1,
|
||||
"in_scope_fragments": 1,
|
||||
"out_of_scope_fragments": 0,
|
||||
"unclear_fragments": 0,
|
||||
"fallback_type": "none",
|
||||
"predicted_route_status": "routed",
|
||||
"expected_route_status": null,
|
||||
"predicted_no_route_reason": null,
|
||||
"expected_no_route_reason": null,
|
||||
"predicted_clarification_required": false,
|
||||
"expected_clarification_required": null,
|
||||
"executable_with_soft_assumptions_fragments": 1,
|
||||
"trace_id": "kl3P7qQGw4BRHD",
|
||||
"request_count_for_case": 0
|
||||
},
|
||||
{
|
||||
"case_id": "BQ-002",
|
||||
"raw_question": "Покажи риски по счету 97",
|
||||
"validation_passed": true,
|
||||
"message_in_scope": true,
|
||||
"scope_confidence": "high",
|
||||
"contains_multiple_tasks": false,
|
||||
"fragments_total": 1,
|
||||
"in_scope_fragments": 1,
|
||||
"out_of_scope_fragments": 0,
|
||||
"unclear_fragments": 0,
|
||||
"fallback_type": "none",
|
||||
"predicted_route_status": "routed",
|
||||
"expected_route_status": null,
|
||||
"predicted_no_route_reason": null,
|
||||
"expected_no_route_reason": null,
|
||||
"predicted_clarification_required": false,
|
||||
"expected_clarification_required": null,
|
||||
"executable_with_soft_assumptions_fragments": 1,
|
||||
"trace_id": "yn8Ku_R5880RIB",
|
||||
"request_count_for_case": 0
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -0,0 +1,137 @@
|
|||
{
|
||||
"run_id": "eval-UNNoKia3JQ",
|
||||
"timestamp": "2026-03-28T17:57:42.190Z",
|
||||
"mode": "single-pass-strict",
|
||||
"use_mock": true,
|
||||
"prompt_version": "normalizer_v2_0_2",
|
||||
"schema_version": "v2_0_2",
|
||||
"dataset": {
|
||||
"source": "inline_raw_questions",
|
||||
"file": null,
|
||||
"raw_questions_count": 3
|
||||
},
|
||||
"cases_total": 3,
|
||||
"metrics": {
|
||||
"schema_validation_pass_rate": 100,
|
||||
"scope_detection_accuracy": null,
|
||||
"scope_in_scope_rate": 33.33,
|
||||
"multi_intent_detected_rate": 0,
|
||||
"clarification_required_rate": 0,
|
||||
"avg_fragments_per_message": 1,
|
||||
"out_of_scope_fragment_rate": 33.33,
|
||||
"routed_fragment_rate": 66.67,
|
||||
"no_route_fragment_rate": 33.33,
|
||||
"route_resolution_accuracy": null,
|
||||
"no_route_precision": null,
|
||||
"false_no_route_rate": null,
|
||||
"execution_state_consistency_rate": 66.67,
|
||||
"executable_with_soft_assumptions_rate": 100,
|
||||
"soft_assumption_used_fragment_rate": 100,
|
||||
"clarification_precision": null,
|
||||
"clarification_recall": null,
|
||||
"false_clarification_rate": null
|
||||
},
|
||||
"budget": {
|
||||
"requests_total": 0,
|
||||
"retries_used": 0
|
||||
},
|
||||
"clarification_eval": {
|
||||
"labeled_cases": 0,
|
||||
"true_positive": 0,
|
||||
"false_positive": 0,
|
||||
"false_negative": 0
|
||||
},
|
||||
"route_eval": {
|
||||
"labeled_cases": 0,
|
||||
"correct_cases": 0,
|
||||
"expected_routed_cases": 0,
|
||||
"no_route_true_positive": 0,
|
||||
"no_route_false_positive": 0
|
||||
},
|
||||
"scope_eval": {
|
||||
"labeled_cases": 0,
|
||||
"correct_cases": 0
|
||||
},
|
||||
"execution_state_eval": {
|
||||
"checks_total": 3,
|
||||
"checks_passed": 2
|
||||
},
|
||||
"route_distribution": {
|
||||
"hybrid_store_plus_live": 1,
|
||||
"no_route": 1,
|
||||
"batch_refresh_then_store": 1
|
||||
},
|
||||
"fallback_distribution": {
|
||||
"none": 1,
|
||||
"out_of_scope": 1,
|
||||
"clarification": 1
|
||||
},
|
||||
"results": [
|
||||
{
|
||||
"case_id": "BQ-001",
|
||||
"raw_question": "Проверь хвосты по поставщикам и разложи цепочку",
|
||||
"validation_passed": true,
|
||||
"message_in_scope": true,
|
||||
"scope_confidence": "high",
|
||||
"contains_multiple_tasks": false,
|
||||
"fragments_total": 1,
|
||||
"in_scope_fragments": 1,
|
||||
"out_of_scope_fragments": 0,
|
||||
"unclear_fragments": 0,
|
||||
"fallback_type": "none",
|
||||
"predicted_route_status": "routed",
|
||||
"expected_route_status": null,
|
||||
"predicted_no_route_reason": null,
|
||||
"expected_no_route_reason": null,
|
||||
"predicted_clarification_required": false,
|
||||
"expected_clarification_required": null,
|
||||
"executable_with_soft_assumptions_fragments": 1,
|
||||
"trace_id": "MY7YLMfaYP3kVR",
|
||||
"request_count_for_case": 0
|
||||
},
|
||||
{
|
||||
"case_id": "BQ-002",
|
||||
"raw_question": "Как вообще по ФСБУ",
|
||||
"validation_passed": true,
|
||||
"message_in_scope": false,
|
||||
"scope_confidence": "low",
|
||||
"contains_multiple_tasks": false,
|
||||
"fragments_total": 1,
|
||||
"in_scope_fragments": 0,
|
||||
"out_of_scope_fragments": 1,
|
||||
"unclear_fragments": 0,
|
||||
"fallback_type": "out_of_scope",
|
||||
"predicted_route_status": "no_route",
|
||||
"expected_route_status": null,
|
||||
"predicted_no_route_reason": "out_of_scope",
|
||||
"expected_no_route_reason": null,
|
||||
"predicted_clarification_required": false,
|
||||
"expected_clarification_required": null,
|
||||
"executable_with_soft_assumptions_fragments": 0,
|
||||
"trace_id": "gpwnhjSvR3NClQ",
|
||||
"request_count_for_case": 0
|
||||
},
|
||||
{
|
||||
"case_id": "BQ-003",
|
||||
"raw_question": "Покажи топ рисков за июнь 2020",
|
||||
"validation_passed": true,
|
||||
"message_in_scope": false,
|
||||
"scope_confidence": "low",
|
||||
"contains_multiple_tasks": false,
|
||||
"fragments_total": 1,
|
||||
"in_scope_fragments": 0,
|
||||
"out_of_scope_fragments": 0,
|
||||
"unclear_fragments": 1,
|
||||
"fallback_type": "clarification",
|
||||
"predicted_route_status": "routed",
|
||||
"expected_route_status": null,
|
||||
"predicted_no_route_reason": null,
|
||||
"expected_no_route_reason": null,
|
||||
"predicted_clarification_required": false,
|
||||
"expected_clarification_required": null,
|
||||
"executable_with_soft_assumptions_fragments": 0,
|
||||
"trace_id": "WYxkGFarO1WeXH",
|
||||
"request_count_for_case": 0
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -0,0 +1,111 @@
|
|||
{
|
||||
"run_id": "eval-X71VuszUM2",
|
||||
"timestamp": "2026-03-28T14:47:45.410Z",
|
||||
"mode": "single-pass-strict",
|
||||
"use_mock": true,
|
||||
"prompt_version": "normalizer_v2_0_2",
|
||||
"schema_version": "v2_0_2",
|
||||
"dataset": {
|
||||
"source": "inline_raw_questions",
|
||||
"file": null,
|
||||
"raw_questions_count": 2
|
||||
},
|
||||
"cases_total": 2,
|
||||
"metrics": {
|
||||
"schema_validation_pass_rate": 100,
|
||||
"scope_detection_accuracy": null,
|
||||
"scope_in_scope_rate": 100,
|
||||
"multi_intent_detected_rate": 0,
|
||||
"clarification_required_rate": 0,
|
||||
"avg_fragments_per_message": 1,
|
||||
"out_of_scope_fragment_rate": 0,
|
||||
"routed_fragment_rate": 100,
|
||||
"no_route_fragment_rate": 0,
|
||||
"route_resolution_accuracy": null,
|
||||
"no_route_precision": null,
|
||||
"false_no_route_rate": null,
|
||||
"execution_state_consistency_rate": 100,
|
||||
"executable_with_soft_assumptions_rate": 100,
|
||||
"soft_assumption_used_fragment_rate": 100,
|
||||
"clarification_precision": null,
|
||||
"clarification_recall": null,
|
||||
"false_clarification_rate": null
|
||||
},
|
||||
"budget": {
|
||||
"requests_total": 0,
|
||||
"retries_used": 0
|
||||
},
|
||||
"clarification_eval": {
|
||||
"labeled_cases": 0,
|
||||
"true_positive": 0,
|
||||
"false_positive": 0,
|
||||
"false_negative": 0
|
||||
},
|
||||
"route_eval": {
|
||||
"labeled_cases": 0,
|
||||
"correct_cases": 0,
|
||||
"expected_routed_cases": 0,
|
||||
"no_route_true_positive": 0,
|
||||
"no_route_false_positive": 0
|
||||
},
|
||||
"scope_eval": {
|
||||
"labeled_cases": 0,
|
||||
"correct_cases": 0
|
||||
},
|
||||
"execution_state_eval": {
|
||||
"checks_total": 2,
|
||||
"checks_passed": 2
|
||||
},
|
||||
"route_distribution": {
|
||||
"hybrid_store_plus_live": 2
|
||||
},
|
||||
"fallback_distribution": {
|
||||
"none": 2
|
||||
},
|
||||
"results": [
|
||||
{
|
||||
"case_id": "BQ-001",
|
||||
"raw_question": "Проверь счет 60 за июнь 2020",
|
||||
"validation_passed": true,
|
||||
"message_in_scope": true,
|
||||
"scope_confidence": "high",
|
||||
"contains_multiple_tasks": false,
|
||||
"fragments_total": 1,
|
||||
"in_scope_fragments": 1,
|
||||
"out_of_scope_fragments": 0,
|
||||
"unclear_fragments": 0,
|
||||
"fallback_type": "none",
|
||||
"predicted_route_status": "routed",
|
||||
"expected_route_status": null,
|
||||
"predicted_no_route_reason": null,
|
||||
"expected_no_route_reason": null,
|
||||
"predicted_clarification_required": false,
|
||||
"expected_clarification_required": null,
|
||||
"executable_with_soft_assumptions_fragments": 1,
|
||||
"trace_id": "QacgRS_Ur2ayEH",
|
||||
"request_count_for_case": 0
|
||||
},
|
||||
{
|
||||
"case_id": "BQ-002",
|
||||
"raw_question": "Покажи риски по счету 97",
|
||||
"validation_passed": true,
|
||||
"message_in_scope": true,
|
||||
"scope_confidence": "high",
|
||||
"contains_multiple_tasks": false,
|
||||
"fragments_total": 1,
|
||||
"in_scope_fragments": 1,
|
||||
"out_of_scope_fragments": 0,
|
||||
"unclear_fragments": 0,
|
||||
"fallback_type": "none",
|
||||
"predicted_route_status": "routed",
|
||||
"expected_route_status": null,
|
||||
"predicted_no_route_reason": null,
|
||||
"expected_no_route_reason": null,
|
||||
"predicted_clarification_required": false,
|
||||
"expected_clarification_required": null,
|
||||
"executable_with_soft_assumptions_fragments": 1,
|
||||
"trace_id": "qtDYC8oP9w1v9B",
|
||||
"request_count_for_case": 0
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -0,0 +1,111 @@
|
|||
{
|
||||
"run_id": "eval-ZBwXbFcCs9",
|
||||
"timestamp": "2026-03-28T14:23:37.997Z",
|
||||
"mode": "single-pass-strict",
|
||||
"use_mock": true,
|
||||
"prompt_version": "normalizer_v2_0_2",
|
||||
"schema_version": "v2_0_2",
|
||||
"dataset": {
|
||||
"source": "inline_raw_questions",
|
||||
"file": null,
|
||||
"raw_questions_count": 2
|
||||
},
|
||||
"cases_total": 2,
|
||||
"metrics": {
|
||||
"schema_validation_pass_rate": 100,
|
||||
"scope_detection_accuracy": null,
|
||||
"scope_in_scope_rate": 100,
|
||||
"multi_intent_detected_rate": 0,
|
||||
"clarification_required_rate": 0,
|
||||
"avg_fragments_per_message": 1,
|
||||
"out_of_scope_fragment_rate": 0,
|
||||
"routed_fragment_rate": 100,
|
||||
"no_route_fragment_rate": 0,
|
||||
"route_resolution_accuracy": null,
|
||||
"no_route_precision": null,
|
||||
"false_no_route_rate": null,
|
||||
"execution_state_consistency_rate": 100,
|
||||
"executable_with_soft_assumptions_rate": 100,
|
||||
"soft_assumption_used_fragment_rate": 100,
|
||||
"clarification_precision": null,
|
||||
"clarification_recall": null,
|
||||
"false_clarification_rate": null
|
||||
},
|
||||
"budget": {
|
||||
"requests_total": 0,
|
||||
"retries_used": 0
|
||||
},
|
||||
"clarification_eval": {
|
||||
"labeled_cases": 0,
|
||||
"true_positive": 0,
|
||||
"false_positive": 0,
|
||||
"false_negative": 0
|
||||
},
|
||||
"route_eval": {
|
||||
"labeled_cases": 0,
|
||||
"correct_cases": 0,
|
||||
"expected_routed_cases": 0,
|
||||
"no_route_true_positive": 0,
|
||||
"no_route_false_positive": 0
|
||||
},
|
||||
"scope_eval": {
|
||||
"labeled_cases": 0,
|
||||
"correct_cases": 0
|
||||
},
|
||||
"execution_state_eval": {
|
||||
"checks_total": 2,
|
||||
"checks_passed": 2
|
||||
},
|
||||
"route_distribution": {
|
||||
"hybrid_store_plus_live": 2
|
||||
},
|
||||
"fallback_distribution": {
|
||||
"none": 2
|
||||
},
|
||||
"results": [
|
||||
{
|
||||
"case_id": "BQ-001",
|
||||
"raw_question": "Проверь счет 60 за июнь 2020",
|
||||
"validation_passed": true,
|
||||
"message_in_scope": true,
|
||||
"scope_confidence": "high",
|
||||
"contains_multiple_tasks": false,
|
||||
"fragments_total": 1,
|
||||
"in_scope_fragments": 1,
|
||||
"out_of_scope_fragments": 0,
|
||||
"unclear_fragments": 0,
|
||||
"fallback_type": "none",
|
||||
"predicted_route_status": "routed",
|
||||
"expected_route_status": null,
|
||||
"predicted_no_route_reason": null,
|
||||
"expected_no_route_reason": null,
|
||||
"predicted_clarification_required": false,
|
||||
"expected_clarification_required": null,
|
||||
"executable_with_soft_assumptions_fragments": 1,
|
||||
"trace_id": "NMt3FqmnkEylaF",
|
||||
"request_count_for_case": 0
|
||||
},
|
||||
{
|
||||
"case_id": "BQ-002",
|
||||
"raw_question": "Покажи риски по НДС и по закрытию",
|
||||
"validation_passed": true,
|
||||
"message_in_scope": true,
|
||||
"scope_confidence": "high",
|
||||
"contains_multiple_tasks": false,
|
||||
"fragments_total": 1,
|
||||
"in_scope_fragments": 1,
|
||||
"out_of_scope_fragments": 0,
|
||||
"unclear_fragments": 0,
|
||||
"fallback_type": "none",
|
||||
"predicted_route_status": "routed",
|
||||
"expected_route_status": null,
|
||||
"predicted_no_route_reason": null,
|
||||
"expected_no_route_reason": null,
|
||||
"predicted_clarification_required": false,
|
||||
"expected_clarification_required": null,
|
||||
"executable_with_soft_assumptions_fragments": 1,
|
||||
"trace_id": "TNvDsELGZj_UDF",
|
||||
"request_count_for_case": 0
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -0,0 +1,137 @@
|
|||
{
|
||||
"run_id": "eval-_XbcacYQdg",
|
||||
"timestamp": "2026-03-28T14:16:09.590Z",
|
||||
"mode": "single-pass-strict",
|
||||
"use_mock": true,
|
||||
"prompt_version": "normalizer_v2_0_2",
|
||||
"schema_version": "v2_0_2",
|
||||
"dataset": {
|
||||
"source": "inline_raw_questions",
|
||||
"file": null,
|
||||
"raw_questions_count": 3
|
||||
},
|
||||
"cases_total": 3,
|
||||
"metrics": {
|
||||
"schema_validation_pass_rate": 100,
|
||||
"scope_detection_accuracy": null,
|
||||
"scope_in_scope_rate": 33.33,
|
||||
"multi_intent_detected_rate": 0,
|
||||
"clarification_required_rate": 0,
|
||||
"avg_fragments_per_message": 1,
|
||||
"out_of_scope_fragment_rate": 33.33,
|
||||
"routed_fragment_rate": 66.67,
|
||||
"no_route_fragment_rate": 33.33,
|
||||
"route_resolution_accuracy": null,
|
||||
"no_route_precision": null,
|
||||
"false_no_route_rate": null,
|
||||
"execution_state_consistency_rate": 66.67,
|
||||
"executable_with_soft_assumptions_rate": 100,
|
||||
"soft_assumption_used_fragment_rate": 100,
|
||||
"clarification_precision": null,
|
||||
"clarification_recall": null,
|
||||
"false_clarification_rate": null
|
||||
},
|
||||
"budget": {
|
||||
"requests_total": 0,
|
||||
"retries_used": 0
|
||||
},
|
||||
"clarification_eval": {
|
||||
"labeled_cases": 0,
|
||||
"true_positive": 0,
|
||||
"false_positive": 0,
|
||||
"false_negative": 0
|
||||
},
|
||||
"route_eval": {
|
||||
"labeled_cases": 0,
|
||||
"correct_cases": 0,
|
||||
"expected_routed_cases": 0,
|
||||
"no_route_true_positive": 0,
|
||||
"no_route_false_positive": 0
|
||||
},
|
||||
"scope_eval": {
|
||||
"labeled_cases": 0,
|
||||
"correct_cases": 0
|
||||
},
|
||||
"execution_state_eval": {
|
||||
"checks_total": 3,
|
||||
"checks_passed": 2
|
||||
},
|
||||
"route_distribution": {
|
||||
"hybrid_store_plus_live": 1,
|
||||
"no_route": 1,
|
||||
"batch_refresh_then_store": 1
|
||||
},
|
||||
"fallback_distribution": {
|
||||
"none": 1,
|
||||
"out_of_scope": 1,
|
||||
"clarification": 1
|
||||
},
|
||||
"results": [
|
||||
{
|
||||
"case_id": "BQ-001",
|
||||
"raw_question": "Проверь хвосты по поставщикам и разложи цепочку",
|
||||
"validation_passed": true,
|
||||
"message_in_scope": true,
|
||||
"scope_confidence": "high",
|
||||
"contains_multiple_tasks": false,
|
||||
"fragments_total": 1,
|
||||
"in_scope_fragments": 1,
|
||||
"out_of_scope_fragments": 0,
|
||||
"unclear_fragments": 0,
|
||||
"fallback_type": "none",
|
||||
"predicted_route_status": "routed",
|
||||
"expected_route_status": null,
|
||||
"predicted_no_route_reason": null,
|
||||
"expected_no_route_reason": null,
|
||||
"predicted_clarification_required": false,
|
||||
"expected_clarification_required": null,
|
||||
"executable_with_soft_assumptions_fragments": 1,
|
||||
"trace_id": "84nqlo6CKGkRZm",
|
||||
"request_count_for_case": 0
|
||||
},
|
||||
{
|
||||
"case_id": "BQ-002",
|
||||
"raw_question": "Как вообще по ФСБУ",
|
||||
"validation_passed": true,
|
||||
"message_in_scope": false,
|
||||
"scope_confidence": "low",
|
||||
"contains_multiple_tasks": false,
|
||||
"fragments_total": 1,
|
||||
"in_scope_fragments": 0,
|
||||
"out_of_scope_fragments": 1,
|
||||
"unclear_fragments": 0,
|
||||
"fallback_type": "out_of_scope",
|
||||
"predicted_route_status": "no_route",
|
||||
"expected_route_status": null,
|
||||
"predicted_no_route_reason": "out_of_scope",
|
||||
"expected_no_route_reason": null,
|
||||
"predicted_clarification_required": false,
|
||||
"expected_clarification_required": null,
|
||||
"executable_with_soft_assumptions_fragments": 0,
|
||||
"trace_id": "Z-6QeOaELWJ40H",
|
||||
"request_count_for_case": 0
|
||||
},
|
||||
{
|
||||
"case_id": "BQ-003",
|
||||
"raw_question": "Покажи топ рисков за июнь 2020",
|
||||
"validation_passed": true,
|
||||
"message_in_scope": false,
|
||||
"scope_confidence": "low",
|
||||
"contains_multiple_tasks": false,
|
||||
"fragments_total": 1,
|
||||
"in_scope_fragments": 0,
|
||||
"out_of_scope_fragments": 0,
|
||||
"unclear_fragments": 1,
|
||||
"fallback_type": "clarification",
|
||||
"predicted_route_status": "routed",
|
||||
"expected_route_status": null,
|
||||
"predicted_no_route_reason": null,
|
||||
"expected_no_route_reason": null,
|
||||
"predicted_clarification_required": false,
|
||||
"expected_clarification_required": null,
|
||||
"executable_with_soft_assumptions_fragments": 0,
|
||||
"trace_id": "fq5Nw3FI86Ldc1",
|
||||
"request_count_for_case": 0
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -0,0 +1,111 @@
|
|||
{
|
||||
"run_id": "eval-bNqkZdQS9g",
|
||||
"timestamp": "2026-03-28T14:23:37.732Z",
|
||||
"mode": "single-pass-strict",
|
||||
"use_mock": true,
|
||||
"prompt_version": "normalizer_v2_0_2",
|
||||
"schema_version": "v2_0_2",
|
||||
"dataset": {
|
||||
"source": "inline_raw_questions",
|
||||
"file": null,
|
||||
"raw_questions_count": 2
|
||||
},
|
||||
"cases_total": 2,
|
||||
"metrics": {
|
||||
"schema_validation_pass_rate": 100,
|
||||
"scope_detection_accuracy": null,
|
||||
"scope_in_scope_rate": 100,
|
||||
"multi_intent_detected_rate": 0,
|
||||
"clarification_required_rate": 0,
|
||||
"avg_fragments_per_message": 1,
|
||||
"out_of_scope_fragment_rate": 0,
|
||||
"routed_fragment_rate": 100,
|
||||
"no_route_fragment_rate": 0,
|
||||
"route_resolution_accuracy": null,
|
||||
"no_route_precision": null,
|
||||
"false_no_route_rate": null,
|
||||
"execution_state_consistency_rate": 100,
|
||||
"executable_with_soft_assumptions_rate": 100,
|
||||
"soft_assumption_used_fragment_rate": 100,
|
||||
"clarification_precision": null,
|
||||
"clarification_recall": null,
|
||||
"false_clarification_rate": null
|
||||
},
|
||||
"budget": {
|
||||
"requests_total": 0,
|
||||
"retries_used": 0
|
||||
},
|
||||
"clarification_eval": {
|
||||
"labeled_cases": 0,
|
||||
"true_positive": 0,
|
||||
"false_positive": 0,
|
||||
"false_negative": 0
|
||||
},
|
||||
"route_eval": {
|
||||
"labeled_cases": 0,
|
||||
"correct_cases": 0,
|
||||
"expected_routed_cases": 0,
|
||||
"no_route_true_positive": 0,
|
||||
"no_route_false_positive": 0
|
||||
},
|
||||
"scope_eval": {
|
||||
"labeled_cases": 0,
|
||||
"correct_cases": 0
|
||||
},
|
||||
"execution_state_eval": {
|
||||
"checks_total": 2,
|
||||
"checks_passed": 2
|
||||
},
|
||||
"route_distribution": {
|
||||
"hybrid_store_plus_live": 2
|
||||
},
|
||||
"fallback_distribution": {
|
||||
"none": 2
|
||||
},
|
||||
"results": [
|
||||
{
|
||||
"case_id": "BQ-001",
|
||||
"raw_question": "Проверь счет 60 за июнь 2020",
|
||||
"validation_passed": true,
|
||||
"message_in_scope": true,
|
||||
"scope_confidence": "high",
|
||||
"contains_multiple_tasks": false,
|
||||
"fragments_total": 1,
|
||||
"in_scope_fragments": 1,
|
||||
"out_of_scope_fragments": 0,
|
||||
"unclear_fragments": 0,
|
||||
"fallback_type": "none",
|
||||
"predicted_route_status": "routed",
|
||||
"expected_route_status": null,
|
||||
"predicted_no_route_reason": null,
|
||||
"expected_no_route_reason": null,
|
||||
"predicted_clarification_required": false,
|
||||
"expected_clarification_required": null,
|
||||
"executable_with_soft_assumptions_fragments": 1,
|
||||
"trace_id": "AnVPMdNwg-rf3T",
|
||||
"request_count_for_case": 0
|
||||
},
|
||||
{
|
||||
"case_id": "BQ-002",
|
||||
"raw_question": "Покажи риски по счету 97",
|
||||
"validation_passed": true,
|
||||
"message_in_scope": true,
|
||||
"scope_confidence": "high",
|
||||
"contains_multiple_tasks": false,
|
||||
"fragments_total": 1,
|
||||
"in_scope_fragments": 1,
|
||||
"out_of_scope_fragments": 0,
|
||||
"unclear_fragments": 0,
|
||||
"fallback_type": "none",
|
||||
"predicted_route_status": "routed",
|
||||
"expected_route_status": null,
|
||||
"predicted_no_route_reason": null,
|
||||
"expected_no_route_reason": null,
|
||||
"predicted_clarification_required": false,
|
||||
"expected_clarification_required": null,
|
||||
"executable_with_soft_assumptions_fragments": 1,
|
||||
"trace_id": "utvJRunZgTZrKu",
|
||||
"request_count_for_case": 0
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -0,0 +1,137 @@
|
|||
{
|
||||
"run_id": "eval-c2aEXxAGeh",
|
||||
"timestamp": "2026-03-28T14:47:43.841Z",
|
||||
"mode": "single-pass-strict",
|
||||
"use_mock": true,
|
||||
"prompt_version": "normalizer_v2_0_2",
|
||||
"schema_version": "v2_0_2",
|
||||
"dataset": {
|
||||
"source": "inline_raw_questions",
|
||||
"file": null,
|
||||
"raw_questions_count": 3
|
||||
},
|
||||
"cases_total": 3,
|
||||
"metrics": {
|
||||
"schema_validation_pass_rate": 100,
|
||||
"scope_detection_accuracy": null,
|
||||
"scope_in_scope_rate": 33.33,
|
||||
"multi_intent_detected_rate": 0,
|
||||
"clarification_required_rate": 0,
|
||||
"avg_fragments_per_message": 1,
|
||||
"out_of_scope_fragment_rate": 33.33,
|
||||
"routed_fragment_rate": 66.67,
|
||||
"no_route_fragment_rate": 33.33,
|
||||
"route_resolution_accuracy": null,
|
||||
"no_route_precision": null,
|
||||
"false_no_route_rate": null,
|
||||
"execution_state_consistency_rate": 66.67,
|
||||
"executable_with_soft_assumptions_rate": 100,
|
||||
"soft_assumption_used_fragment_rate": 100,
|
||||
"clarification_precision": null,
|
||||
"clarification_recall": null,
|
||||
"false_clarification_rate": null
|
||||
},
|
||||
"budget": {
|
||||
"requests_total": 0,
|
||||
"retries_used": 0
|
||||
},
|
||||
"clarification_eval": {
|
||||
"labeled_cases": 0,
|
||||
"true_positive": 0,
|
||||
"false_positive": 0,
|
||||
"false_negative": 0
|
||||
},
|
||||
"route_eval": {
|
||||
"labeled_cases": 0,
|
||||
"correct_cases": 0,
|
||||
"expected_routed_cases": 0,
|
||||
"no_route_true_positive": 0,
|
||||
"no_route_false_positive": 0
|
||||
},
|
||||
"scope_eval": {
|
||||
"labeled_cases": 0,
|
||||
"correct_cases": 0
|
||||
},
|
||||
"execution_state_eval": {
|
||||
"checks_total": 3,
|
||||
"checks_passed": 2
|
||||
},
|
||||
"route_distribution": {
|
||||
"hybrid_store_plus_live": 1,
|
||||
"no_route": 1,
|
||||
"batch_refresh_then_store": 1
|
||||
},
|
||||
"fallback_distribution": {
|
||||
"none": 1,
|
||||
"out_of_scope": 1,
|
||||
"clarification": 1
|
||||
},
|
||||
"results": [
|
||||
{
|
||||
"case_id": "BQ-001",
|
||||
"raw_question": "Проверь хвосты по поставщикам и разложи цепочку",
|
||||
"validation_passed": true,
|
||||
"message_in_scope": true,
|
||||
"scope_confidence": "high",
|
||||
"contains_multiple_tasks": false,
|
||||
"fragments_total": 1,
|
||||
"in_scope_fragments": 1,
|
||||
"out_of_scope_fragments": 0,
|
||||
"unclear_fragments": 0,
|
||||
"fallback_type": "none",
|
||||
"predicted_route_status": "routed",
|
||||
"expected_route_status": null,
|
||||
"predicted_no_route_reason": null,
|
||||
"expected_no_route_reason": null,
|
||||
"predicted_clarification_required": false,
|
||||
"expected_clarification_required": null,
|
||||
"executable_with_soft_assumptions_fragments": 1,
|
||||
"trace_id": "r6cJKlGb3sAo1h",
|
||||
"request_count_for_case": 0
|
||||
},
|
||||
{
|
||||
"case_id": "BQ-002",
|
||||
"raw_question": "Как вообще по ФСБУ",
|
||||
"validation_passed": true,
|
||||
"message_in_scope": false,
|
||||
"scope_confidence": "low",
|
||||
"contains_multiple_tasks": false,
|
||||
"fragments_total": 1,
|
||||
"in_scope_fragments": 0,
|
||||
"out_of_scope_fragments": 1,
|
||||
"unclear_fragments": 0,
|
||||
"fallback_type": "out_of_scope",
|
||||
"predicted_route_status": "no_route",
|
||||
"expected_route_status": null,
|
||||
"predicted_no_route_reason": "out_of_scope",
|
||||
"expected_no_route_reason": null,
|
||||
"predicted_clarification_required": false,
|
||||
"expected_clarification_required": null,
|
||||
"executable_with_soft_assumptions_fragments": 0,
|
||||
"trace_id": "RwtT5cfOm8EQzm",
|
||||
"request_count_for_case": 0
|
||||
},
|
||||
{
|
||||
"case_id": "BQ-003",
|
||||
"raw_question": "Покажи топ рисков за июнь 2020",
|
||||
"validation_passed": true,
|
||||
"message_in_scope": false,
|
||||
"scope_confidence": "low",
|
||||
"contains_multiple_tasks": false,
|
||||
"fragments_total": 1,
|
||||
"in_scope_fragments": 0,
|
||||
"out_of_scope_fragments": 0,
|
||||
"unclear_fragments": 1,
|
||||
"fallback_type": "clarification",
|
||||
"predicted_route_status": "routed",
|
||||
"expected_route_status": null,
|
||||
"predicted_no_route_reason": null,
|
||||
"expected_no_route_reason": null,
|
||||
"predicted_clarification_required": false,
|
||||
"expected_clarification_required": null,
|
||||
"executable_with_soft_assumptions_fragments": 0,
|
||||
"trace_id": "aTubcVPJxRPKKy",
|
||||
"request_count_for_case": 0
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -0,0 +1,111 @@
|
|||
{
|
||||
"run_id": "eval-giqMXxi5Cr",
|
||||
"timestamp": "2026-03-28T14:16:11.355Z",
|
||||
"mode": "single-pass-strict",
|
||||
"use_mock": true,
|
||||
"prompt_version": "normalizer_v2_0_2",
|
||||
"schema_version": "v2_0_2",
|
||||
"dataset": {
|
||||
"source": "inline_raw_questions",
|
||||
"file": null,
|
||||
"raw_questions_count": 2
|
||||
},
|
||||
"cases_total": 2,
|
||||
"metrics": {
|
||||
"schema_validation_pass_rate": 100,
|
||||
"scope_detection_accuracy": null,
|
||||
"scope_in_scope_rate": 100,
|
||||
"multi_intent_detected_rate": 0,
|
||||
"clarification_required_rate": 0,
|
||||
"avg_fragments_per_message": 1,
|
||||
"out_of_scope_fragment_rate": 0,
|
||||
"routed_fragment_rate": 100,
|
||||
"no_route_fragment_rate": 0,
|
||||
"route_resolution_accuracy": null,
|
||||
"no_route_precision": null,
|
||||
"false_no_route_rate": null,
|
||||
"execution_state_consistency_rate": 100,
|
||||
"executable_with_soft_assumptions_rate": 100,
|
||||
"soft_assumption_used_fragment_rate": 100,
|
||||
"clarification_precision": null,
|
||||
"clarification_recall": null,
|
||||
"false_clarification_rate": null
|
||||
},
|
||||
"budget": {
|
||||
"requests_total": 0,
|
||||
"retries_used": 0
|
||||
},
|
||||
"clarification_eval": {
|
||||
"labeled_cases": 0,
|
||||
"true_positive": 0,
|
||||
"false_positive": 0,
|
||||
"false_negative": 0
|
||||
},
|
||||
"route_eval": {
|
||||
"labeled_cases": 0,
|
||||
"correct_cases": 0,
|
||||
"expected_routed_cases": 0,
|
||||
"no_route_true_positive": 0,
|
||||
"no_route_false_positive": 0
|
||||
},
|
||||
"scope_eval": {
|
||||
"labeled_cases": 0,
|
||||
"correct_cases": 0
|
||||
},
|
||||
"execution_state_eval": {
|
||||
"checks_total": 2,
|
||||
"checks_passed": 2
|
||||
},
|
||||
"route_distribution": {
|
||||
"hybrid_store_plus_live": 2
|
||||
},
|
||||
"fallback_distribution": {
|
||||
"none": 2
|
||||
},
|
||||
"results": [
|
||||
{
|
||||
"case_id": "BQ-001",
|
||||
"raw_question": "Проверь счет 60 за июнь 2020",
|
||||
"validation_passed": true,
|
||||
"message_in_scope": true,
|
||||
"scope_confidence": "high",
|
||||
"contains_multiple_tasks": false,
|
||||
"fragments_total": 1,
|
||||
"in_scope_fragments": 1,
|
||||
"out_of_scope_fragments": 0,
|
||||
"unclear_fragments": 0,
|
||||
"fallback_type": "none",
|
||||
"predicted_route_status": "routed",
|
||||
"expected_route_status": null,
|
||||
"predicted_no_route_reason": null,
|
||||
"expected_no_route_reason": null,
|
||||
"predicted_clarification_required": false,
|
||||
"expected_clarification_required": null,
|
||||
"executable_with_soft_assumptions_fragments": 1,
|
||||
"trace_id": "lqgRK3iNNLJBPj",
|
||||
"request_count_for_case": 0
|
||||
},
|
||||
{
|
||||
"case_id": "BQ-002",
|
||||
"raw_question": "Покажи риски по НДС и по закрытию",
|
||||
"validation_passed": true,
|
||||
"message_in_scope": true,
|
||||
"scope_confidence": "high",
|
||||
"contains_multiple_tasks": false,
|
||||
"fragments_total": 1,
|
||||
"in_scope_fragments": 1,
|
||||
"out_of_scope_fragments": 0,
|
||||
"unclear_fragments": 0,
|
||||
"fallback_type": "none",
|
||||
"predicted_route_status": "routed",
|
||||
"expected_route_status": null,
|
||||
"predicted_no_route_reason": null,
|
||||
"expected_no_route_reason": null,
|
||||
"predicted_clarification_required": false,
|
||||
"expected_clarification_required": null,
|
||||
"executable_with_soft_assumptions_fragments": 1,
|
||||
"trace_id": "8F-6dbOWrrZ8DS",
|
||||
"request_count_for_case": 0
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -0,0 +1,111 @@
|
|||
{
|
||||
"run_id": "eval-rbxdQ0Yd-g",
|
||||
"timestamp": "2026-03-28T14:47:45.752Z",
|
||||
"mode": "single-pass-strict",
|
||||
"use_mock": true,
|
||||
"prompt_version": "normalizer_v2_0_2",
|
||||
"schema_version": "v2_0_2",
|
||||
"dataset": {
|
||||
"source": "inline_raw_questions",
|
||||
"file": null,
|
||||
"raw_questions_count": 2
|
||||
},
|
||||
"cases_total": 2,
|
||||
"metrics": {
|
||||
"schema_validation_pass_rate": 100,
|
||||
"scope_detection_accuracy": null,
|
||||
"scope_in_scope_rate": 100,
|
||||
"multi_intent_detected_rate": 0,
|
||||
"clarification_required_rate": 0,
|
||||
"avg_fragments_per_message": 1,
|
||||
"out_of_scope_fragment_rate": 0,
|
||||
"routed_fragment_rate": 100,
|
||||
"no_route_fragment_rate": 0,
|
||||
"route_resolution_accuracy": null,
|
||||
"no_route_precision": null,
|
||||
"false_no_route_rate": null,
|
||||
"execution_state_consistency_rate": 100,
|
||||
"executable_with_soft_assumptions_rate": 100,
|
||||
"soft_assumption_used_fragment_rate": 100,
|
||||
"clarification_precision": null,
|
||||
"clarification_recall": null,
|
||||
"false_clarification_rate": null
|
||||
},
|
||||
"budget": {
|
||||
"requests_total": 0,
|
||||
"retries_used": 0
|
||||
},
|
||||
"clarification_eval": {
|
||||
"labeled_cases": 0,
|
||||
"true_positive": 0,
|
||||
"false_positive": 0,
|
||||
"false_negative": 0
|
||||
},
|
||||
"route_eval": {
|
||||
"labeled_cases": 0,
|
||||
"correct_cases": 0,
|
||||
"expected_routed_cases": 0,
|
||||
"no_route_true_positive": 0,
|
||||
"no_route_false_positive": 0
|
||||
},
|
||||
"scope_eval": {
|
||||
"labeled_cases": 0,
|
||||
"correct_cases": 0
|
||||
},
|
||||
"execution_state_eval": {
|
||||
"checks_total": 2,
|
||||
"checks_passed": 2
|
||||
},
|
||||
"route_distribution": {
|
||||
"hybrid_store_plus_live": 2
|
||||
},
|
||||
"fallback_distribution": {
|
||||
"none": 2
|
||||
},
|
||||
"results": [
|
||||
{
|
||||
"case_id": "BQ-001",
|
||||
"raw_question": "Проверь счет 60 за июнь 2020",
|
||||
"validation_passed": true,
|
||||
"message_in_scope": true,
|
||||
"scope_confidence": "high",
|
||||
"contains_multiple_tasks": false,
|
||||
"fragments_total": 1,
|
||||
"in_scope_fragments": 1,
|
||||
"out_of_scope_fragments": 0,
|
||||
"unclear_fragments": 0,
|
||||
"fallback_type": "none",
|
||||
"predicted_route_status": "routed",
|
||||
"expected_route_status": null,
|
||||
"predicted_no_route_reason": null,
|
||||
"expected_no_route_reason": null,
|
||||
"predicted_clarification_required": false,
|
||||
"expected_clarification_required": null,
|
||||
"executable_with_soft_assumptions_fragments": 1,
|
||||
"trace_id": "KRtUQA4OmuvZXo",
|
||||
"request_count_for_case": 0
|
||||
},
|
||||
{
|
||||
"case_id": "BQ-002",
|
||||
"raw_question": "Покажи риски по НДС и по закрытию",
|
||||
"validation_passed": true,
|
||||
"message_in_scope": true,
|
||||
"scope_confidence": "high",
|
||||
"contains_multiple_tasks": false,
|
||||
"fragments_total": 1,
|
||||
"in_scope_fragments": 1,
|
||||
"out_of_scope_fragments": 0,
|
||||
"unclear_fragments": 0,
|
||||
"fallback_type": "none",
|
||||
"predicted_route_status": "routed",
|
||||
"expected_route_status": null,
|
||||
"predicted_no_route_reason": null,
|
||||
"expected_no_route_reason": null,
|
||||
"predicted_clarification_required": false,
|
||||
"expected_clarification_required": null,
|
||||
"executable_with_soft_assumptions_fragments": 1,
|
||||
"trace_id": "6HH1OsFaNsR6hO",
|
||||
"request_count_for_case": 0
|
||||
}
|
||||
]
|
||||
}
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
File diff suppressed because one or more lines are too long
|
|
@ -4,7 +4,7 @@
|
|||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>NDC AI Normalizer Playground</title>
|
||||
<script type="module" crossorigin src="/assets/index-HMlzOgoV.js"></script>
|
||||
<script type="module" crossorigin src="/assets/index-PA_66ng-.js"></script>
|
||||
<link rel="stylesheet" crossorigin href="/assets/index-Ch7jCAii.css">
|
||||
</head>
|
||||
<body>
|
||||
|
|
|
|||
|
|
@ -41,7 +41,11 @@ function stringifyDebug(value: unknown): string {
|
|||
}
|
||||
}
|
||||
|
||||
function buildConversationExport(sessionId: string, conversation: AssistantConversationItem[]): string {
|
||||
function buildConversationExport(
|
||||
sessionId: string,
|
||||
conversation: AssistantConversationItem[],
|
||||
includeDebugPayload = false
|
||||
): string {
|
||||
const lines: string[] = [];
|
||||
lines.push("# Assistant conversation export");
|
||||
lines.push(`session_id: ${sessionId || "n/a"}`);
|
||||
|
|
@ -61,7 +65,7 @@ function buildConversationExport(sessionId: string, conversation: AssistantConve
|
|||
lines.push(item.text || "(empty)");
|
||||
lines.push("");
|
||||
|
||||
if (item.role === "assistant" && item.debug) {
|
||||
if (includeDebugPayload && item.role === "assistant" && item.debug) {
|
||||
lines.push("### debug_payload_json");
|
||||
lines.push("```json");
|
||||
lines.push(stringifyDebug(item.debug));
|
||||
|
|
@ -143,7 +147,8 @@ export function AssistantPanel({
|
|||
if (conversation.length === 0) {
|
||||
return;
|
||||
}
|
||||
const exportText = buildConversationExport(sessionId, conversation);
|
||||
// Copy full run context for diagnostics (including debug payload blocks).
|
||||
const exportText = buildConversationExport(sessionId, conversation, true);
|
||||
const copied = await copyTextToClipboard(exportText);
|
||||
setCopyState(copied ? "success" : "error");
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue