Укрепить автономные reviewed-маршруты и срезы задолженности
This commit is contained in:
@@ -96,6 +96,27 @@ describe("address follow-up temporal regressions", () => {
|
||||
expect(result).not.toBeNull();
|
||||
expect(result?.intent.intent).toBe("receivables_confirmed_as_of_date");
|
||||
expect(result?.filters.extracted_filters.as_of_date).toBe("2026-04-16");
|
||||
expect(result?.filters.extracted_filters.counterparty).toBeUndefined();
|
||||
});
|
||||
|
||||
it("does not carry self-scope pronouns as counterparty anchors for short debt mirror follow-up", () => {
|
||||
const result = runAddressDecomposeStage("а нам?", {
|
||||
previous_intent: "payables_confirmed_as_of_date",
|
||||
target_intent: "receivables_confirmed_as_of_date",
|
||||
previous_filters: {
|
||||
as_of_date: "2026-05-12",
|
||||
organization: "ООО Альтернатива Плюс",
|
||||
counterparty: "нас"
|
||||
},
|
||||
previous_anchor_type: "counterparty",
|
||||
previous_anchor_value: "нас"
|
||||
});
|
||||
|
||||
expect(result).not.toBeNull();
|
||||
expect(result?.intent.intent).toBe("receivables_confirmed_as_of_date");
|
||||
expect(result?.filters.extracted_filters.as_of_date).toBe("2026-05-12");
|
||||
expect(result?.filters.extracted_filters.organization).toBe("ООО Альтернатива Плюс");
|
||||
expect(result?.filters.extracted_filters.counterparty).toBeUndefined();
|
||||
});
|
||||
|
||||
it("keeps same-date inventory pivot anchored to the previous VAT date", () => {
|
||||
|
||||
@@ -56,11 +56,11 @@ describe("addressIntentResolver regression bridges", () => {
|
||||
expect(result.reasons).toContain("unicode_customer_concentration_bridge_signal_detected");
|
||||
});
|
||||
|
||||
it("defers top-year company revenue wording to business overview discovery", () => {
|
||||
it("routes top-year company revenue wording to exact value-flow ranking", () => {
|
||||
const result = resolveAddressIntent("какой у нас самый доходный год");
|
||||
|
||||
expect(result.intent).toBe("unknown");
|
||||
expect(result.reasons).toContain("unicode_business_overview_earnings_deferred_to_discovery");
|
||||
expect(result.intent).toBe("customer_revenue_and_payments");
|
||||
expect(result.reasons).toContain("unicode_customer_revenue_ranking_bridge_signal_detected");
|
||||
});
|
||||
|
||||
it("defers paired receivables and payables wording to business overview instead of one debt side", () => {
|
||||
|
||||
@@ -2608,12 +2608,12 @@ describe("address intent resolver expansion (M2.3a)", () => {
|
||||
expect(result.intent).toBe("customer_revenue_and_payments");
|
||||
});
|
||||
|
||||
it("defers organization-level yearly profitability wording to business overview discovery", () => {
|
||||
it("routes organization-level top-year revenue wording to exact value-flow ranking", () => {
|
||||
const result = resolveAddressIntent(
|
||||
"\u043a\u0430\u043a\u0438\u0435 \u0441\u0430\u043c\u044b\u0435 \u0434\u043e\u0445\u043e\u0434\u043d\u044b\u0435 \u0433\u043e\u0434\u0430 \u0430\u043b\u044c\u0442\u0435\u0440\u043d\u0430\u0442\u0438\u0432\u044b"
|
||||
);
|
||||
expect(result.intent).toBe("unknown");
|
||||
expect(result.reasons).toContain("unicode_business_overview_earnings_deferred_to_discovery");
|
||||
expect(result.intent).toBe("customer_revenue_and_payments");
|
||||
expect(result.reasons).toContain("unicode_customer_revenue_ranking_bridge_signal_detected");
|
||||
});
|
||||
|
||||
it("defers organization-level profit and margin wording to business overview discovery", () => {
|
||||
@@ -3496,12 +3496,10 @@ describe("address query limited taxonomy and stage diagnostics", { timeout: 1500
|
||||
expect(reply.toLowerCase()).toContain("shortlist");
|
||||
} else {
|
||||
expect(result?.debug.balance_confirmed).toBe(true);
|
||||
expect(reply).toContain("Коротко: подтвержденный долг к оплате");
|
||||
expect(reply).toContain("Это подтвержденный срез обязательств к оплате");
|
||||
expect(reply).toMatch(/\n\nЧто учтено/u);
|
||||
expect(reply).toMatch(/\n\nСводка/u);
|
||||
expect(reply).toMatch(/\n\nКатегории обязательств/u);
|
||||
expect(reply).toMatch(/\n\nПодтвержденные позиции к оплате/u);
|
||||
expect(reply).toContain("Коротко: на 31.05.2020 мы должны");
|
||||
expect(reply).toContain("Крупнейшие позиции к оплате");
|
||||
expect(reply).toContain("Основа: подтвержденный остаток по счетам 60/76");
|
||||
expect(reply.length).toBeLessThan(1800);
|
||||
expect(reply).not.toContain("Блок 1");
|
||||
expect(reply).not.toContain("эвристический");
|
||||
}
|
||||
@@ -3793,17 +3791,16 @@ describe("address query limited taxonomy and stage diagnostics", { timeout: 1500
|
||||
expect(["FACTUAL_LIST", "LIMITED_WITH_REASON", "FACTUAL_SUMMARY"]).toContain(result?.response_type);
|
||||
});
|
||||
|
||||
it("defers yearly profitability wording out of the exact customer value aggregate recipe", async () => {
|
||||
it("routes organization-level yearly revenue ranking wording into the exact customer value aggregate recipe", async () => {
|
||||
const service = new AddressQueryService();
|
||||
const result = await service.tryHandle(
|
||||
"\u043a\u0430\u043a\u0438\u0435 \u0441\u0430\u043c\u044b\u0435 \u0434\u043e\u0445\u043e\u0434\u043d\u044b\u0435 \u0433\u043e\u0434\u0430 \u0430\u043b\u044c\u0442\u0435\u0440\u043d\u0430\u0442\u0438\u0432\u044b"
|
||||
);
|
||||
expect(result?.handled).toBe(true);
|
||||
expect(result?.debug.detected_intent).toBe("unknown");
|
||||
expect(result?.debug.selected_recipe).toBeNull();
|
||||
expect(result?.debug.limited_reason_category).toBe("unsupported");
|
||||
expect(result?.debug.mcp_call_status).toBe("skipped");
|
||||
expect(result?.response_type).toBe("LIMITED_WITH_REASON");
|
||||
expect(result?.debug.detected_intent).toBe("customer_revenue_and_payments");
|
||||
expect(result?.debug.selected_recipe).toBe("address_customer_revenue_and_payments_v1");
|
||||
expect(result?.debug.mcp_call_status).not.toBe("skipped");
|
||||
expect(["FACTUAL_LIST", "LIMITED_WITH_REASON", "FACTUAL_SUMMARY"]).toContain(result?.response_type);
|
||||
});
|
||||
|
||||
it("routes typo highest-check wording into customer value aggregate recipe", async () => {
|
||||
|
||||
@@ -53,6 +53,7 @@ describe("receivables confirmed as-of route", () => {
|
||||
const plan = buildAddressRecipePlan(selected.selected_recipe!, filters);
|
||||
expect(plan.query).toContain("РегистрБухгалтерии.Хозрасчетный.Остатки");
|
||||
expect(plan.query).toContain("СуммаРазвернутыйОстатокДт");
|
||||
expect(plan.query).toContain("СуммаРазвернутыйОстатокКт");
|
||||
expect(plan.query).toContain("Остатки.Счет");
|
||||
expect(plan.query).toContain("62");
|
||||
expect(plan.query).toContain("76");
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
|
||||
import { composeCounterpartyAnalyticsReply } from "../src/services/address_runtime/counterpartyAnalyticsReplyBuilders";
|
||||
import { composeFactualReply } from "../src/services/address_runtime/composeStage";
|
||||
import { composeInventoryReply } from "../src/services/address_runtime/inventoryReplyBuilders";
|
||||
|
||||
describe("address reply builders regressions", () => {
|
||||
@@ -51,6 +52,88 @@ describe("address reply builders regressions", () => {
|
||||
expect(result?.text.split("\n")[0]).toContain("Чапурнов");
|
||||
});
|
||||
|
||||
it("keeps canonical single-best customer wording compact instead of returning the default top list", () => {
|
||||
const result = composeCounterpartyAnalyticsReply(
|
||||
"customer_revenue_and_payments",
|
||||
[
|
||||
{
|
||||
counterparty: "Крупный клиент",
|
||||
amount: 250000,
|
||||
period: "2020-03-31",
|
||||
registrator: "Поступление 1"
|
||||
} as any,
|
||||
{
|
||||
counterparty: "Малый клиент",
|
||||
amount: 100000,
|
||||
period: "2020-04-30",
|
||||
registrator: "Поступление 2"
|
||||
} as any
|
||||
],
|
||||
{
|
||||
userMessage: "определить самого доходного клиента за весь период"
|
||||
},
|
||||
{
|
||||
formatPercent: () => null,
|
||||
formatDateRu: (value: string) => value,
|
||||
formatMoneyRub: (value: number) => `${value} руб.`,
|
||||
extractYearFromIso: (value: string | null) => (value ? Number(value.slice(0, 4)) : null),
|
||||
detectCounterpartyProfileFocus: () => "full_profile",
|
||||
detectCounterpartyLifecycleFocus: () => "active_customers_all_time",
|
||||
hasCounterpartyLifecycleLongevityQuestion: () => false,
|
||||
hasCounterpartyActivityAgeQuestion: () => false,
|
||||
detectRankingLimit: () => 20,
|
||||
detectValueRankingFocus: () => "top_by_total",
|
||||
detectContractValueFocus: () => "top_by_turnover",
|
||||
detectMinOpsForAvgCheck: () => 1,
|
||||
extractRequestedYearFromQuestion: () => null,
|
||||
extractCounterpartyName: (row: any) => row.counterparty,
|
||||
extractContractName: () => null,
|
||||
counterpartyLookupMatches: () => false,
|
||||
toUtcDayTimestamp: () => null,
|
||||
formatAgeYearsMonthsDays: () => "0 дней",
|
||||
normalizeQuestionText: (value: string | null | undefined) => String(value ?? "").toLowerCase()
|
||||
}
|
||||
);
|
||||
|
||||
expect(result?.text.split("\n")[0]).toContain("Крупный клиент");
|
||||
expect(result?.text).not.toContain("2. Малый клиент");
|
||||
});
|
||||
|
||||
it("keeps direct receivables snapshot answers compact", () => {
|
||||
const result = composeFactualReply(
|
||||
"receivables_confirmed_as_of_date",
|
||||
[
|
||||
{
|
||||
period: "2020-05-10",
|
||||
registrator: "Поступление 1",
|
||||
account_dt: "62.01",
|
||||
account_kt: "90.01",
|
||||
amount: 100000,
|
||||
analytics: ["Клиент А"],
|
||||
counterparty: "Клиент А"
|
||||
},
|
||||
{
|
||||
period: "2020-05-11",
|
||||
registrator: "Поступление 2",
|
||||
account_dt: "62.01",
|
||||
account_kt: "90.01",
|
||||
amount: 50000,
|
||||
analytics: ["Клиент Б"],
|
||||
counterparty: "Клиент Б"
|
||||
}
|
||||
],
|
||||
{
|
||||
userMessage: "кто нам должен денег на май 2020",
|
||||
asOfDate: "2020-05-31"
|
||||
}
|
||||
);
|
||||
|
||||
expect(result.text.split("\n")[0]).toContain("Коротко:");
|
||||
expect(result.text.split("\n")[0]).toContain("Клиент А");
|
||||
expect(result.text.length).toBeLessThan(1200);
|
||||
expect(result.text).toContain("Крупнейшие позиции к получению");
|
||||
});
|
||||
|
||||
it("does not overclaim a comparative top customer ranking when only one candidate is present", () => {
|
||||
const result = composeCounterpartyAnalyticsReply(
|
||||
"customer_revenue_and_payments",
|
||||
@@ -190,4 +273,174 @@ describe("address reply builders regressions", () => {
|
||||
expect(firstLine).toContain("К самым старым закупкам");
|
||||
expect(firstLine).not.toContain("2026-04-18");
|
||||
});
|
||||
|
||||
it("excludes mirrored 76 settlements from clean payables", () => {
|
||||
const result = composeFactualReply(
|
||||
"payables_confirmed_as_of_date",
|
||||
[
|
||||
{
|
||||
period: "2026-05-12",
|
||||
registrator: "",
|
||||
account_dt: "51",
|
||||
account_kt: "76.09",
|
||||
amount: 3677454.14,
|
||||
analytics: ["Комитет государственных услуг г. Москвы", "ООО \\Альтернатива Плюс\\", "Финансовое обеспечение заявки"],
|
||||
counterparty: "Комитет государственных услуг г. Москвы",
|
||||
organization: "ООО \\Альтернатива Плюс\\"
|
||||
},
|
||||
{
|
||||
period: "2026-05-12",
|
||||
registrator: "Списание с расчетного счета 1",
|
||||
account_dt: "76.09",
|
||||
account_kt: "51",
|
||||
amount: 1000000,
|
||||
analytics: ["Комитет государственных услуг г. Москвы", "ООО \\Альтернатива Плюс\\", "Финансовое обеспечение заявки"],
|
||||
counterparty: "Комитет государственных услуг г. Москвы",
|
||||
organization: "ООО \\Альтернатива Плюс\\"
|
||||
},
|
||||
{
|
||||
period: "2026-05-12",
|
||||
registrator: "Списание с расчетного счета 2",
|
||||
account_dt: "76.09",
|
||||
account_kt: "51",
|
||||
amount: 2677454.14,
|
||||
analytics: ["Комитет государственных услуг г. Москвы", "ООО \\Альтернатива Плюс\\", "Финансовое обеспечение заявки"],
|
||||
counterparty: "Комитет государственных услуг г. Москвы",
|
||||
organization: "ООО \\Альтернатива Плюс\\"
|
||||
},
|
||||
{
|
||||
period: "2026-05-12",
|
||||
registrator: "Поступление товаров 1",
|
||||
account_dt: "41.01",
|
||||
account_kt: "60.01",
|
||||
amount: 7271.2,
|
||||
analytics: ["Авант мебель"],
|
||||
counterparty: "Авант мебель",
|
||||
organization: "ООО Альтернатива Плюс"
|
||||
}
|
||||
],
|
||||
{
|
||||
userMessage: "мы должны кому-то денег на сегодня?",
|
||||
asOfDate: "2026-05-12"
|
||||
}
|
||||
);
|
||||
|
||||
const firstLine = result.text.split("\n")[0] ?? "";
|
||||
expect(firstLine).toContain("7.271,20");
|
||||
expect(firstLine).toContain("Авант мебель");
|
||||
expect(firstLine).not.toContain("3.677.454,14");
|
||||
expect(result.text).toContain("встречных остатков");
|
||||
expect(result.text).toContain("Комитет государственных услуг г. Москвы");
|
||||
expect(result.text).toContain("Финансовое обеспечение заявки");
|
||||
expect(result.text).not.toContain("договор/аналитика: ООО \\Альтернатива Плюс\\");
|
||||
});
|
||||
|
||||
it("excludes mirrored 76 settlements from clean receivables", () => {
|
||||
const result = composeFactualReply(
|
||||
"receivables_confirmed_as_of_date",
|
||||
[
|
||||
{
|
||||
period: "2026-05-12",
|
||||
registrator: "Реализация 1",
|
||||
account_dt: "62.01",
|
||||
account_kt: "90.01",
|
||||
amount: 9612904.9,
|
||||
analytics: ["Департамент капитального ремонта города Москвы."],
|
||||
counterparty: "Департамент капитального ремонта города Москвы.",
|
||||
organization: "ООО Альтернатива Плюс"
|
||||
},
|
||||
{
|
||||
period: "2026-05-12",
|
||||
registrator: "",
|
||||
account_dt: "51",
|
||||
account_kt: "76.09",
|
||||
amount: 3677454.14,
|
||||
analytics: ["Комитет государственных услуг г. Москвы", "Финансовое обеспечение заявки"],
|
||||
counterparty: "Комитет государственных услуг г. Москвы",
|
||||
organization: "ООО Альтернатива Плюс"
|
||||
},
|
||||
{
|
||||
period: "2026-05-12",
|
||||
registrator: "Списание с расчетного счета 1",
|
||||
account_dt: "76.09",
|
||||
account_kt: "51",
|
||||
amount: 3677454.14,
|
||||
analytics: ["Комитет государственных услуг г. Москвы", "Финансовое обеспечение заявки"],
|
||||
counterparty: "Комитет государственных услуг г. Москвы",
|
||||
organization: "ООО Альтернатива Плюс"
|
||||
}
|
||||
],
|
||||
{
|
||||
userMessage: "а нам кто должен на сегодня?",
|
||||
asOfDate: "2026-05-12"
|
||||
}
|
||||
);
|
||||
|
||||
const firstLine = result.text.split("\n")[0] ?? "";
|
||||
expect(firstLine).toContain("9.612.904,90");
|
||||
expect(firstLine).toContain("Департамент капитального ремонта города Москвы.");
|
||||
expect(firstLine).not.toContain("13.290.359,04");
|
||||
expect(result.text).toContain("встречных остатков");
|
||||
expect(result.text).toContain("Комитет государственных услуг г. Москвы");
|
||||
});
|
||||
|
||||
it("keeps bank-named balance counterparties as counterparties, not settlement analytics", () => {
|
||||
const result = composeFactualReply(
|
||||
"receivables_confirmed_as_of_date",
|
||||
[
|
||||
{
|
||||
period: "2017-05-31",
|
||||
registrator: "Остатки на дату",
|
||||
account_dt: "76.09",
|
||||
account_kt: null,
|
||||
amount: 39079.12,
|
||||
analytics: ["Сбербанк-АСТ, ЗАО", "Финансовое обеспечение заявки"],
|
||||
organization: "ООО \\Альтернатива Плюс\\"
|
||||
}
|
||||
],
|
||||
{
|
||||
userMessage: "кто нам должен денег на май 2017?",
|
||||
asOfDate: "2017-05-31"
|
||||
}
|
||||
);
|
||||
|
||||
const firstLine = result.text.split("\n")[0] ?? "";
|
||||
expect(firstLine).toContain("Сбербанк-АСТ, ЗАО");
|
||||
expect(firstLine).not.toContain("Финансовое обеспечение заявки");
|
||||
});
|
||||
|
||||
it("keeps different 76 contracts separated instead of netting by counterparty only", () => {
|
||||
const result = composeFactualReply(
|
||||
"payables_confirmed_as_of_date",
|
||||
[
|
||||
{
|
||||
period: "2026-05-12",
|
||||
registrator: "",
|
||||
account_dt: "51",
|
||||
account_kt: "76.09",
|
||||
amount: 1000,
|
||||
analytics: ["Комитет государственных услуг г. Москвы", "Договор А"],
|
||||
counterparty: "Комитет государственных услуг г. Москвы",
|
||||
organization: "ООО Альтернатива Плюс"
|
||||
},
|
||||
{
|
||||
period: "2026-05-12",
|
||||
registrator: "Списание с расчетного счета 1",
|
||||
account_dt: "76.09",
|
||||
account_kt: "51",
|
||||
amount: 1000,
|
||||
analytics: ["Комитет государственных услуг г. Москвы", "Договор Б"],
|
||||
counterparty: "Комитет государственных услуг г. Москвы",
|
||||
organization: "ООО Альтернатива Плюс"
|
||||
}
|
||||
],
|
||||
{
|
||||
userMessage: "мы должны кому-то денег на сегодня?",
|
||||
asOfDate: "2026-05-12"
|
||||
}
|
||||
);
|
||||
|
||||
expect(result.text.split("\n")[0]).toContain("1.000,00");
|
||||
expect(result.text).not.toContain("встречных остатков");
|
||||
});
|
||||
});
|
||||
|
||||
@@ -362,12 +362,14 @@ describe("assistant MCP discovery answer adapter", () => {
|
||||
expect(draft.next_step_line).toContain("прибыль/маржу");
|
||||
expect(draft.must_not_claim).toContain("Do not present business overview cash-flow spread as profit or margin.");
|
||||
expect(draft.must_not_claim).toContain("Do not present business overview yearly operating-flow breakdown as profit, financial result, or a complete annual P&L.");
|
||||
expect(draft.must_not_claim).toContain("Do not present business overview supplier concentration as vendor-risk audit, procurement quality, or full expense structure.");
|
||||
expect(draft.must_not_claim).toContain("Do not present reviewed procurement concentration as supplier reliability, delivery quality, payment-purpose classification, or full expense structure.");
|
||||
expect(draft.must_not_claim).toContain("Do not present business overview document/account-section activity profile as process quality, accounting correctness, or completeness of all 1C activity.");
|
||||
expect(draft.must_not_claim).toContain("Do not present business overview counterparty or contract profile as CRM quality, counterparty due diligence, contract-risk audit, or legal completeness.");
|
||||
expect(draft.must_not_claim).toContain("Do not present business overview missing proof families as checked, executed, or confirmed routes.");
|
||||
expect(draft.reason_codes).toContain("answer_contains_business_overview");
|
||||
expect(draft.reason_codes).toContain("answer_contains_business_overview_supplier_concentration");
|
||||
expect(draft.reason_codes).toContain("answer_contains_business_overview_vendor_procurement_quality");
|
||||
expect(draft.reason_codes).toContain("answer_contains_business_overview_vendor_procurement_quality_reviewed_procurement_concentration");
|
||||
expect(draft.reason_codes).toContain("answer_contains_business_overview_yearly_operating_breakdown");
|
||||
expect(draft.reason_codes).toContain("answer_contains_business_overview_document_activity_profile");
|
||||
expect(draft.reason_codes).toContain("answer_contains_business_overview_counterparty_profile");
|
||||
|
||||
@@ -258,20 +258,36 @@ describe("assistant MCP discovery pilot executor", () => {
|
||||
unused_contracts: 372,
|
||||
used_contract_share_pct: 28.46
|
||||
});
|
||||
expect(result.derived_business_overview?.vendor_procurement_quality).toMatchObject({
|
||||
rows_with_amount: 2,
|
||||
total_outgoing_amount: 200000,
|
||||
top_outgoing_share_pct: 75,
|
||||
top_non_financial_supplier_share_pct: 75,
|
||||
financial_institution_leads_outgoing_cash: false,
|
||||
supplier_only_count: 71,
|
||||
mixed_role_count: 23,
|
||||
used_contracts: 148,
|
||||
total_contracts: 520,
|
||||
used_contract_share_pct: 28.46,
|
||||
evidence_status: "reviewed_procurement_concentration",
|
||||
inference_basis: "supplier_payout_concentration_counterparty_contract_profile_confirmed_1c_rows"
|
||||
});
|
||||
expect(result.derived_business_overview?.vendor_procurement_quality?.top_outgoing_counterparty).toMatchObject({
|
||||
axis_value: "Поставщик А",
|
||||
total_amount: 150000
|
||||
});
|
||||
expect(result.derived_business_overview?.missing_proof_families).toEqual(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
family: "accounting_profit_margin",
|
||||
current_status: "reviewed_route_not_wired",
|
||||
current_supported_evidence: null
|
||||
}),
|
||||
expect.objectContaining({
|
||||
family: "vendor_risk_procurement_quality",
|
||||
current_status: "proxy_only_currently",
|
||||
current_supported_evidence: "supplier_concentration_proxy_from_confirmed_outgoing_payment_rows"
|
||||
})
|
||||
])
|
||||
);
|
||||
expect(result.derived_business_overview?.missing_proof_families.map((item) => item.family)).not.toContain(
|
||||
"vendor_risk_procurement_quality"
|
||||
);
|
||||
expect(result.evidence.confirmed_facts.join("\n")).toContain("В 1С подтверждены входящие поступления");
|
||||
expect(result.evidence.confirmed_facts.join("\n")).toContain("Самый крупный подтвержденный поставщик");
|
||||
expect(result.evidence.confirmed_facts.join("\n")).toContain("Годовая раскладка операционного денежного потока");
|
||||
@@ -289,6 +305,8 @@ describe("assistant MCP discovery pilot executor", () => {
|
||||
expect(result.reason_codes).toContain("pilot_derived_business_overview_document_activity_profile_from_confirmed_rows");
|
||||
expect(result.reason_codes).toContain("pilot_derived_business_overview_counterparty_profile_from_confirmed_rows");
|
||||
expect(result.reason_codes).toContain("pilot_derived_business_overview_contract_usage_profile_from_confirmed_rows");
|
||||
expect(result.reason_codes).toContain("pilot_derived_business_overview_vendor_procurement_quality_from_confirmed_rows");
|
||||
expect(result.reason_codes).toContain("pilot_derived_business_overview_vendor_procurement_quality_reviewed_procurement_concentration");
|
||||
expect(result.reason_codes).toContain("pilot_business_overview_missing_proof_families_recorded");
|
||||
expect(deps.executeAddressMcpQuery).toHaveBeenCalledTimes(6);
|
||||
});
|
||||
|
||||
@@ -904,6 +904,40 @@ describe("assistant MCP discovery planner", () => {
|
||||
expect(result.selected_chain_id).not.toBe("entity_resolution");
|
||||
});
|
||||
|
||||
it("keeps ranked value-flow when subject candidates only scope the ranking question", () => {
|
||||
const result = planAssistantMcpDiscovery({
|
||||
dataNeedGraph: {
|
||||
schema_version: "assistant_data_need_graph_v1",
|
||||
policy_owner: "assistantMcpDiscoveryDataNeedGraph",
|
||||
subject_candidates: ["Альтернатива Плюс"],
|
||||
business_fact_family: "value_flow",
|
||||
action_family: "turnover",
|
||||
aggregation_need: null,
|
||||
time_scope_need: "explicit_period",
|
||||
comparison_need: null,
|
||||
ranking_need: "top_desc",
|
||||
proof_expectation: "coverage_checked_fact",
|
||||
clarification_gaps: [],
|
||||
decomposition_candidates: ["collect_scoped_movements", "aggregate_ranked_axis_values", "probe_coverage"],
|
||||
forbidden_overclaim_flags: ["no_raw_model_claims", "no_unchecked_fact_totals"],
|
||||
reason_codes: ["data_need_graph_built", "data_need_graph_ranking_top_desc"]
|
||||
},
|
||||
turnMeaning: {
|
||||
asked_domain_family: "counterparty_value",
|
||||
asked_action_family: "turnover",
|
||||
explicit_date_scope: "all_time",
|
||||
explicit_organization_scope: "Альтернатива Плюс"
|
||||
}
|
||||
});
|
||||
|
||||
expect(result.planner_status).toBe("ready_for_execution");
|
||||
expect(result.selected_chain_id).toBe("value_flow_ranking");
|
||||
expect(result.catalog_chain_template_matches[0]).toBe("value_flow_ranking");
|
||||
expect(result.catalog_chain_template_alignment.selected_chain_matches_top).toBe(true);
|
||||
expect(result.reason_codes).toContain("planner_selected_top_ranked_value_flow_from_data_need_graph");
|
||||
expect(result.reason_codes).toContain("planner_kept_ranking_over_subject_scope_candidates");
|
||||
});
|
||||
|
||||
it("keeps ranked value-flow ready for execution once checked period and organization are known", () => {
|
||||
const result = planAssistantMcpDiscovery({
|
||||
dataNeedGraph: {
|
||||
|
||||
@@ -215,6 +215,95 @@ describe("assistant MCP discovery response candidate", () => {
|
||||
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("procurement-concentration route");
|
||||
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("business_overview_route_template_v1");
|
||||
});
|
||||
|
||||
it("uses a compact direct answer for business-overview top year questions", () => {
|
||||
const candidate = buildAssistantMcpDiscoveryResponseCandidate(
|
||||
entryPoint({
|
||||
|
||||
@@ -367,6 +367,83 @@ describe("assistant MCP discovery response policy", () => {
|
||||
expect(result.reason_codes).not.toContain("mcp_discovery_response_policy_keep_exact_matched_factual_address_reply");
|
||||
});
|
||||
|
||||
it("keeps exact receivables replies when metadata inspection is only a stale fallback candidate", () => {
|
||||
const result = applyAssistantMcpDiscoveryResponsePolicy({
|
||||
currentReply:
|
||||
"Коротко: подтвержденная дебиторская задолженность на 31.05.2017 — 1 234 567,89 руб.",
|
||||
currentReplySource: "address_query_runtime_v1",
|
||||
currentReplyType: "factual",
|
||||
addressRuntimeMeta: {
|
||||
detected_intent: "receivables_confirmed_as_of_date",
|
||||
selected_recipe: "address_receivables_confirmed_as_of_date_v1",
|
||||
mcp_call_status: "matched_non_empty",
|
||||
truth_mode: "confirmed",
|
||||
capability_binding_status: "bound",
|
||||
capability_binding_violations: [],
|
||||
answer_shape_contract: {
|
||||
reply_type: "factual",
|
||||
capability_contract_id: "confirmed_receivables_as_of_date"
|
||||
},
|
||||
assistant_mcp_discovery_entry_point_v1: entryPoint({
|
||||
turn_input: {
|
||||
adapter_status: "ready",
|
||||
should_run_discovery: true,
|
||||
data_need_graph: {
|
||||
business_fact_family: null,
|
||||
action_family: "confirmed_snapshot",
|
||||
clarification_gaps: ["subject"],
|
||||
reason_codes: [
|
||||
"data_need_graph_built",
|
||||
"data_need_graph_family_unknown",
|
||||
"data_need_graph_has_clarification_gaps"
|
||||
]
|
||||
},
|
||||
turn_meaning_ref: {
|
||||
asked_domain_family: "receivables",
|
||||
asked_action_family: "confirmed_snapshot",
|
||||
explicit_date_scope: "2017-05-31",
|
||||
unsupported_but_understood_family: "profit_margin_boundary",
|
||||
stale_replay_forbidden: true
|
||||
},
|
||||
reason_codes: [
|
||||
"mcp_discovery_seeded_from_followup_context",
|
||||
"mcp_discovery_resumed_from_saved_loop_state",
|
||||
"mcp_discovery_data_need_graph_built"
|
||||
]
|
||||
},
|
||||
bridge: {
|
||||
bridge_status: "answer_draft_ready",
|
||||
user_facing_response_allowed: true,
|
||||
business_fact_answer_allowed: true,
|
||||
requires_user_clarification: false,
|
||||
pilot: {
|
||||
pilot_scope: "metadata_inspection_v1"
|
||||
},
|
||||
answer_draft: {
|
||||
answer_mode: "confirmed_with_bounded_inference",
|
||||
headline: "Metadata surface confirmed.",
|
||||
confirmed_lines: ["Available metadata object sets: Document, AccumulationRegister"],
|
||||
inference_lines: [],
|
||||
unknown_lines: ["Detailed metadata fields were not returned by this probe."],
|
||||
limitation_lines: [],
|
||||
next_step_line: null
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
});
|
||||
|
||||
expect(result.applied).toBe(false);
|
||||
expect(result.decision).toBe("keep_current_reply");
|
||||
expect(result.reply_text).toContain("дебиторская задолженность");
|
||||
expect(result.reason_codes).toContain("mcp_discovery_response_policy_keep_exact_matched_factual_address_reply");
|
||||
expect(result.reason_codes).toContain(
|
||||
"mcp_discovery_response_policy_keep_exact_address_reply_over_stale_metadata_discovery"
|
||||
);
|
||||
expect(result.reason_codes).not.toContain("mcp_discovery_response_policy_metadata_candidate_priority");
|
||||
expect(result.reason_codes).not.toContain("mcp_discovery_response_policy_candidate_applied");
|
||||
});
|
||||
|
||||
it("keeps aligned factual address lane answers when the exact lane already matched the same semantic intent", () => {
|
||||
const result = applyAssistantMcpDiscoveryResponsePolicy({
|
||||
currentReply: "ИП Калинин Н.М. | сумма: 216600 | операций: 2",
|
||||
@@ -582,6 +659,71 @@ describe("assistant MCP discovery response policy", () => {
|
||||
expect(result.reason_codes).not.toContain("mcp_discovery_response_policy_keep_full_confirmed_factual_address_reply");
|
||||
});
|
||||
|
||||
it("keeps exact top-year value-flow replies over business-overview direct-money clarification", () => {
|
||||
const result = applyAssistantMcpDiscoveryResponsePolicy({
|
||||
currentReply:
|
||||
"Самый доходный год по подтвержденным поступлениям: 2020 (15 744 052,48 руб. по 20 операциям). Это денежный поток, а не чистая прибыль.",
|
||||
currentReplySource: "address_query_runtime_v1",
|
||||
currentReplyType: "factual",
|
||||
addressRuntimeMeta: {
|
||||
detected_intent: "customer_revenue_and_payments",
|
||||
selected_recipe: "address_customer_revenue_and_payments_v1",
|
||||
mcp_call_status: "matched_non_empty",
|
||||
truth_mode: "confirmed",
|
||||
capability_binding_status: "bound",
|
||||
capability_binding_violations: [],
|
||||
assistant_mcp_discovery_entry_point_v1: entryPoint({
|
||||
turn_input: {
|
||||
adapter_status: "ready",
|
||||
should_run_discovery: true,
|
||||
data_need_graph: {
|
||||
business_fact_family: "business_overview",
|
||||
action_family: "profit_margin_boundary",
|
||||
ranking_need: "top_desc",
|
||||
clarification_gaps: ["organization"],
|
||||
reason_codes: [
|
||||
"data_need_graph_built",
|
||||
"data_need_graph_family_business_overview",
|
||||
"data_need_graph_ranking_top_desc",
|
||||
"data_need_graph_business_overview_direct_money_answer",
|
||||
"data_need_graph_open_scope_total_needs_organization"
|
||||
]
|
||||
},
|
||||
turn_meaning_ref: {
|
||||
asked_domain_family: "business_overview",
|
||||
asked_action_family: "profit_margin_boundary",
|
||||
unsupported_but_understood_family: "profit_margin_boundary",
|
||||
stale_replay_forbidden: true
|
||||
}
|
||||
},
|
||||
bridge: {
|
||||
bridge_status: "answer_draft_ready",
|
||||
user_facing_response_allowed: true,
|
||||
business_fact_answer_allowed: false,
|
||||
requires_user_clarification: true,
|
||||
answer_draft: {
|
||||
answer_mode: "needs_clarification",
|
||||
headline: "Нужно уточнить организацию.",
|
||||
confirmed_lines: [],
|
||||
inference_lines: [],
|
||||
unknown_lines: ["Без организации бизнес-обзор не запускаю."],
|
||||
limitation_lines: [],
|
||||
next_step_line: "Уточните организацию."
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
});
|
||||
|
||||
expect(result.applied).toBe(false);
|
||||
expect(result.decision).toBe("keep_current_reply");
|
||||
expect(result.reply_text).toContain("Самый доходный год");
|
||||
expect(result.reason_codes).toContain(
|
||||
"mcp_discovery_response_policy_keep_exact_value_flow_reply_over_business_overview_direct_money_clarification"
|
||||
);
|
||||
expect(result.reason_codes).not.toContain("mcp_discovery_response_policy_candidate_applied");
|
||||
});
|
||||
|
||||
it("overrides an exact ranking-shaped address reply when bounded open-scope ranking already has organization and period", () => {
|
||||
const result = applyAssistantMcpDiscoveryResponsePolicy({
|
||||
currentReply:
|
||||
|
||||
@@ -757,6 +757,107 @@ describe("assistant MCP discovery runtime bridge", () => {
|
||||
expect(result.reason_codes).toContain("answer_contains_business_overview_debt_due_date_aging_no_payment_terms_configured");
|
||||
});
|
||||
|
||||
it("promotes vendor-risk boundary through reviewed procurement concentration evidence", async () => {
|
||||
const deps = buildSequentialDeps([
|
||||
{ rows: [] },
|
||||
{
|
||||
rows: [
|
||||
{ Period: "2020-01-20T00:00:00", Amount: 700000, Counterparty: "СБЕРБАНК, ПАО" },
|
||||
{ Period: "2020-02-20T00:00:00", Amount: 300000, Counterparty: "Поставщик А" }
|
||||
]
|
||||
},
|
||||
{ rows: [] },
|
||||
{ rows: [] },
|
||||
{
|
||||
rows: [
|
||||
{ Period: "2020-01-01T00:00:00", Registrator: "CP_TOTAL", Amount: 12 },
|
||||
{ Period: "2020-01-01T00:00:00", Registrator: "CP_SUPPLIER_ACTIVE", Amount: 5 },
|
||||
{ Period: "2020-01-01T00:00:00", Registrator: "CP_MIXED_ACTIVE", Amount: 2 },
|
||||
{ Period: "2020-01-01T00:00:00", Registrator: "CP_ACTIVE_UNION", Amount: 7 }
|
||||
]
|
||||
},
|
||||
{
|
||||
rows: [
|
||||
{ Period: "2020-01-01T00:00:00", Registrator: "CT_TOTAL", Amount: 11 },
|
||||
{ Period: "2020-01-01T00:00:00", Registrator: "CT_USED", Amount: 4 }
|
||||
]
|
||||
}
|
||||
]);
|
||||
const result = await runAssistantMcpDiscoveryRuntimeBridge({
|
||||
dataNeedGraph: {
|
||||
schema_version: "assistant_data_need_graph_v1",
|
||||
policy_owner: "assistantMcpDiscoveryDataNeedGraph",
|
||||
subject_candidates: [],
|
||||
business_fact_family: "business_overview",
|
||||
action_family: "vendor_risk_procurement_boundary",
|
||||
aggregation_need: null,
|
||||
time_scope_need: "all_time_scope",
|
||||
comparison_need: null,
|
||||
ranking_need: null,
|
||||
proof_expectation: "vendor_risk_procurement_quality",
|
||||
clarification_gaps: [],
|
||||
decomposition_candidates: [
|
||||
"collect_scoped_movements",
|
||||
"aggregate_checked_amounts",
|
||||
"aggregate_ranked_axis_values",
|
||||
"fetch_supporting_documents",
|
||||
"probe_coverage",
|
||||
"explain_evidence_basis"
|
||||
],
|
||||
forbidden_overclaim_flags: [
|
||||
"no_raw_model_claims",
|
||||
"no_unchecked_vendor_reliability_claim"
|
||||
],
|
||||
reason_codes: ["data_need_graph_built", "data_need_graph_family_business_overview"]
|
||||
},
|
||||
turnMeaning: {
|
||||
asked_domain_family: "business_overview",
|
||||
asked_action_family: "vendor_risk_procurement_boundary",
|
||||
explicit_organization_scope: "OOO Alternative Plus"
|
||||
},
|
||||
deps
|
||||
});
|
||||
const overview = result.pilot.derived_business_overview;
|
||||
const missingFamilies = overview?.missing_proof_families.map((item) => item.family) ?? [];
|
||||
const userFacing = [
|
||||
result.answer_draft.headline,
|
||||
...result.answer_draft.confirmed_lines,
|
||||
...result.answer_draft.inference_lines,
|
||||
...result.answer_draft.unknown_lines,
|
||||
...result.answer_draft.limitation_lines,
|
||||
result.answer_draft.next_step_line ?? ""
|
||||
].join("\n");
|
||||
|
||||
expect(result.bridge_status).toBe("answer_draft_ready");
|
||||
expect(result.business_fact_answer_allowed).toBe(true);
|
||||
expect(result.route_candidate).toMatchObject({
|
||||
candidate_status: "ready_for_reviewed_execution",
|
||||
selected_chain_id: "business_overview",
|
||||
business_fact_family: "business_overview",
|
||||
action_family: "vendor_risk_procurement_boundary",
|
||||
executable_now: true
|
||||
});
|
||||
expect(overview?.vendor_procurement_quality).toMatchObject({
|
||||
total_outgoing_amount: 1000000,
|
||||
top_outgoing_share_pct: 70,
|
||||
top_non_financial_supplier_share_pct: 30,
|
||||
financial_institution_leads_outgoing_cash: true,
|
||||
supplier_only_count: 3,
|
||||
mixed_role_count: 2,
|
||||
used_contracts: 4,
|
||||
total_contracts: 11,
|
||||
evidence_status: "financial_institution_leads_outgoing_cash"
|
||||
});
|
||||
expect(missingFamilies).not.toContain("vendor_risk_procurement_quality");
|
||||
expect(userFacing).toContain("procurement-concentration route");
|
||||
expect(userFacing).toContain("банк/финансовая организация");
|
||||
expect(userFacing).toContain("Поставщик А");
|
||||
expect(userFacing).toContain("Надежность поставщиков");
|
||||
expect(userFacing).not.toContain("outgoing cash concentration proxy");
|
||||
expect(result.reason_codes).toContain("runtime_bridge_route_candidate_ready_for_reviewed_execution");
|
||||
expect(result.reason_codes).toContain("answer_contains_business_overview_vendor_procurement_quality_financial_institution_leads_outgoing_cash");
|
||||
});
|
||||
|
||||
it("bridges selected-item inventory provenance templates through exact document evidence", async () => {
|
||||
const deps = buildDeps([
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user