Open-World: расширить бизнес-обзор факт-семействами
This commit is contained in:
parent
e65b7fdaed
commit
edab736a6d
|
|
@ -15,8 +15,14 @@ If another document says `78%`, `87%`, `92%`, or `85%` for a module that is now
|
|||
- `Planner Autonomy Consolidation`: `100%` for the declared phase83 planner-brain slice, including catalog alignment, live-readiness gating, checked-source sanitation, and accepted mixed replay.
|
||||
- Active next module: broader `Open-World Bounded Autonomy Breadth` over unfamiliar 1C asks, while keeping Post-F and phase83 as regression gates.
|
||||
- Completed active slice: `Business Overview Evidence Fusion`, tracked in `22 - open_world_bounded_autonomy_breadth_2026-05-01.md`.
|
||||
- Current active slice: `Business Overview Catalog Route Fabric`: the route is reviewed in catalog/data-need/planner contracts, while fresh multi-probe runtime execution remains a pending bridge.
|
||||
- Active module progress: `~18% (Open-World Bounded Autonomy Breadth)`.
|
||||
- Completed active slice: `Business Overview Catalog Route Fabric`: the route is reviewed in catalog/data-need/planner contracts and exposes the stable `business_overview` route scope.
|
||||
- Completed active slice: `Business Overview Fresh Multi-Probe Runtime Bridge`: the reviewed route now executes incoming money flow, outgoing supplier payout, activity-window, net-spread, top-customer, and analyst-safe answer drafting, and has passed live semantic replay against the real assistant runtime.
|
||||
- Completed active slice: `Business Overview VAT/Tax Fact-Family Bridge`: explicit-period business overview can include confirmed VAT/tax position, while all-time follow-ups and negated VAT periods do not reuse stale tax scope.
|
||||
- Completed active slice: `Business Overview Debt-Position Fact-Family Bridge`: explicit-period business overview can include confirmed receivables/payables as-of-date debt position, while all-time follow-ups do not reuse stale debt snapshots and debt quality/aging remains unclaimed.
|
||||
- Completed active slice: `Business Overview Inventory-Position Fact-Family Bridge`: explicit-date business overview can include confirmed stock-on-hand inventory position, while all-time follow-ups do not reuse stale inventory snapshots and inventory liquidity/turnover remains unclaimed.
|
||||
- Completed active slice: `Business Overview Open-Settlement Quality Bridge`: explicit-period business overview can check open-contract settlement concentration on 60/62/76, while due-date aging/overdue debt remains unclaimed until a reviewed due-date route exists.
|
||||
- Next active slice: continue `Business Overview Fact-Family Expansion` into profit/margin and due-date debt aging where reviewed routes exist.
|
||||
- Active module progress: `~60% (Open-World Bounded Autonomy Breadth)`.
|
||||
|
||||
## Reporting Rule
|
||||
|
||||
|
|
@ -53,7 +59,7 @@ The project is not yet a universal arbitrary-1C agent.
|
|||
|
||||
Remaining work belongs to the next breadth module:
|
||||
|
||||
- implement the fresh multi-probe `business_overview` runtime bridge behind the reviewed route-fabric contract;
|
||||
- extend `business_overview` beyond money-flow/activity, explicit-period VAT/tax, as-of-date debt position, open-settlement concentration, and as-of-date inventory position into separately proven profit/margin, due-date debt aging/overdue, and real inventory-liquidity 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;
|
||||
|
|
|
|||
|
|
@ -82,18 +82,123 @@ Implemented now:
|
|||
- the data-need graph recognizes broad company analysis as a bounded business-overview evidence need;
|
||||
- fresh business-overview probes require an organization scope instead of silently reusing stale context;
|
||||
- planner output can select `business_overview` as the catalog top match with structured alignment telemetry;
|
||||
- the pilot executor exposes `business_overview_route_template_v1` as an explicit scope, but returns an unsupported runtime boundary until the fresh multi-probe bridge is implemented.
|
||||
- the pilot executor exposes `business_overview_route_template_v1` as the stable runtime scope for the next bridge.
|
||||
|
||||
This is deliberately not a fake runtime success.
|
||||
This slice was deliberately only route fabric: it made the reviewed route visible without pretending fresh runtime evidence existed yet.
|
||||
|
||||
The assistant now has the route-fabric contract for the next slice, while the live business overview still uses the safe evidence-fusion bridge from Slice 1.
|
||||
The assistant now has the route-fabric contract used by Slice 3.
|
||||
|
||||
### Still Pending Runtime Slice
|
||||
## Slice 3 - Business Overview Fresh Multi-Probe Runtime Bridge
|
||||
|
||||
Promote this bridge into a real planner route:
|
||||
This slice makes the reviewed `business_overview` route execute as a bounded multi-probe MCP discovery bridge.
|
||||
|
||||
- run bounded fresh probes for year turnover, top customers, incoming/outgoing/net flow, debt, VAT, and inventory context where available;
|
||||
- return a layered analyst answer with exact evidence, bounded inference, unknowns, and recommended next probes.
|
||||
Implemented now:
|
||||
|
||||
- broad company-analysis turn meaning routes into `business overview evidence with bounded analyst interpretation` instead of being kept only in deterministic living chat;
|
||||
- the bridge runs fresh scoped probes for incoming customer money flow, outgoing supplier payouts, and activity-window evidence;
|
||||
- the pilot derives checked incoming total, checked outgoing total, net cash-flow spread, net direction, top confirmed customer, and activity window;
|
||||
- the answer adapter turns those derived facts into a layered analyst-safe draft: confirmed facts, bounded interpretation, unknowns, and next probes;
|
||||
- response policy can replace the old deterministic broad summary only when the discovery candidate has grounded text, while clarification candidates still preserve the safe deterministic answer;
|
||||
- user-facing answers must not expose `business_overview_route_template_v1`, MCP primitive names, raw planner/debug labels, or profit/margin claims.
|
||||
|
||||
Live semantic replay is now accepted for this slice:
|
||||
|
||||
- `address_truth_harness_phase84_business_overview_multi_probe_bridge_live_20260503_runtime_bridge2` passed `3/3`;
|
||||
- step 1 proves explicit company business overview for `ООО Альтернатива Плюс`;
|
||||
- step 2 proves an exact `Группа СВК` 2020 net-flow follow-up after the company overview without stale organization-scope contamination;
|
||||
- step 3 proves returning to `ООО Альтернатива Плюс` business overview after a counterparty pivot without treating net cash-flow as profit.
|
||||
|
||||
## Slice 4 - Business Overview VAT/Tax Fact-Family Bridge
|
||||
|
||||
This slice adds the first separately checked fact family beyond money-flow/activity.
|
||||
|
||||
Implemented now:
|
||||
|
||||
- explicit-period business overview can select the reviewed VAT/tax recipe and execute the tax probe beside the money-flow/activity probes;
|
||||
- the pilot derives sales VAT, purchase/deduction VAT, net VAT direction, and the checked tax period only from confirmed VAT rows;
|
||||
- the answer adapter can surface VAT/tax position as a confirmed line without treating net cash-flow as profit or margin;
|
||||
- broad all-time business overview does not silently reuse a prior or negated VAT period;
|
||||
- wording such as `do not carry VAT for 2020` / `не тащи НДС за 2020` is treated as a temporal exclusion, not as the active period for the current turn;
|
||||
- organization extraction strips trailing all-time/business-overview clauses so the company scope remains the company name, not the whole user sentence.
|
||||
|
||||
Live semantic replay is accepted for this slice:
|
||||
|
||||
- `address_truth_harness_phase85_business_overview_tax_family_live_20260504_taxfamily2` passed `2/2`;
|
||||
- step 1 proves explicit 2020 business overview may include a confirmed VAT/tax position;
|
||||
- step 2 proves all-time follow-up over the same company does not reuse the 2020 VAT/tax position from the prior turn or from the negated wording;
|
||||
- the accepted debug path shows `explicit_date_scope=null`, `tax_position=null`, and `pilot_business_overview_tax_probe_skipped_without_explicit_period` for the all-time follow-up.
|
||||
|
||||
## Slice 5 - Business Overview Debt-Position Fact-Family Bridge
|
||||
|
||||
This slice adds a second separately checked fact family, but deliberately stops short of debt quality.
|
||||
|
||||
Implemented now:
|
||||
|
||||
- explicit-period business overview can derive an as-of-date from the user-visible period and execute reviewed receivables/payables snapshot recipes beside money-flow/activity and optional VAT/tax probes;
|
||||
- the pilot derives receivables, payables, net debt-position amount, net direction, and top debt-side counterparties only from confirmed 1C balance rows;
|
||||
- the answer adapter can surface the debt-position snapshot as a confirmed line without treating it as overdue debt, debt aging, credit quality, profit, or margin;
|
||||
- all-time business overview does not reuse the prior as-of-date debt snapshot and instead keeps debt position as an unknown fact family until the user gives a new explicit date;
|
||||
- raw organization extraction now strips trailing explicit period clauses such as `за 2020 год` / `на 2020-12-31`, so the company scope remains the company name and the period remains a separate temporal axis.
|
||||
|
||||
Live semantic replay is accepted for this slice:
|
||||
|
||||
- `address_truth_harness_phase86_business_overview_debt_position_live_20260504_debt2` passed `2/2`;
|
||||
- step 1 proves explicit 2020 business overview may include a confirmed receivables/payables debt-position snapshot on `2020-12-31`;
|
||||
- step 2 proves an all-time follow-up over the same company does not reuse the `2020-12-31` debt snapshot as current or all-time debt position;
|
||||
- the accepted debug path shows `organization_scope=ООО Альтернатива Плюс`, `explicit_date_scope=2020`, `debt_position.as_of_date=2020-12-31` for the explicit-period step, and `explicit_date_scope=null`, `debt_position=null`, `pilot_business_overview_debt_probe_skipped_without_explicit_as_of_date` for the all-time follow-up.
|
||||
|
||||
## Slice 6 - Business Overview Inventory-Position Fact-Family Bridge
|
||||
|
||||
This slice adds a third separately checked fact family, but deliberately stops short of warehouse liquidity or turnover.
|
||||
|
||||
Implemented now:
|
||||
|
||||
- explicit-date business overview can derive an as-of date and execute reviewed inventory on-hand and optional purchase-date aging probes beside the existing money-flow/activity, VAT/tax, and debt-position probes;
|
||||
- the pilot derives inventory on-hand rows, rows with amount, rows with quantity, total stock amount, total quantity, top stock items, and optional purchase-date aging signal only from confirmed 1C inventory rows;
|
||||
- the answer adapter can surface the inventory-position snapshot as a confirmed line without treating it as turnover, obsolescence, liquidation value, full inventory health, profit, or margin;
|
||||
- if the current business-overview slice has no incoming/outgoing money rows, the answer no longer emits a fake `net 0` cash-flow interpretation;
|
||||
- business-overview headlines now list only the fact families that were actually confirmed in the current turn, so a stock/debt/tax snapshot does not claim money-flow or activity when those rows were absent;
|
||||
- all-time business overview does not reuse a prior as-of-date inventory snapshot and instead keeps stock/inventory position as an unknown fact family until the user gives a new explicit date.
|
||||
|
||||
Live semantic replay is accepted for this slice:
|
||||
|
||||
- `address_truth_harness_phase87_business_overview_inventory_position_live_20260504_inventory2` passed `2/2`;
|
||||
- step 1 proves explicit-date business overview may include a confirmed warehouse stock snapshot on `2026-04-16`;
|
||||
- step 2 proves an all-time follow-up over the same company does not reuse the `2026-04-16` stock snapshot as current or all-time warehouse position;
|
||||
- the accepted user-facing answer confirms stock amount `716 418,33` rub. over `11` rows and keeps warehouse liquidity/turnover as unconfirmed.
|
||||
|
||||
## Slice 7 - Business Overview Open-Settlement Quality Bridge
|
||||
|
||||
This slice widens the debt family from a simple receivables/payables position into a bounded quality signal, but deliberately stops short of due-date aging or confirmed overdue debt.
|
||||
|
||||
Implemented now:
|
||||
|
||||
- explicit-period business overview can execute the reviewed `open_contracts_confirmed_as_of_date` balance recipe beside money-flow/activity, VAT/tax, debt-position, and inventory probes;
|
||||
- the pilot derives gross open settlement amount, unique open-contract count, unique counterparty count, top open contracts, top counterparties, and concentration percentages only from confirmed 1C balance rows on 60/62/76;
|
||||
- the answer adapter can surface this as `качество открытых расчетов` without treating concentration as contractual due-date aging, confirmed overdue debt, credit risk, profit, or margin;
|
||||
- all-time business overview keeps open-settlement quality unknown unless the current turn has a fresh explicit as-of date;
|
||||
- the remaining hard boundary is still due-date/overdue aging: open contracts prove concentration of open balances, not payment-term delinquency.
|
||||
|
||||
Live semantic replay is accepted for this slice:
|
||||
|
||||
- `npm.cmd test -- assistantMcpDiscoveryPilotExecutor.test.ts`: passed `31/31`;
|
||||
- `npm.cmd test -- assistantMcpDiscoveryAnswerAdapter.test.ts`: passed `34/34` with `1` skipped;
|
||||
- `npm.cmd test -- assistantMcp`: passed `305/305` with `9` skipped;
|
||||
- `npm.cmd run build`: passed;
|
||||
- `address_truth_harness_phase88_business_overview_open_settlement_quality_live_20260504_openquality4` passed `2/2`;
|
||||
- step 1 proves explicit 2020 business overview may include confirmed open-settlement concentration: `35 472 380,36` rub. gross open contract balances, `8` contracts, `11` counterparties, and top contract share `54.2%`;
|
||||
- step 2 proves an all-time follow-up does not reuse the `2020-12-31` open-contract/debt snapshot as current overdue debt or all-time debt quality;
|
||||
- the accepted user-facing answer keeps due-date aging and overdue debt as unconfirmed because open contracts prove concentration of balances, not payment-term delinquency.
|
||||
|
||||
### Still Pending Breadth Slices
|
||||
|
||||
Grow this bridge beyond the first confirmed signal bundle:
|
||||
|
||||
- add separate evidence families for profit/margin and due-date debt aging/overdue quality where reviewed routes exist;
|
||||
- extend inventory evidence from as-of-date stock position into real turnover/liquidity only when reviewed sales velocity, aging, or obsolescence evidence exists;
|
||||
- extend debt evidence from as-of-date position/open-settlement concentration into overdue aging only when reviewed due-date or aging evidence exists;
|
||||
- extend VAT/tax beyond explicit-period tax position only when the requested tax fact is provable and the period is explicit;
|
||||
- keep Post-F stale-scope and phase83 catalog-alignment canaries green while widening the route.
|
||||
|
||||
## Acceptance Signals
|
||||
|
||||
|
|
@ -112,11 +217,43 @@ Initial local validation:
|
|||
- `npm.cmd test -- assistantTurnMeaningPolicy.test.ts assistantLivingChatRuntimeAdapter.test.ts`: passed `20/20`.
|
||||
- `npm.cmd test -- assistantTurnMeaningPolicy.test.ts assistantLivingChatRuntimeAdapter.test.ts assistantRoutePolicy.test.ts assistantMcpDiscoveryResponsePolicy.test.ts`: passed `56/56`.
|
||||
- `npm.cmd run build`: passed.
|
||||
- graphify rebuild: `5977 nodes`, `12983 edges`, `137 communities`.
|
||||
- graphify rebuild at Slice 2 boundary: `5977 nodes`, `12983 edges`, `137 communities`.
|
||||
|
||||
Business-overview route-fabric validation:
|
||||
|
||||
- `npm.cmd test -- assistantMcpCatalogIndex.test.ts assistantMcpDiscoveryDataNeedGraph.test.ts assistantMcpDiscoveryPlanner.test.ts assistantMcpDiscoveryPilotExecutor.test.ts`: passed `102/102`.
|
||||
- fresh multi-probe runtime execution remains intentionally pending behind `business_overview_route_template_v1`.
|
||||
|
||||
Graphify must be rebuilt after this code/doc slice before commit.
|
||||
Business-overview fresh runtime bridge validation:
|
||||
|
||||
- `npm.cmd test -- assistantMcpDiscoveryRuntimeEntryPoint.test.ts assistantMcpDiscoveryPilotExecutor.test.ts assistantMcpDiscoveryAnswerAdapter.test.ts assistantMcpDiscoveryTurnInputAdapter.test.ts assistantMcpDiscoveryResponsePolicy.test.ts assistantMcpDiscoveryPlanner.test.ts`: passed `211/211` with `9` skipped.
|
||||
- `npm.cmd test -- assistantMcp`: passed `296/296` with `9` skipped.
|
||||
- `npm.cmd run build`: passed.
|
||||
- live replay `address_truth_harness_phase84_business_overview_multi_probe_bridge_live_20260503_runtime_bridge2`: accepted `3/3` with `catalog_alignment_ok=true`, `human_answer_quality_ok=true`, and no internal route/debug terms in the user-facing answer.
|
||||
|
||||
Business-overview VAT/tax fact-family validation:
|
||||
|
||||
- `npm.cmd test -- assistantMcpDiscoveryTurnInputAdapter.test.ts`: passed `76/76` with `6` skipped.
|
||||
- `npm.cmd test -- assistantMcp`: passed `300/300` with `9` skipped.
|
||||
- `npm.cmd run build`: passed.
|
||||
- live replay `address_truth_harness_phase85_business_overview_tax_family_live_20260504_taxfamily2`: accepted `2/2` and proved that a negated 2020 VAT period is not reused as an all-time tax position.
|
||||
|
||||
Business-overview debt-position fact-family validation:
|
||||
|
||||
- `npm.cmd test -- assistantMcpDiscoveryPilotExecutor.test.ts`: passed `30/30`.
|
||||
- `npm.cmd test -- assistantMcpDiscoveryAnswerAdapter.test.ts`: passed `33/33` with `1` skipped.
|
||||
- `npm.cmd test -- assistantMcpDiscoveryTurnInputAdapter.test.ts`: passed `77/77` with `6` skipped.
|
||||
- `npm.cmd test -- assistantMcp`: passed `303/303` with `9` skipped.
|
||||
- `npm.cmd run build`: passed.
|
||||
- live replay `address_truth_harness_phase86_business_overview_debt_position_live_20260504_debt2`: accepted `2/2` and proved that explicit debt-position snapshots do not leak into all-time follow-ups.
|
||||
|
||||
Business-overview inventory-position fact-family validation:
|
||||
|
||||
- `npm.cmd test -- assistantMcpDiscoveryPilotExecutor.test.ts`: passed `31/31`.
|
||||
- `npm.cmd test -- assistantMcpDiscoveryAnswerAdapter.test.ts`: passed `34/34` with `1` skipped.
|
||||
- `npm.cmd test -- assistantMcp`: passed `305/305` with `9` skipped.
|
||||
- `npm.cmd run build`: passed.
|
||||
- live replay `address_truth_harness_phase87_business_overview_inventory_position_live_20260504_inventory2`: accepted `2/2` and proved that explicit inventory-position snapshots do not leak into all-time follow-ups.
|
||||
|
||||
Graphify rebuild after Slice 6 code/doc sync: `6001 nodes`, `13058 edges`, `140 communities`.
|
||||
|
||||
Graphify rebuild after Slice 7 code/doc sync: `6008 nodes`, `13078 edges`, `138 communities`.
|
||||
|
|
|
|||
|
|
@ -41,7 +41,7 @@ This package answers the next question:
|
|||
21. [21 - current_status_canon_2026-05-01.md](./21%20-%20current_status_canon_2026-05-01.md)
|
||||
22. [22 - open_world_bounded_autonomy_breadth_2026-05-01.md](./22%20-%20open_world_bounded_autonomy_breadth_2026-05-01.md)
|
||||
|
||||
## Current Status Snapshot (2026-05-01)
|
||||
## Current Status Snapshot (2026-05-04)
|
||||
|
||||
This package is no longer planning-only.
|
||||
|
||||
|
|
@ -51,7 +51,12 @@ Status canon for planning:
|
|||
- Planner Autonomy Consolidation is closed at `100%` for the declared phase83 planner-brain slice.
|
||||
- The active next module is now `Open-World Bounded Autonomy Breadth` over unfamiliar 1C asks, with Post-F and phase83 retained as semantic canaries.
|
||||
- The first active slice is `Business Overview Evidence Fusion`: broad company-analysis wording now produces a richer evidence-grounded business overview from confirmed MCP/session facts instead of a thin generic summary.
|
||||
- The current follow-up slice is `Business Overview Catalog Route Fabric`: `business_overview` is now a reviewed catalog/data-need/planner chain, while fresh multi-probe execution remains honestly bounded behind an unsupported runtime scope until the next bridge is implemented.
|
||||
- The current completed slice is `Business Overview Fresh Multi-Probe Runtime Bridge`: `business_overview` is now a reviewed catalog/data-need/planner chain and a live-replay accepted runtime bridge over incoming money flow, outgoing supplier payouts, activity-window evidence, net-spread, top customer, and analyst-safe answer drafting.
|
||||
- The current completed breadth slice is `Business Overview VAT/Tax Fact-Family Bridge`: explicit-period business overview can include confirmed VAT/tax position, while all-time follow-ups and negated VAT period wording do not reuse stale tax scope.
|
||||
- The current completed breadth slice is `Business Overview Debt-Position Fact-Family Bridge`: explicit-period business overview can include confirmed receivables/payables as-of-date debt position, while all-time follow-ups do not reuse stale debt snapshots and debt quality/aging remains unclaimed.
|
||||
- The current completed breadth slice is `Business Overview Inventory-Position Fact-Family Bridge`: explicit-date business overview can include confirmed stock-on-hand inventory position, while all-time follow-ups do not reuse stale inventory snapshots and inventory liquidity/turnover remains unclaimed.
|
||||
- The current completed breadth slice is `Business Overview Open-Settlement Quality Bridge`: explicit-period business overview can check open-contract settlement concentration, while due-date aging and confirmed overdue debt remain outside the answer until a reviewed due-date route exists.
|
||||
- The next active breadth slice continues `Business Overview Fact-Family Expansion` into profit/margin and due-date debt aging, then broader unfamiliar 1C route breadth 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).
|
||||
|
||||
It now documents a turnaround that is already operational in code, already materially past the acute regression breakpoint, and already moved through bounded MCP autonomy, Post-F hardening, inventory breadth proof, and the declared Planner Autonomy slice:
|
||||
|
|
@ -86,7 +91,7 @@ It now documents a turnaround that is already operational in code, already mater
|
|||
- explicit document/movement data-need now scores over ambiguous carried metadata surfaces without forcing neutral follow-ups into a lane;
|
||||
- lifecycle now behaves as a bounded activity-window inference chain with an explicit legal-fact boundary instead of an unqualified age answer;
|
||||
- current-turn value-flow aggregate questions can override narrower supported exact routes when the user asks for totals/net/payment amounts;
|
||||
- broad business evaluation remains in the deterministic living-chat bridge instead of being displaced by generic metadata discovery;
|
||||
- broad business evaluation remained guarded during phase83 and is now carried forward in the next breadth module as a reviewed `business_overview` discovery route instead of being displaced by generic metadata discovery;
|
||||
- inventory stock snapshot, supplier overlap, purchase provenance, and sale trace are now reviewed catalog chain templates; generic free-form inventory execution remains forbidden, and evidence must pass through reviewed exact recipe bridges;
|
||||
- runtime bridge and answer adapter now keep unsupported inventory route templates behind an explicit user-facing boundary instead of letting template planning look like confirmed stock/supplier/purchase/sale evidence;
|
||||
- inventory catalog templates now bridge through existing exact inventory recipes (`41.01` scoped stock, supplier overlap, purchase provenance, and sale trace) inside the bounded MCP discovery pilot, while missing selected-item anchors still clarify instead of guessing;
|
||||
|
|
@ -115,12 +120,12 @@ Current honest status:
|
|||
- exit-from-danger-zone readiness: `~97%`
|
||||
- pre-multidomain readiness: `~90%`
|
||||
- bounded-autonomy foundation readiness: `~89%`
|
||||
- open-world bounded-autonomy readiness: `~86%`
|
||||
- active Open-World Bounded Autonomy Breadth progress: `~18%`, with business-overview evidence fusion and the reviewed `business_overview` catalog/data-need/planner route-fabric slice locally tested; fresh multi-probe runtime execution is still pending
|
||||
- open-world bounded-autonomy readiness: `~87%`
|
||||
- active Open-World Bounded Autonomy Breadth progress: `~60%`, 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, and the open-settlement quality bridge accepted by live semantic replay; profit/margin, due-date debt aging/overdue, and real inventory-liquidity expansion 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
|
||||
- graph snapshot after latest rebuild: `5977 nodes`, `12983 edges`, `137 communities`
|
||||
- graph snapshot after latest rebuild: `6008 nodes`, `13078 edges`, `138 communities`
|
||||
- current regression-gate breakpoint:
|
||||
- the validated hot paths are no longer structurally broken;
|
||||
- flagship continuity collapse is no longer the primary risk;
|
||||
|
|
@ -160,7 +165,13 @@ Latest live proof now includes:
|
|||
- lifecycle/value-flow Planner Autonomy response gate accepted: `address_truth_harness_phase19_mcp_discovery_response_gate_planner_lifecycle_rerun4` accepted `8/8`, proving bounded lifecycle inference, current-turn value-flow aggregate arbitration, and sanitized evidence wording
|
||||
- broad-evaluation bridge continuity accepted: `address_truth_harness_phase21_net_followup_after_broad_eval_planner_lifecycle_rerun2` accepted `3/3` and `address_truth_harness_phase22_broad_business_evaluation_bridge_planner_lifecycle_rerun2` accepted `3/3`
|
||||
- latest local Planner Autonomy slice accepted: full MCP-discovery suite passed `268/268` with `9` skipped; broad MCP/living-chat/route/meaning slice passed `305/305` with `9` skipped; build passed
|
||||
- business-overview route-fabric slice accepted locally: catalog/data-need/planner/pilot boundary slice passed `102/102`; the pilot executor exposes `business_overview_route_template_v1` as an explicit unsupported scope until the fresh multi-probe bridge exists
|
||||
- business-overview route-fabric slice accepted locally: catalog/data-need/planner/pilot boundary slice passed `102/102`, proving the reviewed `business_overview` chain and stable route scope
|
||||
- business-overview fresh multi-probe runtime bridge accepted locally: targeted runtime-entry/pilot/answer/turn-input/response-policy/planner slice passed `211/211` with `9` skipped; full MCP-discovery suite passed `296/296` with `9` skipped; build passed
|
||||
- business-overview fresh multi-probe runtime bridge accepted live: `address_truth_harness_phase84_business_overview_multi_probe_bridge_live_20260503_runtime_bridge2` accepted `3/3`, proving explicit company overview, exact counterparty net-flow after the company overview, and explicit company overview after a counterparty pivot with `catalog_alignment_ok=true`, `human_answer_quality_ok=true`, and no internal route/debug leakage in the user-facing answer
|
||||
- business-overview VAT/tax fact-family bridge accepted live: `address_truth_harness_phase85_business_overview_tax_family_live_20260504_taxfamily2` accepted `2/2`, proving explicit-period VAT/tax position in the company overview and all-time follow-up protection against stale or negated VAT-period reuse
|
||||
- business-overview debt-position fact-family bridge accepted live: `address_truth_harness_phase86_business_overview_debt_position_live_20260504_debt2` accepted `2/2`, proving explicit-period receivables/payables as-of-date debt position and all-time follow-up protection against stale debt snapshot reuse
|
||||
- business-overview inventory-position fact-family bridge accepted live: `address_truth_harness_phase87_business_overview_inventory_position_live_20260504_inventory2` accepted `2/2`, proving explicit-date stock-on-hand position and all-time follow-up protection against stale inventory snapshot reuse
|
||||
- business-overview open-settlement quality bridge accepted live: `address_truth_harness_phase88_business_overview_open_settlement_quality_live_20260504_openquality4` accepted `2/2`, proving explicit-period open-contract concentration and all-time follow-up protection against stale open-contract/debt-quality reuse
|
||||
- inventory template lift accepted locally: catalog/data-need/planner/turn-input slice passed `139/139` with `6` skipped; full MCP-discovery slice passed `276/276` with `9` skipped; build passed; graphify stayed at `5912 nodes`, `12833 edges`, `138 communities`
|
||||
- inventory runtime-boundary hardening accepted locally: runtime-bridge/answer-adapter/pilot-executor slice passed `68/68` with `1` skipped; full MCP-discovery slice passed `277/277` with `9` skipped; build passed; graphify rebuilt to `5913 nodes`, `12837 edges`, `138 communities`
|
||||
- inventory exact-runtime bridge accepted locally: runtime-bridge/answer-adapter/pilot-executor slice passed `70/70` with `1` skipped; full MCP-discovery slice passed `279/279` with `9` skipped; build passed; graphify rebuilt to `5930 nodes`, `12884 edges`, `135 communities`
|
||||
|
|
|
|||
|
|
@ -0,0 +1,106 @@
|
|||
{
|
||||
"schema_version": "domain_truth_harness_spec_v1",
|
||||
"scenario_id": "address_truth_harness_phase84_business_overview_multi_probe_bridge",
|
||||
"domain": "address_phase84_business_overview_multi_probe_bridge",
|
||||
"title": "Phase 84 business overview multi-probe bridge replay",
|
||||
"description": "Targeted AGENT replay for the fresh business_overview MCP discovery bridge: broad company analysis must execute through reviewed multi-probe evidence, keep profit/margin boundaries honest, and not contaminate later exact counterparty pivots or repeated company-overview pivots.",
|
||||
"bindings": {},
|
||||
"steps": [
|
||||
{
|
||||
"step_id": "step_01_explicit_company_business_overview",
|
||||
"title": "Explicit company overview uses the reviewed business_overview bridge",
|
||||
"question": "Дай бизнес-обзор ООО Альтернатива Плюс по данным 1С: обороты, нетто, активность, что подтверждено и что пока неизвестно.",
|
||||
"expected_catalog_alignment_status": "selected_matches_top",
|
||||
"expected_catalog_chain_top_match": "business_overview",
|
||||
"expected_catalog_selected_matches_top": true,
|
||||
"allowed_reply_types": [
|
||||
"partial_coverage",
|
||||
"factual_with_explanation"
|
||||
],
|
||||
"required_answer_patterns_all": [
|
||||
"(?i)1с|подтвержд",
|
||||
"(?i)входящ|поступлен|оборот|денежн",
|
||||
"(?i)исходящ|платеж|списан|поставщик",
|
||||
"(?i)нетто|разниц|сальдо",
|
||||
"(?i)прибыл|марж|не подтвержд|не доказан",
|
||||
"(?i)ндс|vat|налог|долг|склад|inventory"
|
||||
],
|
||||
"forbidden_answer_patterns": [
|
||||
"(?i)business_overview_route_template_v1",
|
||||
"(?i)query_movements|query_documents|primitive|planner_|runtime_|pilot_",
|
||||
"(?i)это прибыль|маржа составляет|чистая прибыль"
|
||||
],
|
||||
"criticality": "critical",
|
||||
"semantic_tags": [
|
||||
"business_overview_multi_probe",
|
||||
"broad_business_evaluation",
|
||||
"planner_catalog_alignment",
|
||||
"profit_margin_boundary"
|
||||
]
|
||||
},
|
||||
{
|
||||
"step_id": "step_02_exact_net_flow_after_company_overview",
|
||||
"title": "Exact counterparty net-flow still answers after company overview",
|
||||
"question": "Теперь отдельно: какое нетто по деньгам с Группа СВК за 2020 год, сколько получили и сколько заплатили?",
|
||||
"expected_catalog_alignment_status": "selected_matches_top",
|
||||
"expected_catalog_chain_top_match": "value_flow_comparison",
|
||||
"expected_catalog_selected_matches_top": true,
|
||||
"allowed_reply_types": [
|
||||
"partial_coverage",
|
||||
"factual_with_explanation"
|
||||
],
|
||||
"required_answer_patterns_all": [
|
||||
"(?i)свк",
|
||||
"(?i)2020|период",
|
||||
"(?i)получил|входящ|поступлен",
|
||||
"(?i)заплат|исходящ|списан|платеж",
|
||||
"(?i)нетто|разниц|сальдо",
|
||||
"(?i)руб"
|
||||
],
|
||||
"forbidden_answer_patterns": [
|
||||
"(?i)активность компании",
|
||||
"(?i)прибыль компании",
|
||||
"(?i)business_overview_route_template_v1",
|
||||
"(?i)query_movements|query_documents|primitive|planner_|runtime_|pilot_"
|
||||
],
|
||||
"criticality": "critical",
|
||||
"semantic_tags": [
|
||||
"counterparty_net_cash_flow",
|
||||
"post_broad_eval_exact_pivot",
|
||||
"planner_catalog_alignment"
|
||||
]
|
||||
},
|
||||
{
|
||||
"step_id": "step_03_explicit_company_overview_after_counterparty_pivot",
|
||||
"title": "Repeated company overview does not inherit the counterparty pivot",
|
||||
"question": "Вернись к ООО Альтернатива Плюс в целом: дай краткий бизнес-аудит по подтвержденным данным 1С, но не выдавай нетто за прибыль.",
|
||||
"expected_catalog_alignment_status": "selected_matches_top",
|
||||
"expected_catalog_chain_top_match": "business_overview",
|
||||
"expected_catalog_selected_matches_top": true,
|
||||
"allowed_reply_types": [
|
||||
"partial_coverage",
|
||||
"factual_with_explanation"
|
||||
],
|
||||
"required_answer_patterns_all": [
|
||||
"(?i)альтернатива|компани|организац",
|
||||
"(?i)1с|подтвержд",
|
||||
"(?i)нетто|денежн|поток",
|
||||
"(?i)не прибыль|не является прибыл|не марж|марж.*не подтвержд",
|
||||
"(?i)следующ|нужно|не подтвержд|отдельн"
|
||||
],
|
||||
"forbidden_answer_patterns": [
|
||||
"(?i)группа свк.*бизнес-аудит",
|
||||
"(?i)чистая прибыль",
|
||||
"(?i)business_overview_route_template_v1",
|
||||
"(?i)query_movements|query_documents|primitive|planner_|runtime_|pilot_"
|
||||
],
|
||||
"criticality": "critical",
|
||||
"semantic_tags": [
|
||||
"business_overview_after_counterparty_pivot",
|
||||
"stale_scope_guard",
|
||||
"planner_catalog_alignment",
|
||||
"profit_margin_boundary"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -0,0 +1,78 @@
|
|||
{
|
||||
"schema_version": "domain_truth_harness_spec_v1",
|
||||
"scenario_id": "address_truth_harness_phase85_business_overview_tax_family",
|
||||
"domain": "address_phase85_business_overview_tax_family",
|
||||
"title": "Phase 85 business overview VAT/tax fact-family replay",
|
||||
"description": "Targeted replay for Business Overview Fact-Family Expansion: explicit-period company overview may include checked VAT/tax evidence, while all-time follow-up must not reuse the previous VAT period as confirmed tax position.",
|
||||
"bindings": {},
|
||||
"steps": [
|
||||
{
|
||||
"step_id": "step_01_explicit_period_business_overview_with_tax",
|
||||
"title": "Explicit-period business overview includes checked VAT/tax family",
|
||||
"question": "Дай бизнес-обзор ООО Альтернатива Плюс за 2020 год по данным 1С: деньги, нетто, активность, НДС-позиция, что подтверждено и что пока неизвестно. Не выдавай нетто за прибыль.",
|
||||
"expected_catalog_alignment_status": "selected_matches_top",
|
||||
"expected_catalog_chain_top_match": "business_overview",
|
||||
"expected_catalog_selected_matches_top": true,
|
||||
"allowed_reply_types": [
|
||||
"partial_coverage",
|
||||
"factual_with_explanation"
|
||||
],
|
||||
"required_answer_patterns_all": [
|
||||
"(?i)1с|подтвержд",
|
||||
"(?i)2020",
|
||||
"(?i)входящ|поступлен|денежн",
|
||||
"(?i)исходящ|платеж|списан",
|
||||
"(?i)ндс|vat",
|
||||
"(?i)книга продаж|продаж",
|
||||
"(?i)книга покуп|вычет|покуп",
|
||||
"(?i)нетто",
|
||||
"(?i)не прибыль|не марж|прибыль.*не подтвержд|марж.*не подтвержд"
|
||||
],
|
||||
"forbidden_answer_patterns": [
|
||||
"(?i)business_overview_route_template_v1",
|
||||
"(?i)query_movements|query_documents|primitive|planner_|runtime_|pilot_",
|
||||
"(?i)чистая прибыль|маржа составляет|это прибыль"
|
||||
],
|
||||
"criticality": "critical",
|
||||
"semantic_tags": [
|
||||
"business_overview_tax_family",
|
||||
"explicit_period_tax_position",
|
||||
"planner_catalog_alignment",
|
||||
"profit_margin_boundary"
|
||||
]
|
||||
},
|
||||
{
|
||||
"step_id": "step_02_all_time_business_overview_does_not_reuse_tax_period",
|
||||
"title": "All-time overview after explicit tax period keeps VAT unknown",
|
||||
"question": "Теперь по ООО Альтернатива Плюс за все доступное время дай бизнес-обзор в целом, но не тащи НДС за 2020 как подтвержденную общую налоговую позицию.",
|
||||
"expected_catalog_alignment_status": "selected_matches_top",
|
||||
"expected_catalog_chain_top_match": "business_overview",
|
||||
"expected_catalog_selected_matches_top": true,
|
||||
"allowed_reply_types": [
|
||||
"partial_coverage",
|
||||
"factual_with_explanation"
|
||||
],
|
||||
"required_answer_patterns_all": [
|
||||
"(?i)альтернатива|организац|компани",
|
||||
"(?i)все доступн|проверенн.*окн|активност",
|
||||
"(?i)денежн|нетто",
|
||||
"(?i)ндс|vat|налог",
|
||||
"(?i)не подтвержд|нужен отдельн|явн.*период"
|
||||
],
|
||||
"forbidden_answer_patterns": [
|
||||
"(?i)ндс-позиция за 2020",
|
||||
"(?i)книга продаж.*2020",
|
||||
"(?i)книга покуп.*2020",
|
||||
"(?i)business_overview_route_template_v1",
|
||||
"(?i)query_movements|query_documents|primitive|planner_|runtime_|pilot_"
|
||||
],
|
||||
"criticality": "critical",
|
||||
"semantic_tags": [
|
||||
"business_overview_tax_family",
|
||||
"stale_period_guard",
|
||||
"all_time_tax_boundary",
|
||||
"planner_catalog_alignment"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -0,0 +1,79 @@
|
|||
{
|
||||
"schema_version": "domain_truth_harness_spec_v1",
|
||||
"scenario_id": "address_truth_harness_phase86_business_overview_debt_position",
|
||||
"domain": "address_phase86_business_overview_debt_position",
|
||||
"title": "Phase 86 business overview debt-position fact-family replay",
|
||||
"description": "Targeted replay for Business Overview Fact-Family Expansion: explicit-period company overview may include checked receivables/payables as-of-date debt position, while all-time follow-up must not reuse the previous as-of-date debt snapshot as current or all-time debt quality.",
|
||||
"bindings": {},
|
||||
"steps": [
|
||||
{
|
||||
"step_id": "step_01_explicit_period_business_overview_with_debt_position",
|
||||
"title": "Explicit-period business overview includes checked receivables/payables debt position",
|
||||
"question": "Дай бизнес-обзор ООО Альтернатива Плюс за 2020 год по данным 1С: деньги, нетто, активность, дебиторка и кредиторка на дату, что подтверждено и что пока неизвестно. Не выдавай долговой срез за просрочку, качество долга или прибыль.",
|
||||
"expected_catalog_alignment_status": "selected_matches_top",
|
||||
"expected_catalog_chain_top_match": "business_overview",
|
||||
"expected_catalog_selected_matches_top": true,
|
||||
"allowed_reply_types": [
|
||||
"partial_coverage",
|
||||
"factual_with_explanation"
|
||||
],
|
||||
"required_answer_patterns_all": [
|
||||
"(?i)1с|подтвержд",
|
||||
"(?i)2020|2020-12-31",
|
||||
"(?i)входящ|поступлен|денежн",
|
||||
"(?i)исходящ|платеж|списан",
|
||||
"(?i)дебитор|кредитор|долгов",
|
||||
"(?i)нетто",
|
||||
"(?i)не прибыль|не марж|прибыль.*не подтвержд|марж.*не подтвержд",
|
||||
"(?i)качество.*не подтвержд|просроч.*не подтвержд|aging|due-date|не.*просроч"
|
||||
],
|
||||
"forbidden_answer_patterns": [
|
||||
"(?i)business_overview_route_template_v1",
|
||||
"(?i)query_movements|query_documents|primitive|planner_|runtime_|pilot_",
|
||||
"(?i)просроченная дебиторка составляет|качество долга хорошее|кредитный риск низкий",
|
||||
"(?i)чистая прибыль|маржа составляет|это прибыль"
|
||||
],
|
||||
"criticality": "critical",
|
||||
"semantic_tags": [
|
||||
"business_overview_debt_position_family",
|
||||
"explicit_period_as_of_debt_snapshot",
|
||||
"debt_quality_boundary",
|
||||
"profit_margin_boundary",
|
||||
"planner_catalog_alignment"
|
||||
]
|
||||
},
|
||||
{
|
||||
"step_id": "step_02_all_time_business_overview_does_not_reuse_debt_snapshot",
|
||||
"title": "All-time overview after explicit debt position keeps debt snapshot unknown",
|
||||
"question": "Теперь по ООО Альтернатива Плюс за все доступное время дай бизнес-обзор в целом, но не тащи долговой срез на 2020-12-31 как текущую или общую долговую позицию.",
|
||||
"expected_catalog_alignment_status": "selected_matches_top",
|
||||
"expected_catalog_chain_top_match": "business_overview",
|
||||
"expected_catalog_selected_matches_top": true,
|
||||
"allowed_reply_types": [
|
||||
"partial_coverage",
|
||||
"factual_with_explanation"
|
||||
],
|
||||
"required_answer_patterns_all": [
|
||||
"(?i)альтернатива|организац|компани",
|
||||
"(?i)все доступн|проверенн.*окн|активност",
|
||||
"(?i)денежн|нетто",
|
||||
"(?i)дебитор|кредитор|долг",
|
||||
"(?i)не подтвержд|нужен отдельн|явн.*дат|as-of|дату"
|
||||
],
|
||||
"forbidden_answer_patterns": [
|
||||
"(?i)долгов(ой|ая).*2020-12-31",
|
||||
"(?i)дебиторка.*2020-12-31",
|
||||
"(?i)кредиторка.*2020-12-31",
|
||||
"(?i)business_overview_route_template_v1",
|
||||
"(?i)query_movements|query_documents|primitive|planner_|runtime_|pilot_"
|
||||
],
|
||||
"criticality": "critical",
|
||||
"semantic_tags": [
|
||||
"business_overview_debt_position_family",
|
||||
"stale_as_of_debt_snapshot_guard",
|
||||
"all_time_debt_boundary",
|
||||
"planner_catalog_alignment"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -0,0 +1,76 @@
|
|||
{
|
||||
"schema_version": "domain_truth_harness_spec_v1",
|
||||
"scenario_id": "address_truth_harness_phase87_business_overview_inventory_position",
|
||||
"domain": "address_phase87_business_overview_inventory_position",
|
||||
"title": "Phase 87 business overview inventory-position fact-family replay",
|
||||
"description": "Targeted replay for Business Overview Fact-Family Expansion: explicit-date company overview may include checked inventory on-hand and purchase-date aging signals, while all-time follow-up must not reuse the previous inventory as-of-date snapshot as current or all-time warehouse health.",
|
||||
"bindings": {},
|
||||
"steps": [
|
||||
{
|
||||
"step_id": "step_01_explicit_date_business_overview_with_inventory_position",
|
||||
"title": "Explicit-date business overview includes checked inventory position",
|
||||
"question": "Дай бизнес-обзор ООО Альтернатива Плюс на 2026-04-16 по данным 1С: деньги, активность, складской остаток и товарный срез на дату, что подтверждено и что пока неизвестно. Не выдавай складской остаток за ликвидность, оборачиваемость, прибыль или полноценное здоровье бизнеса.",
|
||||
"expected_catalog_alignment_status": "selected_matches_top",
|
||||
"expected_catalog_chain_top_match": "business_overview",
|
||||
"expected_catalog_selected_matches_top": true,
|
||||
"allowed_reply_types": [
|
||||
"partial_coverage",
|
||||
"factual_with_explanation"
|
||||
],
|
||||
"required_answer_patterns_all": [
|
||||
"(?i)1с|подтвержд",
|
||||
"(?i)2026-04-16|16\\.04\\.2026",
|
||||
"(?i)склад|остат|товар",
|
||||
"(?i)руб|сумм|стоимост",
|
||||
"(?i)не прибыль|не марж|прибыль.*не подтвержд|марж.*не подтвержд",
|
||||
"(?i)оборачиваемость|ликвидность|не подтвержд|отдельн"
|
||||
],
|
||||
"forbidden_answer_patterns": [
|
||||
"(?i)business_overview_route_template_v1",
|
||||
"(?i)query_movements|query_documents|primitive|planner_|runtime_|pilot_",
|
||||
"(?i)склад.*ликвидн.*хорош|оборачиваемость.*хорош|залежалость.*низк",
|
||||
"(?i)чистая прибыль|маржа составляет|это прибыль"
|
||||
],
|
||||
"criticality": "critical",
|
||||
"semantic_tags": [
|
||||
"business_overview_inventory_position_family",
|
||||
"explicit_date_inventory_snapshot",
|
||||
"inventory_liquidity_boundary",
|
||||
"profit_margin_boundary",
|
||||
"planner_catalog_alignment"
|
||||
]
|
||||
},
|
||||
{
|
||||
"step_id": "step_02_all_time_business_overview_does_not_reuse_inventory_snapshot",
|
||||
"title": "All-time overview after explicit inventory position keeps inventory snapshot unknown",
|
||||
"question": "Теперь по ООО Альтернатива Плюс за все доступное время дай бизнес-обзор в целом, но не тащи складской срез на 2026-04-16 как текущий или общий all-time склад.",
|
||||
"expected_catalog_alignment_status": "selected_matches_top",
|
||||
"expected_catalog_chain_top_match": "business_overview",
|
||||
"expected_catalog_selected_matches_top": true,
|
||||
"allowed_reply_types": [
|
||||
"partial_coverage",
|
||||
"factual_with_explanation"
|
||||
],
|
||||
"required_answer_patterns_all": [
|
||||
"(?i)альтернатива|организац|компани",
|
||||
"(?i)все доступн|проверенн.*окн|активност",
|
||||
"(?i)денежн|нетто",
|
||||
"(?i)склад|остат|inventory",
|
||||
"(?i)не подтвержд|нужен отдельн|явн.*дат|дату"
|
||||
],
|
||||
"forbidden_answer_patterns": [
|
||||
"(?i)склад(ской|ская|ские|).*2026-04-16",
|
||||
"(?i)остат(ок|ки).*2026-04-16",
|
||||
"(?i)business_overview_route_template_v1",
|
||||
"(?i)query_movements|query_documents|primitive|planner_|runtime_|pilot_"
|
||||
],
|
||||
"criticality": "critical",
|
||||
"semantic_tags": [
|
||||
"business_overview_inventory_position_family",
|
||||
"stale_as_of_inventory_snapshot_guard",
|
||||
"all_time_inventory_boundary",
|
||||
"planner_catalog_alignment"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -0,0 +1,80 @@
|
|||
{
|
||||
"schema_version": "domain_truth_harness_spec_v1",
|
||||
"scenario_id": "address_truth_harness_phase88_business_overview_open_settlement_quality",
|
||||
"domain": "address_phase88_business_overview_open_settlement_quality",
|
||||
"title": "Phase 88 business overview open-settlement quality replay",
|
||||
"description": "Targeted replay for Business Overview Fact-Family Expansion: explicit-date company overview may include checked open-contract settlement concentration, while due-date aging/overdue debt must remain unclaimed and all-time follow-up must not reuse the prior as-of-date debt/open-contract snapshot.",
|
||||
"bindings": {},
|
||||
"steps": [
|
||||
{
|
||||
"step_id": "step_01_explicit_date_business_overview_with_open_settlement_quality",
|
||||
"title": "Explicit-date business overview includes checked open-settlement quality boundary",
|
||||
"question": "Дай бизнес-обзор ООО Альтернатива Плюс за 2020 год по данным 1С: деньги, активность, НДС, дебиторка/кредиторка и качество открытых расчетов по договорам на дату. Отдельно скажи, что подтверждено, а что нельзя считать просрочкой или due-date aging без сроков оплаты.",
|
||||
"expected_catalog_alignment_status": "selected_matches_top",
|
||||
"expected_catalog_chain_top_match": "business_overview",
|
||||
"expected_catalog_selected_matches_top": true,
|
||||
"allowed_reply_types": [
|
||||
"partial_coverage",
|
||||
"factual_with_explanation"
|
||||
],
|
||||
"required_answer_patterns_all": [
|
||||
"(?i)1с|подтвержд",
|
||||
"(?i)2020|2020-12-31|31\\.12\\.2020",
|
||||
"(?i)дебитор|кредитор|долгов",
|
||||
"(?i)открыт.*расчет|открыт.*договор|договорн",
|
||||
"(?i)руб|сумм|остат",
|
||||
"(?i)due-date|срок|просроч|не подтвержд|нельзя считать"
|
||||
],
|
||||
"forbidden_answer_patterns": [
|
||||
"(?i)business_overview_route_template_v1",
|
||||
"(?i)query_movements|query_documents|primitive|planner_|runtime_|pilot_",
|
||||
"(?i)подтвержденн(?:ая|ую|ый|ые)?\\s+просроч",
|
||||
"(?i)просрочк[а-я\\s]{0,20}(?:есть|имеется|найдена|составляет)",
|
||||
"(?i)долг.*плох|кредитн.*риск.*высок",
|
||||
"(?i)чистая прибыль|маржа составляет|это прибыль"
|
||||
],
|
||||
"criticality": "critical",
|
||||
"semantic_tags": [
|
||||
"business_overview_open_settlement_quality_family",
|
||||
"explicit_date_debt_snapshot",
|
||||
"open_contract_concentration_boundary",
|
||||
"due_date_aging_boundary",
|
||||
"planner_catalog_alignment"
|
||||
]
|
||||
},
|
||||
{
|
||||
"step_id": "step_02_all_time_business_overview_does_not_reuse_open_settlement_snapshot",
|
||||
"title": "All-time overview after open-settlement quality keeps prior debt snapshot bounded",
|
||||
"question": "Теперь по ООО Альтернатива Плюс за все доступное время дай общий бизнес-обзор, но не тащи срез открытых договоров на 2020-12-31 как текущую просрочку или all-time качество долга.",
|
||||
"expected_catalog_alignment_status": "selected_matches_top",
|
||||
"expected_catalog_chain_top_match": "business_overview",
|
||||
"expected_catalog_selected_matches_top": true,
|
||||
"allowed_reply_types": [
|
||||
"partial_coverage",
|
||||
"factual_with_explanation"
|
||||
],
|
||||
"required_answer_patterns_all": [
|
||||
"(?i)альтернатива|организац|компани",
|
||||
"(?i)все доступн|проверенн.*окн|активност",
|
||||
"(?i)денежн|нетто",
|
||||
"(?i)долг|дебитор|кредитор|открыт.*расчет",
|
||||
"(?i)не подтвержд|нужен отдельн|явн.*дат|дату"
|
||||
],
|
||||
"forbidden_answer_patterns": [
|
||||
"(?i)2020-12-31",
|
||||
"(?i)31\\.12\\.2020",
|
||||
"(?i)подтвержденн(?:ая|ую|ый|ые)?\\s+просроч",
|
||||
"(?i)просрочк[а-я\\s]{0,20}(?:есть|имеется|найдена|составляет)",
|
||||
"(?i)business_overview_route_template_v1",
|
||||
"(?i)query_movements|query_documents|primitive|planner_|runtime_|pilot_"
|
||||
],
|
||||
"criticality": "critical",
|
||||
"semantic_tags": [
|
||||
"business_overview_open_settlement_quality_family",
|
||||
"stale_as_of_debt_snapshot_guard",
|
||||
"all_time_debt_quality_boundary",
|
||||
"planner_catalog_alignment"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -117,6 +117,9 @@ function isValueFlowPilot(pilot) {
|
|||
pilot.pilot_scope === "counterparty_supplier_payout_query_movements_v1" ||
|
||||
pilot.pilot_scope === "counterparty_bidirectional_value_flow_query_movements_v1");
|
||||
}
|
||||
function isBusinessOverviewPilot(pilot) {
|
||||
return pilot.pilot_scope === "business_overview_route_template_v1";
|
||||
}
|
||||
function isDocumentPilot(pilot) {
|
||||
return pilot.pilot_scope === "counterparty_document_evidence_query_documents_v1";
|
||||
}
|
||||
|
|
@ -333,6 +336,42 @@ function headlineFor(mode, pilot) {
|
|||
}
|
||||
return "Инвентарный route-template уже выбран, но live-исполнение этого generic MCP контура еще не подключено; складской/товарный факт не подтвержден.";
|
||||
}
|
||||
if (isBusinessOverviewPilot(pilot) && pilot.derived_business_overview && mode === "confirmed_with_bounded_inference") {
|
||||
const overview = pilot.derived_business_overview;
|
||||
const families = [];
|
||||
if (overview.incoming_customer_revenue.rows_with_amount > 0 ||
|
||||
overview.outgoing_supplier_payout.rows_with_amount > 0) {
|
||||
families.push("денежный поток");
|
||||
}
|
||||
if (overview.activity_period) {
|
||||
families.push("активность");
|
||||
}
|
||||
if (overview.tax_position) {
|
||||
families.push("НДС-позиция");
|
||||
}
|
||||
if (overview.debt_position) {
|
||||
families.push("долговой срез на дату");
|
||||
}
|
||||
if (overview.debt_open_settlement_quality) {
|
||||
families.push("качество открытых расчетов");
|
||||
}
|
||||
if (overview.inventory_position) {
|
||||
families.push("складской срез на дату");
|
||||
}
|
||||
const unknownFamilies = ["прибыль/маржа"];
|
||||
if (!overview.tax_position) {
|
||||
unknownFamilies.push("НДС");
|
||||
}
|
||||
if (!overview.debt_position) {
|
||||
unknownFamilies.push("долговой срез");
|
||||
}
|
||||
unknownFamilies.push(overview.debt_open_settlement_quality ? "due-date просрочка" : "качество открытых расчетов");
|
||||
unknownFamilies.push(overview.inventory_position ? "полноценная складская ликвидность" : "склад");
|
||||
return `По данным 1С собран ограниченный бизнес-обзор: ${families.join(", ")} подтверждены найденными строками; ${unknownFamilies.join(", ")} остаются отдельными непроверенными областями.`;
|
||||
}
|
||||
if (isBusinessOverviewPilot(pilot) && mode === "checked_sources_only") {
|
||||
return "Бизнес-обзор был запущен, но подтвержденные денежные или activity-сигналы в найденных строках не получены.";
|
||||
}
|
||||
if (isEntityResolutionPilot(pilot) && mode === "needs_clarification") {
|
||||
return "По каталогу 1С нашлось несколько похожих контрагентов, и без уточнения нельзя честно выбрать правильную сущность.";
|
||||
}
|
||||
|
|
@ -469,6 +508,9 @@ function nextStepFor(mode, pilot) {
|
|||
}
|
||||
return "Следующий шаг - связать inventory route-template с exact inventory runtime и затем проверить live-прогоном.";
|
||||
}
|
||||
if (mode === "confirmed_with_bounded_inference" && isBusinessOverviewPilot(pilot)) {
|
||||
return "Если нужен уже управленческий вывод, следующим шагом стоит отдельно проверить прибыль/маржу, долги, НДС и складскую ликвидность, а затем собрать полный бизнес-аудит.";
|
||||
}
|
||||
if (mode === "confirmed_with_bounded_inference" && pilot.derived_metadata_surface) {
|
||||
const surface = pilot.derived_metadata_surface;
|
||||
if (surface.ambiguity_detected && surface.ambiguity_entity_sets.length > 0) {
|
||||
|
|
@ -500,6 +542,14 @@ function buildMustNotClaim(pilot) {
|
|||
claims.push("Do not claim full all-time turnover unless the checked period and coverage prove it.");
|
||||
claims.push("Do not present a derived sum as a legal/accounting final total outside the checked 1C rows.");
|
||||
}
|
||||
if (isBusinessOverviewPilot(pilot)) {
|
||||
claims.push("Do not present business overview cash-flow spread as profit or margin.");
|
||||
claims.push("Do not claim debt quality, VAT position, inventory health, or company health unless those contours were separately checked.");
|
||||
claims.push("Do not present a debt-position snapshot as debt aging, overdue debt, or credit-quality analysis.");
|
||||
claims.push("Do not present open-settlement concentration as contractual due-date aging or confirmed overdue debt.");
|
||||
claims.push("Do not present an inventory snapshot or purchase-date aging signal as turnover, obsolescence, liquidation value, or full inventory health.");
|
||||
claims.push("Do not expose business_overview_route_template_v1 or MCP primitive names in the user answer.");
|
||||
}
|
||||
if (pilot.derived_ranked_value_flow) {
|
||||
claims.push("Do not present a bounded ranking as a complete all-time ranking outside the checked period and organization.");
|
||||
claims.push("Do not imply the top-ranked counterparty is globally final when probe-limit or scope boundaries still exist.");
|
||||
|
|
@ -760,6 +810,95 @@ function derivedBidirectionalValueFlowMonthlyLines(pilot) {
|
|||
}
|
||||
return flow.monthly_breakdown.map((bucket) => `Помесячно: ${monthLabelRu(bucket.month_bucket)} — получили ${bucket.incoming_total_amount_human_ru}, заплатили ${bucket.outgoing_total_amount_human_ru}, ${netLabelRu(bucket.net_direction)} ${bucket.net_amount_human_ru}`);
|
||||
}
|
||||
function businessOverviewNetDirectionRu(direction) {
|
||||
if (direction === "net_incoming") {
|
||||
return "операционный денежный поток в проверенном срезе больше входящий, чем исходящий";
|
||||
}
|
||||
if (direction === "net_outgoing") {
|
||||
return "операционный денежный поток в проверенном срезе больше исходящий, чем входящий";
|
||||
}
|
||||
return "входящий и исходящий денежный поток в проверенном срезе примерно сбалансированы";
|
||||
}
|
||||
function derivedBusinessOverviewConfirmedLines(pilot) {
|
||||
const overview = pilot.derived_business_overview;
|
||||
if (!overview) {
|
||||
return [];
|
||||
}
|
||||
const organization = overview.organization_scope ? ` по организации ${overview.organization_scope}` : "";
|
||||
const period = overview.period_scope ? ` за ${overview.period_scope}` : " за все доступное проверенное окно";
|
||||
const lines = [];
|
||||
if (overview.incoming_customer_revenue.rows_with_amount > 0) {
|
||||
lines.push(`Входящие поступления${organization}${period}: ${overview.incoming_customer_revenue.total_amount_human_ru} по ${overview.incoming_customer_revenue.rows_with_amount} строкам с суммой.`);
|
||||
}
|
||||
if (overview.outgoing_supplier_payout.rows_with_amount > 0) {
|
||||
lines.push(`Исходящие платежи/списания${organization}${period}: ${overview.outgoing_supplier_payout.total_amount_human_ru} по ${overview.outgoing_supplier_payout.rows_with_amount} строкам с суммой.`);
|
||||
}
|
||||
const leader = overview.top_customers[0];
|
||||
if (leader) {
|
||||
lines.push(`Самый крупный подтвержденный клиент в проверенном срезе: ${leader.axis_value} — ${leader.total_amount_human_ru}.`);
|
||||
}
|
||||
if (overview.activity_period) {
|
||||
lines.push(`Окно подтвержденной активности в 1С: ${overview.activity_period.first_activity_date} — ${overview.activity_period.latest_activity_date}; ориентировочно ${overview.activity_period.duration_human_ru}.`);
|
||||
}
|
||||
if (overview.tax_position) {
|
||||
const taxDirection = overview.tax_position.net_vat_direction === "vat_to_pay"
|
||||
? "к уплате"
|
||||
: overview.tax_position.net_vat_direction === "vat_to_recover_or_offset"
|
||||
? "к вычету/зачету"
|
||||
: "сбалансирован";
|
||||
lines.push(`НДС-позиция за ${overview.tax_position.period_scope}: книга продаж ${overview.tax_position.sales_vat_amount_human_ru}, книга покупок/вычеты ${overview.tax_position.purchase_vat_amount_human_ru}, нетто ${taxDirection} ${overview.tax_position.net_vat_amount_human_ru}.`);
|
||||
}
|
||||
if (overview.debt_position) {
|
||||
const debtDirection = overview.debt_position.net_debt_position_direction === "net_receivable"
|
||||
? "в пользу дебиторки"
|
||||
: overview.debt_position.net_debt_position_direction === "net_payable"
|
||||
? "в сторону кредиторки"
|
||||
: "сбалансировано";
|
||||
lines.push(`Долговой срез на ${overview.debt_position.as_of_date}: дебиторка ${overview.debt_position.receivables.total_amount_human_ru}, кредиторка ${overview.debt_position.payables.total_amount_human_ru}, нетто ${debtDirection} ${overview.debt_position.net_debt_position_amount_human_ru}.`);
|
||||
}
|
||||
if (overview.debt_open_settlement_quality) {
|
||||
const quality = overview.debt_open_settlement_quality;
|
||||
const topContract = quality.top_contracts[0];
|
||||
const topContractText = topContract
|
||||
? ` Крупнейший открытый договор: ${topContract.contract}${topContract.counterparty ? ` / ${topContract.counterparty}` : ""} — ${topContract.total_amount_human_ru}${topContract.share_of_gross_open_amount_pct === null ? "" : ` (${topContract.share_of_gross_open_amount_pct}%)`}.`
|
||||
: "";
|
||||
lines.push(`Качество открытых расчетов на ${quality.as_of_date}: брутто открытых договорных остатков ${quality.gross_open_amount_human_ru}, договоров ${quality.unique_contracts}, контрагентов ${quality.unique_counterparties}.${topContractText}`);
|
||||
}
|
||||
if (overview.inventory_position) {
|
||||
const leader = overview.inventory_position.top_items[0];
|
||||
const leaderText = leader
|
||||
? ` Крупнейшая подтвержденная позиция: ${leader.item} — ${leader.total_amount_human_ru}.`
|
||||
: "";
|
||||
lines.push(`Складской срез на ${overview.inventory_position.as_of_date}: остаток ${overview.inventory_position.total_amount_human_ru} по ${overview.inventory_position.rows_with_amount} строкам с суммой и ${overview.inventory_position.rows_with_quantity} строкам с количеством.${leaderText}`);
|
||||
if (overview.inventory_position.aging_signal?.oldest_purchase_date) {
|
||||
const ageText = overview.inventory_position.aging_signal.max_age_days === null
|
||||
? ""
|
||||
: `, максимальный возраст сигнала ${overview.inventory_position.aging_signal.max_age_days} дн.`;
|
||||
lines.push(`Возрастной сигнал склада: самая ранняя найденная дата закупки ${overview.inventory_position.aging_signal.oldest_purchase_date}${ageText}.`);
|
||||
}
|
||||
}
|
||||
return lines;
|
||||
}
|
||||
function derivedBusinessOverviewInferenceLine(pilot) {
|
||||
const overview = pilot.derived_business_overview;
|
||||
if (!overview) {
|
||||
return null;
|
||||
}
|
||||
if (overview.incoming_customer_revenue.rows_with_amount <= 0 &&
|
||||
overview.outgoing_supplier_payout.rows_with_amount <= 0) {
|
||||
return null;
|
||||
}
|
||||
return [
|
||||
`Расчетное нетто по найденным строкам: ${overview.net_amount_human_ru}; ${businessOverviewNetDirectionRu(overview.net_direction)}.`,
|
||||
"Это нормальный операционный сигнал, но не прибыль и не маржа: для управленческого вывода нужны отдельные расходы, себестоимость, долги, налоги и склад."
|
||||
].join(" ");
|
||||
}
|
||||
function businessOverviewUnknownLines(pilot) {
|
||||
if (!pilot.derived_business_overview) {
|
||||
return userFacingUnknowns(pilot.evidence.unknown_facts);
|
||||
}
|
||||
return userFacingUnknowns(pilot.evidence.unknown_facts);
|
||||
}
|
||||
function buildAssistantMcpDiscoveryAnswerDraft(pilot) {
|
||||
const mode = modeFor(pilot);
|
||||
const reasonCodes = [...pilot.reason_codes, ...pilot.evidence.reason_codes];
|
||||
|
|
@ -770,7 +909,8 @@ function buildAssistantMcpDiscoveryAnswerDraft(pilot) {
|
|||
if (pilot.evidence.inferred_facts.length > 0) {
|
||||
pushReason(reasonCodes, "answer_contains_bounded_inference");
|
||||
}
|
||||
const derivedInferenceLine = derivedActivityInferenceLine(pilot) ??
|
||||
const derivedInferenceLine = derivedBusinessOverviewInferenceLine(pilot) ??
|
||||
derivedActivityInferenceLine(pilot) ??
|
||||
derivedMetadataInferenceLine(pilot) ??
|
||||
derivedRankedValueFlowInferenceLine(pilot) ??
|
||||
derivedEntityResolutionInferenceLine(pilot);
|
||||
|
|
@ -785,23 +925,43 @@ function buildAssistantMcpDiscoveryAnswerDraft(pilot) {
|
|||
const monthlyConfirmedLines = derivedBidirectionalValueFlowMonthlyLines(pilot).length > 0
|
||||
? derivedBidirectionalValueFlowMonthlyLines(pilot)
|
||||
: derivedValueFlowMonthlyLines(pilot);
|
||||
const businessOverviewLines = derivedBusinessOverviewConfirmedLines(pilot);
|
||||
if (monthlyConfirmedLines.length > 0) {
|
||||
pushReason(reasonCodes, "answer_contains_monthly_breakdown");
|
||||
}
|
||||
const confirmedLines = pilot.derived_ranked_value_flow && derivedValueLine
|
||||
? [derivedValueLine]
|
||||
: derivedValueLine
|
||||
? [...pilot.evidence.confirmed_facts, derivedValueLine, ...monthlyConfirmedLines]
|
||||
: derivedEntityResolutionLine
|
||||
? [...pilot.evidence.confirmed_facts, derivedEntityResolutionLine]
|
||||
: derivedMetadataLine
|
||||
? [derivedMetadataLine]
|
||||
: pilot.evidence.confirmed_facts;
|
||||
const unknownLines = pilot.derived_metadata_surface
|
||||
? pilot.derived_metadata_surface.available_fields.length > 0
|
||||
? userFacingUnknowns(pilot.evidence.unknown_facts)
|
||||
: ["Детальный список полей этих объектов этим шагом не получен."]
|
||||
: rankedValueFlowUnknownLines(pilot);
|
||||
if (businessOverviewLines.length > 0) {
|
||||
pushReason(reasonCodes, "answer_contains_business_overview");
|
||||
}
|
||||
if (pilot.derived_business_overview?.tax_position) {
|
||||
pushReason(reasonCodes, "answer_contains_business_overview_tax_position");
|
||||
}
|
||||
if (pilot.derived_business_overview?.debt_position) {
|
||||
pushReason(reasonCodes, "answer_contains_business_overview_debt_position");
|
||||
}
|
||||
if (pilot.derived_business_overview?.debt_open_settlement_quality) {
|
||||
pushReason(reasonCodes, "answer_contains_business_overview_open_settlement_quality");
|
||||
}
|
||||
if (pilot.derived_business_overview?.inventory_position) {
|
||||
pushReason(reasonCodes, "answer_contains_business_overview_inventory_position");
|
||||
}
|
||||
const confirmedLines = businessOverviewLines.length > 0
|
||||
? businessOverviewLines
|
||||
: pilot.derived_ranked_value_flow && derivedValueLine
|
||||
? [derivedValueLine]
|
||||
: derivedValueLine
|
||||
? [...pilot.evidence.confirmed_facts, derivedValueLine, ...monthlyConfirmedLines]
|
||||
: derivedEntityResolutionLine
|
||||
? [...pilot.evidence.confirmed_facts, derivedEntityResolutionLine]
|
||||
: derivedMetadataLine
|
||||
? [derivedMetadataLine]
|
||||
: pilot.evidence.confirmed_facts;
|
||||
const unknownLines = pilot.derived_business_overview
|
||||
? businessOverviewUnknownLines(pilot)
|
||||
: pilot.derived_metadata_surface
|
||||
? pilot.derived_metadata_surface.available_fields.length > 0
|
||||
? userFacingUnknowns(pilot.evidence.unknown_facts)
|
||||
: ["Детальный список полей этих объектов этим шагом не получен."]
|
||||
: rankedValueFlowUnknownLines(pilot);
|
||||
return {
|
||||
schema_version: exports.ASSISTANT_MCP_DISCOVERY_ANSWER_DRAFT_SCHEMA_VERSION,
|
||||
policy_owner: "assistantMcpDiscoveryAnswerAdapter",
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load Diff
|
|
@ -511,6 +511,9 @@ function metadataAmbiguityCollapsesToMovementLane(values) {
|
|||
function hasLifecycleSignal(text) {
|
||||
return /(?:сколько\s+лет|как\s+давно|давно\s+ли|возраст|перв(?:ая|ый)\s+актив|когда\s+начал|когда\s+появ|lifecycle|activity\s+duration|business\s+age|how\s+long)/iu.test(text);
|
||||
}
|
||||
function hasBusinessOverviewSignal(text) {
|
||||
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);
|
||||
}
|
||||
function hasValueFlowSignal(text) {
|
||||
return /(?:оборот|выручк|оплат|плат[её]ж|заплат|перечисл|списан|расход|исходящ|входящ|получ(?:ил|ено|ен)|поступил|поступлен|денежн[а-яёa-z0-9_-]*\s+поток|(?<!\p{L})заработ(?:ал|али|ало|аем|ает|ать|ано|ок)(?!\p{L})|supplier|value[-\s]?flow|turnover|revenue|payment|payout|outflow|cash\s+flow|\bearn(?:ed|ing|ings)?\b)/iu.test(text);
|
||||
}
|
||||
|
|
@ -543,13 +546,15 @@ function extractOrganizationScopeFromRawText(value) {
|
|||
if (!match?.[1]) {
|
||||
return null;
|
||||
}
|
||||
return toNonEmptyString(match[1]);
|
||||
return toNonEmptyString(match[1]
|
||||
.replace(/\s+(?:\u043f\u043e\s+\u0434\u0430\u043d\u043d\u044b\u043c\s+1\u0441|\u0432\s+\u0446\u0435\u043b\u043e\u043c|\u0437\u0430\s+(?:\d{4}-\d{2}-\d{2}|\d{4}-\d{2}|(?:19|20)\d{2})(?:\s+\u0433(?:\u043e\u0434|\.)?)?|\u043d\u0430\s+(?:\d{4}-\d{2}-\d{2}|\d{4}-\d{2}|(?:19|20)\d{2})|\u0437\u0430\s+\u0432\u0441[\u0435\u0451]\s+(?:\u0434\u043e\u0441\u0442\u0443\u043f\p{L}+\s+)?\u0432\u0440\u0435\u043c\u044f|\u0437\u0430\s+\u0432\u0435\u0441\u044c\s+(?:\u0434\u043e\u0441\u0442\u0443\u043f\p{L}+\s+)?\u043f\u0435\u0440\u0438\u043e\u0434|\u0437\u0430\s+\u0432\u0441\u044e\s+\u0438\u0441\u0442\u043e\u0440\u0438(?:\u044e|\u0438)|\u0434\u0430\u0439\s+\u0431\u0438\u0437\u043d\u0435\u0441-?\u043e\u0431\u0437\u043e\u0440|\u0431\u0438\u0437\u043d\u0435\u0441-?\u043e\u0431\u0437\u043e\u0440|\u043d\u043e\s+\u043d\u0435).*$/iu, "")
|
||||
.trim());
|
||||
}
|
||||
function hasMonthlyAggregationSignal(text) {
|
||||
return /(?:\u043f\u043e\s+\u043c\u0435\u0441\u044f\u0446\u0430\u043c|\u043f\u043e\u043c\u0435\u0441\u044f\u0447\u043d\u043e|\u0435\u0436\u0435\u043c\u0435\u0441\u044f\u0447\u043d\u043e|month\s+by\s+month|by\s+month|monthly)/iu.test(text);
|
||||
}
|
||||
function hasAllTimeScopeHint(text) {
|
||||
return /(?:\u0437\u0430\s+\u0432\u0441[\u0435\u0451]\s+\u0432\u0440\u0435\u043c\u044f|\u0437\u0430\s+\u0432\u0435\u0441\u044c\s+\u043f\u0435\u0440\u0438\u043e\u0434|\u0437\u0430\s+\u0432\u0441\u044e\s+\u0438\u0441\u0442\u043e\u0440\u0438(?:\u044e|\u0438)|\u0437\u0430\s+\u043b\u044e\u0431\u043e\u0439\s+\u043f\u0435\u0440\u0438\u043e\u0434|for\s+all\s+time|all\s+time|entire\s+period|full\s+history|any\s+period)/iu.test(text);
|
||||
return /(?:\u0437\u0430\s+\u0432\u0441[\u0435\u0451]\s+(?:\u0434\u043e\u0441\u0442\u0443\u043f\p{L}+\s+)?\u0432\u0440\u0435\u043c\u044f|\u0437\u0430\s+\u0432\u0435\u0441\u044c\s+(?:\u0434\u043e\u0441\u0442\u0443\u043f\p{L}+\s+)?\u043f\u0435\u0440\u0438\u043e\u0434|\u0437\u0430\s+\u0432\u0441\u044e\s+\u0438\u0441\u0442\u043e\u0440\u0438(?:\u044e|\u0438)|\u0437\u0430\s+\u043b\u044e\u0431\u043e\u0439\s+\u043f\u0435\u0440\u0438\u043e\u0434|for\s+all\s+time|all\s+time|entire\s+period|full\s+history|any\s+period)/iu.test(text);
|
||||
}
|
||||
function hasMetadataSignal(text) {
|
||||
if (/(?:\u043c\u0435\u0442\u0430\u0434\u0430\u043d|schema|catalog|metadata\s+surface|\u0441\u0442\u0440\u0443\u043a\u0442\u0443\u0440[\u0430\u044b]\s+1\u0441|\u0441\u0445\u0435\u043c[\u0430\u044b]\s+1\u0441)/iu.test(text)) {
|
||||
|
|
@ -742,6 +747,15 @@ function metadataScopeHintFromRawText(text) {
|
|||
function hasExplicitDateScopeLiteral(text) {
|
||||
return /(?:\b(?:19|20)\d{2}\b|\b\d{4}-\d{2}-\d{2}\b|\b\d{4}-\d{2}\b)/iu.test(text);
|
||||
}
|
||||
function stripNegatedTaxDateScopeClauses(text) {
|
||||
const dateScopeLiteral = String.raw `\b(?:\d{4}-\d{2}-\d{2}|\d{4}-\d{2}|(?:19|20)\d{2})\b`;
|
||||
const taxToken = String.raw `(?:\u043d\u0434\u0441|vat)`;
|
||||
const scopeToken = String.raw `(?:\u0437\u0430|\u043d\u0430|\u043f\u043e|for|in)`;
|
||||
const negatedVerb = String.raw `(?:\u0442\u0430\u0449\u0438(?:\u0442\u044c)?|\u0431\u0435\u0440\u0438|\u0431\u0440\u0430\u0442\u044c|\u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0439(?:\u0442\u0435)?|\u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c|\u0441\u0447\u0438\u0442\u0430\u0439(?:\u0442\u0435)?|\u0441\u0447\u0438\u0442\u0430\u0442\u044c|\u043f\u043e\u0434\u0442\u044f\u0433\u0438\u0432\u0430\u0439(?:\u0442\u0435)?|\u043f\u043e\u0434\u0442\u044f\u0433\u0438\u0432\u0430\u0442\u044c|\u043f\u0435\u0440\u0435\u043d\u043e\u0441\u0438|\u043f\u0435\u0440\u0435\u043d\u043e\u0441\u0438\u0442\u044c|pull|reuse|use|carry)`;
|
||||
const direct = new RegExp(String.raw `(?:^|[\s,;:])(?:\u043d\u0435\s+${negatedVerb}\s+${taxToken}\s+${scopeToken}\s+${dateScopeLiteral}|\u0431\u0435\u0437\s+${taxToken}\s+${scopeToken}\s+${dateScopeLiteral}|do\s+not\s+(?:${negatedVerb}\s+)?${taxToken}\s+${scopeToken}\s+${dateScopeLiteral})`, "giu");
|
||||
const reversed = new RegExp(String.raw `${taxToken}\s+${scopeToken}\s+${dateScopeLiteral}\s+(?:\u043d\u0435\s+${negatedVerb}|do\s+not\s+(?:${negatedVerb})?)`, "giu");
|
||||
return text.replace(direct, " ").replace(reversed, " ");
|
||||
}
|
||||
function collectDateScopeFromRawText(text) {
|
||||
const isoDate = text.match(/\b(\d{4}-\d{2}-\d{2})\b/u);
|
||||
if (isoDate?.[1]) {
|
||||
|
|
@ -768,6 +782,9 @@ function isImplicitCurrentDateScope(value) {
|
|||
}
|
||||
function semanticNeedFor(input) {
|
||||
const combined = compactLower(`${input.domain ?? ""} ${input.action ?? ""} ${input.unsupported ?? ""}`);
|
||||
if (/(?:broad_business_evaluation|broad_evaluation|business_summary|business_overview|company analysis|business audit)/iu.test(combined)) {
|
||||
return "business overview evidence with bounded analyst interpretation";
|
||||
}
|
||||
if (input.metadataSignal || /(?:metadata|schema|catalog|inspect_(?:catalog|documents|registers|fields))/iu.test(combined)) {
|
||||
return "1C metadata evidence";
|
||||
}
|
||||
|
|
@ -841,9 +858,11 @@ function buildAssistantMcpDiscoveryTurnInput(input) {
|
|||
const rawEntitySourceText = repairedUserText ?? rawUserText ?? repairedEffectiveText ?? rawEffectiveText ?? rawSignalSourceText;
|
||||
const rawText = compactLower(rawSignalSourceText);
|
||||
const rawReferentialDocumentExclusionSignal = hasReferentialDocumentExclusionFollowupSignal(repairedUserText ?? rawUserText ?? "");
|
||||
const rawLifecycleSignal = hasLifecycleSignal(rawText);
|
||||
const rawBidirectionalValueFlowSignal = !rawLifecycleSignal && hasBidirectionalValueFlowSignal(rawText);
|
||||
const rawValueFlowSignal = !rawLifecycleSignal &&
|
||||
const rawBusinessOverviewSignal = hasBusinessOverviewSignal(rawText);
|
||||
const rawLifecycleSignal = !rawBusinessOverviewSignal && hasLifecycleSignal(rawText);
|
||||
const rawBidirectionalValueFlowSignal = !rawBusinessOverviewSignal && !rawLifecycleSignal && hasBidirectionalValueFlowSignal(rawText);
|
||||
const rawValueFlowSignal = !rawBusinessOverviewSignal &&
|
||||
!rawLifecycleSignal &&
|
||||
(hasValueFlowSignal(rawText) || hasValueRankingSignal(rawText) || rawBidirectionalValueFlowSignal);
|
||||
const rawMetadataSignal = !rawLifecycleSignal &&
|
||||
!rawValueFlowSignal &&
|
||||
|
|
@ -854,9 +873,13 @@ function buildAssistantMcpDiscoveryTurnInput(input) {
|
|||
const rawValueFlowAggregateQuestionSignal = rawValueFlowSignal && hasValueFlowAggregateQuestionSignal(rawText);
|
||||
const monthlyAggregationSignal = hasMonthlyAggregationSignal(rawText);
|
||||
const rawAllTimeScopeSignal = hasAllTimeScopeHint(rawText);
|
||||
const explicitDateScopeLiteralDetected = hasExplicitDateScopeLiteral(rawText);
|
||||
const dateScopeSignalText = stripNegatedTaxDateScopeClauses(rawText);
|
||||
const negatedTaxDateScopeOnlySignal = dateScopeSignalText !== rawText &&
|
||||
hasExplicitDateScopeLiteral(rawText) &&
|
||||
!hasExplicitDateScopeLiteral(dateScopeSignalText);
|
||||
const explicitDateScopeLiteralDetected = hasExplicitDateScopeLiteral(dateScopeSignalText);
|
||||
const relativeCurrentDateHintDetected = hasRelativeCurrentDateHint(rawText);
|
||||
const rawDateScope = collectDateScopeFromRawText(rawText);
|
||||
const rawDateScope = collectDateScopeFromRawText(dateScopeSignalText);
|
||||
const rawMetadataScopeHint = rawMetadataSignal ? metadataScopeHintFromRawText(rawText) : null;
|
||||
const rawEntityCandidate = rawEntityResolutionSignal ? rawEntityResolutionCandidate(rawEntitySourceText) : null;
|
||||
const entityResolutionClarificationCandidate = followupSeed.pilotScope === "entity_resolution_search_v1" &&
|
||||
|
|
@ -870,6 +893,11 @@ function buildAssistantMcpDiscoveryTurnInput(input) {
|
|||
const rawAggregationAxis = toNonEmptyString(assistantTurnMeaning?.asked_aggregation_axis);
|
||||
const unsupported = toNonEmptyString(assistantTurnMeaning?.unsupported_but_understood_family);
|
||||
const broadBusinessEvaluationUnsupported = unsupported === "broad_business_evaluation";
|
||||
const businessOverviewSignal = rawBusinessOverviewSignal ||
|
||||
broadBusinessEvaluationUnsupported ||
|
||||
rawDomain === "business_summary" ||
|
||||
rawDomain === "business_overview" ||
|
||||
rawAction === "broad_evaluation";
|
||||
const explicitIntentCandidate = toNonEmptyString(assistantTurnMeaning?.explicit_intent_candidate);
|
||||
const currentTurnDocumentLaneSignal = rawAction === "list_documents";
|
||||
const currentTurnMovementLaneSignal = rawAction === "list_movements";
|
||||
|
|
@ -1182,10 +1210,12 @@ function buildAssistantMcpDiscoveryTurnInput(input) {
|
|||
: followupDiscoverySeedApplicable || effectiveMetadataFollowupSeedApplicable
|
||||
? followupSeed.unsupported
|
||||
: null;
|
||||
const lifecycleSignal = rawLifecycleSignal || seededDomain === "counterparty_lifecycle";
|
||||
const bidirectionalValueFlowSignal = !lifecycleSignal &&
|
||||
const lifecycleSignal = !businessOverviewSignal && (rawLifecycleSignal || seededDomain === "counterparty_lifecycle");
|
||||
const bidirectionalValueFlowSignal = !businessOverviewSignal &&
|
||||
!lifecycleSignal &&
|
||||
(rawBidirectionalValueFlowSignal || seededAction === "net_value_flow");
|
||||
const valueFlowSignal = !lifecycleSignal &&
|
||||
const valueFlowSignal = !businessOverviewSignal &&
|
||||
!lifecycleSignal &&
|
||||
!metadataGroundedMovementLaneApplicable &&
|
||||
(rawValueFlowSignal || seededDomain === "counterparty_value");
|
||||
const payoutSignal = valueFlowSignal &&
|
||||
|
|
@ -1194,9 +1224,9 @@ function buildAssistantMcpDiscoveryTurnInput(input) {
|
|||
const semanticDataNeed = metadataAmbiguityLaneClarificationApplicable
|
||||
? "metadata lane clarification"
|
||||
: semanticNeedFor({
|
||||
domain: rawDomain ?? seededDomain,
|
||||
action: rawAction ?? seededAction,
|
||||
unsupported: broadBusinessEvaluationUnsupported ? seededUnsupported : unsupported ?? seededUnsupported,
|
||||
domain: businessOverviewSignal ? "business_overview" : rawDomain ?? seededDomain,
|
||||
action: businessOverviewSignal ? "broad_evaluation" : rawAction ?? seededAction,
|
||||
unsupported: businessOverviewSignal ? "broad_business_evaluation" : unsupported ?? seededUnsupported,
|
||||
lifecycleSignal,
|
||||
valueFlowSignal,
|
||||
metadataSignal: rawMetadataSignal || effectiveMetadataFollowupSeedApplicable,
|
||||
|
|
@ -1315,23 +1345,37 @@ function buildAssistantMcpDiscoveryTurnInput(input) {
|
|||
}
|
||||
}
|
||||
const clarificationLoopStillNeedsPeriod = Boolean(followupSeed.loopStatus === "awaiting_clarification" && followupSeed.loopPendingAxes.includes("period"));
|
||||
const businessOverviewRawWithoutDateScope = Boolean(businessOverviewSignal &&
|
||||
!rawAllTimeScopeSignal &&
|
||||
!explicitDateScopeLiteralDetected &&
|
||||
!rawDateScope &&
|
||||
!relativeCurrentDateHintDetected);
|
||||
const predecomposeDateScopeCountsAsCurrentTurnPeriod = Boolean(predecomposeDateScope &&
|
||||
!isImplicitCurrentDateScope(predecomposeDateScope) &&
|
||||
!businessOverviewRawWithoutDateScope);
|
||||
const currentTurnCarriesExplicitPeriod = Boolean(explicitDateScopeLiteralDetected ||
|
||||
rawDateScope ||
|
||||
relativeCurrentDateHintDetected ||
|
||||
(predecomposeDateScope && !isImplicitCurrentDateScope(predecomposeDateScope)));
|
||||
predecomposeDateScopeCountsAsCurrentTurnPeriod);
|
||||
const suppressImplicitCurrentDateScope = Boolean(!currentTurnCarriesExplicitPeriod &&
|
||||
(clarificationLoopStillNeedsPeriod ||
|
||||
businessOverviewSignal ||
|
||||
openScopeValueFlowWithoutResolvedCounterparty ||
|
||||
(valueFlowOrganizationStaysScope && (Boolean(followupSeed.rankingNeed) || bidirectionalValueFlowSignal))));
|
||||
const suppressNegatedTaxOnlyDateScope = Boolean(businessOverviewSignal && negatedTaxDateScopeOnlySignal);
|
||||
const normalizedPredecomposeDateScope = (rawEntitySearchOverridesStaleScope && !currentTurnCarriesExplicitPeriod) ||
|
||||
suppressNegatedTaxOnlyDateScope ||
|
||||
businessOverviewRawWithoutDateScope ||
|
||||
(suppressImplicitCurrentDateScope && isImplicitCurrentDateScope(predecomposeDateScope))
|
||||
? null
|
||||
: predecomposeDateScope;
|
||||
const normalizedAssistantTurnMeaningDateScope = rawEntitySearchOverridesStaleScope ||
|
||||
suppressNegatedTaxOnlyDateScope ||
|
||||
(suppressImplicitCurrentDateScope && isImplicitCurrentDateScope(assistantTurnMeaningDateScope))
|
||||
? null
|
||||
: assistantTurnMeaningDateScope;
|
||||
const normalizedFollowupDateScope = rawEntitySearchOverridesStaleScope ||
|
||||
suppressNegatedTaxOnlyDateScope ||
|
||||
(suppressImplicitCurrentDateScope && isImplicitCurrentDateScope(followupSeed.dateScope))
|
||||
? null
|
||||
: followupSeed.dateScope;
|
||||
|
|
@ -1348,41 +1392,45 @@ function buildAssistantMcpDiscoveryTurnInput(input) {
|
|||
normalizedFollowupDateScope);
|
||||
const clarificationLoopSeedApplied = Boolean(followupSeed.loopStatus === "awaiting_clarification" && followupSeed.loopSelectedChainId);
|
||||
const turnMeaning = {
|
||||
asked_domain_family: lifecycleSignal
|
||||
? "counterparty_lifecycle"
|
||||
: valueFlowSignal
|
||||
? "counterparty_value"
|
||||
: metadataGroundedMovementLaneApplicable
|
||||
? "movements"
|
||||
: metadataGroundedDocumentLaneApplicable
|
||||
? "documents"
|
||||
: entityResolutionSignal
|
||||
? "entity_resolution"
|
||||
: rawMetadataSignal || effectiveMetadataFollowupSeedApplicable
|
||||
? "metadata"
|
||||
: rawDomain ?? seededDomain,
|
||||
asked_action_family: lifecycleSignal
|
||||
? "activity_duration"
|
||||
: valueFlowSignal
|
||||
? bidirectionalValueFlowSignal
|
||||
? "net_value_flow"
|
||||
: payoutSignal
|
||||
? "payout"
|
||||
: rawAction ?? seededAction ?? "turnover"
|
||||
: metadataGroundedMovementLaneApplicable
|
||||
? "list_movements"
|
||||
: metadataGroundedDocumentLaneApplicable
|
||||
? "list_documents"
|
||||
: entityResolutionSignal
|
||||
? "search_business_entity"
|
||||
: rawMetadataSignal || effectiveMetadataFollowupSeedApplicable
|
||||
? metadataActionFromRawText(rawText) ?? seededAction
|
||||
: rawAction ?? seededAction,
|
||||
asked_domain_family: businessOverviewSignal
|
||||
? "business_overview"
|
||||
: lifecycleSignal
|
||||
? "counterparty_lifecycle"
|
||||
: valueFlowSignal
|
||||
? "counterparty_value"
|
||||
: metadataGroundedMovementLaneApplicable
|
||||
? "movements"
|
||||
: metadataGroundedDocumentLaneApplicable
|
||||
? "documents"
|
||||
: entityResolutionSignal
|
||||
? "entity_resolution"
|
||||
: rawMetadataSignal || effectiveMetadataFollowupSeedApplicable
|
||||
? "metadata"
|
||||
: rawDomain ?? seededDomain,
|
||||
asked_action_family: businessOverviewSignal
|
||||
? "broad_evaluation"
|
||||
: lifecycleSignal
|
||||
? "activity_duration"
|
||||
: valueFlowSignal
|
||||
? bidirectionalValueFlowSignal
|
||||
? "net_value_flow"
|
||||
: payoutSignal
|
||||
? "payout"
|
||||
: rawAction ?? seededAction ?? "turnover"
|
||||
: metadataGroundedMovementLaneApplicable
|
||||
? "list_movements"
|
||||
: metadataGroundedDocumentLaneApplicable
|
||||
? "list_documents"
|
||||
: entityResolutionSignal
|
||||
? "search_business_entity"
|
||||
: rawMetadataSignal || effectiveMetadataFollowupSeedApplicable
|
||||
? metadataActionFromRawText(rawText) ?? seededAction
|
||||
: rawAction ?? seededAction,
|
||||
asked_aggregation_axis: monthlyAggregationSignal ? "month" : rawAggregationAxis,
|
||||
seeded_ranking_need: valueFlowSignal && followupSeed.rankingNeed && !rawEntitySearchOverridesStaleScope
|
||||
? followupSeed.rankingNeed
|
||||
: undefined,
|
||||
explicit_entity_candidates: entityCandidates,
|
||||
explicit_entity_candidates: businessOverviewSignal ? [] : entityCandidates,
|
||||
metadata_ambiguity_entity_sets: metadataAmbiguityLaneClarificationApplicable && followupSeed.metadataAmbiguityEntitySets.length > 0
|
||||
? followupSeed.metadataAmbiguityEntitySets
|
||||
: undefined,
|
||||
|
|
@ -1390,29 +1438,32 @@ function buildAssistantMcpDiscoveryTurnInput(input) {
|
|||
explicit_organization_scope: explicitOrganizationScope,
|
||||
explicit_date_scope: explicitDateScope,
|
||||
subject_resolution_optional: metadataScopedLaneWithoutSubject || undefined,
|
||||
unsupported_but_understood_family: unsupported ??
|
||||
(lifecycleSignal
|
||||
? "counterparty_lifecycle"
|
||||
: valueFlowSignal
|
||||
? bidirectionalValueFlowSignal
|
||||
? "counterparty_bidirectional_value_flow_or_netting"
|
||||
: payoutSignal
|
||||
? "counterparty_payouts_or_outflow"
|
||||
: seededUnsupported ?? "counterparty_value_or_turnover"
|
||||
: metadataGroundedMovementLaneApplicable
|
||||
? "movement_evidence"
|
||||
: metadataGroundedDocumentLaneApplicable
|
||||
? "document_evidence"
|
||||
: metadataAmbiguityLaneClarificationApplicable
|
||||
? "metadata_lane_choice_clarification"
|
||||
: entityResolutionSignal
|
||||
? "entity_resolution"
|
||||
: rawMetadataSignal || effectiveMetadataFollowupSeedApplicable
|
||||
? "1c_metadata_surface"
|
||||
: followupDiscoverySeedApplicable
|
||||
? seededUnsupported
|
||||
: null),
|
||||
unsupported_but_understood_family: businessOverviewSignal
|
||||
? "broad_business_evaluation"
|
||||
: unsupported ??
|
||||
(lifecycleSignal
|
||||
? "counterparty_lifecycle"
|
||||
: valueFlowSignal
|
||||
? bidirectionalValueFlowSignal
|
||||
? "counterparty_bidirectional_value_flow_or_netting"
|
||||
: payoutSignal
|
||||
? "counterparty_payouts_or_outflow"
|
||||
: seededUnsupported ?? "counterparty_value_or_turnover"
|
||||
: metadataGroundedMovementLaneApplicable
|
||||
? "movement_evidence"
|
||||
: metadataGroundedDocumentLaneApplicable
|
||||
? "document_evidence"
|
||||
: metadataAmbiguityLaneClarificationApplicable
|
||||
? "metadata_lane_choice_clarification"
|
||||
: entityResolutionSignal
|
||||
? "entity_resolution"
|
||||
: rawMetadataSignal || effectiveMetadataFollowupSeedApplicable
|
||||
? "1c_metadata_surface"
|
||||
: followupDiscoverySeedApplicable
|
||||
? seededUnsupported
|
||||
: null),
|
||||
stale_replay_forbidden: Boolean(assistantTurnMeaning?.stale_replay_forbidden ||
|
||||
businessOverviewSignal ||
|
||||
unsupported ||
|
||||
lifecycleSignal ||
|
||||
valueFlowSignal ||
|
||||
|
|
@ -1467,7 +1518,7 @@ function buildAssistantMcpDiscoveryTurnInput(input) {
|
|||
semanticDataNeed &&
|
||||
(entityCandidates.length > 0 || explicitOrganizationScope || openScopeValueFlowWithoutResolvedCounterparty));
|
||||
const runDiscovery = shouldRunDiscovery({
|
||||
unsupported: broadBusinessEvaluationUnsupported ? seededUnsupported : unsupported ?? seededUnsupported,
|
||||
unsupported: businessOverviewSignal ? "broad_business_evaluation" : unsupported ?? seededUnsupported,
|
||||
lifecycleSignal,
|
||||
valueFlowSignal,
|
||||
metadataSignal: rawMetadataSignal || effectiveMetadataFollowupSeedApplicable,
|
||||
|
|
@ -1481,7 +1532,8 @@ function buildAssistantMcpDiscoveryTurnInput(input) {
|
|||
metadataGroundedMovementLaneApplicable ||
|
||||
metadataGroundedDocumentLaneApplicable ||
|
||||
groundedValueFlowFollowupApplicable,
|
||||
forceDiscoveryOverExplicitIntent: Boolean(entityResolutionClarificationCandidate) ||
|
||||
forceDiscoveryOverExplicitIntent: businessOverviewSignal ||
|
||||
Boolean(entityResolutionClarificationCandidate) ||
|
||||
organizationClarificationFollowupApplicable ||
|
||||
periodClarificationFollowupApplicable ||
|
||||
metadataAmbiguityLaneClarificationApplicable ||
|
||||
|
|
@ -1554,6 +1606,9 @@ function buildAssistantMcpDiscoveryTurnInput(input) {
|
|||
if (rawAllTimeScopeSignal) {
|
||||
pushReason(reasonCodes, "mcp_discovery_all_time_scope_signal_detected");
|
||||
}
|
||||
if (suppressNegatedTaxOnlyDateScope) {
|
||||
pushReason(reasonCodes, "mcp_discovery_negated_tax_period_scope_suppressed");
|
||||
}
|
||||
if (followupDiscoverySeedApplicable) {
|
||||
pushReason(reasonCodes, "mcp_discovery_seeded_from_followup_context");
|
||||
}
|
||||
|
|
@ -1632,8 +1687,8 @@ function buildAssistantMcpDiscoveryTurnInput(input) {
|
|||
if (unsupported) {
|
||||
pushReason(reasonCodes, "mcp_discovery_unsupported_but_understood_turn");
|
||||
}
|
||||
if (broadBusinessEvaluationUnsupported) {
|
||||
pushReason(reasonCodes, "mcp_discovery_broad_business_evaluation_kept_in_living_chat");
|
||||
if (businessOverviewSignal) {
|
||||
pushReason(reasonCodes, "mcp_discovery_broad_business_evaluation_route_candidate");
|
||||
}
|
||||
if (!(valueFlowOrganizationStaysScope && normalizedPredecomposeCounterparty === explicitOrganizationScope) &&
|
||||
normalizedPredecomposeCounterparty) {
|
||||
|
|
|
|||
|
|
@ -117,6 +117,11 @@ function detectBroadBusinessEvaluation(text) {
|
|||
if (!normalized) {
|
||||
return null;
|
||||
}
|
||||
if (/(?:\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(normalized)) {
|
||||
return {
|
||||
family: "broad_business_evaluation"
|
||||
};
|
||||
}
|
||||
if (/(?:как\s+ты\s+оценишь\s+деятельност[ьи]\s+компан|оценк[аи]?\s+деятельност[ьи]\s+компан|оцени\s+(?:компан|бизнес|деятельност)|(?:полный|сводный|нормальн\w*|взросл\w*)\s+анализ\s+(?:компан|бизнес|деятельност)|проанализируй\s+(?:компан|бизнес|деятельност)|(?:что\s+думаешь|какое\s+мнение)\s+(?:о|по)\s+(?:компан|бизнес)|(?:llm[-\s]?)?аудит\s+(?:компан|бизнес)|что\s+у\s+нас\s+вообще\s+происход|где\s+главн(?:ые|ый)\s+риски|как\s+у\s+нас\s+дела\s+по\s+компан)/iu.test(normalized)) {
|
||||
return {
|
||||
family: "broad_business_evaluation"
|
||||
|
|
@ -214,7 +219,7 @@ function createAssistantTurnMeaningPolicy(deps = {}) {
|
|||
asked_domain_family: askedDomainFamily,
|
||||
asked_action_family: askedActionFamily,
|
||||
explicit_intent_candidate: explicitIntentCandidate,
|
||||
explicit_entity_candidates: buildEntityCandidates(counterpartyTurnover),
|
||||
explicit_entity_candidates: broadBusinessEvaluation?.family ? [] : buildEntityCandidates(counterpartyTurnover),
|
||||
meaning_confidence: broadBusinessEvaluation?.family
|
||||
? "medium"
|
||||
: supportedIntent?.confidence ?? (counterpartyTurnover?.family ? "medium" : "low"),
|
||||
|
|
|
|||
|
|
@ -158,6 +158,10 @@ function isValueFlowPilot(pilot: AssistantMcpDiscoveryPilotExecutionContract): b
|
|||
);
|
||||
}
|
||||
|
||||
function isBusinessOverviewPilot(pilot: AssistantMcpDiscoveryPilotExecutionContract): boolean {
|
||||
return pilot.pilot_scope === "business_overview_route_template_v1";
|
||||
}
|
||||
|
||||
function isDocumentPilot(pilot: AssistantMcpDiscoveryPilotExecutionContract): boolean {
|
||||
return pilot.pilot_scope === "counterparty_document_evidence_query_documents_v1";
|
||||
}
|
||||
|
|
@ -431,6 +435,44 @@ function headlineFor(mode: AssistantMcpDiscoveryAnswerMode, pilot: AssistantMcpD
|
|||
}
|
||||
return "Инвентарный route-template уже выбран, но live-исполнение этого generic MCP контура еще не подключено; складской/товарный факт не подтвержден.";
|
||||
}
|
||||
if (isBusinessOverviewPilot(pilot) && pilot.derived_business_overview && mode === "confirmed_with_bounded_inference") {
|
||||
const overview = pilot.derived_business_overview;
|
||||
const families: string[] = [];
|
||||
if (
|
||||
overview.incoming_customer_revenue.rows_with_amount > 0 ||
|
||||
overview.outgoing_supplier_payout.rows_with_amount > 0
|
||||
) {
|
||||
families.push("денежный поток");
|
||||
}
|
||||
if (overview.activity_period) {
|
||||
families.push("активность");
|
||||
}
|
||||
if (overview.tax_position) {
|
||||
families.push("НДС-позиция");
|
||||
}
|
||||
if (overview.debt_position) {
|
||||
families.push("долговой срез на дату");
|
||||
}
|
||||
if (overview.debt_open_settlement_quality) {
|
||||
families.push("качество открытых расчетов");
|
||||
}
|
||||
if (overview.inventory_position) {
|
||||
families.push("складской срез на дату");
|
||||
}
|
||||
const unknownFamilies = ["прибыль/маржа"];
|
||||
if (!overview.tax_position) {
|
||||
unknownFamilies.push("НДС");
|
||||
}
|
||||
if (!overview.debt_position) {
|
||||
unknownFamilies.push("долговой срез");
|
||||
}
|
||||
unknownFamilies.push(overview.debt_open_settlement_quality ? "due-date просрочка" : "качество открытых расчетов");
|
||||
unknownFamilies.push(overview.inventory_position ? "полноценная складская ликвидность" : "склад");
|
||||
return `По данным 1С собран ограниченный бизнес-обзор: ${families.join(", ")} подтверждены найденными строками; ${unknownFamilies.join(", ")} остаются отдельными непроверенными областями.`;
|
||||
}
|
||||
if (isBusinessOverviewPilot(pilot) && mode === "checked_sources_only") {
|
||||
return "Бизнес-обзор был запущен, но подтвержденные денежные или activity-сигналы в найденных строках не получены.";
|
||||
}
|
||||
if (isEntityResolutionPilot(pilot) && mode === "needs_clarification") {
|
||||
return "По каталогу 1С нашлось несколько похожих контрагентов, и без уточнения нельзя честно выбрать правильную сущность.";
|
||||
}
|
||||
|
|
@ -574,6 +616,9 @@ function nextStepFor(mode: AssistantMcpDiscoveryAnswerMode, pilot: AssistantMcpD
|
|||
}
|
||||
return "Следующий шаг - связать inventory route-template с exact inventory runtime и затем проверить live-прогоном.";
|
||||
}
|
||||
if (mode === "confirmed_with_bounded_inference" && isBusinessOverviewPilot(pilot)) {
|
||||
return "Если нужен уже управленческий вывод, следующим шагом стоит отдельно проверить прибыль/маржу, долги, НДС и складскую ликвидность, а затем собрать полный бизнес-аудит.";
|
||||
}
|
||||
if (mode === "confirmed_with_bounded_inference" && pilot.derived_metadata_surface) {
|
||||
const surface = pilot.derived_metadata_surface;
|
||||
if (surface.ambiguity_detected && surface.ambiguity_entity_sets.length > 0) {
|
||||
|
|
@ -606,6 +651,14 @@ function buildMustNotClaim(pilot: AssistantMcpDiscoveryPilotExecutionContract):
|
|||
claims.push("Do not claim full all-time turnover unless the checked period and coverage prove it.");
|
||||
claims.push("Do not present a derived sum as a legal/accounting final total outside the checked 1C rows.");
|
||||
}
|
||||
if (isBusinessOverviewPilot(pilot)) {
|
||||
claims.push("Do not present business overview cash-flow spread as profit or margin.");
|
||||
claims.push("Do not claim debt quality, VAT position, inventory health, or company health unless those contours were separately checked.");
|
||||
claims.push("Do not present a debt-position snapshot as debt aging, overdue debt, or credit-quality analysis.");
|
||||
claims.push("Do not present open-settlement concentration as contractual due-date aging or confirmed overdue debt.");
|
||||
claims.push("Do not present an inventory snapshot or purchase-date aging signal as turnover, obsolescence, liquidation value, or full inventory health.");
|
||||
claims.push("Do not expose business_overview_route_template_v1 or MCP primitive names in the user answer.");
|
||||
}
|
||||
if (pilot.derived_ranked_value_flow) {
|
||||
claims.push("Do not present a bounded ranking as a complete all-time ranking outside the checked period and organization.");
|
||||
claims.push("Do not imply the top-ranked counterparty is globally final when probe-limit or scope boundaries still exist.");
|
||||
|
|
@ -899,6 +952,119 @@ function derivedBidirectionalValueFlowMonthlyLines(pilot: AssistantMcpDiscoveryP
|
|||
);
|
||||
}
|
||||
|
||||
function businessOverviewNetDirectionRu(direction: "net_incoming" | "net_outgoing" | "balanced"): string {
|
||||
if (direction === "net_incoming") {
|
||||
return "операционный денежный поток в проверенном срезе больше входящий, чем исходящий";
|
||||
}
|
||||
if (direction === "net_outgoing") {
|
||||
return "операционный денежный поток в проверенном срезе больше исходящий, чем входящий";
|
||||
}
|
||||
return "входящий и исходящий денежный поток в проверенном срезе примерно сбалансированы";
|
||||
}
|
||||
|
||||
function derivedBusinessOverviewConfirmedLines(pilot: AssistantMcpDiscoveryPilotExecutionContract): string[] {
|
||||
const overview = pilot.derived_business_overview;
|
||||
if (!overview) {
|
||||
return [];
|
||||
}
|
||||
const organization = overview.organization_scope ? ` по организации ${overview.organization_scope}` : "";
|
||||
const period = overview.period_scope ? ` за ${overview.period_scope}` : " за все доступное проверенное окно";
|
||||
const lines: string[] = [];
|
||||
if (overview.incoming_customer_revenue.rows_with_amount > 0) {
|
||||
lines.push(
|
||||
`Входящие поступления${organization}${period}: ${overview.incoming_customer_revenue.total_amount_human_ru} по ${overview.incoming_customer_revenue.rows_with_amount} строкам с суммой.`
|
||||
);
|
||||
}
|
||||
if (overview.outgoing_supplier_payout.rows_with_amount > 0) {
|
||||
lines.push(
|
||||
`Исходящие платежи/списания${organization}${period}: ${overview.outgoing_supplier_payout.total_amount_human_ru} по ${overview.outgoing_supplier_payout.rows_with_amount} строкам с суммой.`
|
||||
);
|
||||
}
|
||||
const leader = overview.top_customers[0];
|
||||
if (leader) {
|
||||
lines.push(`Самый крупный подтвержденный клиент в проверенном срезе: ${leader.axis_value} — ${leader.total_amount_human_ru}.`);
|
||||
}
|
||||
if (overview.activity_period) {
|
||||
lines.push(
|
||||
`Окно подтвержденной активности в 1С: ${overview.activity_period.first_activity_date} — ${overview.activity_period.latest_activity_date}; ориентировочно ${overview.activity_period.duration_human_ru}.`
|
||||
);
|
||||
}
|
||||
if (overview.tax_position) {
|
||||
const taxDirection =
|
||||
overview.tax_position.net_vat_direction === "vat_to_pay"
|
||||
? "к уплате"
|
||||
: overview.tax_position.net_vat_direction === "vat_to_recover_or_offset"
|
||||
? "к вычету/зачету"
|
||||
: "сбалансирован";
|
||||
lines.push(
|
||||
`НДС-позиция за ${overview.tax_position.period_scope}: книга продаж ${overview.tax_position.sales_vat_amount_human_ru}, книга покупок/вычеты ${overview.tax_position.purchase_vat_amount_human_ru}, нетто ${taxDirection} ${overview.tax_position.net_vat_amount_human_ru}.`
|
||||
);
|
||||
}
|
||||
if (overview.debt_position) {
|
||||
const debtDirection =
|
||||
overview.debt_position.net_debt_position_direction === "net_receivable"
|
||||
? "в пользу дебиторки"
|
||||
: overview.debt_position.net_debt_position_direction === "net_payable"
|
||||
? "в сторону кредиторки"
|
||||
: "сбалансировано";
|
||||
lines.push(
|
||||
`Долговой срез на ${overview.debt_position.as_of_date}: дебиторка ${overview.debt_position.receivables.total_amount_human_ru}, кредиторка ${overview.debt_position.payables.total_amount_human_ru}, нетто ${debtDirection} ${overview.debt_position.net_debt_position_amount_human_ru}.`
|
||||
);
|
||||
}
|
||||
if (overview.debt_open_settlement_quality) {
|
||||
const quality = overview.debt_open_settlement_quality;
|
||||
const topContract = quality.top_contracts[0];
|
||||
const topContractText = topContract
|
||||
? ` Крупнейший открытый договор: ${topContract.contract}${topContract.counterparty ? ` / ${topContract.counterparty}` : ""} — ${topContract.total_amount_human_ru}${topContract.share_of_gross_open_amount_pct === null ? "" : ` (${topContract.share_of_gross_open_amount_pct}%)`}.`
|
||||
: "";
|
||||
lines.push(
|
||||
`Качество открытых расчетов на ${quality.as_of_date}: брутто открытых договорных остатков ${quality.gross_open_amount_human_ru}, договоров ${quality.unique_contracts}, контрагентов ${quality.unique_counterparties}.${topContractText}`
|
||||
);
|
||||
}
|
||||
if (overview.inventory_position) {
|
||||
const leader = overview.inventory_position.top_items[0];
|
||||
const leaderText = leader
|
||||
? ` Крупнейшая подтвержденная позиция: ${leader.item} — ${leader.total_amount_human_ru}.`
|
||||
: "";
|
||||
lines.push(
|
||||
`Складской срез на ${overview.inventory_position.as_of_date}: остаток ${overview.inventory_position.total_amount_human_ru} по ${overview.inventory_position.rows_with_amount} строкам с суммой и ${overview.inventory_position.rows_with_quantity} строкам с количеством.${leaderText}`
|
||||
);
|
||||
if (overview.inventory_position.aging_signal?.oldest_purchase_date) {
|
||||
const ageText = overview.inventory_position.aging_signal.max_age_days === null
|
||||
? ""
|
||||
: `, максимальный возраст сигнала ${overview.inventory_position.aging_signal.max_age_days} дн.`;
|
||||
lines.push(
|
||||
`Возрастной сигнал склада: самая ранняя найденная дата закупки ${overview.inventory_position.aging_signal.oldest_purchase_date}${ageText}.`
|
||||
);
|
||||
}
|
||||
}
|
||||
return lines;
|
||||
}
|
||||
|
||||
function derivedBusinessOverviewInferenceLine(pilot: AssistantMcpDiscoveryPilotExecutionContract): string | null {
|
||||
const overview = pilot.derived_business_overview;
|
||||
if (!overview) {
|
||||
return null;
|
||||
}
|
||||
if (
|
||||
overview.incoming_customer_revenue.rows_with_amount <= 0 &&
|
||||
overview.outgoing_supplier_payout.rows_with_amount <= 0
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
return [
|
||||
`Расчетное нетто по найденным строкам: ${overview.net_amount_human_ru}; ${businessOverviewNetDirectionRu(overview.net_direction)}.`,
|
||||
"Это нормальный операционный сигнал, но не прибыль и не маржа: для управленческого вывода нужны отдельные расходы, себестоимость, долги, налоги и склад."
|
||||
].join(" ");
|
||||
}
|
||||
|
||||
function businessOverviewUnknownLines(pilot: AssistantMcpDiscoveryPilotExecutionContract): string[] {
|
||||
if (!pilot.derived_business_overview) {
|
||||
return userFacingUnknowns(pilot.evidence.unknown_facts);
|
||||
}
|
||||
return userFacingUnknowns(pilot.evidence.unknown_facts);
|
||||
}
|
||||
|
||||
export function buildAssistantMcpDiscoveryAnswerDraft(
|
||||
pilot: AssistantMcpDiscoveryPilotExecutionContract
|
||||
): AssistantMcpDiscoveryAnswerDraftContract {
|
||||
|
|
@ -912,6 +1078,7 @@ export function buildAssistantMcpDiscoveryAnswerDraft(
|
|||
pushReason(reasonCodes, "answer_contains_bounded_inference");
|
||||
}
|
||||
const derivedInferenceLine =
|
||||
derivedBusinessOverviewInferenceLine(pilot) ??
|
||||
derivedActivityInferenceLine(pilot) ??
|
||||
derivedMetadataInferenceLine(pilot) ??
|
||||
derivedRankedValueFlowInferenceLine(pilot) ??
|
||||
|
|
@ -929,10 +1096,28 @@ export function buildAssistantMcpDiscoveryAnswerDraft(
|
|||
derivedBidirectionalValueFlowMonthlyLines(pilot).length > 0
|
||||
? derivedBidirectionalValueFlowMonthlyLines(pilot)
|
||||
: derivedValueFlowMonthlyLines(pilot);
|
||||
const businessOverviewLines = derivedBusinessOverviewConfirmedLines(pilot);
|
||||
if (monthlyConfirmedLines.length > 0) {
|
||||
pushReason(reasonCodes, "answer_contains_monthly_breakdown");
|
||||
}
|
||||
const confirmedLines = pilot.derived_ranked_value_flow && derivedValueLine
|
||||
if (businessOverviewLines.length > 0) {
|
||||
pushReason(reasonCodes, "answer_contains_business_overview");
|
||||
}
|
||||
if (pilot.derived_business_overview?.tax_position) {
|
||||
pushReason(reasonCodes, "answer_contains_business_overview_tax_position");
|
||||
}
|
||||
if (pilot.derived_business_overview?.debt_position) {
|
||||
pushReason(reasonCodes, "answer_contains_business_overview_debt_position");
|
||||
}
|
||||
if (pilot.derived_business_overview?.debt_open_settlement_quality) {
|
||||
pushReason(reasonCodes, "answer_contains_business_overview_open_settlement_quality");
|
||||
}
|
||||
if (pilot.derived_business_overview?.inventory_position) {
|
||||
pushReason(reasonCodes, "answer_contains_business_overview_inventory_position");
|
||||
}
|
||||
const confirmedLines = businessOverviewLines.length > 0
|
||||
? businessOverviewLines
|
||||
: pilot.derived_ranked_value_flow && derivedValueLine
|
||||
? [derivedValueLine]
|
||||
: derivedValueLine
|
||||
? [...pilot.evidence.confirmed_facts, derivedValueLine, ...monthlyConfirmedLines]
|
||||
|
|
@ -941,7 +1126,9 @@ export function buildAssistantMcpDiscoveryAnswerDraft(
|
|||
: derivedMetadataLine
|
||||
? [derivedMetadataLine]
|
||||
: pilot.evidence.confirmed_facts;
|
||||
const unknownLines = pilot.derived_metadata_surface
|
||||
const unknownLines = pilot.derived_business_overview
|
||||
? businessOverviewUnknownLines(pilot)
|
||||
: pilot.derived_metadata_surface
|
||||
? pilot.derived_metadata_surface.available_fields.length > 0
|
||||
? userFacingUnknowns(pilot.evidence.unknown_facts)
|
||||
: ["Детальный список полей этих объектов этим шагом не получен."]
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load Diff
|
|
@ -682,6 +682,12 @@ function hasLifecycleSignal(text: string): boolean {
|
|||
);
|
||||
}
|
||||
|
||||
function hasBusinessOverviewSignal(text: string): boolean {
|
||||
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
|
||||
);
|
||||
}
|
||||
|
||||
function hasValueFlowSignal(text: string): boolean {
|
||||
return /(?:оборот|выручк|оплат|плат[её]ж|заплат|перечисл|списан|расход|исходящ|входящ|получ(?:ил|ено|ен)|поступил|поступлен|денежн[а-яёa-z0-9_-]*\s+поток|(?<!\p{L})заработ(?:ал|али|ало|аем|ает|ать|ано|ок)(?!\p{L})|supplier|value[-\s]?flow|turnover|revenue|payment|payout|outflow|cash\s+flow|\bearn(?:ed|ing|ings)?\b)/iu.test(
|
||||
text
|
||||
|
|
@ -737,7 +743,14 @@ function extractOrganizationScopeFromRawText(value: unknown): string | null {
|
|||
if (!match?.[1]) {
|
||||
return null;
|
||||
}
|
||||
return toNonEmptyString(match[1]);
|
||||
return toNonEmptyString(
|
||||
match[1]
|
||||
.replace(
|
||||
/\s+(?:\u043f\u043e\s+\u0434\u0430\u043d\u043d\u044b\u043c\s+1\u0441|\u0432\s+\u0446\u0435\u043b\u043e\u043c|\u0437\u0430\s+(?:\d{4}-\d{2}-\d{2}|\d{4}-\d{2}|(?:19|20)\d{2})(?:\s+\u0433(?:\u043e\u0434|\.)?)?|\u043d\u0430\s+(?:\d{4}-\d{2}-\d{2}|\d{4}-\d{2}|(?:19|20)\d{2})|\u0437\u0430\s+\u0432\u0441[\u0435\u0451]\s+(?:\u0434\u043e\u0441\u0442\u0443\u043f\p{L}+\s+)?\u0432\u0440\u0435\u043c\u044f|\u0437\u0430\s+\u0432\u0435\u0441\u044c\s+(?:\u0434\u043e\u0441\u0442\u0443\u043f\p{L}+\s+)?\u043f\u0435\u0440\u0438\u043e\u0434|\u0437\u0430\s+\u0432\u0441\u044e\s+\u0438\u0441\u0442\u043e\u0440\u0438(?:\u044e|\u0438)|\u0434\u0430\u0439\s+\u0431\u0438\u0437\u043d\u0435\u0441-?\u043e\u0431\u0437\u043e\u0440|\u0431\u0438\u0437\u043d\u0435\u0441-?\u043e\u0431\u0437\u043e\u0440|\u043d\u043e\s+\u043d\u0435).*$/iu,
|
||||
""
|
||||
)
|
||||
.trim()
|
||||
);
|
||||
}
|
||||
|
||||
function hasMonthlyAggregationSignal(text: string): boolean {
|
||||
|
|
@ -747,7 +760,7 @@ function hasMonthlyAggregationSignal(text: string): boolean {
|
|||
}
|
||||
|
||||
function hasAllTimeScopeHint(text: string): boolean {
|
||||
return /(?:\u0437\u0430\s+\u0432\u0441[\u0435\u0451]\s+\u0432\u0440\u0435\u043c\u044f|\u0437\u0430\s+\u0432\u0435\u0441\u044c\s+\u043f\u0435\u0440\u0438\u043e\u0434|\u0437\u0430\s+\u0432\u0441\u044e\s+\u0438\u0441\u0442\u043e\u0440\u0438(?:\u044e|\u0438)|\u0437\u0430\s+\u043b\u044e\u0431\u043e\u0439\s+\u043f\u0435\u0440\u0438\u043e\u0434|for\s+all\s+time|all\s+time|entire\s+period|full\s+history|any\s+period)/iu.test(
|
||||
return /(?:\u0437\u0430\s+\u0432\u0441[\u0435\u0451]\s+(?:\u0434\u043e\u0441\u0442\u0443\u043f\p{L}+\s+)?\u0432\u0440\u0435\u043c\u044f|\u0437\u0430\s+\u0432\u0435\u0441\u044c\s+(?:\u0434\u043e\u0441\u0442\u0443\u043f\p{L}+\s+)?\u043f\u0435\u0440\u0438\u043e\u0434|\u0437\u0430\s+\u0432\u0441\u044e\s+\u0438\u0441\u0442\u043e\u0440\u0438(?:\u044e|\u0438)|\u0437\u0430\s+\u043b\u044e\u0431\u043e\u0439\s+\u043f\u0435\u0440\u0438\u043e\u0434|for\s+all\s+time|all\s+time|entire\s+period|full\s+history|any\s+period)/iu.test(
|
||||
text
|
||||
);
|
||||
}
|
||||
|
|
@ -1000,6 +1013,22 @@ function hasExplicitDateScopeLiteral(text: string): boolean {
|
|||
return /(?:\b(?:19|20)\d{2}\b|\b\d{4}-\d{2}-\d{2}\b|\b\d{4}-\d{2}\b)/iu.test(text);
|
||||
}
|
||||
|
||||
function stripNegatedTaxDateScopeClauses(text: string): string {
|
||||
const dateScopeLiteral = String.raw`\b(?:\d{4}-\d{2}-\d{2}|\d{4}-\d{2}|(?:19|20)\d{2})\b`;
|
||||
const taxToken = String.raw`(?:\u043d\u0434\u0441|vat)`;
|
||||
const scopeToken = String.raw`(?:\u0437\u0430|\u043d\u0430|\u043f\u043e|for|in)`;
|
||||
const negatedVerb = String.raw`(?:\u0442\u0430\u0449\u0438(?:\u0442\u044c)?|\u0431\u0435\u0440\u0438|\u0431\u0440\u0430\u0442\u044c|\u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0439(?:\u0442\u0435)?|\u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c|\u0441\u0447\u0438\u0442\u0430\u0439(?:\u0442\u0435)?|\u0441\u0447\u0438\u0442\u0430\u0442\u044c|\u043f\u043e\u0434\u0442\u044f\u0433\u0438\u0432\u0430\u0439(?:\u0442\u0435)?|\u043f\u043e\u0434\u0442\u044f\u0433\u0438\u0432\u0430\u0442\u044c|\u043f\u0435\u0440\u0435\u043d\u043e\u0441\u0438|\u043f\u0435\u0440\u0435\u043d\u043e\u0441\u0438\u0442\u044c|pull|reuse|use|carry)`;
|
||||
const direct = new RegExp(
|
||||
String.raw`(?:^|[\s,;:])(?:\u043d\u0435\s+${negatedVerb}\s+${taxToken}\s+${scopeToken}\s+${dateScopeLiteral}|\u0431\u0435\u0437\s+${taxToken}\s+${scopeToken}\s+${dateScopeLiteral}|do\s+not\s+(?:${negatedVerb}\s+)?${taxToken}\s+${scopeToken}\s+${dateScopeLiteral})`,
|
||||
"giu"
|
||||
);
|
||||
const reversed = new RegExp(
|
||||
String.raw`${taxToken}\s+${scopeToken}\s+${dateScopeLiteral}\s+(?:\u043d\u0435\s+${negatedVerb}|do\s+not\s+(?:${negatedVerb})?)`,
|
||||
"giu"
|
||||
);
|
||||
return text.replace(direct, " ").replace(reversed, " ");
|
||||
}
|
||||
|
||||
function collectDateScopeFromRawText(text: string): string | null {
|
||||
const isoDate = text.match(/\b(\d{4}-\d{2}-\d{2})\b/u);
|
||||
if (isoDate?.[1]) {
|
||||
|
|
@ -1040,6 +1069,9 @@ function semanticNeedFor(input: {
|
|||
entityResolutionSignal: boolean;
|
||||
}): string | null {
|
||||
const combined = compactLower(`${input.domain ?? ""} ${input.action ?? ""} ${input.unsupported ?? ""}`);
|
||||
if (/(?:broad_business_evaluation|broad_evaluation|business_summary|business_overview|company analysis|business audit)/iu.test(combined)) {
|
||||
return "business overview evidence with bounded analyst interpretation";
|
||||
}
|
||||
if (input.metadataSignal || /(?:metadata|schema|catalog|inspect_(?:catalog|documents|registers|fields))/iu.test(combined)) {
|
||||
return "1C metadata evidence";
|
||||
}
|
||||
|
|
@ -1133,9 +1165,12 @@ export function buildAssistantMcpDiscoveryTurnInput(
|
|||
const rawReferentialDocumentExclusionSignal = hasReferentialDocumentExclusionFollowupSignal(
|
||||
repairedUserText ?? rawUserText ?? ""
|
||||
);
|
||||
const rawLifecycleSignal = hasLifecycleSignal(rawText);
|
||||
const rawBidirectionalValueFlowSignal = !rawLifecycleSignal && hasBidirectionalValueFlowSignal(rawText);
|
||||
const rawBusinessOverviewSignal = hasBusinessOverviewSignal(rawText);
|
||||
const rawLifecycleSignal = !rawBusinessOverviewSignal && hasLifecycleSignal(rawText);
|
||||
const rawBidirectionalValueFlowSignal =
|
||||
!rawBusinessOverviewSignal && !rawLifecycleSignal && hasBidirectionalValueFlowSignal(rawText);
|
||||
const rawValueFlowSignal =
|
||||
!rawBusinessOverviewSignal &&
|
||||
!rawLifecycleSignal &&
|
||||
(hasValueFlowSignal(rawText) || hasValueRankingSignal(rawText) || rawBidirectionalValueFlowSignal);
|
||||
const rawMetadataSignal =
|
||||
|
|
@ -1150,9 +1185,14 @@ export function buildAssistantMcpDiscoveryTurnInput(
|
|||
rawValueFlowSignal && hasValueFlowAggregateQuestionSignal(rawText);
|
||||
const monthlyAggregationSignal = hasMonthlyAggregationSignal(rawText);
|
||||
const rawAllTimeScopeSignal = hasAllTimeScopeHint(rawText);
|
||||
const explicitDateScopeLiteralDetected = hasExplicitDateScopeLiteral(rawText);
|
||||
const dateScopeSignalText = stripNegatedTaxDateScopeClauses(rawText);
|
||||
const negatedTaxDateScopeOnlySignal =
|
||||
dateScopeSignalText !== rawText &&
|
||||
hasExplicitDateScopeLiteral(rawText) &&
|
||||
!hasExplicitDateScopeLiteral(dateScopeSignalText);
|
||||
const explicitDateScopeLiteralDetected = hasExplicitDateScopeLiteral(dateScopeSignalText);
|
||||
const relativeCurrentDateHintDetected = hasRelativeCurrentDateHint(rawText);
|
||||
const rawDateScope = collectDateScopeFromRawText(rawText);
|
||||
const rawDateScope = collectDateScopeFromRawText(dateScopeSignalText);
|
||||
const rawMetadataScopeHint = rawMetadataSignal ? metadataScopeHintFromRawText(rawText) : null;
|
||||
const rawEntityCandidate = rawEntityResolutionSignal ? rawEntityResolutionCandidate(rawEntitySourceText) : null;
|
||||
const entityResolutionClarificationCandidate =
|
||||
|
|
@ -1171,6 +1211,12 @@ export function buildAssistantMcpDiscoveryTurnInput(
|
|||
const rawAggregationAxis = toNonEmptyString(assistantTurnMeaning?.asked_aggregation_axis);
|
||||
const unsupported = toNonEmptyString(assistantTurnMeaning?.unsupported_but_understood_family);
|
||||
const broadBusinessEvaluationUnsupported = unsupported === "broad_business_evaluation";
|
||||
const businessOverviewSignal =
|
||||
rawBusinessOverviewSignal ||
|
||||
broadBusinessEvaluationUnsupported ||
|
||||
rawDomain === "business_summary" ||
|
||||
rawDomain === "business_overview" ||
|
||||
rawAction === "broad_evaluation";
|
||||
const explicitIntentCandidate = toNonEmptyString(assistantTurnMeaning?.explicit_intent_candidate);
|
||||
const currentTurnDocumentLaneSignal = rawAction === "list_documents";
|
||||
const currentTurnMovementLaneSignal = rawAction === "list_movements";
|
||||
|
|
@ -1553,11 +1599,13 @@ export function buildAssistantMcpDiscoveryTurnInput(
|
|||
? followupSeed.unsupported
|
||||
: null;
|
||||
const lifecycleSignal =
|
||||
rawLifecycleSignal || seededDomain === "counterparty_lifecycle";
|
||||
!businessOverviewSignal && (rawLifecycleSignal || seededDomain === "counterparty_lifecycle");
|
||||
const bidirectionalValueFlowSignal =
|
||||
!businessOverviewSignal &&
|
||||
!lifecycleSignal &&
|
||||
(rawBidirectionalValueFlowSignal || seededAction === "net_value_flow");
|
||||
const valueFlowSignal =
|
||||
!businessOverviewSignal &&
|
||||
!lifecycleSignal &&
|
||||
!metadataGroundedMovementLaneApplicable &&
|
||||
(rawValueFlowSignal || seededDomain === "counterparty_value");
|
||||
|
|
@ -1568,9 +1616,9 @@ export function buildAssistantMcpDiscoveryTurnInput(
|
|||
const semanticDataNeed = metadataAmbiguityLaneClarificationApplicable
|
||||
? "metadata lane clarification"
|
||||
: semanticNeedFor({
|
||||
domain: rawDomain ?? seededDomain,
|
||||
action: rawAction ?? seededAction,
|
||||
unsupported: broadBusinessEvaluationUnsupported ? seededUnsupported : unsupported ?? seededUnsupported,
|
||||
domain: businessOverviewSignal ? "business_overview" : rawDomain ?? seededDomain,
|
||||
action: businessOverviewSignal ? "broad_evaluation" : rawAction ?? seededAction,
|
||||
unsupported: businessOverviewSignal ? "broad_business_evaluation" : unsupported ?? seededUnsupported,
|
||||
lifecycleSignal,
|
||||
valueFlowSignal,
|
||||
metadataSignal: rawMetadataSignal || effectiveMetadataFollowupSeedApplicable,
|
||||
|
|
@ -1710,30 +1758,48 @@ export function buildAssistantMcpDiscoveryTurnInput(
|
|||
const clarificationLoopStillNeedsPeriod = Boolean(
|
||||
followupSeed.loopStatus === "awaiting_clarification" && followupSeed.loopPendingAxes.includes("period")
|
||||
);
|
||||
const businessOverviewRawWithoutDateScope = Boolean(
|
||||
businessOverviewSignal &&
|
||||
!rawAllTimeScopeSignal &&
|
||||
!explicitDateScopeLiteralDetected &&
|
||||
!rawDateScope &&
|
||||
!relativeCurrentDateHintDetected
|
||||
);
|
||||
const predecomposeDateScopeCountsAsCurrentTurnPeriod = Boolean(
|
||||
predecomposeDateScope &&
|
||||
!isImplicitCurrentDateScope(predecomposeDateScope) &&
|
||||
!businessOverviewRawWithoutDateScope
|
||||
);
|
||||
const currentTurnCarriesExplicitPeriod = Boolean(
|
||||
explicitDateScopeLiteralDetected ||
|
||||
rawDateScope ||
|
||||
relativeCurrentDateHintDetected ||
|
||||
(predecomposeDateScope && !isImplicitCurrentDateScope(predecomposeDateScope))
|
||||
predecomposeDateScopeCountsAsCurrentTurnPeriod
|
||||
);
|
||||
const suppressImplicitCurrentDateScope = Boolean(
|
||||
!currentTurnCarriesExplicitPeriod &&
|
||||
(clarificationLoopStillNeedsPeriod ||
|
||||
businessOverviewSignal ||
|
||||
openScopeValueFlowWithoutResolvedCounterparty ||
|
||||
(valueFlowOrganizationStaysScope && (Boolean(followupSeed.rankingNeed) || bidirectionalValueFlowSignal)))
|
||||
);
|
||||
const suppressNegatedTaxOnlyDateScope = Boolean(businessOverviewSignal && negatedTaxDateScopeOnlySignal);
|
||||
const normalizedPredecomposeDateScope =
|
||||
(rawEntitySearchOverridesStaleScope && !currentTurnCarriesExplicitPeriod) ||
|
||||
suppressNegatedTaxOnlyDateScope ||
|
||||
businessOverviewRawWithoutDateScope ||
|
||||
(suppressImplicitCurrentDateScope && isImplicitCurrentDateScope(predecomposeDateScope))
|
||||
? null
|
||||
: predecomposeDateScope;
|
||||
const normalizedAssistantTurnMeaningDateScope =
|
||||
rawEntitySearchOverridesStaleScope ||
|
||||
suppressNegatedTaxOnlyDateScope ||
|
||||
(suppressImplicitCurrentDateScope && isImplicitCurrentDateScope(assistantTurnMeaningDateScope))
|
||||
? null
|
||||
: assistantTurnMeaningDateScope;
|
||||
const normalizedFollowupDateScope =
|
||||
rawEntitySearchOverridesStaleScope ||
|
||||
suppressNegatedTaxOnlyDateScope ||
|
||||
(suppressImplicitCurrentDateScope && isImplicitCurrentDateScope(followupSeed.dateScope))
|
||||
? null
|
||||
: followupSeed.dateScope;
|
||||
|
|
@ -1757,7 +1823,9 @@ export function buildAssistantMcpDiscoveryTurnInput(
|
|||
|
||||
const turnMeaning: AssistantMcpDiscoveryTurnMeaningRef = {
|
||||
asked_domain_family:
|
||||
lifecycleSignal
|
||||
businessOverviewSignal
|
||||
? "business_overview"
|
||||
: lifecycleSignal
|
||||
? "counterparty_lifecycle"
|
||||
: valueFlowSignal
|
||||
? "counterparty_value"
|
||||
|
|
@ -1770,9 +1838,11 @@ export function buildAssistantMcpDiscoveryTurnInput(
|
|||
: rawMetadataSignal || effectiveMetadataFollowupSeedApplicable
|
||||
? "metadata"
|
||||
: rawDomain ?? seededDomain,
|
||||
asked_action_family: lifecycleSignal
|
||||
? "activity_duration"
|
||||
: valueFlowSignal
|
||||
asked_action_family: businessOverviewSignal
|
||||
? "broad_evaluation"
|
||||
: lifecycleSignal
|
||||
? "activity_duration"
|
||||
: valueFlowSignal
|
||||
? bidirectionalValueFlowSignal
|
||||
? "net_value_flow"
|
||||
: payoutSignal
|
||||
|
|
@ -1792,7 +1862,7 @@ export function buildAssistantMcpDiscoveryTurnInput(
|
|||
valueFlowSignal && followupSeed.rankingNeed && !rawEntitySearchOverridesStaleScope
|
||||
? followupSeed.rankingNeed
|
||||
: undefined,
|
||||
explicit_entity_candidates: entityCandidates,
|
||||
explicit_entity_candidates: businessOverviewSignal ? [] : entityCandidates,
|
||||
metadata_ambiguity_entity_sets:
|
||||
metadataAmbiguityLaneClarificationApplicable && followupSeed.metadataAmbiguityEntitySets.length > 0
|
||||
? followupSeed.metadataAmbiguityEntitySets
|
||||
|
|
@ -1802,7 +1872,9 @@ export function buildAssistantMcpDiscoveryTurnInput(
|
|||
explicit_date_scope: explicitDateScope,
|
||||
subject_resolution_optional: metadataScopedLaneWithoutSubject || undefined,
|
||||
unsupported_but_understood_family:
|
||||
unsupported ??
|
||||
businessOverviewSignal
|
||||
? "broad_business_evaluation"
|
||||
: unsupported ??
|
||||
(lifecycleSignal
|
||||
? "counterparty_lifecycle"
|
||||
: valueFlowSignal
|
||||
|
|
@ -1826,6 +1898,7 @@ export function buildAssistantMcpDiscoveryTurnInput(
|
|||
: null),
|
||||
stale_replay_forbidden: Boolean(
|
||||
assistantTurnMeaning?.stale_replay_forbidden ||
|
||||
businessOverviewSignal ||
|
||||
unsupported ||
|
||||
lifecycleSignal ||
|
||||
valueFlowSignal ||
|
||||
|
|
@ -1886,7 +1959,7 @@ export function buildAssistantMcpDiscoveryTurnInput(
|
|||
);
|
||||
|
||||
const runDiscovery = shouldRunDiscovery({
|
||||
unsupported: broadBusinessEvaluationUnsupported ? seededUnsupported : unsupported ?? seededUnsupported,
|
||||
unsupported: businessOverviewSignal ? "broad_business_evaluation" : unsupported ?? seededUnsupported,
|
||||
lifecycleSignal,
|
||||
valueFlowSignal,
|
||||
metadataSignal: rawMetadataSignal || effectiveMetadataFollowupSeedApplicable,
|
||||
|
|
@ -1902,6 +1975,7 @@ export function buildAssistantMcpDiscoveryTurnInput(
|
|||
metadataGroundedDocumentLaneApplicable ||
|
||||
groundedValueFlowFollowupApplicable,
|
||||
forceDiscoveryOverExplicitIntent:
|
||||
businessOverviewSignal ||
|
||||
Boolean(entityResolutionClarificationCandidate) ||
|
||||
organizationClarificationFollowupApplicable ||
|
||||
periodClarificationFollowupApplicable ||
|
||||
|
|
@ -1976,6 +2050,9 @@ export function buildAssistantMcpDiscoveryTurnInput(
|
|||
if (rawAllTimeScopeSignal) {
|
||||
pushReason(reasonCodes, "mcp_discovery_all_time_scope_signal_detected");
|
||||
}
|
||||
if (suppressNegatedTaxOnlyDateScope) {
|
||||
pushReason(reasonCodes, "mcp_discovery_negated_tax_period_scope_suppressed");
|
||||
}
|
||||
if (followupDiscoverySeedApplicable) {
|
||||
pushReason(reasonCodes, "mcp_discovery_seeded_from_followup_context");
|
||||
}
|
||||
|
|
@ -2054,8 +2131,8 @@ export function buildAssistantMcpDiscoveryTurnInput(
|
|||
if (unsupported) {
|
||||
pushReason(reasonCodes, "mcp_discovery_unsupported_but_understood_turn");
|
||||
}
|
||||
if (broadBusinessEvaluationUnsupported) {
|
||||
pushReason(reasonCodes, "mcp_discovery_broad_business_evaluation_kept_in_living_chat");
|
||||
if (businessOverviewSignal) {
|
||||
pushReason(reasonCodes, "mcp_discovery_broad_business_evaluation_route_candidate");
|
||||
}
|
||||
if (
|
||||
!(valueFlowOrganizationStaysScope && normalizedPredecomposeCounterparty === explicitOrganizationScope) &&
|
||||
|
|
|
|||
|
|
@ -122,6 +122,13 @@ function detectBroadBusinessEvaluation(text) {
|
|||
if (!normalized) {
|
||||
return null;
|
||||
}
|
||||
if (
|
||||
/(?:\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(normalized)
|
||||
) {
|
||||
return {
|
||||
family: "broad_business_evaluation"
|
||||
};
|
||||
}
|
||||
if (
|
||||
/(?:как\s+ты\s+оценишь\s+деятельност[ьи]\s+компан|оценк[аи]?\s+деятельност[ьи]\s+компан|оцени\s+(?:компан|бизнес|деятельност)|(?:полный|сводный|нормальн\w*|взросл\w*)\s+анализ\s+(?:компан|бизнес|деятельност)|проанализируй\s+(?:компан|бизнес|деятельност)|(?:что\s+думаешь|какое\s+мнение)\s+(?:о|по)\s+(?:компан|бизнес)|(?:llm[-\s]?)?аудит\s+(?:компан|бизнес)|что\s+у\s+нас\s+вообще\s+происход|где\s+главн(?:ые|ый)\s+риски|как\s+у\s+нас\s+дела\s+по\s+компан)/iu.test(
|
||||
normalized
|
||||
|
|
@ -232,7 +239,7 @@ export function createAssistantTurnMeaningPolicy(deps = {}) {
|
|||
asked_domain_family: askedDomainFamily,
|
||||
asked_action_family: askedActionFamily,
|
||||
explicit_intent_candidate: explicitIntentCandidate,
|
||||
explicit_entity_candidates: buildEntityCandidates(counterpartyTurnover),
|
||||
explicit_entity_candidates: broadBusinessEvaluation?.family ? [] : buildEntityCandidates(counterpartyTurnover),
|
||||
meaning_confidence: broadBusinessEvaluation?.family
|
||||
? "medium"
|
||||
: supportedIntent?.confidence ?? (counterpartyTurnover?.family ? "medium" : "low"),
|
||||
|
|
|
|||
|
|
@ -180,6 +180,283 @@ describe("assistant MCP discovery answer adapter", () => {
|
|||
expect(draft.must_not_claim).toContain("Do not present the confirmed movement rows as a complete movement universe.");
|
||||
});
|
||||
|
||||
it("turns business overview multi-probe evidence into an analyst-safe draft", 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: "all_time_scope",
|
||||
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_summary",
|
||||
asked_action_family: "broad_evaluation",
|
||||
explicit_organization_scope: "ООО Альтернатива Плюс",
|
||||
unsupported_but_understood_family: "broad_business_evaluation"
|
||||
}
|
||||
});
|
||||
const pilot = await executeAssistantMcpDiscoveryPilot(
|
||||
planner,
|
||||
buildSequentialDeps([
|
||||
{
|
||||
rows: [
|
||||
{ Period: "2020-01-15T00:00:00", Amount: 120000, Counterparty: "Клиент А" },
|
||||
{ Period: "2020-02-15T00:00:00", Amount: 80000, Counterparty: "Клиент Б" }
|
||||
]
|
||||
},
|
||||
{
|
||||
rows: [{ Period: "2020-01-20T00:00:00", Amount: 150000, Counterparty: "Поставщик А" }]
|
||||
},
|
||||
{
|
||||
rows: [
|
||||
{ Период: "2020-01-15T00:00:00", Регистратор: "Поступление 1" },
|
||||
{ Период: "2020-12-15T00:00:00", Регистратор: "Поступление 2" }
|
||||
]
|
||||
}
|
||||
])
|
||||
);
|
||||
|
||||
const draft = buildAssistantMcpDiscoveryAnswerDraft(pilot);
|
||||
|
||||
expect(draft.answer_mode).toBe("confirmed_with_bounded_inference");
|
||||
expect(draft.headline).toContain("бизнес-обзор");
|
||||
expect(draft.confirmed_lines.join("\n")).toContain("Входящие поступления");
|
||||
expect(draft.confirmed_lines.join("\n")).toContain("Самый крупный подтвержденный клиент");
|
||||
expect(draft.inference_lines.join("\n")).toContain("не прибыль и не маржа");
|
||||
expect(draft.unknown_lines.join("\n")).toContain("Прибыль и маржа");
|
||||
expect(draft.unknown_lines.join("\n")).toContain("Налоговая/VAT-позиция");
|
||||
expect(draft.must_not_claim).toContain("Do not present business overview cash-flow spread as profit or margin.");
|
||||
expect(draft.reason_codes).toContain("answer_contains_business_overview");
|
||||
});
|
||||
|
||||
it("surfaces checked VAT/tax position in business overview without treating it as profit", 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_summary",
|
||||
asked_action_family: "broad_evaluation",
|
||||
explicit_organization_scope: "ООО Альтернатива Плюс",
|
||||
explicit_date_scope: "2020",
|
||||
unsupported_but_understood_family: "broad_business_evaluation"
|
||||
}
|
||||
});
|
||||
const pilot = await executeAssistantMcpDiscoveryPilot(
|
||||
planner,
|
||||
buildSequentialDeps([
|
||||
{ rows: [{ Period: "2020-01-15T00:00:00", Amount: 120000, Counterparty: "Клиент А" }] },
|
||||
{ rows: [{ Period: "2020-01-20T00:00:00", Amount: 50000, Counterparty: "Поставщик А" }] },
|
||||
{
|
||||
rows: [
|
||||
{ Регистратор: "VAT_BOOK_SALES", СчетДт: "68.02", Сумма: 40000 },
|
||||
{ Регистратор: "VAT_BOOK_PURCHASES", СчетДт: "19", Сумма: 12000 }
|
||||
]
|
||||
},
|
||||
{
|
||||
rows: [
|
||||
{ Период: "2020-01-15T00:00:00", Регистратор: "Поступление 1" },
|
||||
{ Период: "2020-12-15T00:00:00", Регистратор: "Поступление 2" }
|
||||
]
|
||||
}
|
||||
])
|
||||
);
|
||||
|
||||
const draft = buildAssistantMcpDiscoveryAnswerDraft(pilot);
|
||||
|
||||
expect(draft.headline).toContain("НДС-позиция");
|
||||
expect(draft.confirmed_lines.join("\n")).toContain("НДС-позиция за 2020");
|
||||
expect(draft.confirmed_lines.join("\n")).toContain("нетто к уплате 28 000 руб.");
|
||||
expect(draft.inference_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.must_not_claim).toContain("Do not present business overview cash-flow spread as profit or margin.");
|
||||
});
|
||||
|
||||
it("surfaces checked debt-position and open-settlement quality without treating them as overdue debt", 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_summary",
|
||||
asked_action_family: "broad_evaluation",
|
||||
explicit_organization_scope: "ООО Альтернатива Плюс",
|
||||
explicit_date_scope: "2020",
|
||||
unsupported_but_understood_family: "broad_business_evaluation"
|
||||
}
|
||||
});
|
||||
const pilot = await executeAssistantMcpDiscoveryPilot(
|
||||
planner,
|
||||
buildSequentialDeps([
|
||||
{ rows: [{ Period: "2020-01-15T00:00:00", Amount: 120000, Counterparty: "Клиент А" }] },
|
||||
{ rows: [{ Period: "2020-01-20T00:00:00", Amount: 50000, Counterparty: "Поставщик А" }] },
|
||||
{ rows: [] },
|
||||
{
|
||||
rows: [
|
||||
{ Period: "2020-12-31T00:00:00", Amount: 100000, Counterparty: "Клиент А" }
|
||||
]
|
||||
},
|
||||
{
|
||||
rows: [
|
||||
{ Period: "2020-12-31T00:00:00", Amount: 40000, Counterparty: "Поставщик А" }
|
||||
]
|
||||
},
|
||||
{
|
||||
rows: [
|
||||
{ Period: "2020-12-31T00:00:00", Amount: 100000, Counterparty: "Клиент А", Contract: "Договор А" },
|
||||
{ Period: "2020-12-31T00:00:00", Amount: 50000, Counterparty: "Поставщик А", Contract: "Договор Б" }
|
||||
]
|
||||
},
|
||||
{ rows: [] },
|
||||
{ rows: [] },
|
||||
{
|
||||
rows: [
|
||||
{ Period: "2020-01-15T00:00:00", Registrator: "Поступление 1" },
|
||||
{ Period: "2020-12-15T00:00:00", Registrator: "Поступление 2" }
|
||||
]
|
||||
}
|
||||
])
|
||||
);
|
||||
|
||||
const draft = buildAssistantMcpDiscoveryAnswerDraft(pilot);
|
||||
|
||||
expect(draft.headline).toContain("долговой срез");
|
||||
expect(draft.headline).toContain("качество открытых расчетов");
|
||||
expect(draft.confirmed_lines.join("\n")).toContain("Долговой срез на 2020-12-31");
|
||||
expect(draft.confirmed_lines.join("\n")).toContain("Качество открытых расчетов на 2020-12-31");
|
||||
expect(draft.confirmed_lines.join("\n")).toContain("нетто");
|
||||
expect(draft.unknown_lines.join("\n")).toContain("due-date");
|
||||
expect(draft.reason_codes).toContain("answer_contains_business_overview_debt_position");
|
||||
expect(draft.reason_codes).toContain("answer_contains_business_overview_open_settlement_quality");
|
||||
expect(draft.must_not_claim).toContain("Do not present a debt-position snapshot as debt aging, overdue debt, or credit-quality analysis.");
|
||||
expect(draft.must_not_claim).toContain("Do not present open-settlement concentration as contractual due-date aging or confirmed overdue debt.");
|
||||
});
|
||||
|
||||
it("surfaces checked inventory-position snapshot in business overview without treating it as warehouse liquidity", 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_summary",
|
||||
asked_action_family: "broad_evaluation",
|
||||
explicit_organization_scope: "ООО Тест",
|
||||
explicit_date_scope: "2020",
|
||||
unsupported_but_understood_family: "broad_business_evaluation"
|
||||
}
|
||||
});
|
||||
const pilot = await executeAssistantMcpDiscoveryPilot(
|
||||
planner,
|
||||
buildSequentialDeps([
|
||||
{ rows: [{ Period: "2020-01-15T00:00:00", Amount: 120000, Counterparty: "Клиент А" }] },
|
||||
{ rows: [{ Period: "2020-01-20T00:00:00", Amount: 50000, Counterparty: "Поставщик А" }] },
|
||||
{ rows: [] },
|
||||
{ rows: [] },
|
||||
{ rows: [] },
|
||||
{ rows: [] },
|
||||
{
|
||||
rows: [
|
||||
{ Period: "2020-12-31T00:00:00", Amount: 250000, Quantity: 10, Item: "Товар А" },
|
||||
{ Period: "2020-12-31T00:00:00", Amount: 50000, Quantity: 5, Item: "Товар Б" }
|
||||
]
|
||||
},
|
||||
{
|
||||
rows: [
|
||||
{ Period: "2020-01-10T00:00:00", Amount: 200000, Quantity: 8, Item: "Товар А" }
|
||||
]
|
||||
},
|
||||
{ rows: [{ Period: "2020-01-15T00:00:00", Registrator: "Поступление 1" }] }
|
||||
])
|
||||
);
|
||||
|
||||
const draft = buildAssistantMcpDiscoveryAnswerDraft(pilot);
|
||||
|
||||
expect(draft.headline).toContain("складской срез");
|
||||
expect(draft.confirmed_lines.join("\n")).toContain("Складской срез на 2020-12-31");
|
||||
expect(draft.confirmed_lines.join("\n")).toContain("Товар А");
|
||||
expect(draft.unknown_lines.join("\n")).toContain("оборачиваемость");
|
||||
expect(draft.reason_codes).toContain("answer_contains_business_overview_inventory_position");
|
||||
expect(draft.must_not_claim).toContain("Do not present an inventory snapshot or purchase-date aging signal as turnover, obsolescence, liquidation value, or full inventory health.");
|
||||
});
|
||||
|
||||
it("renders metadata-scoped movement all-time follow-up as an all-time bounded answer", async () => {
|
||||
const planner = planAssistantMcpDiscovery({
|
||||
dataNeedGraph: {
|
||||
|
|
|
|||
|
|
@ -103,7 +103,7 @@ describe("assistant MCP discovery pilot executor", () => {
|
|||
expect(deps.executeAddressMcpQuery).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("keeps business overview as an explicit unsupported runtime scope until the fresh multi-probe bridge exists", async () => {
|
||||
it("executes business overview as a bounded multi-probe bridge over money flow and activity evidence", async () => {
|
||||
const planner = planAssistantMcpDiscovery({
|
||||
dataNeedGraph: {
|
||||
schema_version: "assistant_data_need_graph_v1",
|
||||
|
|
@ -134,24 +134,350 @@ describe("assistant MCP discovery pilot executor", () => {
|
|||
explicit_organization_scope: "ООО Альтернатива Плюс"
|
||||
}
|
||||
});
|
||||
const deps = buildDeps([]);
|
||||
const deps = buildSequentialDeps([
|
||||
{
|
||||
rows: [
|
||||
{ Period: "2020-01-15T00:00:00", Amount: 120000, Counterparty: "Клиент А" },
|
||||
{ Period: "2020-02-15T00:00:00", Amount: 80000, Counterparty: "Клиент Б" }
|
||||
]
|
||||
},
|
||||
{
|
||||
rows: [
|
||||
{ Period: "2020-01-20T00:00:00", Amount: 150000, Counterparty: "Поставщик А" }
|
||||
]
|
||||
},
|
||||
{
|
||||
rows: [
|
||||
{ Период: "2020-01-15T00:00:00", Регистратор: "Поступление 1" },
|
||||
{ Период: "2020-12-15T00:00:00", Регистратор: "Поступление 2" }
|
||||
]
|
||||
}
|
||||
]);
|
||||
|
||||
const result = await executeAssistantMcpDiscoveryPilot(planner, deps);
|
||||
|
||||
expect(planner.planner_status).toBe("ready_for_execution");
|
||||
expect(result.pilot_status).toBe("unsupported");
|
||||
expect(result.pilot_status).toBe("executed");
|
||||
expect(result.pilot_scope).toBe("business_overview_route_template_v1");
|
||||
expect(result.mcp_execution_performed).toBe(false);
|
||||
expect(result.executed_primitives).toEqual([]);
|
||||
expect(result.mcp_execution_performed).toBe(true);
|
||||
expect(result.executed_primitives).toEqual(["query_movements", "query_documents"]);
|
||||
expect(result.skipped_primitives).toEqual([
|
||||
"query_movements",
|
||||
"aggregate_by_axis",
|
||||
"query_documents",
|
||||
"probe_coverage",
|
||||
"explain_evidence_basis"
|
||||
]);
|
||||
expect(result.reason_codes).toContain("pilot_scope_unsupported_for_live_execution");
|
||||
expect(deps.executeAddressMcpQuery).not.toHaveBeenCalled();
|
||||
expect(result.derived_business_overview).toMatchObject({
|
||||
organization_scope: "ООО Альтернатива Плюс",
|
||||
incoming_customer_revenue: {
|
||||
total_amount: 200000,
|
||||
rows_with_amount: 2
|
||||
},
|
||||
outgoing_supplier_payout: {
|
||||
total_amount: 150000,
|
||||
rows_with_amount: 1
|
||||
},
|
||||
net_amount: 50000,
|
||||
net_direction: "net_incoming"
|
||||
});
|
||||
expect(result.derived_business_overview?.top_customers[0]).toMatchObject({
|
||||
axis_value: "Клиент А",
|
||||
total_amount: 120000
|
||||
});
|
||||
expect(result.derived_business_overview?.activity_period?.duration_total_months).toBe(11);
|
||||
expect(result.evidence.confirmed_facts.join("\n")).toContain("В 1С подтверждены входящие поступления");
|
||||
expect(result.evidence.unknown_facts).toContain(
|
||||
"Прибыль и маржа этим бизнес-обзором не подтверждены: нужны себестоимость, расходы и закрывающие документы."
|
||||
);
|
||||
expect(result.reason_codes).toContain("pilot_derived_business_overview_from_confirmed_rows");
|
||||
expect(deps.executeAddressMcpQuery).toHaveBeenCalledTimes(3);
|
||||
});
|
||||
|
||||
it("adds a checked VAT/tax family to business overview only when an explicit period is available", 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: [
|
||||
{ Period: "2020-01-15T00:00:00", Amount: 120000, Counterparty: "Клиент А" }
|
||||
]
|
||||
},
|
||||
{
|
||||
rows: [
|
||||
{ Period: "2020-01-20T00:00:00", Amount: 50000, Counterparty: "Поставщик А" }
|
||||
]
|
||||
},
|
||||
{
|
||||
rows: [
|
||||
{ Регистратор: "VAT_BOOK_SALES", СчетДт: "68.02", Сумма: 40000 },
|
||||
{ Регистратор: "VAT_BOOK_PURCHASES", СчетДт: "19", Сумма: 12000 }
|
||||
]
|
||||
},
|
||||
{ rows: [] },
|
||||
{ rows: [] },
|
||||
{ rows: [] },
|
||||
{ rows: [] },
|
||||
{ rows: [] },
|
||||
{
|
||||
rows: [
|
||||
{ Период: "2020-01-15T00:00:00", Регистратор: "Поступление 1" },
|
||||
{ Период: "2020-12-15T00:00:00", Регистратор: "Поступление 2" }
|
||||
]
|
||||
}
|
||||
]);
|
||||
|
||||
const result = await executeAssistantMcpDiscoveryPilot(planner, deps);
|
||||
|
||||
expect(result.pilot_status).toBe("executed");
|
||||
expect(result.derived_business_overview?.tax_position).toMatchObject({
|
||||
period_scope: "2020",
|
||||
rows_matched: 2,
|
||||
rows_with_amount: 2,
|
||||
sales_vat_amount: 40000,
|
||||
purchase_vat_amount: 12000,
|
||||
net_vat_amount: 28000,
|
||||
net_vat_direction: "vat_to_pay"
|
||||
});
|
||||
expect(result.derived_business_overview?.missing_signal_families).not.toContain("tax_position");
|
||||
expect(result.evidence.confirmed_facts.join("\n")).toContain("НДС-позиция за 2020 подтверждена");
|
||||
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);
|
||||
const taxCall = deps.executeAddressMcpQuery.mock.calls[2]?.[0];
|
||||
expect(String(taxCall?.query ?? "")).toContain("НДСЗаписиКнигиПродаж");
|
||||
expect(String(taxCall?.query ?? "")).toContain("НДСЗаписиКнигиПокупок");
|
||||
});
|
||||
|
||||
it("adds a checked debt-position family to business overview only as an as-of-date snapshot", 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: [{ Period: "2020-01-15T00:00:00", Amount: 120000, Counterparty: "Клиент А" }] },
|
||||
{ rows: [{ Period: "2020-01-20T00:00:00", Amount: 50000, Counterparty: "Поставщик А" }] },
|
||||
{ rows: [] },
|
||||
{
|
||||
rows: [
|
||||
{ Period: "2020-12-31T00:00:00", Amount: 70000, Counterparty: "Клиент А" },
|
||||
{ Period: "2020-12-31T00:00:00", Amount: 30000, Counterparty: "Клиент Б" }
|
||||
]
|
||||
},
|
||||
{
|
||||
rows: [
|
||||
{ Period: "2020-12-31T00:00:00", Amount: 40000, Counterparty: "Поставщик А" }
|
||||
]
|
||||
},
|
||||
{
|
||||
rows: [
|
||||
{ Period: "2020-12-31T00:00:00", Amount: 90000, Counterparty: "Клиент А", Contract: "Договор А" },
|
||||
{ Period: "2020-12-31T00:00:00", Amount: 30000, Counterparty: "Клиент Б", Contract: "Договор Б" }
|
||||
]
|
||||
},
|
||||
{ rows: [] },
|
||||
{ rows: [] },
|
||||
{
|
||||
rows: [
|
||||
{ Period: "2020-01-15T00:00:00", Registrator: "Поступление 1" },
|
||||
{ Period: "2020-12-15T00:00:00", Registrator: "Поступление 2" }
|
||||
]
|
||||
}
|
||||
]);
|
||||
|
||||
const result = await executeAssistantMcpDiscoveryPilot(planner, deps);
|
||||
|
||||
expect(result.pilot_status).toBe("executed");
|
||||
expect(result.derived_business_overview?.debt_position).toMatchObject({
|
||||
as_of_date: "2020-12-31",
|
||||
receivables: {
|
||||
total_amount: 100000,
|
||||
rows_with_amount: 2
|
||||
},
|
||||
payables: {
|
||||
total_amount: 40000,
|
||||
rows_with_amount: 1
|
||||
},
|
||||
net_debt_position_amount: 60000,
|
||||
net_debt_position_direction: "net_receivable"
|
||||
});
|
||||
expect(result.derived_business_overview?.debt_position?.receivables.top_counterparties[0]).toMatchObject({
|
||||
axis_value: "Клиент А",
|
||||
total_amount: 70000
|
||||
});
|
||||
expect(result.derived_business_overview?.debt_open_settlement_quality).toMatchObject({
|
||||
as_of_date: "2020-12-31",
|
||||
rows_with_amount: 2,
|
||||
gross_open_amount: 120000,
|
||||
unique_counterparties: 2,
|
||||
unique_contracts: 2,
|
||||
concentration_top_contract_pct: 75
|
||||
});
|
||||
expect(result.derived_business_overview?.missing_signal_families).not.toContain("debt_position");
|
||||
expect(result.derived_business_overview?.missing_signal_families).not.toContain("debt_open_settlement_quality");
|
||||
expect(result.derived_business_overview?.missing_signal_families).toContain("debt_due_date_aging_quality");
|
||||
expect(result.evidence.confirmed_facts.join("\n")).toContain("Долговая позиция на 2020-12-31");
|
||||
expect(result.evidence.confirmed_facts.join("\n")).toContain("Качество открытых расчетов на 2020-12-31");
|
||||
expect(result.evidence.unknown_facts.join("\n")).toContain("due-date");
|
||||
expect(result.reason_codes).toContain("pilot_business_overview_debt_query_mcp_executed");
|
||||
expect(result.reason_codes).toContain("pilot_derived_business_overview_debt_position_from_confirmed_rows");
|
||||
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(deps.executeAddressMcpQuery).toHaveBeenCalledTimes(9);
|
||||
const receivablesCall = deps.executeAddressMcpQuery.mock.calls[3]?.[0];
|
||||
const payablesCall = deps.executeAddressMcpQuery.mock.calls[4]?.[0];
|
||||
const openContractsCall = deps.executeAddressMcpQuery.mock.calls[5]?.[0];
|
||||
expect(String(receivablesCall?.query ?? "")).toContain("62");
|
||||
expect(String(payablesCall?.query ?? "")).toContain("60");
|
||||
expect(String(openContractsCall?.query ?? "")).toContain("СуммаРазвернутыйОстатокКт");
|
||||
});
|
||||
|
||||
it("adds a checked inventory-position family to business overview only as an as-of-date snapshot", 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: [{ Period: "2020-01-15T00:00:00", Amount: 120000, Counterparty: "Клиент А" }] },
|
||||
{ rows: [{ Period: "2020-01-20T00:00:00", Amount: 50000, Counterparty: "Поставщик А" }] },
|
||||
{ rows: [] },
|
||||
{ rows: [] },
|
||||
{ rows: [] },
|
||||
{ rows: [] },
|
||||
{
|
||||
rows: [
|
||||
{ Period: "2020-12-31T00:00:00", Amount: 250000, Quantity: 10, Item: "Товар А" },
|
||||
{ Period: "2020-12-31T00:00:00", Amount: 50000, Quantity: 5, Item: "Товар Б" }
|
||||
]
|
||||
},
|
||||
{
|
||||
rows: [
|
||||
{ Period: "2020-01-10T00:00:00", Amount: 200000, Quantity: 8, Item: "Товар А" },
|
||||
{ Period: "2020-11-01T00:00:00", Amount: 50000, Quantity: 2, Item: "Товар Б" }
|
||||
]
|
||||
},
|
||||
{
|
||||
rows: [
|
||||
{ Period: "2020-01-15T00:00:00", Registrator: "Поступление 1" },
|
||||
{ Period: "2020-12-15T00:00:00", Registrator: "Поступление 2" }
|
||||
]
|
||||
}
|
||||
]);
|
||||
|
||||
const result = await executeAssistantMcpDiscoveryPilot(planner, deps);
|
||||
|
||||
expect(result.pilot_status).toBe("executed");
|
||||
expect(result.derived_business_overview?.inventory_position).toMatchObject({
|
||||
as_of_date: "2020-12-31",
|
||||
rows_matched: 2,
|
||||
rows_with_amount: 2,
|
||||
rows_with_quantity: 2,
|
||||
total_amount: 300000,
|
||||
total_quantity: 15,
|
||||
aging_signal: {
|
||||
rows_matched: 2,
|
||||
rows_with_purchase_date: 2,
|
||||
oldest_purchase_date: "2020-01-10",
|
||||
latest_purchase_date: "2020-11-01",
|
||||
max_age_days: 356
|
||||
}
|
||||
});
|
||||
expect(result.derived_business_overview?.inventory_position?.top_items[0]).toMatchObject({
|
||||
item: "Товар А",
|
||||
total_amount: 250000,
|
||||
total_quantity: 10
|
||||
});
|
||||
expect(result.derived_business_overview?.missing_signal_families).not.toContain("inventory_position");
|
||||
expect(result.derived_business_overview?.missing_signal_families).toContain("inventory_turnover_quality");
|
||||
expect(result.evidence.confirmed_facts.join("\n")).toContain("Складской срез на 2020-12-31");
|
||||
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);
|
||||
const inventoryCall = deps.executeAddressMcpQuery.mock.calls[6]?.[0];
|
||||
expect(inventoryCall?.account_scope).toContain("41.01");
|
||||
});
|
||||
|
||||
it("uses the explicit selected chain id when choosing the movement pilot scope", async () => {
|
||||
|
|
|
|||
|
|
@ -221,7 +221,7 @@ describe("assistant MCP discovery planner", () => {
|
|||
}
|
||||
});
|
||||
|
||||
it("builds a catalog-compatible business overview plan without pretending the fresh runtime probe exists yet", () => {
|
||||
it("builds a catalog-compatible business overview plan for the fresh multi-probe runtime bridge", () => {
|
||||
const result = planAssistantMcpDiscovery({
|
||||
dataNeedGraph: {
|
||||
schema_version: "assistant_data_need_graph_v1",
|
||||
|
|
|
|||
|
|
@ -737,4 +737,67 @@ describe("assistant MCP discovery response policy", () => {
|
|||
"mcp_discovery_response_policy_keep_broad_business_summary_over_clarification_candidate"
|
||||
);
|
||||
});
|
||||
|
||||
it("replaces deterministic broad business evaluation summary with a grounded business overview candidate", () => {
|
||||
const result = applyAssistantMcpDiscoveryResponsePolicy({
|
||||
currentReply: "legacy broad summary",
|
||||
currentReplySource: "deterministic_broad_business_evaluation_contract",
|
||||
livingChatSource: "deterministic_broad_business_evaluation_contract",
|
||||
modeDecisionReason: "unsupported_current_turn_meaning_boundary",
|
||||
addressRuntimeMeta: {
|
||||
assistant_mcp_discovery_entry_point_v1: entryPoint({
|
||||
turn_input: {
|
||||
adapter_status: "ready",
|
||||
should_run_discovery: true,
|
||||
turn_meaning_ref: {
|
||||
asked_domain_family: "business_summary",
|
||||
asked_action_family: "broad_evaluation",
|
||||
unsupported_but_understood_family: "broad_business_evaluation",
|
||||
stale_replay_forbidden: true
|
||||
},
|
||||
data_need_graph: {
|
||||
business_fact_family: "business_overview",
|
||||
clarification_gaps: []
|
||||
}
|
||||
},
|
||||
bridge: {
|
||||
bridge_status: "answer_draft_ready",
|
||||
user_facing_response_allowed: true,
|
||||
business_fact_answer_allowed: true,
|
||||
requires_user_clarification: false,
|
||||
answer_draft: {
|
||||
answer_mode: "confirmed_with_bounded_inference",
|
||||
headline: "Business overview was assembled from confirmed 1C money flow and activity rows.",
|
||||
confirmed_lines: [
|
||||
"Incoming customer money flow: 200000.00 RUB.",
|
||||
"Outgoing supplier payouts: 150000.00 RUB.",
|
||||
"Top confirmed customer by incoming money flow: Client A - 120000.00 RUB."
|
||||
],
|
||||
inference_lines: [
|
||||
"Net confirmed cash-flow spread is +50000.00 RUB; this is not profit or margin."
|
||||
],
|
||||
unknown_lines: [
|
||||
"Profit and margin are not confirmed by this overview.",
|
||||
"VAT/tax position is not confirmed by this overview."
|
||||
],
|
||||
limitation_lines: ["Business overview is limited to checked 1C rows."],
|
||||
next_step_line: "Check profit/margin, debt quality, VAT/tax position, and inventory liquidity."
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
});
|
||||
|
||||
expect(result.applied).toBe(true);
|
||||
expect(result.decision).toBe("apply_candidate");
|
||||
expect(result.reply_source).toBe("mcp_discovery_response_candidate_guarded");
|
||||
expect(result.reply_text).toContain("Incoming customer money flow");
|
||||
expect(result.reply_text).toContain("this is not profit or margin");
|
||||
expect(result.reply_text).toContain("VAT/tax position is not confirmed");
|
||||
expect(result.reply_text).not.toContain("query_movements");
|
||||
expect(result.reason_codes).toContain("mcp_discovery_response_policy_candidate_applied");
|
||||
expect(result.reason_codes).not.toContain(
|
||||
"mcp_discovery_response_policy_keep_broad_business_summary_over_clarification_candidate"
|
||||
);
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -37,6 +37,22 @@ function buildBidirectionalDeps(
|
|||
};
|
||||
}
|
||||
|
||||
function buildSequentialDeps(results: Array<{ rows: Array<Record<string, unknown>>; error?: string | null }>) {
|
||||
const executeAddressMcpQuery = vi.fn(async () => {
|
||||
const next = results.shift() ?? { rows: [] };
|
||||
const rows = next.rows;
|
||||
const error = next.error ?? null;
|
||||
return {
|
||||
fetched_rows: rows.length,
|
||||
matched_rows: error ? 0 : rows.length,
|
||||
raw_rows: rows,
|
||||
rows: error ? [] : rows,
|
||||
error
|
||||
};
|
||||
});
|
||||
return { executeAddressMcpQuery };
|
||||
}
|
||||
|
||||
function buildMetadataDeps(rows: Array<Record<string, unknown>>, error: string | null = null) {
|
||||
return {
|
||||
executeAddressMcpMetadata: vi.fn(async () => ({
|
||||
|
|
@ -114,6 +130,56 @@ describe("assistant MCP discovery runtime entry point", () => {
|
|||
expect(result.reason_codes).toContain("mcp_discovery_unsupported_but_understood_turn");
|
||||
});
|
||||
|
||||
it("runs the business overview bridge from broad evaluation turn meaning through multi-probe evidence", async () => {
|
||||
const deps = buildSequentialDeps([
|
||||
{
|
||||
rows: [
|
||||
{ Period: "2020-01-15T00:00:00", Amount: 120000, Counterparty: "Client A" },
|
||||
{ Period: "2020-02-15T00:00:00", Amount: 80000, Counterparty: "Client B" }
|
||||
]
|
||||
},
|
||||
{
|
||||
rows: [{ Period: "2020-01-20T00:00:00", Amount: 150000, Counterparty: "Supplier A" }]
|
||||
},
|
||||
{
|
||||
rows: [
|
||||
{ Period: "2020-01-15T00:00:00", Registrar: "Customer payment 1" },
|
||||
{ Period: "2020-12-15T00:00:00", Registrar: "Customer payment 2" }
|
||||
]
|
||||
}
|
||||
]);
|
||||
|
||||
const result = await runAssistantMcpDiscoveryRuntimeEntryPoint({
|
||||
assistantTurnMeaning: {
|
||||
asked_domain_family: "business_summary",
|
||||
asked_action_family: "broad_evaluation",
|
||||
explicit_organization_scope: "Alternative Plus",
|
||||
unsupported_but_understood_family: "broad_business_evaluation",
|
||||
stale_replay_forbidden: true
|
||||
},
|
||||
deps
|
||||
});
|
||||
|
||||
expect(result.entry_status).toBe("bridge_executed");
|
||||
expect(result.discovery_attempted).toBe(true);
|
||||
expect(result.turn_input.semantic_data_need).toBe("business overview evidence with bounded analyst interpretation");
|
||||
expect(result.turn_input.data_need_graph?.business_fact_family).toBe("business_overview");
|
||||
expect(result.turn_input.data_need_graph?.clarification_gaps).toEqual([]);
|
||||
expect(result.bridge?.bridge_status).toBe("answer_draft_ready");
|
||||
expect(result.bridge?.pilot.pilot_scope).toBe("business_overview_route_template_v1");
|
||||
expect(result.bridge?.pilot.derived_business_overview).toMatchObject({
|
||||
organization_scope: "Alternative Plus",
|
||||
net_amount: 50000,
|
||||
net_direction: "net_incoming"
|
||||
});
|
||||
expect(result.bridge?.answer_draft.answer_mode).toBe("confirmed_with_bounded_inference");
|
||||
expect(result.bridge?.answer_draft.confirmed_lines.join("\n")).toContain("Client A");
|
||||
expect(result.bridge?.answer_draft.inference_lines.join("\n")).toContain("\u043d\u0435 \u043f\u0440\u0438\u0431\u044b\u043b\u044c");
|
||||
expect(result.bridge?.answer_draft.unknown_lines.join("\n")).toContain("VAT");
|
||||
expect(result.reason_codes).toContain("pilot_derived_business_overview_from_confirmed_rows");
|
||||
expect(deps.executeAddressMcpQuery).toHaveBeenCalledTimes(3);
|
||||
});
|
||||
|
||||
it("runs the bridge for raw metadata wording without an exact route owner", async () => {
|
||||
const result = await runAssistantMcpDiscoveryRuntimeEntryPoint({
|
||||
userMessage: "какие документы и поля есть в 1С по НДС?",
|
||||
|
|
|
|||
|
|
@ -1409,7 +1409,7 @@ describe("assistant MCP discovery turn input adapter", () => {
|
|||
expect(result.reason_codes).toContain("mcp_discovery_not_applicable_for_supported_exact_turn");
|
||||
});
|
||||
|
||||
it("does not replace broad business evaluation with metadata discovery", () => {
|
||||
it("routes broad business evaluation into business overview discovery without metadata drift", () => {
|
||||
const result = buildAssistantMcpDiscoveryTurnInput({
|
||||
userMessage:
|
||||
"\u041a\u0430\u043a \u0442\u044b \u043e\u0446\u0435\u043d\u0438\u0448\u044c \u0434\u0435\u044f\u0442\u0435\u043b\u044c\u043d\u043e\u0441\u0442\u044c \u043a\u043e\u043c\u043f\u0430\u043d\u0438\u0438?",
|
||||
|
|
@ -1422,11 +1422,117 @@ describe("assistant MCP discovery turn input adapter", () => {
|
|||
}
|
||||
});
|
||||
|
||||
expect(result.adapter_status).toBe("not_applicable");
|
||||
expect(result.should_run_discovery).toBe(false);
|
||||
expect(result.turn_meaning_ref).toBeNull();
|
||||
expect(result.reason_codes).toContain("mcp_discovery_broad_business_evaluation_kept_in_living_chat");
|
||||
expect(result.reason_codes).toContain("mcp_discovery_not_applicable_for_supported_exact_turn");
|
||||
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.data_need_graph?.clarification_gaps).toContain("organization");
|
||||
expect(result.turn_meaning_ref).toMatchObject({
|
||||
asked_domain_family: "business_overview",
|
||||
asked_action_family: "broad_evaluation",
|
||||
explicit_date_scope: "2026-05-01",
|
||||
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).toContain("mcp_discovery_data_need_graph_built");
|
||||
expect(result.reason_codes).not.toContain("mcp_discovery_metadata_signal_detected");
|
||||
expect(result.reason_codes).not.toContain("mcp_discovery_not_applicable_for_supported_exact_turn");
|
||||
});
|
||||
|
||||
it("lets raw business-overview wording override stale exact turnover meaning", () => {
|
||||
const result = buildAssistantMcpDiscoveryTurnInput({
|
||||
userMessage:
|
||||
"\u0414\u0430\u0439 \u0431\u0438\u0437\u043d\u0435\u0441-\u043e\u0431\u0437\u043e\u0440 \u041e\u041e\u041e \u0410\u043b\u044c\u0442\u0435\u0440\u043d\u0430\u0442\u0438\u0432\u0430 \u041f\u043b\u044e\u0441 \u043f\u043e \u0434\u0430\u043d\u043d\u044b\u043c 1\u0421: \u043e\u0431\u043e\u0440\u043e\u0442\u044b, \u043d\u0435\u0442\u0442\u043e, \u0430\u043a\u0442\u0438\u0432\u043d\u043e\u0441\u0442\u044c.",
|
||||
assistantTurnMeaning: {
|
||||
asked_domain_family: "counterparty",
|
||||
asked_action_family: "counterparty_value_or_turnover",
|
||||
explicit_intent_candidate: "customer_revenue_and_payments",
|
||||
explicit_entity_candidates: [{ value: "\u043d\u0435\u0438\u0437\u0432\u0435\u0441\u0442\u043d\u043e." }],
|
||||
stale_replay_forbidden: false
|
||||
},
|
||||
predecomposeContract: {
|
||||
entities: { organization: "\u041e\u041e\u041e \u0410\u043b\u044c\u0442\u0435\u0440\u043d\u0430\u0442\u0438\u0432\u0430 \u041f\u043b\u044e\u0441" },
|
||||
period: { period_to: "2026-05-03" }
|
||||
}
|
||||
});
|
||||
|
||||
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.data_need_graph?.time_scope_need).toBe("all_time_scope");
|
||||
expect(result.data_need_graph?.clarification_gaps).toEqual([]);
|
||||
expect(result.turn_meaning_ref).toMatchObject({
|
||||
asked_domain_family: "business_overview",
|
||||
asked_action_family: "broad_evaluation",
|
||||
explicit_organization_scope: "\u041e\u041e\u041e \u0410\u043b\u044c\u0442\u0435\u0440\u043d\u0430\u0442\u0438\u0432\u0430 \u041f\u043b\u044e\u0441",
|
||||
unsupported_but_understood_family: "broad_business_evaluation",
|
||||
stale_replay_forbidden: true
|
||||
});
|
||||
expect(result.turn_meaning_ref?.explicit_entity_candidates).toBeUndefined();
|
||||
expect(result.reason_codes).toContain("mcp_discovery_broad_business_evaluation_route_candidate");
|
||||
expect(result.reason_codes).not.toContain("mcp_discovery_value_flow_signal_detected");
|
||||
});
|
||||
|
||||
it("keeps explicit year out of the organization scope for raw business overview wording", () => {
|
||||
const result = buildAssistantMcpDiscoveryTurnInput({
|
||||
userMessage:
|
||||
"Дай бизнес-обзор ООО Альтернатива Плюс за 2020 год по данным 1С: деньги, нетто, активность, дебиторка и кредиторка на дату.",
|
||||
assistantTurnMeaning: {
|
||||
asked_domain_family: "business_summary",
|
||||
asked_action_family: "broad_evaluation",
|
||||
unsupported_but_understood_family: "broad_business_evaluation",
|
||||
stale_replay_forbidden: true
|
||||
},
|
||||
predecomposeContract: {
|
||||
entities: { organization: "ООО Альтернатива Плюс" },
|
||||
period: { period_from: "2020-01-01", period_to: "2020-12-31" }
|
||||
}
|
||||
});
|
||||
|
||||
expect(result.adapter_status).toBe("ready");
|
||||
expect(result.should_run_discovery).toBe(true);
|
||||
expect(result.data_need_graph?.business_fact_family).toBe("business_overview");
|
||||
expect(result.data_need_graph?.time_scope_need).toBe("explicit_period");
|
||||
expect(result.turn_meaning_ref).toMatchObject({
|
||||
explicit_organization_scope: "ООО Альтернатива Плюс",
|
||||
explicit_date_scope: "2020"
|
||||
});
|
||||
});
|
||||
|
||||
it("keeps all-time business overview from reusing a negated VAT period as active scope", () => {
|
||||
const result = buildAssistantMcpDiscoveryTurnInput({
|
||||
userMessage:
|
||||
"\u0422\u0435\u043f\u0435\u0440\u044c \u043f\u043e \u041e\u041e\u041e \u0410\u043b\u044c\u0442\u0435\u0440\u043d\u0430\u0442\u0438\u0432\u0430 \u041f\u043b\u044e\u0441 \u0437\u0430 \u0432\u0441\u0435 \u0434\u043e\u0441\u0442\u0443\u043f\u043d\u043e\u0435 \u0432\u0440\u0435\u043c\u044f \u0434\u0430\u0439 \u0431\u0438\u0437\u043d\u0435\u0441-\u043e\u0431\u0437\u043e\u0440 \u0432 \u0446\u0435\u043b\u043e\u043c, \u043d\u043e \u043d\u0435 \u0442\u0430\u0449\u0438 \u041d\u0414\u0421 \u0437\u0430 2020 \u043a\u0430\u043a \u043f\u043e\u0434\u0442\u0432\u0435\u0440\u0436\u0434\u0435\u043d\u043d\u0443\u044e \u043e\u0431\u0449\u0443\u044e \u043d\u0430\u043b\u043e\u0433\u043e\u0432\u0443\u044e \u043f\u043e\u0437\u0438\u0446\u0438\u044e.",
|
||||
assistantTurnMeaning: {
|
||||
asked_domain_family: "business_summary",
|
||||
asked_action_family: "broad_evaluation",
|
||||
explicit_date_scope: "2020-12-31",
|
||||
unsupported_but_understood_family: "broad_business_evaluation",
|
||||
stale_replay_forbidden: true
|
||||
},
|
||||
predecomposeContract: {
|
||||
entities: { organization: "\u041e\u041e\u041e \u0410\u043b\u044c\u0442\u0435\u0440\u043d\u0430\u0442\u0438\u0432\u0430 \u041f\u043b\u044e\u0441" },
|
||||
period: { period_to: "2020-12-31" }
|
||||
}
|
||||
});
|
||||
|
||||
expect(result.adapter_status).toBe("ready");
|
||||
expect(result.should_run_discovery).toBe(true);
|
||||
expect(result.data_need_graph?.business_fact_family).toBe("business_overview");
|
||||
expect(result.data_need_graph?.time_scope_need).toBe("all_time_scope");
|
||||
expect(result.turn_meaning_ref).toMatchObject({
|
||||
asked_domain_family: "business_overview",
|
||||
asked_action_family: "broad_evaluation",
|
||||
explicit_organization_scope: "\u041e\u041e\u041e \u0410\u043b\u044c\u0442\u0435\u0440\u043d\u0430\u0442\u0438\u0432\u0430 \u041f\u043b\u044e\u0441",
|
||||
unsupported_but_understood_family: "broad_business_evaluation",
|
||||
stale_replay_forbidden: true
|
||||
});
|
||||
expect(result.turn_meaning_ref?.explicit_date_scope).toBeUndefined();
|
||||
expect(result.turn_meaning_ref?.explicit_entity_candidates).toBeUndefined();
|
||||
expect(result.reason_codes).toContain("mcp_discovery_all_time_scope_signal_detected");
|
||||
expect(result.reason_codes).toContain("mcp_discovery_negated_tax_period_scope_suppressed");
|
||||
});
|
||||
|
||||
it("does not bootstrap metadata discovery from a referential document exclusion follow-up over exact document context", () => {
|
||||
|
|
|
|||
|
|
@ -126,4 +126,23 @@ describe("assistantTurnMeaningPolicy", () => {
|
|||
expect(meaning.unsupported_but_understood_family).toBe("broad_business_evaluation");
|
||||
expect(meaning.reason_codes).toContain("broad_business_evaluation_current_turn_signal");
|
||||
});
|
||||
|
||||
it("lets explicit business overview wording beat turnover and net-flow cues", () => {
|
||||
const policy = buildPolicy({
|
||||
resolveAddressIntent: () => ({ intent: "customer_revenue_and_payments", confidence: "high" })
|
||||
});
|
||||
|
||||
const meaning = policy.resolveAssistantTurnMeaning({
|
||||
rawUserMessage:
|
||||
"\u0414\u0430\u0439 \u0431\u0438\u0437\u043d\u0435\u0441-\u043e\u0431\u0437\u043e\u0440 \u041e\u041e\u041e \u0410\u043b\u044c\u0442\u0435\u0440\u043d\u0430\u0442\u0438\u0432\u0430 \u041f\u043b\u044e\u0441: \u043e\u0431\u043e\u0440\u043e\u0442\u044b, \u043d\u0435\u0442\u0442\u043e, \u0430\u043a\u0442\u0438\u0432\u043d\u043e\u0441\u0442\u044c."
|
||||
});
|
||||
|
||||
expect(meaning.explicit_intent_candidate).toBeNull();
|
||||
expect(meaning.asked_domain_family).toBe("business_summary");
|
||||
expect(meaning.asked_action_family).toBe("broad_evaluation");
|
||||
expect(meaning.explicit_entity_candidates).toEqual([]);
|
||||
expect(meaning.unsupported_but_understood_family).toBe("broad_business_evaluation");
|
||||
expect(meaning.stale_replay_forbidden).toBe(true);
|
||||
expect(meaning.reason_codes).toContain("broad_business_evaluation_current_turn_signal");
|
||||
});
|
||||
});
|
||||
|
|
|
|||
Loading…
Reference in New Issue