АРЧ АП11 - Архитектура после регресса: + Архитектура: восстановить bridge от provenance выбранной позиции к VAT-периоду и закрыть phase10 replay
This commit is contained in:
@@ -0,0 +1,17 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
|
||||
import { extractAddressFilters } from "../src/services/addressFilterExtractor";
|
||||
|
||||
describe("address filter extractor regressions", () => {
|
||||
it("keeps explicit month window for confirmed VAT tax-period intent", () => {
|
||||
const extracted = extractAddressFilters(
|
||||
"сколько ндс надо заплатить в налоговую за декабрь 2019",
|
||||
"vat_liability_confirmed_for_tax_period"
|
||||
);
|
||||
|
||||
expect(extracted.extracted_filters.period_from).toBe("2019-12-01");
|
||||
expect(extracted.extracted_filters.period_to).toBe("2019-12-31");
|
||||
expect(extracted.warnings).toContain("period_derived_from_month_phrase");
|
||||
expect(extracted.warnings).not.toContain("period_derived_from_tax_quarter_for_confirmed_vat_liability");
|
||||
});
|
||||
});
|
||||
@@ -55,7 +55,7 @@ describe("address follow-up temporal regressions", () => {
|
||||
});
|
||||
|
||||
expect(result).not.toBeNull();
|
||||
expect(result?.intent.intent).toBe("vat_payable_confirmed_as_of_date");
|
||||
expect(result?.intent.intent).toBe("vat_liability_confirmed_for_tax_period");
|
||||
expect(result?.filters.extracted_filters.period_from).toBe("2017-05-01");
|
||||
expect(result?.filters.extracted_filters.period_to).toBe("2017-05-31");
|
||||
expect(result?.filters.extracted_filters.as_of_date).toBe("2017-05-31");
|
||||
@@ -76,7 +76,7 @@ describe("address follow-up temporal regressions", () => {
|
||||
});
|
||||
|
||||
expect(result).not.toBeNull();
|
||||
expect(result?.intent.intent).toBe("vat_payable_confirmed_as_of_date");
|
||||
expect(result?.intent.intent).toBe("vat_liability_confirmed_for_tax_period");
|
||||
expect(result?.filters.extracted_filters.period_from).toBe("2017-05-01");
|
||||
expect(result?.filters.extracted_filters.period_to).toBe("2017-05-31");
|
||||
expect(result?.filters.extracted_filters.as_of_date).toBe("2017-05-31");
|
||||
@@ -116,4 +116,26 @@ describe("address follow-up temporal regressions", () => {
|
||||
expect(result?.filters.extracted_filters.warehouse).toBeUndefined();
|
||||
expect(result?.baseReasons).toContain("as_of_date_from_followup_context");
|
||||
});
|
||||
|
||||
it("retargets inventory purchase-date VAT bridge into confirmed VAT period with inherited purchase month", () => {
|
||||
const result = runAddressDecomposeStage("ндс можешь прикинуть на дату покупки рабочей станции?", {
|
||||
previous_intent: "inventory_purchase_provenance_for_item",
|
||||
target_intent: "vat_liability_confirmed_for_tax_period",
|
||||
previous_filters: {
|
||||
item: "Рабочая станция универсального специалиста",
|
||||
organization: 'ООО "Альтернатива Плюс"',
|
||||
period_from: "2015-02-01",
|
||||
period_to: "2015-02-28",
|
||||
as_of_date: "2016-03-31"
|
||||
},
|
||||
previous_anchor_type: "item",
|
||||
previous_anchor_value: "Рабочая станция универсального специалиста"
|
||||
});
|
||||
|
||||
expect(result).not.toBeNull();
|
||||
expect(result?.intent.intent).toBe("vat_liability_confirmed_for_tax_period");
|
||||
expect(result?.filters.extracted_filters.period_from).toBe("2015-02-01");
|
||||
expect(result?.filters.extracted_filters.period_to).toBe("2015-02-28");
|
||||
expect(result?.baseReasons).toContain("period_from_followup_context");
|
||||
});
|
||||
});
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { resolveAddressIntent } from "../src/services/addressIntentResolver";
|
||||
|
||||
describe("addressIntentResolver regression bridges", () => {
|
||||
it("detects colloquial VAT liability for a month period", () => {
|
||||
const result = resolveAddressIntent("прикинь какой ндс нам надо заплатить на февраль 2017");
|
||||
|
||||
expect(result.intent).toBe("vat_liability_confirmed_for_tax_period");
|
||||
});
|
||||
|
||||
it("detects payables snapshot wording in plain human form", () => {
|
||||
const result = resolveAddressIntent("мы должны комуто денег на сегодня?");
|
||||
|
||||
expect(result.intent).toBe("payables_confirmed_as_of_date");
|
||||
});
|
||||
|
||||
it("detects top customer all-time revenue wording", () => {
|
||||
const result = resolveAddressIntent("кто у нас самый доходный клиент за все время");
|
||||
|
||||
expect(result.intent).toBe("customer_revenue_and_payments");
|
||||
});
|
||||
|
||||
it("detects top-year company revenue wording", () => {
|
||||
const result = resolveAddressIntent("какой у нас самый доходный год");
|
||||
|
||||
expect(result.intent).toBe("customer_revenue_and_payments");
|
||||
});
|
||||
|
||||
it("does not collapse very old stock request into generic inventory snapshot", () => {
|
||||
const result = resolveAddressIntent("Есть ли остатки товара, которые закупались очень давно");
|
||||
|
||||
expect(result.intent).toBe("inventory_aging_by_purchase_date");
|
||||
});
|
||||
it("detects bare historical inventory root with explicit month-year", () => {
|
||||
const result = resolveAddressIntent("остатки на март 2016");
|
||||
|
||||
expect(result.intent).toBe("inventory_on_hand_as_of_date");
|
||||
});
|
||||
});
|
||||
@@ -2747,14 +2747,15 @@ describe("address filter extraction for balance drilldown", () => {
|
||||
expect(extracted.warnings).toContain("period_to_derived_from_as_of_date_for_vat_forecast");
|
||||
});
|
||||
|
||||
it("derives full tax quarter window for confirmed VAT tax-period intent from month phrase", () => {
|
||||
it("keeps explicit month window for confirmed VAT tax-period intent from month phrase", () => {
|
||||
const extracted = extractAddressFilters(
|
||||
"сколько ндс надо заплатить в налоговую за декабрь 2019",
|
||||
"vat_liability_confirmed_for_tax_period"
|
||||
);
|
||||
expect(extracted.extracted_filters.period_from).toBe("2019-10-01");
|
||||
expect(extracted.extracted_filters.period_from).toBe("2019-12-01");
|
||||
expect(extracted.extracted_filters.period_to).toBe("2019-12-31");
|
||||
expect(extracted.warnings).toContain("period_derived_from_tax_quarter_for_confirmed_vat_liability");
|
||||
expect(extracted.warnings).toContain("period_derived_from_month_phrase");
|
||||
expect(extracted.warnings).not.toContain("period_derived_from_tax_quarter_for_confirmed_vat_liability");
|
||||
});
|
||||
|
||||
it("derives VAT forecast quarter-to-date window for explicit day+month+year phrase", () => {
|
||||
|
||||
@@ -0,0 +1,145 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
|
||||
import { composeCounterpartyAnalyticsReply } from "../src/services/address_runtime/counterpartyAnalyticsReplyBuilders";
|
||||
import { composeInventoryReply } from "../src/services/address_runtime/inventoryReplyBuilders";
|
||||
|
||||
describe("address reply builders regressions", () => {
|
||||
it("starts top customer aggregate reply with a direct business answer", () => {
|
||||
const result = composeCounterpartyAnalyticsReply(
|
||||
"customer_revenue_and_payments",
|
||||
[
|
||||
{
|
||||
amount: 250000,
|
||||
period: "2020-03-31",
|
||||
registrator: "Поступление 1"
|
||||
} 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: () => 5,
|
||||
detectValueRankingFocus: () => "top_by_total",
|
||||
detectContractValueFocus: () => "top_by_turnover",
|
||||
detectMinOpsForAvgCheck: () => 1,
|
||||
extractRequestedYearFromQuestion: () => null,
|
||||
extractCounterpartyName: () => "Чапурнов",
|
||||
extractContractName: () => null,
|
||||
counterpartyLookupMatches: () => false,
|
||||
toUtcDayTimestamp: () => null,
|
||||
formatAgeYearsMonthsDays: () => "0 дней",
|
||||
normalizeQuestionText: (value: string | null | undefined) => String(value ?? "")
|
||||
}
|
||||
);
|
||||
|
||||
expect(result?.text.split("\n")[0]).toContain("Самый доходный клиент");
|
||||
expect(result?.text.split("\n")[0]).toContain("Чапурнов");
|
||||
});
|
||||
|
||||
it("starts top year aggregate reply with a direct business answer", () => {
|
||||
const result = composeCounterpartyAnalyticsReply(
|
||||
"customer_revenue_and_payments",
|
||||
[
|
||||
{
|
||||
amount: 320000,
|
||||
period: "2021-05-12",
|
||||
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: () => 5,
|
||||
detectValueRankingFocus: () => "top_years_by_total",
|
||||
detectContractValueFocus: () => "top_by_turnover",
|
||||
detectMinOpsForAvgCheck: () => 1,
|
||||
extractRequestedYearFromQuestion: () => null,
|
||||
extractCounterpartyName: () => "Чапурнов",
|
||||
extractContractName: () => null,
|
||||
counterpartyLookupMatches: () => false,
|
||||
toUtcDayTimestamp: () => null,
|
||||
formatAgeYearsMonthsDays: () => "0 дней",
|
||||
normalizeQuestionText: (value: string | null | undefined) => String(value ?? "")
|
||||
}
|
||||
);
|
||||
|
||||
expect(result?.text.split("\n")[0]).toContain("Самый доходный год");
|
||||
expect(result?.text.split("\n")[0]).toContain("2021");
|
||||
});
|
||||
|
||||
it("keeps very old stock answer free of explicit as-of date in the first line", () => {
|
||||
const result = composeInventoryReply(
|
||||
"inventory_aging_by_purchase_date",
|
||||
[
|
||||
{
|
||||
amount: 1000,
|
||||
period: "2015-02-05",
|
||||
registrator: "Поступление 3"
|
||||
} as any
|
||||
],
|
||||
{
|
||||
userMessage: "Есть ли остатки товара, которые закупались очень давно",
|
||||
asOfDate: "2026-04-18"
|
||||
},
|
||||
{
|
||||
resolvePayablesAsOfDate: () => "2026-04-18",
|
||||
buildInventoryOnHandAggregate: () => [],
|
||||
uniqueStrings: (values: string[]) => values,
|
||||
formatDateRu: (value: string) => value,
|
||||
formatNumberWithDots: (value: number) => String(value),
|
||||
formatMoneyRub: (value: number) => `${value} ₽`,
|
||||
isInventoryPurchaseMovement: () => true,
|
||||
summarizeInventoryTraceRows: () => ({
|
||||
item: "Рабочая станция",
|
||||
warehouses: ["Основной склад"],
|
||||
organizations: ["ООО Альтернатива Плюс"],
|
||||
counterparties: ["Чапурнов"],
|
||||
documents: ["Поступление 3"],
|
||||
firstPeriod: "2015-02-05",
|
||||
lastPeriod: "2015-02-05",
|
||||
totalAmount: 1000
|
||||
}),
|
||||
formatInventoryTraceRows: () => [],
|
||||
hasInventoryPurchaseDateActionFocus: () => false,
|
||||
inventoryTraceDateLabel: (value: string | null) => value ?? "дата не указана",
|
||||
extractInventoryCounterpartyCandidates: () => ["Чапурнов"],
|
||||
buildInventoryAgingByItemAggregate: () => [
|
||||
{
|
||||
item: "Рабочая станция",
|
||||
warehouse: "Основной склад",
|
||||
organization: "ООО Альтернатива Плюс",
|
||||
firstPurchasePeriod: "2015-02-05",
|
||||
lastPurchasePeriod: "2015-02-05",
|
||||
operations: 1,
|
||||
documentCount: 1,
|
||||
counterparties: ["Чапурнов"],
|
||||
ageDays: 4089
|
||||
}
|
||||
],
|
||||
formatInventoryAgingRows: () => ["1. Рабочая станция | первая закупка: 2015-02-05"],
|
||||
isInventorySaleMovement: () => false
|
||||
}
|
||||
);
|
||||
|
||||
const firstLine = result?.text.split("\n")[0] ?? "";
|
||||
expect(firstLine).toContain("К самым старым закупкам");
|
||||
expect(firstLine).not.toContain("2026-04-18");
|
||||
});
|
||||
});
|
||||
@@ -157,6 +157,53 @@ describe("assistantRoutePolicy", () => {
|
||||
expect(decision.orchestrationContract?.semantic_route_arbitration?.supported_address_intent_detected).toBe(true);
|
||||
});
|
||||
|
||||
it("does not let deep session continuation override an exact VAT period route", () => {
|
||||
const policy = buildPolicy({
|
||||
detectAddressQuestionMode: () => ({ mode: "address_query", confidence: "high" }),
|
||||
resolveAddressIntent: () => ({ intent: "vat_liability_confirmed_for_tax_period", confidence: "high" }),
|
||||
resolveAddressToolGateDecision: () => ({
|
||||
runAddressLane: true,
|
||||
decision: "run_address_lane",
|
||||
reason: "address_mode_classifier_detected"
|
||||
}),
|
||||
hasDeepSessionContinuationSignal: () => true
|
||||
});
|
||||
|
||||
const decision = policy.resolveAssistantOrchestrationDecision({
|
||||
rawUserMessage: "а какой НДС мы должны сгрузить на март 2020?",
|
||||
effectiveAddressUserMessage: "а какой НДС мы должны сгрузить на март 2020?",
|
||||
followupContext: null,
|
||||
llmPreDecomposeMeta: {
|
||||
applied: true,
|
||||
llmCanonicalCandidateDetected: true,
|
||||
predecomposeContract: {
|
||||
mode: "address_query",
|
||||
mode_confidence: "high",
|
||||
intent: "vat_liability_confirmed_for_tax_period",
|
||||
intent_confidence: "high"
|
||||
},
|
||||
semanticExtractionContract: {
|
||||
valid: true,
|
||||
apply_canonical_recommended: true,
|
||||
extraction: {
|
||||
query_shape: "UNKNOWN",
|
||||
aggregation_profile: "unknown"
|
||||
},
|
||||
guard_hints: {
|
||||
deep_investigation_signal_detected: false
|
||||
}
|
||||
}
|
||||
} as any,
|
||||
useMock: false
|
||||
});
|
||||
|
||||
expect(decision.runAddressLane).toBe(true);
|
||||
expect(decision.toolGateDecision).toBe("run_address_lane");
|
||||
expect(decision.toolGateReason).toBe("address_mode_classifier_detected");
|
||||
expect(decision.livingMode).toBe("address_data");
|
||||
expect(decision.orchestrationContract?.deep_session_continuation_fallback_to_deep).toBe(false);
|
||||
});
|
||||
|
||||
it("routes memory recap follow-up over grounded answer to chat", () => {
|
||||
const policy = buildPolicy({
|
||||
resolveRouteMemorySignals: () => ({
|
||||
|
||||
@@ -254,6 +254,133 @@ describe("assistantTransitionPolicy", () => {
|
||||
expect(carryover?.followupContext?.root_context_only).toBeUndefined();
|
||||
});
|
||||
|
||||
it("bridges selected-item purchase provenance into a VAT period follow-up", () => {
|
||||
const item = "Рабочая станция универсального специалиста";
|
||||
const policy = buildPolicy({
|
||||
findLastAddressAssistantItem: () => ({
|
||||
text: [
|
||||
`По позиции ${item} однозначный поставщик не подтвержден.`,
|
||||
"Подтверждение:",
|
||||
"- Первая найденная дата закупки: 05.02.2015.",
|
||||
"- Последняя найденная дата закупки: 22.07.2015."
|
||||
].join("\n"),
|
||||
debug: {
|
||||
detected_intent: "inventory_purchase_provenance_for_item",
|
||||
extracted_filters: {
|
||||
item,
|
||||
organization: 'ООО "Альтернатива Плюс"',
|
||||
as_of_date: "2016-03-31"
|
||||
},
|
||||
anchor_type: "item",
|
||||
anchor_value_resolved: item
|
||||
}
|
||||
}),
|
||||
hasAddressFollowupContextSignal: () => false,
|
||||
findRecentInventoryRootFrame: () => null,
|
||||
resolveAddressIntent: () => ({ intent: "unknown" })
|
||||
});
|
||||
|
||||
const carryover = policy.resolveAddressFollowupCarryoverContext(
|
||||
"ндс можешь прикинуть на дату покупки рабочей станции?",
|
||||
[],
|
||||
null,
|
||||
null,
|
||||
{
|
||||
session_context: {
|
||||
active_focus_object: {
|
||||
object_type: "item",
|
||||
label: item,
|
||||
provenance_result_set_id: "rs-provenance"
|
||||
},
|
||||
active_result_set_id: "rs-provenance"
|
||||
},
|
||||
result_sets: [
|
||||
{
|
||||
result_set_id: "rs-provenance",
|
||||
intent: "inventory_purchase_provenance_for_item",
|
||||
entity_refs: [
|
||||
{
|
||||
index: 1,
|
||||
entity_type: "item",
|
||||
value: "Поступление товаров и услуг 00000000023 от 05.02.2015 0:00:00"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
);
|
||||
|
||||
expect(carryover?.followupContext?.previous_intent).toBe("inventory_purchase_provenance_for_item");
|
||||
expect(carryover?.followupContext?.target_intent).toBe("vat_liability_confirmed_for_tax_period");
|
||||
expect(carryover?.followupContext?.previous_filters).toMatchObject({
|
||||
item,
|
||||
organization: 'ООО "Альтернатива Плюс"',
|
||||
period_from: "2015-02-01",
|
||||
period_to: "2015-02-28"
|
||||
});
|
||||
});
|
||||
|
||||
it("keeps selected-object continuity for purchase-date VAT bridge even when an inventory root frame exists", () => {
|
||||
const item = "Рабочая станция универсального специалиста";
|
||||
const policy = buildPolicy({
|
||||
findLastAddressAssistantItem: () => ({
|
||||
text: [
|
||||
`По позиции ${item} однозначный поставщик не подтвержден.`,
|
||||
"Подтверждение:",
|
||||
"- Первая найденная дата закупки: 05.02.2015."
|
||||
].join("\n"),
|
||||
debug: {
|
||||
detected_intent: "inventory_purchase_provenance_for_item",
|
||||
extracted_filters: {
|
||||
item,
|
||||
organization: 'ООО "Альтернатива Плюс"',
|
||||
as_of_date: "2016-03-31"
|
||||
},
|
||||
anchor_type: "item",
|
||||
anchor_value_resolved: item
|
||||
}
|
||||
}),
|
||||
hasAddressFollowupContextSignal: () => false,
|
||||
hasForeignAccountingPivotOverInventoryMessage: () => true,
|
||||
resolveAddressIntent: () => ({ intent: "unknown" })
|
||||
});
|
||||
|
||||
const carryover = policy.resolveAddressFollowupCarryoverContext(
|
||||
"ндс можешь прикинуть на дату покупки рабочей станции?",
|
||||
[],
|
||||
null,
|
||||
null,
|
||||
{
|
||||
session_context: {
|
||||
active_focus_object: {
|
||||
object_type: "item",
|
||||
label: item,
|
||||
provenance_result_set_id: "rs-provenance"
|
||||
},
|
||||
active_result_set_id: "rs-provenance"
|
||||
},
|
||||
result_sets: [
|
||||
{
|
||||
result_set_id: "rs-provenance",
|
||||
intent: "inventory_purchase_provenance_for_item",
|
||||
entity_refs: [
|
||||
{
|
||||
index: 1,
|
||||
entity_type: "item",
|
||||
value: "Поступление товаров и услуг 00000000023 от 05.02.2015 0:00:00"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
);
|
||||
|
||||
expect(carryover?.followupSelectionMode).toBe("carry_previous_intent");
|
||||
expect(carryover?.followupContext?.root_context_only).toBeUndefined();
|
||||
expect(carryover?.followupContext?.previous_intent).toBe("inventory_purchase_provenance_for_item");
|
||||
expect(carryover?.followupContext?.target_intent).toBe("vat_liability_confirmed_for_tax_period");
|
||||
});
|
||||
|
||||
it("drops stale carryover for a fresh standalone topic from another intent family", () => {
|
||||
const policy = buildPolicy({
|
||||
findLastAddressAssistantItem: () => ({
|
||||
|
||||
Reference in New Issue
Block a user