ДОМЕНЫ - ВОПРОСЫ - СКЛАД - Протянуть exact capability складских остатков товаров на дату

This commit is contained in:
2026-04-13 20:40:24 +03:00
parent c2ac0c610b
commit 2b48229312
27 changed files with 1781 additions and 95 deletions
@@ -33,6 +33,15 @@ describe("address capability policy", () => {
expect(isCapabilityRouteBlocked(decision)).toBe(false);
});
it("maps inventory-on-hand intent to compute exact capability", () => {
const decision = resolveAddressCapabilityRouteDecision("inventory_on_hand_as_of_date");
expect(decision.capability_id).toBe("confirmed_inventory_on_hand_as_of_date");
expect(decision.capability_layer).toBe("compute");
expect(decision.capability_route_mode).toBe("exact");
expect(decision.capability_route_enabled).toBe(true);
expect(isCapabilityRouteBlocked(decision)).toBe(false);
});
it("maps document drilldown intent to navigation capability", () => {
const decision = resolveAddressCapabilityRouteDecision("list_documents_by_contract");
expect(decision.capability_id).toBe("documents_drilldown");
@@ -4327,6 +4327,72 @@ describe("address recipe catalog counterparty filtering", () => {
expect(plan.query).toContain("VAT_BOOK_SALES");
expect(plan.query).toContain("VAT_BOOK_PURCHASES");
});
it("keeps inventory-on-hand phrasing in address lane", () => {
const result = detectAddressQuestionMode("Какие товары сейчас лежат на складе");
expect(result.mode).toBe("address_query");
});
it("detects exact inventory-on-hand intent", () => {
const result = resolveAddressIntent("Какие товары сейчас лежат на складе");
expect(result.intent).toBe("inventory_on_hand_as_of_date");
expect(result.confidence).toBe("high");
});
it("derives as_of_date for inventory-on-hand from explicit month window", () => {
const filters = extractAddressFilters(
"Какие товары лежат на складе на март 2020",
"inventory_on_hand_as_of_date"
).extracted_filters;
expect(filters.period_from).toBe("2020-03-01");
expect(filters.period_to).toBe("2020-03-31");
expect(filters.as_of_date).toBe("2020-03-31");
});
it("builds exact balance query for inventory-on-hand snapshot", () => {
const selected = selectAddressRecipe("inventory_on_hand_as_of_date", {
as_of_date: "2020-03-31"
});
expect(selected.selected_recipe?.recipe_id).toBe("address_inventory_on_hand_as_of_date_v1");
const plan = buildAddressRecipePlan(selected.selected_recipe!, {
as_of_date: "2020-03-31"
});
expect(plan.query).toContain("РегистрБухгалтерии.Хозрасчетный.Остатки(");
expect(plan.query).toContain("КоличествоРазвернутыйОстатокДт");
expect(plan.query).toContain("ПОДСТРОКА(ЕСТЬNULL(Остатки.Счет.Код, \"\"), 1, 5) = \"41.01\"");
});
it("renders confirmed inventory-on-hand snapshot from normalized rows", () => {
const reply = composeFactualReply(
"inventory_on_hand_as_of_date",
[
{
period: "2020-03-31T23:59:59Z",
registrator: "Остатки на дату",
account_dt: "41.01",
account_kt: "",
amount: 712500,
analytics: ["Шкаф картотечный", "Основной склад", "ООО Ромашка"],
item: "Шкаф картотечный",
warehouse: "Основной склад",
organization: "ООО Ромашка",
quantity: 15
}
],
{
asOfDate: "2020-03-31",
useRubCurrency: true
}
);
expect(reply.responseType).toBe("FACTUAL_LIST");
expect(reply.text).toContain("Собран подтвержденный срез товаров на складах");
expect(reply.text).toContain("Контур: остатки по счету 41.01");
expect(reply.text).toContain("Шкаф картотечный");
expect(reply.text).toContain("Основной склад");
expect(reply.semantics?.result_mode).toBe("confirmed_balance");
expect(reply.semantics?.balance_confirmed).toBe(true);
});
});
@@ -45,6 +45,17 @@ describe("address route expectations contract", () => {
expect(audit.reason).toBe("route_expectation_matched");
});
it("matches expected recipe and result mode for exact inventory route", () => {
const audit = evaluateAddressRouteExpectation({
intent: "inventory_on_hand_as_of_date",
selectedRecipe: "address_inventory_on_hand_as_of_date_v1",
requestedResultMode: "confirmed_balance",
resultMode: "confirmed_balance"
});
expect(audit.status).toBe("matched");
expect(audit.reason).toBe("route_expectation_matched");
});
it("matches expected recipe and result mode for exact VAT tax-period liability route", () => {
const audit = evaluateAddressRouteExpectation({
intent: "vat_liability_confirmed_for_tax_period",
@@ -646,6 +646,24 @@ describe("assistant orchestration contract", () => {
expect(decision.toolGateReason).toBe("address_mode_classifier_detected");
});
it("keeps inventory-on-hand query in address lane", () => {
const decision = resolveAssistantOrchestrationDecision({
rawUserMessage: "Какие товары сейчас лежат на складе",
effectiveAddressUserMessage: "Какие товары сейчас лежат на складе",
followupContext: null,
llmPreDecomposeMeta: null as any,
useMock: true
} as any);
expect(decision.runAddressLane).toBe(true);
expect(decision.toolGateDecision).toBe("run_address_lane");
expect(["address_intent_resolver_detected", "address_mode_classifier_detected", "address_signal_detected"]).toContain(
String(decision.toolGateReason)
);
expect(decision.livingMode).toBe("address_data");
expect(decision.livingReason).toBe("address_lane_triggered");
});
it("keeps open-contracts request in address lane even with stale deep followup context when LLM contract is absent", () => {
const decision = resolveAssistantOrchestrationDecision({
rawUserMessage: "Покажи незакрытые договоры на 2020-12-31",