From 264cbcee12a68a782efece8c754d841c29072b45 Mon Sep 17 00:00:00 2001 From: dctouch Date: Tue, 5 May 2026 12:05:11 +0300 Subject: [PATCH] =?UTF-8?q?Open-World:=20=D0=B7=D0=B0=D0=BA=D1=80=D0=B5?= =?UTF-8?q?=D0=BF=D0=B8=D1=82=D1=8C=20=D0=B3=D1=80=D0=B0=D0=BD=D0=B8=D1=86?= =?UTF-8?q?=D1=83=20=D0=BF=D1=80=D0=BE=D1=81=D1=80=D0=BE=D1=87=D0=BA=D0=B8?= =?UTF-8?q?=20=D0=B4=D0=BE=D0=BB=D0=B3=D0=BE=D0=B2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../21 - current_status_canon_2026-05-01.md | 5 +-- ...rld_bounded_autonomy_breadth_2026-05-01.md | 23 ++++++++++++++ .../11 - architecture_turnaround/README.md | 3 +- .../dist/services/addressIntentResolver.js | 15 +++++++++ .../services/address_runtime/composeStage.js | 9 ++++-- .../assistantMcpDiscoveryTurnInputAdapter.js | 13 +++++++- .../services/assistantTurnMeaningPolicy.js | 17 ++++++++++ .../src/services/addressIntentResolver.ts | 27 ++++++++++++++++ .../services/address_runtime/composeStage.ts | 10 ++++-- .../assistantMcpDiscoveryTurnInputAdapter.ts | 24 +++++++++++++- .../services/assistantTurnMeaningPolicy.ts | 28 +++++++++++++++++ .../tests/addressQueryRuntimeM23.test.ts | 9 ++++++ ...istantMcpDiscoveryTurnInputAdapter.test.ts | 31 +++++++++++++++++++ .../tests/assistantTurnMeaningPolicy.test.ts | 10 ++++++ 14 files changed, 215 insertions(+), 9 deletions(-) diff --git a/docs/ARCH/11 - architecture_turnaround/21 - current_status_canon_2026-05-01.md b/docs/ARCH/11 - architecture_turnaround/21 - current_status_canon_2026-05-01.md index 5e6ccf7..4d8924b 100644 --- a/docs/ARCH/11 - architecture_turnaround/21 - current_status_canon_2026-05-01.md +++ b/docs/ARCH/11 - architecture_turnaround/21 - current_status_canon_2026-05-01.md @@ -33,8 +33,9 @@ If another document says `78%`, `87%`, `92%`, or `85%` for a module that is now - Completed active slice: `Business Overview Yearly Operating-Flow Proxy Bridge`: business overview now derives annual incoming/outgoing/net buckets from confirmed money-flow rows and can name the strongest incoming year and best operating-net year without claiming profit or P&L. - Completed active slice: `Business Overview Earnings Wording Arbitration Bridge`: organization-level earnings/best-year/overall-turnover wording now routes to `business_overview` instead of the exact customer-value lane, while explicit customer/counterparty wording remains in `customer_revenue_and_payments`. - Completed active slice: `Business Overview Profit/Margin Wording Boundary Bridge`: organization-level profit, margin, financial-result, and P&L wording now routes to `business_overview` with clean-profit boundary wording, while explicit customer/item/contract routes still use exact recipes. +- Completed active slice: `Business Overview Debt Due-Date Boundary Bridge`: organization-level overdue debt, debt quality, debt aging, due-date, and credit-risk wording now routes to `business_overview`, while explicit buyer/debtor lists stay in exact receivables routes with a due-date proof boundary. - Next active slice: continue breadth into exact company-wide accounting profit/margin, real due-date debt aging, confirmed inventory reserve/write-off/liquidation evidence, and broader unfamiliar 1C route families only where reviewed evidence routes exist. -- Active module progress: `~93% (Open-World Bounded Autonomy Breadth)`. +- Active module progress: `~94% (Open-World Bounded Autonomy Breadth)`. ## Reporting Rule @@ -71,7 +72,7 @@ The project is not yet a universal arbitrary-1C agent. Remaining work belongs to the next breadth module: -- extend `business_overview` beyond money-flow/activity, customer and supplier concentration, yearly operating-flow dynamics, explicit profit/margin wording boundaries, explicit-period VAT/tax, as-of-date debt position, open-settlement concentration, contract-date debt age, debt staleness-risk proxy, as-of-date inventory position, trading-margin proxy, sales-to-stock inventory proxy, and warehouse staleness-risk proxy into separately proven exact accounting profit/margin, due-date debt aging/overdue, and confirmed reserve/write-off/liquidation inventory evidence families; +- extend `business_overview` beyond money-flow/activity, customer and supplier concentration, yearly operating-flow dynamics, explicit profit/margin wording boundaries, explicit debt due-date wording boundaries, explicit-period VAT/tax, as-of-date debt position, open-settlement concentration, contract-date debt age, debt staleness-risk proxy, as-of-date inventory position, trading-margin proxy, sales-to-stock inventory proxy, and warehouse staleness-risk proxy into separately proven exact accounting profit/margin, due-date debt aging/overdue, and confirmed reserve/write-off/liquidation inventory evidence families; - broader dynamic schema traversal for unfamiliar 1C asks; - more primitive descriptors where live evidence proves a real gap; - more replay-backed domain packs that start from user business meaning, not from route convenience; diff --git a/docs/ARCH/11 - architecture_turnaround/22 - open_world_bounded_autonomy_breadth_2026-05-01.md b/docs/ARCH/11 - architecture_turnaround/22 - open_world_bounded_autonomy_breadth_2026-05-01.md index 6a660ec..1ed6a4c 100644 --- a/docs/ARCH/11 - architecture_turnaround/22 - open_world_bounded_autonomy_breadth_2026-05-01.md +++ b/docs/ARCH/11 - architecture_turnaround/22 - open_world_bounded_autonomy_breadth_2026-05-01.md @@ -577,3 +577,26 @@ Local validation is accepted for this slice: - `npm.cmd run build`: passed. Graphify rebuild after Slice 19 code/doc sync: `6052 nodes`, `13187 edges`, `138 communities`. + +## Slice 20 - Business Overview Debt Due-Date Boundary Bridge + +This slice tightens the debt-side boundary for overdue/due-date wording. + +The runtime still does not have a reviewed payment-term/due-date aging route. It has confirmed debt position, open-settlement concentration, contract-date age signal, and debt staleness-risk proxy. Therefore the correct behavior is to route organization-level overdue/due-date questions to `business_overview` and keep due-date aging as an explicit unproved family, while exact buyer/debtor lists remain available as bounded 62/76 shortlists. + +Implemented now: + +- turn meaning policy treats organization-level overdue debt, debt quality, debt aging, due-date, and credit-risk wording as `broad_business_evaluation` when the user is asking about the company as a whole; +- the discovery turn-input adapter promotes the same company-level wording into `business overview evidence with bounded analyst interpretation`; +- the address intent resolver defers organization-level due-date/overdue wording with `unicode_business_overview_debt_due_date_deferred_to_discovery`; +- explicit party/list wording such as "какие покупатели пока не оплатили" stays in the existing receivables exact lane; +- exact receivables debt-aging/list answers now include a user-facing proof boundary: 62/76 age/tail evidence is not confirmed contractual overdue or due-date aging without payment terms from contracts. + +This is a boundary and arbitration slice, not a true overdue engine. It prevents old exact debt routes from overclaiming while preserving the useful receivables shortlist and the richer business-overview staleness proxy. + +Local validation is accepted for this slice: + +- `npm.cmd test -- assistantTurnMeaningPolicy.test.ts assistantMcpDiscoveryTurnInputAdapter.test.ts addressQueryRuntimeM23.test.ts`: passed `501` with `6` skipped. +- `npm.cmd run build`: passed. + +Graphify rebuild after Slice 20 code/doc sync: `6056 nodes`, `13195 edges`, `139 communities`. diff --git a/docs/ARCH/11 - architecture_turnaround/README.md b/docs/ARCH/11 - architecture_turnaround/README.md index a8fbaac..cb1d5c8 100644 --- a/docs/ARCH/11 - architecture_turnaround/README.md +++ b/docs/ARCH/11 - architecture_turnaround/README.md @@ -68,6 +68,7 @@ Status canon for planning: - The current completed breadth slice is `Business Overview Yearly Operating-Flow Proxy Bridge`: company analysis now builds annual incoming/outgoing/net buckets from confirmed money-flow rows and names strongest years as operating-flow proxy, not profit or full P&L. - The current completed breadth slice is `Business Overview Earnings Wording Arbitration Bridge`: organization-level earnings, best-year, and overall-turnover wording now reaches `business_overview` instead of the exact customer-value route, while explicit customer/counterparty ranking remains unchanged. - The current completed breadth slice is `Business Overview Profit/Margin Wording Boundary Bridge`: organization-level profit, margin, financial-result, and P&L wording now reaches `business_overview` with explicit clean-profit boundaries, while explicit customer/item/contract routes remain unchanged. +- The current completed breadth slice is `Business Overview Debt Due-Date Boundary Bridge`: organization-level overdue debt, debt quality, debt aging, due-date, and credit-risk wording now reaches `business_overview`, while explicit buyer/debtor lists stay in exact receivables routes with a due-date proof boundary. - The next active breadth slice continues breadth into exact company-wide accounting profit/margin, real due-date debt aging, confirmed reserve/write-off/liquidation inventory evidence, and broader unfamiliar 1C route families without relaxing truth boundaries. - The short source of truth for status wording is [21 - current_status_canon_2026-05-01.md](./21%20-%20current_status_canon_2026-05-01.md). @@ -133,7 +134,7 @@ Current honest status: - pre-multidomain readiness: `~90%` - bounded-autonomy foundation readiness: `~89%` - open-world bounded-autonomy readiness: `~87%` -- active Open-World Bounded Autonomy Breadth progress: `~93%`, with business-overview evidence fusion, the reviewed `business_overview` catalog/data-need/planner route-fabric slice, the fresh multi-probe runtime bridge, the explicit-period VAT/tax fact-family bridge, the explicit-period debt-position bridge, the explicit-date inventory-position bridge, the open-settlement quality bridge accepted by live semantic replay, selected-item profitability bridged by local semantic/runtime regression tests, contract-date debt age bridged locally, debt staleness-risk proxy bridged locally, supplier concentration proxy bridged locally, yearly operating-flow proxy bridged locally, earnings/best-year wording arbitration bridged locally, profit/margin wording boundary arbitration bridged locally, analyst synthesis added to business-overview answer drafting, company-period trading margin proxy bridged locally, inventory sales-to-stock proxy bridged locally, inventory staleness-risk proxy bridged locally, and gap-specific answer shaping bridged locally; exact accounting profit/margin, true due-date debt aging/overdue, confirmed vendor-risk/procurement-quality analysis, and confirmed reserve/write-off/liquidation inventory evidence are still pending +- active Open-World Bounded Autonomy Breadth progress: `~94%`, with business-overview evidence fusion, the reviewed `business_overview` catalog/data-need/planner route-fabric slice, the fresh multi-probe runtime bridge, the explicit-period VAT/tax fact-family bridge, the explicit-period debt-position bridge, the explicit-date inventory-position bridge, the open-settlement quality bridge accepted by live semantic replay, selected-item profitability bridged by local semantic/runtime regression tests, contract-date debt age bridged locally, debt staleness-risk proxy bridged locally, debt due-date boundary arbitration bridged locally, supplier concentration proxy bridged locally, yearly operating-flow proxy bridged locally, earnings/best-year wording arbitration bridged locally, profit/margin wording boundary arbitration bridged locally, analyst synthesis added to business-overview answer drafting, company-period trading margin proxy bridged locally, inventory sales-to-stock proxy bridged locally, inventory staleness-risk proxy bridged locally, and gap-specific answer shaping bridged locally; exact accounting profit/margin, true due-date debt aging/overdue, confirmed vendor-risk/procurement-quality analysis, and confirmed reserve/write-off/liquidation inventory evidence are still pending - Post-F semantic integrity module progress: `~99%` operationally closed, with remaining risk now treated as next-slice discovery rather than an open blocker inside the closed slice - active inventory-stock breadth slice progress: `100%` for the declared scenario pack, not for arbitrary inventory questions - Planner Autonomy Consolidation progress: `100%` for the declared module, with catalog-fabric, value-flow arbitration, lifecycle bounded inference, broad-evaluation bridge, inventory catalog templates, inventory runtime-boundary honesty, exact inventory recipe bridging, unambiguous metadata-surface lane inference, catalog chain-template scoring, structured chain-match contract exposure, runtime/debug propagation, subject-aware bidirectional comparison arbitration, structured catalog-alignment verdicts, representative alignment regression guard, catalog-alignment reason-code telemetry, explicit `alignment_status` propagation, truth-harness/acceptance-matrix surfacing, soft divergence warning, `catalog_alignment_ok` acceptance invariant, step-level expected catalog-alignment assertions, phase66 and phase32 spec alignment expectations, AGENT source-catalog surfacing, generated phase83 mixed planner-brain replay spec, checked-source user-facing error sanitation, surface-grounded catalog promotion, and guarded live phase83 acceptance validated. Broader unfamiliar 1C asks are now next-module breadth work rather than an open blocker inside this declared slice diff --git a/llm_normalizer/backend/dist/services/addressIntentResolver.js b/llm_normalizer/backend/dist/services/addressIntentResolver.js index 86228ff..d5759f3 100644 --- a/llm_normalizer/backend/dist/services/addressIntentResolver.js +++ b/llm_normalizer/backend/dist/services/addressIntentResolver.js @@ -1493,6 +1493,18 @@ function hasOrganizationLevelEarningsOverviewBridgeSignal(text) { /(?:\u043a\u0430\u043a\w*|\u0441\u043a\u043e\u043b\u044c\u043a\u043e|\u0441\u043a\u043e\u043a\w*|\u043f\u043e\u043a\u0430\u0436|\u0434\u0430\u0439|\u0443\s+\u043d\u0430\u0441|\u043d\u0430\u0448\w*|\u043c\u044b\b|\u043a\u043e\u043c\u043f\u0430\u043d|\u0431\u0438\u0437\u043d\u0435\u0441|\u043e\u0440\u0433\u0430\u043d\u0438\u0437\u0430\u0446|\u0432\s+\u0446\u0435\u043b\u043e\u043c|\u0432\u043e\u043e\u0431\u0449\u0435|(?:19|20)\d{2}|all\s+time|what|which|how\s+much|show|give|company|business|organization|our|we|us)/iu.test(normalized); return hasYearRankingCue || hasCompanyEarningsCue || hasCompanyProfitMarginCue; } +function hasOrganizationLevelDebtDueDateOverviewBridgeSignal(text) { + const normalized = String(text ?? "").trim().toLowerCase(); + if (!normalized) { + return false; + } + if (/(?:\u043a\u0442\u043e|\u043a\u043e\u043c\u0443|\u043a\u0430\u043a\u0438\u0435\s+(?:\u043f\u043e\u043a\u0443\u043f\u0430\u0442\u0435\u043b|\u043a\u043b\u0438\u0435\u043d\u0442|\u043a\u043e\u043d\u0442\u0440\u0430\u0433\u0435\u043d\u0442|\u0437\u0430\u043a\u0430\u0437\u0447\u0438\u043a|\u0434\u043e\u043b\u0436\u043d\u0438\u043a)|who\b|which\s+(?:customers|clients|counterparties|debtors))/iu.test(normalized)) { + return false; + } + const hasDueDateDebtCue = /(?:\u043f\u0440\u043e\u0441\u0440\u043e\u0447\w*|\u0441\u0440\u043e\u043a\w*\s+\u043e\u043f\u043b\u0430\u0442|\u0441\u0440\u043e\u043a\u0438\s+\u0434\u0430\u0432\u043d\u043e\s+\u043f\u0440\u043e\u0448|\u0434\u043e\u043b\u0433\w*\s+aging|\u043a\u0430\u0447\u0435\u0441\u0442\u0432\w*\s+\u0434\u043e\u043b\u0433|\u0434\u043e\u043b\u0433\w*\s+\u043a\u0430\u0447\u0435\u0441\u0442\u0432|due[-\s]?date|overdue|debt\s+aging|debt\s+quality|credit\s+risk)/iu.test(normalized); + const hasCompanyScopeCue = /(?:\u0443\s+\u043d\u0430\u0441|\u043d\u0430\u0448\w*|\u043f\u043e\s+\u043a\u043e\u043c\u043f\u0430\u043d|\u043a\u043e\u043c\u043f\u0430\u043d|\u043e\u0440\u0433\u0430\u043d\u0438\u0437\u0430\u0446|\u0431\u0438\u0437\u043d\u0435\u0441|\u0432\s+\u0446\u0435\u043b\u043e\u043c|\u043e\u0431\u0449\w*|\u043a\u0430\u043a\w*|\u043f\u043e\u043a\u0430\u0436|\u0434\u0430\u0439|\u0441\u0440\u0435\u0437|\u0430\u043d\u0430\u043b\u0438\u0437|(?:19|20)\d{2}|company|business|organization|overall|our|we|us|show|give|analysis)/iu.test(normalized); + return hasDueDateDebtCue && hasCompanyScopeCue; +} function hasSpecificCounterpartyRevenueBridgeSignal(text) { const normalized = String(text ?? "").trim().toLowerCase(); if (!normalized) { @@ -1653,6 +1665,9 @@ function resolveUnicodeAddressIntentBridge(text) { if (hasOrganizationLevelEarningsOverviewBridgeSignal(normalized)) { return unicodeBridgeResolution("unknown", "high", "unicode_business_overview_earnings_deferred_to_discovery"); } + if (hasOrganizationLevelDebtDueDateOverviewBridgeSignal(normalized)) { + return unicodeBridgeResolution("unknown", "high", "unicode_business_overview_debt_due_date_deferred_to_discovery"); + } if (hasBidirectionalValueFlowComparisonSignal(normalized)) { return unicodeBridgeResolution("unknown", "high", "unicode_bidirectional_value_flow_deferred_to_discovery"); } diff --git a/llm_normalizer/backend/dist/services/address_runtime/composeStage.js b/llm_normalizer/backend/dist/services/address_runtime/composeStage.js index 325bc17..cc048c4 100644 --- a/llm_normalizer/backend/dist/services/address_runtime/composeStage.js +++ b/llm_normalizer/backend/dist/services/address_runtime/composeStage.js @@ -311,6 +311,9 @@ function hasReceivablesDebtAgingFocus(userMessage) { const hasCounterpartySignal = /(?:заказчик|клиент|покупател|контрагент|должник|counterpart|customer|client|buyer)/iu.test(text); return hasDebtSignal && hasLongevitySignal && hasCounterpartySignal; } +function receivablesDueDateBoundaryLine() { + return "Граница доказанности: это возрастной сигнал/хвост дебиторки по 62/76, а не подтвержденная договорная просрочка или due-date aging; для due-date нужны сроки оплаты из договора или условий оплаты."; +} function formatAgeYearsMonthsDays(daysRaw) { const safeDays = Math.max(0, Math.floor(daysRaw)); const years = Math.floor(safeDays / 365); @@ -3229,7 +3232,8 @@ function composeFactualReplyBody(intent, rows, options = {}) { "Проверил должников по сроку жизни задолженности (контур 62/76).", `Дата среза: ${formatDateRu(asOfDate)}.`, `Строк в выборке: ${rows.length}.`, - `Контрагентов с сигналом: ${aging.length}.` + `Контрагентов с сигналом: ${aging.length}.`, + receivablesDueDateBoundaryLine() ]; if (aging.length > 0) { lines.push("Приоритет ручной проверки (по возрасту долга, по убыванию):"); @@ -3255,7 +3259,8 @@ function composeFactualReplyBody(intent, rows, options = {}) { const lines = [ "Проверил покупателей с признаками затянутой оплаты (контур 62/76).", `Строк в выборке: ${rows.length}.`, - `Контрагентов с сигналом: ${counterparties.length}.` + `Контрагентов с сигналом: ${counterparties.length}.`, + receivablesDueDateBoundaryLine() ]; if (counterparties.length > 0) { lines.push("Приоритет ручной проверки (по сумме/частоте хвостов):"); diff --git a/llm_normalizer/backend/dist/services/assistantMcpDiscoveryTurnInputAdapter.js b/llm_normalizer/backend/dist/services/assistantMcpDiscoveryTurnInputAdapter.js index 3ae38ce..0092a00 100644 --- a/llm_normalizer/backend/dist/services/assistantMcpDiscoveryTurnInputAdapter.js +++ b/llm_normalizer/backend/dist/services/assistantMcpDiscoveryTurnInputAdapter.js @@ -524,8 +524,19 @@ function hasOrganizationLevelEarningsOverviewSignal(text) { /(?:\u043a\u0430\u043a\w*|\u0441\u043a\u043e\u043b\u044c\u043a\u043e|\u0441\u043a\u043e\u043a\w*|\u043f\u043e\u043a\u0430\u0436|\u0434\u0430\u0439|\u0443\s+\u043d\u0430\u0441|\u043d\u0430\u0448\w*|\u043c\u044b\b|\u043a\u043e\u043c\u043f\u0430\u043d|\u0431\u0438\u0437\u043d\u0435\u0441|\u043e\u0440\u0433\u0430\u043d\u0438\u0437\u0430\u0446|\u0432\s+\u0446\u0435\u043b\u043e\u043c|\u0432\u043e\u043e\u0431\u0449\u0435|(?:19|20)\d{2}|all\s+time|what|which|how\s+much|show|give|company|business|organization|our|we|us)/iu.test(text); return hasYearRankingCue || hasCompanyEarningsCue || hasCompanyProfitMarginCue; } +function hasOrganizationLevelDebtDueDateOverviewSignal(text) { + if (!text) { + return false; + } + if (/(?:\u043a\u0442\u043e|\u043a\u043e\u043c\u0443|\u043a\u0430\u043a\u0438\u0435\s+(?:\u043f\u043e\u043a\u0443\u043f\u0430\u0442\u0435\u043b|\u043a\u043b\u0438\u0435\u043d\u0442|\u043a\u043e\u043d\u0442\u0440\u0430\u0433\u0435\u043d\u0442|\u0437\u0430\u043a\u0430\u0437\u0447\u0438\u043a|\u0434\u043e\u043b\u0436\u043d\u0438\u043a)|who\b|which\s+(?:customers|clients|counterparties|debtors))/iu.test(text)) { + return false; + } + const hasDueDateDebtCue = /(?:\u043f\u0440\u043e\u0441\u0440\u043e\u0447\w*|\u0441\u0440\u043e\u043a\w*\s+\u043e\u043f\u043b\u0430\u0442|\u0441\u0440\u043e\u043a\u0438\s+\u0434\u0430\u0432\u043d\u043e\s+\u043f\u0440\u043e\u0448|\u0434\u043e\u043b\u0433\w*\s+aging|\u043a\u0430\u0447\u0435\u0441\u0442\u0432\w*\s+\u0434\u043e\u043b\u0433|\u0434\u043e\u043b\u0433\w*\s+\u043a\u0430\u0447\u0435\u0441\u0442\u0432|due[-\s]?date|overdue|debt\s+aging|debt\s+quality|credit\s+risk)/iu.test(text); + const hasCompanyScopeCue = /(?:\u0443\s+\u043d\u0430\u0441|\u043d\u0430\u0448\w*|\u043f\u043e\s+\u043a\u043e\u043c\u043f\u0430\u043d|\u043a\u043e\u043c\u043f\u0430\u043d|\u043e\u0440\u0433\u0430\u043d\u0438\u0437\u0430\u0446|\u0431\u0438\u0437\u043d\u0435\u0441|\u0432\s+\u0446\u0435\u043b\u043e\u043c|\u043e\u0431\u0449\w*|\u043a\u0430\u043a\w*|\u043f\u043e\u043a\u0430\u0436|\u0434\u0430\u0439|\u0441\u0440\u0435\u0437|\u0430\u043d\u0430\u043b\u0438\u0437|(?:19|20)\d{2}|company|business|organization|overall|our|we|us|show|give|analysis)/iu.test(text); + return hasDueDateDebtCue && hasCompanyScopeCue; +} function hasBusinessOverviewSignal(text) { - if (hasOrganizationLevelEarningsOverviewSignal(text)) { + if (hasOrganizationLevelEarningsOverviewSignal(text) || hasOrganizationLevelDebtDueDateOverviewSignal(text)) { return true; } return /(?:\u0431\u0438\u0437\u043d\u0435\u0441[-\s]?\u043e\u0431\u0437\u043e\u0440|\u0431\u0438\u0437\u043d\u0435\u0441[-\s]?\u0430\u0443\u0434\u0438\u0442|\u043f\u043e\u043b\u043d\w*\s+\u0430\u043d\u0430\u043b\u0438\u0437\s+(?:\u043a\u043e\u043c\u043f\u0430\u043d|\u0431\u0438\u0437\u043d\u0435\u0441|\u0434\u0435\u044f\u0442\u0435\u043b)|\u0441\u0432\u043e\u0434\u043d\w*\s+\u0430\u043d\u0430\u043b\u0438\u0437\s+(?:\u043a\u043e\u043c\u043f\u0430\u043d|\u0431\u0438\u0437\u043d\u0435\u0441|\u0434\u0435\u044f\u0442\u0435\u043b)|\u043a\u0430\u043a\s+\u0442\u044b\s+\u043e\u0446\u0435\u043d(?:\u0438\u0448\u044c|\u0438)\s+\u0434\u0435\u044f\u0442\u0435\u043b\u044c\u043d\u043e\u0441\u0442|\u043a\u043e\u043c\u043f\u0430\u043d(?:\u0438\u0438|\u0438\u044e|\u0438\u044f)\s+\u0432\s+\u0446\u0435\u043b\u043e\u043c|company\s+(?:analysis|overview)|business\s+(?:overview|audit)|llm[-\s]?audit|бизнес[-\s]?РѕР±Р·РѕСЂ|бизнес[-\s]?аудит)/iu.test(text); diff --git a/llm_normalizer/backend/dist/services/assistantTurnMeaningPolicy.js b/llm_normalizer/backend/dist/services/assistantTurnMeaningPolicy.js index 75584c0..663cfaa 100644 --- a/llm_normalizer/backend/dist/services/assistantTurnMeaningPolicy.js +++ b/llm_normalizer/backend/dist/services/assistantTurnMeaningPolicy.js @@ -126,6 +126,18 @@ function hasOrganizationLevelEarningsOverviewSignal(text) { /(?:\u043a\u0430\u043a\w*|\u0441\u043a\u043e\u043b\u044c\u043a\u043e|\u0441\u043a\u043e\u043a\w*|\u043f\u043e\u043a\u0430\u0436|\u0434\u0430\u0439|\u0443\s+\u043d\u0430\u0441|\u043d\u0430\u0448\w*|\u043c\u044b\b|\u043a\u043e\u043c\u043f\u0430\u043d|\u0431\u0438\u0437\u043d\u0435\u0441|\u043e\u0440\u0433\u0430\u043d\u0438\u0437\u0430\u0446|\u0432\s+\u0446\u0435\u043b\u043e\u043c|\u0432\u043e\u043e\u0431\u0449\u0435|(?:19|20)\d{2}|all\s+time|what|which|how\s+much|show|give|company|business|organization|our|we|us)/iu.test(normalized); return hasYearRankingCue || hasCompanyEarningsCue || hasCompanyProfitMarginCue; } +function hasOrganizationLevelDebtDueDateOverviewSignal(text) { + const normalized = String(text ?? ""); + if (!normalized) { + return false; + } + if (/(?:\u043a\u0442\u043e|\u043a\u043e\u043c\u0443|\u043a\u0430\u043a\u0438\u0435\s+(?:\u043f\u043e\u043a\u0443\u043f\u0430\u0442\u0435\u043b|\u043a\u043b\u0438\u0435\u043d\u0442|\u043a\u043e\u043d\u0442\u0440\u0430\u0433\u0435\u043d\u0442|\u0437\u0430\u043a\u0430\u0437\u0447\u0438\u043a|\u0434\u043e\u043b\u0436\u043d\u0438\u043a)|who\b|which\s+(?:customers|clients|counterparties|debtors))/iu.test(normalized)) { + return false; + } + const hasDueDateDebtCue = /(?:\u043f\u0440\u043e\u0441\u0440\u043e\u0447\w*|\u0441\u0440\u043e\u043a\w*\s+\u043e\u043f\u043b\u0430\u0442|\u0441\u0440\u043e\u043a\u0438\s+\u0434\u0430\u0432\u043d\u043e\s+\u043f\u0440\u043e\u0448|\u0434\u043e\u043b\u0433\w*\s+aging|\u043a\u0430\u0447\u0435\u0441\u0442\u0432\w*\s+\u0434\u043e\u043b\u0433|\u0434\u043e\u043b\u0433\w*\s+\u043a\u0430\u0447\u0435\u0441\u0442\u0432|due[-\s]?date|overdue|debt\s+aging|debt\s+quality|credit\s+risk)/iu.test(normalized); + const hasCompanyScopeCue = /(?:\u0443\s+\u043d\u0430\u0441|\u043d\u0430\u0448\w*|\u043f\u043e\s+\u043a\u043e\u043c\u043f\u0430\u043d|\u043a\u043e\u043c\u043f\u0430\u043d|\u043e\u0440\u0433\u0430\u043d\u0438\u0437\u0430\u0446|\u0431\u0438\u0437\u043d\u0435\u0441|\u0432\s+\u0446\u0435\u043b\u043e\u043c|\u043e\u0431\u0449\w*|\u043a\u0430\u043a\w*|\u043f\u043e\u043a\u0430\u0436|\u0434\u0430\u0439|\u0441\u0440\u0435\u0437|\u0430\u043d\u0430\u043b\u0438\u0437|(?:19|20)\d{2}|company|business|organization|overall|our|we|us|show|give|analysis)/iu.test(normalized); + return hasDueDateDebtCue && hasCompanyScopeCue; +} function detectBroadBusinessEvaluation(text) { const normalized = String(text ?? ""); if (!normalized) { @@ -146,6 +158,11 @@ function detectBroadBusinessEvaluation(text) { family: "broad_business_evaluation" }; } + if (hasOrganizationLevelDebtDueDateOverviewSignal(normalized)) { + return { + family: "broad_business_evaluation" + }; + } return null; } function buildEntityCandidates(counterpartyTurnover) { diff --git a/llm_normalizer/backend/src/services/addressIntentResolver.ts b/llm_normalizer/backend/src/services/addressIntentResolver.ts index 0599316..8f56bed 100644 --- a/llm_normalizer/backend/src/services/addressIntentResolver.ts +++ b/llm_normalizer/backend/src/services/addressIntentResolver.ts @@ -1873,6 +1873,29 @@ function hasOrganizationLevelEarningsOverviewBridgeSignal(text: string): boolean return hasYearRankingCue || hasCompanyEarningsCue || hasCompanyProfitMarginCue; } +function hasOrganizationLevelDebtDueDateOverviewBridgeSignal(text: string): boolean { + const normalized = String(text ?? "").trim().toLowerCase(); + if (!normalized) { + return false; + } + if ( + /(?:\u043a\u0442\u043e|\u043a\u043e\u043c\u0443|\u043a\u0430\u043a\u0438\u0435\s+(?:\u043f\u043e\u043a\u0443\u043f\u0430\u0442\u0435\u043b|\u043a\u043b\u0438\u0435\u043d\u0442|\u043a\u043e\u043d\u0442\u0440\u0430\u0433\u0435\u043d\u0442|\u0437\u0430\u043a\u0430\u0437\u0447\u0438\u043a|\u0434\u043e\u043b\u0436\u043d\u0438\u043a)|who\b|which\s+(?:customers|clients|counterparties|debtors))/iu.test( + normalized + ) + ) { + return false; + } + const hasDueDateDebtCue = + /(?:\u043f\u0440\u043e\u0441\u0440\u043e\u0447\w*|\u0441\u0440\u043e\u043a\w*\s+\u043e\u043f\u043b\u0430\u0442|\u0441\u0440\u043e\u043a\u0438\s+\u0434\u0430\u0432\u043d\u043e\s+\u043f\u0440\u043e\u0448|\u0434\u043e\u043b\u0433\w*\s+aging|\u043a\u0430\u0447\u0435\u0441\u0442\u0432\w*\s+\u0434\u043e\u043b\u0433|\u0434\u043e\u043b\u0433\w*\s+\u043a\u0430\u0447\u0435\u0441\u0442\u0432|due[-\s]?date|overdue|debt\s+aging|debt\s+quality|credit\s+risk)/iu.test( + normalized + ); + const hasCompanyScopeCue = + /(?:\u0443\s+\u043d\u0430\u0441|\u043d\u0430\u0448\w*|\u043f\u043e\s+\u043a\u043e\u043c\u043f\u0430\u043d|\u043a\u043e\u043c\u043f\u0430\u043d|\u043e\u0440\u0433\u0430\u043d\u0438\u0437\u0430\u0446|\u0431\u0438\u0437\u043d\u0435\u0441|\u0432\s+\u0446\u0435\u043b\u043e\u043c|\u043e\u0431\u0449\w*|\u043a\u0430\u043a\w*|\u043f\u043e\u043a\u0430\u0436|\u0434\u0430\u0439|\u0441\u0440\u0435\u0437|\u0430\u043d\u0430\u043b\u0438\u0437|(?:19|20)\d{2}|company|business|organization|overall|our|we|us|show|give|analysis)/iu.test( + normalized + ); + return hasDueDateDebtCue && hasCompanyScopeCue; +} + function hasSpecificCounterpartyRevenueBridgeSignal(text: string): boolean { const normalized = String(text ?? "").trim().toLowerCase(); if (!normalized) { @@ -2134,6 +2157,10 @@ function resolveUnicodeAddressIntentBridge(text: string): AddressIntentResolutio return unicodeBridgeResolution("unknown", "high", "unicode_business_overview_earnings_deferred_to_discovery"); } + if (hasOrganizationLevelDebtDueDateOverviewBridgeSignal(normalized)) { + return unicodeBridgeResolution("unknown", "high", "unicode_business_overview_debt_due_date_deferred_to_discovery"); + } + if (hasBidirectionalValueFlowComparisonSignal(normalized)) { return unicodeBridgeResolution( "unknown", diff --git a/llm_normalizer/backend/src/services/address_runtime/composeStage.ts b/llm_normalizer/backend/src/services/address_runtime/composeStage.ts index f12c334..0516994 100644 --- a/llm_normalizer/backend/src/services/address_runtime/composeStage.ts +++ b/llm_normalizer/backend/src/services/address_runtime/composeStage.ts @@ -459,6 +459,10 @@ function hasReceivablesDebtAgingFocus(userMessage: string | null | undefined): b return hasDebtSignal && hasLongevitySignal && hasCounterpartySignal; } +function receivablesDueDateBoundaryLine(): string { + return "Граница доказанности: это возрастной сигнал/хвост дебиторки по 62/76, а не подтвержденная договорная просрочка или due-date aging; для due-date нужны сроки оплаты из договора или условий оплаты."; +} + function formatAgeYearsMonthsDays(daysRaw: number): string { const safeDays = Math.max(0, Math.floor(daysRaw)); const years = Math.floor(safeDays / 365); @@ -4133,7 +4137,8 @@ function composeFactualReplyBody( "Проверил должников по сроку жизни задолженности (контур 62/76).", `Дата среза: ${formatDateRu(asOfDate)}.`, `Строк в выборке: ${rows.length}.`, - `Контрагентов с сигналом: ${aging.length}.` + `Контрагентов с сигналом: ${aging.length}.`, + receivablesDueDateBoundaryLine() ]; if (aging.length > 0) { @@ -4166,7 +4171,8 @@ function composeFactualReplyBody( const lines = [ "Проверил покупателей с признаками затянутой оплаты (контур 62/76).", `Строк в выборке: ${rows.length}.`, - `Контрагентов с сигналом: ${counterparties.length}.` + `Контрагентов с сигналом: ${counterparties.length}.`, + receivablesDueDateBoundaryLine() ]; if (counterparties.length > 0) { lines.push("Приоритет ручной проверки (по сумме/частоте хвостов):"); diff --git a/llm_normalizer/backend/src/services/assistantMcpDiscoveryTurnInputAdapter.ts b/llm_normalizer/backend/src/services/assistantMcpDiscoveryTurnInputAdapter.ts index 4a7f743..c2ad7cf 100644 --- a/llm_normalizer/backend/src/services/assistantMcpDiscoveryTurnInputAdapter.ts +++ b/llm_normalizer/backend/src/services/assistantMcpDiscoveryTurnInputAdapter.ts @@ -710,8 +710,30 @@ function hasOrganizationLevelEarningsOverviewSignal(text: string): boolean { return hasYearRankingCue || hasCompanyEarningsCue || hasCompanyProfitMarginCue; } +function hasOrganizationLevelDebtDueDateOverviewSignal(text: string): boolean { + if (!text) { + return false; + } + if ( + /(?:\u043a\u0442\u043e|\u043a\u043e\u043c\u0443|\u043a\u0430\u043a\u0438\u0435\s+(?:\u043f\u043e\u043a\u0443\u043f\u0430\u0442\u0435\u043b|\u043a\u043b\u0438\u0435\u043d\u0442|\u043a\u043e\u043d\u0442\u0440\u0430\u0433\u0435\u043d\u0442|\u0437\u0430\u043a\u0430\u0437\u0447\u0438\u043a|\u0434\u043e\u043b\u0436\u043d\u0438\u043a)|who\b|which\s+(?:customers|clients|counterparties|debtors))/iu.test( + text + ) + ) { + return false; + } + const hasDueDateDebtCue = + /(?:\u043f\u0440\u043e\u0441\u0440\u043e\u0447\w*|\u0441\u0440\u043e\u043a\w*\s+\u043e\u043f\u043b\u0430\u0442|\u0441\u0440\u043e\u043a\u0438\s+\u0434\u0430\u0432\u043d\u043e\s+\u043f\u0440\u043e\u0448|\u0434\u043e\u043b\u0433\w*\s+aging|\u043a\u0430\u0447\u0435\u0441\u0442\u0432\w*\s+\u0434\u043e\u043b\u0433|\u0434\u043e\u043b\u0433\w*\s+\u043a\u0430\u0447\u0435\u0441\u0442\u0432|due[-\s]?date|overdue|debt\s+aging|debt\s+quality|credit\s+risk)/iu.test( + text + ); + const hasCompanyScopeCue = + /(?:\u0443\s+\u043d\u0430\u0441|\u043d\u0430\u0448\w*|\u043f\u043e\s+\u043a\u043e\u043c\u043f\u0430\u043d|\u043a\u043e\u043c\u043f\u0430\u043d|\u043e\u0440\u0433\u0430\u043d\u0438\u0437\u0430\u0446|\u0431\u0438\u0437\u043d\u0435\u0441|\u0432\s+\u0446\u0435\u043b\u043e\u043c|\u043e\u0431\u0449\w*|\u043a\u0430\u043a\w*|\u043f\u043e\u043a\u0430\u0436|\u0434\u0430\u0439|\u0441\u0440\u0435\u0437|\u0430\u043d\u0430\u043b\u0438\u0437|(?:19|20)\d{2}|company|business|organization|overall|our|we|us|show|give|analysis)/iu.test( + text + ); + return hasDueDateDebtCue && hasCompanyScopeCue; +} + function hasBusinessOverviewSignal(text: string): boolean { - if (hasOrganizationLevelEarningsOverviewSignal(text)) { + if (hasOrganizationLevelEarningsOverviewSignal(text) || hasOrganizationLevelDebtDueDateOverviewSignal(text)) { return true; } return /(?:\u0431\u0438\u0437\u043d\u0435\u0441[-\s]?\u043e\u0431\u0437\u043e\u0440|\u0431\u0438\u0437\u043d\u0435\u0441[-\s]?\u0430\u0443\u0434\u0438\u0442|\u043f\u043e\u043b\u043d\w*\s+\u0430\u043d\u0430\u043b\u0438\u0437\s+(?:\u043a\u043e\u043c\u043f\u0430\u043d|\u0431\u0438\u0437\u043d\u0435\u0441|\u0434\u0435\u044f\u0442\u0435\u043b)|\u0441\u0432\u043e\u0434\u043d\w*\s+\u0430\u043d\u0430\u043b\u0438\u0437\s+(?:\u043a\u043e\u043c\u043f\u0430\u043d|\u0431\u0438\u0437\u043d\u0435\u0441|\u0434\u0435\u044f\u0442\u0435\u043b)|\u043a\u0430\u043a\s+\u0442\u044b\s+\u043e\u0446\u0435\u043d(?:\u0438\u0448\u044c|\u0438)\s+\u0434\u0435\u044f\u0442\u0435\u043b\u044c\u043d\u043e\u0441\u0442|\u043a\u043e\u043c\u043f\u0430\u043d(?:\u0438\u0438|\u0438\u044e|\u0438\u044f)\s+\u0432\s+\u0446\u0435\u043b\u043e\u043c|company\s+(?:analysis|overview)|business\s+(?:overview|audit)|llm[-\s]?audit|бизнес[-\s]?РѕР±Р·РѕСЂ|бизнес[-\s]?аудит)/iu.test( diff --git a/llm_normalizer/backend/src/services/assistantTurnMeaningPolicy.ts b/llm_normalizer/backend/src/services/assistantTurnMeaningPolicy.ts index 3b82709..ec74c25 100644 --- a/llm_normalizer/backend/src/services/assistantTurnMeaningPolicy.ts +++ b/llm_normalizer/backend/src/services/assistantTurnMeaningPolicy.ts @@ -146,6 +146,29 @@ function hasOrganizationLevelEarningsOverviewSignal(text) { return hasYearRankingCue || hasCompanyEarningsCue || hasCompanyProfitMarginCue; } +function hasOrganizationLevelDebtDueDateOverviewSignal(text) { + const normalized = String(text ?? ""); + if (!normalized) { + return false; + } + if ( + /(?:\u043a\u0442\u043e|\u043a\u043e\u043c\u0443|\u043a\u0430\u043a\u0438\u0435\s+(?:\u043f\u043e\u043a\u0443\u043f\u0430\u0442\u0435\u043b|\u043a\u043b\u0438\u0435\u043d\u0442|\u043a\u043e\u043d\u0442\u0440\u0430\u0433\u0435\u043d\u0442|\u0437\u0430\u043a\u0430\u0437\u0447\u0438\u043a|\u0434\u043e\u043b\u0436\u043d\u0438\u043a)|who\b|which\s+(?:customers|clients|counterparties|debtors))/iu.test( + normalized + ) + ) { + return false; + } + const hasDueDateDebtCue = + /(?:\u043f\u0440\u043e\u0441\u0440\u043e\u0447\w*|\u0441\u0440\u043e\u043a\w*\s+\u043e\u043f\u043b\u0430\u0442|\u0441\u0440\u043e\u043a\u0438\s+\u0434\u0430\u0432\u043d\u043e\s+\u043f\u0440\u043e\u0448|\u0434\u043e\u043b\u0433\w*\s+aging|\u043a\u0430\u0447\u0435\u0441\u0442\u0432\w*\s+\u0434\u043e\u043b\u0433|\u0434\u043e\u043b\u0433\w*\s+\u043a\u0430\u0447\u0435\u0441\u0442\u0432|due[-\s]?date|overdue|debt\s+aging|debt\s+quality|credit\s+risk)/iu.test( + normalized + ); + const hasCompanyScopeCue = + /(?:\u0443\s+\u043d\u0430\u0441|\u043d\u0430\u0448\w*|\u043f\u043e\s+\u043a\u043e\u043c\u043f\u0430\u043d|\u043a\u043e\u043c\u043f\u0430\u043d|\u043e\u0440\u0433\u0430\u043d\u0438\u0437\u0430\u0446|\u0431\u0438\u0437\u043d\u0435\u0441|\u0432\s+\u0446\u0435\u043b\u043e\u043c|\u043e\u0431\u0449\w*|\u043a\u0430\u043a\w*|\u043f\u043e\u043a\u0430\u0436|\u0434\u0430\u0439|\u0441\u0440\u0435\u0437|\u0430\u043d\u0430\u043b\u0438\u0437|(?:19|20)\d{2}|company|business|organization|overall|our|we|us|show|give|analysis)/iu.test( + normalized + ); + return hasDueDateDebtCue && hasCompanyScopeCue; +} + function detectBroadBusinessEvaluation(text) { const normalized = String(text ?? ""); if (!normalized) { @@ -172,6 +195,11 @@ function detectBroadBusinessEvaluation(text) { family: "broad_business_evaluation" }; } + if (hasOrganizationLevelDebtDueDateOverviewSignal(normalized)) { + return { + family: "broad_business_evaluation" + }; + } return null; } diff --git a/llm_normalizer/backend/tests/addressQueryRuntimeM23.test.ts b/llm_normalizer/backend/tests/addressQueryRuntimeM23.test.ts index b9a22a7..22be54d 100644 --- a/llm_normalizer/backend/tests/addressQueryRuntimeM23.test.ts +++ b/llm_normalizer/backend/tests/addressQueryRuntimeM23.test.ts @@ -1577,6 +1577,7 @@ describe("address compose stage utf8 headers", () => { expect(reply.responseType).toBe("FACTUAL_LIST"); expect(reply.text).toContain("Проверил должников по сроку жизни задолженности"); expect(reply.text).toContain("Дата среза: 11.04.2026."); + expect(reply.text).toContain("не подтвержденная договорная просрочка или due-date aging"); expect(reply.text).toContain("Приоритет ручной проверки (по возрасту долга, по убыванию):"); expect(reply.text).toContain("1. Контрагент А | договоры:"); expect(reply.text).toContain("2. Контрагент Б | договоры:"); @@ -2752,6 +2753,14 @@ describe("address intent resolver expansion (M2.3a)", () => { expect(result.intent).toBe("list_receivables_counterparties"); }); + it("defers organization-level overdue debt wording to business overview discovery", () => { + const result = resolveAddressIntent( + "\u043a\u0430\u043a\u0430\u044f \u0443 \u043d\u0430\u0441 \u043f\u0440\u043e\u0441\u0440\u043e\u0447\u043a\u0430 \u043f\u043e \u0434\u043e\u043b\u0433\u0430\u043c \u0437\u0430 2020?" + ); + expect(result.intent).toBe("unknown"); + expect(result.reasons).toContain("unicode_business_overview_debt_due_date_deferred_to_discovery"); + }); + it("routes reconciliation mismatch wording into open contracts intent", () => { const result = resolveAddressIntent( "Покажи контрагентов, по которым сальдо скорее всего не совпадет с их актом сверки. Может, стоит поторопиться и запросить сверку?" diff --git a/llm_normalizer/backend/tests/assistantMcpDiscoveryTurnInputAdapter.test.ts b/llm_normalizer/backend/tests/assistantMcpDiscoveryTurnInputAdapter.test.ts index 8df1bd0..a20ccb8 100644 --- a/llm_normalizer/backend/tests/assistantMcpDiscoveryTurnInputAdapter.test.ts +++ b/llm_normalizer/backend/tests/assistantMcpDiscoveryTurnInputAdapter.test.ts @@ -1992,6 +1992,37 @@ describe("assistant MCP discovery turn input adapter", () => { expect(result.data_need_graph?.clarification_gaps).toEqual([]); }); + it("routes organization-level overdue debt wording to business overview instead of exact receivables recipes", () => { + 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: + "\u043a\u0430\u043a\u0430\u044f \u0443 \u043d\u0430\u0441 \u043f\u0440\u043e\u0441\u0440\u043e\u0447\u043a\u0430 \u043f\u043e \u0434\u043e\u043b\u0433\u0430\u043c \u0437\u0430 2020?", + followupContext: { + previous_discovery_pilot_scope: "open_items_by_counterparty_or_contract", + previous_filters: { + organization: orgName, + as_of_date: "2026-04-23" + } + } + }); + + 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.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.reason_codes).toContain("mcp_discovery_broad_business_evaluation_route_candidate"); + expect(result.reason_codes).not.toContain("mcp_discovery_date_scope_from_followup_context"); + expect(result.data_need_graph?.clarification_gaps).toEqual([]); + }); + it("resumes an open-scope total clarification loop from saved state when the user resolves the pending period with all-time wording", () => { const orgName = "ООО Альтернатива Плюс"; const result = buildAssistantMcpDiscoveryTurnInput({ diff --git a/llm_normalizer/backend/tests/assistantTurnMeaningPolicy.test.ts b/llm_normalizer/backend/tests/assistantTurnMeaningPolicy.test.ts index 3cd3803..cbc3a9b 100644 --- a/llm_normalizer/backend/tests/assistantTurnMeaningPolicy.test.ts +++ b/llm_normalizer/backend/tests/assistantTurnMeaningPolicy.test.ts @@ -177,5 +177,15 @@ describe("assistantTurnMeaningPolicy", () => { expect(profitMargin.asked_action_family).toBe("broad_evaluation"); expect(profitMargin.unsupported_but_understood_family).toBe("broad_business_evaluation"); expect(profitMargin.reason_codes).toContain("broad_business_evaluation_current_turn_signal"); + + const overdueDebt = policy.resolveAssistantTurnMeaning({ + rawUserMessage: + "\u043a\u0430\u043a\u0430\u044f \u0443 \u043d\u0430\u0441 \u043f\u0440\u043e\u0441\u0440\u043e\u0447\u043a\u0430 \u043f\u043e \u0434\u043e\u043b\u0433\u0430\u043c \u0437\u0430 2020?" + }); + expect(overdueDebt.explicit_intent_candidate).toBeNull(); + expect(overdueDebt.asked_domain_family).toBe("business_summary"); + expect(overdueDebt.asked_action_family).toBe("broad_evaluation"); + expect(overdueDebt.unsupported_but_understood_family).toBe("broad_business_evaluation"); + expect(overdueDebt.reason_codes).toContain("broad_business_evaluation_current_turn_signal"); }); });