1338 lines
67 KiB
TypeScript
1338 lines
67 KiB
TypeScript
import { describe, expect, it } from "vitest";
|
||
import { buildAssistantMcpDiscoveryResponseCandidate } from "../src/services/assistantMcpDiscoveryResponseCandidate";
|
||
|
||
function entryPoint(overrides: Record<string, unknown> = {}) {
|
||
return {
|
||
schema_version: "assistant_mcp_discovery_runtime_entry_point_v1",
|
||
policy_owner: "assistantMcpDiscoveryRuntimeEntryPoint",
|
||
entry_status: "bridge_executed",
|
||
hot_runtime_wired: false,
|
||
discovery_attempted: true,
|
||
turn_input: { adapter_status: "ready" },
|
||
bridge: {
|
||
bridge_status: "answer_draft_ready",
|
||
user_facing_response_allowed: true,
|
||
business_fact_answer_allowed: true,
|
||
requires_user_clarification: false,
|
||
answer_draft: {
|
||
answer_mode: "confirmed_with_bounded_inference",
|
||
headline: "По данным 1С есть подтвержденная активность; длительность можно оценивать только как вывод.",
|
||
confirmed_lines: ["1C activity rows were found for counterparty SVK"],
|
||
inference_lines: ["Business activity duration may be inferred from first and latest confirmed 1C activity rows"],
|
||
unknown_lines: ["Legal registration date is not proven by this MCP discovery pilot"],
|
||
limitation_lines: ["query_documents was skipped internally", "MCP fetch window was limited"],
|
||
next_step_line: null
|
||
}
|
||
},
|
||
reason_codes: ["runtime_entry_point_bridge_executed"],
|
||
...overrides
|
||
} as any;
|
||
}
|
||
|
||
describe("assistant MCP discovery response candidate", () => {
|
||
it("builds a Russian guarded candidate from a confirmed discovery draft", () => {
|
||
const candidate = buildAssistantMcpDiscoveryResponseCandidate(entryPoint());
|
||
|
||
expect(candidate.candidate_status).toBe("ready_for_guarded_use");
|
||
expect(candidate.hot_runtime_wired).toBe(false);
|
||
expect(candidate.reply_type).toBe("partial_coverage");
|
||
expect(candidate.eligible_for_future_hot_runtime).toBe(true);
|
||
expect(candidate.reply_text).toContain("Коротко:");
|
||
expect(candidate.reply_text).toContain("В 1С найдены строки активности по контрагенту SVK.");
|
||
expect(candidate.reply_text).toContain("Юридическая дата регистрации этим поиском не подтверждена.");
|
||
expect(candidate.reply_text).not.toContain("query_documents");
|
||
expect(candidate.reply_text).not.toContain("primitive");
|
||
});
|
||
|
||
it("keeps inventory reserve boundary answers direct instead of compacting into a money overview", () => {
|
||
const candidate = buildAssistantMcpDiscoveryResponseCandidate(
|
||
entryPoint({
|
||
turn_input: {
|
||
adapter_status: "ready",
|
||
turn_meaning_ref: {
|
||
asked_domain_family: "business_overview",
|
||
asked_action_family: "inventory_reserve_boundary",
|
||
unsupported_but_understood_family: "inventory_reserve_liquidation_boundary"
|
||
},
|
||
data_need_graph: {
|
||
business_fact_family: "business_overview",
|
||
ranking_need: null,
|
||
reason_codes: ["data_need_graph_family_business_overview"]
|
||
}
|
||
},
|
||
bridge: {
|
||
bridge_status: "answer_draft_ready",
|
||
user_facing_response_allowed: true,
|
||
business_fact_answer_allowed: true,
|
||
requires_user_clarification: false,
|
||
pilot: {
|
||
pilot_scope: "business_overview_route_template_v1",
|
||
derived_business_overview: {
|
||
period_scope: null,
|
||
incoming_customer_revenue: {
|
||
total_amount_human_ru: "157 192 981,43 руб.",
|
||
coverage_limited_by_probe_limit: true
|
||
},
|
||
outgoing_supplier_payout: {
|
||
total_amount_human_ru: "35 439 044,74 руб.",
|
||
coverage_limited_by_probe_limit: true
|
||
},
|
||
net_amount_human_ru: "121 753 936,69 руб.",
|
||
net_direction: "net_incoming"
|
||
}
|
||
},
|
||
answer_draft: {
|
||
answer_mode: "confirmed_with_bounded_inference",
|
||
headline:
|
||
"\u041a\u043e\u0440\u043e\u0442\u043a\u043e: \u0442\u043e\u0447\u043d\u043e \u043f\u043e\u0434\u0442\u0432\u0435\u0440\u0434\u0438\u0442\u044c \u0440\u0435\u0437\u0435\u0440\u0432 \u043f\u043e\u0434 \u043d\u0435\u043b\u0438\u043a\u0432\u0438\u0434\u044b \u043f\u043e \u0442\u0435\u043a\u0443\u0449\u0438\u043c \u0434\u0430\u043d\u043d\u044b\u043c \u043d\u0435\u043b\u044c\u0437\u044f.",
|
||
confirmed_lines: ["Денежный обзор здесь не является ответом на складской резерв."],
|
||
inference_lines: [],
|
||
unknown_lines: [
|
||
"\u0420\u0435\u0437\u0435\u0440\u0432\u044b, \u0441\u043f\u0438\u0441\u0430\u043d\u0438\u044f \u0438 \u043b\u0438\u043a\u0432\u0438\u0434\u0430\u0446\u0438\u043e\u043d\u043d\u0430\u044f \u0441\u0442\u043e\u0438\u043c\u043e\u0441\u0442\u044c \u0441\u043a\u043b\u0430\u0434\u0430 \u043d\u0435 \u043f\u043e\u0434\u0442\u0432\u0435\u0440\u0436\u0434\u0435\u043d\u044b."
|
||
],
|
||
limitation_lines: [],
|
||
next_step_line: null
|
||
}
|
||
}
|
||
})
|
||
);
|
||
|
||
expect(candidate.reply_text).toContain(
|
||
"\u0442\u043e\u0447\u043d\u043e \u043f\u043e\u0434\u0442\u0432\u0435\u0440\u0434\u0438\u0442\u044c \u0440\u0435\u0437\u0435\u0440\u0432"
|
||
);
|
||
expect(candidate.reply_text).toContain("\u041f\u0440\u043e\u0432\u0435\u0440\u0438\u0442\u044c \u043d\u0443\u0436\u043d\u043e");
|
||
expect(candidate.reply_text).not.toContain("157 192 981");
|
||
expect(candidate.reply_text).not.toContain("лимит");
|
||
});
|
||
|
||
it("keeps profit/margin boundary answers direct instead of compacting into a money overview", () => {
|
||
const candidate = buildAssistantMcpDiscoveryResponseCandidate(
|
||
entryPoint({
|
||
turn_input: {
|
||
adapter_status: "ready",
|
||
turn_meaning_ref: {
|
||
asked_domain_family: "business_overview",
|
||
asked_action_family: "profit_margin_boundary",
|
||
unsupported_but_understood_family: "profit_margin_boundary"
|
||
},
|
||
data_need_graph: {
|
||
business_fact_family: "business_overview",
|
||
ranking_need: null,
|
||
reason_codes: ["data_need_graph_family_business_overview"]
|
||
}
|
||
},
|
||
bridge: {
|
||
bridge_status: "answer_draft_ready",
|
||
user_facing_response_allowed: true,
|
||
business_fact_answer_allowed: true,
|
||
requires_user_clarification: false,
|
||
pilot: {
|
||
pilot_scope: "business_overview_route_template_v1",
|
||
derived_business_overview: {
|
||
incoming_customer_revenue: {
|
||
total_amount_human_ru: "47 628 853,03 руб."
|
||
},
|
||
outgoing_supplier_payout: {
|
||
total_amount_human_ru: "19 568 878,06 руб."
|
||
},
|
||
net_amount_human_ru: "28 059 974,97 руб.",
|
||
net_direction: "net_incoming"
|
||
}
|
||
},
|
||
answer_draft: {
|
||
answer_mode: "confirmed_with_bounded_inference",
|
||
headline:
|
||
"Коротко: нельзя точно подтвердить чистую прибыль и маржу по текущему срезу 1С; есть только bounded operating-flow/trading-margin proxy, не P&L и не бухгалтерский финансовый результат.",
|
||
confirmed_lines: ["Денежный обзор здесь не является ответом на чистую прибыль."],
|
||
inference_lines: [],
|
||
unknown_lines: ["Для точного P&L нужны себестоимость, расходы и закрытие периода."],
|
||
limitation_lines: [],
|
||
next_step_line: null
|
||
}
|
||
}
|
||
})
|
||
);
|
||
|
||
expect(candidate.reply_text).toContain("нельзя точно подтвердить чистую прибыль");
|
||
expect(candidate.reply_text).toContain("полный отчет о прибылях и убытках");
|
||
expect(candidate.reply_text).toContain("себестоимости");
|
||
expect(candidate.reply_text).not.toContain("47 628 853");
|
||
});
|
||
|
||
it("answers profit follow-ups with a direct cash-flow boundary before accounting result detail", () => {
|
||
const candidate = buildAssistantMcpDiscoveryResponseCandidate(
|
||
entryPoint({
|
||
turn_input: {
|
||
adapter_status: "ready",
|
||
turn_meaning_ref: {
|
||
asked_domain_family: "business_overview",
|
||
asked_action_family: "profit_margin_boundary",
|
||
unsupported_but_understood_family: "profit_margin_boundary",
|
||
explicit_date_scope: "2020"
|
||
},
|
||
data_need_graph: {
|
||
business_fact_family: "business_overview",
|
||
ranking_need: null,
|
||
reason_codes: ["data_need_graph_family_business_overview"]
|
||
}
|
||
},
|
||
bridge: {
|
||
bridge_status: "answer_draft_ready",
|
||
user_facing_response_allowed: true,
|
||
business_fact_answer_allowed: true,
|
||
requires_user_clarification: false,
|
||
pilot: {
|
||
pilot_scope: "business_overview_route_template_v1",
|
||
derived_business_overview: {
|
||
accounting_financial_result: {
|
||
period_scope: "2020",
|
||
final_result_direction: "loss",
|
||
final_result_amount_human_ru: "7 136 815,85 руб.",
|
||
net_margin_to_revenue_pct: -59.41
|
||
}
|
||
}
|
||
},
|
||
answer_draft: {
|
||
answer_mode: "confirmed_with_bounded_inference",
|
||
headline: "Коротко: по бухгалтерскому маршруту 90/91/99 за 2020 подтвержден учетный убыток.",
|
||
confirmed_lines: [],
|
||
inference_lines: [],
|
||
unknown_lines: [],
|
||
limitation_lines: [],
|
||
next_step_line: null
|
||
}
|
||
}
|
||
})
|
||
);
|
||
|
||
expect(candidate.reply_text).toContain("нет, денежное операционное нетто не стоит считать чистой прибылью");
|
||
expect(candidate.reply_text).toContain("по закрытию счетов 90/91/99 в 1С за 2020");
|
||
expect(candidate.reply_text).toContain("учетный убыток");
|
||
expect(candidate.reply_text).toContain("маржа к подтвержденной выручке -59.41%");
|
||
expect(candidate.reply_text).not.toContain("бухгалтерскому маршруту");
|
||
expect(candidate.reply_text).not.toContain("маржа к выручке 90.01");
|
||
});
|
||
|
||
it("keeps vendor-risk boundary answers direct instead of compacting into a money overview", () => {
|
||
const candidate = buildAssistantMcpDiscoveryResponseCandidate(
|
||
entryPoint({
|
||
turn_input: {
|
||
adapter_status: "ready",
|
||
turn_meaning_ref: {
|
||
asked_domain_family: "business_overview",
|
||
asked_action_family: "vendor_risk_procurement_boundary",
|
||
unsupported_but_understood_family: "vendor_risk_procurement_boundary"
|
||
},
|
||
data_need_graph: {
|
||
business_fact_family: "business_overview",
|
||
ranking_need: null,
|
||
reason_codes: ["data_need_graph_family_business_overview"]
|
||
}
|
||
},
|
||
bridge: {
|
||
bridge_status: "answer_draft_ready",
|
||
user_facing_response_allowed: true,
|
||
business_fact_answer_allowed: true,
|
||
requires_user_clarification: false,
|
||
pilot: {
|
||
pilot_scope: "business_overview_route_template_v1",
|
||
derived_business_overview: {
|
||
outgoing_supplier_payout: {
|
||
total_amount_human_ru: "19 568 878,06 руб."
|
||
},
|
||
top_suppliers: [
|
||
{
|
||
axis_value: "СБЕРБАНК, ПАО",
|
||
total_amount_human_ru: "6 653 022,52 руб."
|
||
}
|
||
]
|
||
}
|
||
},
|
||
answer_draft: {
|
||
answer_mode: "confirmed_with_bounded_inference",
|
||
headline: "Коротко: точный риск зависимости от одного поставщика по текущим данным не подтвержден.",
|
||
confirmed_lines: [],
|
||
inference_lines: [],
|
||
unknown_lines: ["Vendor-risk route не подключен."],
|
||
limitation_lines: [],
|
||
next_step_line: null
|
||
}
|
||
}
|
||
})
|
||
);
|
||
|
||
expect(candidate.reply_text).toContain("риск зависимости");
|
||
expect(candidate.reply_text).toContain("сигнал концентрации исходящих денег");
|
||
expect(candidate.reply_text).toContain("банк/финансовая организация");
|
||
expect(candidate.reply_text).toContain("не доказанная зависимость от обычного поставщика");
|
||
expect(candidate.reply_text).not.toContain("крупнейший подтвержденный поставщик/получатель исходящих платежей: СБЕРБАНК");
|
||
expect(candidate.reply_text).not.toContain("операционное нетто");
|
||
});
|
||
|
||
it("uses reviewed procurement-concentration evidence for vendor-risk boundary answers", () => {
|
||
const candidate = buildAssistantMcpDiscoveryResponseCandidate(
|
||
entryPoint({
|
||
turn_input: {
|
||
adapter_status: "ready",
|
||
turn_meaning_ref: {
|
||
asked_domain_family: "business_overview",
|
||
asked_action_family: "vendor_risk_procurement_boundary",
|
||
unsupported_but_understood_family: "vendor_risk_procurement_boundary"
|
||
},
|
||
data_need_graph: {
|
||
business_fact_family: "business_overview",
|
||
ranking_need: null,
|
||
reason_codes: ["data_need_graph_family_business_overview"]
|
||
}
|
||
},
|
||
bridge: {
|
||
bridge_status: "answer_draft_ready",
|
||
user_facing_response_allowed: true,
|
||
business_fact_answer_allowed: true,
|
||
requires_user_clarification: false,
|
||
pilot: {
|
||
pilot_scope: "business_overview_route_template_v1",
|
||
derived_business_overview: {
|
||
period_scope: "2020",
|
||
outgoing_supplier_payout: {
|
||
total_amount_human_ru: "1 000 000 руб."
|
||
},
|
||
top_suppliers: [
|
||
{
|
||
axis_value: "СБЕРБАНК, ПАО",
|
||
total_amount_human_ru: "700 000 руб."
|
||
}
|
||
],
|
||
vendor_procurement_quality: {
|
||
period_scope: "2020",
|
||
rows_with_amount: 2,
|
||
total_outgoing_amount: 1000000,
|
||
total_outgoing_amount_human_ru: "1 000 000 руб.",
|
||
top_outgoing_counterparty: {
|
||
axis_value: "СБЕРБАНК, ПАО",
|
||
total_amount: 700000,
|
||
total_amount_human_ru: "700 000 руб.",
|
||
rows_with_amount: 1,
|
||
share_pct: 70,
|
||
is_likely_financial_institution: true
|
||
},
|
||
top_outgoing_share_pct: 70,
|
||
top_non_financial_supplier: {
|
||
axis_value: "Поставщик А",
|
||
total_amount: 300000,
|
||
total_amount_human_ru: "300 000 руб.",
|
||
rows_with_amount: 1,
|
||
share_pct: 30,
|
||
is_likely_financial_institution: false
|
||
},
|
||
top_non_financial_supplier_share_pct: 30,
|
||
financial_institution_leads_outgoing_cash: true,
|
||
supplier_only_count: null,
|
||
mixed_role_count: null,
|
||
used_contracts: null,
|
||
total_contracts: null,
|
||
used_contract_share_pct: null,
|
||
evidence_status: "financial_institution_leads_outgoing_cash",
|
||
inference_basis: "supplier_payout_concentration_counterparty_contract_profile_confirmed_1c_rows"
|
||
}
|
||
}
|
||
},
|
||
answer_draft: {
|
||
answer_mode: "confirmed_with_bounded_inference",
|
||
headline: "Vendor route.",
|
||
confirmed_lines: [],
|
||
inference_lines: [],
|
||
unknown_lines: [],
|
||
limitation_lines: [],
|
||
next_step_line: null
|
||
}
|
||
}
|
||
})
|
||
);
|
||
|
||
expect(candidate.reply_text).toContain("проверка концентрации закупок/исходящих платежей");
|
||
expect(candidate.reply_text).toContain("банк/финансовая организация");
|
||
expect(candidate.reply_text).toContain("Поставщик А");
|
||
expect(candidate.reply_text).toContain("надежность поставщика");
|
||
expect(candidate.reply_text).not.toContain("outgoing cash concentration proxy");
|
||
expect(candidate.reply_text).not.toContain("procurement-concentration route");
|
||
expect(candidate.reply_text).not.toContain("business_overview_route_template_v1");
|
||
});
|
||
|
||
it("uses a compact direct answer for business-overview top year questions", () => {
|
||
const candidate = buildAssistantMcpDiscoveryResponseCandidate(
|
||
entryPoint({
|
||
turn_input: {
|
||
adapter_status: "ready",
|
||
turn_meaning_ref: {
|
||
raw_message:
|
||
"Дай краткий обзор ООО Альтернатива Плюс за 2020 и отметь, если в топах есть банк, почему его нельзя читать как обычного клиента или поставщика.",
|
||
effective_message:
|
||
"Дать краткий обзор ООО Альтернатива Плюс за 2020: входящие, исходящие, нетто и банковскую границу.",
|
||
explicit_organization_scope: "ООО Альтернатива Плюс"
|
||
},
|
||
data_need_graph: {
|
||
business_fact_family: "business_overview",
|
||
ranking_need: "top_desc",
|
||
reason_codes: [
|
||
"data_need_graph_family_business_overview",
|
||
"data_need_graph_ranking_top_desc",
|
||
"data_need_graph_business_overview_direct_money_answer"
|
||
]
|
||
}
|
||
},
|
||
bridge: {
|
||
bridge_status: "answer_draft_ready",
|
||
user_facing_response_allowed: true,
|
||
business_fact_answer_allowed: true,
|
||
requires_user_clarification: false,
|
||
pilot: {
|
||
pilot_scope: "business_overview_route_template_v1",
|
||
derived_business_overview: {
|
||
period_scope: null,
|
||
incoming_customer_revenue: {
|
||
total_amount_human_ru: "157 192 981,43 руб.",
|
||
coverage_limited_by_probe_limit: true
|
||
},
|
||
outgoing_supplier_payout: {
|
||
total_amount_human_ru: "35 439 044,74 руб.",
|
||
coverage_limited_by_probe_limit: true
|
||
},
|
||
net_amount_human_ru: "121 753 936,69 руб.",
|
||
net_direction: "net_incoming",
|
||
top_customers: [],
|
||
yearly_breakdown: [
|
||
{
|
||
year_bucket: "2014",
|
||
incoming_total_amount: 11239150.41,
|
||
incoming_total_amount_human_ru: "11 239 150,41 руб.",
|
||
incoming_rows_with_amount: 4,
|
||
net_amount: -634486.17,
|
||
net_amount_human_ru: "634 486,17 руб.",
|
||
net_direction: "net_outgoing"
|
||
},
|
||
{
|
||
year_bucket: "2015",
|
||
incoming_total_amount: 136723459.73,
|
||
incoming_total_amount_human_ru: "136 723 459,73 руб.",
|
||
incoming_rows_with_amount: 54,
|
||
net_amount: 113158051.57,
|
||
net_amount_human_ru: "113 158 051,57 руб.",
|
||
net_direction: "net_incoming"
|
||
}
|
||
]
|
||
}
|
||
},
|
||
answer_draft: {
|
||
answer_mode: "confirmed_with_bounded_inference",
|
||
headline: "Ограниченный бизнес-обзор с большим полотном.",
|
||
confirmed_lines: ["Профиль операционной активности: лишняя широкая строка."],
|
||
inference_lines: ["Сводный LLM-аудит: лишняя широкая строка."],
|
||
unknown_lines: ["Прибыль и маржа не подтверждены."],
|
||
limitation_lines: [],
|
||
next_step_line: null
|
||
}
|
||
}
|
||
})
|
||
);
|
||
|
||
expect(candidate.reply_text).toContain("в доступном проверенном срезе 1С");
|
||
expect(candidate.reply_text).toContain("по компании ООО Альтернатива Плюс");
|
||
expect(candidate.reply_text).toContain("лидирует 2015");
|
||
expect(candidate.reply_text).toContain("2015");
|
||
expect(candidate.reply_text).toContain("136 723 459,73 руб.");
|
||
expect(candidate.reply_text).toContain("банк/финансовую организацию");
|
||
expect(candidate.reply_text).toContain("нельзя автоматически читать как обычного клиента или поставщика");
|
||
expect(candidate.reply_text).toContain("не полный бухгалтерский рейтинг доходности");
|
||
expect(candidate.reply_text).toContain("не как чистую бухгалтерскую прибыль");
|
||
expect(candidate.reply_text).toContain("Годовое операционное нетто в широком срезе не ранжирую");
|
||
expect(candidate.reply_text).toContain("проверка достигла лимита строк");
|
||
expect(candidate.reply_text).toContain("выбрать конкретный год или квартал для дозапроса");
|
||
expect(candidate.reply_text).toContain("без выдачи непроверенного итога");
|
||
expect(candidate.reply_text).not.toContain("По расчетному операционному нетто лучший год");
|
||
expect(candidate.reply_text).not.toContain("По годам:");
|
||
expect(candidate.reply_text).not.toContain("лимит выборки MCP");
|
||
expect(candidate.reply_text).not.toContain("MCP-срез");
|
||
expect(candidate.reply_text).not.toContain("Что подтверждено:");
|
||
expect(candidate.reply_text).not.toContain("Профиль операционной активности");
|
||
});
|
||
|
||
it("uses a compact direct answer for business-overview earned-money totals", () => {
|
||
const candidate = buildAssistantMcpDiscoveryResponseCandidate(
|
||
entryPoint({
|
||
turn_input: {
|
||
adapter_status: "ready",
|
||
data_need_graph: {
|
||
business_fact_family: "business_overview",
|
||
ranking_need: null,
|
||
reason_codes: [
|
||
"data_need_graph_family_business_overview",
|
||
"data_need_graph_business_overview_direct_money_answer"
|
||
]
|
||
}
|
||
},
|
||
bridge: {
|
||
bridge_status: "answer_draft_ready",
|
||
user_facing_response_allowed: true,
|
||
business_fact_answer_allowed: true,
|
||
requires_user_clarification: false,
|
||
pilot: {
|
||
pilot_scope: "business_overview_route_template_v1",
|
||
derived_business_overview: {
|
||
period_scope: "2017",
|
||
incoming_customer_revenue: {
|
||
total_amount_human_ru: "16 932 063,96 руб.",
|
||
coverage_limited_by_probe_limit: false
|
||
},
|
||
outgoing_supplier_payout: {
|
||
total_amount_human_ru: "4 458 027,05 руб.",
|
||
coverage_limited_by_probe_limit: true
|
||
},
|
||
net_amount_human_ru: "12 474 036,91 руб.",
|
||
net_direction: "net_incoming",
|
||
top_customers: [
|
||
{
|
||
axis_value: "ГКУ УКРиС",
|
||
total_amount_human_ru: "11 536 836,23 руб."
|
||
}
|
||
],
|
||
top_suppliers: [
|
||
{
|
||
axis_value: "ООО Поставщик",
|
||
total_amount_human_ru: "2 200 000 руб."
|
||
}
|
||
],
|
||
yearly_breakdown: []
|
||
}
|
||
},
|
||
answer_draft: {
|
||
answer_mode: "confirmed_with_bounded_inference",
|
||
headline: "Ограниченный бизнес-обзор с большим полотном.",
|
||
confirmed_lines: ["Складской срез на дату: лишняя широкая строка."],
|
||
inference_lines: ["Риски и контуры внимания: лишняя широкая строка."],
|
||
unknown_lines: [],
|
||
limitation_lines: [],
|
||
next_step_line: null
|
||
}
|
||
}
|
||
})
|
||
);
|
||
|
||
expect(candidate.reply_text).toContain("за 2017");
|
||
expect(candidate.reply_text).toContain("получили 16 932 063,96 руб.");
|
||
expect(candidate.reply_text).toContain("исходящие платежи/списания 4 458 027,05 руб.");
|
||
expect(candidate.reply_text).toContain("12 474 036,91 руб");
|
||
expect(candidate.reply_text?.split("\n")[0]).toContain("крупнейший источник входящих денег: ГКУ УКРиС");
|
||
expect(candidate.reply_text?.split("\n")[0]).toContain("крупнейший получатель исходящих денег: ООО Поставщик");
|
||
expect(candidate.reply_text).toContain("операционный денежный показатель");
|
||
expect(candidate.reply_text).not.toContain("Что можно сказать только как вывод:");
|
||
expect(candidate.reply_text).not.toContain("Складской срез");
|
||
});
|
||
|
||
it("labels organization-scoped bidirectional value-flow continuations as company scope", () => {
|
||
const candidate = buildAssistantMcpDiscoveryResponseCandidate(
|
||
entryPoint({
|
||
turn_input: {
|
||
adapter_status: "ready",
|
||
turn_meaning_ref: {
|
||
explicit_organization_scope: "ООО Альтернатива Плюс",
|
||
explicit_date_scope: "2020"
|
||
},
|
||
data_need_graph: {
|
||
business_fact_family: "value_flow",
|
||
comparison_need: "incoming_vs_outgoing",
|
||
reason_codes: ["data_need_graph_family_value_flow"]
|
||
}
|
||
},
|
||
bridge: {
|
||
bridge_status: "answer_draft_ready",
|
||
user_facing_response_allowed: true,
|
||
business_fact_answer_allowed: true,
|
||
requires_user_clarification: false,
|
||
pilot: {
|
||
derived_bidirectional_value_flow: {
|
||
counterparty: null,
|
||
period_scope: "2020",
|
||
incoming_customer_revenue: {
|
||
total_amount_human_ru: "47 628 853,03 руб.",
|
||
rows_with_amount: 44
|
||
},
|
||
outgoing_supplier_payout: {
|
||
total_amount_human_ru: "43 763 351,53 руб.",
|
||
rows_with_amount: 299
|
||
},
|
||
net_amount_human_ru: "3 865 501,50 руб.",
|
||
net_direction: "net_incoming",
|
||
coverage_limited_by_probe_limit: false
|
||
}
|
||
},
|
||
answer_draft: {
|
||
answer_mode: "confirmed_with_bounded_inference",
|
||
headline: "Денежный поток подтвержден.",
|
||
confirmed_lines: [],
|
||
inference_lines: [],
|
||
unknown_lines: [],
|
||
limitation_lines: [],
|
||
next_step_line: null
|
||
}
|
||
}
|
||
})
|
||
);
|
||
|
||
expect(candidate.reply_text?.split("\n")[0]).toContain("по компании ООО Альтернатива Плюс за период 2020");
|
||
expect(candidate.reply_text).not.toContain("по контрагенту запрошенному контрагенту");
|
||
});
|
||
|
||
it("does not present bank-like incoming leaders as ordinary client revenue", () => {
|
||
const candidate = buildAssistantMcpDiscoveryResponseCandidate(
|
||
entryPoint({
|
||
turn_input: {
|
||
adapter_status: "ready",
|
||
data_need_graph: {
|
||
business_fact_family: "business_overview",
|
||
ranking_need: null,
|
||
reason_codes: [
|
||
"data_need_graph_family_business_overview",
|
||
"data_need_graph_business_overview_direct_money_answer"
|
||
]
|
||
}
|
||
},
|
||
bridge: {
|
||
bridge_status: "answer_draft_ready",
|
||
user_facing_response_allowed: true,
|
||
business_fact_answer_allowed: true,
|
||
requires_user_clarification: false,
|
||
pilot: {
|
||
pilot_scope: "business_overview_route_template_v1",
|
||
derived_business_overview: {
|
||
period_scope: "2020",
|
||
incoming_customer_revenue: {
|
||
total_amount_human_ru: "24 885 659,31 руб.",
|
||
coverage_limited_by_probe_limit: false
|
||
},
|
||
outgoing_supplier_payout: {
|
||
total_amount_human_ru: "19 568 878,06 руб.",
|
||
coverage_limited_by_probe_limit: false
|
||
},
|
||
net_amount_human_ru: "5 316 781,25 руб.",
|
||
net_direction: "net_incoming",
|
||
top_customers: [
|
||
{
|
||
axis_value: "СБЕРБАНК, ПАО",
|
||
total_amount_human_ru: "12 792 194,31 руб.",
|
||
counterparty_role_hint: "bank_or_financial_institution"
|
||
},
|
||
{
|
||
axis_value: "Группа СВК",
|
||
total_amount_human_ru: "12 093 465 руб.",
|
||
counterparty_role_hint: "ordinary_counterparty"
|
||
}
|
||
],
|
||
top_suppliers: [],
|
||
yearly_breakdown: []
|
||
}
|
||
},
|
||
answer_draft: {
|
||
answer_mode: "confirmed_with_bounded_inference",
|
||
headline: "Ограниченный бизнес-обзор.",
|
||
confirmed_lines: [],
|
||
inference_lines: [],
|
||
unknown_lines: [],
|
||
limitation_lines: [],
|
||
next_step_line: null
|
||
}
|
||
}
|
||
})
|
||
);
|
||
|
||
const firstLine = candidate.reply_text?.split("\n")[0] ?? "";
|
||
expect(firstLine).toContain("крупнейший входящий денежный источник: СБЕРБАНК, ПАО");
|
||
expect(firstLine).toContain("не называю это клиентской выручкой");
|
||
expect(firstLine).toContain("крупнейший небанковский входящий контрагент: Группа СВК");
|
||
expect(candidate.reply_text).not.toContain("крупнейший источник входящих денег: СБЕРБАНК");
|
||
expect(candidate.reply_text).not.toContain("Самый крупный подтвержденный клиент");
|
||
});
|
||
|
||
it("mentions separate counterparty scope in company plus counterparty business summaries", () => {
|
||
const candidate = buildAssistantMcpDiscoveryResponseCandidate(
|
||
entryPoint({
|
||
turn_input: {
|
||
adapter_status: "ready",
|
||
turn_meaning_ref: {
|
||
asked_domain_family: "business_overview",
|
||
asked_action_family: "broad_evaluation",
|
||
explicit_organization_scope: "ООО Альтернатива Плюс",
|
||
business_overview_separate_entity_candidates: ["Группа СВК"]
|
||
},
|
||
data_need_graph: {
|
||
business_fact_family: "business_overview",
|
||
subject_candidates: [],
|
||
ranking_need: null,
|
||
reason_codes: ["data_need_graph_family_business_overview"]
|
||
}
|
||
},
|
||
bridge: {
|
||
bridge_status: "answer_draft_ready",
|
||
user_facing_response_allowed: true,
|
||
business_fact_answer_allowed: true,
|
||
requires_user_clarification: false,
|
||
pilot: {
|
||
pilot_scope: "business_overview_route_template_v1",
|
||
derived_business_overview: {
|
||
period_scope: null,
|
||
incoming_customer_revenue: {
|
||
total_amount_human_ru: "157 192 981,43 руб.",
|
||
coverage_limited_by_probe_limit: true
|
||
},
|
||
outgoing_supplier_payout: {
|
||
total_amount_human_ru: "35 439 044,74 руб.",
|
||
coverage_limited_by_probe_limit: true
|
||
},
|
||
net_amount_human_ru: "121 753 936,69 руб.",
|
||
net_direction: "net_incoming",
|
||
top_customers: [],
|
||
yearly_breakdown: []
|
||
}
|
||
},
|
||
answer_draft: {
|
||
answer_mode: "confirmed_with_bounded_inference",
|
||
headline: "Company summary.",
|
||
confirmed_lines: [],
|
||
inference_lines: [],
|
||
unknown_lines: [],
|
||
limitation_lines: [],
|
||
next_step_line: null
|
||
}
|
||
}
|
||
})
|
||
);
|
||
|
||
expect(candidate.reply_text).toContain("по компании ООО Альтернатива Плюс");
|
||
expect(candidate.reply_text).toContain("Группа СВК");
|
||
expect(candidate.reply_text?.split("\n")[0]).toContain("суммы компании не переношу");
|
||
expect(candidate.reply_text).toContain("нельзя делать вывод о выручке, долге или прибыльности");
|
||
expect(candidate.reply_text).toContain("без отдельного контрагентского среза");
|
||
});
|
||
|
||
it("adds missing proof boundaries for broad all-time business overviews", () => {
|
||
const candidate = buildAssistantMcpDiscoveryResponseCandidate(
|
||
entryPoint({
|
||
turn_input: {
|
||
adapter_status: "ready",
|
||
turn_meaning_ref: {
|
||
asked_domain_family: "business_overview",
|
||
asked_action_family: "broad_evaluation",
|
||
explicit_organization_scope: "ООО Альтернатива Плюс"
|
||
},
|
||
data_need_graph: {
|
||
business_fact_family: "business_overview",
|
||
subject_candidates: [],
|
||
ranking_need: null,
|
||
reason_codes: ["data_need_graph_family_business_overview"]
|
||
}
|
||
},
|
||
bridge: {
|
||
bridge_status: "answer_draft_ready",
|
||
user_facing_response_allowed: true,
|
||
business_fact_answer_allowed: true,
|
||
requires_user_clarification: false,
|
||
pilot: {
|
||
pilot_scope: "business_overview_route_template_v1",
|
||
derived_business_overview: {
|
||
period_scope: null,
|
||
incoming_customer_revenue: {
|
||
total_amount_human_ru: "157 192 981,43 руб.",
|
||
coverage_limited_by_probe_limit: true
|
||
},
|
||
outgoing_supplier_payout: {
|
||
total_amount_human_ru: "35 439 044,74 руб.",
|
||
coverage_limited_by_probe_limit: true
|
||
},
|
||
net_amount_human_ru: "121 753 936,69 руб.",
|
||
net_direction: "net_incoming",
|
||
top_customers: [],
|
||
top_suppliers: [],
|
||
yearly_breakdown: []
|
||
}
|
||
},
|
||
answer_draft: {
|
||
answer_mode: "confirmed_with_bounded_inference",
|
||
headline: "Company summary.",
|
||
confirmed_lines: [],
|
||
inference_lines: [],
|
||
unknown_lines: [],
|
||
limitation_lines: [],
|
||
next_step_line: null
|
||
}
|
||
}
|
||
})
|
||
);
|
||
|
||
expect(candidate.reply_text).toContain("Что не подтверждено в этом срезе");
|
||
expect(candidate.reply_text).toContain("НДС");
|
||
expect(candidate.reply_text).toContain("долги");
|
||
expect(candidate.reply_text).toContain("склад");
|
||
expect(candidate.reply_text).not.toContain("capability_id");
|
||
});
|
||
|
||
it("reuses previous counterparty value-flow bundle in company plus counterparty summaries", () => {
|
||
const candidate = buildAssistantMcpDiscoveryResponseCandidate(
|
||
entryPoint({
|
||
turn_input: {
|
||
adapter_status: "ready",
|
||
turn_meaning_ref: {
|
||
asked_domain_family: "business_overview",
|
||
asked_action_family: "broad_evaluation",
|
||
explicit_organization_scope: "ООО Альтернатива Плюс",
|
||
business_overview_separate_entity_candidates: ["Группа СВК"],
|
||
previous_counterparty_value_flow_bundle: {
|
||
counterparty: "Группа СВК",
|
||
incoming_customer_revenue: {
|
||
total_amount_human_ru: "20 653 490 руб.",
|
||
rows_with_amount: 26,
|
||
rows_matched: 26,
|
||
first_movement_date: "2020-07-27",
|
||
latest_movement_date: "2021-11-10"
|
||
},
|
||
outgoing_supplier_payout: {
|
||
total_amount_human_ru: "2 129 651 руб.",
|
||
rows_with_amount: 1,
|
||
rows_matched: 1,
|
||
first_movement_date: "2022-01-20",
|
||
latest_movement_date: "2022-01-20"
|
||
},
|
||
net_amount_human_ru: "18 523 839 руб.",
|
||
net_direction: "net_incoming"
|
||
},
|
||
previous_counterparty_document_bundle: {
|
||
counterparty: "Группа СВК",
|
||
document_count: 19
|
||
}
|
||
},
|
||
data_need_graph: {
|
||
business_fact_family: "business_overview",
|
||
subject_candidates: [],
|
||
ranking_need: null,
|
||
reason_codes: ["data_need_graph_family_business_overview"]
|
||
}
|
||
},
|
||
bridge: {
|
||
bridge_status: "answer_draft_ready",
|
||
user_facing_response_allowed: true,
|
||
business_fact_answer_allowed: true,
|
||
requires_user_clarification: false,
|
||
pilot: {
|
||
pilot_scope: "business_overview_route_template_v1",
|
||
derived_business_overview: {
|
||
period_scope: null,
|
||
incoming_customer_revenue: { total_amount_human_ru: "157 192 981,43 руб." },
|
||
outgoing_supplier_payout: { total_amount_human_ru: "35 439 044,74 руб." },
|
||
net_amount_human_ru: "121 753 936,69 руб.",
|
||
net_direction: "net_incoming",
|
||
top_customers: [],
|
||
yearly_breakdown: []
|
||
}
|
||
},
|
||
answer_draft: {
|
||
answer_mode: "confirmed_with_bounded_inference",
|
||
headline: "Company summary.",
|
||
confirmed_lines: [],
|
||
inference_lines: [],
|
||
unknown_lines: [],
|
||
limitation_lines: [],
|
||
next_step_line: null
|
||
}
|
||
}
|
||
})
|
||
);
|
||
|
||
const firstLine = candidate.reply_text?.split("\n")[0] ?? "";
|
||
expect(firstLine).toContain("отдельно по Группа СВК: получили 20 653 490 руб.");
|
||
expect(firstLine).toContain("можно утверждать только эти подтвержденные срезы");
|
||
expect(firstLine).toContain("нельзя называть это чистой прибылью");
|
||
expect(candidate.reply_text).toContain("Отдельно по контрагенту Группа СВК: подтверждено получили 20 653 490 руб.");
|
||
expect(candidate.reply_text).toContain("заплатили 2 129 651 руб.");
|
||
expect(candidate.reply_text).toContain("Можно утверждать:");
|
||
expect(candidate.reply_text).toContain("Нельзя утверждать:");
|
||
expect(candidate.reply_text).toContain("документы по цепочке: найдено 19");
|
||
expect(candidate.reply_text).toContain("ранее подтвержденный контрагентский срез");
|
||
});
|
||
|
||
it("localizes value-flow evidence without leaking pilot mechanics", () => {
|
||
const candidate = buildAssistantMcpDiscoveryResponseCandidate(
|
||
entryPoint({
|
||
bridge: {
|
||
bridge_status: "answer_draft_ready",
|
||
user_facing_response_allowed: true,
|
||
business_fact_answer_allowed: true,
|
||
requires_user_clarification: false,
|
||
answer_draft: {
|
||
answer_mode: "confirmed_with_bounded_inference",
|
||
headline: "По данным 1С найдены строки входящих денежных поступлений; сумму можно называть только в рамках проверенного периода и найденных строк.",
|
||
confirmed_lines: [
|
||
"1C value-flow rows were found for counterparty SVK",
|
||
"По найденным строкам входящих денежных поступлений в 1С по контрагенту SVK за период 2020 сумма входящих денежных поступлений составляет 3 750 руб."
|
||
],
|
||
inference_lines: ["Counterparty value-flow total was calculated from confirmed 1C movement rows"],
|
||
unknown_lines: ["Full turnover outside the checked period is not proven by this MCP discovery pilot"],
|
||
limitation_lines: ["pilot_value_flow_uses_query_movements_and_derives_aggregate"],
|
||
next_step_line: null
|
||
}
|
||
}
|
||
})
|
||
);
|
||
|
||
expect(candidate.candidate_status).toBe("ready_for_guarded_use");
|
||
expect(candidate.reply_text).toContain("В 1С найдены строки входящих денежных поступлений по контрагенту SVK.");
|
||
expect(candidate.reply_text).toContain("3 750 руб.");
|
||
expect(candidate.reply_text).toContain("Полный объем входящих поступлений вне проверенного периода этим поиском не подтвержден.");
|
||
expect(candidate.reply_text).not.toContain("pilot_");
|
||
expect(candidate.reply_text).not.toContain("query_movements");
|
||
});
|
||
|
||
it("localizes supplier-payout evidence without leaking pilot mechanics", () => {
|
||
const candidate = buildAssistantMcpDiscoveryResponseCandidate(
|
||
entryPoint({
|
||
bridge: {
|
||
bridge_status: "answer_draft_ready",
|
||
user_facing_response_allowed: true,
|
||
business_fact_answer_allowed: true,
|
||
requires_user_clarification: false,
|
||
answer_draft: {
|
||
answer_mode: "confirmed_with_bounded_inference",
|
||
headline: "По данным 1С найдены строки исходящих платежей/списаний; сумму можно называть только в рамках проверенного периода и найденных строк.",
|
||
confirmed_lines: [
|
||
"1C supplier-payout rows were found for counterparty SVK",
|
||
"По найденным строкам исходящих платежей/списаний в 1С по контрагенту SVK за период 2020 сумма исходящих платежей/списаний составляет 5 000 руб."
|
||
],
|
||
inference_lines: ["Counterparty supplier-payout total was calculated from confirmed 1C outgoing payment rows"],
|
||
unknown_lines: [
|
||
"Complete requested-period coverage is not proven because the MCP discovery probe row limit was reached",
|
||
"Full supplier-payout amount outside the checked period is not proven by this MCP discovery pilot"
|
||
],
|
||
limitation_lines: ["pilot_value_flow_uses_query_movements_and_derives_aggregate"],
|
||
next_step_line: null
|
||
}
|
||
}
|
||
})
|
||
);
|
||
|
||
expect(candidate.candidate_status).toBe("ready_for_guarded_use");
|
||
expect(candidate.reply_text).toContain("В 1С найдены строки исходящих платежей/списаний по контрагенту SVK.");
|
||
expect(candidate.reply_text).toContain("5 000 руб.");
|
||
expect(candidate.reply_text).toContain("Полное покрытие запрошенного периода не подтверждено");
|
||
expect(candidate.reply_text).toContain("Полный объем исходящих платежей вне проверенного периода этим поиском не подтвержден.");
|
||
expect(candidate.reply_text).not.toContain("pilot_");
|
||
expect(candidate.reply_text).not.toContain("query_movements");
|
||
});
|
||
|
||
it("localizes bidirectional net value-flow evidence without leaking pilot mechanics", () => {
|
||
const candidate = buildAssistantMcpDiscoveryResponseCandidate(
|
||
entryPoint({
|
||
bridge: {
|
||
bridge_status: "answer_draft_ready",
|
||
user_facing_response_allowed: true,
|
||
business_fact_answer_allowed: true,
|
||
requires_user_clarification: false,
|
||
answer_draft: {
|
||
answer_mode: "confirmed_with_bounded_inference",
|
||
headline: "По данным 1С найдены строки входящих и исходящих денежных движений; нетто можно называть только как расчет по найденным строкам и проверенному периоду.",
|
||
confirmed_lines: [
|
||
"1C bidirectional value-flow rows were checked for counterparty SVK: incoming=found, outgoing=found",
|
||
"По найденным строкам 1С по контрагенту SVK за период 2020: получили 12 500,50 руб. по входящим движениям, заплатили 4 000 руб. по исходящим платежам/списаниям. Расчетное нетто в нашу сторону: 8 500,50 руб."
|
||
],
|
||
inference_lines: [
|
||
"Counterparty net value-flow was calculated as incoming confirmed 1C rows minus outgoing confirmed 1C rows"
|
||
],
|
||
unknown_lines: [
|
||
"Complete requested-period coverage for bidirectional value-flow is not proven because at least one MCP discovery probe row limit was reached",
|
||
"Full bidirectional value-flow outside the checked period is not proven by this MCP discovery pilot"
|
||
],
|
||
limitation_lines: ["pilot_bidirectional_value_flow_uses_two_query_movements_and_derives_net"],
|
||
next_step_line: null
|
||
}
|
||
}
|
||
})
|
||
);
|
||
|
||
expect(candidate.candidate_status).toBe("ready_for_guarded_use");
|
||
expect(candidate.reply_text).toContain("В 1С проверены входящие и исходящие денежные строки по контрагенту SVK");
|
||
expect(candidate.reply_text).toContain("получили 12 500,50 руб.");
|
||
expect(candidate.reply_text).toContain("заплатили 4 000 руб.");
|
||
expect(candidate.reply_text).toContain("нетто в нашу сторону: 8 500,50 руб.");
|
||
expect(candidate.reply_text).toContain("Полное покрытие запрошенного периода по двустороннему денежному потоку не подтверждено");
|
||
expect(candidate.reply_text).not.toContain("pilot_");
|
||
expect(candidate.reply_text).not.toContain("query_movements");
|
||
});
|
||
|
||
it("uses a compact direct first line for derived bidirectional value-flow totals", () => {
|
||
const candidate = buildAssistantMcpDiscoveryResponseCandidate(
|
||
entryPoint({
|
||
bridge: {
|
||
bridge_status: "answer_draft_ready",
|
||
user_facing_response_allowed: true,
|
||
business_fact_answer_allowed: true,
|
||
requires_user_clarification: false,
|
||
pilot: {
|
||
pilot_scope: "counterparty_bidirectional_value_flow_query_movements_v1",
|
||
derived_bidirectional_value_flow: {
|
||
counterparty: "SVK",
|
||
period_scope: "2020",
|
||
net_amount_human_ru: "8 500,50 руб.",
|
||
net_direction: "net_incoming",
|
||
coverage_limited_by_probe_limit: false,
|
||
incoming_customer_revenue: {
|
||
rows_matched: 2,
|
||
rows_with_amount: 2,
|
||
total_amount_human_ru: "12 500,50 руб.",
|
||
first_movement_date: "2020-01-15",
|
||
latest_movement_date: "2020-02-20"
|
||
},
|
||
outgoing_supplier_payout: {
|
||
rows_matched: 1,
|
||
rows_with_amount: 1,
|
||
total_amount_human_ru: "4 000 руб.",
|
||
first_movement_date: "2020-03-10",
|
||
latest_movement_date: "2020-03-10"
|
||
}
|
||
}
|
||
},
|
||
answer_draft: {
|
||
answer_mode: "confirmed_with_bounded_inference",
|
||
headline: "По данным 1С найдены строки входящих и исходящих денежных движений.",
|
||
confirmed_lines: ["1C bidirectional value-flow rows were checked for counterparty SVK: incoming=found, outgoing=found"],
|
||
inference_lines: ["Counterparty net value-flow was calculated as incoming confirmed 1C rows minus outgoing confirmed 1C rows"],
|
||
unknown_lines: [],
|
||
limitation_lines: [],
|
||
next_step_line: null
|
||
}
|
||
}
|
||
})
|
||
);
|
||
|
||
const firstLine = candidate.reply_text?.split("\n")[0] ?? "";
|
||
expect(firstLine).toContain("Коротко:");
|
||
expect(firstLine).toContain("SVK");
|
||
expect(firstLine).toContain("получили 12 500,50 руб.");
|
||
expect(firstLine).toContain("заплатили 4 000 руб.");
|
||
expect(firstLine).toContain("нетто в нашу сторону: 8 500,50 руб.");
|
||
expect(candidate.reply_text).toContain("Основа:");
|
||
expect(candidate.reply_text).not.toContain("Что подтверждено");
|
||
expect(candidate.reply_text).not.toContain("pilot_");
|
||
expect(candidate.reply_text).not.toContain("query_movements");
|
||
});
|
||
|
||
it("keeps monthly breakdown lines user-facing and localizes monthly inference basis", () => {
|
||
const candidate = buildAssistantMcpDiscoveryResponseCandidate(
|
||
entryPoint({
|
||
bridge: {
|
||
bridge_status: "answer_draft_ready",
|
||
user_facing_response_allowed: true,
|
||
business_fact_answer_allowed: true,
|
||
requires_user_clarification: false,
|
||
answer_draft: {
|
||
answer_mode: "confirmed_with_bounded_inference",
|
||
headline: "По данным 1С найдены строки входящих и исходящих денежных движений; нетто и помесячная раскладка могут называться только как расчет по найденным строкам и проверенному периоду.",
|
||
confirmed_lines: [
|
||
"1C bidirectional value-flow rows were checked for counterparty SVK: incoming=found, outgoing=found",
|
||
"Помесячно: янв 2020 — получили 10 000 руб., заплатили 4 000 руб., нетто в нашу сторону 6 000 руб."
|
||
],
|
||
inference_lines: [
|
||
"Counterparty monthly net value-flow breakdown was grouped by month over confirmed incoming and outgoing 1C rows"
|
||
],
|
||
unknown_lines: [],
|
||
limitation_lines: [],
|
||
next_step_line: null
|
||
}
|
||
}
|
||
})
|
||
);
|
||
|
||
expect(candidate.reply_text).toContain("Помесячно: янв 2020");
|
||
expect(candidate.reply_text).toContain("Помесячная нетто-раскладка сгруппирована только по подтвержденным входящим и исходящим строкам 1С.");
|
||
expect(candidate.reply_text).not.toContain("Counterparty monthly net value-flow breakdown");
|
||
});
|
||
|
||
it("localizes recovered coverage facts without leaking broad-probe wording", () => {
|
||
const candidate = buildAssistantMcpDiscoveryResponseCandidate(
|
||
entryPoint({
|
||
bridge: {
|
||
bridge_status: "answer_draft_ready",
|
||
user_facing_response_allowed: true,
|
||
business_fact_answer_allowed: true,
|
||
requires_user_clarification: false,
|
||
answer_draft: {
|
||
answer_mode: "confirmed_with_bounded_inference",
|
||
headline: "По данным 1С найдены строки исходящих платежей/списаний; сумму можно называть только в рамках проверенного периода и найденных строк.",
|
||
confirmed_lines: ["1C supplier-payout rows were found for counterparty SVK"],
|
||
inference_lines: [
|
||
"Requested period coverage was recovered through monthly 1C value-flow probes after the broad probe hit the row limit"
|
||
],
|
||
unknown_lines: [],
|
||
limitation_lines: [],
|
||
next_step_line: null
|
||
}
|
||
}
|
||
})
|
||
);
|
||
|
||
expect(candidate.reply_text).toContain("Покрытие запрошенного периода восстановлено помесячными проверками 1С");
|
||
expect(candidate.reply_text).not.toContain("broad probe hit the row limit");
|
||
});
|
||
|
||
it("localizes document evidence without leaking raw English facts", () => {
|
||
const candidate = buildAssistantMcpDiscoveryResponseCandidate(
|
||
entryPoint({
|
||
bridge: {
|
||
bridge_status: "answer_draft_ready",
|
||
user_facing_response_allowed: true,
|
||
business_fact_answer_allowed: true,
|
||
requires_user_clarification: false,
|
||
answer_draft: {
|
||
answer_mode: "confirmed_with_bounded_inference",
|
||
headline: "По данным 1С найдены строки документов; ответ ограничен проверенным периодом и найденными строками.",
|
||
confirmed_lines: ["1C document rows were found for counterparty SVK"],
|
||
inference_lines: ["Counterparty document evidence is limited to confirmed 1C document rows in the checked scope"],
|
||
unknown_lines: ["Full document history outside the checked period is not proven by this MCP discovery pilot"],
|
||
limitation_lines: [],
|
||
next_step_line: null
|
||
}
|
||
}
|
||
})
|
||
);
|
||
|
||
expect(candidate.reply_text).toContain("В 1С найдены строки документов по контрагенту SVK.");
|
||
expect(candidate.reply_text).toContain("Срез документов ограничен только подтвержденными строками документов");
|
||
expect(candidate.reply_text).toContain("Полный исторический срез документов вне проверенного периода этим поиском не подтвержден.");
|
||
expect(candidate.reply_text).not.toContain("1C document rows were found");
|
||
});
|
||
|
||
it("localizes movement evidence without leaking raw English facts", () => {
|
||
const candidate = buildAssistantMcpDiscoveryResponseCandidate(
|
||
entryPoint({
|
||
bridge: {
|
||
bridge_status: "answer_draft_ready",
|
||
user_facing_response_allowed: true,
|
||
business_fact_answer_allowed: true,
|
||
requires_user_clarification: false,
|
||
answer_draft: {
|
||
answer_mode: "confirmed_with_bounded_inference",
|
||
headline: "По данным 1С найдены строки движений; ответ ограничен проверенным периодом и найденными строками.",
|
||
confirmed_lines: ["1C movement rows were found for counterparty SVK"],
|
||
inference_lines: ["Counterparty movement evidence is limited to confirmed 1C movement rows in the checked scope"],
|
||
unknown_lines: ["Full movement history outside the checked period is not proven by this MCP discovery pilot"],
|
||
limitation_lines: [],
|
||
next_step_line: null
|
||
}
|
||
}
|
||
})
|
||
);
|
||
|
||
expect(candidate.reply_text).toContain("В 1С найдены строки движений по контрагенту SVK.");
|
||
expect(candidate.reply_text).toContain("Срез движений ограничен только подтвержденными строками движений");
|
||
expect(candidate.reply_text).toContain("Полный исторический срез движений вне проверенного периода этим поиском не подтвержден.");
|
||
expect(candidate.reply_text).not.toContain("1C movement rows were found");
|
||
});
|
||
|
||
it("localizes metadata evidence without leaking raw MCP wording", () => {
|
||
const candidate = buildAssistantMcpDiscoveryResponseCandidate(
|
||
entryPoint({
|
||
bridge: {
|
||
bridge_status: "answer_draft_ready",
|
||
user_facing_response_allowed: true,
|
||
business_fact_answer_allowed: true,
|
||
requires_user_clarification: false,
|
||
answer_draft: {
|
||
answer_mode: "confirmed_with_bounded_inference",
|
||
headline: "По данным 1С найдена подтвержденная metadata-поверхность.",
|
||
confirmed_lines: [
|
||
'Confirmed 1C metadata surface for scope "НДС": 7 rows and 3 matching objects',
|
||
"Available metadata object sets: accumulation_register, document",
|
||
"Selected metadata entity set: Документ",
|
||
"Selected metadata objects: Документ.СчетФактураВыданный",
|
||
"Available metadata fields/sections: amount, vat_rate, organization"
|
||
],
|
||
inference_lines: [
|
||
"A likely next checked lane may be inferred as document_evidence from the confirmed metadata surface"
|
||
],
|
||
unknown_lines: [
|
||
'No matching 1C metadata objects were confirmed for scope "Прибыль"',
|
||
"Exact downstream metadata surface remains ambiguous across: Документ, РегистрНакопления"
|
||
],
|
||
limitation_lines: ["Detailed metadata fields were not returned by this MCP metadata probe"],
|
||
next_step_line: null
|
||
}
|
||
}
|
||
})
|
||
);
|
||
|
||
expect(candidate.reply_text).toContain('В 1С подтверждена metadata-поверхность по области "НДС"');
|
||
expect(candidate.reply_text).toContain("Доступные типы metadata-объектов");
|
||
expect(candidate.reply_text).toContain("Выбранное семейство metadata-объектов: Документ");
|
||
expect(candidate.reply_text).toContain("Выбранные metadata-объекты для следующего шага");
|
||
expect(candidate.reply_text).toContain("Доступные metadata-поля/секции");
|
||
expect(candidate.reply_text).toContain("контур документов");
|
||
expect(candidate.reply_text).toContain('В 1С не подтверждены metadata-объекты по области "Прибыль"');
|
||
expect(candidate.reply_text).toContain("неоднозначна между family");
|
||
expect(candidate.reply_text).toContain("Эта MCP-проверка metadata не вернула детальный список полей");
|
||
expect(candidate.reply_text).not.toContain("Confirmed 1C metadata surface");
|
||
});
|
||
|
||
it("returns not applicable when discovery was skipped for an exact supported route", () => {
|
||
const candidate = buildAssistantMcpDiscoveryResponseCandidate({
|
||
schema_version: "assistant_mcp_discovery_runtime_entry_point_v1",
|
||
policy_owner: "assistantMcpDiscoveryRuntimeEntryPoint",
|
||
entry_status: "skipped_not_applicable",
|
||
hot_runtime_wired: false,
|
||
discovery_attempted: false,
|
||
turn_input: { adapter_status: "not_applicable" },
|
||
bridge: null,
|
||
reason_codes: []
|
||
} as any);
|
||
|
||
expect(candidate.candidate_status).toBe("not_applicable");
|
||
expect(candidate.reply_text).toBeNull();
|
||
expect(candidate.eligible_for_future_hot_runtime).toBe(false);
|
||
});
|
||
|
||
it("creates a clarification candidate from a clarification bridge", () => {
|
||
const candidate = buildAssistantMcpDiscoveryResponseCandidate(
|
||
entryPoint({
|
||
bridge: {
|
||
bridge_status: "needs_clarification",
|
||
user_facing_response_allowed: true,
|
||
business_fact_answer_allowed: false,
|
||
requires_user_clarification: true,
|
||
answer_draft: {
|
||
answer_mode: "needs_clarification",
|
||
headline: "Нужно уточнить контекст перед поиском в 1С.",
|
||
confirmed_lines: [],
|
||
inference_lines: [],
|
||
unknown_lines: [],
|
||
limitation_lines: [],
|
||
next_step_line: "Уточните контрагента, период или организацию."
|
||
}
|
||
}
|
||
})
|
||
);
|
||
|
||
expect(candidate.candidate_status).toBe("clarification_candidate");
|
||
expect(candidate.reply_type).toBe("clarification_required");
|
||
expect(candidate.reply_text).toContain("Нужно уточнить контекст");
|
||
expect(candidate.eligible_for_future_hot_runtime).toBe(true);
|
||
});
|
||
|
||
it("surfaces metadata lane-choice clarification as a user-facing clarification candidate", () => {
|
||
const candidate = buildAssistantMcpDiscoveryResponseCandidate(
|
||
entryPoint({
|
||
bridge: {
|
||
bridge_status: "needs_clarification",
|
||
user_facing_response_allowed: true,
|
||
business_fact_answer_allowed: false,
|
||
requires_user_clarification: true,
|
||
answer_draft: {
|
||
answer_mode: "needs_clarification",
|
||
headline: "По подтвержденной metadata-поверхности видно несколько конкурирующих data-lane, и без явного выбора дальше идти нельзя.",
|
||
confirmed_lines: [],
|
||
inference_lines: [],
|
||
unknown_lines: [],
|
||
limitation_lines: [],
|
||
next_step_line: "Уточните, в какой контур идти дальше: по документам или по движениям/регистрам."
|
||
}
|
||
}
|
||
})
|
||
);
|
||
|
||
expect(candidate.candidate_status).toBe("clarification_candidate");
|
||
expect(candidate.reply_type).toBe("clarification_required");
|
||
expect(candidate.reply_text).toContain("data-lane");
|
||
expect(candidate.reply_text).toContain("по документам");
|
||
expect(candidate.reply_text).toContain("по движениям/регистрам");
|
||
});
|
||
|
||
it("does not expose unsupported bridge output as a future hot candidate", () => {
|
||
const candidate = buildAssistantMcpDiscoveryResponseCandidate(
|
||
entryPoint({
|
||
bridge: {
|
||
bridge_status: "unsupported",
|
||
user_facing_response_allowed: true,
|
||
business_fact_answer_allowed: false,
|
||
requires_user_clarification: false,
|
||
answer_draft: null
|
||
}
|
||
})
|
||
);
|
||
|
||
expect(candidate.candidate_status).toBe("unsupported");
|
||
expect(candidate.reply_type).toBe("no_grounded_answer");
|
||
expect(candidate.reply_text).toBeNull();
|
||
expect(candidate.eligible_for_future_hot_runtime).toBe(false);
|
||
});
|
||
it("localizes open-scope bidirectional comparison scope and probe-limit wording without contour garbage", () => {
|
||
const candidate = buildAssistantMcpDiscoveryResponseCandidate(
|
||
entryPoint({
|
||
bridge: {
|
||
bridge_status: "answer_draft_ready",
|
||
user_facing_response_allowed: true,
|
||
business_fact_answer_allowed: true,
|
||
requires_user_clarification: false,
|
||
answer_draft: {
|
||
answer_mode: "confirmed_with_bounded_inference",
|
||
headline:
|
||
"\u041f\u043e \u0434\u0430\u043d\u043d\u044b\u043c 1\u0421 \u043d\u0430\u0439\u0434\u0435\u043d\u044b \u0441\u0442\u0440\u043e\u043a\u0438 \u0432\u0445\u043e\u0434\u044f\u0449\u0438\u0445 \u0438 \u0438\u0441\u0445\u043e\u0434\u044f\u0449\u0438\u0445 \u0434\u0435\u043d\u0435\u0436\u043d\u044b\u0445 \u0434\u0432\u0438\u0436\u0435\u043d\u0438\u0439; \u043d\u0435\u0442\u0442\u043e \u043c\u043e\u0436\u043d\u043e \u043d\u0430\u0437\u044b\u0432\u0430\u0442\u044c \u0442\u043e\u043b\u044c\u043a\u043e \u043a\u0430\u043a \u0440\u0430\u0441\u0447\u0435\u0442 \u043f\u043e \u043d\u0430\u0439\u0434\u0435\u043d\u043d\u044b\u043c \u0441\u0442\u0440\u043e\u043a\u0430\u043c \u0438 \u043f\u0440\u043e\u0432\u0435\u0440\u0435\u043d\u043d\u043e\u043c\u0443 \u043f\u0435\u0440\u0438\u043e\u0434\u0443.",
|
||
confirmed_lines: [
|
||
"1C bidirectional value-flow rows were checked for the requested counterparty scope: incoming=found, outgoing=found"
|
||
],
|
||
inference_lines: [],
|
||
unknown_lines: [],
|
||
limitation_lines: [
|
||
"Requested period hit the MCP row limit, but the approved monthly recovery probe budget is smaller than the required subperiod count"
|
||
],
|
||
next_step_line: null
|
||
}
|
||
}
|
||
})
|
||
);
|
||
|
||
expect(candidate.reply_text).toContain(
|
||
"\u0412 1\u0421 \u043f\u0440\u043e\u0432\u0435\u0440\u0435\u043d\u044b \u0432\u0445\u043e\u0434\u044f\u0449\u0438\u0435 \u0438 \u0438\u0441\u0445\u043e\u0434\u044f\u0449\u0438\u0435 \u0434\u0435\u043d\u0435\u0436\u043d\u044b\u0435 \u0441\u0442\u0440\u043e\u043a\u0438 \u0432 \u0437\u0430\u043f\u0440\u043e\u0448\u0435\u043d\u043d\u043e\u043c \u0441\u0440\u0435\u0437\u0435"
|
||
);
|
||
expect(candidate.reply_text).toContain("Запрошенный период достиг лимита строк");
|
||
expect(candidate.reply_text).not.toContain("уперся в лимит строк MCP");
|
||
expect(candidate.reply_text).not.toContain(
|
||
"\u043a\u043e\u043d\u0442\u0440\u0430\u0433\u0435\u043d\u0442\u0441\u043a\u043e\u043c\u0443 \u043a\u043e\u043d\u0442\u0443\u0440\u0443"
|
||
);
|
||
expect(candidate.reply_text).not.toContain("Requested period hit the MCP row limit");
|
||
});
|
||
|
||
it("filters MCP discovery scope-mechanics from clarification unknown and limitation blocks", () => {
|
||
const candidate = buildAssistantMcpDiscoveryResponseCandidate(
|
||
entryPoint({
|
||
bridge: {
|
||
bridge_status: "needs_clarification",
|
||
user_facing_response_allowed: true,
|
||
business_fact_answer_allowed: false,
|
||
requires_user_clarification: true,
|
||
answer_draft: {
|
||
answer_mode: "needs_clarification",
|
||
headline:
|
||
"Могу посчитать общий денежный поток в проверяемом окне, но для проверяемого поиска в 1С нужно проверяемый период и организацию.",
|
||
confirmed_lines: [],
|
||
inference_lines: [],
|
||
unknown_lines: ["MCP discovery pilot needs more scope before execution"],
|
||
limitation_lines: ["MCP discovery pilot needs more scope before execution"],
|
||
next_step_line:
|
||
"Уточните период и организацию, и я продолжу поиск по денежному потоку в 1С."
|
||
}
|
||
}
|
||
})
|
||
);
|
||
|
||
expect(candidate.reply_text).toContain("Уточните период и организацию");
|
||
expect(candidate.reply_text).not.toContain("MCP discovery");
|
||
expect(candidate.reply_text).not.toContain("needs more scope before execution");
|
||
expect(candidate.reply_text).not.toContain("Что не подтверждено:");
|
||
expect(candidate.reply_text).not.toContain("Ограничения проверки:");
|
||
});
|
||
});
|