Stage 3 Wave 1: lifecycle-слой встроен в pipeline

This commit is contained in:
2026-03-26 15:48:38 +03:00
parent 96353cfd48
commit d0b842adb0
59 changed files with 5132 additions and 154 deletions
+3 -1
View File
@@ -3,7 +3,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.ARCH_EXPORT_2020_DIR = exports.SCHEMAS_DIR = exports.EVAL_DATASETS_DIR = exports.REPORTS_DIR = exports.PROMPTS_DIR = exports.ASSISTANT_SESSIONS_DIR = exports.EVAL_CASES_DIR = exports.PRESETS_DIR = exports.TRACES_DIR = exports.DATA_DIR = exports.FEATURE_ASSISTANT_STAGE2_EVAL_V1 = exports.FEATURE_ASSISTANT_PROBLEM_UNIT_CONTINUITY_V1 = exports.FEATURE_ASSISTANT_PROBLEM_CENTRIC_ANSWER_V1 = exports.FEATURE_ASSISTANT_PROBLEM_UNITS_V1 = exports.FEATURE_ASSISTANT_ACCOUNTANT_EVAL_V1 = exports.FEATURE_ASSISTANT_ANSWER_POLICY_V11 = exports.FEATURE_ASSISTANT_ANTI_GENERIC_RANKING_GUARD_V1 = exports.FEATURE_ASSISTANT_MIN_EVIDENCE_GATE_V1 = exports.FEATURE_ASSISTANT_BROAD_GUARD_V1 = exports.FEATURE_ASSISTANT_EVIDENCE_ENRICHMENT_V1 = exports.FEATURE_ASSISTANT_STATE_FOLLOWUP_BINDING_V1 = exports.FEATURE_ASSISTANT_CONTRACTS_V11 = exports.FEATURE_ASSISTANT_INVESTIGATION_STATE_V1 = exports.DEFAULT_PROMPT_VERSION = exports.DEFAULT_MAX_OUTPUT_TOKENS = exports.DEFAULT_TEMPERATURE = exports.DEFAULT_MODEL = exports.DEFAULT_OPENAI_BASE_URL = exports.TIMEZONE = exports.PORT = exports.MODULE_ROOT = exports.BACKEND_ROOT = void 0;
exports.ARCH_EXPORT_2020_DIR = exports.SCHEMAS_DIR = exports.EVAL_DATASETS_DIR = exports.REPORTS_DIR = exports.PROMPTS_DIR = exports.ASSISTANT_SESSIONS_DIR = exports.EVAL_CASES_DIR = exports.PRESETS_DIR = exports.TRACES_DIR = exports.DATA_DIR = exports.FEATURE_ASSISTANT_LIFECYCLE_ANSWER_V1 = exports.FEATURE_ASSISTANT_LIFECYCLE_RUNTIME_V1 = exports.FEATURE_ASSISTANT_STAGE2_EVAL_V1 = exports.FEATURE_ASSISTANT_PROBLEM_UNIT_CONTINUITY_V1 = exports.FEATURE_ASSISTANT_PROBLEM_CENTRIC_ANSWER_V1 = exports.FEATURE_ASSISTANT_PROBLEM_UNITS_V1 = exports.FEATURE_ASSISTANT_ACCOUNTANT_EVAL_V1 = exports.FEATURE_ASSISTANT_ANSWER_POLICY_V11 = exports.FEATURE_ASSISTANT_ANTI_GENERIC_RANKING_GUARD_V1 = exports.FEATURE_ASSISTANT_MIN_EVIDENCE_GATE_V1 = exports.FEATURE_ASSISTANT_BROAD_GUARD_V1 = exports.FEATURE_ASSISTANT_EVIDENCE_ENRICHMENT_V1 = exports.FEATURE_ASSISTANT_STATE_FOLLOWUP_BINDING_V1 = exports.FEATURE_ASSISTANT_CONTRACTS_V11 = exports.FEATURE_ASSISTANT_INVESTIGATION_STATE_V1 = exports.DEFAULT_PROMPT_VERSION = exports.DEFAULT_MAX_OUTPUT_TOKENS = exports.DEFAULT_TEMPERATURE = exports.DEFAULT_MODEL = exports.DEFAULT_OPENAI_BASE_URL = exports.TIMEZONE = exports.PORT = exports.MODULE_ROOT = exports.BACKEND_ROOT = void 0;
const path_1 = __importDefault(require("path"));
exports.BACKEND_ROOT = path_1.default.resolve(__dirname, "..");
exports.MODULE_ROOT = path_1.default.resolve(exports.BACKEND_ROOT, "..");
@@ -34,6 +34,8 @@ exports.FEATURE_ASSISTANT_PROBLEM_UNITS_V1 = toBooleanFlag(process.env.FEATURE_A
exports.FEATURE_ASSISTANT_PROBLEM_CENTRIC_ANSWER_V1 = toBooleanFlag(process.env.FEATURE_ASSISTANT_PROBLEM_CENTRIC_ANSWER_V1, false);
exports.FEATURE_ASSISTANT_PROBLEM_UNIT_CONTINUITY_V1 = toBooleanFlag(process.env.FEATURE_ASSISTANT_PROBLEM_UNIT_CONTINUITY_V1, false);
exports.FEATURE_ASSISTANT_STAGE2_EVAL_V1 = toBooleanFlag(process.env.FEATURE_ASSISTANT_STAGE2_EVAL_V1, false);
exports.FEATURE_ASSISTANT_LIFECYCLE_RUNTIME_V1 = toBooleanFlag(process.env.FEATURE_ASSISTANT_LIFECYCLE_RUNTIME_V1, false);
exports.FEATURE_ASSISTANT_LIFECYCLE_ANSWER_V1 = toBooleanFlag(process.env.FEATURE_ASSISTANT_LIFECYCLE_ANSWER_V1, false);
exports.DATA_DIR = process.env.DATA_DIR ?? path_1.default.resolve(exports.MODULE_ROOT, "data");
exports.TRACES_DIR = path_1.default.resolve(exports.DATA_DIR, "traces");
exports.PRESETS_DIR = path_1.default.resolve(exports.DATA_DIR, "presets");
+96 -7
View File
@@ -282,6 +282,59 @@ function formatAffectedScope(unit) {
}
return scopeParts.join("; ");
}
function formatLifecycleScope(unit) {
if (!unit.lifecycle_domain) {
return null;
}
const parts = [`domain=${unit.lifecycle_domain}`];
if (unit.current_lifecycle_state) {
parts.push(`current=${unit.current_lifecycle_state}`);
}
if (unit.expected_lifecycle_state) {
parts.push(`expected=${unit.expected_lifecycle_state}`);
}
if (unit.lifecycle_defect_type) {
parts.push(`defect=${unit.lifecycle_defect_type}`);
}
if (unit.missing_transition) {
parts.push(`missing_transition=${unit.missing_transition}`);
}
if (unit.invalid_transition) {
parts.push(`invalid_transition=${unit.invalid_transition}`);
}
if (unit.stale_duration) {
parts.push(`stale_duration=${unit.stale_duration}`);
}
return parts.join(", ");
}
function rankProblemUnitsForAnswer(units, lifecycleAnswerEnabled) {
if (!lifecycleAnswerEnabled) {
return units.slice().sort((left, right) => {
const severityDiff = right.severity.score - left.severity.score;
if (severityDiff !== 0)
return severityDiff;
return right.confidence.score - left.confidence.score;
});
}
return units.slice().sort((left, right) => {
const lifecycleRankDiff = (right.lifecycle_ranking_score ?? 0) - (left.lifecycle_ranking_score ?? 0);
if (lifecycleRankDiff !== 0)
return lifecycleRankDiff;
const lifecycleConfidenceDiff = (right.lifecycle_confidence?.score ?? 0) - (left.lifecycle_confidence?.score ?? 0);
if (lifecycleConfidenceDiff !== 0)
return lifecycleConfidenceDiff;
const severityDiff = right.severity.score - left.severity.score;
if (severityDiff !== 0)
return severityDiff;
return right.confidence.score - left.confidence.score;
});
}
function hasLifecycleResolution(units) {
return units.some((unit) => Boolean(unit.lifecycle_domain) &&
Boolean(unit.current_lifecycle_state) &&
Boolean(unit.expected_lifecycle_state) &&
Boolean(unit.lifecycle_defect_type));
}
function buildProblemCentricActions(input) {
const actions = [];
const unitTypes = new Set(input.units.map((item) => item.problem_unit_type));
@@ -300,6 +353,17 @@ function buildProblemCentricActions(input) {
if (unitTypes.has("lifecycle_anomaly_node")) {
actions.push("Проверьте lifecycle объекта: ожидаемый этап не должен оставаться в partially_linked состоянии.");
}
for (const unit of input.units) {
if (unit.lifecycle_defect_type === "stale_active_state") {
actions.push("Проверьте, почему объект завис: ожидаемый переход не должен оставаться в активной стадии.");
}
if (unit.lifecycle_defect_type === "misclosed_state") {
actions.push("Проверьте закрывающий документ и проводки: закрытие может быть формальным, но некорректным по пути.");
}
if (unit.lifecycle_defect_type === "cross_branch_state_conflict") {
actions.push("Сверьте бухгалтерскую и смежную ветки (например, НДС/расчеты): обнаружен межконтурный конфликт состояния.");
}
}
if (input.mode === "clarification_required") {
if (input.missingAnchors.period) {
actions.push("Уточните период проверки, чтобы зафиксировать границы проблемного контура.");
@@ -657,6 +721,12 @@ function buildDirectAnswer(input) {
return "Не удалось сформировать обоснованный ответ; нужно уточнение запроса.";
}
function buildProblemCentricAnswerSummary(input) {
if (input.lifecycleEnriched && input.summary?.lifecycle_enriched_units && input.summary.lifecycle_enriched_units > 0) {
if (input.mode === "clarification_required") {
return "Выявлены lifecycle-дефекты, но для надежного вывода требуется уточнение предметных якорей.";
}
return `Сформирован lifecycle-aware problem срез: выделено ${input.summary.lifecycle_enriched_units} lifecycle-узлов с приоритетом по дефектам перехода.`;
}
if (input.mode === "clarification_required") {
return "Выявлены проблемные кластеры, но для надежного вывода требуется предметное уточнение фокуса.";
}
@@ -673,10 +743,23 @@ function buildProblemCentricDirectAnswer(input) {
? "Обнаружены проблемные зоны, но без уточнения якорей сильный factual-вывод преждевременен."
: input.weakUnits
? "Выделены проблемные зоны с ограниченной надежностью; вывод дан в ограниченном режиме."
: "Выделены ключевые проблемные зоны и их влияние на учетный контур.";
: input.lifecycleAnswerEnabled && hasLifecycleResolution(input.units)
? "Выделены lifecycle-проблемы: определены текущие/ожидаемые стадии и тип нарушения перехода."
: "Выделены ключевые проблемные зоны и их влияние на учетный контур.";
const unitLines = input.units.map((unit) => {
const scope = formatAffectedScope(unit);
return `- ${unit.title}: ${unit.business_defect_class}; ${scope}; severity=${unit.severity.grade}, confidence=${unit.confidence.grade}.`;
const lifecycleScope = input.lifecycleAnswerEnabled ? formatLifecycleScope(unit) : null;
const lifecycleInterpretation = input.lifecycleAnswerEnabled ? unit.business_lifecycle_interpretation : null;
const segments = [
`${unit.title}: ${unit.business_defect_class}`,
scope,
lifecycleScope,
lifecycleInterpretation,
`severity=${unit.severity.grade}`,
`confidence=${unit.confidence.grade}`,
unit.lifecycle_confidence ? `lifecycle_confidence=${unit.lifecycle_confidence.grade}` : null
].filter((item) => Boolean(item));
return `- ${segments.join("; ")}.`;
});
if (unitLines.length === 0) {
return `${lead}\nПроблемные кластеры не удалось детализировать в текущем срезе.`;
@@ -685,6 +768,7 @@ function buildProblemCentricDirectAnswer(input) {
}
function buildProblemCentricAnswerStructure(input) {
const weakUnits = input.selectedUnits.every((item) => item.confidence.grade === "low");
const lifecycleEnriched = input.lifecycleAnswerEnabled && hasLifecycleResolution(input.selectedUnits);
const unitMechanismNotes = uniqueStrings(input.selectedUnits
.map((item) => item.mechanism_summary)
.filter((item) => typeof item === "string" && item.trim().length > 0), 6);
@@ -724,12 +808,14 @@ function buildProblemCentricAnswerStructure(input) {
answer_summary: buildProblemCentricAnswerSummary({
mode: input.mode,
weakUnits,
summary: input.problemSummary
summary: input.problemSummary,
lifecycleEnriched
}),
direct_answer: buildProblemCentricDirectAnswer({
mode: input.mode,
units: input.selectedUnits,
weakUnits
weakUnits,
lifecycleAnswerEnabled: input.lifecycleAnswerEnabled
}),
mechanism_block: {
status: mechanismStatus,
@@ -836,10 +922,11 @@ function composeAssistantAnswerV11(input) {
const mechanismNotes = uniqueStrings(evidenceItems
.map((item) => item.mechanism_note)
.filter((item) => typeof item === "string" && item.trim().length > 0), 6);
const lifecycleAnswerEnabled = Boolean(input.enableLifecycleAnswerV1);
const problemUnits = flattenProblemUnits(input.retrievalResults);
const problemUnitSummary = selectProblemUnitSummary(input.retrievalResults);
const problemHeavyUnits = problemUnits.filter((item) => PROBLEM_HEAVY_TYPES.has(item.problem_unit_type));
const selectedProblemUnits = problemHeavyUnits.slice(0, 4);
const selectedProblemUnits = rankProblemUnitsForAnswer(problemHeavyUnits, lifecycleAnswerEnabled).slice(0, 4);
const claimEvidenceLinks = buildClaimEvidenceLinks(input.retrievalResults);
const aggregateEvidenceConfidence = aggregateConfidence(input.retrievalResults, evidenceItems);
const lowConfidenceSignals = evidenceItems.filter((item) => item.confidence === "low").length;
@@ -903,8 +990,10 @@ function composeAssistantAnswerV11(input) {
groundingCheck: input.groundingCheck,
retrievalResults: input.retrievalResults,
missingAnchors,
coverageReport: input.coverageReport
coverageReport: input.coverageReport,
lifecycleAnswerEnabled
});
const lifecycleModeActive = lifecycleAnswerEnabled && hasLifecycleResolution(selectedProblemUnits);
return {
assistant_reply: renderPolicyReply(problemCentricStructure),
fallback_type: decision.fallback_type,
@@ -912,7 +1001,7 @@ function composeAssistantAnswerV11(input) {
answer_structure_v11: problemCentricStructure,
problem_centric_answer_applied: true,
problem_units_used_count: selectedProblemUnits.length,
problem_answer_mode: "stage2_problem_centric_v1",
problem_answer_mode: lifecycleModeActive ? "stage3_lifecycle_aware_v1" : "stage2_problem_centric_v1",
problem_unit_ids_used: selectedProblemUnits.map((item) => item.problem_unit_id)
};
}
+2 -1
View File
@@ -968,7 +968,8 @@ class AssistantService {
coverageReport: coverageEvaluation.coverage,
groundingCheck,
enableAnswerPolicyV11: config_1.FEATURE_ASSISTANT_ANSWER_POLICY_V11,
enableProblemCentricAnswerV1: config_1.FEATURE_ASSISTANT_PROBLEM_CENTRIC_ANSWER_V1
enableProblemCentricAnswerV1: config_1.FEATURE_ASSISTANT_PROBLEM_CENTRIC_ANSWER_V1,
enableLifecycleAnswerV1: config_1.FEATURE_ASSISTANT_LIFECYCLE_ANSWER_V1
});
const answerStructureV11 = config_1.FEATURE_ASSISTANT_CONTRACTS_V11
? config_1.FEATURE_ASSISTANT_ANSWER_POLICY_V11 && composition.answer_structure_v11
+824
View File
@@ -0,0 +1,824 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.LifecycleRegistry = void 0;
exports.classifyLifecycleDefect = classifyLifecycleDefect;
exports.resolveLifecycle = resolveLifecycle;
exports.enrichProblemUnitLifecycle = enrichProblemUnitLifecycle;
exports.rankLifecycleProblemUnits = rankLifecycleProblemUnits;
const stage3Lifecycle_1 = require("../types/stage3Lifecycle");
function clampUnitScore(value) {
if (!Number.isFinite(value)) {
return 0;
}
if (value <= 0)
return 0;
if (value >= 1)
return 1;
return Number(value.toFixed(2));
}
function lifecycleConfidenceGrade(score) {
if (score >= 0.75)
return "high";
if (score >= 0.45)
return "medium";
return "low";
}
function uniqueStrings(values, limit = 16) {
return Array.from(new Set(values.map((item) => item.trim()).filter(Boolean))).slice(0, limit);
}
function includesAny(source, patterns) {
return patterns.some((pattern) => pattern.test(source));
}
function hasToken(values, pattern) {
return values.some((value) => pattern.test(value));
}
function defaultExpectedState(domain) {
if (domain === "bank_settlement")
return "settlement_closed";
if (domain === "customer_settlement")
return "receivable_closed";
if (domain === "deferred_expense")
return "fully_written_off";
if (domain === "fixed_asset")
return "depreciation_active";
if (domain === "vat_flow")
return "vat_deducted";
return "close_completed";
}
const LIFECYCLE_DOMAIN_MODELS = {
bank_settlement: {
schema_version: stage3Lifecycle_1.LIFECYCLE_MODEL_SCHEMA_VERSION,
lifecycle_domain: "bank_settlement",
lifecycle_object_types: ["payment_settlement_link"],
states: [
{
state_code: "initiated_payment",
state_label: "Платеж инициирован",
state_class: "initial",
entry_conditions: ["payment_order_created"],
exit_conditions: ["bank_recorded"],
is_terminal: false,
is_problematic: false,
business_meaning: "Есть инициирование платежа."
},
{
state_code: "bank_recorded",
state_label: "Платеж отражен банком",
state_class: "active",
entry_conditions: ["bank_statement_recorded"],
exit_conditions: ["settlement_linked"],
is_terminal: false,
is_problematic: false,
business_meaning: "Движение денег зафиксировано, ожидается расчетное закрытие."
},
{
state_code: "settlement_closed",
state_label: "Расчет закрыт",
state_class: "terminal",
entry_conditions: ["payment_to_settlement_linked"],
exit_conditions: [],
is_terminal: true,
is_problematic: false,
business_meaning: "Платеж доведен до расчетного результата."
},
{
state_code: "stale_unlinked_payment",
state_label: "Платеж завис без закрытия",
state_class: "problematic",
entry_conditions: ["bank_recorded", "missing_link"],
exit_conditions: ["settlement_closed"],
is_terminal: false,
is_problematic: true,
business_meaning: "Платеж отражен, но ожидаемая связь по расчету не завершена."
},
{
state_code: "misclosed_payment",
state_label: "Платеж закрыт некорректно",
state_class: "problematic",
entry_conditions: ["wrong_document_type_or_posting_mismatch"],
exit_conditions: ["settlement_closed"],
is_terminal: false,
is_problematic: true,
business_meaning: "Формальное закрытие есть, но путь закрытия неверный."
}
],
transitions: [
{
from_state: "initiated_payment",
to_state: "bank_recorded",
transition_type: "expected",
required_evidence: ["bank_statement_recorded"],
optional_evidence: ["payment_order"],
forbidden_conditions: [],
business_meaning: "Платеж должен появиться во выписке."
},
{
from_state: "bank_recorded",
to_state: "settlement_closed",
transition_type: "expected",
required_evidence: ["payment_to_settlement_link"],
optional_evidence: ["document_to_posting"],
forbidden_conditions: ["wrong_document_type"],
business_meaning: "После выписки должен закрываться расчет."
}
],
defects: []
},
customer_settlement: {
schema_version: stage3Lifecycle_1.LIFECYCLE_MODEL_SCHEMA_VERSION,
lifecycle_domain: "customer_settlement",
lifecycle_object_types: ["receivable_chain"],
states: [
{
state_code: "invoice_issued",
state_label: "Реализация отражена",
state_class: "initial",
entry_conditions: ["realization_document_exists"],
exit_conditions: ["payment_recorded"],
is_terminal: false,
is_problematic: false,
business_meaning: "Возникла дебиторская позиция."
},
{
state_code: "payment_recorded",
state_label: "Оплата отражена",
state_class: "active",
entry_conditions: ["payment_document_exists"],
exit_conditions: ["receivable_closed"],
is_terminal: false,
is_problematic: false,
business_meaning: "Оплата есть, ожидается корректное закрытие."
},
{
state_code: "receivable_closed",
state_label: "Дебиторка закрыта",
state_class: "terminal",
entry_conditions: ["closing_document_linked"],
exit_conditions: [],
is_terminal: true,
is_problematic: false,
business_meaning: "Дебиторская позиция закрыта корректно."
},
{
state_code: "stale_receivable",
state_label: "Дебиторка зависла",
state_class: "problematic",
entry_conditions: ["unresolved_settlement"],
exit_conditions: ["receivable_closed"],
is_terminal: false,
is_problematic: true,
business_meaning: "Позиция остается незавершенной дольше ожидаемого."
}
],
transitions: [
{
from_state: "invoice_issued",
to_state: "payment_recorded",
transition_type: "expected",
required_evidence: ["payment_document_exists"],
optional_evidence: [],
forbidden_conditions: [],
business_meaning: "После реализации ожидается оплата/зачет."
},
{
from_state: "payment_recorded",
to_state: "receivable_closed",
transition_type: "expected",
required_evidence: ["closing_document_linked"],
optional_evidence: ["register_movement_exists"],
forbidden_conditions: ["cross_branch_inconsistency"],
business_meaning: "Оплата должна завершаться корректным закрытием расчета."
}
],
defects: []
},
deferred_expense: {
schema_version: stage3Lifecycle_1.LIFECYCLE_MODEL_SCHEMA_VERSION,
lifecycle_domain: "deferred_expense",
lifecycle_object_types: ["deferred_expense_item"],
states: [
{
state_code: "recognized",
state_label: "РБП признан",
state_class: "initial",
entry_conditions: ["deferred_expense_created"],
exit_conditions: ["writeoff_started"],
is_terminal: false,
is_problematic: false,
business_meaning: "РБП поставлен на учет."
},
{
state_code: "partially_written_off",
state_label: "Частичное списание",
state_class: "active",
entry_conditions: ["partial_writeoff_exists"],
exit_conditions: ["fully_written_off"],
is_terminal: false,
is_problematic: false,
business_meaning: "Списание идет по графику."
},
{
state_code: "fully_written_off",
state_label: "РБП полностью списан",
state_class: "terminal",
entry_conditions: ["full_writeoff_exists"],
exit_conditions: [],
is_terminal: true,
is_problematic: false,
business_meaning: "РБП завершил lifecycle."
},
{
state_code: "overdue_writeoff",
state_label: "Просроченное списание",
state_class: "problematic",
entry_conditions: ["period_boundary", "missing_link"],
exit_conditions: ["fully_written_off"],
is_terminal: false,
is_problematic: true,
business_meaning: "РБП живет дольше допустимого окна."
}
],
transitions: [],
defects: []
},
fixed_asset: {
schema_version: stage3Lifecycle_1.LIFECYCLE_MODEL_SCHEMA_VERSION,
lifecycle_domain: "fixed_asset",
lifecycle_object_types: ["fixed_asset_card"],
states: [
{
state_code: "capitalized",
state_label: "Капвложения отражены",
state_class: "initial",
entry_conditions: ["capitalization_document_exists"],
exit_conditions: ["accepted_for_accounting"],
is_terminal: false,
is_problematic: false,
business_meaning: "Объект зафиксирован как вложение."
},
{
state_code: "accepted_for_accounting",
state_label: "Принят к учету",
state_class: "active",
entry_conditions: ["acceptance_document_exists"],
exit_conditions: ["depreciation_active"],
is_terminal: false,
is_problematic: false,
business_meaning: "Объект переведен в основной контур учета."
},
{
state_code: "depreciation_active",
state_label: "Амортизация активна",
state_class: "active",
entry_conditions: ["depreciation_register_movement"],
exit_conditions: ["disposed"],
is_terminal: false,
is_problematic: false,
business_meaning: "Жизненный цикл ОС идет штатно."
},
{
state_code: "contradictory_asset_state",
state_label: "Противоречивый статус ОС",
state_class: "problematic",
entry_conditions: ["posting_mismatch_or_wrong_path"],
exit_conditions: ["depreciation_active"],
is_terminal: false,
is_problematic: true,
business_meaning: "Статус ОС формально есть, но смыслово противоречив."
},
{
state_code: "disposed",
state_label: "Выбыл",
state_class: "terminal",
entry_conditions: ["disposal_document_exists"],
exit_conditions: [],
is_terminal: true,
is_problematic: false,
business_meaning: "Жизненный цикл ОС завершен."
}
],
transitions: [],
defects: []
},
vat_flow: {
schema_version: stage3Lifecycle_1.LIFECYCLE_MODEL_SCHEMA_VERSION,
lifecycle_domain: "vat_flow",
lifecycle_object_types: ["vat_document_chain"],
states: [
{
state_code: "vat_registered",
state_label: "НДС отражен документно",
state_class: "initial",
entry_conditions: ["invoice_registered"],
exit_conditions: ["vat_reflected"],
is_terminal: false,
is_problematic: false,
business_meaning: "Сформирован первичный документный слой НДС."
},
{
state_code: "vat_reflected",
state_label: "НДС отражен в учете",
state_class: "active",
entry_conditions: ["vat_register_movement"],
exit_conditions: ["vat_deducted"],
is_terminal: false,
is_problematic: false,
business_meaning: "НДС проходит штатную стадию отражения."
},
{
state_code: "vat_deducted",
state_label: "НДС принят к вычету",
state_class: "terminal",
entry_conditions: ["deduction_confirmed"],
exit_conditions: [],
is_terminal: true,
is_problematic: false,
business_meaning: "НДС-цепочка завершена корректно."
},
{
state_code: "vat_conflict",
state_label: "Конфликт НДС-цепочки",
state_class: "problematic",
entry_conditions: ["cross_branch_inconsistency"],
exit_conditions: ["vat_reflected"],
is_terminal: false,
is_problematic: true,
business_meaning: "Бухгалтерская и налоговая ветки расходятся."
}
],
transitions: [],
defects: []
},
period_close: {
schema_version: stage3Lifecycle_1.LIFECYCLE_MODEL_SCHEMA_VERSION,
lifecycle_domain: "period_close",
lifecycle_object_types: ["period_close_blocker"],
states: [
{
state_code: "preclose_checks",
state_label: "Предзакрытие",
state_class: "active",
entry_conditions: ["period_scope_detected"],
exit_conditions: ["close_ready"],
is_terminal: false,
is_problematic: false,
business_meaning: "Идет проверка готовности периода."
},
{
state_code: "close_ready",
state_label: "Готов к закрытию",
state_class: "active",
entry_conditions: ["no_blockers_detected"],
exit_conditions: ["close_completed"],
is_terminal: false,
is_problematic: false,
business_meaning: "Период может быть закрыт."
},
{
state_code: "close_completed",
state_label: "Закрытие завершено",
state_class: "terminal",
entry_conditions: ["close_operation_done"],
exit_conditions: [],
is_terminal: true,
is_problematic: false,
business_meaning: "Период закрыт."
},
{
state_code: "close_blocked",
state_label: "Закрытие заблокировано",
state_class: "problematic",
entry_conditions: ["period_close_risk_or_stale_state"],
exit_conditions: ["close_ready"],
is_terminal: false,
is_problematic: true,
business_meaning: "Есть lifecycle-дефекты, влияющие на закрытие."
},
{
state_code: "close_contradicted",
state_label: "Закрыт формально, но с противоречием",
state_class: "problematic",
entry_conditions: ["misclosed_or_cross_branch_conflict"],
exit_conditions: ["close_completed"],
is_terminal: false,
is_problematic: true,
business_meaning: "Формальное закрытие не согласовано с фактическими ветками."
}
],
transitions: [],
defects: []
}
};
const SHARED_DEFECTS = [
{
defect_code: "missing_expected_transition",
defect_class: "path",
severity_hint: "medium",
business_meaning: "Ожидаемый переход не произошел.",
evidence_requirements: ["expected_state", "missing_transition_signal"],
period_impact_potential: "indirect"
},
{
defect_code: "invalid_transition",
defect_class: "path",
severity_hint: "high",
business_meaning: "Переход произошел по некорректному пути.",
evidence_requirements: ["invalid_transition_signal"],
period_impact_potential: "indirect"
},
{
defect_code: "stale_active_state",
defect_class: "timing",
severity_hint: "high",
business_meaning: "Объект завис в активном состоянии.",
evidence_requirements: ["stale_marker", "missing_transition_signal"],
period_impact_potential: "direct"
},
{
defect_code: "contradictory_state",
defect_class: "consistency",
severity_hint: "high",
business_meaning: "Статусы объекта противоречат друг другу.",
evidence_requirements: ["contradiction_signal"],
period_impact_potential: "direct"
},
{
defect_code: "premature_terminal_state",
defect_class: "closure",
severity_hint: "medium",
business_meaning: "Терминальное состояние наступило преждевременно.",
evidence_requirements: ["terminal_state", "missing_required_previous_state"],
period_impact_potential: "indirect"
},
{
defect_code: "misclosed_state",
defect_class: "closure",
severity_hint: "high",
business_meaning: "Контур формально закрыт, но закрыт неверно.",
evidence_requirements: ["wrong_closure_path"],
period_impact_potential: "direct"
},
{
defect_code: "orphan_intermediate_state",
defect_class: "path",
severity_hint: "medium",
business_meaning: "Промежуточная стадия осталась без корректного продолжения.",
evidence_requirements: ["intermediate_state_without_next"],
period_impact_potential: "indirect"
},
{
defect_code: "cross_branch_state_conflict",
defect_class: "consistency",
severity_hint: "high",
business_meaning: "Состояния соседних веток учета противоречат друг другу.",
evidence_requirements: ["cross_branch_conflict_signal"],
period_impact_potential: "direct"
}
];
for (const domain of stage3Lifecycle_1.STAGE3_LIFECYCLE_DOMAINS) {
LIFECYCLE_DOMAIN_MODELS[domain].defects = SHARED_DEFECTS;
}
class LifecycleRegistryImpl {
models;
constructor(models) {
this.models = models;
}
listDomains() {
return stage3Lifecycle_1.STAGE3_LIFECYCLE_DOMAINS.slice();
}
getDomain(domain) {
return this.models[domain];
}
}
exports.LifecycleRegistry = new LifecycleRegistryImpl(LIFECYCLE_DOMAIN_MODELS);
function inferLifecycleDomain(input) {
const unitTokens = [
input.unit.problem_unit_type,
input.unit.business_defect_class,
input.unit.mechanism_summary,
input.unit.failed_expected_edge ?? "",
input.unit.expected_state ?? "",
input.unit.actual_state ?? "",
...input.unit.affected_accounts,
...input.unit.affected_entities,
...input.unit.affected_documents,
...input.unit.affected_counterparties,
...input.candidates.flatMap((item) => item.anomaly_patterns),
...input.candidates.flatMap((item) => item.relation_pattern_hits)
]
.join(" ")
.toLowerCase();
if (includesAny(unitTokens, [/\bnds\b/, /\bvat\b/, /\btax\b/, /cross[_\s-]?branch/, /\b19\b/, /\b68\b/])) {
return "vat_flow";
}
if (includesAny(unitTokens, [/\bperiod\b/, /\bclose\b/, /закрыт/, /reporting/]) || input.unit.problem_unit_type === "period_risk_cluster") {
return "period_close";
}
if (includesAny(unitTokens, [/deferred/, /writeoff/, /рбп/, /\b97\b/])) {
return "deferred_expense";
}
if (includesAny(unitTokens, [/fixed[_\s-]?asset/, /амортиз/, /ос\b/, /\b01\b/, /\b02\b/, /\b08\b/])) {
return "fixed_asset";
}
if (includesAny(unitTokens, [/buyer/, /customer/, /дебитор/, /\b62\b/])) {
return "customer_settlement";
}
return "bank_settlement";
}
function inferCurrentState(domain, input) {
const explicitActual = input.unit.actual_state?.trim();
if (explicitActual) {
return explicitActual;
}
const anomalies = input.candidates.flatMap((item) => item.anomaly_patterns).map((item) => item.toLowerCase());
const relations = input.candidates.flatMap((item) => item.relation_pattern_hits).map((item) => item.toLowerCase());
const hasStale = hasToken(anomalies, /(no_continuation|stale|tail|missing_link|broken_lifecycle|partially_linked)/);
const hasInvalid = hasToken(anomalies, /(posting_mismatch|wrong_document_type|cross_domain_inconsistency|misclose|cross_branch)/);
if (domain === "bank_settlement") {
if (hasInvalid)
return "misclosed_payment";
if (hasStale)
return "stale_unlinked_payment";
if (hasToken(relations, /payment_to_settlement/))
return "bank_recorded";
return "initiated_payment";
}
if (domain === "customer_settlement") {
if (hasStale)
return "stale_receivable";
if (hasToken(relations, /payment|settlement/))
return "payment_recorded";
return "invoice_issued";
}
if (domain === "deferred_expense") {
if (hasStale)
return "overdue_writeoff";
if (hasToken(relations, /writeoff|partial/))
return "partially_written_off";
return "recognized";
}
if (domain === "fixed_asset") {
if (hasInvalid)
return "contradictory_asset_state";
if (hasToken(relations, /depreciation|amort/))
return "depreciation_active";
if (hasToken(relations, /accept|учет/))
return "accepted_for_accounting";
return "capitalized";
}
if (domain === "vat_flow") {
if (hasInvalid || hasToken(anomalies, /cross_branch|inconsistency/))
return "vat_conflict";
if (hasToken(relations, /invoice_to_vat|vat/))
return "vat_reflected";
return "vat_registered";
}
if (hasInvalid)
return "close_contradicted";
if (hasStale || input.unit.period_impact?.impact_class === "close_risk")
return "close_blocked";
return "preclose_checks";
}
function inferExpectedState(domain, input) {
const explicitExpected = input.unit.expected_state?.trim();
if (explicitExpected) {
return explicitExpected;
}
return defaultExpectedState(domain);
}
function inferMissingTransition(input) {
if (typeof input.unit.failed_expected_edge === "string" && input.unit.failed_expected_edge.trim().length > 0) {
return input.unit.failed_expected_edge.trim();
}
const anomalies = input.candidates.flatMap((item) => item.anomaly_patterns).join(" ").toLowerCase();
if (/(missing_link|no_continuation|broken_lifecycle|tail|unresolved)/.test(anomalies)) {
return "expected_transition_not_observed";
}
return null;
}
function inferInvalidTransition(input) {
const anomalies = input.candidates.flatMap((item) => item.anomaly_patterns).join(" ").toLowerCase();
if (/(cross_branch|cross_domain_inconsistency)/.test(anomalies)) {
return "cross_branch_conflict_transition";
}
if (/(wrong_document_type|posting_mismatch|misclose)/.test(anomalies)) {
return "invalid_document_or_posting_transition";
}
return null;
}
function classifyLifecycleDefect(input) {
const current = input.currentState.toLowerCase();
if (input.invalidTransition?.includes("cross_branch")) {
return "cross_branch_state_conflict";
}
if (input.invalidTransition) {
if (current.includes("misclosed") || input.domain === "period_close") {
return "misclosed_state";
}
return "invalid_transition";
}
if (input.missingTransition) {
if (current.includes("stale") || current.includes("overdue") || input.periodCloseSensitive) {
return "stale_active_state";
}
return "missing_expected_transition";
}
if (current.includes("contradict")) {
return "contradictory_state";
}
if (current.includes("closed") && !input.expectedState.toLowerCase().includes("closed")) {
return "premature_terminal_state";
}
if (input.currentState !== input.expectedState && !input.currentState.toLowerCase().includes("closed")) {
return "orphan_intermediate_state";
}
return null;
}
function resolutionConfidence(unitConfidence, input) {
let score = unitConfidence.score;
if (input.hasExplicitStates)
score += 0.1;
if (input.hasDefectSignal)
score += 0.08;
if (input.candidateCount >= 2)
score += 0.05;
if (input.hasSnapshotLimitations)
score -= 0.12;
const normalized = clampUnitScore(score);
return {
score: normalized,
grade: lifecycleConfidenceGrade(normalized)
};
}
function staleDurationHint(domain, defect, input) {
const anomalies = input.candidates.flatMap((item) => item.anomaly_patterns).join(" ").toLowerCase();
if (defect !== "stale_active_state") {
return undefined;
}
if (/(period_boundary|period|close_risk)/.test(anomalies) || domain === "period_close") {
return "period_boundary_exceeded";
}
return "unknown_snapshot_window";
}
function lifecycleInterpretation(input) {
const base = `Текущая стадия: ${input.currentState}; ожидаемая стадия: ${input.expectedState}.`;
if (input.defect === "stale_active_state") {
return `${base} Объект завис во времени и не дошел до ожидаемого перехода.`;
}
if (input.defect === "misclosed_state") {
return `${base} Контур закрыт формально, но путь закрытия противоречит бухгалтерской логике.`;
}
if (input.defect === "cross_branch_state_conflict") {
return `${base} Между ветками домена ${input.domain} обнаружено противоречие состояний.`;
}
if (input.defect === "missing_expected_transition") {
return `${base} Не зафиксирован ожидаемый переход (${input.missingTransition ?? "unknown_transition"}).`;
}
if (input.defect === "invalid_transition") {
return `${base} Зафиксирован некорректный переход (${input.invalidTransition ?? "invalid_transition"}).`;
}
return `${base} Lifecycle-разрешение не выявило критичный дефект, но состояние требует наблюдения.`;
}
function resolveLifecycle(input) {
const lifecycle_domain = inferLifecycleDomain(input);
const currentState = inferCurrentState(lifecycle_domain, input);
const expectedState = inferExpectedState(lifecycle_domain, input);
const missingTransition = inferMissingTransition(input);
const invalidTransition = inferInvalidTransition(input);
const defect = classifyLifecycleDefect({
domain: lifecycle_domain,
currentState,
expectedState,
missingTransition,
invalidTransition,
periodCloseSensitive: input.unit.period_impact?.impact_class === "close_risk"
});
const evidenceIds = uniqueStrings(input.unit.evidence_pack, 8);
const limitations = uniqueStrings([
...input.unit.snapshot_limitations,
...(input.candidates.some((item) => item.confidence_hint === "low") ? ["low_confidence_candidates_present"] : []),
...(input.unit.actual_state ? [] : ["actual_state_inferred"]),
...(input.unit.expected_state ? [] : ["expected_state_inferred"])
], 8);
const confidence = resolutionConfidence(input.unit.confidence, {
hasExplicitStates: Boolean(input.unit.actual_state || input.unit.expected_state),
hasDefectSignal: Boolean(defect || missingTransition || invalidTransition),
candidateCount: input.candidates.length,
hasSnapshotLimitations: limitations.length > 0
});
return {
lifecycle_object_id: `lcobj-${input.unit.problem_unit_id}`,
lifecycle_domain,
resolved_current_state: currentState,
resolved_expected_state: expectedState,
resolved_previous_states: [],
missing_transitions: missingTransition ? [missingTransition] : [],
invalid_transitions: invalidTransition ? [invalidTransition] : [],
detected_defects: defect ? [defect] : [],
state_confidence: confidence,
resolution_evidence: evidenceIds,
snapshot_limitations: limitations
};
}
function lifecycleRanking(defect, input) {
let score = input.unit.severity.score;
const basis = ["base_problem_severity"];
if (defect === "cross_branch_state_conflict") {
score += 0.55;
basis.push("cross_branch_conflict_weight");
}
else if (defect === "misclosed_state") {
score += 0.45;
basis.push("misclosed_state_weight");
}
else if (defect === "stale_active_state") {
score += 0.35;
basis.push("stale_duration_weight");
}
else if (defect === "invalid_transition") {
score += 0.3;
basis.push("invalid_transition_weight");
}
else if (defect === "missing_expected_transition") {
score += 0.25;
basis.push("missing_transition_weight");
}
if (input.staleDuration) {
score += 0.15;
basis.push("stale_duration_present");
}
if (input.unit.period_impact?.impact_class === "close_risk") {
score += 0.22;
basis.push("period_close_impact");
}
if (input.resolution.state_confidence.grade === "high") {
score += 0.08;
basis.push("state_confidence_weight");
}
return {
lifecycle_ranking_score: Number(score.toFixed(2)),
lifecycle_ranking_basis: basis
};
}
function enrichProblemUnitLifecycle(input) {
const resolution = resolveLifecycle(input);
const defect = resolution.detected_defects[0] ?? null;
const staleDuration = staleDurationHint(resolution.lifecycle_domain, defect, input);
const ranking = lifecycleRanking(defect, {
unit: input.unit,
resolution,
staleDuration
});
return {
...input.unit,
lifecycle_domain: resolution.lifecycle_domain,
lifecycle_object_id: resolution.lifecycle_object_id,
current_lifecycle_state: resolution.resolved_current_state,
expected_lifecycle_state: resolution.resolved_expected_state,
...(resolution.missing_transitions.length > 0
? {
missing_transition: resolution.missing_transitions[0]
}
: {}),
...(resolution.invalid_transitions.length > 0
? {
invalid_transition: resolution.invalid_transitions[0]
}
: {}),
...(defect
? {
lifecycle_defect_type: defect
}
: {}),
...(staleDuration
? {
stale_duration: staleDuration
}
: {}),
lifecycle_confidence: resolution.state_confidence,
business_lifecycle_interpretation: lifecycleInterpretation({
domain: resolution.lifecycle_domain,
currentState: resolution.resolved_current_state,
expectedState: resolution.resolved_expected_state,
defect,
missingTransition: resolution.missing_transitions[0] ?? null,
invalidTransition: resolution.invalid_transitions[0] ?? null
}),
lifecycle_resolution: resolution,
lifecycle_ranking_score: ranking.lifecycle_ranking_score,
lifecycle_ranking_basis: ranking.lifecycle_ranking_basis
};
}
function rankLifecycleProblemUnits(units) {
return units
.slice()
.sort((left, right) => {
const rankDiff = (right.lifecycle_ranking_score ?? 0) - (left.lifecycle_ranking_score ?? 0);
if (rankDiff !== 0)
return rankDiff;
const severityDiff = right.severity.score - left.severity.score;
if (severityDiff !== 0)
return severityDiff;
return right.confidence.score - left.confidence.score;
});
}
+109 -5
View File
@@ -8,6 +8,8 @@ exports.buildProblemUnit = buildProblemUnit;
exports.collapseDuplicates = collapseDuplicates;
exports.assembleProblemUnits = assembleProblemUnits;
const stage2ProblemUnits_1 = require("../types/stage2ProblemUnits");
const config_1 = require("../config");
const lifecycleRuntime_1 = require("./lifecycleRuntime");
function toObject(value) {
if (!value || typeof value !== "object" || Array.isArray(value)) {
return null;
@@ -382,6 +384,17 @@ function collapseSignature(unit) {
const backlink = unit.entity_backlinks[0] ? `${unit.entity_backlinks[0].entity}|${unit.entity_backlinks[0].id}` : "none";
return [unit.problem_unit_type, unit.business_defect_class, unit.failed_expected_edge ?? "none", backlink].join("|");
}
function preferredLifecycleSource(left, right) {
const leftRank = left.lifecycle_ranking_score ?? 0;
const rightRank = right.lifecycle_ranking_score ?? 0;
if (rightRank > leftRank) {
return right;
}
if (leftRank > rightRank) {
return left;
}
return right.confidence.score > left.confidence.score ? right : left;
}
function collapseDuplicates(units) {
const bySignature = new Map();
let duplicateCollapses = 0;
@@ -393,6 +406,7 @@ function collapseDuplicates(units) {
continue;
}
duplicateCollapses += 1;
const preferredLifecycle = preferredLifecycleSource(existing, unit);
bySignature.set(signature, {
...existing,
evidence_pack: uniqueStrings([...existing.evidence_pack, ...unit.evidence_pack]),
@@ -408,7 +422,72 @@ function collapseDuplicates(units) {
affected_contracts: uniqueStrings([...existing.affected_contracts, ...unit.affected_contracts]),
snapshot_limitations: uniqueStrings([...existing.snapshot_limitations, ...unit.snapshot_limitations]),
severity: unit.severity.score > existing.severity.score ? unit.severity : existing.severity,
confidence: unit.confidence.score > existing.confidence.score ? unit.confidence : existing.confidence
confidence: unit.confidence.score > existing.confidence.score ? unit.confidence : existing.confidence,
...(preferredLifecycle.lifecycle_domain
? {
lifecycle_domain: preferredLifecycle.lifecycle_domain
}
: {}),
...(preferredLifecycle.lifecycle_object_id
? {
lifecycle_object_id: preferredLifecycle.lifecycle_object_id
}
: {}),
...(preferredLifecycle.current_lifecycle_state
? {
current_lifecycle_state: preferredLifecycle.current_lifecycle_state
}
: {}),
...(preferredLifecycle.expected_lifecycle_state
? {
expected_lifecycle_state: preferredLifecycle.expected_lifecycle_state
}
: {}),
...(preferredLifecycle.missing_transition
? {
missing_transition: preferredLifecycle.missing_transition
}
: {}),
...(preferredLifecycle.invalid_transition
? {
invalid_transition: preferredLifecycle.invalid_transition
}
: {}),
...(preferredLifecycle.lifecycle_defect_type
? {
lifecycle_defect_type: preferredLifecycle.lifecycle_defect_type
}
: {}),
...(preferredLifecycle.stale_duration
? {
stale_duration: preferredLifecycle.stale_duration
}
: {}),
...(preferredLifecycle.lifecycle_confidence
? {
lifecycle_confidence: preferredLifecycle.lifecycle_confidence
}
: {}),
...(preferredLifecycle.business_lifecycle_interpretation
? {
business_lifecycle_interpretation: preferredLifecycle.business_lifecycle_interpretation
}
: {}),
...(preferredLifecycle.lifecycle_resolution
? {
lifecycle_resolution: preferredLifecycle.lifecycle_resolution
}
: {}),
...(preferredLifecycle.lifecycle_ranking_score !== undefined
? {
lifecycle_ranking_score: preferredLifecycle.lifecycle_ranking_score
}
: {}),
...(preferredLifecycle.lifecycle_ranking_basis
? {
lifecycle_ranking_basis: preferredLifecycle.lifecycle_ranking_basis
}
: {})
});
}
return {
@@ -429,10 +508,20 @@ function buildSummary(units, duplicateCollapses) {
medium: 0,
high: 0
};
const lifecycleDomainDistribution = {};
const lifecycleDefectDistribution = {};
let lifecycleEnrichedUnits = 0;
for (const unit of units) {
typeDistribution[unit.problem_unit_type] = (typeDistribution[unit.problem_unit_type] ?? 0) + 1;
severityDistribution[unit.severity.grade] += 1;
confidenceDistribution[unit.confidence.grade] += 1;
if (unit.lifecycle_domain) {
lifecycleEnrichedUnits += 1;
lifecycleDomainDistribution[unit.lifecycle_domain] = (lifecycleDomainDistribution[unit.lifecycle_domain] ?? 0) + 1;
}
if (unit.lifecycle_defect_type) {
lifecycleDefectDistribution[unit.lifecycle_defect_type] = (lifecycleDefectDistribution[unit.lifecycle_defect_type] ?? 0) + 1;
}
}
return {
schema_version: stage2ProblemUnits_1.PROBLEM_UNIT_SUMMARY_SCHEMA_VERSION,
@@ -442,7 +531,10 @@ function buildSummary(units, duplicateCollapses) {
type_distribution: typeDistribution,
severity_distribution: severityDistribution,
confidence_distribution: confidenceDistribution,
primary_unit_type: units[0]?.problem_unit_type ?? null
primary_unit_type: units[0]?.problem_unit_type ?? null,
lifecycle_enriched_units: lifecycleEnrichedUnits,
lifecycle_domain_distribution: lifecycleDomainDistribution,
lifecycle_defect_distribution: lifecycleDefectDistribution
};
}
function assembleProblemUnits(input) {
@@ -457,11 +549,23 @@ function assembleProblemUnits(input) {
risk_factors: uniqueStrings(input.risk_factors ?? [])
});
const clusters = clusterCandidateEvidence(candidates);
const units = clusters.map((cluster, index) => buildProblemUnit(cluster, index));
const units = clusters.map((cluster, index) => {
const baseUnit = buildProblemUnit(cluster, index);
if (!config_1.FEATURE_ASSISTANT_LIFECYCLE_RUNTIME_V1) {
return baseUnit;
}
return (0, lifecycleRuntime_1.enrichProblemUnitLifecycle)({
unit: baseUnit,
candidates: cluster.candidates
});
});
const collapsed = collapseDuplicates(units);
const rankedUnits = config_1.FEATURE_ASSISTANT_LIFECYCLE_RUNTIME_V1
? (0, lifecycleRuntime_1.rankLifecycleProblemUnits)(collapsed.problem_units)
: collapsed.problem_units;
return {
candidate_evidence: candidates,
problem_units: collapsed.problem_units,
problem_unit_summary: buildSummary(collapsed.problem_units, collapsed.duplicate_collapses)
problem_units: rankedUnits,
problem_unit_summary: buildSummary(rankedUnits, collapsed.duplicate_collapses)
};
}
@@ -69,7 +69,10 @@ function mergeSummaryWithProblemUnitMeta(summary, input) {
problem_unit_types: input.unitTypes,
problem_unit_duplicate_collapses: input.duplicateCollapses,
problem_unit_severity_distribution: input.severityDistribution,
problem_unit_confidence_distribution: input.confidenceDistribution
problem_unit_confidence_distribution: input.confidenceDistribution,
lifecycle_enriched_units: input.lifecycleEnrichedUnits,
problem_unit_lifecycle_domain_distribution: input.lifecycleDomainDistribution,
problem_unit_lifecycle_defect_distribution: input.lifecycleDefectDistribution
};
}
function normalizeConfidence(value) {
@@ -411,7 +414,10 @@ function normalizeRetrievalResult(fragmentId, requirementIds, route, raw) {
unitTypes: assembled.problem_unit_summary.unit_types,
duplicateCollapses: assembled.problem_unit_summary.duplicate_collapses,
severityDistribution: assembled.problem_unit_summary.severity_distribution,
confidenceDistribution: assembled.problem_unit_summary.confidence_distribution
confidenceDistribution: assembled.problem_unit_summary.confidence_distribution,
lifecycleEnrichedUnits: assembled.problem_unit_summary.lifecycle_enriched_units ?? 0,
lifecycleDomainDistribution: (assembled.problem_unit_summary.lifecycle_domain_distribution ?? {}),
lifecycleDefectDistribution: (assembled.problem_unit_summary.lifecycle_defect_distribution ?? {})
});
return {
...baseResult,
+22
View File
@@ -0,0 +1,22 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.LIFECYCLE_DEFECT_TYPES = exports.STAGE3_LIFECYCLE_DOMAINS = exports.LIFECYCLE_MODEL_SCHEMA_VERSION = void 0;
exports.LIFECYCLE_MODEL_SCHEMA_VERSION = "lifecycle_model_v0_1";
exports.STAGE3_LIFECYCLE_DOMAINS = [
"bank_settlement",
"customer_settlement",
"deferred_expense",
"fixed_asset",
"vat_flow",
"period_close"
];
exports.LIFECYCLE_DEFECT_TYPES = [
"missing_expected_transition",
"invalid_transition",
"stale_active_state",
"contradictory_state",
"premature_terminal_state",
"misclosed_state",
"orphan_intermediate_state",
"cross_branch_state_conflict"
];
+8
View File
@@ -67,6 +67,14 @@ export const FEATURE_ASSISTANT_STAGE2_EVAL_V1 = toBooleanFlag(
process.env.FEATURE_ASSISTANT_STAGE2_EVAL_V1,
false
);
export const FEATURE_ASSISTANT_LIFECYCLE_RUNTIME_V1 = toBooleanFlag(
process.env.FEATURE_ASSISTANT_LIFECYCLE_RUNTIME_V1,
false
);
export const FEATURE_ASSISTANT_LIFECYCLE_ANSWER_V1 = toBooleanFlag(
process.env.FEATURE_ASSISTANT_LIFECYCLE_ANSWER_V1,
false
);
export const DATA_DIR = process.env.DATA_DIR ?? path.resolve(MODULE_ROOT, "data");
export const TRACES_DIR = path.resolve(DATA_DIR, "traces");
@@ -10,7 +10,7 @@ import type { RouteHintSummary } from "../types/normalizer";
import type { AnswerStructureV11, EvidenceConfidence, EvidenceItem, EvidenceLimitationReasonCode } from "../types/stage1Contracts";
import type { ProblemUnit, ProblemUnitSummary, ProblemUnitType } from "../types/stage2ProblemUnits";
type ProblemAnswerMode = "stage1_policy_v11" | "stage2_problem_centric_v1";
type ProblemAnswerMode = "stage1_policy_v11" | "stage2_problem_centric_v1" | "stage3_lifecycle_aware_v1";
interface ComposeAnswerInput {
userMessage: string;
@@ -21,6 +21,7 @@ interface ComposeAnswerInput {
groundingCheck: AnswerGroundingCheck;
enableAnswerPolicyV11?: boolean;
enableProblemCentricAnswerV1?: boolean;
enableLifecycleAnswerV1?: boolean;
}
interface ComposeAnswerOutput {
@@ -382,6 +383,61 @@ function formatAffectedScope(unit: ProblemUnit): string {
return scopeParts.join("; ");
}
function formatLifecycleScope(unit: ProblemUnit): string | null {
if (!unit.lifecycle_domain) {
return null;
}
const parts: string[] = [`domain=${unit.lifecycle_domain}`];
if (unit.current_lifecycle_state) {
parts.push(`current=${unit.current_lifecycle_state}`);
}
if (unit.expected_lifecycle_state) {
parts.push(`expected=${unit.expected_lifecycle_state}`);
}
if (unit.lifecycle_defect_type) {
parts.push(`defect=${unit.lifecycle_defect_type}`);
}
if (unit.missing_transition) {
parts.push(`missing_transition=${unit.missing_transition}`);
}
if (unit.invalid_transition) {
parts.push(`invalid_transition=${unit.invalid_transition}`);
}
if (unit.stale_duration) {
parts.push(`stale_duration=${unit.stale_duration}`);
}
return parts.join(", ");
}
function rankProblemUnitsForAnswer(units: ProblemUnit[], lifecycleAnswerEnabled: boolean): ProblemUnit[] {
if (!lifecycleAnswerEnabled) {
return units.slice().sort((left, right) => {
const severityDiff = right.severity.score - left.severity.score;
if (severityDiff !== 0) return severityDiff;
return right.confidence.score - left.confidence.score;
});
}
return units.slice().sort((left, right) => {
const lifecycleRankDiff = (right.lifecycle_ranking_score ?? 0) - (left.lifecycle_ranking_score ?? 0);
if (lifecycleRankDiff !== 0) return lifecycleRankDiff;
const lifecycleConfidenceDiff = (right.lifecycle_confidence?.score ?? 0) - (left.lifecycle_confidence?.score ?? 0);
if (lifecycleConfidenceDiff !== 0) return lifecycleConfidenceDiff;
const severityDiff = right.severity.score - left.severity.score;
if (severityDiff !== 0) return severityDiff;
return right.confidence.score - left.confidence.score;
});
}
function hasLifecycleResolution(units: ProblemUnit[]): boolean {
return units.some(
(unit) =>
Boolean(unit.lifecycle_domain) &&
Boolean(unit.current_lifecycle_state) &&
Boolean(unit.expected_lifecycle_state) &&
Boolean(unit.lifecycle_defect_type)
);
}
function buildProblemCentricActions(input: {
units: ProblemUnit[];
mode: PolicyMode;
@@ -406,6 +462,17 @@ function buildProblemCentricActions(input: {
if (unitTypes.has("lifecycle_anomaly_node")) {
actions.push("Проверьте lifecycle объекта: ожидаемый этап не должен оставаться в partially_linked состоянии.");
}
for (const unit of input.units) {
if (unit.lifecycle_defect_type === "stale_active_state") {
actions.push("Проверьте, почему объект завис: ожидаемый переход не должен оставаться в активной стадии.");
}
if (unit.lifecycle_defect_type === "misclosed_state") {
actions.push("Проверьте закрывающий документ и проводки: закрытие может быть формальным, но некорректным по пути.");
}
if (unit.lifecycle_defect_type === "cross_branch_state_conflict") {
actions.push("Сверьте бухгалтерскую и смежную ветки (например, НДС/расчеты): обнаружен межконтурный конфликт состояния.");
}
}
if (input.mode === "clarification_required") {
if (input.missingAnchors.period) {
@@ -825,7 +892,14 @@ function buildProblemCentricAnswerSummary(input: {
mode: PolicyMode;
weakUnits: boolean;
summary: ProblemUnitSummary | null;
lifecycleEnriched: boolean;
}): string {
if (input.lifecycleEnriched && input.summary?.lifecycle_enriched_units && input.summary.lifecycle_enriched_units > 0) {
if (input.mode === "clarification_required") {
return "Выявлены lifecycle-дефекты, но для надежного вывода требуется уточнение предметных якорей.";
}
return `Сформирован lifecycle-aware problem срез: выделено ${input.summary.lifecycle_enriched_units} lifecycle-узлов с приоритетом по дефектам перехода.`;
}
if (input.mode === "clarification_required") {
return "Выявлены проблемные кластеры, но для надежного вывода требуется предметное уточнение фокуса.";
}
@@ -842,17 +916,31 @@ function buildProblemCentricDirectAnswer(input: {
mode: PolicyMode;
units: ProblemUnit[];
weakUnits: boolean;
lifecycleAnswerEnabled: boolean;
}): string {
const lead =
input.mode === "clarification_required"
? "Обнаружены проблемные зоны, но без уточнения якорей сильный factual-вывод преждевременен."
: input.weakUnits
? "Выделены проблемные зоны с ограниченной надежностью; вывод дан в ограниченном режиме."
: "Выделены ключевые проблемные зоны и их влияние на учетный контур.";
: input.lifecycleAnswerEnabled && hasLifecycleResolution(input.units)
? "Выделены lifecycle-проблемы: определены текущие/ожидаемые стадии и тип нарушения перехода."
: "Выделены ключевые проблемные зоны и их влияние на учетный контур.";
const unitLines = input.units.map((unit) => {
const scope = formatAffectedScope(unit);
return `- ${unit.title}: ${unit.business_defect_class}; ${scope}; severity=${unit.severity.grade}, confidence=${unit.confidence.grade}.`;
const lifecycleScope = input.lifecycleAnswerEnabled ? formatLifecycleScope(unit) : null;
const lifecycleInterpretation = input.lifecycleAnswerEnabled ? unit.business_lifecycle_interpretation : null;
const segments = [
`${unit.title}: ${unit.business_defect_class}`,
scope,
lifecycleScope,
lifecycleInterpretation,
`severity=${unit.severity.grade}`,
`confidence=${unit.confidence.grade}`,
unit.lifecycle_confidence ? `lifecycle_confidence=${unit.lifecycle_confidence.grade}` : null
].filter((item): item is string => Boolean(item));
return `- ${segments.join("; ")}.`;
});
if (unitLines.length === 0) {
@@ -873,8 +961,10 @@ function buildProblemCentricAnswerStructure(input: {
retrievalResults: UnifiedRetrievalResult[];
missingAnchors: MissingAnchors;
coverageReport: RequirementCoverageReport;
lifecycleAnswerEnabled: boolean;
}): AnswerStructureV11 {
const weakUnits = input.selectedUnits.every((item) => item.confidence.grade === "low");
const lifecycleEnriched = input.lifecycleAnswerEnabled && hasLifecycleResolution(input.selectedUnits);
const unitMechanismNotes = uniqueStrings(
input.selectedUnits
.map((item) => item.mechanism_summary)
@@ -932,12 +1022,14 @@ function buildProblemCentricAnswerStructure(input: {
answer_summary: buildProblemCentricAnswerSummary({
mode: input.mode,
weakUnits,
summary: input.problemSummary
summary: input.problemSummary,
lifecycleEnriched
}),
direct_answer: buildProblemCentricDirectAnswer({
mode: input.mode,
units: input.selectedUnits,
weakUnits
weakUnits,
lifecycleAnswerEnabled: input.lifecycleAnswerEnabled
}),
mechanism_block: {
status: mechanismStatus,
@@ -1059,10 +1151,11 @@ function composeAssistantAnswerV11(input: ComposeAnswerInput): ComposeAnswerOutp
.filter((item): item is string => typeof item === "string" && item.trim().length > 0),
6
);
const lifecycleAnswerEnabled = Boolean(input.enableLifecycleAnswerV1);
const problemUnits = flattenProblemUnits(input.retrievalResults);
const problemUnitSummary = selectProblemUnitSummary(input.retrievalResults);
const problemHeavyUnits = problemUnits.filter((item) => PROBLEM_HEAVY_TYPES.has(item.problem_unit_type));
const selectedProblemUnits = problemHeavyUnits.slice(0, 4);
const selectedProblemUnits = rankProblemUnitsForAnswer(problemHeavyUnits, lifecycleAnswerEnabled).slice(0, 4);
const claimEvidenceLinks = buildClaimEvidenceLinks(input.retrievalResults);
const aggregateEvidenceConfidence = aggregateConfidence(input.retrievalResults, evidenceItems);
const lowConfidenceSignals = evidenceItems.filter((item) => item.confidence === "low").length;
@@ -1138,9 +1231,12 @@ function composeAssistantAnswerV11(input: ComposeAnswerInput): ComposeAnswerOutp
groundingCheck: input.groundingCheck,
retrievalResults: input.retrievalResults,
missingAnchors,
coverageReport: input.coverageReport
coverageReport: input.coverageReport,
lifecycleAnswerEnabled
});
const lifecycleModeActive = lifecycleAnswerEnabled && hasLifecycleResolution(selectedProblemUnits);
return {
assistant_reply: renderPolicyReply(problemCentricStructure),
fallback_type: decision.fallback_type,
@@ -1148,7 +1244,7 @@ function composeAssistantAnswerV11(input: ComposeAnswerInput): ComposeAnswerOutp
answer_structure_v11: problemCentricStructure,
problem_centric_answer_applied: true,
problem_units_used_count: selectedProblemUnits.length,
problem_answer_mode: "stage2_problem_centric_v1",
problem_answer_mode: lifecycleModeActive ? "stage3_lifecycle_aware_v1" : "stage2_problem_centric_v1",
problem_unit_ids_used: selectedProblemUnits.map((item) => item.problem_unit_id)
};
}
@@ -19,6 +19,7 @@ import {
FEATURE_ASSISTANT_CONTRACTS_V11,
FEATURE_ASSISTANT_EVIDENCE_ENRICHMENT_V1,
FEATURE_ASSISTANT_INVESTIGATION_STATE_V1,
FEATURE_ASSISTANT_LIFECYCLE_ANSWER_V1,
FEATURE_ASSISTANT_PROBLEM_CENTRIC_ANSWER_V1,
FEATURE_ASSISTANT_PROBLEM_UNIT_CONTINUITY_V1,
FEATURE_ASSISTANT_STATE_FOLLOWUP_BINDING_V1
@@ -1203,7 +1204,8 @@ export class AssistantService {
coverageReport: coverageEvaluation.coverage,
groundingCheck,
enableAnswerPolicyV11: FEATURE_ASSISTANT_ANSWER_POLICY_V11,
enableProblemCentricAnswerV1: FEATURE_ASSISTANT_PROBLEM_CENTRIC_ANSWER_V1
enableProblemCentricAnswerV1: FEATURE_ASSISTANT_PROBLEM_CENTRIC_ANSWER_V1,
enableLifecycleAnswerV1: FEATURE_ASSISTANT_LIFECYCLE_ANSWER_V1
});
const answerStructureV11 = FEATURE_ASSISTANT_CONTRACTS_V11
@@ -0,0 +1,865 @@
import type { CandidateEvidenceItem, ProblemConfidence, ProblemUnit, ProblemUnitType } from "../types/stage2ProblemUnits";
import {
LIFECYCLE_MODEL_SCHEMA_VERSION,
STAGE3_LIFECYCLE_DOMAINS,
type LifecycleConfidence,
type LifecycleDefectDefinition,
type LifecycleDefectType,
type LifecycleDomain,
type LifecycleDomainModel,
type LifecycleResolution
} from "../types/stage3Lifecycle";
interface LifecycleResolverInput {
unit: ProblemUnit;
candidates: CandidateEvidenceItem[];
}
interface LifecycleRankingResult {
lifecycle_ranking_score: number;
lifecycle_ranking_basis: string[];
}
function clampUnitScore(value: number): number {
if (!Number.isFinite(value)) {
return 0;
}
if (value <= 0) return 0;
if (value >= 1) return 1;
return Number(value.toFixed(2));
}
function lifecycleConfidenceGrade(score: number): LifecycleConfidence["grade"] {
if (score >= 0.75) return "high";
if (score >= 0.45) return "medium";
return "low";
}
function uniqueStrings(values: string[], limit = 16): string[] {
return Array.from(new Set(values.map((item) => item.trim()).filter(Boolean))).slice(0, limit);
}
function includesAny(source: string, patterns: RegExp[]): boolean {
return patterns.some((pattern) => pattern.test(source));
}
function hasToken(values: string[], pattern: RegExp): boolean {
return values.some((value) => pattern.test(value));
}
function defaultExpectedState(domain: LifecycleDomain): string {
if (domain === "bank_settlement") return "settlement_closed";
if (domain === "customer_settlement") return "receivable_closed";
if (domain === "deferred_expense") return "fully_written_off";
if (domain === "fixed_asset") return "depreciation_active";
if (domain === "vat_flow") return "vat_deducted";
return "close_completed";
}
const LIFECYCLE_DOMAIN_MODELS: Record<LifecycleDomain, LifecycleDomainModel> = {
bank_settlement: {
schema_version: LIFECYCLE_MODEL_SCHEMA_VERSION,
lifecycle_domain: "bank_settlement",
lifecycle_object_types: ["payment_settlement_link"],
states: [
{
state_code: "initiated_payment",
state_label: "Платеж инициирован",
state_class: "initial",
entry_conditions: ["payment_order_created"],
exit_conditions: ["bank_recorded"],
is_terminal: false,
is_problematic: false,
business_meaning: "Есть инициирование платежа."
},
{
state_code: "bank_recorded",
state_label: "Платеж отражен банком",
state_class: "active",
entry_conditions: ["bank_statement_recorded"],
exit_conditions: ["settlement_linked"],
is_terminal: false,
is_problematic: false,
business_meaning: "Движение денег зафиксировано, ожидается расчетное закрытие."
},
{
state_code: "settlement_closed",
state_label: "Расчет закрыт",
state_class: "terminal",
entry_conditions: ["payment_to_settlement_linked"],
exit_conditions: [],
is_terminal: true,
is_problematic: false,
business_meaning: "Платеж доведен до расчетного результата."
},
{
state_code: "stale_unlinked_payment",
state_label: "Платеж завис без закрытия",
state_class: "problematic",
entry_conditions: ["bank_recorded", "missing_link"],
exit_conditions: ["settlement_closed"],
is_terminal: false,
is_problematic: true,
business_meaning: "Платеж отражен, но ожидаемая связь по расчету не завершена."
},
{
state_code: "misclosed_payment",
state_label: "Платеж закрыт некорректно",
state_class: "problematic",
entry_conditions: ["wrong_document_type_or_posting_mismatch"],
exit_conditions: ["settlement_closed"],
is_terminal: false,
is_problematic: true,
business_meaning: "Формальное закрытие есть, но путь закрытия неверный."
}
],
transitions: [
{
from_state: "initiated_payment",
to_state: "bank_recorded",
transition_type: "expected",
required_evidence: ["bank_statement_recorded"],
optional_evidence: ["payment_order"],
forbidden_conditions: [],
business_meaning: "Платеж должен появиться во выписке."
},
{
from_state: "bank_recorded",
to_state: "settlement_closed",
transition_type: "expected",
required_evidence: ["payment_to_settlement_link"],
optional_evidence: ["document_to_posting"],
forbidden_conditions: ["wrong_document_type"],
business_meaning: "После выписки должен закрываться расчет."
}
],
defects: []
},
customer_settlement: {
schema_version: LIFECYCLE_MODEL_SCHEMA_VERSION,
lifecycle_domain: "customer_settlement",
lifecycle_object_types: ["receivable_chain"],
states: [
{
state_code: "invoice_issued",
state_label: "Реализация отражена",
state_class: "initial",
entry_conditions: ["realization_document_exists"],
exit_conditions: ["payment_recorded"],
is_terminal: false,
is_problematic: false,
business_meaning: "Возникла дебиторская позиция."
},
{
state_code: "payment_recorded",
state_label: "Оплата отражена",
state_class: "active",
entry_conditions: ["payment_document_exists"],
exit_conditions: ["receivable_closed"],
is_terminal: false,
is_problematic: false,
business_meaning: "Оплата есть, ожидается корректное закрытие."
},
{
state_code: "receivable_closed",
state_label: "Дебиторка закрыта",
state_class: "terminal",
entry_conditions: ["closing_document_linked"],
exit_conditions: [],
is_terminal: true,
is_problematic: false,
business_meaning: "Дебиторская позиция закрыта корректно."
},
{
state_code: "stale_receivable",
state_label: "Дебиторка зависла",
state_class: "problematic",
entry_conditions: ["unresolved_settlement"],
exit_conditions: ["receivable_closed"],
is_terminal: false,
is_problematic: true,
business_meaning: "Позиция остается незавершенной дольше ожидаемого."
}
],
transitions: [
{
from_state: "invoice_issued",
to_state: "payment_recorded",
transition_type: "expected",
required_evidence: ["payment_document_exists"],
optional_evidence: [],
forbidden_conditions: [],
business_meaning: "После реализации ожидается оплата/зачет."
},
{
from_state: "payment_recorded",
to_state: "receivable_closed",
transition_type: "expected",
required_evidence: ["closing_document_linked"],
optional_evidence: ["register_movement_exists"],
forbidden_conditions: ["cross_branch_inconsistency"],
business_meaning: "Оплата должна завершаться корректным закрытием расчета."
}
],
defects: []
},
deferred_expense: {
schema_version: LIFECYCLE_MODEL_SCHEMA_VERSION,
lifecycle_domain: "deferred_expense",
lifecycle_object_types: ["deferred_expense_item"],
states: [
{
state_code: "recognized",
state_label: "РБП признан",
state_class: "initial",
entry_conditions: ["deferred_expense_created"],
exit_conditions: ["writeoff_started"],
is_terminal: false,
is_problematic: false,
business_meaning: "РБП поставлен на учет."
},
{
state_code: "partially_written_off",
state_label: "Частичное списание",
state_class: "active",
entry_conditions: ["partial_writeoff_exists"],
exit_conditions: ["fully_written_off"],
is_terminal: false,
is_problematic: false,
business_meaning: "Списание идет по графику."
},
{
state_code: "fully_written_off",
state_label: "РБП полностью списан",
state_class: "terminal",
entry_conditions: ["full_writeoff_exists"],
exit_conditions: [],
is_terminal: true,
is_problematic: false,
business_meaning: "РБП завершил lifecycle."
},
{
state_code: "overdue_writeoff",
state_label: "Просроченное списание",
state_class: "problematic",
entry_conditions: ["period_boundary", "missing_link"],
exit_conditions: ["fully_written_off"],
is_terminal: false,
is_problematic: true,
business_meaning: "РБП живет дольше допустимого окна."
}
],
transitions: [],
defects: []
},
fixed_asset: {
schema_version: LIFECYCLE_MODEL_SCHEMA_VERSION,
lifecycle_domain: "fixed_asset",
lifecycle_object_types: ["fixed_asset_card"],
states: [
{
state_code: "capitalized",
state_label: "Капвложения отражены",
state_class: "initial",
entry_conditions: ["capitalization_document_exists"],
exit_conditions: ["accepted_for_accounting"],
is_terminal: false,
is_problematic: false,
business_meaning: "Объект зафиксирован как вложение."
},
{
state_code: "accepted_for_accounting",
state_label: "Принят к учету",
state_class: "active",
entry_conditions: ["acceptance_document_exists"],
exit_conditions: ["depreciation_active"],
is_terminal: false,
is_problematic: false,
business_meaning: "Объект переведен в основной контур учета."
},
{
state_code: "depreciation_active",
state_label: "Амортизация активна",
state_class: "active",
entry_conditions: ["depreciation_register_movement"],
exit_conditions: ["disposed"],
is_terminal: false,
is_problematic: false,
business_meaning: "Жизненный цикл ОС идет штатно."
},
{
state_code: "contradictory_asset_state",
state_label: "Противоречивый статус ОС",
state_class: "problematic",
entry_conditions: ["posting_mismatch_or_wrong_path"],
exit_conditions: ["depreciation_active"],
is_terminal: false,
is_problematic: true,
business_meaning: "Статус ОС формально есть, но смыслово противоречив."
},
{
state_code: "disposed",
state_label: "Выбыл",
state_class: "terminal",
entry_conditions: ["disposal_document_exists"],
exit_conditions: [],
is_terminal: true,
is_problematic: false,
business_meaning: "Жизненный цикл ОС завершен."
}
],
transitions: [],
defects: []
},
vat_flow: {
schema_version: LIFECYCLE_MODEL_SCHEMA_VERSION,
lifecycle_domain: "vat_flow",
lifecycle_object_types: ["vat_document_chain"],
states: [
{
state_code: "vat_registered",
state_label: "НДС отражен документно",
state_class: "initial",
entry_conditions: ["invoice_registered"],
exit_conditions: ["vat_reflected"],
is_terminal: false,
is_problematic: false,
business_meaning: "Сформирован первичный документный слой НДС."
},
{
state_code: "vat_reflected",
state_label: "НДС отражен в учете",
state_class: "active",
entry_conditions: ["vat_register_movement"],
exit_conditions: ["vat_deducted"],
is_terminal: false,
is_problematic: false,
business_meaning: "НДС проходит штатную стадию отражения."
},
{
state_code: "vat_deducted",
state_label: "НДС принят к вычету",
state_class: "terminal",
entry_conditions: ["deduction_confirmed"],
exit_conditions: [],
is_terminal: true,
is_problematic: false,
business_meaning: "НДС-цепочка завершена корректно."
},
{
state_code: "vat_conflict",
state_label: "Конфликт НДС-цепочки",
state_class: "problematic",
entry_conditions: ["cross_branch_inconsistency"],
exit_conditions: ["vat_reflected"],
is_terminal: false,
is_problematic: true,
business_meaning: "Бухгалтерская и налоговая ветки расходятся."
}
],
transitions: [],
defects: []
},
period_close: {
schema_version: LIFECYCLE_MODEL_SCHEMA_VERSION,
lifecycle_domain: "period_close",
lifecycle_object_types: ["period_close_blocker"],
states: [
{
state_code: "preclose_checks",
state_label: "Предзакрытие",
state_class: "active",
entry_conditions: ["period_scope_detected"],
exit_conditions: ["close_ready"],
is_terminal: false,
is_problematic: false,
business_meaning: "Идет проверка готовности периода."
},
{
state_code: "close_ready",
state_label: "Готов к закрытию",
state_class: "active",
entry_conditions: ["no_blockers_detected"],
exit_conditions: ["close_completed"],
is_terminal: false,
is_problematic: false,
business_meaning: "Период может быть закрыт."
},
{
state_code: "close_completed",
state_label: "Закрытие завершено",
state_class: "terminal",
entry_conditions: ["close_operation_done"],
exit_conditions: [],
is_terminal: true,
is_problematic: false,
business_meaning: "Период закрыт."
},
{
state_code: "close_blocked",
state_label: "Закрытие заблокировано",
state_class: "problematic",
entry_conditions: ["period_close_risk_or_stale_state"],
exit_conditions: ["close_ready"],
is_terminal: false,
is_problematic: true,
business_meaning: "Есть lifecycle-дефекты, влияющие на закрытие."
},
{
state_code: "close_contradicted",
state_label: "Закрыт формально, но с противоречием",
state_class: "problematic",
entry_conditions: ["misclosed_or_cross_branch_conflict"],
exit_conditions: ["close_completed"],
is_terminal: false,
is_problematic: true,
business_meaning: "Формальное закрытие не согласовано с фактическими ветками."
}
],
transitions: [],
defects: []
}
};
const SHARED_DEFECTS: LifecycleDefectDefinition[] = [
{
defect_code: "missing_expected_transition",
defect_class: "path",
severity_hint: "medium",
business_meaning: "Ожидаемый переход не произошел.",
evidence_requirements: ["expected_state", "missing_transition_signal"],
period_impact_potential: "indirect"
},
{
defect_code: "invalid_transition",
defect_class: "path",
severity_hint: "high",
business_meaning: "Переход произошел по некорректному пути.",
evidence_requirements: ["invalid_transition_signal"],
period_impact_potential: "indirect"
},
{
defect_code: "stale_active_state",
defect_class: "timing",
severity_hint: "high",
business_meaning: "Объект завис в активном состоянии.",
evidence_requirements: ["stale_marker", "missing_transition_signal"],
period_impact_potential: "direct"
},
{
defect_code: "contradictory_state",
defect_class: "consistency",
severity_hint: "high",
business_meaning: "Статусы объекта противоречат друг другу.",
evidence_requirements: ["contradiction_signal"],
period_impact_potential: "direct"
},
{
defect_code: "premature_terminal_state",
defect_class: "closure",
severity_hint: "medium",
business_meaning: "Терминальное состояние наступило преждевременно.",
evidence_requirements: ["terminal_state", "missing_required_previous_state"],
period_impact_potential: "indirect"
},
{
defect_code: "misclosed_state",
defect_class: "closure",
severity_hint: "high",
business_meaning: "Контур формально закрыт, но закрыт неверно.",
evidence_requirements: ["wrong_closure_path"],
period_impact_potential: "direct"
},
{
defect_code: "orphan_intermediate_state",
defect_class: "path",
severity_hint: "medium",
business_meaning: "Промежуточная стадия осталась без корректного продолжения.",
evidence_requirements: ["intermediate_state_without_next"],
period_impact_potential: "indirect"
},
{
defect_code: "cross_branch_state_conflict",
defect_class: "consistency",
severity_hint: "high",
business_meaning: "Состояния соседних веток учета противоречат друг другу.",
evidence_requirements: ["cross_branch_conflict_signal"],
period_impact_potential: "direct"
}
];
for (const domain of STAGE3_LIFECYCLE_DOMAINS) {
LIFECYCLE_DOMAIN_MODELS[domain].defects = SHARED_DEFECTS;
}
class LifecycleRegistryImpl {
constructor(private readonly models: Record<LifecycleDomain, LifecycleDomainModel>) {}
public listDomains(): LifecycleDomain[] {
return STAGE3_LIFECYCLE_DOMAINS.slice();
}
public getDomain(domain: LifecycleDomain): LifecycleDomainModel {
return this.models[domain];
}
}
export const LifecycleRegistry = new LifecycleRegistryImpl(LIFECYCLE_DOMAIN_MODELS);
function inferLifecycleDomain(input: LifecycleResolverInput): LifecycleDomain {
const unitTokens = [
input.unit.problem_unit_type,
input.unit.business_defect_class,
input.unit.mechanism_summary,
input.unit.failed_expected_edge ?? "",
input.unit.expected_state ?? "",
input.unit.actual_state ?? "",
...input.unit.affected_accounts,
...input.unit.affected_entities,
...input.unit.affected_documents,
...input.unit.affected_counterparties,
...input.candidates.flatMap((item) => item.anomaly_patterns),
...input.candidates.flatMap((item) => item.relation_pattern_hits)
]
.join(" ")
.toLowerCase();
if (includesAny(unitTokens, [/\bnds\b/, /\bvat\b/, /\btax\b/, /cross[_\s-]?branch/, /\b19\b/, /\b68\b/])) {
return "vat_flow";
}
if (includesAny(unitTokens, [/\bperiod\b/, /\bclose\b/, /закрыт/, /reporting/]) || input.unit.problem_unit_type === "period_risk_cluster") {
return "period_close";
}
if (includesAny(unitTokens, [/deferred/, /writeoff/, /рбп/, /\b97\b/])) {
return "deferred_expense";
}
if (includesAny(unitTokens, [/fixed[_\s-]?asset/, /амортиз/, /ос\b/, /\b01\b/, /\b02\b/, /\b08\b/])) {
return "fixed_asset";
}
if (includesAny(unitTokens, [/buyer/, /customer/, /дебитор/, /\b62\b/])) {
return "customer_settlement";
}
return "bank_settlement";
}
function inferCurrentState(domain: LifecycleDomain, input: LifecycleResolverInput): string {
const explicitActual = input.unit.actual_state?.trim();
if (explicitActual) {
return explicitActual;
}
const anomalies = input.candidates.flatMap((item) => item.anomaly_patterns).map((item) => item.toLowerCase());
const relations = input.candidates.flatMap((item) => item.relation_pattern_hits).map((item) => item.toLowerCase());
const hasStale = hasToken(anomalies, /(no_continuation|stale|tail|missing_link|broken_lifecycle|partially_linked)/);
const hasInvalid = hasToken(anomalies, /(posting_mismatch|wrong_document_type|cross_domain_inconsistency|misclose|cross_branch)/);
if (domain === "bank_settlement") {
if (hasInvalid) return "misclosed_payment";
if (hasStale) return "stale_unlinked_payment";
if (hasToken(relations, /payment_to_settlement/)) return "bank_recorded";
return "initiated_payment";
}
if (domain === "customer_settlement") {
if (hasStale) return "stale_receivable";
if (hasToken(relations, /payment|settlement/)) return "payment_recorded";
return "invoice_issued";
}
if (domain === "deferred_expense") {
if (hasStale) return "overdue_writeoff";
if (hasToken(relations, /writeoff|partial/)) return "partially_written_off";
return "recognized";
}
if (domain === "fixed_asset") {
if (hasInvalid) return "contradictory_asset_state";
if (hasToken(relations, /depreciation|amort/)) return "depreciation_active";
if (hasToken(relations, /accept|учет/)) return "accepted_for_accounting";
return "capitalized";
}
if (domain === "vat_flow") {
if (hasInvalid || hasToken(anomalies, /cross_branch|inconsistency/)) return "vat_conflict";
if (hasToken(relations, /invoice_to_vat|vat/)) return "vat_reflected";
return "vat_registered";
}
if (hasInvalid) return "close_contradicted";
if (hasStale || input.unit.period_impact?.impact_class === "close_risk") return "close_blocked";
return "preclose_checks";
}
function inferExpectedState(domain: LifecycleDomain, input: LifecycleResolverInput): string {
const explicitExpected = input.unit.expected_state?.trim();
if (explicitExpected) {
return explicitExpected;
}
return defaultExpectedState(domain);
}
function inferMissingTransition(input: LifecycleResolverInput): string | null {
if (typeof input.unit.failed_expected_edge === "string" && input.unit.failed_expected_edge.trim().length > 0) {
return input.unit.failed_expected_edge.trim();
}
const anomalies = input.candidates.flatMap((item) => item.anomaly_patterns).join(" ").toLowerCase();
if (/(missing_link|no_continuation|broken_lifecycle|tail|unresolved)/.test(anomalies)) {
return "expected_transition_not_observed";
}
return null;
}
function inferInvalidTransition(input: LifecycleResolverInput): string | null {
const anomalies = input.candidates.flatMap((item) => item.anomaly_patterns).join(" ").toLowerCase();
if (/(cross_branch|cross_domain_inconsistency)/.test(anomalies)) {
return "cross_branch_conflict_transition";
}
if (/(wrong_document_type|posting_mismatch|misclose)/.test(anomalies)) {
return "invalid_document_or_posting_transition";
}
return null;
}
export function classifyLifecycleDefect(input: {
domain: LifecycleDomain;
currentState: string;
expectedState: string;
missingTransition: string | null;
invalidTransition: string | null;
periodCloseSensitive: boolean;
}): LifecycleDefectType | null {
const current = input.currentState.toLowerCase();
if (input.invalidTransition?.includes("cross_branch")) {
return "cross_branch_state_conflict";
}
if (input.invalidTransition) {
if (current.includes("misclosed") || input.domain === "period_close") {
return "misclosed_state";
}
return "invalid_transition";
}
if (input.missingTransition) {
if (current.includes("stale") || current.includes("overdue") || input.periodCloseSensitive) {
return "stale_active_state";
}
return "missing_expected_transition";
}
if (current.includes("contradict")) {
return "contradictory_state";
}
if (current.includes("closed") && !input.expectedState.toLowerCase().includes("closed")) {
return "premature_terminal_state";
}
if (input.currentState !== input.expectedState && !input.currentState.toLowerCase().includes("closed")) {
return "orphan_intermediate_state";
}
return null;
}
function resolutionConfidence(unitConfidence: ProblemConfidence, input: {
hasExplicitStates: boolean;
hasDefectSignal: boolean;
candidateCount: number;
hasSnapshotLimitations: boolean;
}): LifecycleConfidence {
let score = unitConfidence.score;
if (input.hasExplicitStates) score += 0.1;
if (input.hasDefectSignal) score += 0.08;
if (input.candidateCount >= 2) score += 0.05;
if (input.hasSnapshotLimitations) score -= 0.12;
const normalized = clampUnitScore(score);
return {
score: normalized,
grade: lifecycleConfidenceGrade(normalized)
};
}
function staleDurationHint(domain: LifecycleDomain, defect: LifecycleDefectType | null, input: LifecycleResolverInput): string | undefined {
const anomalies = input.candidates.flatMap((item) => item.anomaly_patterns).join(" ").toLowerCase();
if (defect !== "stale_active_state") {
return undefined;
}
if (/(period_boundary|period|close_risk)/.test(anomalies) || domain === "period_close") {
return "period_boundary_exceeded";
}
return "unknown_snapshot_window";
}
function lifecycleInterpretation(input: {
domain: LifecycleDomain;
currentState: string;
expectedState: string;
defect: LifecycleDefectType | null;
missingTransition: string | null;
invalidTransition: string | null;
}): string {
const base = `Текущая стадия: ${input.currentState}; ожидаемая стадия: ${input.expectedState}.`;
if (input.defect === "stale_active_state") {
return `${base} Объект завис во времени и не дошел до ожидаемого перехода.`;
}
if (input.defect === "misclosed_state") {
return `${base} Контур закрыт формально, но путь закрытия противоречит бухгалтерской логике.`;
}
if (input.defect === "cross_branch_state_conflict") {
return `${base} Между ветками домена ${input.domain} обнаружено противоречие состояний.`;
}
if (input.defect === "missing_expected_transition") {
return `${base} Не зафиксирован ожидаемый переход (${input.missingTransition ?? "unknown_transition"}).`;
}
if (input.defect === "invalid_transition") {
return `${base} Зафиксирован некорректный переход (${input.invalidTransition ?? "invalid_transition"}).`;
}
return `${base} Lifecycle-разрешение не выявило критичный дефект, но состояние требует наблюдения.`;
}
export function resolveLifecycle(input: LifecycleResolverInput): LifecycleResolution {
const lifecycle_domain = inferLifecycleDomain(input);
const currentState = inferCurrentState(lifecycle_domain, input);
const expectedState = inferExpectedState(lifecycle_domain, input);
const missingTransition = inferMissingTransition(input);
const invalidTransition = inferInvalidTransition(input);
const defect = classifyLifecycleDefect({
domain: lifecycle_domain,
currentState,
expectedState,
missingTransition,
invalidTransition,
periodCloseSensitive: input.unit.period_impact?.impact_class === "close_risk"
});
const evidenceIds = uniqueStrings(input.unit.evidence_pack, 8);
const limitations = uniqueStrings(
[
...input.unit.snapshot_limitations,
...(input.candidates.some((item) => item.confidence_hint === "low") ? ["low_confidence_candidates_present"] : []),
...(input.unit.actual_state ? [] : ["actual_state_inferred"]),
...(input.unit.expected_state ? [] : ["expected_state_inferred"])
],
8
);
const confidence = resolutionConfidence(input.unit.confidence, {
hasExplicitStates: Boolean(input.unit.actual_state || input.unit.expected_state),
hasDefectSignal: Boolean(defect || missingTransition || invalidTransition),
candidateCount: input.candidates.length,
hasSnapshotLimitations: limitations.length > 0
});
return {
lifecycle_object_id: `lcobj-${input.unit.problem_unit_id}`,
lifecycle_domain,
resolved_current_state: currentState,
resolved_expected_state: expectedState,
resolved_previous_states: [],
missing_transitions: missingTransition ? [missingTransition] : [],
invalid_transitions: invalidTransition ? [invalidTransition] : [],
detected_defects: defect ? [defect] : [],
state_confidence: confidence,
resolution_evidence: evidenceIds,
snapshot_limitations: limitations
};
}
function lifecycleRanking(defect: LifecycleDefectType | null, input: {
unit: ProblemUnit;
resolution: LifecycleResolution;
staleDuration?: string;
}): LifecycleRankingResult {
let score = input.unit.severity.score;
const basis: string[] = ["base_problem_severity"];
if (defect === "cross_branch_state_conflict") {
score += 0.55;
basis.push("cross_branch_conflict_weight");
} else if (defect === "misclosed_state") {
score += 0.45;
basis.push("misclosed_state_weight");
} else if (defect === "stale_active_state") {
score += 0.35;
basis.push("stale_duration_weight");
} else if (defect === "invalid_transition") {
score += 0.3;
basis.push("invalid_transition_weight");
} else if (defect === "missing_expected_transition") {
score += 0.25;
basis.push("missing_transition_weight");
}
if (input.staleDuration) {
score += 0.15;
basis.push("stale_duration_present");
}
if (input.unit.period_impact?.impact_class === "close_risk") {
score += 0.22;
basis.push("period_close_impact");
}
if (input.resolution.state_confidence.grade === "high") {
score += 0.08;
basis.push("state_confidence_weight");
}
return {
lifecycle_ranking_score: Number(score.toFixed(2)),
lifecycle_ranking_basis: basis
};
}
export function enrichProblemUnitLifecycle(input: LifecycleResolverInput): ProblemUnit {
const resolution = resolveLifecycle(input);
const defect = resolution.detected_defects[0] ?? null;
const staleDuration = staleDurationHint(resolution.lifecycle_domain, defect, input);
const ranking = lifecycleRanking(defect, {
unit: input.unit,
resolution,
staleDuration
});
return {
...input.unit,
lifecycle_domain: resolution.lifecycle_domain,
lifecycle_object_id: resolution.lifecycle_object_id,
current_lifecycle_state: resolution.resolved_current_state,
expected_lifecycle_state: resolution.resolved_expected_state,
...(resolution.missing_transitions.length > 0
? {
missing_transition: resolution.missing_transitions[0]
}
: {}),
...(resolution.invalid_transitions.length > 0
? {
invalid_transition: resolution.invalid_transitions[0]
}
: {}),
...(defect
? {
lifecycle_defect_type: defect
}
: {}),
...(staleDuration
? {
stale_duration: staleDuration
}
: {}),
lifecycle_confidence: resolution.state_confidence,
business_lifecycle_interpretation: lifecycleInterpretation({
domain: resolution.lifecycle_domain,
currentState: resolution.resolved_current_state,
expectedState: resolution.resolved_expected_state,
defect,
missingTransition: resolution.missing_transitions[0] ?? null,
invalidTransition: resolution.invalid_transitions[0] ?? null
}),
lifecycle_resolution: resolution,
lifecycle_ranking_score: ranking.lifecycle_ranking_score,
lifecycle_ranking_basis: ranking.lifecycle_ranking_basis
};
}
export function rankLifecycleProblemUnits(units: ProblemUnit[]): ProblemUnit[] {
return units
.slice()
.sort((left, right) => {
const rankDiff = (right.lifecycle_ranking_score ?? 0) - (left.lifecycle_ranking_score ?? 0);
if (rankDiff !== 0) return rankDiff;
const severityDiff = right.severity.score - left.severity.score;
if (severityDiff !== 0) return severityDiff;
return right.confidence.score - left.confidence.score;
});
}
@@ -13,6 +13,8 @@ import {
PROBLEM_UNIT_SCHEMA_VERSION,
PROBLEM_UNIT_SUMMARY_SCHEMA_VERSION
} from "../types/stage2ProblemUnits";
import { FEATURE_ASSISTANT_LIFECYCLE_RUNTIME_V1 } from "../config";
import { enrichProblemUnitLifecycle, rankLifecycleProblemUnits } from "./lifecycleRuntime";
type RetrievalResultType = "list" | "summary" | "object" | "chain" | "ranking";
@@ -486,6 +488,18 @@ function collapseSignature(unit: ProblemUnit): string {
return [unit.problem_unit_type, unit.business_defect_class, unit.failed_expected_edge ?? "none", backlink].join("|");
}
function preferredLifecycleSource(left: ProblemUnit, right: ProblemUnit): ProblemUnit {
const leftRank = left.lifecycle_ranking_score ?? 0;
const rightRank = right.lifecycle_ranking_score ?? 0;
if (rightRank > leftRank) {
return right;
}
if (leftRank > rightRank) {
return left;
}
return right.confidence.score > left.confidence.score ? right : left;
}
export function collapseDuplicates(units: ProblemUnit[]): {
problem_units: ProblemUnit[];
duplicate_collapses: number;
@@ -502,6 +516,7 @@ export function collapseDuplicates(units: ProblemUnit[]): {
}
duplicateCollapses += 1;
const preferredLifecycle = preferredLifecycleSource(existing, unit);
bySignature.set(signature, {
...existing,
evidence_pack: uniqueStrings([...existing.evidence_pack, ...unit.evidence_pack]),
@@ -521,7 +536,72 @@ export function collapseDuplicates(units: ProblemUnit[]): {
affected_contracts: uniqueStrings([...existing.affected_contracts, ...unit.affected_contracts]),
snapshot_limitations: uniqueStrings([...existing.snapshot_limitations, ...unit.snapshot_limitations]),
severity: unit.severity.score > existing.severity.score ? unit.severity : existing.severity,
confidence: unit.confidence.score > existing.confidence.score ? unit.confidence : existing.confidence
confidence: unit.confidence.score > existing.confidence.score ? unit.confidence : existing.confidence,
...(preferredLifecycle.lifecycle_domain
? {
lifecycle_domain: preferredLifecycle.lifecycle_domain
}
: {}),
...(preferredLifecycle.lifecycle_object_id
? {
lifecycle_object_id: preferredLifecycle.lifecycle_object_id
}
: {}),
...(preferredLifecycle.current_lifecycle_state
? {
current_lifecycle_state: preferredLifecycle.current_lifecycle_state
}
: {}),
...(preferredLifecycle.expected_lifecycle_state
? {
expected_lifecycle_state: preferredLifecycle.expected_lifecycle_state
}
: {}),
...(preferredLifecycle.missing_transition
? {
missing_transition: preferredLifecycle.missing_transition
}
: {}),
...(preferredLifecycle.invalid_transition
? {
invalid_transition: preferredLifecycle.invalid_transition
}
: {}),
...(preferredLifecycle.lifecycle_defect_type
? {
lifecycle_defect_type: preferredLifecycle.lifecycle_defect_type
}
: {}),
...(preferredLifecycle.stale_duration
? {
stale_duration: preferredLifecycle.stale_duration
}
: {}),
...(preferredLifecycle.lifecycle_confidence
? {
lifecycle_confidence: preferredLifecycle.lifecycle_confidence
}
: {}),
...(preferredLifecycle.business_lifecycle_interpretation
? {
business_lifecycle_interpretation: preferredLifecycle.business_lifecycle_interpretation
}
: {}),
...(preferredLifecycle.lifecycle_resolution
? {
lifecycle_resolution: preferredLifecycle.lifecycle_resolution
}
: {}),
...(preferredLifecycle.lifecycle_ranking_score !== undefined
? {
lifecycle_ranking_score: preferredLifecycle.lifecycle_ranking_score
}
: {}),
...(preferredLifecycle.lifecycle_ranking_basis
? {
lifecycle_ranking_basis: preferredLifecycle.lifecycle_ranking_basis
}
: {})
});
}
@@ -544,11 +624,21 @@ function buildSummary(units: ProblemUnit[], duplicateCollapses: number): Problem
medium: 0,
high: 0
};
const lifecycleDomainDistribution: NonNullable<ProblemUnitSummary["lifecycle_domain_distribution"]> = {};
const lifecycleDefectDistribution: NonNullable<ProblemUnitSummary["lifecycle_defect_distribution"]> = {};
let lifecycleEnrichedUnits = 0;
for (const unit of units) {
typeDistribution[unit.problem_unit_type] = (typeDistribution[unit.problem_unit_type] ?? 0) + 1;
severityDistribution[unit.severity.grade] += 1;
confidenceDistribution[unit.confidence.grade] += 1;
if (unit.lifecycle_domain) {
lifecycleEnrichedUnits += 1;
lifecycleDomainDistribution[unit.lifecycle_domain] = (lifecycleDomainDistribution[unit.lifecycle_domain] ?? 0) + 1;
}
if (unit.lifecycle_defect_type) {
lifecycleDefectDistribution[unit.lifecycle_defect_type] = (lifecycleDefectDistribution[unit.lifecycle_defect_type] ?? 0) + 1;
}
}
return {
@@ -559,7 +649,10 @@ function buildSummary(units: ProblemUnit[], duplicateCollapses: number): Problem
type_distribution: typeDistribution,
severity_distribution: severityDistribution,
confidence_distribution: confidenceDistribution,
primary_unit_type: units[0]?.problem_unit_type ?? null
primary_unit_type: units[0]?.problem_unit_type ?? null,
lifecycle_enriched_units: lifecycleEnrichedUnits,
lifecycle_domain_distribution: lifecycleDomainDistribution,
lifecycle_defect_distribution: lifecycleDefectDistribution
};
}
@@ -580,13 +673,24 @@ export function assembleProblemUnits(input: AssembleProblemUnitsInput): {
risk_factors: uniqueStrings(input.risk_factors ?? [])
});
const clusters = clusterCandidateEvidence(candidates);
const units = clusters.map((cluster, index) => buildProblemUnit(cluster, index));
const units = clusters.map((cluster, index) => {
const baseUnit = buildProblemUnit(cluster, index);
if (!FEATURE_ASSISTANT_LIFECYCLE_RUNTIME_V1) {
return baseUnit;
}
return enrichProblemUnitLifecycle({
unit: baseUnit,
candidates: cluster.candidates
});
});
const collapsed = collapseDuplicates(units);
const rankedUnits = FEATURE_ASSISTANT_LIFECYCLE_RUNTIME_V1
? rankLifecycleProblemUnits(collapsed.problem_units)
: collapsed.problem_units;
return {
candidate_evidence: candidates,
problem_units: collapsed.problem_units,
problem_unit_summary: buildSummary(collapsed.problem_units, collapsed.duplicate_collapses)
problem_units: rankedUnits,
problem_unit_summary: buildSummary(rankedUnits, collapsed.duplicate_collapses)
};
}
@@ -104,6 +104,9 @@ function mergeSummaryWithProblemUnitMeta(
duplicateCollapses: number;
severityDistribution: Record<string, number>;
confidenceDistribution: Record<string, number>;
lifecycleEnrichedUnits: number;
lifecycleDomainDistribution: Record<string, number>;
lifecycleDefectDistribution: Record<string, number>;
}
): Record<string, unknown> {
return {
@@ -114,7 +117,10 @@ function mergeSummaryWithProblemUnitMeta(
problem_unit_types: input.unitTypes,
problem_unit_duplicate_collapses: input.duplicateCollapses,
problem_unit_severity_distribution: input.severityDistribution,
problem_unit_confidence_distribution: input.confidenceDistribution
problem_unit_confidence_distribution: input.confidenceDistribution,
lifecycle_enriched_units: input.lifecycleEnrichedUnits,
problem_unit_lifecycle_domain_distribution: input.lifecycleDomainDistribution,
problem_unit_lifecycle_defect_distribution: input.lifecycleDefectDistribution
};
}
@@ -526,7 +532,10 @@ export function normalizeRetrievalResult(
unitTypes: assembled.problem_unit_summary.unit_types,
duplicateCollapses: assembled.problem_unit_summary.duplicate_collapses,
severityDistribution: assembled.problem_unit_summary.severity_distribution,
confidenceDistribution: assembled.problem_unit_summary.confidence_distribution
confidenceDistribution: assembled.problem_unit_summary.confidence_distribution,
lifecycleEnrichedUnits: assembled.problem_unit_summary.lifecycle_enriched_units ?? 0,
lifecycleDomainDistribution: (assembled.problem_unit_summary.lifecycle_domain_distribution ?? {}) as Record<string, number>,
lifecycleDefectDistribution: (assembled.problem_unit_summary.lifecycle_defect_distribution ?? {}) as Record<string, number>
});
return {
@@ -21,7 +21,10 @@ export type AssistantReplyType =
export type RetrievalResultStatus = "ok" | "empty" | "partial" | "error";
export type RetrievalResultType = "list" | "summary" | "object" | "chain" | "ranking";
export type RetrievalConfidence = "high" | "medium" | "low";
export type AssistantProblemAnswerMode = "stage1_policy_v11" | "stage2_problem_centric_v1";
export type AssistantProblemAnswerMode =
| "stage1_policy_v11"
| "stage2_problem_centric_v1"
| "stage3_lifecycle_aware_v1";
export interface AssistantRequirement {
requirement_id: string;
@@ -1,5 +1,6 @@
import type { InvestigationState } from "./stage1Contracts";
import type { EvidenceSourceRef } from "./stage1Contracts";
import type { LifecycleConfidence, LifecycleDefectType, LifecycleDomain, LifecycleResolution } from "./stage3Lifecycle";
export const CANDIDATE_EVIDENCE_SCHEMA_VERSION = "candidate_evidence_v0_1" as const;
export const PROBLEM_UNIT_SCHEMA_VERSION = "problem_unit_v0_1" as const;
@@ -75,6 +76,19 @@ export interface ProblemUnit {
evidence_pack: string[];
entity_backlinks: ProblemUnitEntityBacklink[];
snapshot_limitations: string[];
lifecycle_domain?: LifecycleDomain;
lifecycle_object_id?: string;
current_lifecycle_state?: string;
expected_lifecycle_state?: string;
missing_transition?: string;
invalid_transition?: string;
lifecycle_defect_type?: LifecycleDefectType;
stale_duration?: string;
lifecycle_confidence?: LifecycleConfidence;
business_lifecycle_interpretation?: string;
lifecycle_resolution?: LifecycleResolution;
lifecycle_ranking_score?: number;
lifecycle_ranking_basis?: string[];
}
export interface ProblemUnitSummary {
@@ -86,6 +100,9 @@ export interface ProblemUnitSummary {
severity_distribution: Record<ProblemSeverityGrade, number>;
confidence_distribution: Record<ProblemConfidenceGrade, number>;
primary_unit_type: ProblemUnitType | null;
lifecycle_enriched_units?: number;
lifecycle_domain_distribution?: Partial<Record<LifecycleDomain, number>>;
lifecycle_defect_distribution?: Partial<Record<LifecycleDefectType, number>>;
}
export interface InvestigationProblemUnitState {
@@ -0,0 +1,90 @@
export const LIFECYCLE_MODEL_SCHEMA_VERSION = "lifecycle_model_v0_1" as const;
export const STAGE3_LIFECYCLE_DOMAINS = [
"bank_settlement",
"customer_settlement",
"deferred_expense",
"fixed_asset",
"vat_flow",
"period_close"
] as const;
export type LifecycleDomain = (typeof STAGE3_LIFECYCLE_DOMAINS)[number];
export type LifecycleStateClass = "initial" | "active" | "terminal" | "problematic";
export type LifecycleTransitionType = "expected" | "optional" | "forbidden";
export const LIFECYCLE_DEFECT_TYPES = [
"missing_expected_transition",
"invalid_transition",
"stale_active_state",
"contradictory_state",
"premature_terminal_state",
"misclosed_state",
"orphan_intermediate_state",
"cross_branch_state_conflict"
] as const;
export type LifecycleDefectType = (typeof LIFECYCLE_DEFECT_TYPES)[number];
export interface LifecycleStateDefinition {
state_code: string;
state_label: string;
state_class: LifecycleStateClass;
entry_conditions: string[];
exit_conditions: string[];
is_terminal: boolean;
is_problematic: boolean;
business_meaning: string;
}
export interface LifecycleTransitionDefinition {
from_state: string;
to_state: string;
transition_type: LifecycleTransitionType;
required_evidence: string[];
optional_evidence: string[];
forbidden_conditions: string[];
business_meaning: string;
}
export interface LifecycleDefectDefinition {
defect_code: LifecycleDefectType;
defect_class: "timing" | "path" | "consistency" | "closure";
severity_hint: "low" | "medium" | "high";
business_meaning: string;
evidence_requirements: string[];
period_impact_potential: "none" | "indirect" | "direct";
}
export interface LifecycleDomainModel {
schema_version: typeof LIFECYCLE_MODEL_SCHEMA_VERSION;
lifecycle_domain: LifecycleDomain;
lifecycle_object_types: string[];
states: LifecycleStateDefinition[];
transitions: LifecycleTransitionDefinition[];
defects: LifecycleDefectDefinition[];
}
export type LifecycleConfidenceGrade = "low" | "medium" | "high";
export interface LifecycleConfidence {
score: number;
grade: LifecycleConfidenceGrade;
}
export interface LifecycleResolution {
lifecycle_object_id: string;
lifecycle_domain: LifecycleDomain;
resolved_current_state: string;
resolved_expected_state: string;
resolved_previous_states: string[];
missing_transitions: string[];
invalid_transitions: string[];
detected_defects: LifecycleDefectType[];
state_confidence: LifecycleConfidence;
resolution_evidence: string[];
snapshot_limitations: string[];
}
@@ -0,0 +1,232 @@
import { describe, expect, it } from "vitest";
import { composeAssistantAnswer } from "../src/services/answerComposer";
import type { AnswerGroundingCheck, RequirementCoverageReport, UnifiedRetrievalResult } from "../src/types/assistant";
import type { ProblemUnit, ProblemUnitSummary } 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(partial = false): RequirementCoverageReport {
return {
requirements_total: 1,
requirements_covered: partial ? 0 : 1,
requirements_uncovered: partial ? ["R1"] : [],
requirements_partially_covered: partial ? ["R1"] : [],
clarification_needed_for: [],
out_of_scope_requirements: []
};
}
function buildGrounding(status: AnswerGroundingCheck["status"]): AnswerGroundingCheck {
return {
status,
route_subject_match: true,
missing_requirements: status === "partial" ? ["R1"] : [],
reasons: status === "partial" ? ["Coverage is partial for lifecycle-focused analysis."] : [],
why_included_summary: ["synthetic-test"],
selection_reason_summary: ["synthetic-test"]
};
}
function buildLifecycleProblemUnit(): ProblemUnit {
return {
schema_version: "problem_unit_v0_1",
problem_unit_id: "pu-lc-1",
problem_unit_type: "lifecycle_anomaly_node",
title: "Lifecycle anomaly node detected",
mechanism_summary: "Mechanism candidate: expected transition is missing.",
business_defect_class: "missing_expected_transition",
severity: {
score: 0.84,
grade: "high"
},
confidence: {
score: 0.68,
grade: "medium"
},
affected_entities: ["Document:DOC-1"],
affected_documents: ["Document:DOC-1"],
affected_postings: [],
affected_accounts: ["51", "60"],
affected_counterparties: ["Counterparty:CP-1"],
affected_contracts: ["Contract:CTR-1"],
expected_state: "settlement_closed",
actual_state: "stale_unlinked_payment",
failed_expected_edge: "payment_to_settlement",
period_impact: {
is_period_sensitive: true,
impact_class: "close_risk"
},
evidence_pack: ["cand-1"],
entity_backlinks: [{ entity: "Document", id: "DOC-1" }],
snapshot_limitations: [],
lifecycle_domain: "bank_settlement",
lifecycle_object_id: "lcobj-pu-lc-1",
current_lifecycle_state: "stale_unlinked_payment",
expected_lifecycle_state: "settlement_closed",
missing_transition: "payment_to_settlement",
lifecycle_defect_type: "stale_active_state",
stale_duration: "period_boundary_exceeded",
lifecycle_confidence: {
score: 0.79,
grade: "high"
},
business_lifecycle_interpretation:
"Текущая стадия: stale_unlinked_payment; ожидаемая стадия: settlement_closed. Объект завис во времени и не дошел до ожидаемого перехода.",
lifecycle_ranking_score: 1.41,
lifecycle_ranking_basis: ["base_problem_severity", "stale_duration_weight", "period_close_impact"]
};
}
function buildSummary(units: ProblemUnit[]): ProblemUnitSummary {
const unitTypes = Array.from(new Set(units.map((item) => item.problem_unit_type)));
return {
schema_version: "problem_unit_summary_v0_1",
units_total: units.length,
duplicate_collapses: 0,
unit_types: unitTypes,
type_distribution: {
lifecycle_anomaly_node: units.length
},
severity_distribution: {
low: 0,
medium: 0,
high: units.length
},
confidence_distribution: {
low: 0,
medium: units.length,
high: 0
},
primary_unit_type: unitTypes[0] ?? null,
lifecycle_enriched_units: units.length,
lifecycle_domain_distribution: {
bank_settlement: units.length
},
lifecycle_defect_distribution: {
stale_active_state: units.length
}
};
}
function buildRetrievalResult(problemUnits: ProblemUnit[]): UnifiedRetrievalResult {
return {
fragment_id: "F1",
requirement_ids: ["R1"],
route: "hybrid_store_plus_live",
status: "ok",
result_type: "chain",
items: [
{
counterparty_id: "CP-1",
operations_count: 5,
document_refs_count: 3
}
],
raw_entities: [],
candidate_evidence: [],
problem_units: problemUnits,
problem_unit_summary: buildSummary(problemUnits),
summary: {
broad_query_detected: true,
broad_result_flag: true,
minimum_evidence_failed: false,
degraded_to: "partial",
narrowing_strength: "weak"
},
evidence: [
{
evidence_id: "ev-1",
claim_ref: "requirement:R1",
source_type: "retrieval_item",
source_ref: {
schema_version: "evidence_source_ref_v1",
namespace: "snapshot_2020",
entity: "Document",
id: "DOC-1",
period: "2020-06",
canonical_ref: "evidence_source_ref_v1|snapshot_2020|document|doc-1|2020-06"
},
pointer: {
fragment_id: "F1",
route: "hybrid_store_plus_live",
source: {
namespace: "snapshot_2020",
entity: "Document",
id: "DOC-1",
period: "2020-06"
},
locator: {
field_path: "risk_score",
item_index: 0
}
},
evidence_kind: "mechanism_link",
mechanism_note: "failed_edge=payment_to_settlement",
confidence: "medium",
limitation: null,
payload: {
risk_score: 5
}
}
],
why_included: ["synthetic-test"],
selection_reason: ["synthetic-test"],
risk_factors: ["broken_chain"],
business_interpretation: ["synthetic-test"],
confidence: "medium",
limitations: [],
errors: []
};
}
describe("assistant lifecycle-aware answer mode v1", () => {
it("promotes stage3 lifecycle mode when lifecycle answer flag is enabled", () => {
const units = [buildLifecycleProblemUnit()];
const output = composeAssistantAnswer({
userMessage: "Проверь, где зависли платежи по 51/60 и какой переход не завершился.",
routeSummary: buildRouteSummary(),
retrievalResults: [buildRetrievalResult(units)],
requirements: [
{
requirement_id: "R1",
source_fragment_id: "F1",
requirement_text: "Проверить lifecycle-переход",
subject_tokens: ["chain", "account_51", "account_60"],
status: "covered",
route: "hybrid_store_plus_live"
}
],
coverageReport: buildCoverage(true),
groundingCheck: buildGrounding("partial"),
enableAnswerPolicyV11: true,
enableProblemCentricAnswerV1: true,
enableLifecycleAnswerV1: true
});
expect(output.problem_centric_answer_applied).toBe(true);
expect(output.problem_answer_mode).toBe("stage3_lifecycle_aware_v1");
expect(output.answer_structure_v11?.answer_summary).toMatch(/lifecycle|Lifecycle/i);
expect(output.answer_structure_v11?.direct_answer).toContain("current=stale_unlinked_payment");
expect(output.answer_structure_v11?.direct_answer).toContain("expected=settlement_closed");
expect(output.answer_structure_v11?.direct_answer).toContain("defect=stale_active_state");
});
});
@@ -0,0 +1,162 @@
import { describe, expect, it } from "vitest";
import type { CandidateEvidenceItem, ProblemUnit } from "../src/types/stage2ProblemUnits";
import { enrichProblemUnitLifecycle, rankLifecycleProblemUnits, resolveLifecycle } from "../src/services/lifecycleRuntime";
function buildBaseProblemUnit(input: {
id: string;
type: ProblemUnit["problem_unit_type"];
expectedState?: string;
actualState?: string;
failedEdge?: string;
impactCloseRisk?: boolean;
severity?: number;
confidence?: number;
}): ProblemUnit {
const severityScore = input.severity ?? 0.62;
const confidenceScore = input.confidence ?? 0.58;
const severityGrade: ProblemUnit["severity"]["grade"] = severityScore >= 0.7 ? "high" : severityScore >= 0.4 ? "medium" : "low";
const confidenceGrade: ProblemUnit["confidence"]["grade"] =
confidenceScore >= 0.75 ? "high" : confidenceScore >= 0.45 ? "medium" : "low";
return {
schema_version: "problem_unit_v0_1",
problem_unit_id: input.id,
problem_unit_type: input.type,
title: "Synthetic unit",
mechanism_summary: "Synthetic mechanism",
business_defect_class: "broken_lifecycle",
severity: {
score: Number(severityScore.toFixed(2)),
grade: severityGrade
},
confidence: {
score: Number(confidenceScore.toFixed(2)),
grade: confidenceGrade
},
affected_entities: ["Document:DOC-1"],
affected_documents: ["Document:DOC-1"],
affected_postings: [],
affected_accounts: ["51", "60"],
affected_counterparties: [],
affected_contracts: [],
...(input.expectedState
? {
expected_state: input.expectedState
}
: {}),
...(input.actualState
? {
actual_state: input.actualState
}
: {}),
...(input.failedEdge
? {
failed_expected_edge: input.failedEdge
}
: {}),
...(input.impactCloseRisk
? {
period_impact: {
is_period_sensitive: true,
impact_class: "close_risk" as const
}
}
: {}),
evidence_pack: ["cand-1"],
entity_backlinks: [{ entity: "Document", id: "DOC-1" }],
snapshot_limitations: []
};
}
function buildCandidate(input: {
id: string;
anomalies?: string[];
relations?: string[];
confidence?: "high" | "medium" | "low";
}): CandidateEvidenceItem {
return {
schema_version: "candidate_evidence_v0_1",
candidate_id: input.id,
route: "hybrid_store_plus_live",
source_ref: {
schema_version: "evidence_source_ref_v1",
namespace: "snapshot_2020",
entity: "Document",
id: "DOC-1",
period: "2020-06",
canonical_ref: "evidence_source_ref_v1|snapshot_2020|document|doc-1|2020-06"
},
relation_pattern_hits: input.relations ?? [],
anomaly_patterns: input.anomalies ?? [],
entity_backlinks: [{ entity: "Document", id: "DOC-1" }],
confidence_hint: input.confidence ?? "medium"
};
}
describe("lifecycle runtime stage3", () => {
it("resolves stale missing transition for bank settlement chains", () => {
const unit = buildBaseProblemUnit({
id: "pu-bank-1",
type: "broken_chain_segment",
expectedState: "settlement_closed",
failedEdge: "payment_to_settlement"
});
const candidates = [
buildCandidate({
id: "cand-1",
relations: ["payment_to_settlement", "statement_to_document"],
anomalies: ["broken_lifecycle", "missing_link", "no_continuation"]
})
];
const resolution = resolveLifecycle({ unit, candidates });
expect(resolution.lifecycle_domain).toBe("bank_settlement");
expect(resolution.missing_transitions.length).toBeGreaterThan(0);
expect(resolution.detected_defects[0]).toBe("stale_active_state");
});
it("classifies cross-branch conflicts for VAT flow", () => {
const unit = buildBaseProblemUnit({
id: "pu-vat-1",
type: "cross_branch_inconsistency_cluster"
});
const candidates = [
buildCandidate({
id: "cand-1",
anomalies: ["cross_branch_inconsistency", "vat_chain_conflict"],
relations: ["invoice_to_vat"]
})
];
const enriched = enrichProblemUnitLifecycle({ unit, candidates });
expect(enriched.lifecycle_domain).toBe("vat_flow");
expect(enriched.lifecycle_defect_type).toBe("cross_branch_state_conflict");
expect(typeof enriched.business_lifecycle_interpretation).toBe("string");
});
it("sorts lifecycle-ranked units by lifecycle severity and ranking score", () => {
const stale = enrichProblemUnitLifecycle({
unit: buildBaseProblemUnit({
id: "pu-stale",
type: "period_risk_cluster",
impactCloseRisk: true,
expectedState: "close_completed"
}),
candidates: [buildCandidate({ id: "cand-stale", anomalies: ["period_close_risk", "missing_link", "no_continuation"] })]
});
const generic = enrichProblemUnitLifecycle({
unit: buildBaseProblemUnit({
id: "pu-generic",
type: "document_conflict",
severity: 0.4,
confidence: 0.45
}),
candidates: [buildCandidate({ id: "cand-generic", anomalies: ["anomaly_signal"] })]
});
const ranked = rankLifecycleProblemUnits([generic, stale]);
expect(ranked[0].problem_unit_id).toBe("pu-stale");
expect((ranked[0].lifecycle_ranking_score ?? 0) >= (ranked[1].lifecycle_ranking_score ?? 0)).toBe(true);
});
});
@@ -0,0 +1,116 @@
import { afterEach, describe, expect, it, vi } from "vitest";
const PROBLEM_UNITS_FLAG = "FEATURE_ASSISTANT_PROBLEM_UNITS_V1";
const LIFECYCLE_RUNTIME_FLAG = "FEATURE_ASSISTANT_LIFECYCLE_RUNTIME_V1";
const ORIGINAL_PROBLEM_UNITS_FLAG = process.env[PROBLEM_UNITS_FLAG];
const ORIGINAL_LIFECYCLE_RUNTIME_FLAG = process.env[LIFECYCLE_RUNTIME_FLAG];
function restoreFlags(): void {
if (ORIGINAL_PROBLEM_UNITS_FLAG === undefined) {
delete process.env[PROBLEM_UNITS_FLAG];
} else {
process.env[PROBLEM_UNITS_FLAG] = ORIGINAL_PROBLEM_UNITS_FLAG;
}
if (ORIGINAL_LIFECYCLE_RUNTIME_FLAG === undefined) {
delete process.env[LIFECYCLE_RUNTIME_FLAG];
} else {
process.env[LIFECYCLE_RUNTIME_FLAG] = ORIGINAL_LIFECYCLE_RUNTIME_FLAG;
}
}
async function normalizeWithFlags(input: {
problemUnitsFlag: "0" | "1";
lifecycleRuntimeFlag: "0" | "1";
}) {
process.env[PROBLEM_UNITS_FLAG] = input.problemUnitsFlag;
process.env[LIFECYCLE_RUNTIME_FLAG] = input.lifecycleRuntimeFlag;
vi.resetModules();
const { normalizeRetrievalResult } = await import("../src/services/retrievalResultNormalizer");
return normalizeRetrievalResult("F1", ["R1"], "hybrid_store_plus_live", {
status: "ok",
result_type: "list",
items: [
{
source_entity: "Document",
source_id: "DOC-1",
risk_score: 4
}
],
summary: {
broad_query_detected: false
},
evidence: [
{
evidence_id: "ev-1",
claim_ref: "requirement:R1",
source_type: "retrieval_item",
pointer: {
fragment_id: "F1",
route: "hybrid_store_plus_live",
source: {
namespace: "snapshot_2020",
entity: "Document",
id: "DOC-1",
period: "2020-06"
},
locator: {
field_path: "risk_score",
item_index: 0
}
},
failed_expected_edge: "payment_to_settlement",
anomaly_patterns: ["period_close_risk", "broken_lifecycle", "missing_link", "no_continuation"],
confidence: "medium"
}
],
why_included: ["test"],
selection_reason: ["test"],
risk_factors: ["test"],
business_interpretation: ["test"],
confidence: "medium",
limitations: [],
errors: []
});
}
describe.sequential("retrieval lifecycle runtime rollout", () => {
afterEach(() => {
restoreFlags();
vi.resetModules();
});
it("keeps Stage 2 payload when lifecycle runtime flag is OFF", async () => {
const result = await normalizeWithFlags({
problemUnitsFlag: "1",
lifecycleRuntimeFlag: "0"
});
expect(Array.isArray(result.problem_units)).toBe(true);
const first = result.problem_units?.[0];
expect(first?.lifecycle_domain).toBeUndefined();
expect(first?.lifecycle_defect_type).toBeUndefined();
expect(result.summary.lifecycle_enriched_units).toBe(0);
});
it("adds lifecycle fields to problem units when lifecycle runtime flag is ON", async () => {
const result = await normalizeWithFlags({
problemUnitsFlag: "1",
lifecycleRuntimeFlag: "1"
});
expect(Array.isArray(result.problem_units)).toBe(true);
expect(result.problem_units?.length).toBeGreaterThan(0);
const first = result.problem_units?.[0];
expect(first?.lifecycle_domain).toBeTruthy();
expect(first?.current_lifecycle_state).toBeTruthy();
expect(first?.expected_lifecycle_state).toBeTruthy();
expect(first?.lifecycle_defect_type).toBeTruthy();
expect(typeof first?.business_lifecycle_interpretation).toBe("string");
expect(result.problem_unit_summary?.lifecycle_enriched_units).toBeGreaterThan(0);
expect(result.summary.lifecycle_enriched_units).toBeGreaterThan(0);
expect(result.summary.problem_unit_lifecycle_domain_distribution).toBeTruthy();
expect(result.summary.problem_unit_lifecycle_defect_distribution).toBeTruthy();
});
});
@@ -0,0 +1,135 @@
{
"run_id": "eval-0M5PNZp9FY",
"timestamp": "2026-03-26T12:41:47.018Z",
"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": 33.33,
"no_route_fragment_rate": 66.67,
"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": 3,
"checks_passed": 3
},
"route_distribution": {
"store_feature_risk": 1,
"no_route": 2
},
"fallback_distribution": {
"none": 1,
"out_of_scope": 2
},
"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": "JOdfhbRQuPyASw",
"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": "2ynm4eijBPvh5R",
"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": "out_of_scope",
"predicted_route_status": "no_route",
"expected_route_status": null,
"predicted_no_route_reason": "insufficient_specificity",
"expected_no_route_reason": null,
"predicted_clarification_required": false,
"expected_clarification_required": null,
"executable_with_soft_assumptions_fragments": 0,
"trace_id": "2ooQZ_w1Ek5E9S",
"request_count_for_case": 0
}
]
}
@@ -0,0 +1,135 @@
{
"run_id": "eval-CcP-swdiJD",
"timestamp": "2026-03-26T12:40:31.062Z",
"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": 33.33,
"no_route_fragment_rate": 66.67,
"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": 3,
"checks_passed": 3
},
"route_distribution": {
"store_feature_risk": 1,
"no_route": 2
},
"fallback_distribution": {
"none": 1,
"out_of_scope": 2
},
"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": "IHevFSGCO0dpRm",
"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": "XMpXdBJSLHT1HO",
"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": "out_of_scope",
"predicted_route_status": "no_route",
"expected_route_status": null,
"predicted_no_route_reason": "insufficient_specificity",
"expected_no_route_reason": null,
"predicted_clarification_required": false,
"expected_clarification_required": null,
"executable_with_soft_assumptions_fragments": 0,
"trace_id": "DEbkFEJ6DCCgZY",
"request_count_for_case": 0
}
]
}
@@ -0,0 +1,111 @@
{
"run_id": "eval-EDvNkoALBS",
"timestamp": "2026-03-26T12:41:09.015Z",
"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": {
"store_feature_risk": 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": "Kmd3qLt9xwLLDl",
"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": "CZwGn0tv9Drnrr",
"request_count_for_case": 0
}
]
}
@@ -0,0 +1,111 @@
{
"run_id": "eval-FRq1sdepEh",
"timestamp": "2026-03-26T12:41:47.585Z",
"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": {
"store_feature_risk": 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": "c4ZViCUU5BgSZA",
"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": "hPtH-BgauVnlzg",
"request_count_for_case": 0
}
]
}
@@ -0,0 +1,111 @@
{
"run_id": "eval-Y6lFA96ywu",
"timestamp": "2026-03-26T12:40:32.436Z",
"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": {
"store_feature_risk": 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": "YAs0TDXGr0ZVNq",
"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": "aPDtpysxCNUiwy",
"request_count_for_case": 0
}
]
}
@@ -0,0 +1,111 @@
{
"run_id": "eval-YfhJafdzmT",
"timestamp": "2026-03-26T12:41:09.003Z",
"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": {
"store_feature_risk": 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": "1ueLJSRaJedoxg",
"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": "f_76vMb-8Q45n2",
"request_count_for_case": 0
}
]
}
@@ -0,0 +1,111 @@
{
"run_id": "eval-iJqHDv7Xnw",
"timestamp": "2026-03-26T12:40:32.418Z",
"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": {
"store_feature_risk": 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": "ja-YiQxiELJhva",
"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": "GHPHZ1GIZLnraN",
"request_count_for_case": 0
}
]
}
@@ -0,0 +1,135 @@
{
"run_id": "eval-nARkOiG8ly",
"timestamp": "2026-03-26T12:41:06.518Z",
"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": 33.33,
"no_route_fragment_rate": 66.67,
"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": 3,
"checks_passed": 3
},
"route_distribution": {
"store_feature_risk": 1,
"no_route": 2
},
"fallback_distribution": {
"none": 1,
"out_of_scope": 2
},
"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": "GgUFOuZwDAOjx5",
"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": "CPZBol6f1zT-Cv",
"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": "out_of_scope",
"predicted_route_status": "no_route",
"expected_route_status": null,
"predicted_no_route_reason": "insufficient_specificity",
"expected_no_route_reason": null,
"predicted_clarification_required": false,
"expected_clarification_required": null,
"executable_with_soft_assumptions_fragments": 0,
"trace_id": "MnZHNkwlt1HeY1",
"request_count_for_case": 0
}
]
}
@@ -0,0 +1,111 @@
{
"run_id": "eval-ny0OTiGoC1",
"timestamp": "2026-03-26T12:41:47.583Z",
"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": {
"store_feature_risk": 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": "6BsWdYOwlVkrW7",
"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": "miQ7qO3z2rd5bw",
"request_count_for_case": 0
}
]
}
Binary file not shown.