Post-F: закрыть ручные провалы НДС, выручки и item-flow

This commit is contained in:
2026-04-24 20:36:34 +03:00
parent 7262603a26
commit 739e8b808f
16 changed files with 695 additions and 37 deletions
@@ -30,6 +30,61 @@ describe("counterparty shipment item flow and open-items routing", () => {
expect(result.reasons).toContain("counterparty_item_flow_signal_detected");
});
it("keeps plain Russian counterparty item-flow wording out of stale inventory context", async () => {
executeAddressMcpQueryMock
.mockResolvedValueOnce({
fetched_rows: 1,
matched_rows: 1,
raw_rows: [
{
Counterparty: "Чепурнов П.Д.",
Registrator: "Чепурнов П.Д."
}
],
rows: [],
error: null
})
.mockResolvedValueOnce({
fetched_rows: 1,
matched_rows: 1,
raw_rows: [
{
Period: "2022-01-20T12:00:03Z",
Registrator: "Поступление товаров и услуг 000000001 от 20.01.2022",
AccountDt: "41.01",
AccountKt: "60.01",
Amount: 890660,
Nomenclature: "Услуги по договору",
Counterparty: "Чепурнов П.Д.",
Contract: "Договор № 11/1 от 25.11.2020 г.",
Organization: 'ООО "Альтернатива Плюс"'
}
],
rows: [],
error: null
});
const service = new AddressQueryService();
const result = await service.tryHandle("что нам отгружал чепурнов? какой товар или услугу?", {
followupContext: {
previous_intent: "inventory_on_hand_as_of_date",
target_intent: "inventory_on_hand_as_of_date",
previous_filters: {
organization: 'ООО "Альтернатива Плюс"',
as_of_date: "2026-04-24"
},
previous_anchor_type: "organization",
previous_anchor_value: 'ООО "Альтернатива Плюс"'
}
});
expect(result?.handled).toBe(true);
expect(result?.debug.detected_intent).toBe("list_documents_by_counterparty");
expect(result?.debug.selected_recipe).toBe("address_documents_by_counterparty_v1");
expect(String(result?.reply_text ?? "")).toContain("Чепурнов П.Д.");
expect(String(result?.reply_text ?? "")).toContain("Услуги по договору");
});
it("routes account 60 tails wording to open items intent", () => {
const result = resolveAddressIntent("хвосты покажи по счету 60 на август 2022");
expect(result.intent).toBe("open_items_by_counterparty_or_contract");
@@ -60,8 +115,10 @@ describe("counterparty shipment item flow and open-items routing", () => {
});
it("uses purchase document query for fuzzy counterparty item-flow wording", async () => {
executeAddressMcpQueryMock
.mockResolvedValueOnce({
executeAddressMcpQueryMock.mockImplementation(async (request?: { query?: string }) => {
const query = String(request?.query ?? "");
if (query.includes("Справочник.Контрагенты")) {
return {
fetched_rows: 1,
matched_rows: 1,
raw_rows: [
@@ -72,8 +129,9 @@ describe("counterparty shipment item flow and open-items routing", () => {
],
rows: [],
error: null
})
.mockResolvedValueOnce({
};
}
return {
fetched_rows: 2,
matched_rows: 2,
raw_rows: [
@@ -104,7 +162,8 @@ describe("counterparty shipment item flow and open-items routing", () => {
],
rows: [],
error: null
});
};
});
const service = new AddressQueryService();
const result = await service.tryHandle(
@@ -117,4 +117,40 @@ describe("counterparty lifecycle organization scope regressions", () => {
expect(result?.debug.extracted_filters?.counterparty).toBeUndefined();
expect(String(result?.reply_text ?? "")).toContain('ООО "Ромашка"');
});
it("routes who-is-the-highest-value-customer wording to revenue ranking, not lifecycle activity", async () => {
executeAddressMcpQueryMock.mockResolvedValueOnce({
fetched_rows: 2,
matched_rows: 2,
raw_rows: [
{
Period: "2021-01-15T00:00:00Z",
Registrator: "CP_CUSTOMER_ACTIVITY",
AccountDt: "51",
AccountKt: "62.01",
Amount: 150000,
Counterparty: "Группа СВК"
},
{
Period: "2021-02-20T00:00:00Z",
Registrator: "CP_CUSTOMER_ACTIVITY",
AccountDt: "51",
AccountKt: "62.01",
Amount: 80000,
Counterparty: "Чепурнов П.Д."
}
],
rows: [],
error: null
});
const service = new AddressQueryService();
const result = await service.tryHandle("кто у нас самый доходный клиент за все время");
expect(result?.handled).toBe(true);
expect(result?.debug.detected_intent).toBe("customer_revenue_and_payments");
expect(result?.debug.selected_recipe).toBe("address_customer_revenue_and_payments_v1");
expect(String(result?.reply_text ?? "")).toContain("Самый доходный клиент");
expect(String(result?.reply_text ?? "")).toContain("Группа СВК");
});
});
@@ -3955,11 +3955,11 @@ describe("address query limited taxonomy and stage diagnostics", { timeout: 1500
expect(result?.debug.mcp_call_status).not.toBe("skipped");
});
it("returns factual confirmed VAT snapshot instead of partial when payable rows are absent", async () => {
it("returns factual confirmed VAT tax-period answer instead of partial when payable rows are absent", async () => {
const service = new AddressQueryService();
const result = await service.tryHandle("скок ндс платить надо на март 2020");
expect(result?.handled).toBe(true);
expect(result?.debug.detected_intent).toBe("vat_payable_confirmed_as_of_date");
expect(result?.debug.detected_intent).toBe("vat_liability_confirmed_for_tax_period");
expect(["FACTUAL_LIST", "FACTUAL_SUMMARY"]).toContain(result?.response_type);
expect(result?.reply_type).not.toBe("partial_coverage");
expect(result?.debug.result_mode).toBe("confirmed_balance");
@@ -4692,13 +4692,13 @@ describe("address decompose stage follow-up carryover", () => {
period_to: "2020-12-31"
},
previous_anchor_type: "counterparty",
previous_anchor_value: "?П Калинин Н.М.",
previous_anchor_value: "ИП Калинин Н.М.",
resolved_counterparty_from_display: true
});
expect(result).not.toBeNull();
expect(result?.mode.mode).toBe("address_query");
expect(result?.intent.intent).toBe("customer_revenue_and_payments");
expect(result?.filters.extracted_filters.counterparty).toBe("?П Калинин Н.М.");
expect(result?.filters.extracted_filters.counterparty).toBe("ИП Калинин Н.М.");
expect(result?.filters.extracted_filters.period_from).toBe("2020-01-01");
expect(result?.filters.extracted_filters.period_to).toBe("2020-12-31");
expect(
@@ -5201,6 +5201,19 @@ describe("address recipe catalog counterparty filtering", () => {
expect(plan.query).not.toContain("ПРЕДСТАВЛЕНИЕ(Движения.СчетДт) ПОДОБНО");
});
it("materializes VAT forecast aggregate inside the requested period window", () => {
const filters = extractAddressFilters(
"прикинь какой ндс нам надо заплатить на февраль 2017",
"vat_payable_forecast"
).extracted_filters;
const selected = selectAddressRecipe("vat_payable_forecast", filters);
expect(selected.selected_recipe).toBeTruthy();
const plan = buildAddressRecipePlan(selected.selected_recipe!, filters);
expect(plan.query).toContain("ДАТАВРЕМЯ(2017, 2, 28, 23, 59, 59) КАК Период");
expect(plan.query).not.toContain("ДАТАВРЕМЯ(2000, 1, 1, 0, 0, 0) КАК Период");
});
it("builds confirmed VAT tax-period query from sales and purchase VAT books", () => {
const filters = extractAddressFilters(
"сколько ндс надо заплатить в налоговую за декабрь 2019",