Semantic Gate: закрепить контрагентский value-flow и денежный разбор
This commit is contained in:
parent
9be3fb29b7
commit
ba23b056b8
|
|
@ -17,7 +17,7 @@ It did not reopen Post-F and it did not prove that the Open-World implementation
|
||||||
From this point forward:
|
From this point forward:
|
||||||
|
|
||||||
- `~99%` for Open-World means implementation breadth through `Business Overview Missing Proof Ledger`;
|
- `~99%` for Open-World means implementation breadth through `Business Overview Missing Proof Ledger`;
|
||||||
- accepted module progress is `~98%` after the EHMO-derived Semantic Control Gate subset accepted live at `21/21`;
|
- accepted module progress is `~99%` after the EHMO-derived Semantic Control Gate subset accepted live again at `21/21` with W5/W7 hardening;
|
||||||
- the active work is finishing the control-gate closure surface, not immediate expansion into more proof families.
|
- the active work is finishing the control-gate closure surface, not immediate expansion into more proof families.
|
||||||
- full `100%` is still held back until the fat manual GUI pack is rerun/reviewed or remaining rough answers are explicitly classified outside the declared contour.
|
- full `100%` is still held back until the fat manual GUI pack is rerun/reviewed or remaining rough answers are explicitly classified outside the declared contour.
|
||||||
|
|
||||||
|
|
@ -54,9 +54,10 @@ For the current execution spine, read `23 - current_execution_spine_and_semantic
|
||||||
- Completed active slice: `Business Overview Document/Account Activity Profile Bridge`: business overview now executes the reviewed `document_type_and_account_section_profile` recipe and surfaces confirmed operational activity mix without claiming process quality, accounting correctness, or complete 1C activity coverage.
|
- Completed active slice: `Business Overview Document/Account Activity Profile Bridge`: business overview now executes the reviewed `document_type_and_account_section_profile` recipe and surfaces confirmed operational activity mix without claiming process quality, accounting correctness, or complete 1C activity coverage.
|
||||||
- Completed active slice: `Business Overview Counterparty/Contract Profile Bridge`: business overview now executes reviewed `counterparty_population_and_roles` and `contract_usage_overview` recipes, surfacing active counterparty role split and contract usage without claiming CRM quality, counterparty due diligence, legal completeness, or contract-risk.
|
- Completed active slice: `Business Overview Counterparty/Contract Profile Bridge`: business overview now executes reviewed `counterparty_population_and_roles` and `contract_usage_overview` recipes, surfacing active counterparty role split and contract usage without claiming CRM quality, counterparty due diligence, legal completeness, or contract-risk.
|
||||||
- Completed active slice: `Business Overview Missing Proof Ledger`: business overview now records machine-readable hard proof gaps for accounting profit/margin, due-date debt aging, inventory reserve/liquidation quality, and vendor/procurement quality, distinguishing proxy-only evidence from reviewed routes that are not wired yet.
|
- Completed active slice: `Business Overview Missing Proof Ledger`: business overview now records machine-readable hard proof gaps for accounting profit/margin, due-date debt aging, inventory reserve/liquidation quality, and vendor/procurement quality, distinguishing proxy-only evidence from reviewed routes that are not wired yet.
|
||||||
|
- Completed semantic-control slice: `W5/W7 Counterparty Value-Flow And Money-Breakdown Integrity`: bank-document/value-flow recipes now materialize explicit counterparty predicates, zero-row supplier-payment checks answer as checked negative evidence, compound money-breakdown wording stays in `business_overview`, and MCP discovery receives active organization scope only when the current turn has no explicit organization.
|
||||||
- Implementation breadth: `~99% (Open-World Bounded Autonomy Breadth through Slice 25)`.
|
- Implementation breadth: `~99% (Open-World Bounded Autonomy Breadth through Slice 25)`.
|
||||||
- Next active slice: `Open-World Semantic Control Gate`, covering garbage-anchor protection, business-overview continuation, intent dominance, frame hygiene, counterparty/organization arbitration, and final-summary answer shape.
|
- Next active slice: `Open-World Semantic Control Gate`, covering garbage-anchor protection, business-overview continuation, intent dominance, frame hygiene, counterparty/organization arbitration, and final-summary answer shape.
|
||||||
- Active module progress: `~98% (Open-World Bounded Autonomy Breadth, active slice: Semantic Control Gate)`.
|
- Active module progress: `~99% (Open-World Bounded Autonomy Breadth, active slice: Semantic Control Gate)`.
|
||||||
|
|
||||||
## Reporting Rule
|
## Reporting Rule
|
||||||
|
|
||||||
|
|
@ -64,7 +65,7 @@ Use these labels when reporting progress:
|
||||||
|
|
||||||
- `Прогресс модуля: 99% (Post-F Semantic Integrity Hardening, operationally closed/regression gate)` when discussing the Post-F slice itself.
|
- `Прогресс модуля: 99% (Post-F Semantic Integrity Hardening, operationally closed/regression gate)` when discussing the Post-F slice itself.
|
||||||
- `Прогресс модуля: 100% (Planner Autonomy Consolidation, declared phase83 slice closed)` when discussing the planner-autonomy slice that was just completed.
|
- `Прогресс модуля: 100% (Planner Autonomy Consolidation, declared phase83 slice closed)` when discussing the planner-autonomy slice that was just completed.
|
||||||
- `Прогресс модуля: 98% (Open-World Bounded Autonomy Breadth, active slice: Semantic Control Gate)` while discussing current module closure after the EHMO-derived critical subset accepted live.
|
- `Прогресс модуля: 99% (Open-World Bounded Autonomy Breadth, active slice: Semantic Control Gate)` while discussing current module closure after the EHMO-derived critical subset accepted live again with W5/W7 hardening.
|
||||||
- `Open-World Business Overview implementation breadth: ~99%, Semantic Control Gate critical subset accepted, fat GUI pack still pending` when discussing only the already wired Slice 25 breadth.
|
- `Open-World Business Overview implementation breadth: ~99%, Semantic Control Gate critical subset accepted, fat GUI pack still pending` when discussing only the already wired Slice 25 breadth.
|
||||||
- `Прогресс модуля: X% (Open-World Bounded Autonomy Breadth, active slice: <name>)` for later breadth work after the Semantic Control Gate is accepted.
|
- `Прогресс модуля: X% (Open-World Bounded Autonomy Breadth, active slice: <name>)` for later breadth work after the Semantic Control Gate is accepted.
|
||||||
|
|
||||||
|
|
@ -97,7 +98,7 @@ The project is not yet a universal arbitrary-1C agent.
|
||||||
|
|
||||||
Remaining work belongs to the next breadth module:
|
Remaining work belongs to the next breadth module:
|
||||||
|
|
||||||
- finish closure of the `Open-World Semantic Control Gate` opened by `assistant-stage1-EHMOy3lNFt`; the EHMO-derived critical subset is accepted live, but the fat GUI pack and residual answer-shape roughness still need final review;
|
- finish closure of the `Open-World Semantic Control Gate` opened by `assistant-stage1-EHMOy3lNFt`; the EHMO-derived critical subset is accepted live after W5/W7 hardening, but the fat GUI pack and residual answer-shape roughness still need final review;
|
||||||
- extend `business_overview` beyond money-flow/activity, customer and supplier concentration, document/account-section activity mix, counterparty role split, contract usage, yearly operating-flow dynamics, explicit profit/margin wording boundaries, explicit debt due-date wording boundaries, explicit inventory reserve/liquidation wording boundaries, explicit supplier/procurement-quality wording boundaries, explicit-period VAT/tax, as-of-date debt position, open-settlement concentration, contract-date debt age, debt staleness-risk proxy, as-of-date inventory position, trading-margin proxy, sales-to-stock inventory proxy, warehouse staleness-risk proxy, and the missing-proof ledger into separately proven exact accounting profit/margin, due-date debt aging/overdue, confirmed vendor-risk/procurement-quality analysis, and confirmed reserve/write-off/liquidation inventory evidence families;
|
- extend `business_overview` beyond money-flow/activity, customer and supplier concentration, document/account-section activity mix, counterparty role split, contract usage, yearly operating-flow dynamics, explicit profit/margin wording boundaries, explicit debt due-date wording boundaries, explicit inventory reserve/liquidation wording boundaries, explicit supplier/procurement-quality wording boundaries, explicit-period VAT/tax, as-of-date debt position, open-settlement concentration, contract-date debt age, debt staleness-risk proxy, as-of-date inventory position, trading-margin proxy, sales-to-stock inventory proxy, warehouse staleness-risk proxy, and the missing-proof ledger into separately proven exact accounting profit/margin, due-date debt aging/overdue, confirmed vendor-risk/procurement-quality analysis, and confirmed reserve/write-off/liquidation inventory evidence families;
|
||||||
- broader dynamic schema traversal for unfamiliar 1C asks;
|
- broader dynamic schema traversal for unfamiliar 1C asks;
|
||||||
- more primitive descriptors where live evidence proves a real gap;
|
- more primitive descriptors where live evidence proves a real gap;
|
||||||
|
|
|
||||||
|
|
@ -66,12 +66,12 @@ This gate is not a request to tune the assistant for every weird question in tha
|
||||||
Current status should be reported as:
|
Current status should be reported as:
|
||||||
|
|
||||||
- implementation breadth: `~99%` for Open-World Business Overview through Slice 25;
|
- implementation breadth: `~99%` for Open-World Business Overview through Slice 25;
|
||||||
- accepted module progress: `~98% (Open-World Bounded Autonomy Breadth, active slice: Semantic Control Gate)`.
|
- accepted module progress: `~99% (Open-World Bounded Autonomy Breadth, active slice: Semantic Control Gate)`.
|
||||||
|
|
||||||
This is not a regression from `99%` to `96%`. It is a metric split:
|
This is not a regression from `99%` to `96%`. It is a metric split:
|
||||||
|
|
||||||
- `99%` describes wired breadth;
|
- `99%` describes wired breadth;
|
||||||
- `98%` describes closure confidence after the EHMO-derived critical subset passed live replay; the gate is still not full module closure until the fat manual GUI pack and remaining answer-shape residuals are reviewed.
|
- `99%` describes closure confidence after the EHMO-derived critical subset passed live replay again with W5/W7 hardening; the gate is still not full module closure until the fat manual GUI pack and remaining answer-shape residuals are reviewed.
|
||||||
|
|
||||||
## Current Local Cut
|
## Current Local Cut
|
||||||
|
|
||||||
|
|
@ -94,7 +94,14 @@ Local cut 3 is implemented:
|
||||||
- W6: final `executive summary` / "confirmed, proxy, missing evidence, manual checks" wording is handled as deterministic conversation memory synthesis instead of generic address lookup.
|
- W6: final `executive summary` / "confirmed, proxy, missing evidence, manual checks" wording is handled as deterministic conversation memory synthesis instead of generic address lookup.
|
||||||
- W1/W6 hygiene: low-quality recap counterparty anchors such as standalone service prepositions are suppressed before they can appear as `«для»`-style pseudo-counterparties in the final answer.
|
- W1/W6 hygiene: low-quality recap counterparty anchors such as standalone service prepositions are suppressed before they can appear as `«для»`-style pseudo-counterparties in the final answer.
|
||||||
|
|
||||||
The EHMO-derived critical subset is now live-accepted. The remaining gate pressure is the fat manual GUI pack and known residual answer-shape roughness around selected counterparty money/document/movement follow-ups.
|
The EHMO-derived critical subset is now live-accepted after the W5/W7 pass. The remaining gate pressure is the fat manual GUI pack and known residual answer-shape roughness, not an unresolved critical subset failure.
|
||||||
|
|
||||||
|
Local cut 4 is implemented:
|
||||||
|
|
||||||
|
- W5: bank-document/value-flow recipes now materialize explicit counterparty predicates for customer, supplier, lifecycle, and bank-document profiles instead of relying on post-filter cleanup.
|
||||||
|
- W5: zero-row counterparty supplier-payment/value-flow checks now answer as checked negative evidence with period, organization, counterparty, and unknown-outside-boundary wording instead of looking like a generic failure.
|
||||||
|
- W7: compound organization-level money-breakdown wording now stays in `business_overview` and receives the active organization scope inside MCP discovery when the current turn has no explicit organization.
|
||||||
|
- W7 hygiene: pseudo predecompose counterparty anchors are filtered before they can pollute business-overview continuations.
|
||||||
|
|
||||||
## Failure Classes To Fix
|
## Failure Classes To Fix
|
||||||
|
|
||||||
|
|
@ -128,12 +135,13 @@ The next implementation pass should be cut into these work units:
|
||||||
4. `Semantic Control Gate W4 - frame reset and stale carryover policy`
|
4. `Semantic Control Gate W4 - frame reset and stale carryover policy`
|
||||||
5. `Semantic Control Gate W5 - counterparty/organization arbitration after pivots`
|
5. `Semantic Control Gate W5 - counterparty/organization arbitration after pivots`
|
||||||
6. `Semantic Control Gate W6 - final-summary answer lane`
|
6. `Semantic Control Gate W6 - final-summary answer lane`
|
||||||
|
7. `Semantic Control Gate W7 - broad money-breakdown and organization-scope discovery bridge`
|
||||||
|
|
||||||
Each work unit should add focused local tests and then be validated against the EHMO-derived semantic subset.
|
Each work unit should add focused local tests and then be validated against the EHMO-derived semantic subset.
|
||||||
|
|
||||||
## Acceptance Gate
|
## Acceptance Gate
|
||||||
|
|
||||||
The current module can move from `~98%` toward closure only after:
|
The current module can move from `~99%` toward closure only after:
|
||||||
|
|
||||||
- the EHMO-derived critical subset remains accepted after future nearby edits;
|
- the EHMO-derived critical subset remains accepted after future nearby edits;
|
||||||
- old canaries remain green: Post-F, phase83, inventory selected-object, VAT continuity, SVK document/movement chains;
|
- old canaries remain green: Post-F, phase83, inventory selected-object, VAT continuity, SVK document/movement chains;
|
||||||
|
|
@ -164,21 +172,19 @@ Manual runtime run reviewed as the gate opener:
|
||||||
Live EHMO-derived critical subset proof:
|
Live EHMO-derived critical subset proof:
|
||||||
|
|
||||||
- spec: `docs/orchestration/address_truth_harness_phase89_open_world_semantic_control_gate_ehmo_subset.json`
|
- spec: `docs/orchestration/address_truth_harness_phase89_open_world_semantic_control_gate_ehmo_subset.json`
|
||||||
- run: `artifacts/domain_runs/address_truth_harness_phase89_open_world_semantic_control_gate_ehmo_subset_live_fix4_20260505`
|
- run: `artifacts/domain_runs/address_truth_harness_phase89_open_world_semantic_control_gate_ehmo_subset_live_fix8b_20260505`
|
||||||
- result: `accepted`, `21/21`, `0` warnings, MCP live-readiness `ready`
|
- result: `accepted`, `21/21`, `0` warnings, MCP live-readiness `ready`
|
||||||
- key covered repairs: business-audit synthesis no longer falls into capability help; final executive summary uses confirmed/proxy/missing/manual-check sections and filters pseudo-counterparty garbage.
|
- key covered repairs: business-audit synthesis no longer falls into capability help; money-breakdown continuations use grounded business-overview evidence; SVK outgoing zero-row value-flow is rendered as checked negative evidence; final executive summary uses confirmed/proxy/missing/manual-check sections and filters pseudo-counterparty garbage.
|
||||||
|
|
||||||
Graphify snapshot at this status cut:
|
Graphify snapshot at this status cut:
|
||||||
|
|
||||||
- `6081 nodes`
|
- see `graphify-out/GRAPH_REPORT.md`
|
||||||
- `13263 edges`
|
|
||||||
- `140 communities`
|
|
||||||
|
|
||||||
## Reporting Rule
|
## Reporting Rule
|
||||||
|
|
||||||
Until the fat manual GUI pack is reviewed or residuals are explicitly classified, use:
|
Until the fat manual GUI pack is reviewed or residuals are explicitly classified, use:
|
||||||
|
|
||||||
`Прогресс модуля: 98% (Open-World Bounded Autonomy Breadth, active slice: Semantic Control Gate)`
|
`Прогресс модуля: 99% (Open-World Bounded Autonomy Breadth, active slice: Semantic Control Gate)`
|
||||||
|
|
||||||
If discussing only the already wired business-overview breadth, say:
|
If discussing only the already wired business-overview breadth, say:
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -144,11 +144,11 @@ Current honest status:
|
||||||
- bounded-autonomy foundation readiness: `~89%`
|
- bounded-autonomy foundation readiness: `~89%`
|
||||||
- open-world bounded-autonomy readiness: `~87%`
|
- open-world bounded-autonomy readiness: `~87%`
|
||||||
- active Open-World Bounded Autonomy Breadth implementation breadth: `~99%`, with business-overview evidence fusion, the reviewed `business_overview` catalog/data-need/planner route-fabric slice, the fresh multi-probe runtime bridge, the explicit-period VAT/tax fact-family bridge, the explicit-period debt-position bridge, the explicit-date inventory-position bridge, the open-settlement quality bridge accepted by live semantic replay, selected-item profitability bridged by local semantic/runtime regression tests, contract-date debt age bridged locally, debt staleness-risk proxy bridged locally, debt due-date boundary arbitration bridged locally, inventory reserve/liquidation boundary arbitration bridged locally, supplier/procurement-quality boundary arbitration bridged locally, supplier concentration proxy bridged locally, document/account-section activity profile bridged locally, counterparty population/roles and contract usage profiles bridged locally, yearly operating-flow proxy bridged locally, earnings/best-year wording arbitration bridged locally, profit/margin wording boundary arbitration bridged locally, analyst synthesis added to business-overview answer drafting, company-period trading margin proxy bridged locally, inventory sales-to-stock proxy bridged locally, inventory staleness-risk proxy bridged locally, gap-specific answer shaping bridged locally, and missing proof families recorded as runtime evidence ledger; exact accounting profit/margin, true due-date debt aging/overdue, confirmed vendor-risk/procurement-quality analysis, and confirmed reserve/write-off/liquidation inventory evidence are still pending
|
- active Open-World Bounded Autonomy Breadth implementation breadth: `~99%`, with business-overview evidence fusion, the reviewed `business_overview` catalog/data-need/planner route-fabric slice, the fresh multi-probe runtime bridge, the explicit-period VAT/tax fact-family bridge, the explicit-period debt-position bridge, the explicit-date inventory-position bridge, the open-settlement quality bridge accepted by live semantic replay, selected-item profitability bridged by local semantic/runtime regression tests, contract-date debt age bridged locally, debt staleness-risk proxy bridged locally, debt due-date boundary arbitration bridged locally, inventory reserve/liquidation boundary arbitration bridged locally, supplier/procurement-quality boundary arbitration bridged locally, supplier concentration proxy bridged locally, document/account-section activity profile bridged locally, counterparty population/roles and contract usage profiles bridged locally, yearly operating-flow proxy bridged locally, earnings/best-year wording arbitration bridged locally, profit/margin wording boundary arbitration bridged locally, analyst synthesis added to business-overview answer drafting, company-period trading margin proxy bridged locally, inventory sales-to-stock proxy bridged locally, inventory staleness-risk proxy bridged locally, gap-specific answer shaping bridged locally, and missing proof families recorded as runtime evidence ledger; exact accounting profit/margin, true due-date debt aging/overdue, confirmed vendor-risk/procurement-quality analysis, and confirmed reserve/write-off/liquidation inventory evidence are still pending
|
||||||
- active Open-World Bounded Autonomy Breadth accepted-module progress: `~98%`, because the EHMO-derived `Open-World Semantic Control Gate` critical subset now accepts live at `21/21`; full closure is still held back for the fat manual GUI pack and remaining answer-shape residual review
|
- active Open-World Bounded Autonomy Breadth accepted-module progress: `~99%`, because the EHMO-derived `Open-World Semantic Control Gate` critical subset accepts live at `21/21` after W5/W7 hardening; full closure is still held back for the fat manual GUI pack and remaining answer-shape residual review
|
||||||
- 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
|
- 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
|
- 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
|
- 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: `6081 nodes`, `13263 edges`, `140 communities`
|
- graph snapshot after latest rebuild: see `graphify-out/GRAPH_REPORT.md`
|
||||||
- current regression-gate breakpoint:
|
- current regression-gate breakpoint:
|
||||||
- the validated hot paths are no longer structurally broken;
|
- the validated hot paths are no longer structurally broken;
|
||||||
- flagship continuity collapse is no longer the primary risk;
|
- flagship continuity collapse is no longer the primary risk;
|
||||||
|
|
@ -177,7 +177,7 @@ Latest live proof now includes:
|
||||||
- `address_truth_harness_phase82_human_mixed_integrity_status_dialog_post_f_account_injection_guard_clean_scope` accepted `19/19`, with the `Жуковке 51` numeric counterparty suffix kept as counterparty scope instead of leaking as account `51`
|
- `address_truth_harness_phase82_human_mixed_integrity_status_dialog_post_f_account_injection_guard_clean_scope` accepted `19/19`, with the `Жуковке 51` numeric counterparty suffix kept as counterparty scope instead of leaking as account `51`
|
||||||
- `address_truth_harness_post_f_cross_stage_canary_agent_20260424_live7` accepted `24/24`, proving a saved cross-stage AGENT canary across VAT metadata, metadata-scoped organization/document pivots, numeric counterparty suffixes, open-organization value-flow clarification, ranked value-flow year switches, and SVK grounded reset; the saved autorun is `AGENT | Post-F cross-stage semantic integrity canary` (`gen-ag04241406-abe4d8`)
|
- `address_truth_harness_post_f_cross_stage_canary_agent_20260424_live7` accepted `24/24`, proving a saved cross-stage AGENT canary across VAT metadata, metadata-scoped organization/document pivots, numeric counterparty suffixes, open-organization value-flow clarification, ranked value-flow year switches, and SVK grounded reset; the saved autorun is `AGENT | Post-F cross-stage semantic integrity canary` (`gen-ag04241406-abe4d8`)
|
||||||
- `address_truth_harness_post_f_manual_failures_20260424_live3` accepted `11/11`, proving the manual failure slice from `assistant-stage1-9liEOh-7JP`: VAT purchase-date, VAT February 2017, highest-value customer, and Chepurnov item-flow after stale inventory context; the saved autorun is `AGENT | Post-F ручные провалы VAT revenue item-flow live3` (`gen-ag04241710-bdb248`)
|
- `address_truth_harness_post_f_manual_failures_20260424_live3` accepted `11/11`, proving the manual failure slice from `assistant-stage1-9liEOh-7JP`: VAT purchase-date, VAT February 2017, highest-value customer, and Chepurnov item-flow after stale inventory context; the saved autorun is `AGENT | Post-F ручные провалы VAT revenue item-flow live3` (`gen-ag04241710-bdb248`)
|
||||||
- `address_truth_harness_phase89_open_world_semantic_control_gate_ehmo_subset_live_fix4_20260505` accepted `21/21`, proving the EHMO-derived Semantic Control Gate subset after business-audit lane repair, final executive-summary memory synthesis, and pseudo-counterparty recap filtering
|
- `address_truth_harness_phase89_open_world_semantic_control_gate_ehmo_subset_live_fix8b_20260505` accepted `21/21`, proving the EHMO-derived Semantic Control Gate subset after business-audit lane repair, money-breakdown business-overview recovery, SVK zero-row value-flow answer shaping, final executive-summary memory synthesis, and pseudo-counterparty recap filtering
|
||||||
- `address_truth_harness_phase11_manual_followup_meta_quality_live_rerun_vatfix` accepted `10/10`
|
- `address_truth_harness_phase11_manual_followup_meta_quality_live_rerun_vatfix` accepted `10/10`
|
||||||
- `address_truth_harness_phase20_continuity_stabilization_live_rerun_vatfix` accepted `6/6`
|
- `address_truth_harness_phase20_continuity_stabilization_live_rerun_vatfix` accepted `6/6`
|
||||||
- `addressQueryRuntimeM23.test.ts` full semantic/runtime slice accepted `403/403` after Post-F VAT/date-basis, scope-recovery, open value-flow organization clarification, document-vs-bank arbitration, and reply-shape hardening
|
- `addressQueryRuntimeM23.test.ts` full semantic/runtime slice accepted `403/403` after Post-F VAT/date-basis, scope-recovery, open value-flow organization clarification, document-vs-bank arbitration, and reply-shape hardening
|
||||||
|
|
@ -204,7 +204,7 @@ Latest live proof now includes:
|
||||||
- business-overview supplier concentration proxy accepted locally: targeted executor/answer-adapter slice passed `66/66` with `1` skipped; M23 route/runtime regression passed `412/412`; build passed; graphify rebuilt to `6041 nodes`, `13162 edges`, `136 communities`; the proxy ranks confirmed outgoing payment counterparties while vendor risk, procurement quality, and full expense structure remain unclaimed
|
- business-overview supplier concentration proxy accepted locally: targeted executor/answer-adapter slice passed `66/66` with `1` skipped; M23 route/runtime regression passed `412/412`; build passed; graphify rebuilt to `6041 nodes`, `13162 edges`, `136 communities`; the proxy ranks confirmed outgoing payment counterparties while vendor risk, procurement quality, and full expense structure remain unclaimed
|
||||||
- business-overview yearly operating-flow proxy accepted locally: targeted executor/answer-adapter slice passed `66/66` with `1` skipped; M23 route/runtime regression passed `412/412`; build passed; graphify rebuilt to `6047 nodes`, `13177 edges`, `139 communities`; the proxy builds annual incoming/outgoing/net buckets from confirmed money-flow rows while profit, финрезультат, and full P&L remain unclaimed
|
- business-overview yearly operating-flow proxy accepted locally: targeted executor/answer-adapter slice passed `66/66` with `1` skipped; M23 route/runtime regression passed `412/412`; build passed; graphify rebuilt to `6047 nodes`, `13177 edges`, `139 communities`; the proxy builds annual incoming/outgoing/net buckets from confirmed money-flow rows while profit, финрезультат, and full P&L remain unclaimed
|
||||||
- business-overview missing proof ledger accepted locally: targeted executor/answer-adapter slice passed `66/66` with `1` skipped; M23 route/runtime regression passed `416/416`; build passed; graphify count is recorded in the current graph snapshot; hard remaining proof gaps are now visible as machine-readable `missing_proof_families` rather than only prose warnings
|
- business-overview missing proof ledger accepted locally: targeted executor/answer-adapter slice passed `66/66` with `1` skipped; M23 route/runtime regression passed `416/416`; build passed; graphify count is recorded in the current graph snapshot; hard remaining proof gaps are now visible as machine-readable `missing_proof_families` rather than only prose warnings
|
||||||
- semantic control gate critical subset accepted live: focused W2/W3/W6 regressions passed `54/54`; broader living/router semantic slice passed `90/90`; build passed; EHMO-derived replay `address_truth_harness_phase89_open_world_semantic_control_gate_ehmo_subset_live_fix4_20260505` accepted `21/21` with `0` warnings; graphify rebuilt to `6081 nodes`, `13263 edges`, `140 communities`; fat manual GUI pack remains the closure check
|
- semantic control gate critical subset accepted live: W5/W7 focused regression plus focused W2/W3/W6 and broader living/router semantic slices passed locally; build passed; EHMO-derived replay `address_truth_harness_phase89_open_world_semantic_control_gate_ehmo_subset_live_fix8b_20260505` accepted `21/21` with `0` warnings; graphify rebuilt after this cut; fat manual GUI pack remains the closure check
|
||||||
- business-overview earnings wording arbitration accepted locally: turn-meaning/turn-input slice passed `85/85` with `6` skipped; M23 route/runtime regression passed `412/412`; runtime-entry/pilot/answer slice passed `85/85` with `3` skipped; build passed; graphify rebuilt to `6052 nodes`, `13187 edges`, `138 communities`; organization-level earnings/best-year wording now reaches `business_overview` while explicit customer/counterparty ranking remains in exact customer value routes
|
- business-overview earnings wording arbitration accepted locally: turn-meaning/turn-input slice passed `85/85` with `6` skipped; M23 route/runtime regression passed `412/412`; runtime-entry/pilot/answer slice passed `85/85` with `3` skipped; build passed; graphify rebuilt to `6052 nodes`, `13187 edges`, `138 communities`; organization-level earnings/best-year wording now reaches `business_overview` while explicit customer/counterparty ranking remains in exact customer value routes
|
||||||
- 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 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 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`
|
||||||
|
|
|
||||||
|
|
@ -1077,6 +1077,9 @@ function buildWhereClause(filters, fieldPath, extraConditions = []) {
|
||||||
}
|
}
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
|
function buildBankDocumentWhereClause(filters, dateFieldPath, counterpartyFieldPath) {
|
||||||
|
return buildWhereClause(filters, dateFieldPath, [buildCounterpartyReferenceCondition(filters, [counterpartyFieldPath])].filter((item) => Boolean(item)));
|
||||||
|
}
|
||||||
function buildManagementWhereClause(filters, fieldPath) {
|
function buildManagementWhereClause(filters, fieldPath) {
|
||||||
return buildWhereClause(filters, fieldPath);
|
return buildWhereClause(filters, fieldPath);
|
||||||
}
|
}
|
||||||
|
|
@ -1398,8 +1401,8 @@ function buildAddressRecipePlan(recipe, filters) {
|
||||||
const query = recipe.query_template === "bank_docs"
|
const query = recipe.query_template === "bank_docs"
|
||||||
? BANK_DOCS_QUERY_TEMPLATE
|
? BANK_DOCS_QUERY_TEMPLATE
|
||||||
.replaceAll("__LIMIT__", String(resolvedLimit))
|
.replaceAll("__LIMIT__", String(resolvedLimit))
|
||||||
.replace("__WHERE_OUT__", buildWhereClause(filters, "БанкСписание.Дата"))
|
.replace("__WHERE_OUT__", buildBankDocumentWhereClause(filters, "БанкСписание.Дата", "БанкСписание.Контрагент"))
|
||||||
.replace("__WHERE_IN__", buildWhereClause(filters, "БанкПоступление.Дата"))
|
.replace("__WHERE_IN__", buildBankDocumentWhereClause(filters, "БанкПоступление.Дата", "БанкПоступление.Контрагент"))
|
||||||
.replaceAll("__ORDER_DIRECTION__", resolveOrderDirection(filters.sort))
|
.replaceAll("__ORDER_DIRECTION__", resolveOrderDirection(filters.sort))
|
||||||
: recipe.query_template === "period_profile"
|
: recipe.query_template === "period_profile"
|
||||||
? PERIOD_COVERAGE_PROFILE_QUERY_TEMPLATE.replaceAll("__WHERE_CLAUSE__", buildManagementWhereClause(filters, "Движения.Период"))
|
? PERIOD_COVERAGE_PROFILE_QUERY_TEMPLATE.replaceAll("__WHERE_CLAUSE__", buildManagementWhereClause(filters, "Движения.Период"))
|
||||||
|
|
@ -1407,10 +1410,10 @@ function buildAddressRecipePlan(recipe, filters) {
|
||||||
? DOCUMENT_TYPE_AND_SECTION_PROFILE_QUERY_TEMPLATE.replaceAll("__WHERE_CLAUSE__", buildManagementWhereClause(filters, "Движения.Период"))
|
? DOCUMENT_TYPE_AND_SECTION_PROFILE_QUERY_TEMPLATE.replaceAll("__WHERE_CLAUSE__", buildManagementWhereClause(filters, "Движения.Период"))
|
||||||
: recipe.query_template === "counterparty_roles_profile"
|
: recipe.query_template === "counterparty_roles_profile"
|
||||||
? COUNTERPARTY_POPULATION_AND_ROLES_QUERY_TEMPLATE
|
? COUNTERPARTY_POPULATION_AND_ROLES_QUERY_TEMPLATE
|
||||||
.replaceAll("__WHERE_OUT__", buildWhereClause(filters, "БанкСписание.Дата"))
|
.replaceAll("__WHERE_OUT__", buildBankDocumentWhereClause(filters, "БанкСписание.Дата", "БанкСписание.Контрагент"))
|
||||||
.replaceAll("__WHERE_IN__", buildWhereClause(filters, "БанкПоступление.Дата"))
|
.replaceAll("__WHERE_IN__", buildBankDocumentWhereClause(filters, "БанкПоступление.Дата", "БанкПоступление.Контрагент"))
|
||||||
: recipe.query_template === "counterparty_lifecycle_profile"
|
: recipe.query_template === "counterparty_lifecycle_profile"
|
||||||
? COUNTERPARTY_ACTIVITY_LIFECYCLE_QUERY_TEMPLATE.replaceAll("__WHERE_IN__", buildWhereClause(filters, "БанкПоступление.Дата"))
|
? COUNTERPARTY_ACTIVITY_LIFECYCLE_QUERY_TEMPLATE.replaceAll("__WHERE_IN__", buildBankDocumentWhereClause(filters, "БанкПоступление.Дата", "БанкПоступление.Контрагент"))
|
||||||
: recipe.query_template === "contract_usage_profile"
|
: recipe.query_template === "contract_usage_profile"
|
||||||
? CONTRACT_USAGE_OVERVIEW_QUERY_TEMPLATE
|
? CONTRACT_USAGE_OVERVIEW_QUERY_TEMPLATE
|
||||||
.replaceAll("__WHERE_OUT_USED__", buildUsedContractWhereClause(filters, "БанкСписание.Дата", "БанкСписание.ДоговорКонтрагента"))
|
.replaceAll("__WHERE_OUT_USED__", buildUsedContractWhereClause(filters, "БанкСписание.Дата", "БанкСписание.ДоговорКонтрагента"))
|
||||||
|
|
@ -1418,12 +1421,12 @@ function buildAddressRecipePlan(recipe, filters) {
|
||||||
: recipe.query_template === "customer_revenue_profile"
|
: recipe.query_template === "customer_revenue_profile"
|
||||||
? CUSTOMER_REVENUE_PROFILE_QUERY_TEMPLATE
|
? CUSTOMER_REVENUE_PROFILE_QUERY_TEMPLATE
|
||||||
.replaceAll("__LIMIT__", String(resolvedLimit))
|
.replaceAll("__LIMIT__", String(resolvedLimit))
|
||||||
.replaceAll("__WHERE_IN__", buildWhereClause(filters, "БанкПоступление.Дата"))
|
.replaceAll("__WHERE_IN__", buildBankDocumentWhereClause(filters, "БанкПоступление.Дата", "БанкПоступление.Контрагент"))
|
||||||
.replaceAll("__ORDER_DIRECTION__", resolveOrderDirection(filters.sort))
|
.replaceAll("__ORDER_DIRECTION__", resolveOrderDirection(filters.sort))
|
||||||
: recipe.query_template === "supplier_payout_profile"
|
: recipe.query_template === "supplier_payout_profile"
|
||||||
? SUPPLIER_PAYOUT_PROFILE_QUERY_TEMPLATE
|
? SUPPLIER_PAYOUT_PROFILE_QUERY_TEMPLATE
|
||||||
.replaceAll("__LIMIT__", String(resolvedLimit))
|
.replaceAll("__LIMIT__", String(resolvedLimit))
|
||||||
.replaceAll("__WHERE_OUT__", buildWhereClause(filters, "БанкСписание.Дата"))
|
.replaceAll("__WHERE_OUT__", buildBankDocumentWhereClause(filters, "БанкСписание.Дата", "БанкСписание.Контрагент"))
|
||||||
.replaceAll("__ORDER_DIRECTION__", resolveOrderDirection(filters.sort))
|
.replaceAll("__ORDER_DIRECTION__", resolveOrderDirection(filters.sort))
|
||||||
: recipe.query_template === "contract_value_profile"
|
: recipe.query_template === "contract_value_profile"
|
||||||
? CONTRACT_VALUE_PROFILE_QUERY_TEMPLATE
|
? CONTRACT_VALUE_PROFILE_QUERY_TEMPLATE
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,42 @@ function toRecordObject(value) {
|
||||||
}
|
}
|
||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
function sessionOrganizationName(sessionOrganizationScope, toNonEmptyString) {
|
||||||
|
const scope = toRecordObject(sessionOrganizationScope);
|
||||||
|
return toNonEmptyString(scope?.selectedOrganization) ?? toNonEmptyString(scope?.activeOrganization);
|
||||||
|
}
|
||||||
|
function predecomposeOrganizationName(predecomposeContract, toNonEmptyString) {
|
||||||
|
const entities = toRecordObject(predecomposeContract?.entities);
|
||||||
|
return (toNonEmptyString(entities?.organization) ??
|
||||||
|
toNonEmptyString(predecomposeContract?.organization));
|
||||||
|
}
|
||||||
|
function mergeOrganizationIntoDiscoveryFollowupContext(followupContext, organization) {
|
||||||
|
if (!organization) {
|
||||||
|
return followupContext;
|
||||||
|
}
|
||||||
|
const base = followupContext ? { ...followupContext } : {};
|
||||||
|
const previousFilters = toRecordObject(base.previous_filters)
|
||||||
|
? { ...base.previous_filters }
|
||||||
|
: {};
|
||||||
|
if (!previousFilters.organization) {
|
||||||
|
previousFilters.organization = organization;
|
||||||
|
}
|
||||||
|
base.previous_filters = previousFilters;
|
||||||
|
const rootFilters = toRecordObject(base.root_filters)
|
||||||
|
? { ...base.root_filters }
|
||||||
|
: {};
|
||||||
|
if (!rootFilters.organization) {
|
||||||
|
rootFilters.organization = organization;
|
||||||
|
}
|
||||||
|
base.root_filters = rootFilters;
|
||||||
|
if (!base.previous_anchor_type) {
|
||||||
|
base.previous_anchor_type = "organization";
|
||||||
|
}
|
||||||
|
if (!base.previous_anchor_value) {
|
||||||
|
base.previous_anchor_value = organization;
|
||||||
|
}
|
||||||
|
return base;
|
||||||
|
}
|
||||||
function hasSelectedObjectInventorySignal(text) {
|
function hasSelectedObjectInventorySignal(text) {
|
||||||
return /(?:по\s+выбранному\s+объекту|по\s+выбранной\s+позиции|по\s+этой\s+позиции|по\s+этому\s+товару|по\s+ним|selected\s+object)/iu.test(String(text ?? ""));
|
return /(?:по\s+выбранному\s+объекту|по\s+выбранной\s+позиции|по\s+этой\s+позиции|по\s+этому\s+товару|по\s+ним|selected\s+object)/iu.test(String(text ?? ""));
|
||||||
}
|
}
|
||||||
|
|
@ -155,6 +191,10 @@ async function buildAssistantAddressOrchestrationRuntime(input) {
|
||||||
const orchestrationDecision = routePolicyRuntime.orchestrationDecision;
|
const orchestrationDecision = routePolicyRuntime.orchestrationDecision;
|
||||||
const orchestrationContract = toRecordObject(orchestrationDecision.orchestrationContract);
|
const orchestrationContract = toRecordObject(orchestrationDecision.orchestrationContract);
|
||||||
const predecomposeContract = toRecordObject(addressPreDecompose.predecomposeContract);
|
const predecomposeContract = toRecordObject(addressPreDecompose.predecomposeContract);
|
||||||
|
const explicitPredecomposeOrganization = predecomposeOrganizationName(predecomposeContract, input.toNonEmptyString);
|
||||||
|
const discoveryFollowupContext = mergeOrganizationIntoDiscoveryFollowupContext(followupContext, explicitPredecomposeOrganization
|
||||||
|
? null
|
||||||
|
: sessionOrganizationName(input.sessionOrganizationScope ?? null, input.toNonEmptyString));
|
||||||
const dialogContinuationContract = input.buildAddressDialogContinuationContractV2(input.userMessage, addressInputMessage, carryover, addressPreDecompose);
|
const dialogContinuationContract = input.buildAddressDialogContinuationContractV2(input.userMessage, addressInputMessage, carryover, addressPreDecompose);
|
||||||
const runDiscoveryEntryPoint = input.runMcpDiscoveryRuntimeEntryPoint ?? assistantMcpDiscoveryRuntimeEntryPoint_1.runAssistantMcpDiscoveryRuntimeEntryPoint;
|
const runDiscoveryEntryPoint = input.runMcpDiscoveryRuntimeEntryPoint ?? assistantMcpDiscoveryRuntimeEntryPoint_1.runAssistantMcpDiscoveryRuntimeEntryPoint;
|
||||||
let mcpDiscoveryRuntimeEntryPoint = null;
|
let mcpDiscoveryRuntimeEntryPoint = null;
|
||||||
|
|
@ -165,7 +205,7 @@ async function buildAssistantAddressOrchestrationRuntime(input) {
|
||||||
effectiveMessage: addressInputMessage,
|
effectiveMessage: addressInputMessage,
|
||||||
assistantTurnMeaning: toRecordObject(orchestrationContract?.assistant_turn_meaning),
|
assistantTurnMeaning: toRecordObject(orchestrationContract?.assistant_turn_meaning),
|
||||||
predecomposeContract,
|
predecomposeContract,
|
||||||
followupContext
|
followupContext: discoveryFollowupContext
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
catch (error) {
|
catch (error) {
|
||||||
|
|
|
||||||
|
|
@ -117,6 +117,10 @@ function isValueFlowPilot(pilot) {
|
||||||
pilot.pilot_scope === "counterparty_supplier_payout_query_movements_v1" ||
|
pilot.pilot_scope === "counterparty_supplier_payout_query_movements_v1" ||
|
||||||
pilot.pilot_scope === "counterparty_bidirectional_value_flow_query_movements_v1");
|
pilot.pilot_scope === "counterparty_bidirectional_value_flow_query_movements_v1");
|
||||||
}
|
}
|
||||||
|
function isSingleDirectionValueFlowPilot(pilot) {
|
||||||
|
return (pilot.pilot_scope === "counterparty_value_flow_query_movements_v1" ||
|
||||||
|
pilot.pilot_scope === "counterparty_supplier_payout_query_movements_v1");
|
||||||
|
}
|
||||||
function isBusinessOverviewPilot(pilot) {
|
function isBusinessOverviewPilot(pilot) {
|
||||||
return pilot.pilot_scope === "business_overview_route_template_v1";
|
return pilot.pilot_scope === "business_overview_route_template_v1";
|
||||||
}
|
}
|
||||||
|
|
@ -191,6 +195,52 @@ function explicitOrganizationScope(pilot) {
|
||||||
const normalized = value.trim();
|
const normalized = value.trim();
|
||||||
return normalized.length > 0 ? normalized : null;
|
return normalized.length > 0 ? normalized : null;
|
||||||
}
|
}
|
||||||
|
function hasExecutedZeroValueFlowRows(pilot) {
|
||||||
|
const summary = pilot.source_rows_summary ?? "";
|
||||||
|
return (pilot.mcp_execution_performed &&
|
||||||
|
isSingleDirectionValueFlowPilot(pilot) &&
|
||||||
|
!pilot.derived_value_flow &&
|
||||||
|
(/0\s+MCP\s+value-flow\s+rows\s+fetched/i.test(summary) ||
|
||||||
|
/\b0\s+matched\s+value-flow\s+scope\b/i.test(summary)));
|
||||||
|
}
|
||||||
|
function valueFlowDirectionLabelRu(pilot) {
|
||||||
|
return pilot.pilot_scope === "counterparty_supplier_payout_query_movements_v1"
|
||||||
|
? "исходящих платежей/списаний"
|
||||||
|
: "входящих денежных поступлений";
|
||||||
|
}
|
||||||
|
function valueFlowZeroResultConfirmedLine(pilot) {
|
||||||
|
if (!hasExecutedZeroValueFlowRows(pilot)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
const counterparty = firstEntityCandidate(pilot);
|
||||||
|
if (!counterparty) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
const organization = explicitOrganizationScope(pilot);
|
||||||
|
const period = explicitDateScope(pilot);
|
||||||
|
const organizationPart = organization ? ` по организации ${organization}` : "";
|
||||||
|
const periodPart = period ? ` за период ${period}` : " в проверенном окне";
|
||||||
|
return `В проверенном срезе 1С по контрагенту ${counterparty}${organizationPart}${periodPart} ${valueFlowDirectionLabelRu(pilot)} не найдено.`;
|
||||||
|
}
|
||||||
|
function valueFlowZeroResultUnknownLine(pilot) {
|
||||||
|
if (!hasExecutedZeroValueFlowRows(pilot)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
const counterparty = firstEntityCandidate(pilot);
|
||||||
|
if (!counterparty) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
const period = explicitDateScope(pilot);
|
||||||
|
const periodPart = period ? ` вне периода ${period}` : " вне проверенного окна";
|
||||||
|
return `Это не доказывает отсутствие операций с контрагентом ${counterparty}${periodPart} или вне доступного банковского контура.`;
|
||||||
|
}
|
||||||
|
function valueFlowZeroResultHeadline(pilot) {
|
||||||
|
const confirmedLine = valueFlowZeroResultConfirmedLine(pilot);
|
||||||
|
if (!confirmedLine) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return confirmedLine;
|
||||||
|
}
|
||||||
function hasAllTimeScope(pilot) {
|
function hasAllTimeScope(pilot) {
|
||||||
return (dryRunHasAxis(pilot, "all_time_scope") ||
|
return (dryRunHasAxis(pilot, "all_time_scope") ||
|
||||||
pilot.reason_codes.includes("mcp_discovery_all_time_scope_signal_detected") ||
|
pilot.reason_codes.includes("mcp_discovery_all_time_scope_signal_detected") ||
|
||||||
|
|
@ -497,6 +547,10 @@ function headlineFor(mode, pilot) {
|
||||||
}
|
}
|
||||||
return "По данным 1С найдены строки входящих денежных поступлений; сумму можно называть только в рамках проверенного периода и найденных строк.";
|
return "По данным 1С найдены строки входящих денежных поступлений; сумму можно называть только в рамках проверенного периода и найденных строк.";
|
||||||
}
|
}
|
||||||
|
const zeroValueFlowHeadline = valueFlowZeroResultHeadline(pilot);
|
||||||
|
if (mode === "checked_sources_only" && zeroValueFlowHeadline) {
|
||||||
|
return zeroValueFlowHeadline;
|
||||||
|
}
|
||||||
if (isDocumentPilot(pilot) && mode === "confirmed_with_bounded_inference") {
|
if (isDocumentPilot(pilot) && mode === "confirmed_with_bounded_inference") {
|
||||||
return `По документам${documentOrMovementScopeRu(pilot)} в 1С найдены подтвержденные строки; ответ ограничен проверенным окном и найденными строками.`;
|
return `По документам${documentOrMovementScopeRu(pilot)} в 1С найдены подтвержденные строки; ответ ограничен проверенным окном и найденными строками.`;
|
||||||
}
|
}
|
||||||
|
|
@ -1275,6 +1329,10 @@ function businessOverviewUnknownLines(pilot) {
|
||||||
}
|
}
|
||||||
return userFacingUnknowns(pilot.evidence.unknown_facts);
|
return userFacingUnknowns(pilot.evidence.unknown_facts);
|
||||||
}
|
}
|
||||||
|
function appendValueFlowZeroResultUnknown(lines, pilot) {
|
||||||
|
const zeroLine = valueFlowZeroResultUnknownLine(pilot);
|
||||||
|
return zeroLine ? uniqueStrings([zeroLine, ...lines]) : lines;
|
||||||
|
}
|
||||||
function buildAssistantMcpDiscoveryAnswerDraft(pilot) {
|
function buildAssistantMcpDiscoveryAnswerDraft(pilot) {
|
||||||
const mode = modeFor(pilot);
|
const mode = modeFor(pilot);
|
||||||
const reasonCodes = [...pilot.reason_codes, ...pilot.evidence.reason_codes];
|
const reasonCodes = [...pilot.reason_codes, ...pilot.evidence.reason_codes];
|
||||||
|
|
@ -1364,18 +1422,20 @@ function buildAssistantMcpDiscoveryAnswerDraft(pilot) {
|
||||||
? [derivedValueLine]
|
? [derivedValueLine]
|
||||||
: derivedValueLine
|
: derivedValueLine
|
||||||
? [...pilot.evidence.confirmed_facts, derivedValueLine, ...monthlyConfirmedLines]
|
? [...pilot.evidence.confirmed_facts, derivedValueLine, ...monthlyConfirmedLines]
|
||||||
: derivedEntityResolutionLine
|
: valueFlowZeroResultConfirmedLine(pilot)
|
||||||
? [...pilot.evidence.confirmed_facts, derivedEntityResolutionLine]
|
? [valueFlowZeroResultConfirmedLine(pilot)]
|
||||||
: derivedMetadataLine
|
: derivedEntityResolutionLine
|
||||||
? [derivedMetadataLine]
|
? [...pilot.evidence.confirmed_facts, derivedEntityResolutionLine]
|
||||||
: pilot.evidence.confirmed_facts;
|
: derivedMetadataLine
|
||||||
|
? [derivedMetadataLine]
|
||||||
|
: pilot.evidence.confirmed_facts;
|
||||||
const unknownLines = pilot.derived_business_overview
|
const unknownLines = pilot.derived_business_overview
|
||||||
? businessOverviewUnknownLines(pilot)
|
? businessOverviewUnknownLines(pilot)
|
||||||
: pilot.derived_metadata_surface
|
: pilot.derived_metadata_surface
|
||||||
? pilot.derived_metadata_surface.available_fields.length > 0
|
? pilot.derived_metadata_surface.available_fields.length > 0
|
||||||
? userFacingUnknowns(pilot.evidence.unknown_facts)
|
? userFacingUnknowns(pilot.evidence.unknown_facts)
|
||||||
: ["Детальный список полей этих объектов этим шагом не получен."]
|
: ["Детальный список полей этих объектов этим шагом не получен."]
|
||||||
: rankedValueFlowUnknownLines(pilot);
|
: appendValueFlowZeroResultUnknown(rankedValueFlowUnknownLines(pilot), pilot);
|
||||||
return {
|
return {
|
||||||
schema_version: exports.ASSISTANT_MCP_DISCOVERY_ANSWER_DRAFT_SCHEMA_VERSION,
|
schema_version: exports.ASSISTANT_MCP_DISCOVERY_ANSWER_DRAFT_SCHEMA_VERSION,
|
||||||
policy_owner: "assistantMcpDiscoveryAnswerAdapter",
|
policy_owner: "assistantMcpDiscoveryAnswerAdapter",
|
||||||
|
|
|
||||||
|
|
@ -171,7 +171,9 @@ function pushScopedEntityCandidate(target, value, groundedFollowupEntity) {
|
||||||
if (!text) {
|
if (!text) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if ((groundedFollowupEntity && isReferentialEntityPlaceholder(text)) || isValueFlowPredicateEntityCandidate(text)) {
|
if (isInvalidEntityCandidate(text) ||
|
||||||
|
(groundedFollowupEntity && isReferentialEntityPlaceholder(text)) ||
|
||||||
|
isValueFlowPredicateEntityCandidate(text)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
pushUnique(target, text);
|
pushUnique(target, text);
|
||||||
|
|
@ -652,7 +654,13 @@ function hasBusinessOverviewContinuationSignal(text) {
|
||||||
const hasAnalystContinuationCue = /(?:можно\s+ли|если\s+нет|proxy|прокси|аудит|оцен|что\s+думаешь|нормальн\p{L}*\s+прибыл|прибыл|марж|рентаб|ликвидн|просроч|качество\s+долг|риск|налогов\p{L}*\s+вывод)/iu.test(normalized);
|
const hasAnalystContinuationCue = /(?:можно\s+ли|если\s+нет|proxy|прокси|аудит|оцен|что\s+думаешь|нормальн\p{L}*\s+прибыл|прибыл|марж|рентаб|ликвидн|просроч|качество\s+долг|риск|налогов\p{L}*\s+вывод)/iu.test(normalized);
|
||||||
const hasTaxContinuationCue = /(?:ндс|vat)[\s\S]{0,120}(?:позици|период|основан|не\s+хватает|налогов\p{L}*\s+вывод)|(?:позици|налогов\p{L}*\s+вывод)[\s\S]{0,80}(?:ндс|vat)/iu.test(normalized);
|
const hasTaxContinuationCue = /(?:ндс|vat)[\s\S]{0,120}(?:позици|период|основан|не\s+хватает|налогов\p{L}*\s+вывод)|(?:позици|налогов\p{L}*\s+вывод)[\s\S]{0,80}(?:ндс|vat)/iu.test(normalized);
|
||||||
const hasFinalSummaryCue = /(?:\u0447\u0442\u043e\s+\u043c\u044b\s+\u0437\u043d\u0430\u0435\u043c|\u0447\u0442\u043e\s+\u043f\u043e\u043d\u044f\u0442\u043d\u043e|\u0447\u0442\u043e\s+\u043f\u0440\u043e\u0432\u0435\u0440\w*\s+\u0434\u0430\u043b\u044c\u0448\u0435|\u0441\u043b\u0435\u0434\u0443\u044e\u0449\w*\s+\u0448\u0430\u0433|\u0438\u0442\u043e\u0433\w*\s+\u0432\u044b\u0432\u043e\u0434|\u043a\u0430\u043a\u043e\u0439\s+\u0432\u044b\u0432\u043e\u0434|\u0447\u0442\u043e\s+\u0441\s+\u044d\u0442\u0438\u043c\s+\u0434\u0435\u043b\u0430\u0442\u044c|what\s+do\s+we\s+know|what\s+is\s+missing|next\s+step|final\s+summary)/iu.test(normalized);
|
const hasFinalSummaryCue = /(?:\u0447\u0442\u043e\s+\u043c\u044b\s+\u0437\u043d\u0430\u0435\u043c|\u0447\u0442\u043e\s+\u043f\u043e\u043d\u044f\u0442\u043d\u043e|\u0447\u0442\u043e\s+\u043f\u0440\u043e\u0432\u0435\u0440\w*\s+\u0434\u0430\u043b\u044c\u0448\u0435|\u0441\u043b\u0435\u0434\u0443\u044e\u0449\w*\s+\u0448\u0430\u0433|\u0438\u0442\u043e\u0433\w*\s+\u0432\u044b\u0432\u043e\u0434|\u043a\u0430\u043a\u043e\u0439\s+\u0432\u044b\u0432\u043e\u0434|\u0447\u0442\u043e\s+\u0441\s+\u044d\u0442\u0438\u043c\s+\u0434\u0435\u043b\u0430\u0442\u044c|what\s+do\s+we\s+know|what\s+is\s+missing|next\s+step|final\s+summary)/iu.test(normalized);
|
||||||
return hasEvidenceContinuationCue || hasAnalystContinuationCue || hasTaxContinuationCue || hasFinalSummaryCue;
|
const hasMoneyBreakdownCue = /(?:\u0440\u0430\u0441\u043a\u0440\u043e\p{L}*\s+\u0434\u0435\u043d\p{L}*|\u0441\u043a\u043e\u043b\u044c\u043a\u043e\s+\u0432\u0441\u0435\u0433\u043e\s+\u043f\u043e\u043b\u0443\u0447|\u0441\u043a\u043e\u043b\u044c\u043a\u043e\s+(?:\u0432\u0441\u0435\u0433\u043e\s+)?\u0437\u0430\u043f\u043b\u0430\u0442|\u0447\u0438\u0441\u0442\p{L}*\s+\u0434\u0435\u043d\u0435\u0436\u043d\p{L}*\s+\u043f\u043e\u0442\u043e\u043a|\u0433\u043b\u0430\u0432\u043d\p{L}*\s+(?:\u043a\u043b\u0438\u0435\u043d\u0442|\u043f\u043e\u0441\u0442\u0430\u0432\u0449\u0438\u043a)|top\s+(?:customer|supplier)|cash\s+breakdown)/iu.test(normalized) &&
|
||||||
|
/(?:\u043f\u043e\u043b\u0443\u0447|\u0437\u0430\u043f\u043b\u0430\u0442|\u043d\u0435\u0442\u0442\u043e|\u0434\u0435\u043d\p{L}*|\u043a\u043b\u0438\u0435\u043d\u0442|\u043f\u043e\u0441\u0442\u0430\u0432\u0449\u0438\u043a|received|paid|net|cash|customer|supplier)/iu.test(normalized);
|
||||||
|
return (hasEvidenceContinuationCue ||
|
||||||
|
hasAnalystContinuationCue ||
|
||||||
|
hasTaxContinuationCue ||
|
||||||
|
hasFinalSummaryCue ||
|
||||||
|
hasMoneyBreakdownCue);
|
||||||
}
|
}
|
||||||
function hasExplicitTopicSwitchSignal(text) {
|
function hasExplicitTopicSwitchSignal(text) {
|
||||||
return /(?:^|\s)(?:\u0442\u0435\u043f\u0435\u0440\u044c|\u0430\s+\u0442\u0435\u043f\u0435\u0440\u044c|\u0434\u0430\u043b\u044c\u0448\u0435|\u043e\u0442\u0434\u0435\u043b\u044c\u043d\u043e|\u043f\u0435\u0440\u0435\u0439\u0434[\u0451\u0435]\u043c|\u0441\u043c\u0435\u043d\u0438\u043c\s+\u0442\u0435\u043c\u0443|\u0432\u0435\u0440\u043d[\u0451\u0435]\u043c\u0441\u044f\s+\u043a|now|next|switch\s+to)(?:\s|$)/iu.test(text);
|
return /(?:^|\s)(?:\u0442\u0435\u043f\u0435\u0440\u044c|\u0430\s+\u0442\u0435\u043f\u0435\u0440\u044c|\u0434\u0430\u043b\u044c\u0448\u0435|\u043e\u0442\u0434\u0435\u043b\u044c\u043d\u043e|\u043f\u0435\u0440\u0435\u0439\u0434[\u0451\u0435]\u043c|\u0441\u043c\u0435\u043d\u0438\u043c\s+\u0442\u0435\u043c\u0443|\u0432\u0435\u0440\u043d[\u0451\u0435]\u043c\u0441\u044f\s+\u043a|now|next|switch\s+to)(?:\s|$)/iu.test(text);
|
||||||
|
|
@ -1134,7 +1142,7 @@ function buildAssistantMcpDiscoveryTurnInput(input) {
|
||||||
predecomposeOrganizationMirrorsCounterparty));
|
predecomposeOrganizationMirrorsCounterparty));
|
||||||
const normalizedPredecomposeCounterparty = organizationMirrorsPredecomposeCounterparty
|
const normalizedPredecomposeCounterparty = organizationMirrorsPredecomposeCounterparty
|
||||||
? null
|
? null
|
||||||
: predecomposeEntities.counterparty;
|
: normalizeFollowupCounterpartyCandidate(predecomposeEntities.counterparty);
|
||||||
const predecomposeDateScope = collectDateScope(predecomposeContract);
|
const predecomposeDateScope = collectDateScope(predecomposeContract);
|
||||||
const periodClarificationFollowupApplicable = Boolean(followupSeed.domain &&
|
const periodClarificationFollowupApplicable = Boolean(followupSeed.domain &&
|
||||||
followupSeed.loopStatus === "awaiting_clarification" &&
|
followupSeed.loopStatus === "awaiting_clarification" &&
|
||||||
|
|
|
||||||
|
|
@ -164,6 +164,18 @@ function hasOrganizationLevelSupplierQualityOverviewSignal(text) {
|
||||||
const hasCompanyScopeCue = /(?:\u0443\s+\u043d\u0430\u0441|\u043d\u0430\u0448\w*|\u043f\u043e\s+\u043a\u043e\u043c\u043f\u0430\u043d|\u043a\u043e\u043c\u043f\u0430\u043d|\u043e\u0440\u0433\u0430\u043d\u0438\u0437\u0430\u0446|\u0431\u0438\u0437\u043d\u0435\u0441|\u0432\s+\u0446\u0435\u043b\u043e\u043c|\u043e\u0431\u0449\w*|\u043a\u0430\u043a\w*|\u043f\u043e\u043a\u0430\u0436|\u0434\u0430\u0439|\u0441\u0440\u0435\u0437|\u0430\u043d\u0430\u043b\u0438\u0437|(?:19|20)\d{2}|company|business|organization|overall|our|we|us|show|give|analysis)/iu.test(normalized);
|
const hasCompanyScopeCue = /(?:\u0443\s+\u043d\u0430\u0441|\u043d\u0430\u0448\w*|\u043f\u043e\s+\u043a\u043e\u043c\u043f\u0430\u043d|\u043a\u043e\u043c\u043f\u0430\u043d|\u043e\u0440\u0433\u0430\u043d\u0438\u0437\u0430\u0446|\u0431\u0438\u0437\u043d\u0435\u0441|\u0432\s+\u0446\u0435\u043b\u043e\u043c|\u043e\u0431\u0449\w*|\u043a\u0430\u043a\w*|\u043f\u043e\u043a\u0430\u0436|\u0434\u0430\u0439|\u0441\u0440\u0435\u0437|\u0430\u043d\u0430\u043b\u0438\u0437|(?:19|20)\d{2}|company|business|organization|overall|our|we|us|show|give|analysis)/iu.test(normalized);
|
||||||
return hasSupplierScopeCue && hasSupplierQualityCue && hasCompanyScopeCue;
|
return hasSupplierScopeCue && hasSupplierQualityCue && hasCompanyScopeCue;
|
||||||
}
|
}
|
||||||
|
function hasOrganizationLevelMoneyBreakdownSignal(text) {
|
||||||
|
const normalized = String(text ?? "");
|
||||||
|
if (!normalized) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const hasIncomingCue = /(?:\u043f\u043e\u043b\u0443\u0447|\u0432\u0445\u043e\u0434\u044f\u0449|\u043f\u043e\u0441\u0442\u0443\u043f|\u043a\u043b\u0438\u0435\u043d\u0442|received|incoming|customer)/iu.test(normalized);
|
||||||
|
const hasOutgoingCue = /(?:\u0437\u0430\u043f\u043b\u0430\u0442|\u0438\u0441\u0445\u043e\u0434\u044f\u0449|\u0441\u043f\u0438\u0441\u0430\u043d|\u043f\u043e\u0441\u0442\u0430\u0432\u0449\u0438\u043a|paid|outgoing|supplier)/iu.test(normalized);
|
||||||
|
const hasNetCue = /(?:\u043d\u0435\u0442\u0442\u043e|\u0447\u0438\u0441\u0442\p{L}*\s+\u0434\u0435\u043d\u0435\u0436\u043d\p{L}*\s+\u043f\u043e\u0442\u043e\u043a|net\s+(?:cash|flow)|cash\s+flow)/iu.test(normalized);
|
||||||
|
const hasRankingCue = /(?:\u0433\u043b\u0430\u0432\u043d\p{L}*\s+(?:\u043a\u043b\u0438\u0435\u043d\u0442|\u043f\u043e\u0441\u0442\u0430\u0432\u0449\u0438\u043a)|top\s+(?:customer|supplier))/iu.test(normalized);
|
||||||
|
const hasBreakdownCue = /(?:\u0440\u0430\u0441\u043a\u0440\u043e\p{L}*|\u043f\u043e\u0434\u0440\u043e\u0431\u043d|\u0441\u043a\u043e\u043b\u044c\u043a\u043e\s+\u0432\u0441\u0435\u0433\u043e|\u0441\u0432\u043e\u0434\p{L}*|breakdown|detail)/iu.test(normalized);
|
||||||
|
return hasBreakdownCue && hasIncomingCue && hasOutgoingCue && (hasNetCue || hasRankingCue);
|
||||||
|
}
|
||||||
function detectBroadBusinessEvaluation(text) {
|
function detectBroadBusinessEvaluation(text) {
|
||||||
const normalized = String(text ?? "");
|
const normalized = String(text ?? "");
|
||||||
if (!normalized) {
|
if (!normalized) {
|
||||||
|
|
@ -199,6 +211,11 @@ function detectBroadBusinessEvaluation(text) {
|
||||||
family: "broad_business_evaluation"
|
family: "broad_business_evaluation"
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
if (hasOrganizationLevelMoneyBreakdownSignal(normalized)) {
|
||||||
|
return {
|
||||||
|
family: "broad_business_evaluation"
|
||||||
|
};
|
||||||
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
function buildEntityCandidates(counterpartyTurnover) {
|
function buildEntityCandidates(counterpartyTurnover) {
|
||||||
|
|
|
||||||
|
|
@ -1116,6 +1116,20 @@ function buildWhereClause(filters: AddressFilterSet, fieldPath: string, extraCon
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function buildBankDocumentWhereClause(
|
||||||
|
filters: AddressFilterSet,
|
||||||
|
dateFieldPath: string,
|
||||||
|
counterpartyFieldPath: string
|
||||||
|
): string {
|
||||||
|
return buildWhereClause(
|
||||||
|
filters,
|
||||||
|
dateFieldPath,
|
||||||
|
[buildCounterpartyReferenceCondition(filters, [counterpartyFieldPath])].filter((item): item is string =>
|
||||||
|
Boolean(item)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
function buildManagementWhereClause(filters: AddressFilterSet, fieldPath: string): string {
|
function buildManagementWhereClause(filters: AddressFilterSet, fieldPath: string): string {
|
||||||
return buildWhereClause(filters, fieldPath);
|
return buildWhereClause(filters, fieldPath);
|
||||||
}
|
}
|
||||||
|
|
@ -1538,8 +1552,14 @@ export function buildAddressRecipePlan(
|
||||||
recipe.query_template === "bank_docs"
|
recipe.query_template === "bank_docs"
|
||||||
? BANK_DOCS_QUERY_TEMPLATE
|
? BANK_DOCS_QUERY_TEMPLATE
|
||||||
.replaceAll("__LIMIT__", String(resolvedLimit))
|
.replaceAll("__LIMIT__", String(resolvedLimit))
|
||||||
.replace("__WHERE_OUT__", buildWhereClause(filters, "БанкСписание.Дата"))
|
.replace(
|
||||||
.replace("__WHERE_IN__", buildWhereClause(filters, "БанкПоступление.Дата"))
|
"__WHERE_OUT__",
|
||||||
|
buildBankDocumentWhereClause(filters, "БанкСписание.Дата", "БанкСписание.Контрагент")
|
||||||
|
)
|
||||||
|
.replace(
|
||||||
|
"__WHERE_IN__",
|
||||||
|
buildBankDocumentWhereClause(filters, "БанкПоступление.Дата", "БанкПоступление.Контрагент")
|
||||||
|
)
|
||||||
.replaceAll("__ORDER_DIRECTION__", resolveOrderDirection(filters.sort))
|
.replaceAll("__ORDER_DIRECTION__", resolveOrderDirection(filters.sort))
|
||||||
: recipe.query_template === "period_profile"
|
: recipe.query_template === "period_profile"
|
||||||
? PERIOD_COVERAGE_PROFILE_QUERY_TEMPLATE.replaceAll(
|
? PERIOD_COVERAGE_PROFILE_QUERY_TEMPLATE.replaceAll(
|
||||||
|
|
@ -1553,12 +1573,18 @@ export function buildAddressRecipePlan(
|
||||||
)
|
)
|
||||||
: recipe.query_template === "counterparty_roles_profile"
|
: recipe.query_template === "counterparty_roles_profile"
|
||||||
? COUNTERPARTY_POPULATION_AND_ROLES_QUERY_TEMPLATE
|
? COUNTERPARTY_POPULATION_AND_ROLES_QUERY_TEMPLATE
|
||||||
.replaceAll("__WHERE_OUT__", buildWhereClause(filters, "БанкСписание.Дата"))
|
.replaceAll(
|
||||||
.replaceAll("__WHERE_IN__", buildWhereClause(filters, "БанкПоступление.Дата"))
|
"__WHERE_OUT__",
|
||||||
|
buildBankDocumentWhereClause(filters, "БанкСписание.Дата", "БанкСписание.Контрагент")
|
||||||
|
)
|
||||||
|
.replaceAll(
|
||||||
|
"__WHERE_IN__",
|
||||||
|
buildBankDocumentWhereClause(filters, "БанкПоступление.Дата", "БанкПоступление.Контрагент")
|
||||||
|
)
|
||||||
: recipe.query_template === "counterparty_lifecycle_profile"
|
: recipe.query_template === "counterparty_lifecycle_profile"
|
||||||
? COUNTERPARTY_ACTIVITY_LIFECYCLE_QUERY_TEMPLATE.replaceAll(
|
? COUNTERPARTY_ACTIVITY_LIFECYCLE_QUERY_TEMPLATE.replaceAll(
|
||||||
"__WHERE_IN__",
|
"__WHERE_IN__",
|
||||||
buildWhereClause(filters, "БанкПоступление.Дата")
|
buildBankDocumentWhereClause(filters, "БанкПоступление.Дата", "БанкПоступление.Контрагент")
|
||||||
)
|
)
|
||||||
: recipe.query_template === "contract_usage_profile"
|
: recipe.query_template === "contract_usage_profile"
|
||||||
? CONTRACT_USAGE_OVERVIEW_QUERY_TEMPLATE
|
? CONTRACT_USAGE_OVERVIEW_QUERY_TEMPLATE
|
||||||
|
|
@ -1573,12 +1599,18 @@ export function buildAddressRecipePlan(
|
||||||
: recipe.query_template === "customer_revenue_profile"
|
: recipe.query_template === "customer_revenue_profile"
|
||||||
? CUSTOMER_REVENUE_PROFILE_QUERY_TEMPLATE
|
? CUSTOMER_REVENUE_PROFILE_QUERY_TEMPLATE
|
||||||
.replaceAll("__LIMIT__", String(resolvedLimit))
|
.replaceAll("__LIMIT__", String(resolvedLimit))
|
||||||
.replaceAll("__WHERE_IN__", buildWhereClause(filters, "БанкПоступление.Дата"))
|
.replaceAll(
|
||||||
|
"__WHERE_IN__",
|
||||||
|
buildBankDocumentWhereClause(filters, "БанкПоступление.Дата", "БанкПоступление.Контрагент")
|
||||||
|
)
|
||||||
.replaceAll("__ORDER_DIRECTION__", resolveOrderDirection(filters.sort))
|
.replaceAll("__ORDER_DIRECTION__", resolveOrderDirection(filters.sort))
|
||||||
: recipe.query_template === "supplier_payout_profile"
|
: recipe.query_template === "supplier_payout_profile"
|
||||||
? SUPPLIER_PAYOUT_PROFILE_QUERY_TEMPLATE
|
? SUPPLIER_PAYOUT_PROFILE_QUERY_TEMPLATE
|
||||||
.replaceAll("__LIMIT__", String(resolvedLimit))
|
.replaceAll("__LIMIT__", String(resolvedLimit))
|
||||||
.replaceAll("__WHERE_OUT__", buildWhereClause(filters, "БанкСписание.Дата"))
|
.replaceAll(
|
||||||
|
"__WHERE_OUT__",
|
||||||
|
buildBankDocumentWhereClause(filters, "БанкСписание.Дата", "БанкСписание.Контрагент")
|
||||||
|
)
|
||||||
.replaceAll("__ORDER_DIRECTION__", resolveOrderDirection(filters.sort))
|
.replaceAll("__ORDER_DIRECTION__", resolveOrderDirection(filters.sort))
|
||||||
: recipe.query_template === "contract_value_profile"
|
: recipe.query_template === "contract_value_profile"
|
||||||
? CONTRACT_VALUE_PROFILE_QUERY_TEMPLATE
|
? CONTRACT_VALUE_PROFILE_QUERY_TEMPLATE
|
||||||
|
|
|
||||||
|
|
@ -76,6 +76,60 @@ function toRecordObject(value: unknown): Record<string, unknown> | null {
|
||||||
return value as Record<string, unknown>;
|
return value as Record<string, unknown>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function sessionOrganizationName(
|
||||||
|
sessionOrganizationScope: BuildAssistantAddressOrchestrationRuntimeInput["sessionOrganizationScope"],
|
||||||
|
toNonEmptyString: BuildAssistantAddressOrchestrationRuntimeInput["toNonEmptyString"]
|
||||||
|
): string | null {
|
||||||
|
const scope = toRecordObject(sessionOrganizationScope);
|
||||||
|
return toNonEmptyString(scope?.selectedOrganization) ?? toNonEmptyString(scope?.activeOrganization);
|
||||||
|
}
|
||||||
|
|
||||||
|
function predecomposeOrganizationName(
|
||||||
|
predecomposeContract: Record<string, unknown> | null,
|
||||||
|
toNonEmptyString: BuildAssistantAddressOrchestrationRuntimeInput["toNonEmptyString"]
|
||||||
|
): string | null {
|
||||||
|
const entities = toRecordObject(predecomposeContract?.entities);
|
||||||
|
return (
|
||||||
|
toNonEmptyString(entities?.organization) ??
|
||||||
|
toNonEmptyString(predecomposeContract?.organization)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function mergeOrganizationIntoDiscoveryFollowupContext(
|
||||||
|
followupContext: Record<string, unknown> | null,
|
||||||
|
organization: string | null
|
||||||
|
): Record<string, unknown> | null {
|
||||||
|
if (!organization) {
|
||||||
|
return followupContext;
|
||||||
|
}
|
||||||
|
|
||||||
|
const base = followupContext ? { ...followupContext } : {};
|
||||||
|
const previousFilters = toRecordObject(base.previous_filters)
|
||||||
|
? { ...(base.previous_filters as Record<string, unknown>) }
|
||||||
|
: {};
|
||||||
|
if (!previousFilters.organization) {
|
||||||
|
previousFilters.organization = organization;
|
||||||
|
}
|
||||||
|
base.previous_filters = previousFilters;
|
||||||
|
|
||||||
|
const rootFilters = toRecordObject(base.root_filters)
|
||||||
|
? { ...(base.root_filters as Record<string, unknown>) }
|
||||||
|
: {};
|
||||||
|
if (!rootFilters.organization) {
|
||||||
|
rootFilters.organization = organization;
|
||||||
|
}
|
||||||
|
base.root_filters = rootFilters;
|
||||||
|
|
||||||
|
if (!base.previous_anchor_type) {
|
||||||
|
base.previous_anchor_type = "organization";
|
||||||
|
}
|
||||||
|
if (!base.previous_anchor_value) {
|
||||||
|
base.previous_anchor_value = organization;
|
||||||
|
}
|
||||||
|
|
||||||
|
return base;
|
||||||
|
}
|
||||||
|
|
||||||
function hasSelectedObjectInventorySignal(text: string | null): boolean {
|
function hasSelectedObjectInventorySignal(text: string | null): boolean {
|
||||||
return /(?:по\s+выбранному\s+объекту|по\s+выбранной\s+позиции|по\s+этой\s+позиции|по\s+этому\s+товару|по\s+ним|selected\s+object)/iu.test(
|
return /(?:по\s+выбранному\s+объекту|по\s+выбранной\s+позиции|по\s+этой\s+позиции|по\s+этому\s+товару|по\s+ним|selected\s+object)/iu.test(
|
||||||
String(text ?? "")
|
String(text ?? "")
|
||||||
|
|
@ -308,6 +362,13 @@ export async function buildAssistantAddressOrchestrationRuntime(
|
||||||
const orchestrationDecision = routePolicyRuntime.orchestrationDecision;
|
const orchestrationDecision = routePolicyRuntime.orchestrationDecision;
|
||||||
const orchestrationContract = toRecordObject(orchestrationDecision.orchestrationContract);
|
const orchestrationContract = toRecordObject(orchestrationDecision.orchestrationContract);
|
||||||
const predecomposeContract = toRecordObject(addressPreDecompose.predecomposeContract);
|
const predecomposeContract = toRecordObject(addressPreDecompose.predecomposeContract);
|
||||||
|
const explicitPredecomposeOrganization = predecomposeOrganizationName(predecomposeContract, input.toNonEmptyString);
|
||||||
|
const discoveryFollowupContext = mergeOrganizationIntoDiscoveryFollowupContext(
|
||||||
|
followupContext,
|
||||||
|
explicitPredecomposeOrganization
|
||||||
|
? null
|
||||||
|
: sessionOrganizationName(input.sessionOrganizationScope ?? null, input.toNonEmptyString)
|
||||||
|
);
|
||||||
const dialogContinuationContract = input.buildAddressDialogContinuationContractV2(
|
const dialogContinuationContract = input.buildAddressDialogContinuationContractV2(
|
||||||
input.userMessage,
|
input.userMessage,
|
||||||
addressInputMessage,
|
addressInputMessage,
|
||||||
|
|
@ -323,7 +384,7 @@ export async function buildAssistantAddressOrchestrationRuntime(
|
||||||
effectiveMessage: addressInputMessage,
|
effectiveMessage: addressInputMessage,
|
||||||
assistantTurnMeaning: toRecordObject(orchestrationContract?.assistant_turn_meaning),
|
assistantTurnMeaning: toRecordObject(orchestrationContract?.assistant_turn_meaning),
|
||||||
predecomposeContract,
|
predecomposeContract,
|
||||||
followupContext
|
followupContext: discoveryFollowupContext
|
||||||
})) as Record<string, unknown>;
|
})) as Record<string, unknown>;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
mcpDiscoveryRuntimeEntryPointError = String(error instanceof Error ? error.message : error ?? "unknown_error").slice(0, 280);
|
mcpDiscoveryRuntimeEntryPointError = String(error instanceof Error ? error.message : error ?? "unknown_error").slice(0, 280);
|
||||||
|
|
|
||||||
|
|
@ -160,6 +160,13 @@ function isValueFlowPilot(pilot: AssistantMcpDiscoveryPilotExecutionContract): b
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function isSingleDirectionValueFlowPilot(pilot: AssistantMcpDiscoveryPilotExecutionContract): boolean {
|
||||||
|
return (
|
||||||
|
pilot.pilot_scope === "counterparty_value_flow_query_movements_v1" ||
|
||||||
|
pilot.pilot_scope === "counterparty_supplier_payout_query_movements_v1"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
function isBusinessOverviewPilot(pilot: AssistantMcpDiscoveryPilotExecutionContract): boolean {
|
function isBusinessOverviewPilot(pilot: AssistantMcpDiscoveryPilotExecutionContract): boolean {
|
||||||
return pilot.pilot_scope === "business_overview_route_template_v1";
|
return pilot.pilot_scope === "business_overview_route_template_v1";
|
||||||
}
|
}
|
||||||
|
|
@ -251,6 +258,61 @@ function explicitOrganizationScope(pilot: AssistantMcpDiscoveryPilotExecutionCon
|
||||||
return normalized.length > 0 ? normalized : null;
|
return normalized.length > 0 ? normalized : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function hasExecutedZeroValueFlowRows(pilot: AssistantMcpDiscoveryPilotExecutionContract): boolean {
|
||||||
|
const summary = pilot.source_rows_summary ?? "";
|
||||||
|
return (
|
||||||
|
pilot.mcp_execution_performed &&
|
||||||
|
isSingleDirectionValueFlowPilot(pilot) &&
|
||||||
|
!pilot.derived_value_flow &&
|
||||||
|
(/0\s+MCP\s+value-flow\s+rows\s+fetched/i.test(summary) ||
|
||||||
|
/\b0\s+matched\s+value-flow\s+scope\b/i.test(summary))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function valueFlowDirectionLabelRu(pilot: AssistantMcpDiscoveryPilotExecutionContract): string {
|
||||||
|
return pilot.pilot_scope === "counterparty_supplier_payout_query_movements_v1"
|
||||||
|
? "исходящих платежей/списаний"
|
||||||
|
: "входящих денежных поступлений";
|
||||||
|
}
|
||||||
|
|
||||||
|
function valueFlowZeroResultConfirmedLine(pilot: AssistantMcpDiscoveryPilotExecutionContract): string | null {
|
||||||
|
if (!hasExecutedZeroValueFlowRows(pilot)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
const counterparty = firstEntityCandidate(pilot);
|
||||||
|
if (!counterparty) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
const organization = explicitOrganizationScope(pilot);
|
||||||
|
const period = explicitDateScope(pilot);
|
||||||
|
const organizationPart = organization ? ` по организации ${organization}` : "";
|
||||||
|
const periodPart = period ? ` за период ${period}` : " в проверенном окне";
|
||||||
|
return `В проверенном срезе 1С по контрагенту ${counterparty}${organizationPart}${periodPart} ${valueFlowDirectionLabelRu(
|
||||||
|
pilot
|
||||||
|
)} не найдено.`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function valueFlowZeroResultUnknownLine(pilot: AssistantMcpDiscoveryPilotExecutionContract): string | null {
|
||||||
|
if (!hasExecutedZeroValueFlowRows(pilot)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
const counterparty = firstEntityCandidate(pilot);
|
||||||
|
if (!counterparty) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
const period = explicitDateScope(pilot);
|
||||||
|
const periodPart = period ? ` вне периода ${period}` : " вне проверенного окна";
|
||||||
|
return `Это не доказывает отсутствие операций с контрагентом ${counterparty}${periodPart} или вне доступного банковского контура.`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function valueFlowZeroResultHeadline(pilot: AssistantMcpDiscoveryPilotExecutionContract): string | null {
|
||||||
|
const confirmedLine = valueFlowZeroResultConfirmedLine(pilot);
|
||||||
|
if (!confirmedLine) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return confirmedLine;
|
||||||
|
}
|
||||||
|
|
||||||
function hasAllTimeScope(pilot: AssistantMcpDiscoveryPilotExecutionContract): boolean {
|
function hasAllTimeScope(pilot: AssistantMcpDiscoveryPilotExecutionContract): boolean {
|
||||||
return (
|
return (
|
||||||
dryRunHasAxis(pilot, "all_time_scope") ||
|
dryRunHasAxis(pilot, "all_time_scope") ||
|
||||||
|
|
@ -600,6 +662,10 @@ function headlineFor(mode: AssistantMcpDiscoveryAnswerMode, pilot: AssistantMcpD
|
||||||
}
|
}
|
||||||
return "По данным 1С найдены строки входящих денежных поступлений; сумму можно называть только в рамках проверенного периода и найденных строк.";
|
return "По данным 1С найдены строки входящих денежных поступлений; сумму можно называть только в рамках проверенного периода и найденных строк.";
|
||||||
}
|
}
|
||||||
|
const zeroValueFlowHeadline = valueFlowZeroResultHeadline(pilot);
|
||||||
|
if (mode === "checked_sources_only" && zeroValueFlowHeadline) {
|
||||||
|
return zeroValueFlowHeadline;
|
||||||
|
}
|
||||||
if (isDocumentPilot(pilot) && mode === "confirmed_with_bounded_inference") {
|
if (isDocumentPilot(pilot) && mode === "confirmed_with_bounded_inference") {
|
||||||
return `По документам${documentOrMovementScopeRu(pilot)} в 1С найдены подтвержденные строки; ответ ограничен проверенным окном и найденными строками.`;
|
return `По документам${documentOrMovementScopeRu(pilot)} в 1С найдены подтвержденные строки; ответ ограничен проверенным окном и найденными строками.`;
|
||||||
}
|
}
|
||||||
|
|
@ -1487,6 +1553,11 @@ function businessOverviewUnknownLines(pilot: AssistantMcpDiscoveryPilotExecution
|
||||||
return userFacingUnknowns(pilot.evidence.unknown_facts);
|
return userFacingUnknowns(pilot.evidence.unknown_facts);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function appendValueFlowZeroResultUnknown(lines: string[], pilot: AssistantMcpDiscoveryPilotExecutionContract): string[] {
|
||||||
|
const zeroLine = valueFlowZeroResultUnknownLine(pilot);
|
||||||
|
return zeroLine ? uniqueStrings([zeroLine, ...lines]) : lines;
|
||||||
|
}
|
||||||
|
|
||||||
export function buildAssistantMcpDiscoveryAnswerDraft(
|
export function buildAssistantMcpDiscoveryAnswerDraft(
|
||||||
pilot: AssistantMcpDiscoveryPilotExecutionContract
|
pilot: AssistantMcpDiscoveryPilotExecutionContract
|
||||||
): AssistantMcpDiscoveryAnswerDraftContract {
|
): AssistantMcpDiscoveryAnswerDraftContract {
|
||||||
|
|
@ -1581,6 +1652,8 @@ export function buildAssistantMcpDiscoveryAnswerDraft(
|
||||||
? [derivedValueLine]
|
? [derivedValueLine]
|
||||||
: derivedValueLine
|
: derivedValueLine
|
||||||
? [...pilot.evidence.confirmed_facts, derivedValueLine, ...monthlyConfirmedLines]
|
? [...pilot.evidence.confirmed_facts, derivedValueLine, ...monthlyConfirmedLines]
|
||||||
|
: valueFlowZeroResultConfirmedLine(pilot)
|
||||||
|
? [valueFlowZeroResultConfirmedLine(pilot)!]
|
||||||
: derivedEntityResolutionLine
|
: derivedEntityResolutionLine
|
||||||
? [...pilot.evidence.confirmed_facts, derivedEntityResolutionLine]
|
? [...pilot.evidence.confirmed_facts, derivedEntityResolutionLine]
|
||||||
: derivedMetadataLine
|
: derivedMetadataLine
|
||||||
|
|
@ -1592,7 +1665,7 @@ export function buildAssistantMcpDiscoveryAnswerDraft(
|
||||||
? pilot.derived_metadata_surface.available_fields.length > 0
|
? pilot.derived_metadata_surface.available_fields.length > 0
|
||||||
? userFacingUnknowns(pilot.evidence.unknown_facts)
|
? userFacingUnknowns(pilot.evidence.unknown_facts)
|
||||||
: ["Детальный список полей этих объектов этим шагом не получен."]
|
: ["Детальный список полей этих объектов этим шагом не получен."]
|
||||||
: rankedValueFlowUnknownLines(pilot);
|
: appendValueFlowZeroResultUnknown(rankedValueFlowUnknownLines(pilot), pilot);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
schema_version: ASSISTANT_MCP_DISCOVERY_ANSWER_DRAFT_SCHEMA_VERSION,
|
schema_version: ASSISTANT_MCP_DISCOVERY_ANSWER_DRAFT_SCHEMA_VERSION,
|
||||||
|
|
|
||||||
|
|
@ -235,7 +235,11 @@ function pushScopedEntityCandidate(
|
||||||
if (!text) {
|
if (!text) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if ((groundedFollowupEntity && isReferentialEntityPlaceholder(text)) || isValueFlowPredicateEntityCandidate(text)) {
|
if (
|
||||||
|
isInvalidEntityCandidate(text) ||
|
||||||
|
(groundedFollowupEntity && isReferentialEntityPlaceholder(text)) ||
|
||||||
|
isValueFlowPredicateEntityCandidate(text)
|
||||||
|
) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
pushUnique(target, text);
|
pushUnique(target, text);
|
||||||
|
|
@ -911,7 +915,20 @@ function hasBusinessOverviewContinuationSignal(text: string): boolean {
|
||||||
/(?:\u0447\u0442\u043e\s+\u043c\u044b\s+\u0437\u043d\u0430\u0435\u043c|\u0447\u0442\u043e\s+\u043f\u043e\u043d\u044f\u0442\u043d\u043e|\u0447\u0442\u043e\s+\u043f\u0440\u043e\u0432\u0435\u0440\w*\s+\u0434\u0430\u043b\u044c\u0448\u0435|\u0441\u043b\u0435\u0434\u0443\u044e\u0449\w*\s+\u0448\u0430\u0433|\u0438\u0442\u043e\u0433\w*\s+\u0432\u044b\u0432\u043e\u0434|\u043a\u0430\u043a\u043e\u0439\s+\u0432\u044b\u0432\u043e\u0434|\u0447\u0442\u043e\s+\u0441\s+\u044d\u0442\u0438\u043c\s+\u0434\u0435\u043b\u0430\u0442\u044c|what\s+do\s+we\s+know|what\s+is\s+missing|next\s+step|final\s+summary)/iu.test(
|
/(?:\u0447\u0442\u043e\s+\u043c\u044b\s+\u0437\u043d\u0430\u0435\u043c|\u0447\u0442\u043e\s+\u043f\u043e\u043d\u044f\u0442\u043d\u043e|\u0447\u0442\u043e\s+\u043f\u0440\u043e\u0432\u0435\u0440\w*\s+\u0434\u0430\u043b\u044c\u0448\u0435|\u0441\u043b\u0435\u0434\u0443\u044e\u0449\w*\s+\u0448\u0430\u0433|\u0438\u0442\u043e\u0433\w*\s+\u0432\u044b\u0432\u043e\u0434|\u043a\u0430\u043a\u043e\u0439\s+\u0432\u044b\u0432\u043e\u0434|\u0447\u0442\u043e\s+\u0441\s+\u044d\u0442\u0438\u043c\s+\u0434\u0435\u043b\u0430\u0442\u044c|what\s+do\s+we\s+know|what\s+is\s+missing|next\s+step|final\s+summary)/iu.test(
|
||||||
normalized
|
normalized
|
||||||
);
|
);
|
||||||
return hasEvidenceContinuationCue || hasAnalystContinuationCue || hasTaxContinuationCue || hasFinalSummaryCue;
|
const hasMoneyBreakdownCue =
|
||||||
|
/(?:\u0440\u0430\u0441\u043a\u0440\u043e\p{L}*\s+\u0434\u0435\u043d\p{L}*|\u0441\u043a\u043e\u043b\u044c\u043a\u043e\s+\u0432\u0441\u0435\u0433\u043e\s+\u043f\u043e\u043b\u0443\u0447|\u0441\u043a\u043e\u043b\u044c\u043a\u043e\s+(?:\u0432\u0441\u0435\u0433\u043e\s+)?\u0437\u0430\u043f\u043b\u0430\u0442|\u0447\u0438\u0441\u0442\p{L}*\s+\u0434\u0435\u043d\u0435\u0436\u043d\p{L}*\s+\u043f\u043e\u0442\u043e\u043a|\u0433\u043b\u0430\u0432\u043d\p{L}*\s+(?:\u043a\u043b\u0438\u0435\u043d\u0442|\u043f\u043e\u0441\u0442\u0430\u0432\u0449\u0438\u043a)|top\s+(?:customer|supplier)|cash\s+breakdown)/iu.test(
|
||||||
|
normalized
|
||||||
|
) &&
|
||||||
|
/(?:\u043f\u043e\u043b\u0443\u0447|\u0437\u0430\u043f\u043b\u0430\u0442|\u043d\u0435\u0442\u0442\u043e|\u0434\u0435\u043d\p{L}*|\u043a\u043b\u0438\u0435\u043d\u0442|\u043f\u043e\u0441\u0442\u0430\u0432\u0449\u0438\u043a|received|paid|net|cash|customer|supplier)/iu.test(
|
||||||
|
normalized
|
||||||
|
);
|
||||||
|
return (
|
||||||
|
hasEvidenceContinuationCue ||
|
||||||
|
hasAnalystContinuationCue ||
|
||||||
|
hasTaxContinuationCue ||
|
||||||
|
hasFinalSummaryCue ||
|
||||||
|
hasMoneyBreakdownCue
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function hasExplicitTopicSwitchSignal(text: string): boolean {
|
function hasExplicitTopicSwitchSignal(text: string): boolean {
|
||||||
|
|
@ -1568,7 +1585,7 @@ export function buildAssistantMcpDiscoveryTurnInput(
|
||||||
);
|
);
|
||||||
const normalizedPredecomposeCounterparty = organizationMirrorsPredecomposeCounterparty
|
const normalizedPredecomposeCounterparty = organizationMirrorsPredecomposeCounterparty
|
||||||
? null
|
? null
|
||||||
: predecomposeEntities.counterparty;
|
: normalizeFollowupCounterpartyCandidate(predecomposeEntities.counterparty);
|
||||||
const predecomposeDateScope = collectDateScope(predecomposeContract);
|
const predecomposeDateScope = collectDateScope(predecomposeContract);
|
||||||
const periodClarificationFollowupApplicable = Boolean(
|
const periodClarificationFollowupApplicable = Boolean(
|
||||||
followupSeed.domain &&
|
followupSeed.domain &&
|
||||||
|
|
|
||||||
|
|
@ -223,6 +223,29 @@ function hasOrganizationLevelSupplierQualityOverviewSignal(text) {
|
||||||
return hasSupplierScopeCue && hasSupplierQualityCue && hasCompanyScopeCue;
|
return hasSupplierScopeCue && hasSupplierQualityCue && hasCompanyScopeCue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function hasOrganizationLevelMoneyBreakdownSignal(text) {
|
||||||
|
const normalized = String(text ?? "");
|
||||||
|
if (!normalized) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const hasIncomingCue = /(?:\u043f\u043e\u043b\u0443\u0447|\u0432\u0445\u043e\u0434\u044f\u0449|\u043f\u043e\u0441\u0442\u0443\u043f|\u043a\u043b\u0438\u0435\u043d\u0442|received|incoming|customer)/iu.test(
|
||||||
|
normalized
|
||||||
|
);
|
||||||
|
const hasOutgoingCue = /(?:\u0437\u0430\u043f\u043b\u0430\u0442|\u0438\u0441\u0445\u043e\u0434\u044f\u0449|\u0441\u043f\u0438\u0441\u0430\u043d|\u043f\u043e\u0441\u0442\u0430\u0432\u0449\u0438\u043a|paid|outgoing|supplier)/iu.test(
|
||||||
|
normalized
|
||||||
|
);
|
||||||
|
const hasNetCue = /(?:\u043d\u0435\u0442\u0442\u043e|\u0447\u0438\u0441\u0442\p{L}*\s+\u0434\u0435\u043d\u0435\u0436\u043d\p{L}*\s+\u043f\u043e\u0442\u043e\u043a|net\s+(?:cash|flow)|cash\s+flow)/iu.test(
|
||||||
|
normalized
|
||||||
|
);
|
||||||
|
const hasRankingCue = /(?:\u0433\u043b\u0430\u0432\u043d\p{L}*\s+(?:\u043a\u043b\u0438\u0435\u043d\u0442|\u043f\u043e\u0441\u0442\u0430\u0432\u0449\u0438\u043a)|top\s+(?:customer|supplier))/iu.test(
|
||||||
|
normalized
|
||||||
|
);
|
||||||
|
const hasBreakdownCue = /(?:\u0440\u0430\u0441\u043a\u0440\u043e\p{L}*|\u043f\u043e\u0434\u0440\u043e\u0431\u043d|\u0441\u043a\u043e\u043b\u044c\u043a\u043e\s+\u0432\u0441\u0435\u0433\u043e|\u0441\u0432\u043e\u0434\p{L}*|breakdown|detail)/iu.test(
|
||||||
|
normalized
|
||||||
|
);
|
||||||
|
return hasBreakdownCue && hasIncomingCue && hasOutgoingCue && (hasNetCue || hasRankingCue);
|
||||||
|
}
|
||||||
|
|
||||||
function detectBroadBusinessEvaluation(text) {
|
function detectBroadBusinessEvaluation(text) {
|
||||||
const normalized = String(text ?? "");
|
const normalized = String(text ?? "");
|
||||||
if (!normalized) {
|
if (!normalized) {
|
||||||
|
|
@ -264,6 +287,11 @@ function detectBroadBusinessEvaluation(text) {
|
||||||
family: "broad_business_evaluation"
|
family: "broad_business_evaluation"
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
if (hasOrganizationLevelMoneyBreakdownSignal(normalized)) {
|
||||||
|
return {
|
||||||
|
family: "broad_business_evaluation"
|
||||||
|
};
|
||||||
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -5110,6 +5110,23 @@ describe("address recipe catalog counterparty filtering", () => {
|
||||||
expect(plan.query).toContain("БанкПоступление.ДоговорКонтрагента");
|
expect(plan.query).toContain("БанкПоступление.ДоговорКонтрагента");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("injects counterparty condition into customer value recipe", () => {
|
||||||
|
const selected = selectAddressRecipe("customer_revenue_and_payments", {
|
||||||
|
counterparty: "Группа СВК",
|
||||||
|
period_from: "2020-01-01",
|
||||||
|
period_to: "2020-12-31"
|
||||||
|
});
|
||||||
|
expect(selected.selected_recipe).toBeTruthy();
|
||||||
|
const plan = buildAddressRecipePlan(selected.selected_recipe!, {
|
||||||
|
counterparty: "Группа СВК",
|
||||||
|
period_from: "2020-01-01",
|
||||||
|
period_to: "2020-12-31"
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(plan.query).toContain('БанкПоступление.Контрагент.Наименование ПОДОБНО "%Группа%"');
|
||||||
|
expect(plan.query).toContain('БанкПоступление.Контрагент.Наименование ПОДОБНО "%СВК%"');
|
||||||
|
});
|
||||||
|
|
||||||
it("expands customer value analytics sample independently from visible ranking size", () => {
|
it("expands customer value analytics sample independently from visible ranking size", () => {
|
||||||
const filters = extractAddressFilters("какой у нас самый доходный год", "customer_revenue_and_payments");
|
const filters = extractAddressFilters("какой у нас самый доходный год", "customer_revenue_and_payments");
|
||||||
const selected = selectAddressRecipe("customer_revenue_and_payments", filters.extracted_filters);
|
const selected = selectAddressRecipe("customer_revenue_and_payments", filters.extracted_filters);
|
||||||
|
|
@ -5132,6 +5149,23 @@ describe("address recipe catalog counterparty filtering", () => {
|
||||||
expect(plan.query).toContain("БанкСписание.ДоговорКонтрагента");
|
expect(plan.query).toContain("БанкСписание.ДоговорКонтрагента");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("injects counterparty condition into supplier payout recipe", () => {
|
||||||
|
const selected = selectAddressRecipe("supplier_payouts_profile", {
|
||||||
|
counterparty: "Группа СВК",
|
||||||
|
period_from: "2020-01-01",
|
||||||
|
period_to: "2020-12-31"
|
||||||
|
});
|
||||||
|
expect(selected.selected_recipe).toBeTruthy();
|
||||||
|
const plan = buildAddressRecipePlan(selected.selected_recipe!, {
|
||||||
|
counterparty: "Группа СВК",
|
||||||
|
period_from: "2020-01-01",
|
||||||
|
period_to: "2020-12-31"
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(plan.query).toContain('БанкСписание.Контрагент.Наименование ПОДОБНО "%Группа%"');
|
||||||
|
expect(plan.query).toContain('БанкСписание.Контрагент.Наименование ПОДОБНО "%СВК%"');
|
||||||
|
});
|
||||||
|
|
||||||
it("selects contract value recipe and keeps top-20 default", () => {
|
it("selects contract value recipe and keeps top-20 default", () => {
|
||||||
const selected = selectAddressRecipe("contract_usage_and_value", {});
|
const selected = selectAddressRecipe("contract_usage_and_value", {});
|
||||||
expect(selected.selected_recipe).toBeTruthy();
|
expect(selected.selected_recipe).toBeTruthy();
|
||||||
|
|
@ -5168,6 +5202,27 @@ describe("address recipe catalog counterparty filtering", () => {
|
||||||
expect(plan.query).toContain("ПоступлениеНаРасчетныйСчет");
|
expect(plan.query).toContain("ПоступлениеНаРасчетныйСчет");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("injects counterparty condition into lifecycle and bank document recipes", () => {
|
||||||
|
const lifecycle = selectAddressRecipe("counterparty_activity_lifecycle", {
|
||||||
|
counterparty: "Группа СВК"
|
||||||
|
});
|
||||||
|
const bankDocs = selectAddressRecipe("bank_operations_by_counterparty", {
|
||||||
|
counterparty: "Группа СВК"
|
||||||
|
});
|
||||||
|
expect(lifecycle.selected_recipe).toBeTruthy();
|
||||||
|
expect(bankDocs.selected_recipe).toBeTruthy();
|
||||||
|
const lifecyclePlan = buildAddressRecipePlan(lifecycle.selected_recipe!, {
|
||||||
|
counterparty: "Группа СВК"
|
||||||
|
});
|
||||||
|
const bankDocsPlan = buildAddressRecipePlan(bankDocs.selected_recipe!, {
|
||||||
|
counterparty: "Группа СВК"
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(lifecyclePlan.query).toContain('БанкПоступление.Контрагент.Наименование ПОДОБНО "%СВК%"');
|
||||||
|
expect(bankDocsPlan.query).toContain('БанкПоступление.Контрагент.Наименование ПОДОБНО "%СВК%"');
|
||||||
|
expect(bankDocsPlan.query).toContain('БанкСписание.Контрагент.Наименование ПОДОБНО "%СВК%"');
|
||||||
|
});
|
||||||
|
|
||||||
it("boosts limit for all-time counterparty queries", () => {
|
it("boosts limit for all-time counterparty queries", () => {
|
||||||
const filters = extractAddressFilters(
|
const filters = extractAddressFilters(
|
||||||
"Покажи документы по контрагенту тестовый за все время",
|
"Покажи документы по контрагенту тестовый за все время",
|
||||||
|
|
|
||||||
|
|
@ -154,6 +154,75 @@ describe("assistant address orchestration runtime adapter", () => {
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("passes active session organization into MCP discovery when carryover context is absent", async () => {
|
||||||
|
const runMcpDiscoveryRuntimeEntryPoint = vi.fn(async () => ({
|
||||||
|
schema_version: "assistant_mcp_discovery_runtime_entry_point_v1",
|
||||||
|
policy_owner: "assistantMcpDiscoveryRuntimeEntryPoint",
|
||||||
|
entry_status: "bridge_executed",
|
||||||
|
hot_runtime_wired: false,
|
||||||
|
discovery_attempted: true
|
||||||
|
}));
|
||||||
|
const input = buildInput({
|
||||||
|
userMessage: "money breakdown 2020",
|
||||||
|
sessionOrganizationScope: {
|
||||||
|
activeOrganization: "Org A",
|
||||||
|
selectedOrganization: null,
|
||||||
|
knownOrganizations: ["Org A"]
|
||||||
|
},
|
||||||
|
runAddressLlmPreDecompose: vi.fn(async () => ({
|
||||||
|
attempted: true,
|
||||||
|
applied: false,
|
||||||
|
effectiveMessage: "money breakdown 2020",
|
||||||
|
reason: "raw_kept",
|
||||||
|
predecomposeContract: {
|
||||||
|
mode: "unsupported",
|
||||||
|
intent: "unknown",
|
||||||
|
period: {
|
||||||
|
scope: "year",
|
||||||
|
period_from: "2020-01-01",
|
||||||
|
period_to: "2020-12-31",
|
||||||
|
has_explicit_period: true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})),
|
||||||
|
resolveAddressFollowupCarryoverContext: vi.fn(() => null),
|
||||||
|
resolveAssistantOrchestrationDecision: vi.fn(() => ({
|
||||||
|
runAddressLane: false,
|
||||||
|
livingMode: "chat",
|
||||||
|
livingReason: "unsupported_current_turn_meaning_boundary",
|
||||||
|
toolGateDecision: "skip_address_lane",
|
||||||
|
toolGateReason: "unsupported_current_turn_meaning_boundary",
|
||||||
|
orchestrationContract: {
|
||||||
|
schema_version: "assistant_orchestration_contract_v1",
|
||||||
|
assistant_turn_meaning: {
|
||||||
|
schema_version: "assistant_turn_meaning_v1",
|
||||||
|
asked_domain_family: "business_overview",
|
||||||
|
asked_action_family: "broad_evaluation",
|
||||||
|
unsupported_but_understood_family: "broad_business_evaluation"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})),
|
||||||
|
runMcpDiscoveryRuntimeEntryPoint
|
||||||
|
});
|
||||||
|
|
||||||
|
await buildAssistantAddressOrchestrationRuntime(input);
|
||||||
|
|
||||||
|
expect(runMcpDiscoveryRuntimeEntryPoint).toHaveBeenCalledWith(
|
||||||
|
expect.objectContaining({
|
||||||
|
followupContext: expect.objectContaining({
|
||||||
|
previous_anchor_type: "organization",
|
||||||
|
previous_anchor_value: "Org A",
|
||||||
|
previous_filters: expect.objectContaining({
|
||||||
|
organization: "Org A"
|
||||||
|
}),
|
||||||
|
root_filters: expect.objectContaining({
|
||||||
|
organization: "Org A"
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
it("passes grounded discovery follow-up carryover into MCP discovery entry point for a short year switch", async () => {
|
it("passes grounded discovery follow-up carryover into MCP discovery entry point for a short year switch", async () => {
|
||||||
const runMcpDiscoveryRuntimeEntryPoint = vi.fn(async () => ({
|
const runMcpDiscoveryRuntimeEntryPoint = vi.fn(async () => ({
|
||||||
schema_version: "assistant_mcp_discovery_runtime_entry_point_v1",
|
schema_version: "assistant_mcp_discovery_runtime_entry_point_v1",
|
||||||
|
|
|
||||||
|
|
@ -1287,6 +1287,35 @@ describe("assistant MCP discovery answer adapter", () => {
|
||||||
expect(draft.unknown_lines).toContain("Full supplier-payout amount outside the checked period is not proven by this MCP discovery pilot");
|
expect(draft.unknown_lines).toContain("Full supplier-payout amount outside the checked period is not proven by this MCP discovery pilot");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("renders zero-row supplier payout as a checked negative with period and counterparty", async () => {
|
||||||
|
const planner = planAssistantMcpDiscovery({
|
||||||
|
turnMeaning: {
|
||||||
|
asked_domain_family: "counterparty_value",
|
||||||
|
asked_action_family: "payout",
|
||||||
|
explicit_entity_candidates: ["Группа СВК"],
|
||||||
|
explicit_organization_scope: "ООО Альтернатива Плюс",
|
||||||
|
explicit_date_scope: "2020",
|
||||||
|
unsupported_but_understood_family: "counterparty_payouts_or_outflow"
|
||||||
|
}
|
||||||
|
});
|
||||||
|
const pilot = await executeAssistantMcpDiscoveryPilot(planner, buildDeps([]));
|
||||||
|
|
||||||
|
const draft = buildAssistantMcpDiscoveryAnswerDraft(pilot);
|
||||||
|
const confirmedText = draft.confirmed_lines.join("\n");
|
||||||
|
const unknownText = draft.unknown_lines.join("\n");
|
||||||
|
|
||||||
|
expect(draft.answer_mode).toBe("checked_sources_only");
|
||||||
|
expect(draft.headline).toContain("Группа СВК");
|
||||||
|
expect(draft.headline).toContain("2020");
|
||||||
|
expect(draft.headline).toContain("исходящих платежей");
|
||||||
|
expect(confirmedText).toContain("Группа СВК");
|
||||||
|
expect(confirmedText).toContain("ООО Альтернатива Плюс");
|
||||||
|
expect(confirmedText).toContain("2020");
|
||||||
|
expect(confirmedText).toContain("не найдено");
|
||||||
|
expect(unknownText).toContain("вне периода 2020");
|
||||||
|
expect(unknownText).toContain("вне доступного банковского контура");
|
||||||
|
});
|
||||||
|
|
||||||
it("turns bidirectional value-flow evidence into a bounded net cash answer draft", async () => {
|
it("turns bidirectional value-flow evidence into a bounded net cash answer draft", async () => {
|
||||||
const planner = planAssistantMcpDiscovery({
|
const planner = planAssistantMcpDiscovery({
|
||||||
turnMeaning: {
|
turnMeaning: {
|
||||||
|
|
|
||||||
|
|
@ -177,7 +177,7 @@ describe("assistant MCP discovery runtime entry point", () => {
|
||||||
expect(result.bridge?.answer_draft.inference_lines.join("\n")).toContain("\u043d\u0435 \u043f\u0440\u0438\u0431\u044b\u043b\u044c");
|
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.bridge?.answer_draft.unknown_lines.join("\n")).toContain("VAT");
|
||||||
expect(result.reason_codes).toContain("pilot_derived_business_overview_from_confirmed_rows");
|
expect(result.reason_codes).toContain("pilot_derived_business_overview_from_confirmed_rows");
|
||||||
expect(deps.executeAddressMcpQuery).toHaveBeenCalledTimes(3);
|
expect(deps.executeAddressMcpQuery).toHaveBeenCalledTimes(6);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("runs the bridge for raw metadata wording without an exact route owner", async () => {
|
it("runs the bridge for raw metadata wording without an exact route owner", async () => {
|
||||||
|
|
|
||||||
|
|
@ -2787,6 +2787,54 @@ describe("assistant MCP discovery turn input adapter", () => {
|
||||||
expect(result.reason_codes).toContain("mcp_discovery_business_overview_continuation_from_followup_context");
|
expect(result.reason_codes).toContain("mcp_discovery_business_overview_continuation_from_followup_context");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("keeps detailed money-breakdown follow-up in business overview without pseudo counterparty anchors", () => {
|
||||||
|
const orgName =
|
||||||
|
"\u041e\u041e\u041e \u0410\u043b\u044c\u0442\u0435\u0440\u043d\u0430\u0442\u0438\u0432\u0430 \u041f\u043b\u044e\u0441";
|
||||||
|
const result = buildAssistantMcpDiscoveryTurnInput({
|
||||||
|
userMessage:
|
||||||
|
"\u0420\u0430\u0441\u043a\u0440\u043e\u0439 \u0434\u0435\u043d\u044c\u0433\u0438 \u043f\u043e\u0434\u0440\u043e\u0431\u043d\u0435\u0435: \u0441\u043a\u043e\u043b\u044c\u043a\u043e \u0432\u0441\u0435\u0433\u043e \u043f\u043e\u043b\u0443\u0447\u0438\u043b\u0438, \u0441\u043a\u043e\u043b\u044c\u043a\u043e \u0437\u0430\u043f\u043b\u0430\u0442\u0438\u043b\u0438, \u043a\u0430\u043a\u043e\u0439 \u0447\u0438\u0441\u0442\u044b\u0439 \u0434\u0435\u043d\u0435\u0436\u043d\u044b\u0439 \u043f\u043e\u0442\u043e\u043a, \u043a\u0442\u043e \u0433\u043b\u0430\u0432\u043d\u044b\u0439 \u043a\u043b\u0438\u0435\u043d\u0442 \u0438 \u043a\u0442\u043e \u0433\u043b\u0430\u0432\u043d\u044b\u0439 \u043f\u043e\u0441\u0442\u0430\u0432\u0449\u0438\u043a \u0432 2020.",
|
||||||
|
assistantTurnMeaning: {
|
||||||
|
asked_domain_family: "counterparty_value",
|
||||||
|
asked_action_family: "net_value_flow",
|
||||||
|
explicit_intent_candidate: "customer_revenue_and_payments",
|
||||||
|
explicit_entity_candidates: [
|
||||||
|
"\u0438 \u043a\u0442\u043e \u0433\u043b\u0430\u0432\u043d\u044b\u0439 \u043f\u043e\u0441\u0442\u0430\u0432\u0449\u0438\u043a \u0432"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
predecomposeContract: {
|
||||||
|
entities: {
|
||||||
|
counterparty: "\u0438 \u043a\u0442\u043e \u0433\u043b\u0430\u0432\u043d\u044b\u0439 \u043f\u043e\u0441\u0442\u0430\u0432\u0449\u0438\u043a \u0432"
|
||||||
|
},
|
||||||
|
period: { period_from: "2020-01-01", period_to: "2020-12-31" }
|
||||||
|
},
|
||||||
|
followupContext: {
|
||||||
|
previous_discovery_pilot_scope: "business_overview_route_template_v1",
|
||||||
|
previous_filters: {
|
||||||
|
organization: orgName,
|
||||||
|
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.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?.subject_candidates).toEqual([]);
|
||||||
|
expect(result.turn_meaning_ref).toMatchObject({
|
||||||
|
asked_domain_family: "business_overview",
|
||||||
|
asked_action_family: "broad_evaluation",
|
||||||
|
explicit_organization_scope: orgName,
|
||||||
|
explicit_date_scope: "2020",
|
||||||
|
unsupported_but_understood_family: "broad_business_evaluation",
|
||||||
|
stale_replay_forbidden: true
|
||||||
|
});
|
||||||
|
expect(result.turn_meaning_ref?.explicit_entity_candidates).toBeUndefined();
|
||||||
|
expect(result.reason_codes).toContain("mcp_discovery_business_overview_continuation_from_followup_context");
|
||||||
|
expect(result.reason_codes).not.toContain("mcp_discovery_counterparty_from_predecompose");
|
||||||
|
});
|
||||||
|
|
||||||
it("continues business overview on by-these-data profit wording without grounding pseudo anchors", () => {
|
it("continues business overview on by-these-data profit wording without grounding pseudo anchors", () => {
|
||||||
const orgName =
|
const orgName =
|
||||||
"\u041e\u041e\u041e \u0410\u043b\u044c\u0442\u0435\u0440\u043d\u0430\u0442\u0438\u0432\u0430 \u041f\u043b\u044e\u0441";
|
"\u041e\u041e\u041e \u0410\u043b\u044c\u0442\u0435\u0440\u043d\u0430\u0442\u0438\u0432\u0430 \u041f\u043b\u044e\u0441";
|
||||||
|
|
|
||||||
|
|
@ -146,6 +146,25 @@ describe("assistantTurnMeaningPolicy", () => {
|
||||||
expect(meaning.reason_codes).toContain("broad_business_evaluation_current_turn_signal");
|
expect(meaning.reason_codes).toContain("broad_business_evaluation_current_turn_signal");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("treats compound money breakdown as business overview instead of narrow customer revenue", () => {
|
||||||
|
const policy = buildPolicy({
|
||||||
|
resolveAddressIntent: () => ({ intent: "customer_revenue_and_payments", confidence: "high" })
|
||||||
|
});
|
||||||
|
|
||||||
|
const meaning = policy.resolveAssistantTurnMeaning({
|
||||||
|
rawUserMessage:
|
||||||
|
"\u0420\u0430\u0441\u043a\u0440\u043e\u0439 \u0434\u0435\u043d\u044c\u0433\u0438 \u043f\u043e\u0434\u0440\u043e\u0431\u043d\u0435\u0435: \u0441\u043a\u043e\u043b\u044c\u043a\u043e \u0432\u0441\u0435\u0433\u043e \u043f\u043e\u043b\u0443\u0447\u0438\u043b\u0438, \u0441\u043a\u043e\u043b\u044c\u043a\u043e \u0437\u0430\u043f\u043b\u0430\u0442\u0438\u043b\u0438, \u043a\u0430\u043a\u043e\u0439 \u0447\u0438\u0441\u0442\u044b\u0439 \u0434\u0435\u043d\u0435\u0436\u043d\u044b\u0439 \u043f\u043e\u0442\u043e\u043a, \u043a\u0442\u043e \u0433\u043b\u0430\u0432\u043d\u044b\u0439 \u043a\u043b\u0438\u0435\u043d\u0442 \u0438 \u043a\u0442\u043e \u0433\u043b\u0430\u0432\u043d\u044b\u0439 \u043f\u043e\u0441\u0442\u0430\u0432\u0449\u0438\u043a \u0432 2020."
|
||||||
|
});
|
||||||
|
|
||||||
|
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");
|
||||||
|
});
|
||||||
|
|
||||||
it("treats organization-level earnings and best-year wording as business overview", () => {
|
it("treats organization-level earnings and best-year wording as business overview", () => {
|
||||||
const policy = buildPolicy({
|
const policy = buildPolicy({
|
||||||
resolveAddressIntent: () => ({ intent: "customer_revenue_and_payments", confidence: "high" })
|
resolveAddressIntent: () => ({ intent: "customer_revenue_and_payments", confidence: "high" })
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue