Semantic Gate: закрепить контрагентский value-flow и денежный разбор
This commit is contained in:
@@ -5110,6 +5110,23 @@ describe("address recipe catalog counterparty filtering", () => {
|
||||
expect(plan.query).toContain("БанкПоступление.ДоговорКонтрагента");
|
||||
});
|
||||
|
||||
it("injects counterparty condition into customer value recipe", () => {
|
||||
const selected = selectAddressRecipe("customer_revenue_and_payments", {
|
||||
counterparty: "Группа СВК",
|
||||
period_from: "2020-01-01",
|
||||
period_to: "2020-12-31"
|
||||
});
|
||||
expect(selected.selected_recipe).toBeTruthy();
|
||||
const plan = buildAddressRecipePlan(selected.selected_recipe!, {
|
||||
counterparty: "Группа СВК",
|
||||
period_from: "2020-01-01",
|
||||
period_to: "2020-12-31"
|
||||
});
|
||||
|
||||
expect(plan.query).toContain('БанкПоступление.Контрагент.Наименование ПОДОБНО "%Группа%"');
|
||||
expect(plan.query).toContain('БанкПоступление.Контрагент.Наименование ПОДОБНО "%СВК%"');
|
||||
});
|
||||
|
||||
it("expands customer value analytics sample independently from visible ranking size", () => {
|
||||
const filters = extractAddressFilters("какой у нас самый доходный год", "customer_revenue_and_payments");
|
||||
const selected = selectAddressRecipe("customer_revenue_and_payments", filters.extracted_filters);
|
||||
@@ -5132,6 +5149,23 @@ describe("address recipe catalog counterparty filtering", () => {
|
||||
expect(plan.query).toContain("БанкСписание.ДоговорКонтрагента");
|
||||
});
|
||||
|
||||
it("injects counterparty condition into supplier payout recipe", () => {
|
||||
const selected = selectAddressRecipe("supplier_payouts_profile", {
|
||||
counterparty: "Группа СВК",
|
||||
period_from: "2020-01-01",
|
||||
period_to: "2020-12-31"
|
||||
});
|
||||
expect(selected.selected_recipe).toBeTruthy();
|
||||
const plan = buildAddressRecipePlan(selected.selected_recipe!, {
|
||||
counterparty: "Группа СВК",
|
||||
period_from: "2020-01-01",
|
||||
period_to: "2020-12-31"
|
||||
});
|
||||
|
||||
expect(plan.query).toContain('БанкСписание.Контрагент.Наименование ПОДОБНО "%Группа%"');
|
||||
expect(plan.query).toContain('БанкСписание.Контрагент.Наименование ПОДОБНО "%СВК%"');
|
||||
});
|
||||
|
||||
it("selects contract value recipe and keeps top-20 default", () => {
|
||||
const selected = selectAddressRecipe("contract_usage_and_value", {});
|
||||
expect(selected.selected_recipe).toBeTruthy();
|
||||
@@ -5168,6 +5202,27 @@ describe("address recipe catalog counterparty filtering", () => {
|
||||
expect(plan.query).toContain("ПоступлениеНаРасчетныйСчет");
|
||||
});
|
||||
|
||||
it("injects counterparty condition into lifecycle and bank document recipes", () => {
|
||||
const lifecycle = selectAddressRecipe("counterparty_activity_lifecycle", {
|
||||
counterparty: "Группа СВК"
|
||||
});
|
||||
const bankDocs = selectAddressRecipe("bank_operations_by_counterparty", {
|
||||
counterparty: "Группа СВК"
|
||||
});
|
||||
expect(lifecycle.selected_recipe).toBeTruthy();
|
||||
expect(bankDocs.selected_recipe).toBeTruthy();
|
||||
const lifecyclePlan = buildAddressRecipePlan(lifecycle.selected_recipe!, {
|
||||
counterparty: "Группа СВК"
|
||||
});
|
||||
const bankDocsPlan = buildAddressRecipePlan(bankDocs.selected_recipe!, {
|
||||
counterparty: "Группа СВК"
|
||||
});
|
||||
|
||||
expect(lifecyclePlan.query).toContain('БанкПоступление.Контрагент.Наименование ПОДОБНО "%СВК%"');
|
||||
expect(bankDocsPlan.query).toContain('БанкПоступление.Контрагент.Наименование ПОДОБНО "%СВК%"');
|
||||
expect(bankDocsPlan.query).toContain('БанкСписание.Контрагент.Наименование ПОДОБНО "%СВК%"');
|
||||
});
|
||||
|
||||
it("boosts limit for all-time counterparty queries", () => {
|
||||
const filters = extractAddressFilters(
|
||||
"Покажи документы по контрагенту тестовый за все время",
|
||||
|
||||
@@ -154,6 +154,75 @@ describe("assistant address orchestration runtime adapter", () => {
|
||||
);
|
||||
});
|
||||
|
||||
it("passes active session organization into MCP discovery when carryover context is absent", async () => {
|
||||
const runMcpDiscoveryRuntimeEntryPoint = vi.fn(async () => ({
|
||||
schema_version: "assistant_mcp_discovery_runtime_entry_point_v1",
|
||||
policy_owner: "assistantMcpDiscoveryRuntimeEntryPoint",
|
||||
entry_status: "bridge_executed",
|
||||
hot_runtime_wired: false,
|
||||
discovery_attempted: true
|
||||
}));
|
||||
const input = buildInput({
|
||||
userMessage: "money breakdown 2020",
|
||||
sessionOrganizationScope: {
|
||||
activeOrganization: "Org A",
|
||||
selectedOrganization: null,
|
||||
knownOrganizations: ["Org A"]
|
||||
},
|
||||
runAddressLlmPreDecompose: vi.fn(async () => ({
|
||||
attempted: true,
|
||||
applied: false,
|
||||
effectiveMessage: "money breakdown 2020",
|
||||
reason: "raw_kept",
|
||||
predecomposeContract: {
|
||||
mode: "unsupported",
|
||||
intent: "unknown",
|
||||
period: {
|
||||
scope: "year",
|
||||
period_from: "2020-01-01",
|
||||
period_to: "2020-12-31",
|
||||
has_explicit_period: true
|
||||
}
|
||||
}
|
||||
})),
|
||||
resolveAddressFollowupCarryoverContext: vi.fn(() => null),
|
||||
resolveAssistantOrchestrationDecision: vi.fn(() => ({
|
||||
runAddressLane: false,
|
||||
livingMode: "chat",
|
||||
livingReason: "unsupported_current_turn_meaning_boundary",
|
||||
toolGateDecision: "skip_address_lane",
|
||||
toolGateReason: "unsupported_current_turn_meaning_boundary",
|
||||
orchestrationContract: {
|
||||
schema_version: "assistant_orchestration_contract_v1",
|
||||
assistant_turn_meaning: {
|
||||
schema_version: "assistant_turn_meaning_v1",
|
||||
asked_domain_family: "business_overview",
|
||||
asked_action_family: "broad_evaluation",
|
||||
unsupported_but_understood_family: "broad_business_evaluation"
|
||||
}
|
||||
}
|
||||
})),
|
||||
runMcpDiscoveryRuntimeEntryPoint
|
||||
});
|
||||
|
||||
await buildAssistantAddressOrchestrationRuntime(input);
|
||||
|
||||
expect(runMcpDiscoveryRuntimeEntryPoint).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
followupContext: expect.objectContaining({
|
||||
previous_anchor_type: "organization",
|
||||
previous_anchor_value: "Org A",
|
||||
previous_filters: expect.objectContaining({
|
||||
organization: "Org A"
|
||||
}),
|
||||
root_filters: expect.objectContaining({
|
||||
organization: "Org A"
|
||||
})
|
||||
})
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
it("passes grounded discovery follow-up carryover into MCP discovery entry point for a short year switch", async () => {
|
||||
const runMcpDiscoveryRuntimeEntryPoint = vi.fn(async () => ({
|
||||
schema_version: "assistant_mcp_discovery_runtime_entry_point_v1",
|
||||
|
||||
@@ -1287,6 +1287,35 @@ describe("assistant MCP discovery answer adapter", () => {
|
||||
expect(draft.unknown_lines).toContain("Full supplier-payout amount outside the checked period is not proven by this MCP discovery pilot");
|
||||
});
|
||||
|
||||
it("renders zero-row supplier payout as a checked negative with period and counterparty", async () => {
|
||||
const planner = planAssistantMcpDiscovery({
|
||||
turnMeaning: {
|
||||
asked_domain_family: "counterparty_value",
|
||||
asked_action_family: "payout",
|
||||
explicit_entity_candidates: ["Группа СВК"],
|
||||
explicit_organization_scope: "ООО Альтернатива Плюс",
|
||||
explicit_date_scope: "2020",
|
||||
unsupported_but_understood_family: "counterparty_payouts_or_outflow"
|
||||
}
|
||||
});
|
||||
const pilot = await executeAssistantMcpDiscoveryPilot(planner, buildDeps([]));
|
||||
|
||||
const draft = buildAssistantMcpDiscoveryAnswerDraft(pilot);
|
||||
const confirmedText = draft.confirmed_lines.join("\n");
|
||||
const unknownText = draft.unknown_lines.join("\n");
|
||||
|
||||
expect(draft.answer_mode).toBe("checked_sources_only");
|
||||
expect(draft.headline).toContain("Группа СВК");
|
||||
expect(draft.headline).toContain("2020");
|
||||
expect(draft.headline).toContain("исходящих платежей");
|
||||
expect(confirmedText).toContain("Группа СВК");
|
||||
expect(confirmedText).toContain("ООО Альтернатива Плюс");
|
||||
expect(confirmedText).toContain("2020");
|
||||
expect(confirmedText).toContain("не найдено");
|
||||
expect(unknownText).toContain("вне периода 2020");
|
||||
expect(unknownText).toContain("вне доступного банковского контура");
|
||||
});
|
||||
|
||||
it("turns bidirectional value-flow evidence into a bounded net cash answer draft", async () => {
|
||||
const planner = planAssistantMcpDiscovery({
|
||||
turnMeaning: {
|
||||
|
||||
@@ -177,7 +177,7 @@ describe("assistant MCP discovery runtime entry point", () => {
|
||||
expect(result.bridge?.answer_draft.inference_lines.join("\n")).toContain("\u043d\u0435 \u043f\u0440\u0438\u0431\u044b\u043b\u044c");
|
||||
expect(result.bridge?.answer_draft.unknown_lines.join("\n")).toContain("VAT");
|
||||
expect(result.reason_codes).toContain("pilot_derived_business_overview_from_confirmed_rows");
|
||||
expect(deps.executeAddressMcpQuery).toHaveBeenCalledTimes(3);
|
||||
expect(deps.executeAddressMcpQuery).toHaveBeenCalledTimes(6);
|
||||
});
|
||||
|
||||
it("runs the bridge for raw metadata wording without an exact route owner", async () => {
|
||||
|
||||
@@ -2787,6 +2787,54 @@ describe("assistant MCP discovery turn input adapter", () => {
|
||||
expect(result.reason_codes).toContain("mcp_discovery_business_overview_continuation_from_followup_context");
|
||||
});
|
||||
|
||||
it("keeps detailed money-breakdown follow-up in business overview without pseudo counterparty anchors", () => {
|
||||
const orgName =
|
||||
"\u041e\u041e\u041e \u0410\u043b\u044c\u0442\u0435\u0440\u043d\u0430\u0442\u0438\u0432\u0430 \u041f\u043b\u044e\u0441";
|
||||
const result = buildAssistantMcpDiscoveryTurnInput({
|
||||
userMessage:
|
||||
"\u0420\u0430\u0441\u043a\u0440\u043e\u0439 \u0434\u0435\u043d\u044c\u0433\u0438 \u043f\u043e\u0434\u0440\u043e\u0431\u043d\u0435\u0435: \u0441\u043a\u043e\u043b\u044c\u043a\u043e \u0432\u0441\u0435\u0433\u043e \u043f\u043e\u043b\u0443\u0447\u0438\u043b\u0438, \u0441\u043a\u043e\u043b\u044c\u043a\u043e \u0437\u0430\u043f\u043b\u0430\u0442\u0438\u043b\u0438, \u043a\u0430\u043a\u043e\u0439 \u0447\u0438\u0441\u0442\u044b\u0439 \u0434\u0435\u043d\u0435\u0436\u043d\u044b\u0439 \u043f\u043e\u0442\u043e\u043a, \u043a\u0442\u043e \u0433\u043b\u0430\u0432\u043d\u044b\u0439 \u043a\u043b\u0438\u0435\u043d\u0442 \u0438 \u043a\u0442\u043e \u0433\u043b\u0430\u0432\u043d\u044b\u0439 \u043f\u043e\u0441\u0442\u0430\u0432\u0449\u0438\u043a \u0432 2020.",
|
||||
assistantTurnMeaning: {
|
||||
asked_domain_family: "counterparty_value",
|
||||
asked_action_family: "net_value_flow",
|
||||
explicit_intent_candidate: "customer_revenue_and_payments",
|
||||
explicit_entity_candidates: [
|
||||
"\u0438 \u043a\u0442\u043e \u0433\u043b\u0430\u0432\u043d\u044b\u0439 \u043f\u043e\u0441\u0442\u0430\u0432\u0449\u0438\u043a \u0432"
|
||||
]
|
||||
},
|
||||
predecomposeContract: {
|
||||
entities: {
|
||||
counterparty: "\u0438 \u043a\u0442\u043e \u0433\u043b\u0430\u0432\u043d\u044b\u0439 \u043f\u043e\u0441\u0442\u0430\u0432\u0449\u0438\u043a \u0432"
|
||||
},
|
||||
period: { period_from: "2020-01-01", period_to: "2020-12-31" }
|
||||
},
|
||||
followupContext: {
|
||||
previous_discovery_pilot_scope: "business_overview_route_template_v1",
|
||||
previous_filters: {
|
||||
organization: orgName,
|
||||
period_from: "2020-01-01",
|
||||
period_to: "2020-12-31"
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
expect(result.adapter_status).toBe("ready");
|
||||
expect(result.should_run_discovery).toBe(true);
|
||||
expect(result.semantic_data_need).toBe("business overview evidence with bounded analyst interpretation");
|
||||
expect(result.data_need_graph?.business_fact_family).toBe("business_overview");
|
||||
expect(result.data_need_graph?.subject_candidates).toEqual([]);
|
||||
expect(result.turn_meaning_ref).toMatchObject({
|
||||
asked_domain_family: "business_overview",
|
||||
asked_action_family: "broad_evaluation",
|
||||
explicit_organization_scope: orgName,
|
||||
explicit_date_scope: "2020",
|
||||
unsupported_but_understood_family: "broad_business_evaluation",
|
||||
stale_replay_forbidden: true
|
||||
});
|
||||
expect(result.turn_meaning_ref?.explicit_entity_candidates).toBeUndefined();
|
||||
expect(result.reason_codes).toContain("mcp_discovery_business_overview_continuation_from_followup_context");
|
||||
expect(result.reason_codes).not.toContain("mcp_discovery_counterparty_from_predecompose");
|
||||
});
|
||||
|
||||
it("continues business overview on by-these-data profit wording without grounding pseudo anchors", () => {
|
||||
const orgName =
|
||||
"\u041e\u041e\u041e \u0410\u043b\u044c\u0442\u0435\u0440\u043d\u0430\u0442\u0438\u0432\u0430 \u041f\u043b\u044e\u0441";
|
||||
|
||||
@@ -146,6 +146,25 @@ describe("assistantTurnMeaningPolicy", () => {
|
||||
expect(meaning.reason_codes).toContain("broad_business_evaluation_current_turn_signal");
|
||||
});
|
||||
|
||||
it("treats compound money breakdown as business overview instead of narrow customer revenue", () => {
|
||||
const policy = buildPolicy({
|
||||
resolveAddressIntent: () => ({ intent: "customer_revenue_and_payments", confidence: "high" })
|
||||
});
|
||||
|
||||
const meaning = policy.resolveAssistantTurnMeaning({
|
||||
rawUserMessage:
|
||||
"\u0420\u0430\u0441\u043a\u0440\u043e\u0439 \u0434\u0435\u043d\u044c\u0433\u0438 \u043f\u043e\u0434\u0440\u043e\u0431\u043d\u0435\u0435: \u0441\u043a\u043e\u043b\u044c\u043a\u043e \u0432\u0441\u0435\u0433\u043e \u043f\u043e\u043b\u0443\u0447\u0438\u043b\u0438, \u0441\u043a\u043e\u043b\u044c\u043a\u043e \u0437\u0430\u043f\u043b\u0430\u0442\u0438\u043b\u0438, \u043a\u0430\u043a\u043e\u0439 \u0447\u0438\u0441\u0442\u044b\u0439 \u0434\u0435\u043d\u0435\u0436\u043d\u044b\u0439 \u043f\u043e\u0442\u043e\u043a, \u043a\u0442\u043e \u0433\u043b\u0430\u0432\u043d\u044b\u0439 \u043a\u043b\u0438\u0435\u043d\u0442 \u0438 \u043a\u0442\u043e \u0433\u043b\u0430\u0432\u043d\u044b\u0439 \u043f\u043e\u0441\u0442\u0430\u0432\u0449\u0438\u043a \u0432 2020."
|
||||
});
|
||||
|
||||
expect(meaning.explicit_intent_candidate).toBeNull();
|
||||
expect(meaning.asked_domain_family).toBe("business_summary");
|
||||
expect(meaning.asked_action_family).toBe("broad_evaluation");
|
||||
expect(meaning.explicit_entity_candidates).toEqual([]);
|
||||
expect(meaning.unsupported_but_understood_family).toBe("broad_business_evaluation");
|
||||
expect(meaning.stale_replay_forbidden).toBe(true);
|
||||
expect(meaning.reason_codes).toContain("broad_business_evaluation_current_turn_signal");
|
||||
});
|
||||
|
||||
it("treats organization-level earnings and best-year wording as business overview", () => {
|
||||
const policy = buildPolicy({
|
||||
resolveAddressIntent: () => ({ intent: "customer_revenue_and_payments", confidence: "high" })
|
||||
|
||||
Reference in New Issue
Block a user