Open-World: добавить торговый margin proxy в бизнес-обзор

This commit is contained in:
2026-05-04 10:07:26 +03:00
parent 822baedcba
commit 062655eca0
12 changed files with 633 additions and 57 deletions
@@ -294,11 +294,22 @@ describe("assistant MCP discovery answer adapter", () => {
{ Регистратор: "VAT_BOOK_PURCHASES", СчетДт: "19", Сумма: 12000 }
]
},
{ rows: [] },
{ rows: [] },
{ rows: [] },
{ rows: [] },
{ rows: [] },
{
rows: [
{ Период: "2020-01-15T00:00:00", Регистратор: "Поступление 1" },
{ Период: "2020-12-15T00:00:00", Регистратор: "Поступление 2" }
]
},
{
rows: [
{ Period: "2020-03-01T00:00:00", Amount: 200000, Item: "Товар А", Counterparty: "Клиент А", СчетКт: "41.01" },
{ Period: "2020-02-01T00:00:00", Amount: 120000, Item: "Товар А", Counterparty: "Поставщик А", СчетДт: "41.01" }
]
}
])
);
@@ -306,12 +317,20 @@ describe("assistant MCP discovery answer adapter", () => {
const draft = buildAssistantMcpDiscoveryAnswerDraft(pilot);
expect(draft.headline).toContain("НДС-позиция");
expect(draft.headline).toContain("торговый margin proxy");
expect(draft.confirmed_lines.join("\n")).toContain("НДС-позиция за 2020");
expect(draft.confirmed_lines.join("\n")).toContain("нетто к уплате 28 000 руб.");
expect(draft.confirmed_lines.join("\n")).toContain("Торговый margin proxy за 2020");
expect(draft.confirmed_lines.join("\n")).toContain("валовый спред proxy 80 000 руб.");
expect(draft.confirmed_lines.join("\n")).toContain("не чистая прибыль");
expect(draft.inference_lines.join("\n")).toContain("торговый спред proxy 80 000 руб.");
expect(draft.inference_lines.join("\n")).toContain("не прибыль и не маржа");
expect(draft.unknown_lines.join("\n")).toContain("Чистая прибыль");
expect(draft.unknown_lines.join("\n")).not.toContain("Налоговая/VAT-позиция");
expect(draft.reason_codes).toContain("answer_contains_business_overview_tax_position");
expect(draft.reason_codes).toContain("answer_contains_business_overview_trading_margin_proxy");
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 trading-margin proxy as clean profit, accounting financial result, or exact cost-of-sales margin.");
});
it("surfaces checked debt-position and open-settlement quality without treating them as overdue debt", async () => {
@@ -151,6 +151,12 @@ describe("assistant MCP discovery pilot executor", () => {
{ Период: "2020-01-15T00:00:00", Регистратор: "Поступление 1" },
{ Период: "2020-12-15T00:00:00", Регистратор: "Поступление 2" }
]
},
{
rows: [
{ Period: "2020-03-01T00:00:00", Amount: 200000, Item: "Товар А", Counterparty: "Клиент А", СчетКт: "41.01" },
{ Period: "2020-02-01T00:00:00", Amount: 120000, Item: "Товар А", Counterparty: "Поставщик А", СчетДт: "41.01" }
]
}
]);
@@ -251,6 +257,12 @@ describe("assistant MCP discovery pilot executor", () => {
{ Период: "2020-01-15T00:00:00", Регистратор: "Поступление 1" },
{ Период: "2020-12-15T00:00:00", Регистратор: "Поступление 2" }
]
},
{
rows: [
{ Period: "2020-03-01T00:00:00", Amount: 200000, Item: "Товар А", Counterparty: "Клиент А", AccountKt: "41.01" },
{ Period: "2020-02-01T00:00:00", Amount: 120000, Item: "Товар А", Counterparty: "Поставщик А", AccountDt: "41.01" }
]
}
]);
@@ -266,15 +278,96 @@ describe("assistant MCP discovery pilot executor", () => {
net_vat_amount: 28000,
net_vat_direction: "vat_to_pay"
});
expect(result.derived_business_overview?.trading_margin_proxy).toMatchObject({
period_scope: "2020",
sales_rows_with_amount: 1,
purchase_rows_with_amount: 1,
sales_revenue: 200000,
purchase_cost_proxy: 120000,
gross_spread_proxy: 80000,
margin_to_revenue_pct: 40
});
expect(result.derived_business_overview?.missing_signal_families).not.toContain("tax_position");
expect(result.derived_business_overview?.missing_signal_families).not.toContain("profit_margin");
expect(result.derived_business_overview?.missing_signal_families).toContain("accounting_profit_margin");
expect(result.evidence.confirmed_facts.join("\n")).toContain("НДС-позиция за 2020 подтверждена");
expect(result.evidence.confirmed_facts.join("\n")).toContain("Торговый margin proxy за 2020");
expect(result.evidence.unknown_facts.join("\n")).toContain("Чистая прибыль");
expect(result.evidence.unknown_facts.join("\n")).not.toContain("Налоговая/VAT-позиция этим бизнес-обзором не подтверждена");
expect(result.reason_codes).toContain("pilot_business_overview_tax_query_mcp_executed");
expect(result.reason_codes).toContain("pilot_derived_business_overview_tax_position_from_confirmed_rows");
expect(deps.executeAddressMcpQuery).toHaveBeenCalledTimes(9);
expect(result.reason_codes).toContain("pilot_business_overview_trading_margin_query_mcp_executed");
expect(result.reason_codes).toContain("pilot_derived_business_overview_trading_margin_proxy_from_confirmed_rows");
expect(deps.executeAddressMcpQuery).toHaveBeenCalledTimes(10);
const taxCall = deps.executeAddressMcpQuery.mock.calls[2]?.[0];
const tradingMarginCall = deps.executeAddressMcpQuery.mock.calls[9]?.[0];
expect(String(taxCall?.query ?? "")).toContain("НДСЗаписиКнигиПродаж");
expect(String(taxCall?.query ?? "")).toContain("НДСЗаписиКнигиПокупок");
expect(String(tradingMarginCall?.query ?? "")).toContain("Документ.РеализацияТоваровУслуг.Товары");
expect(String(tradingMarginCall?.query ?? "")).toContain("Документ.ПоступлениеТоваровУслуг.Товары");
});
it("keeps negative trading-margin proxy signed instead of reporting an absolute spread", async () => {
const planner = planAssistantMcpDiscovery({
dataNeedGraph: {
schema_version: "assistant_data_need_graph_v1",
policy_owner: "assistantMcpDiscoveryDataNeedGraph",
subject_candidates: [],
business_fact_family: "business_overview",
action_family: "broad_evaluation",
aggregation_need: null,
time_scope_need: "explicit_period",
comparison_need: null,
ranking_need: null,
proof_expectation: "bounded_inference",
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_profit_or_margin_claim_without_evidence"],
reason_codes: ["data_need_graph_built", "data_need_graph_family_business_overview"]
},
turnMeaning: {
asked_domain_family: "business_overview",
asked_action_family: "broad_evaluation",
explicit_organization_scope: "ООО Альтернатива Плюс",
explicit_date_scope: "2020"
}
});
const deps = buildSequentialDeps([
{ rows: [] },
{ rows: [] },
{ rows: [] },
{ rows: [] },
{ rows: [] },
{ rows: [] },
{ rows: [] },
{ rows: [] },
{ rows: [] },
{
rows: [
{ Period: "2020-03-01T00:00:00", Amount: 100000, Item: "Товар А", Counterparty: "Клиент А", AccountKt: "41.01" },
{ Period: "2020-02-01T00:00:00", Amount: 150000, Item: "Товар А", Counterparty: "Поставщик А", AccountDt: "41.01" }
]
}
]);
const result = await executeAssistantMcpDiscoveryPilot(planner, deps);
expect(result.derived_business_overview?.trading_margin_proxy).toMatchObject({
sales_revenue: 100000,
purchase_cost_proxy: 150000,
gross_spread_proxy: -50000,
gross_spread_proxy_human_ru: "-50 000 руб.",
margin_to_revenue_pct: -50
});
expect(result.evidence.confirmed_facts.join("\n")).toContain("валовый спред proxy -50 000 руб.");
expect(result.evidence.confirmed_facts.join("\n")).toContain("не чистая прибыль");
});
it("adds a checked debt-position family to business overview only as an as-of-date snapshot", async () => {
@@ -337,7 +430,8 @@ describe("assistant MCP discovery pilot executor", () => {
{ Period: "2020-01-15T00:00:00", Registrator: "Поступление 1" },
{ Period: "2020-12-15T00:00:00", Registrator: "Поступление 2" }
]
}
},
{ rows: [] }
]);
const result = await executeAssistantMcpDiscoveryPilot(planner, deps);
@@ -393,7 +487,7 @@ describe("assistant MCP discovery pilot executor", () => {
expect(result.reason_codes).toContain("pilot_business_overview_open_contracts_query_mcp_executed");
expect(result.reason_codes).toContain("pilot_derived_business_overview_open_settlement_quality_from_confirmed_rows");
expect(result.reason_codes).toContain("pilot_derived_business_overview_debt_age_signal_from_contract_dates");
expect(deps.executeAddressMcpQuery).toHaveBeenCalledTimes(9);
expect(deps.executeAddressMcpQuery).toHaveBeenCalledTimes(10);
const receivablesCall = deps.executeAddressMcpQuery.mock.calls[3]?.[0];
const payablesCall = deps.executeAddressMcpQuery.mock.calls[4]?.[0];
const openContractsCall = deps.executeAddressMcpQuery.mock.calls[5]?.[0];
@@ -458,7 +552,8 @@ describe("assistant MCP discovery pilot executor", () => {
{ Period: "2020-01-15T00:00:00", Registrator: "Поступление 1" },
{ Period: "2020-12-15T00:00:00", Registrator: "Поступление 2" }
]
}
},
{ rows: [] }
]);
const result = await executeAssistantMcpDiscoveryPilot(planner, deps);
@@ -490,7 +585,7 @@ describe("assistant MCP discovery pilot executor", () => {
expect(result.evidence.unknown_facts.join("\n")).toContain("оборачиваемость");
expect(result.reason_codes).toContain("pilot_business_overview_inventory_query_mcp_executed");
expect(result.reason_codes).toContain("pilot_derived_business_overview_inventory_position_from_confirmed_rows");
expect(deps.executeAddressMcpQuery).toHaveBeenCalledTimes(9);
expect(deps.executeAddressMcpQuery).toHaveBeenCalledTimes(10);
const inventoryCall = deps.executeAddressMcpQuery.mock.calls[6]?.[0];
expect(inventoryCall?.account_scope).toContain("41.01");
});