Post-F: закрепить семантическую целостность MCP-цепочек

This commit is contained in:
dctouch 2026-04-24 18:35:12 +03:00
parent 830e05fe97
commit 796a9a93f2
24 changed files with 2408 additions and 104 deletions

BIN
artifacts/m23_latest.json Normal file

Binary file not shown.

View File

@ -154,7 +154,7 @@ This phase is only healthy when the following are true:
5. exact route success cannot silently degrade into a semantically wrong user answer; 5. exact route success cannot silently degrade into a semantically wrong user answer;
6. replay verdict is based first on human business meaning, only then on technical internals. 6. replay verdict is based first on human business meaning, only then on technical internals.
## Current Status - 2026-04-23 ## Current Status - 2026-04-24
This phase is already active in runtime code and replay-backed. This phase is already active in runtime code and replay-backed.
@ -164,6 +164,10 @@ Materially hardened in this pass:
- explicit one-organization open value-flow asks now stop for organization clarification instead of answering a global total under missing scope; - explicit one-organization open value-flow asks now stop for organization clarification instead of answering a global total under missing scope;
- incoming-vs-outgoing money comparisons now defer away from one-sided exact supplier/customer recipes into the bounded bidirectional value-flow discovery path; - incoming-vs-outgoing money comparisons now defer away from one-sided exact supplier/customer recipes into the bounded bidirectional value-flow discovery path;
- document asks with numeric counterparty suffixes such as `Жуковка 51` no longer collapse into bank-operation routing only because the suffix looks like account `51`; - document asks with numeric counterparty suffixes such as `Жуковка 51` no longer collapse into bank-operation routing only because the suffix looks like account `51`;
- rejected LLM predecompose rewrites can no longer inject account scope or truncated semantic hints for numeric counterparty suffixes such as `Жуковке 51`; the session state now keeps the counterparty anchor and does not carry `account: 51`;
- explicit raw entity search after a ranking/value-flow dialogue now resets stale assistant/followup scope: `Найди в 1С Группу СВК` becomes `source_signal=raw_text` with `explicit_entity_candidates=["Группа СВК"]`, no stale organization/date/ranking scope in the discovery turn input, and downstream follow-ups then carry the grounded SVK object intentionally;
- open-organization ranked value-flow questions now stay subjectless by design: predicate-shaped entity pollution such as `принёс наибольшую выручку организации в` is discarded, referential organization placeholders such as `эта организация` are resolved through the active organization scope, and the MCP ranking answer overrides exact-lane year-summary replies when the user asked `кто`;
- ranked value-flow answer drafts no longer leak raw English/internal evidence lines into the user-facing response;
- VAT exact materialization now survives period-window filtering instead of collapsing into `recipe_visibility_gap`; - VAT exact materialization now survives period-window filtering instead of collapsing into `recipe_visibility_gap`;
- confirmed VAT tax-period turns now keep raw month extraction separate from runtime tax-quarter execution, so exact VAT routes do not lose the intended tax period; - confirmed VAT tax-period turns now keep raw month extraction separate from runtime tax-quarter execution, so exact VAT routes do not lose the intended tax period;
- post-pivot receivables recovery now preserves the selected open-items recipe without weakening strict account-scope guards for risk replies; - post-pivot receivables recovery now preserves the selected open-items recipe without weakening strict account-scope guards for risk replies;
@ -183,6 +187,8 @@ Replay-backed anchors for the current layer include:
- `address_truth_harness_phase72_document_to_contracts_year_switch_live_rerun3` - `address_truth_harness_phase72_document_to_contracts_year_switch_live_rerun3`
- `address_truth_harness_phase80_payments_to_contracts_to_documents_all_time_live_rerun1` - `address_truth_harness_phase80_payments_to_contracts_to_documents_all_time_live_rerun1`
- `address_truth_harness_phase82_human_mixed_integrity_status_dialog_post_m23_rerun_documents_scope_bidirectional` - `address_truth_harness_phase82_human_mixed_integrity_status_dialog_post_m23_rerun_documents_scope_bidirectional`
- `address_truth_harness_phase82_human_mixed_integrity_status_dialog_post_f_account_injection_guard_clean_scope`
- `address_truth_harness_post_f_cross_stage_canary_agent_20260424_live5`, accepted `24/24`, and saved into autoruns as `AGENT | Post-F cross-stage semantic integrity canary` (`gen-ag04241406-abe4d8`)
## Honest Remaining Risk ## Honest Remaining Risk

View File

@ -36,7 +36,7 @@ This package answers the next question:
16. [16 - data_need_graph_and_open_world_mcp_plan_2026-04-22.md](./16%20-%20data_need_graph_and_open_world_mcp_plan_2026-04-22.md) 16. [16 - data_need_graph_and_open_world_mcp_plan_2026-04-22.md](./16%20-%20data_need_graph_and_open_world_mcp_plan_2026-04-22.md)
17. [17 - post_f_semantic_integrity_hardening_2026-04-23.md](./17%20-%20post_f_semantic_integrity_hardening_2026-04-23.md) 17. [17 - post_f_semantic_integrity_hardening_2026-04-23.md](./17%20-%20post_f_semantic_integrity_hardening_2026-04-23.md)
## Current Status Snapshot (2026-04-23) ## Current Status Snapshot (2026-04-24)
This package is no longer planning-only. This package is no longer planning-only.
@ -65,7 +65,7 @@ Current honest status:
- pre-multidomain readiness: `~88%` - pre-multidomain readiness: `~88%`
- bounded-autonomy foundation readiness: `~86%` - bounded-autonomy foundation readiness: `~86%`
- open-world bounded-autonomy readiness: `~71%` - open-world bounded-autonomy readiness: `~71%`
- graph snapshot after latest rebuild: `5886 nodes`, `12754 edges`, `136 communities` - graph snapshot after latest rebuild: `5891 nodes`, `12769 edges`, `135 communities`
- current breakpoint: - current 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;
@ -91,6 +91,8 @@ Latest live proof now includes:
- `address_truth_harness_phase72_document_to_contracts_year_switch_live_rerun3` accepted - `address_truth_harness_phase72_document_to_contracts_year_switch_live_rerun3` accepted
- `address_truth_harness_phase80_payments_to_contracts_to_documents_all_time_live_rerun1` accepted - `address_truth_harness_phase80_payments_to_contracts_to_documents_all_time_live_rerun1` accepted
- `address_truth_harness_phase82_human_mixed_integrity_status_dialog_post_m23_rerun_documents_scope_bidirectional` accepted `19/19` - `address_truth_harness_phase82_human_mixed_integrity_status_dialog_post_m23_rerun_documents_scope_bidirectional` accepted `19/19`
- `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_live5` accepted `24/24`, proving a saved cross-stage AGENT canary across VAT metadata, 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_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

View File

@ -0,0 +1,249 @@
{
"schema_version": "domain_truth_harness_spec_v1",
"scenario_id": "address_truth_harness_phase82_human_mixed_integrity_status_dialog",
"domain": "address_phase82_human_mixed_integrity_status_dialog",
"title": "ARCH: Post-F Semantic Integrity Hardening mixed human status dialog",
"description": "Human-facing AGENT dialog that mixes the current Post-F repeated-pivot integrity contour with earlier bounded-autonomy contours: repeated counterparty pivots across documents, payments, and contracts; organization-scoped money analytics without a preselected counterparty; and a grounded named-counterparty money chain through incoming, payout, net, documents, and movements. The scenario uses explicit human resets between topics so the assistant must prove both continuity and clean topic switching rather than guessing vague 'continue' turns.",
"bindings": {},
"steps": [
{
"step_id": "step_01_documents_by_counterparty",
"title": "Open documents for the counterparty",
"question": "Покажи документы по Жуковке 51.",
"allowed_reply_types": ["factual", "factual_with_explanation", "partial_coverage"],
"required_answer_patterns_all": [
"(?i)жуковк",
"(?i)документ|сч[её]т|акт|накладн|строк"
],
"criticality": "critical",
"semantic_tags": ["post_f_integrity_hardening", "documents_by_counterparty", "pivot_seed", "human_dialog"]
},
{
"step_id": "step_02_payments_by_pronoun_followup",
"title": "Pivot to payments",
"question": "Хорошо, а теперь платежи по нему тоже покажи.",
"allowed_reply_types": ["factual", "factual_with_explanation", "partial_coverage"],
"required_answer_patterns_all": [
"(?i)жуковк|контрагент",
"(?i)платеж|операц|банк|поступлен|списан"
],
"criticality": "critical",
"semantic_tags": ["post_f_integrity_hardening", "payments_followup", "counterparty_pronoun_resolution", "human_dialog"]
},
{
"step_id": "step_03_contracts_after_payments_pivot",
"title": "Pivot to contracts",
"question": "А по нему договоры?",
"allowed_reply_types": ["factual", "factual_with_explanation", "partial_coverage"],
"required_answer_patterns_all": [
"(?i)жуковк|контрагент",
"(?i)договор|контракт|соглаш"
],
"criticality": "critical",
"semantic_tags": ["post_f_integrity_hardening", "contracts_followup", "second_pivot", "human_dialog"]
},
{
"step_id": "step_04_documents_after_contracts_pivot",
"title": "Pivot back to documents",
"question": "А по нему документы?",
"allowed_reply_types": ["factual", "factual_with_explanation", "partial_coverage"],
"required_answer_patterns_all": [
"(?i)жуковк|контрагент",
"(?i)документ|сч[её]т|акт|накладн|строк"
],
"criticality": "critical",
"semantic_tags": ["post_f_integrity_hardening", "documents_followup", "third_pivot", "human_dialog"]
},
{
"step_id": "step_05_payments_after_third_pivot",
"title": "Pivot back again to payments",
"question": "А по нему платежи?",
"allowed_reply_types": ["factual", "factual_with_explanation", "partial_coverage"],
"required_answer_patterns_all": [
"(?i)жуковк|контрагент",
"(?i)платеж|операц|банк|поступлен|списан"
],
"criticality": "critical",
"semantic_tags": ["post_f_integrity_hardening", "payments_followup", "fourth_pivot", "human_dialog"]
},
{
"step_id": "step_06_year_switch_after_fourth_pivot",
"title": "Switch the year after the fourth pivot",
"question": "А за 2021?",
"allowed_reply_types": ["factual", "factual_with_explanation", "partial_coverage"],
"required_answer_patterns_all": [
"(?i)2021",
"(?i)жуковк|контрагент",
"(?i)платеж|операц|банк|поступлен|списан"
],
"forbidden_answer_patterns": [
"(?i)метадан",
"(?i)схем",
"(?i)объект[а-я]* 1с"
],
"criticality": "critical",
"semantic_tags": ["post_f_integrity_hardening", "year_switch_after_fourth_pivot", "payments_followup", "human_dialog"]
},
{
"step_id": "step_07_open_scope_incoming_total",
"title": "Reset to organization-scoped money analytics without a preselected counterparty",
"question": "С Жуковкой закончили. Теперь нужна другая задача: быстрый денежный срез по одной организации. Если для ответа нужна организация, просто уточни ее. Сколько вообще входящих денег было за 2020 год?",
"allowed_reply_types": ["clarification_required", "partial_coverage"],
"required_answer_patterns_all": [
"(?i)уточн|нужно",
"(?i)организац"
],
"criticality": "critical",
"semantic_tags": ["organization_scope", "open_scope_total", "topic_reset", "human_dialog"]
},
{
"step_id": "step_08_open_scope_org_clarification",
"title": "Provide the organization and get the 2020 incoming total",
"question": "По ООО Альтернатива Плюс.",
"allowed_reply_types": ["factual", "factual_with_explanation", "partial_coverage"],
"required_answer_patterns_all": [
"(?i)2020",
"(?i)входящ|поступлен|получ"
],
"forbidden_answer_patterns": [
"(?i)уточните .*контрагент",
"(?i)не найден контрагент",
"(?i)уточните .*организац"
],
"criticality": "critical",
"semantic_tags": ["organization_scope", "open_scope_total", "organization_clarification", "human_dialog"]
},
{
"step_id": "step_09_open_scope_all_time_followup",
"title": "Broaden the same organization slice to all available time",
"question": "Понял, тогда за все время.",
"allowed_reply_types": ["factual", "factual_with_explanation", "partial_coverage"],
"required_answer_patterns_all": [
"(?i)все доступное время|все время|весь период",
"(?i)входящ|поступлен|получ"
],
"forbidden_answer_patterns": [
"(?i)за 2020",
"(?i)уточните .*контрагент",
"(?i)уточните .*период",
"(?i)уточните .*организац"
],
"criticality": "critical",
"semantic_tags": ["organization_scope", "all_time_followup", "human_dialog"]
},
{
"step_id": "step_10_bidirectional_comparison",
"title": "Ask which money direction is larger for the organization",
"question": "Хорошо. А что по ООО Альтернатива Плюс больше в 2020 году: входящие или исходящие деньги?",
"allowed_reply_types": ["factual", "factual_with_explanation", "partial_coverage"],
"required_answer_patterns_all": [
"(?i)2020",
"(?i)входящ|исходящ|получ|заплат|больше"
],
"criticality": "critical",
"semantic_tags": ["organization_scope", "value_flow_comparison", "human_dialog"]
},
{
"step_id": "step_11_comparison_year_switch",
"title": "Ask the same comparison for another year",
"question": "А что по ООО Альтернатива Плюс больше уже за 2021 год: входящие или исходящие деньги?",
"allowed_reply_types": ["factual", "factual_with_explanation", "partial_coverage"],
"required_answer_patterns_all": [
"(?i)2021",
"(?i)входящ|исходящ|получ|заплат|больше"
],
"criticality": "critical",
"semantic_tags": ["organization_scope", "value_flow_comparison", "year_switch", "human_dialog"]
},
{
"step_id": "step_12_ranking_top_counterparty",
"title": "Ask who brought the most money for the organization",
"question": "И кто больше всего принес денег этой организации в 2020 году?",
"allowed_reply_types": ["factual", "factual_with_explanation", "partial_coverage"],
"required_answer_patterns_all": [
"(?i)2020",
"(?i)кто|контрагент|клиент|принес|доход"
],
"criticality": "critical",
"semantic_tags": ["organization_scope", "value_flow_ranking", "human_dialog"]
},
{
"step_id": "step_13_ranking_year_switch",
"title": "Ask the same ranking for another year",
"question": "А в 2021 году?",
"allowed_reply_types": ["factual", "factual_with_explanation", "partial_coverage"],
"required_answer_patterns_all": [
"(?i)2021"
],
"criticality": "critical",
"semantic_tags": ["organization_scope", "value_flow_ranking", "year_switch", "human_dialog"]
},
{
"step_id": "step_14_ground_named_counterparty",
"title": "Reset to a named grounded counterparty",
"question": "Теперь отдельная тема по конкретному контрагенту. Найди в 1С Группу СВК.",
"allowed_reply_types": ["factual", "factual_with_explanation", "partial_coverage"],
"required_answer_patterns_all": [
"(?i)свк|группа свк|контрагент"
],
"criticality": "critical",
"semantic_tags": ["grounded_counterparty", "topic_reset", "human_dialog"]
},
{
"step_id": "step_15_counterparty_incoming_2020",
"title": "Ask how much money was received from the grounded counterparty",
"question": "Сколько получили по нему за 2020 год?",
"allowed_reply_types": ["factual", "factual_with_explanation", "partial_coverage"],
"required_answer_patterns_all": [
"(?i)2020",
"(?i)получ|входящ|поступлен"
],
"criticality": "critical",
"semantic_tags": ["grounded_counterparty", "incoming_value_flow", "human_dialog"]
},
{
"step_id": "step_16_counterparty_payout_followup",
"title": "Ask how much was paid to the same grounded counterparty",
"question": "А теперь сколько заплатили?",
"allowed_reply_types": ["factual", "factual_with_explanation", "partial_coverage"],
"required_answer_patterns_all": [
"(?i)заплат|исходящ|списан|платеж"
],
"criticality": "critical",
"semantic_tags": ["grounded_counterparty", "payout_value_flow", "human_dialog"]
},
{
"step_id": "step_17_counterparty_net_followup",
"title": "Ask for net after incoming and payout",
"question": "А какое нетто?",
"allowed_reply_types": ["factual", "factual_with_explanation", "partial_coverage"],
"required_answer_patterns_all": [
"(?i)нетто|сальдо|получ|заплат"
],
"criticality": "critical",
"semantic_tags": ["grounded_counterparty", "net_value_flow", "human_dialog"]
},
{
"step_id": "step_18_counterparty_documents_pivot",
"title": "Pivot from money to documents for the same counterparty",
"question": "А по документам?",
"allowed_reply_types": ["factual", "factual_with_explanation", "partial_coverage"],
"required_answer_patterns_all": [
"(?i)документ|счет|сч[её]т[- ]?фактур|накладн|акт|строк"
],
"criticality": "critical",
"semantic_tags": ["grounded_counterparty", "documents_pivot", "human_dialog"]
},
{
"step_id": "step_19_counterparty_movements_pivot",
"title": "Pivot from documents to movements for the same counterparty",
"question": "А по движениям?",
"allowed_reply_types": ["factual", "factual_with_explanation", "partial_coverage"],
"required_answer_patterns_all": [
"(?i)движени|операц|платеж|поступлен|списан|строк"
],
"criticality": "critical",
"semantic_tags": ["grounded_counterparty", "movements_pivot", "human_dialog"]
}
]
}

View File

@ -0,0 +1,238 @@
{
"schema_version": "domain_truth_harness_spec_v1",
"scenario_id": "address_truth_harness_post_f_cross_stage_canary_agent_20260424",
"domain": "address_post_f_cross_stage_canary_agent",
"title": "AGENT | Post-F cross-stage semantic integrity canary",
"description": "Long human-facing AGENT dialog for the end of Post-F Semantic Integrity Hardening. It deliberately crosses older validated contours and the current fix surface in one session: VAT/metadata orientation, movement-to-document pivots, numeric counterparty suffix scope, repeated documents/payments/contracts pivots, organization open-scope clarification, bidirectional value-flow, ranking, grounded SVK counterparty chains, and final document/movement pivots. The goal is to catch stale scope, account injection, wrong post-pivot arbitration, materialization gaps, and regressions in older bounded MCP stages before saving the run into autoruns.",
"bindings": {},
"steps": [
{
"step_id": "step_01_vat_metadata_orientation",
"title": "Orient inside VAT objects",
"question": "Мне нужно понять, где в 1С по НДС вообще лежат данные. Какие объекты стоит смотреть по НДС?",
"allowed_reply_types": ["factual", "factual_with_explanation", "partial_coverage"],
"required_answer_patterns_all": ["(?i)ндс", "(?i)метадан|metadata|документ|регистр|объект"],
"criticality": "critical",
"semantic_tags": ["legacy_phase64", "metadata_surface", "vat_orientation", "cross_stage_canary"]
},
{
"step_id": "step_02_vat_movements_org_needs_period",
"title": "Choose movement lane and organization",
"question": "Хорошо, тогда покажи движения по ООО Альтернатива Плюс.",
"allowed_reply_types": ["clarification_required", "partial_coverage", "factual_with_explanation"],
"required_answer_patterns_all": ["(?i)движени|регистр|операц|период|уточн"],
"criticality": "critical",
"semantic_tags": ["legacy_phase64", "movement_lane_after_metadata", "organization_scope"]
},
{
"step_id": "step_03_vat_movements_2020",
"title": "Provide the period",
"question": "За 2020 год.",
"allowed_reply_types": ["factual", "factual_with_explanation", "partial_coverage"],
"required_answer_patterns_all": ["(?i)2020", "(?i)ндс|движени|регистр|операц|строк|поступлен|списан"],
"criticality": "critical",
"semantic_tags": ["legacy_phase64", "movement_execution", "period_clarification_resume"]
},
{
"step_id": "step_04_vat_documents_pivot",
"title": "Pivot from movements to documents",
"question": "А теперь по документам?",
"allowed_reply_types": ["factual", "factual_with_explanation", "partial_coverage"],
"required_answer_patterns_all": ["(?i)документ|счет|сч[её]т|накладн|акт|строк"],
"forbidden_answer_patterns": ["(?i)уточните .*организац", "(?i)уточните .*период"],
"criticality": "critical",
"semantic_tags": ["legacy_phase64", "document_pivot_after_movement", "scope_reuse"]
},
{
"step_id": "step_05_vat_documents_year_switch",
"title": "Switch document slice to another year",
"question": "А теперь за 2021 год?",
"allowed_reply_types": ["factual", "factual_with_explanation", "partial_coverage"],
"required_answer_patterns_all": ["(?i)2021", "(?i)документ|счет|сч[её]т|накладн|акт|строк"],
"forbidden_answer_patterns": ["(?i)уточните .*организац", "(?i)уточните .*период"],
"criticality": "critical",
"semantic_tags": ["legacy_phase64", "year_switch_after_document_pivot"]
},
{
"step_id": "step_06_vat_documents_all_time",
"title": "Broaden document slice to all available time",
"question": "А теперь за все время?",
"allowed_reply_types": ["factual", "factual_with_explanation", "partial_coverage"],
"required_answer_patterns_all": ["(?i)все доступное время|все время|весь период|истори", "(?i)документ|счет|сч[её]т|накладн|акт|строк"],
"forbidden_answer_patterns": ["(?i)уточните .*период"],
"criticality": "critical",
"semantic_tags": ["legacy_phase64", "all_time_followup", "document_lane_continuity"]
},
{
"step_id": "step_07_reset_to_numeric_counterparty_documents",
"title": "Reset to numeric counterparty suffix documents",
"question": "С НДС закончили. Новая тема: покажи документы по Жуковке 51.",
"allowed_reply_types": ["factual", "factual_with_explanation", "partial_coverage"],
"required_answer_patterns_all": ["(?i)жуковк", "(?i)документ|счет|сч[её]т|накладн|акт|строк"],
"forbidden_answer_patterns": ["(?i)счет 51", "(?i)account 51"],
"criticality": "critical",
"semantic_tags": ["post_f", "numeric_counterparty_suffix", "account_injection_guard", "topic_reset"]
},
{
"step_id": "step_08_numeric_counterparty_payments",
"title": "Pivot numeric counterparty to payments",
"question": "Хорошо, а теперь платежи по нему тоже покажи.",
"allowed_reply_types": ["factual", "factual_with_explanation", "partial_coverage"],
"required_answer_patterns_all": ["(?i)жуковк|контрагент", "(?i)платеж|операц|банк|поступлен|списан"],
"criticality": "critical",
"semantic_tags": ["post_f", "counterparty_pronoun_resolution", "payments_followup"]
},
{
"step_id": "step_09_numeric_counterparty_contracts",
"title": "Pivot numeric counterparty to contracts",
"question": "А по нему договоры?",
"allowed_reply_types": ["factual", "factual_with_explanation", "partial_coverage"],
"required_answer_patterns_all": ["(?i)жуковк|контрагент", "(?i)договор|контракт|соглаш"],
"criticality": "critical",
"semantic_tags": ["post_f", "contracts_followup", "repeated_pivot"]
},
{
"step_id": "step_10_numeric_counterparty_documents_again",
"title": "Pivot back to documents again",
"question": "А по нему документы?",
"allowed_reply_types": ["factual", "factual_with_explanation", "partial_coverage"],
"required_answer_patterns_all": ["(?i)жуковк|контрагент", "(?i)документ|счет|сч[её]т|накладн|акт|строк"],
"criticality": "critical",
"semantic_tags": ["post_f", "documents_followup", "repeated_pivot"]
},
{
"step_id": "step_11_numeric_counterparty_year_switch",
"title": "Switch numeric counterparty active lane to 2021",
"question": "А за 2021?",
"allowed_reply_types": ["factual", "factual_with_explanation", "partial_coverage"],
"required_answer_patterns_all": ["(?i)2021", "(?i)жуковк|контрагент"],
"criticality": "critical",
"semantic_tags": ["post_f", "year_switch_after_repeated_pivot"]
},
{
"step_id": "step_12_open_org_money_requires_clarification",
"title": "Reset to open organization-scoped value flow",
"question": "С Жуковкой закончили. Теперь другая задача: быстрый денежный срез по одной организации. Если для ответа нужна организация, просто уточни ее. Сколько вообще входящих денег было за 2020 год?",
"allowed_reply_types": ["clarification_required", "partial_coverage"],
"required_answer_patterns_all": ["(?i)уточн|нужно", "(?i)организац"],
"criticality": "critical",
"semantic_tags": ["post_f", "open_scope_total", "organization_clarification", "topic_reset"]
},
{
"step_id": "step_13_open_org_clarification_answer",
"title": "Provide organization for open money slice",
"question": "По ООО Альтернатива Плюс.",
"allowed_reply_types": ["factual", "factual_with_explanation", "partial_coverage"],
"required_answer_patterns_all": ["(?i)2020|проверенн|строк", "(?i)входящ|поступлен|получ"],
"forbidden_answer_patterns": ["(?i)уточните .*контрагент", "(?i)уточните .*организац"],
"criticality": "critical",
"semantic_tags": ["post_f", "organization_scope", "clarification_resume"]
},
{
"step_id": "step_14_open_org_all_time",
"title": "Broaden same organization money slice",
"question": "Понял, тогда за все время.",
"allowed_reply_types": ["factual", "factual_with_explanation", "partial_coverage"],
"required_answer_patterns_all": ["(?i)все доступное время|все время|весь период|истори", "(?i)входящ|поступлен|получ"],
"forbidden_answer_patterns": ["(?i)уточните .*контрагент", "(?i)уточните .*организац"],
"criticality": "critical",
"semantic_tags": ["post_f", "organization_scope", "all_time_followup"]
},
{
"step_id": "step_15_open_org_bidirectional_2020",
"title": "Bidirectional comparison for organization",
"question": "Хорошо. А что по ООО Альтернатива Плюс больше в 2020 году: входящие или исходящие деньги?",
"allowed_reply_types": ["factual", "factual_with_explanation", "partial_coverage"],
"required_answer_patterns_all": ["(?i)2020", "(?i)входящ|исходящ|получ|заплат|больше"],
"criticality": "critical",
"semantic_tags": ["post_f", "bidirectional_value_flow", "organization_scope"]
},
{
"step_id": "step_16_open_org_ranking_2020",
"title": "Ranking after organization comparison",
"question": "И кто больше всего принес денег этой организации в 2020 году?",
"allowed_reply_types": ["factual", "factual_with_explanation", "partial_coverage"],
"required_answer_patterns_all": ["(?i)2020", "(?i)кто|контрагент|клиент|принес|доход|поступлен"],
"criticality": "critical",
"semantic_tags": ["legacy_phase39", "value_flow_ranking", "organization_scope"]
},
{
"step_id": "step_17_open_org_ranking_year_switch",
"title": "Ranking year switch",
"question": "А в 2021 году?",
"allowed_reply_types": ["factual", "factual_with_explanation", "partial_coverage"],
"required_answer_patterns_all": ["(?i)2021"],
"criticality": "critical",
"semantic_tags": ["legacy_phase39", "year_switch", "organization_scope"]
},
{
"step_id": "step_18_ground_svk_counterparty",
"title": "Reset to grounded SVK counterparty",
"question": "Теперь отдельная тема по конкретному контрагенту. Найди в 1С Группу СВК.",
"allowed_reply_types": ["factual", "factual_with_explanation", "partial_coverage"],
"required_answer_patterns_all": ["(?i)свк", "(?i)контрагент|каталог|1с|заземл"],
"criticality": "critical",
"semantic_tags": ["legacy_phase67", "grounded_counterparty", "topic_reset"]
},
{
"step_id": "step_19_svk_incoming_2020",
"title": "SVK incoming value flow",
"question": "Сколько получили по нему за 2020 год?",
"allowed_reply_types": ["factual", "factual_with_explanation", "partial_coverage"],
"required_answer_patterns_all": ["(?i)2020", "(?i)свк|контрагент|входящ|поступлен|получ"],
"forbidden_answer_patterns": ["(?i)жуковк"],
"criticality": "critical",
"semantic_tags": ["legacy_phase67", "grounded_counterparty", "incoming_value_flow"]
},
{
"step_id": "step_20_svk_payout",
"title": "SVK outgoing value flow",
"question": "А теперь сколько заплатили?",
"allowed_reply_types": ["factual", "factual_with_explanation", "partial_coverage"],
"required_answer_patterns_all": ["(?i)свк|контрагент|заплат|исходящ|списан|платеж"],
"forbidden_answer_patterns": ["(?i)жуковк"],
"criticality": "critical",
"semantic_tags": ["legacy_phase67", "grounded_counterparty", "payout_value_flow"]
},
{
"step_id": "step_21_svk_net",
"title": "SVK net value flow",
"question": "А какое нетто?",
"allowed_reply_types": ["factual", "factual_with_explanation", "partial_coverage"],
"required_answer_patterns_all": ["(?i)свк|контрагент|нетто|сальдо|получ|заплат"],
"forbidden_answer_patterns": ["(?i)жуковк"],
"criticality": "critical",
"semantic_tags": ["legacy_phase67", "grounded_counterparty", "net_value_flow"]
},
{
"step_id": "step_22_svk_documents_pivot",
"title": "SVK documents pivot",
"question": "А по документам?",
"allowed_reply_types": ["factual", "factual_with_explanation", "partial_coverage"],
"required_answer_patterns_all": ["(?i)свк|контрагент|документ|счет|сч[её]т|накладн|акт|строк"],
"forbidden_answer_patterns": ["(?i)жуковк"],
"criticality": "critical",
"semantic_tags": ["legacy_phase67", "grounded_counterparty", "documents_pivot"]
},
{
"step_id": "step_23_svk_movements_pivot",
"title": "SVK movements pivot",
"question": "А по движениям?",
"allowed_reply_types": ["factual", "factual_with_explanation", "partial_coverage"],
"required_answer_patterns_all": ["(?i)свк|контрагент|движени|операц|платеж|поступлен|списан|строк"],
"forbidden_answer_patterns": ["(?i)жуковк"],
"criticality": "critical",
"semantic_tags": ["legacy_phase67", "grounded_counterparty", "movements_pivot"]
},
{
"step_id": "step_24_svk_year_switch",
"title": "SVK year switch",
"question": "А теперь тот же смысл за 2021 год.",
"allowed_reply_types": ["factual", "factual_with_explanation", "partial_coverage"],
"required_answer_patterns_all": ["(?i)2021", "(?i)свк|контрагент"],
"forbidden_answer_patterns": ["(?i)жуковк"],
"criticality": "critical",
"semantic_tags": ["legacy_phase67", "grounded_counterparty", "year_switch"]
}
]
}

View File

@ -52,6 +52,14 @@ function isInternalMechanicsLine(value) {
function userFacingUnknowns(values) { function userFacingUnknowns(values) {
return uniqueStrings(values).filter((value) => !isInternalMechanicsLine(value)); return uniqueStrings(values).filter((value) => !isInternalMechanicsLine(value));
} }
function rankedValueFlowUnknownLines(pilot) {
if (!pilot.derived_ranked_value_flow) {
return userFacingUnknowns(pilot.evidence.unknown_facts);
}
const ranking = pilot.derived_ranked_value_flow;
const period = ranking.period_scope ? `периода ${ranking.period_scope}` : "проверенного окна";
return [`Полный рейтинг контрагентов вне ${period} этим поиском не подтвержден.`];
}
function userFacingLimitations(values) { function userFacingLimitations(values) {
return uniqueStrings(values).filter((value) => !isInternalMechanicsLine(value)); return uniqueStrings(values).filter((value) => !isInternalMechanicsLine(value));
} }
@ -718,13 +726,15 @@ function buildAssistantMcpDiscoveryAnswerDraft(pilot) {
if (monthlyConfirmedLines.length > 0) { if (monthlyConfirmedLines.length > 0) {
pushReason(reasonCodes, "answer_contains_monthly_breakdown"); pushReason(reasonCodes, "answer_contains_monthly_breakdown");
} }
const confirmedLines = derivedValueLine const confirmedLines = pilot.derived_ranked_value_flow && derivedValueLine
? [...pilot.evidence.confirmed_facts, derivedValueLine, ...monthlyConfirmedLines] ? [derivedValueLine]
: derivedEntityResolutionLine : derivedValueLine
? [...pilot.evidence.confirmed_facts, derivedEntityResolutionLine] ? [...pilot.evidence.confirmed_facts, derivedValueLine, ...monthlyConfirmedLines]
: derivedMetadataLine : derivedEntityResolutionLine
? [...pilot.evidence.confirmed_facts, derivedMetadataLine] ? [...pilot.evidence.confirmed_facts, derivedEntityResolutionLine]
: pilot.evidence.confirmed_facts; : derivedMetadataLine
? [...pilot.evidence.confirmed_facts, derivedMetadataLine]
: pilot.evidence.confirmed_facts;
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",
@ -732,7 +742,7 @@ function buildAssistantMcpDiscoveryAnswerDraft(pilot) {
headline: headlineFor(mode, pilot), headline: headlineFor(mode, pilot),
confirmed_lines: uniqueStrings(confirmedLines), confirmed_lines: uniqueStrings(confirmedLines),
inference_lines: uniqueStrings(inferenceLines), inference_lines: uniqueStrings(inferenceLines),
unknown_lines: userFacingUnknowns(pilot.evidence.unknown_facts), unknown_lines: rankedValueFlowUnknownLines(pilot),
limitation_lines: userFacingLimitations([...pilot.query_limitations, ...pilot.evidence.query_limitations]), limitation_lines: userFacingLimitations([...pilot.query_limitations, ...pilot.evidence.query_limitations]),
next_step_line: nextStepFor(mode, pilot), next_step_line: nextStepFor(mode, pilot),
internal_mechanics_allowed: false, internal_mechanics_allowed: false,

View File

@ -59,9 +59,42 @@ function isReferentialEntityPlaceholder(value) {
"этом" "этом"
]).has((0, addressTextRepair_1.normalizeRussianComparableText)(value)); ]).has((0, addressTextRepair_1.normalizeRussianComparableText)(value));
} }
function isReferentialOrganizationPlaceholder(value) {
if (!value) {
return false;
}
return new Set([
"эта организация",
"этой организации",
"этой организацией",
"эту организацию",
"эта компания",
"этой компании",
"этой компанией",
"эту компанию",
"наша организация",
"нашей организации",
"нашей компанией"
]).has((0, addressTextRepair_1.normalizeRussianComparableText)(value));
}
function isValueFlowPredicateEntityCandidate(value) {
if (!value) {
return false;
}
const text = compactLower(value);
const looksLikeRankingPredicate = /(?:прин[её]с|принес|выручк|доходн|больше\s+всего|наибольш)/iu.test(text) &&
/(?:организац|компан|ден[её]г|выручк|поступлен|плат[её]ж)/iu.test(text);
if (!looksLikeRankingPredicate) {
return false;
}
return !/(?<!\p{L})(?:ооо|ип|ао|пао|зао|llc|inc|corp)(?!\p{L})/iu.test(text);
}
function isInvalidEntityCandidate(value) {
return Boolean(value && (isReferentialEntityPlaceholder(value) || isValueFlowPredicateEntityCandidate(value)));
}
function normalizeFollowupCounterpartyCandidate(value) { function normalizeFollowupCounterpartyCandidate(value) {
const text = candidateValue(value); const text = candidateValue(value);
if (!text || isReferentialEntityPlaceholder(text)) { if (!text || isInvalidEntityCandidate(text)) {
return null; return null;
} }
return text; return text;
@ -71,7 +104,7 @@ function pushScopedEntityCandidate(target, value, groundedFollowupEntity) {
if (!text) { if (!text) {
return; return;
} }
if (groundedFollowupEntity && isReferentialEntityPlaceholder(text)) { if ((groundedFollowupEntity && isReferentialEntityPlaceholder(text)) || isValueFlowPredicateEntityCandidate(text)) {
return; return;
} }
pushUnique(target, text); pushUnique(target, text);
@ -88,7 +121,7 @@ function pushNormalizedEntityResolutionCandidate(target, value) {
return; return;
} }
const normalized = canonicalizeEntityResolutionCandidate(text); const normalized = canonicalizeEntityResolutionCandidate(text);
if (normalized && !target.includes(normalized)) { if (normalized && !isInvalidEntityCandidate(normalized) && !target.includes(normalized)) {
target.push(normalized); target.push(normalized);
} }
} }
@ -119,18 +152,25 @@ function collectEntityCandidates(value) {
const result = []; const result = [];
if (Array.isArray(value)) { if (Array.isArray(value)) {
for (const item of value) { for (const item of value) {
pushUnique(result, candidateValue(item)); const candidate = candidateValue(item);
if (!isInvalidEntityCandidate(candidate)) {
pushUnique(result, candidate);
}
} }
return result; return result;
} }
pushUnique(result, candidateValue(value)); const candidate = candidateValue(value);
if (!isInvalidEntityCandidate(candidate)) {
pushUnique(result, candidate);
}
return result; return result;
} }
function collectPredecomposeEntities(predecompose) { function collectPredecomposeEntities(predecompose) {
const entities = toRecordObject(predecompose?.entities); const entities = toRecordObject(predecompose?.entities);
const organization = toNonEmptyString(entities?.organization);
return { return {
counterparty: toNonEmptyString(entities?.counterparty), counterparty: toNonEmptyString(entities?.counterparty),
organization: toNonEmptyString(entities?.organization) organization: isReferentialOrganizationPlaceholder(organization) ? null : organization
}; };
} }
function collectDateScope(predecompose) { function collectDateScope(predecompose) {
@ -541,23 +581,25 @@ function hasMetadataDownstreamContinuationSignal(text) {
} }
function hasEntityResolutionSignal(text) { function hasEntityResolutionSignal(text) {
const hasSearchVerb = /(?:найд(?:и|ите|ем|у)|поищ(?:и|ите|ем)|найти|поиск|search|find|look\s*up)/iu.test(text); const hasSearchVerb = /(?:найд(?:и|ите|ем|у)|поищ(?:и|ите|ем)|найти|поиск|search|find|look\s*up)/iu.test(text);
const hasEntityNoun = /(?:контрагент(?:а|ов)?|поставщик(?:а|ов)?|клиент(?:а|ов)?|counterpart(?:y|ies)|supplier(?:s)?|customer(?:s)?)/iu.test(text); const hasEntityNoun = /(?:контрагент(?:а|ов|у|ом|е)?|поставщик(?:а|ов|у|ом|е)?|клиент(?:а|ов|у|ом|е)?|counterpart(?:y|ies)|supplier(?:s)?|customer(?:s)?)/iu.test(text);
return hasSearchVerb && hasEntityNoun; return hasSearchVerb && hasEntityNoun;
} }
function normalizeEntityResolutionCandidate(value) { function normalizeEntityResolutionCandidate(value) {
return value return value
.replace(/^(?:в\s*1с\s+|в\s+1c\s+|по\s+имени\s+)/iu, "") .replace(/^(?:в\s*1с\s+|в\s+1c\s+|по\s+имени\s+)/iu, "")
.replace(/[?!.]+$/gu, "") .replace(/[?!.]+$/gu, "")
.replace(/^(?:контрагент(?:а|ов)?|поставщик(?:а|ов)?|клиент(?:а|ов)?)\s+/iu, "") .replace(/^(?:контрагент(?:а|ов|у|ом|е)?|поставщик(?:а|ов|у|ом|е)?|клиент(?:а|ов|у|ом|е)?)\s+/iu, "")
.replace(/^(?:counterpart(?:y|ies)|supplier(?:s)?|customer(?:s)?)\s+/iu, "") .replace(/^(?:counterpart(?:y|ies)|supplier(?:s)?|customer(?:s)?)\s+/iu, "")
.replace(/^группу\s+/iu, "Группа ")
.replace(/^[«"'\s]+|[»"'\s]+$/gu, "") .replace(/^[«"'\s]+|[»"'\s]+$/gu, "")
.replace(/\s+/g, " ") .replace(/\s+/g, " ")
.trim(); .trim();
} }
function rawEntityResolutionCandidate(text) { function rawEntityResolutionCandidate(text) {
const patterns = [ const patterns = [
/(?:найд(?:и|ите|ем|у)|поищ(?:и|ите|ем)|найти|search|find|look\s*up)\s+(?:в\s*1с\s+|в\s+1c\s+)?(?:контрагент(?:а|ов)?|поставщик(?:а|ов)?|клиент(?:а|ов)?|counterpart(?:y|ies)|supplier(?:s)?|customer(?:s)?)\s+(.+)$/iu, /(?:найд(?:и|ите|ем|у)|поищ(?:и|ите|ем)|найти|search|find|look\s*up)\s+(?:в\s*1с\s+|в\s+1c\s+)?(?:контрагент(?:а|ов|у|ом|е)?|поставщик(?:а|ов|у|ом|е)?|клиент(?:а|ов|у|ом|е)?|counterpart(?:y|ies)|supplier(?:s)?|customer(?:s)?)\s+(.+)$/iu,
/(?:контрагент(?:а|ов)?|поставщик(?:а|ов)?|клиент(?:а|ов)?|counterpart(?:y|ies)|supplier(?:s)?|customer(?:s)?)\s+(.+?)\s+(?:найд(?:и|ите|ем|у)|поищ(?:и|ите|ем)|найти|search|find|look\s*up)\b/iu /(?:найд(?:и|ите|ем|у)|поищ(?:и|ите|ем)|найти|search|find|look\s*up)\s+(?:в\s*1с\s+|в\s+1c\s+)(.+)$/iu,
/(?:контрагент(?:а|ов|у|ом|е)?|поставщик(?:а|ов|у|ом|е)?|клиент(?:а|ов|у|ом|е)?|counterpart(?:y|ies)|supplier(?:s)?|customer(?:s)?)\s+(.+?)\s+(?:найд(?:и|ите|ем|у)|поищ(?:и|ите|ем)|найти|search|find|look\s*up)\b/iu
]; ];
for (const pattern of patterns) { for (const pattern of patterns) {
const match = text.match(pattern); const match = text.match(pattern);
@ -797,6 +839,7 @@ function buildAssistantMcpDiscoveryTurnInput(input) {
? resolveEntityResolutionAmbiguityChoice(rawEntitySourceText, followupSeed.entityResolutionAmbiguityCandidates) ? resolveEntityResolutionAmbiguityChoice(rawEntitySourceText, followupSeed.entityResolutionAmbiguityCandidates)
: null; : null;
const entityResolutionSignal = rawEntityResolutionSignal || Boolean(rawEntityCandidate) || Boolean(entityResolutionClarificationCandidate); const entityResolutionSignal = rawEntityResolutionSignal || Boolean(rawEntityCandidate) || Boolean(entityResolutionClarificationCandidate);
const rawEntitySearchOverridesStaleScope = Boolean(rawEntityCandidate && entityResolutionSignal);
const rawDomain = toNonEmptyString(assistantTurnMeaning?.asked_domain_family); const rawDomain = toNonEmptyString(assistantTurnMeaning?.asked_domain_family);
const rawAction = toNonEmptyString(assistantTurnMeaning?.asked_action_family); const rawAction = toNonEmptyString(assistantTurnMeaning?.asked_action_family);
const rawAggregationAxis = toNonEmptyString(assistantTurnMeaning?.asked_aggregation_axis); const rawAggregationAxis = toNonEmptyString(assistantTurnMeaning?.asked_aggregation_axis);
@ -815,10 +858,14 @@ function buildAssistantMcpDiscoveryTurnInput(input) {
hasMovementEvidenceFollowupSignal(rawText) || hasMovementEvidenceFollowupSignal(rawText) ||
hasPronounMovementEvidenceFollowupSignal(rawText); hasPronounMovementEvidenceFollowupSignal(rawText);
const assistantTurnMeaningDateScope = toNonEmptyString(assistantTurnMeaning?.explicit_date_scope); const assistantTurnMeaningDateScope = toNonEmptyString(assistantTurnMeaning?.explicit_date_scope);
const assistantTurnMeaningOrganizationScope = toNonEmptyString(assistantTurnMeaning?.explicit_organization_scope); const rawAssistantTurnMeaningOrganizationScope = toNonEmptyString(assistantTurnMeaning?.explicit_organization_scope);
const assistantTurnMeaningOrganizationScope = isReferentialOrganizationPlaceholder(rawAssistantTurnMeaningOrganizationScope)
? null
: rawAssistantTurnMeaningOrganizationScope;
const rawOrganizationMentionSignal = hasOrganizationScopeSignalUtf8(rawText); const rawOrganizationMentionSignal = hasOrganizationScopeSignalUtf8(rawText);
const rawOrganizationScope = extractOrganizationScopeFromRawText(rawUserText ?? rawEffectiveText ?? rawSignalSourceText); const rawOrganizationScope = extractOrganizationScopeFromRawText(rawUserText ?? rawEffectiveText ?? rawSignalSourceText);
const currentTurnOrganizationScope = rawOrganizationScope ?? predecomposeEntities.organization ?? assistantTurnMeaningOrganizationScope; const currentTurnFreshOrganizationScope = rawOrganizationScope ?? predecomposeEntities.organization;
const currentTurnOrganizationScope = currentTurnFreshOrganizationScope ?? assistantTurnMeaningOrganizationScope;
const explicitOrganizationScopeSignal = Boolean(rawOrganizationMentionSignal && currentTurnOrganizationScope); const explicitOrganizationScopeSignal = Boolean(rawOrganizationMentionSignal && currentTurnOrganizationScope);
const organizationClarificationFollowupApplicable = Boolean(followupSeed.domain === "counterparty_value" && const organizationClarificationFollowupApplicable = Boolean(followupSeed.domain === "counterparty_value" &&
!followupSeed.counterparty && !followupSeed.counterparty &&
@ -1130,30 +1177,40 @@ function buildAssistantMcpDiscoveryTurnInput(input) {
lifecycleSignal || lifecycleSignal ||
metadataGroundedDocumentLaneApplicable || metadataGroundedDocumentLaneApplicable ||
metadataGroundedMovementLaneApplicable)); metadataGroundedMovementLaneApplicable));
const metadataLaneScopeHint = explicitCurrentCounterpartyOverridesFollowupEntity const metadataLaneScopeHint = rawEntitySearchOverridesStaleScope
? null ? null
: rawMetadataScopeHint ?? : explicitCurrentCounterpartyOverridesFollowupEntity
followupSeed.metadataScopeHint ?? ? null
followupSeed.discoveryEntity ?? : rawMetadataScopeHint ??
followupSeed.metadataSelectedEntitySet ?? followupSeed.metadataScopeHint ??
null; followupSeed.discoveryEntity ??
followupSeed.metadataSelectedEntitySet ??
null;
const metadataScopedLaneWithoutSubject = Boolean((metadataGroundedMovementLaneApplicable || metadataGroundedDocumentLaneApplicable) && const metadataScopedLaneWithoutSubject = Boolean((metadataGroundedMovementLaneApplicable || metadataGroundedDocumentLaneApplicable) &&
!followupSeed.counterparty && !followupSeed.counterparty &&
metadataLaneCarryoverAvailable); metadataLaneCarryoverAvailable);
const groundedFollowupEntity = metadataScopedLaneWithoutSubject const groundedFollowupEntity = metadataScopedLaneWithoutSubject
? null ? null
: explicitCurrentCounterpartyOverridesFollowupEntity : rawEntitySearchOverridesStaleScope
? null ? null
: followupSeed.counterparty ?? followupSeed.discoveryEntity; : explicitCurrentCounterpartyOverridesFollowupEntity
? null
: followupSeed.counterparty ?? followupSeed.discoveryEntity;
const entityCandidates = entityResolutionSignal ? [] : []; const entityCandidates = entityResolutionSignal ? [] : [];
if (entityResolutionSignal) { if (entityResolutionSignal) {
pushNormalizedEntityResolutionCandidate(entityCandidates, entityResolutionClarificationCandidate); pushNormalizedEntityResolutionCandidate(entityCandidates, entityResolutionClarificationCandidate);
pushNormalizedEntityResolutionCandidate(entityCandidates, rawEntityCandidate); pushNormalizedEntityResolutionCandidate(entityCandidates, rawEntityCandidate);
for (const candidate of collectEntityCandidates(assistantTurnMeaning?.explicit_entity_candidates)) { for (const candidate of collectEntityCandidates(assistantTurnMeaning?.explicit_entity_candidates)) {
pushNormalizedEntityResolutionCandidate(entityCandidates, candidate); if (!rawEntitySearchOverridesStaleScope || sameScopedName(candidate, rawEntityCandidate)) {
pushNormalizedEntityResolutionCandidate(entityCandidates, candidate);
}
}
if (!rawEntitySearchOverridesStaleScope || sameScopedName(normalizedPredecomposeCounterparty, rawEntityCandidate)) {
pushNormalizedEntityResolutionCandidate(entityCandidates, normalizedPredecomposeCounterparty);
}
if (!rawEntitySearchOverridesStaleScope) {
pushNormalizedEntityResolutionCandidate(entityCandidates, followupSeed.counterparty);
} }
pushNormalizedEntityResolutionCandidate(entityCandidates, normalizedPredecomposeCounterparty);
pushNormalizedEntityResolutionCandidate(entityCandidates, followupSeed.counterparty);
} }
else { else {
if (groundedFollowupEntity) { if (groundedFollowupEntity) {
@ -1190,9 +1247,12 @@ function buildAssistantMcpDiscoveryTurnInput(input) {
pushUnique(entityCandidates, predecomposeEntities.organization); pushUnique(entityCandidates, predecomposeEntities.organization);
pushUnique(entityCandidates, followupSeed.organization); pushUnique(entityCandidates, followupSeed.organization);
} }
const explicitOrganizationScope = valueFlowOrganizationStaysScope || !openScopeValueFlowWithoutCounterparty const explicitOrganizationScope = rawEntitySearchOverridesStaleScope && !currentTurnFreshOrganizationScope
? currentTurnOrganizationScope ?? followupSeed.organization ? null
: null; : valueFlowOrganizationStaysScope || !openScopeValueFlowWithoutCounterparty
? (rawEntitySearchOverridesStaleScope ? currentTurnFreshOrganizationScope : currentTurnOrganizationScope) ??
followupSeed.organization
: null;
if (explicitCurrentCounterpartyCandidate && if (explicitCurrentCounterpartyCandidate &&
(valueFlowSignal || lifecycleSignal || metadataGroundedDocumentLaneApplicable || metadataGroundedMovementLaneApplicable)) { (valueFlowSignal || lifecycleSignal || metadataGroundedDocumentLaneApplicable || metadataGroundedMovementLaneApplicable)) {
for (let index = entityCandidates.length - 1; index >= 0; index -= 1) { for (let index = entityCandidates.length - 1; index >= 0; index -= 1) {
@ -1226,11 +1286,16 @@ function buildAssistantMcpDiscoveryTurnInput(input) {
(clarificationLoopStillNeedsPeriod || (clarificationLoopStillNeedsPeriod ||
openScopeValueFlowWithoutResolvedCounterparty || openScopeValueFlowWithoutResolvedCounterparty ||
(valueFlowOrganizationStaysScope && (Boolean(followupSeed.rankingNeed) || bidirectionalValueFlowSignal)))); (valueFlowOrganizationStaysScope && (Boolean(followupSeed.rankingNeed) || bidirectionalValueFlowSignal))));
const normalizedPredecomposeDateScope = suppressImplicitCurrentDateScope && isImplicitCurrentDateScope(predecomposeDateScope) ? null : predecomposeDateScope; const normalizedPredecomposeDateScope = (rawEntitySearchOverridesStaleScope && !currentTurnCarriesExplicitPeriod) ||
const normalizedAssistantTurnMeaningDateScope = suppressImplicitCurrentDateScope && isImplicitCurrentDateScope(assistantTurnMeaningDateScope) (suppressImplicitCurrentDateScope && isImplicitCurrentDateScope(predecomposeDateScope))
? null
: predecomposeDateScope;
const normalizedAssistantTurnMeaningDateScope = rawEntitySearchOverridesStaleScope ||
(suppressImplicitCurrentDateScope && isImplicitCurrentDateScope(assistantTurnMeaningDateScope))
? null ? null
: assistantTurnMeaningDateScope; : assistantTurnMeaningDateScope;
const normalizedFollowupDateScope = suppressImplicitCurrentDateScope && isImplicitCurrentDateScope(followupSeed.dateScope) const normalizedFollowupDateScope = rawEntitySearchOverridesStaleScope ||
(suppressImplicitCurrentDateScope && isImplicitCurrentDateScope(followupSeed.dateScope))
? null ? null
: followupSeed.dateScope; : followupSeed.dateScope;
const explicitDateScope = rawAllTimeScopeSignal const explicitDateScope = rawAllTimeScopeSignal
@ -1277,7 +1342,9 @@ function buildAssistantMcpDiscoveryTurnInput(input) {
? metadataActionFromRawText(rawText) ?? seededAction ? metadataActionFromRawText(rawText) ?? seededAction
: rawAction ?? seededAction, : rawAction ?? seededAction,
asked_aggregation_axis: monthlyAggregationSignal ? "month" : rawAggregationAxis, asked_aggregation_axis: monthlyAggregationSignal ? "month" : rawAggregationAxis,
seeded_ranking_need: valueFlowSignal && followupSeed.rankingNeed ? followupSeed.rankingNeed : undefined, seeded_ranking_need: valueFlowSignal && followupSeed.rankingNeed && !rawEntitySearchOverridesStaleScope
? followupSeed.rankingNeed
: undefined,
explicit_entity_candidates: entityCandidates, explicit_entity_candidates: entityCandidates,
metadata_ambiguity_entity_sets: metadataAmbiguityLaneClarificationApplicable && followupSeed.metadataAmbiguityEntitySets.length > 0 metadata_ambiguity_entity_sets: metadataAmbiguityLaneClarificationApplicable && followupSeed.metadataAmbiguityEntitySets.length > 0
? followupSeed.metadataAmbiguityEntitySets ? followupSeed.metadataAmbiguityEntitySets
@ -1381,28 +1448,30 @@ function buildAssistantMcpDiscoveryTurnInput(input) {
groundedValueFlowFollowupApplicable groundedValueFlowFollowupApplicable
}); });
const hasTurnMeaning = Object.keys(cleanTurnMeaning).length > 0; const hasTurnMeaning = Object.keys(cleanTurnMeaning).length > 0;
const sourceSignal = assistantTurnMeaning const sourceSignal = rawEntitySearchOverridesStaleScope
? "assistant_turn_meaning" ? "raw_text"
: followupDiscoverySeedApplicable || : assistantTurnMeaning
Boolean(entityResolutionClarificationCandidate) || ? "assistant_turn_meaning"
effectiveMetadataFollowupSeedApplicable || : followupDiscoverySeedApplicable ||
metadataAmbiguityLaneClarificationApplicable Boolean(entityResolutionClarificationCandidate) ||
? "followup_context" effectiveMetadataFollowupSeedApplicable ||
: metadataGroundedMovementLaneApplicable metadataAmbiguityLaneClarificationApplicable
? "followup_context" ? "followup_context"
: metadataGroundedDocumentLaneApplicable : metadataGroundedMovementLaneApplicable
? "followup_context" ? "followup_context"
: predecomposeContract : metadataGroundedDocumentLaneApplicable
? "predecompose_contract" ? "followup_context"
: lifecycleSignal : predecomposeContract
? "raw_text" ? "predecompose_contract"
: valueFlowSignal : lifecycleSignal
? "raw_text" ? "raw_text"
: entityResolutionSignal : valueFlowSignal
? "raw_text" ? "raw_text"
: rawMetadataSignal || effectiveMetadataFollowupSeedApplicable : entityResolutionSignal
? "raw_text" ? "raw_text"
: "none"; : rawMetadataSignal || effectiveMetadataFollowupSeedApplicable
? "raw_text"
: "none";
if (lifecycleSignal) { if (lifecycleSignal) {
pushReason(reasonCodes, "mcp_discovery_lifecycle_signal_detected"); pushReason(reasonCodes, "mcp_discovery_lifecycle_signal_detected");
} }
@ -1521,7 +1590,7 @@ function buildAssistantMcpDiscoveryTurnInput(input) {
normalizedPredecomposeCounterparty) { normalizedPredecomposeCounterparty) {
pushReason(reasonCodes, "mcp_discovery_counterparty_from_predecompose"); pushReason(reasonCodes, "mcp_discovery_counterparty_from_predecompose");
} }
if (followupSeed.counterparty) { if (followupSeed.counterparty && !rawEntitySearchOverridesStaleScope) {
pushReason(reasonCodes, "mcp_discovery_counterparty_from_followup_context"); pushReason(reasonCodes, "mcp_discovery_counterparty_from_followup_context");
} }
if (followupDateScopeApplied) { if (followupDateScopeApplied) {

View File

@ -1932,7 +1932,7 @@ function textMojibakeScoreForAddress(value) {
const source = String(value ?? ""); const source = String(value ?? "");
const cyrillic = (source.match(/[А-Яа-яЁё]/g) ?? []).length; const cyrillic = (source.match(/[А-Яа-яЁё]/g) ?? []).length;
const latin = (source.match(/[A-Za-z]/g) ?? []).length; const latin = (source.match(/[A-Za-z]/g) ?? []).length;
const hardMarkers = (source.match(/[Ѓѓ‚„…†‡€‰‹ЉЊЌЋЏ<EFBFBD>?’“”•–—™љ›њќћџ]/g) ?? []).length; const hardMarkers = (source.match(/[Ѓѓ‚„…†‡€‰‹ЉЊЌЋЏ\uFFFD?’“”•–—™љ›њќћџ]/g) ?? []).length;
const pairMarkers = (source.match(/(?:Р.|С.|Ð.|Ñ.)/g) ?? []).length; const pairMarkers = (source.match(/(?:Р.|С.|Ð.|Ñ.)/g) ?? []).length;
const doubleEncodedMarkers = (source.match(/(?:Г[Ђ-џ]|В[Ђ-џ]|Ã.|Â.)/gu) ?? []).length; const doubleEncodedMarkers = (source.match(/(?:Г[Ђ-џ]|В[Ђ-џ]|Ã.|Â.)/gu) ?? []).length;
return cyrillic + latin - hardMarkers * 3 - pairMarkers * 2 - doubleEncodedMarkers * 2; return cyrillic + latin - hardMarkers * 3 - pairMarkers * 2 - doubleEncodedMarkers * 2;
@ -1942,7 +1942,7 @@ function looksLikeMojibakeForAddress(value) {
if (!source.trim()) { if (!source.trim()) {
return false; return false;
} }
if (/[Ѓѓ‚„…†‡€‰‹ЉЊЌЋЏ<EFBFBD>?’“”•–—™љ›њќћџ]/.test(source)) { if (/[Ѓѓ‚„…†‡€‰‹ЉЊЌЋЏ\uFFFD?’“”•–—™љ›њќћџ]/.test(source)) {
return true; return true;
} }
if ((source.match(/(?:Р.|С.|Ð.|Ñ.)/g) ?? []).length >= 2) { if ((source.match(/(?:Р.|С.|Ð.|Ñ.)/g) ?? []).length >= 2) {
@ -2173,7 +2173,7 @@ function normalizeCounterpartyForFollowupMatch(value) {
return compactWhitespace(repairAddressMojibake(String(value ?? "")) return compactWhitespace(repairAddressMojibake(String(value ?? ""))
.toLowerCase() .toLowerCase()
.replace(/ё/g, "е") .replace(/ё/g, "е")
.replace(/[«»"'`“”„’<EFBFBD>?]/g, " ") .replace(/[«»"'`“”„’\uFFFD?]/g, " ")
.replace(/[^a-zа-я0-9\s._-]+/giu, " ")); .replace(/[^a-zа-я0-9\s._-]+/giu, " "));
} }
function normalizeCounterpartyTokenForFollowupMatch(value) { function normalizeCounterpartyTokenForFollowupMatch(value) {
@ -2219,7 +2219,7 @@ function extractDisplayedAddressEntityCandidates(replyText, entityType = "unknow
if (parts.length >= 2 && /^\d{4}-\d{2}-\d{2}/.test(parts[0] ?? "")) { if (parts.length >= 2 && /^\d{4}-\d{2}-\d{2}/.test(parts[0] ?? "")) {
counterpartyCandidate = parts[1] ?? counterpartyCandidate; counterpartyCandidate = parts[1] ?? counterpartyCandidate;
} }
const cleanedCandidate = compactWhitespace(counterpartyCandidate.replace(/^["'«»“”„`<EFBFBD>?]+|["'«»“”„`<>?]+$/gu, "")); const cleanedCandidate = compactWhitespace(counterpartyCandidate.replace(/^["'«»“”„`\uFFFD?]+|["'«»“”„`\uFFFD?]+$/gu, ""));
if (!cleanedCandidate || cleanedCandidate.length < 2) { if (!cleanedCandidate || cleanedCandidate.length < 2) {
continue; continue;
} }
@ -3268,6 +3268,11 @@ function hasSameDateAccountFollowupSignalForPredecompose(text) {
/(?:^|\s)по\s+\d{2}(?:[.,]\d{1,2})?(?=$|[\s,.;:!?])/iu.test(source) || /(?:^|\s)по\s+\d{2}(?:[.,]\d{1,2})?(?=$|[\s,.;:!?])/iu.test(source) ||
/\b\d{2}(?:[.,]\d{1,2})\b/u.test(source)); /\b\d{2}(?:[.,]\d{1,2})\b/u.test(source));
} }
function isCounterpartyDrilldownIntentForPredecompose(intent) {
return intent === "list_documents_by_counterparty" ||
intent === "bank_operations_by_counterparty" ||
intent === "list_contracts_by_counterparty";
}
function hasPredecomposeDiagnosticUncertaintyLead(text) { function hasPredecomposeDiagnosticUncertaintyLead(text) {
const normalized = compactWhitespace(repairAddressMojibake(String(text ?? "")).toLowerCase()); const normalized = compactWhitespace(repairAddressMojibake(String(text ?? "")).toLowerCase());
if (!normalized) { if (!normalized) {
@ -3486,8 +3491,16 @@ async function runAddressLlmPreDecompose(normalizerService, payload, userMessage
const sourceHasExplicitAccountAnchor = (0, addressIntentResolver_1.hasAccountNumberAnchor)(repairedSourceMessage || userMessage) || const sourceHasExplicitAccountAnchor = (0, addressIntentResolver_1.hasAccountNumberAnchor)(repairedSourceMessage || userMessage) ||
(0, addressIntentResolver_1.hasCompactAccountCodeToken)(repairedSourceMessage || userMessage); (0, addressIntentResolver_1.hasCompactAccountCodeToken)(repairedSourceMessage || userMessage);
const candidateInjectsAccountAnchor = Boolean(toNonEmptyString(candidatePredecomposeContract?.entities?.account)); const candidateInjectsAccountAnchor = Boolean(toNonEmptyString(candidatePredecomposeContract?.entities?.account));
if (sourceIntentResolution.intent === "inventory_on_hand_as_of_date" && const sourceAnchorQuality = evaluateAddressAnchorQuality(repairedSourceMessage || userMessage);
candidateIntentResolution.intent === "inventory_on_hand_as_of_date" && const candidateAccountInjectedIntoCounterpartyAnchor = isCounterpartyDrilldownIntentForPredecompose(sourceIntentResolution.intent) &&
sourceIntentResolution.intent === candidateIntentResolution.intent &&
sourceAnchorQuality.anchorType === "counterparty" &&
sourceAnchorQuality.quality >= 2 &&
!sourceHasExplicitAccountAnchor &&
candidateInjectsAccountAnchor;
if (((sourceIntentResolution.intent === "inventory_on_hand_as_of_date" &&
candidateIntentResolution.intent === "inventory_on_hand_as_of_date") ||
candidateAccountInjectedIntoCounterpartyAnchor) &&
!sourceHasExplicitAccountAnchor && !sourceHasExplicitAccountAnchor &&
candidateInjectsAccountAnchor) { candidateInjectsAccountAnchor) {
return attachAddressPredecomposeContract({ return attachAddressPredecomposeContract({
@ -3500,10 +3513,9 @@ async function runAddressLlmPreDecompose(normalizerService, payload, userMessage
reason: "normalized_fragment_rejected_anchor_injection", reason: "normalized_fragment_rejected_anchor_injection",
fallbackRuleHit: null, fallbackRuleHit: null,
sanitizedUserMessage, sanitizedUserMessage,
semanticHints: candidateMeta?.semanticHints ?? null semanticHints: null
}, userMessage); }, userMessage);
} }
const sourceAnchorQuality = evaluateAddressAnchorQuality(repairedSourceMessage || userMessage);
const candidateAnchorQuality = evaluateAddressAnchorQuality(candidate); const candidateAnchorQuality = evaluateAddressAnchorQuality(candidate);
const sameIntentForAnchorSafety = sourceAnchorQuality.intent !== "unknown" && sourceAnchorQuality.intent === candidateAnchorQuality.intent; const sameIntentForAnchorSafety = sourceAnchorQuality.intent !== "unknown" && sourceAnchorQuality.intent === candidateAnchorQuality.intent;
const sourceSelectedObjectItemAnchorValue = toNonEmptyString((0, addressFilterExtractor_1.extractSelectedObjectQuotedValue)(userMessage)) ?? const sourceSelectedObjectItemAnchorValue = toNonEmptyString((0, addressFilterExtractor_1.extractSelectedObjectQuotedValue)(userMessage)) ??

View File

@ -82,6 +82,15 @@ function userFacingUnknowns(values: string[]): string[] {
return uniqueStrings(values).filter((value) => !isInternalMechanicsLine(value)); return uniqueStrings(values).filter((value) => !isInternalMechanicsLine(value));
} }
function rankedValueFlowUnknownLines(pilot: AssistantMcpDiscoveryPilotExecutionContract): string[] {
if (!pilot.derived_ranked_value_flow) {
return userFacingUnknowns(pilot.evidence.unknown_facts);
}
const ranking = pilot.derived_ranked_value_flow;
const period = ranking.period_scope ? `периода ${ranking.period_scope}` : "проверенного окна";
return [`Полный рейтинг контрагентов вне ${period} этим поиском не подтвержден.`];
}
function userFacingLimitations(values: string[]): string[] { function userFacingLimitations(values: string[]): string[] {
return uniqueStrings(values).filter((value) => !isInternalMechanicsLine(value)); return uniqueStrings(values).filter((value) => !isInternalMechanicsLine(value));
} }
@ -856,7 +865,9 @@ export function buildAssistantMcpDiscoveryAnswerDraft(
if (monthlyConfirmedLines.length > 0) { if (monthlyConfirmedLines.length > 0) {
pushReason(reasonCodes, "answer_contains_monthly_breakdown"); pushReason(reasonCodes, "answer_contains_monthly_breakdown");
} }
const confirmedLines = derivedValueLine const confirmedLines = pilot.derived_ranked_value_flow && derivedValueLine
? [derivedValueLine]
: derivedValueLine
? [...pilot.evidence.confirmed_facts, derivedValueLine, ...monthlyConfirmedLines] ? [...pilot.evidence.confirmed_facts, derivedValueLine, ...monthlyConfirmedLines]
: derivedEntityResolutionLine : derivedEntityResolutionLine
? [...pilot.evidence.confirmed_facts, derivedEntityResolutionLine] ? [...pilot.evidence.confirmed_facts, derivedEntityResolutionLine]
@ -871,7 +882,7 @@ export function buildAssistantMcpDiscoveryAnswerDraft(
headline: headlineFor(mode, pilot), headline: headlineFor(mode, pilot),
confirmed_lines: uniqueStrings(confirmedLines), confirmed_lines: uniqueStrings(confirmedLines),
inference_lines: uniqueStrings(inferenceLines), inference_lines: uniqueStrings(inferenceLines),
unknown_lines: userFacingUnknowns(pilot.evidence.unknown_facts), unknown_lines: rankedValueFlowUnknownLines(pilot),
limitation_lines: userFacingLimitations([...pilot.query_limitations, ...pilot.evidence.query_limitations]), limitation_lines: userFacingLimitations([...pilot.query_limitations, ...pilot.evidence.query_limitations]),
next_step_line: nextStepFor(mode, pilot), next_step_line: nextStepFor(mode, pilot),
internal_mechanics_allowed: false, internal_mechanics_allowed: false,

View File

@ -102,9 +102,46 @@ function isReferentialEntityPlaceholder(value: string): boolean {
]).has(normalizeRussianComparableText(value)); ]).has(normalizeRussianComparableText(value));
} }
function isReferentialOrganizationPlaceholder(value: string | null): boolean {
if (!value) {
return false;
}
return new Set([
"эта организация",
"этой организации",
"этой организацией",
"эту организацию",
"эта компания",
"этой компании",
"этой компанией",
"эту компанию",
"наша организация",
"нашей организации",
"нашей компанией"
]).has(normalizeRussianComparableText(value));
}
function isValueFlowPredicateEntityCandidate(value: string | null): boolean {
if (!value) {
return false;
}
const text = compactLower(value);
const looksLikeRankingPredicate =
/(?:прин[её]с|принес|выручк|доходн|больше\s+всего|наибольш)/iu.test(text) &&
/(?:организац|компан|ден[её]г|выручк|поступлен|плат[её]ж)/iu.test(text);
if (!looksLikeRankingPredicate) {
return false;
}
return !/(?<!\p{L})(?:ооо|ип|ао|пао|зао|llc|inc|corp)(?!\p{L})/iu.test(text);
}
function isInvalidEntityCandidate(value: string | null): boolean {
return Boolean(value && (isReferentialEntityPlaceholder(value) || isValueFlowPredicateEntityCandidate(value)));
}
function normalizeFollowupCounterpartyCandidate(value: unknown): string | null { function normalizeFollowupCounterpartyCandidate(value: unknown): string | null {
const text = candidateValue(value); const text = candidateValue(value);
if (!text || isReferentialEntityPlaceholder(text)) { if (!text || isInvalidEntityCandidate(text)) {
return null; return null;
} }
return text; return text;
@ -119,7 +156,7 @@ function pushScopedEntityCandidate(
if (!text) { if (!text) {
return; return;
} }
if (groundedFollowupEntity && isReferentialEntityPlaceholder(text)) { if ((groundedFollowupEntity && isReferentialEntityPlaceholder(text)) || isValueFlowPredicateEntityCandidate(text)) {
return; return;
} }
pushUnique(target, text); pushUnique(target, text);
@ -138,7 +175,7 @@ function pushNormalizedEntityResolutionCandidate(target: string[], value: unknow
return; return;
} }
const normalized = canonicalizeEntityResolutionCandidate(text); const normalized = canonicalizeEntityResolutionCandidate(text);
if (normalized && !target.includes(normalized)) { if (normalized && !isInvalidEntityCandidate(normalized) && !target.includes(normalized)) {
target.push(normalized); target.push(normalized);
} }
} }
@ -175,11 +212,17 @@ function collectEntityCandidates(value: unknown): string[] {
const result: string[] = []; const result: string[] = [];
if (Array.isArray(value)) { if (Array.isArray(value)) {
for (const item of value) { for (const item of value) {
pushUnique(result, candidateValue(item)); const candidate = candidateValue(item);
if (!isInvalidEntityCandidate(candidate)) {
pushUnique(result, candidate);
}
} }
return result; return result;
} }
pushUnique(result, candidateValue(value)); const candidate = candidateValue(value);
if (!isInvalidEntityCandidate(candidate)) {
pushUnique(result, candidate);
}
return result; return result;
} }
@ -188,9 +231,10 @@ function collectPredecomposeEntities(predecompose: Record<string, unknown> | nul
organization: string | null; organization: string | null;
} { } {
const entities = toRecordObject(predecompose?.entities); const entities = toRecordObject(predecompose?.entities);
const organization = toNonEmptyString(entities?.organization);
return { return {
counterparty: toNonEmptyString(entities?.counterparty), counterparty: toNonEmptyString(entities?.counterparty),
organization: toNonEmptyString(entities?.organization) organization: isReferentialOrganizationPlaceholder(organization) ? null : organization
}; };
} }
@ -767,7 +811,7 @@ function hasMetadataDownstreamContinuationSignal(text: string): boolean {
function hasEntityResolutionSignal(text: string): boolean { function hasEntityResolutionSignal(text: string): boolean {
const hasSearchVerb = /(?:найд(?:и|ите|ем|у)|поищ(?:и|ите|ем)|найти|поиск|search|find|look\s*up)/iu.test(text); const hasSearchVerb = /(?:найд(?:и|ите|ем|у)|поищ(?:и|ите|ем)|найти|поиск|search|find|look\s*up)/iu.test(text);
const hasEntityNoun = const hasEntityNoun =
/(?:контрагент(?:а|ов)?|поставщик(?:а|ов)?|клиент(?:а|ов)?|counterpart(?:y|ies)|supplier(?:s)?|customer(?:s)?)/iu.test( /(?:контрагент(?:а|ов|у|ом|е)?|поставщик(?:а|ов|у|ом|е)?|клиент(?:а|ов|у|ом|е)?|counterpart(?:y|ies)|supplier(?:s)?|customer(?:s)?)/iu.test(
text text
); );
return hasSearchVerb && hasEntityNoun; return hasSearchVerb && hasEntityNoun;
@ -777,8 +821,9 @@ function normalizeEntityResolutionCandidate(value: string): string {
return value return value
.replace(/^(?:в\s*1с\s+|в\s+1c\s+|по\s+имени\s+)/iu, "") .replace(/^(?:в\s*1с\s+|в\s+1c\s+|по\s+имени\s+)/iu, "")
.replace(/[?!.]+$/gu, "") .replace(/[?!.]+$/gu, "")
.replace(/^(?:контрагент(?:а|ов)?|поставщик(?:а|ов)?|клиент(?:а|ов)?)\s+/iu, "") .replace(/^(?:контрагент(?:а|ов|у|ом|е)?|поставщик(?:а|ов|у|ом|е)?|клиент(?:а|ов|у|ом|е)?)\s+/iu, "")
.replace(/^(?:counterpart(?:y|ies)|supplier(?:s)?|customer(?:s)?)\s+/iu, "") .replace(/^(?:counterpart(?:y|ies)|supplier(?:s)?|customer(?:s)?)\s+/iu, "")
.replace(/^группу\s+/iu, "Группа ")
.replace(/^[«"'\s]+|[»"'\s]+$/gu, "") .replace(/^[«"'\s]+|[»"'\s]+$/gu, "")
.replace(/\s+/g, " ") .replace(/\s+/g, " ")
.trim(); .trim();
@ -786,8 +831,9 @@ function normalizeEntityResolutionCandidate(value: string): string {
function rawEntityResolutionCandidate(text: string): string | null { function rawEntityResolutionCandidate(text: string): string | null {
const patterns = [ const patterns = [
/(?:найд(?:и|ите|ем|у)|поищ(?:и|ите|ем)|найти|search|find|look\s*up)\s+(?:в\s*1с\s+|в\s+1c\s+)?(?:контрагент(?:а|ов)?|поставщик(?:а|ов)?|клиент(?:а|ов)?|counterpart(?:y|ies)|supplier(?:s)?|customer(?:s)?)\s+(.+)$/iu, /(?:найд(?:и|ите|ем|у)|поищ(?:и|ите|ем)|найти|search|find|look\s*up)\s+(?:в\s*1с\s+|в\s+1c\s+)?(?:контрагент(?:а|ов|у|ом|е)?|поставщик(?:а|ов|у|ом|е)?|клиент(?:а|ов|у|ом|е)?|counterpart(?:y|ies)|supplier(?:s)?|customer(?:s)?)\s+(.+)$/iu,
/(?:контрагент(?:а|ов)?|поставщик(?:а|ов)?|клиент(?:а|ов)?|counterpart(?:y|ies)|supplier(?:s)?|customer(?:s)?)\s+(.+?)\s+(?:найд(?:и|ите|ем|у)|поищ(?:и|ите|ем)|найти|search|find|look\s*up)\b/iu /(?:найд(?:и|ите|ем|у)|поищ(?:и|ите|ем)|найти|search|find|look\s*up)\s+(?:в\s*1с\s+|в\s+1c\s+)(.+)$/iu,
/(?:контрагент(?:а|ов|у|ом|е)?|поставщик(?:а|ов|у|ом|е)?|клиент(?:а|ов|у|ом|е)?|counterpart(?:y|ies)|supplier(?:s)?|customer(?:s)?)\s+(.+?)\s+(?:найд(?:и|ите|ем|у)|поищ(?:и|ите|ем)|найти|search|find|look\s*up)\b/iu
]; ];
for (const pattern of patterns) { for (const pattern of patterns) {
const match = text.match(pattern); const match = text.match(pattern);
@ -1085,6 +1131,7 @@ export function buildAssistantMcpDiscoveryTurnInput(
: null; : null;
const entityResolutionSignal = const entityResolutionSignal =
rawEntityResolutionSignal || Boolean(rawEntityCandidate) || Boolean(entityResolutionClarificationCandidate); rawEntityResolutionSignal || Boolean(rawEntityCandidate) || Boolean(entityResolutionClarificationCandidate);
const rawEntitySearchOverridesStaleScope = Boolean(rawEntityCandidate && entityResolutionSignal);
const rawDomain = toNonEmptyString(assistantTurnMeaning?.asked_domain_family); const rawDomain = toNonEmptyString(assistantTurnMeaning?.asked_domain_family);
const rawAction = toNonEmptyString(assistantTurnMeaning?.asked_action_family); const rawAction = toNonEmptyString(assistantTurnMeaning?.asked_action_family);
const rawAggregationAxis = toNonEmptyString(assistantTurnMeaning?.asked_aggregation_axis); const rawAggregationAxis = toNonEmptyString(assistantTurnMeaning?.asked_aggregation_axis);
@ -1105,11 +1152,17 @@ export function buildAssistantMcpDiscoveryTurnInput(
hasMovementEvidenceFollowupSignal(rawText) || hasMovementEvidenceFollowupSignal(rawText) ||
hasPronounMovementEvidenceFollowupSignal(rawText); hasPronounMovementEvidenceFollowupSignal(rawText);
const assistantTurnMeaningDateScope = toNonEmptyString(assistantTurnMeaning?.explicit_date_scope); const assistantTurnMeaningDateScope = toNonEmptyString(assistantTurnMeaning?.explicit_date_scope);
const assistantTurnMeaningOrganizationScope = toNonEmptyString(assistantTurnMeaning?.explicit_organization_scope); const rawAssistantTurnMeaningOrganizationScope = toNonEmptyString(assistantTurnMeaning?.explicit_organization_scope);
const assistantTurnMeaningOrganizationScope = isReferentialOrganizationPlaceholder(
rawAssistantTurnMeaningOrganizationScope
)
? null
: rawAssistantTurnMeaningOrganizationScope;
const rawOrganizationMentionSignal = hasOrganizationScopeSignalUtf8(rawText); const rawOrganizationMentionSignal = hasOrganizationScopeSignalUtf8(rawText);
const rawOrganizationScope = extractOrganizationScopeFromRawText(rawUserText ?? rawEffectiveText ?? rawSignalSourceText); const rawOrganizationScope = extractOrganizationScopeFromRawText(rawUserText ?? rawEffectiveText ?? rawSignalSourceText);
const currentTurnFreshOrganizationScope = rawOrganizationScope ?? predecomposeEntities.organization;
const currentTurnOrganizationScope = const currentTurnOrganizationScope =
rawOrganizationScope ?? predecomposeEntities.organization ?? assistantTurnMeaningOrganizationScope; currentTurnFreshOrganizationScope ?? assistantTurnMeaningOrganizationScope;
const explicitOrganizationScopeSignal = Boolean(rawOrganizationMentionSignal && currentTurnOrganizationScope); const explicitOrganizationScopeSignal = Boolean(rawOrganizationMentionSignal && currentTurnOrganizationScope);
const organizationClarificationFollowupApplicable = Boolean( const organizationClarificationFollowupApplicable = Boolean(
followupSeed.domain === "counterparty_value" && followupSeed.domain === "counterparty_value" &&
@ -1492,7 +1545,9 @@ export function buildAssistantMcpDiscoveryTurnInput(
metadataGroundedMovementLaneApplicable) metadataGroundedMovementLaneApplicable)
); );
const metadataLaneScopeHint = const metadataLaneScopeHint =
explicitCurrentCounterpartyOverridesFollowupEntity rawEntitySearchOverridesStaleScope
? null
: explicitCurrentCounterpartyOverridesFollowupEntity
? null ? null
: rawMetadataScopeHint ?? : rawMetadataScopeHint ??
followupSeed.metadataScopeHint ?? followupSeed.metadataScopeHint ??
@ -1506,6 +1561,8 @@ export function buildAssistantMcpDiscoveryTurnInput(
); );
const groundedFollowupEntity = metadataScopedLaneWithoutSubject const groundedFollowupEntity = metadataScopedLaneWithoutSubject
? null ? null
: rawEntitySearchOverridesStaleScope
? null
: explicitCurrentCounterpartyOverridesFollowupEntity : explicitCurrentCounterpartyOverridesFollowupEntity
? null ? null
: followupSeed.counterparty ?? followupSeed.discoveryEntity; : followupSeed.counterparty ?? followupSeed.discoveryEntity;
@ -1514,10 +1571,16 @@ export function buildAssistantMcpDiscoveryTurnInput(
pushNormalizedEntityResolutionCandidate(entityCandidates, entityResolutionClarificationCandidate); pushNormalizedEntityResolutionCandidate(entityCandidates, entityResolutionClarificationCandidate);
pushNormalizedEntityResolutionCandidate(entityCandidates, rawEntityCandidate); pushNormalizedEntityResolutionCandidate(entityCandidates, rawEntityCandidate);
for (const candidate of collectEntityCandidates(assistantTurnMeaning?.explicit_entity_candidates)) { for (const candidate of collectEntityCandidates(assistantTurnMeaning?.explicit_entity_candidates)) {
pushNormalizedEntityResolutionCandidate(entityCandidates, candidate); if (!rawEntitySearchOverridesStaleScope || sameScopedName(candidate, rawEntityCandidate)) {
pushNormalizedEntityResolutionCandidate(entityCandidates, candidate);
}
}
if (!rawEntitySearchOverridesStaleScope || sameScopedName(normalizedPredecomposeCounterparty, rawEntityCandidate)) {
pushNormalizedEntityResolutionCandidate(entityCandidates, normalizedPredecomposeCounterparty);
}
if (!rawEntitySearchOverridesStaleScope) {
pushNormalizedEntityResolutionCandidate(entityCandidates, followupSeed.counterparty);
} }
pushNormalizedEntityResolutionCandidate(entityCandidates, normalizedPredecomposeCounterparty);
pushNormalizedEntityResolutionCandidate(entityCandidates, followupSeed.counterparty);
} else { } else {
if (groundedFollowupEntity) { if (groundedFollowupEntity) {
pushUnique(entityCandidates, groundedFollowupEntity); pushUnique(entityCandidates, groundedFollowupEntity);
@ -1562,8 +1625,11 @@ export function buildAssistantMcpDiscoveryTurnInput(
pushUnique(entityCandidates, followupSeed.organization); pushUnique(entityCandidates, followupSeed.organization);
} }
const explicitOrganizationScope = const explicitOrganizationScope =
valueFlowOrganizationStaysScope || !openScopeValueFlowWithoutCounterparty rawEntitySearchOverridesStaleScope && !currentTurnFreshOrganizationScope
? currentTurnOrganizationScope ?? followupSeed.organization ? null
: valueFlowOrganizationStaysScope || !openScopeValueFlowWithoutCounterparty
? (rawEntitySearchOverridesStaleScope ? currentTurnFreshOrganizationScope : currentTurnOrganizationScope) ??
followupSeed.organization
: null; : null;
if ( if (
explicitCurrentCounterpartyCandidate && explicitCurrentCounterpartyCandidate &&
@ -1609,13 +1675,18 @@ export function buildAssistantMcpDiscoveryTurnInput(
(valueFlowOrganizationStaysScope && (Boolean(followupSeed.rankingNeed) || bidirectionalValueFlowSignal))) (valueFlowOrganizationStaysScope && (Boolean(followupSeed.rankingNeed) || bidirectionalValueFlowSignal)))
); );
const normalizedPredecomposeDateScope = const normalizedPredecomposeDateScope =
suppressImplicitCurrentDateScope && isImplicitCurrentDateScope(predecomposeDateScope) ? null : predecomposeDateScope; (rawEntitySearchOverridesStaleScope && !currentTurnCarriesExplicitPeriod) ||
(suppressImplicitCurrentDateScope && isImplicitCurrentDateScope(predecomposeDateScope))
? null
: predecomposeDateScope;
const normalizedAssistantTurnMeaningDateScope = const normalizedAssistantTurnMeaningDateScope =
suppressImplicitCurrentDateScope && isImplicitCurrentDateScope(assistantTurnMeaningDateScope) rawEntitySearchOverridesStaleScope ||
(suppressImplicitCurrentDateScope && isImplicitCurrentDateScope(assistantTurnMeaningDateScope))
? null ? null
: assistantTurnMeaningDateScope; : assistantTurnMeaningDateScope;
const normalizedFollowupDateScope = const normalizedFollowupDateScope =
suppressImplicitCurrentDateScope && isImplicitCurrentDateScope(followupSeed.dateScope) rawEntitySearchOverridesStaleScope ||
(suppressImplicitCurrentDateScope && isImplicitCurrentDateScope(followupSeed.dateScope))
? null ? null
: followupSeed.dateScope; : followupSeed.dateScope;
const explicitDateScope = const explicitDateScope =
@ -1670,7 +1741,9 @@ export function buildAssistantMcpDiscoveryTurnInput(
: rawAction ?? seededAction, : rawAction ?? seededAction,
asked_aggregation_axis: monthlyAggregationSignal ? "month" : rawAggregationAxis, asked_aggregation_axis: monthlyAggregationSignal ? "month" : rawAggregationAxis,
seeded_ranking_need: seeded_ranking_need:
valueFlowSignal && followupSeed.rankingNeed ? followupSeed.rankingNeed : undefined, valueFlowSignal && followupSeed.rankingNeed && !rawEntitySearchOverridesStaleScope
? followupSeed.rankingNeed
: undefined,
explicit_entity_candidates: entityCandidates, explicit_entity_candidates: entityCandidates,
metadata_ambiguity_entity_sets: metadata_ambiguity_entity_sets:
metadataAmbiguityLaneClarificationApplicable && followupSeed.metadataAmbiguityEntitySets.length > 0 metadataAmbiguityLaneClarificationApplicable && followupSeed.metadataAmbiguityEntitySets.length > 0
@ -1782,9 +1855,11 @@ export function buildAssistantMcpDiscoveryTurnInput(
groundedValueFlowFollowupApplicable groundedValueFlowFollowupApplicable
}); });
const hasTurnMeaning = Object.keys(cleanTurnMeaning).length > 0; const hasTurnMeaning = Object.keys(cleanTurnMeaning).length > 0;
const sourceSignal: AssistantMcpDiscoveryTurnInputSource = assistantTurnMeaning const sourceSignal: AssistantMcpDiscoveryTurnInputSource = rawEntitySearchOverridesStaleScope
? "assistant_turn_meaning" ? "raw_text"
: followupDiscoverySeedApplicable || : assistantTurnMeaning
? "assistant_turn_meaning"
: followupDiscoverySeedApplicable ||
Boolean(entityResolutionClarificationCandidate) || Boolean(entityResolutionClarificationCandidate) ||
effectiveMetadataFollowupSeedApplicable || effectiveMetadataFollowupSeedApplicable ||
metadataAmbiguityLaneClarificationApplicable metadataAmbiguityLaneClarificationApplicable
@ -1925,7 +2000,7 @@ export function buildAssistantMcpDiscoveryTurnInput(
) { ) {
pushReason(reasonCodes, "mcp_discovery_counterparty_from_predecompose"); pushReason(reasonCodes, "mcp_discovery_counterparty_from_predecompose");
} }
if (followupSeed.counterparty) { if (followupSeed.counterparty && !rawEntitySearchOverridesStaleScope) {
pushReason(reasonCodes, "mcp_discovery_counterparty_from_followup_context"); pushReason(reasonCodes, "mcp_discovery_counterparty_from_followup_context");
} }
if (followupDateScopeApplied) { if (followupDateScopeApplied) {

View File

@ -1888,7 +1888,7 @@ function textMojibakeScoreForAddress(value) {
const source = String(value ?? ""); const source = String(value ?? "");
const cyrillic = (source.match(/[А-Яа-яЁё]/g) ?? []).length; const cyrillic = (source.match(/[А-Яа-яЁё]/g) ?? []).length;
const latin = (source.match(/[A-Za-z]/g) ?? []).length; const latin = (source.match(/[A-Za-z]/g) ?? []).length;
const hardMarkers = (source.match(/[Ѓѓ‚„…†‡€‰‹ЉЊЌЋЏ<EFBFBD>?’“”•–—™љ›њќћџ]/g) ?? []).length; const hardMarkers = (source.match(/[Ѓѓ‚„…†‡€‰‹ЉЊЌЋЏ\uFFFD?’“”•–—™љ›њќћџ]/g) ?? []).length;
const pairMarkers = (source.match(/(?:Р.|С.|Ð.|Ñ.)/g) ?? []).length; const pairMarkers = (source.match(/(?:Р.|С.|Ð.|Ñ.)/g) ?? []).length;
const doubleEncodedMarkers = (source.match(/(?:Г[Ђ-џ]|В[Ђ-џ]|Ã.|Â.)/gu) ?? []).length; const doubleEncodedMarkers = (source.match(/(?:Г[Ђ-џ]|В[Ђ-џ]|Ã.|Â.)/gu) ?? []).length;
return cyrillic + latin - hardMarkers * 3 - pairMarkers * 2 - doubleEncodedMarkers * 2; return cyrillic + latin - hardMarkers * 3 - pairMarkers * 2 - doubleEncodedMarkers * 2;
@ -1898,7 +1898,7 @@ function looksLikeMojibakeForAddress(value) {
if (!source.trim()) { if (!source.trim()) {
return false; return false;
} }
if (/[Ѓѓ‚„…†‡€‰‹ЉЊЌЋЏ<EFBFBD>?’“”•–—™љ›њќћџ]/.test(source)) { if (/[Ѓѓ‚„…†‡€‰‹ЉЊЌЋЏ\uFFFD?’“”•–—™љ›њќћџ]/.test(source)) {
return true; return true;
} }
if ((source.match(/(?:Р.|С.|Ð.|Ñ.)/g) ?? []).length >= 2) { if ((source.match(/(?:Р.|С.|Ð.|Ñ.)/g) ?? []).length >= 2) {
@ -2129,7 +2129,7 @@ function normalizeCounterpartyForFollowupMatch(value) {
return compactWhitespace(repairAddressMojibake(String(value ?? "")) return compactWhitespace(repairAddressMojibake(String(value ?? ""))
.toLowerCase() .toLowerCase()
.replace(/ё/g, "е") .replace(/ё/g, "е")
.replace(/[«»"'`“”„’<EFBFBD>?]/g, " ") .replace(/[«»"'`“”„’\uFFFD?]/g, " ")
.replace(/[^a-zа-я0-9\s._-]+/giu, " ")); .replace(/[^a-zа-я0-9\s._-]+/giu, " "));
} }
function normalizeCounterpartyTokenForFollowupMatch(value) { function normalizeCounterpartyTokenForFollowupMatch(value) {
@ -2175,7 +2175,7 @@ function extractDisplayedAddressEntityCandidates(replyText, entityType = "unknow
if (parts.length >= 2 && /^\d{4}-\d{2}-\d{2}/.test(parts[0] ?? "")) { if (parts.length >= 2 && /^\d{4}-\d{2}-\d{2}/.test(parts[0] ?? "")) {
counterpartyCandidate = parts[1] ?? counterpartyCandidate; counterpartyCandidate = parts[1] ?? counterpartyCandidate;
} }
const cleanedCandidate = compactWhitespace(counterpartyCandidate.replace(/^["'«»`<EFBFBD>?]+|["'«»`<EFBFBD>?]+$/gu, "")); const cleanedCandidate = compactWhitespace(counterpartyCandidate.replace(/^["'«»`\uFFFD?]+|["'«»`\uFFFD?]+$/gu, ""));
if (!cleanedCandidate || cleanedCandidate.length < 2) { if (!cleanedCandidate || cleanedCandidate.length < 2) {
continue; continue;
} }
@ -3224,6 +3224,11 @@ function hasSameDateAccountFollowupSignalForPredecompose(text) {
/(?:^|\s)по\s+\d{2}(?:[.,]\d{1,2})?(?=$|[\s,.;:!?])/iu.test(source) || /(?:^|\s)по\s+\d{2}(?:[.,]\d{1,2})?(?=$|[\s,.;:!?])/iu.test(source) ||
/\b\d{2}(?:[.,]\d{1,2})\b/u.test(source)); /\b\d{2}(?:[.,]\d{1,2})\b/u.test(source));
} }
function isCounterpartyDrilldownIntentForPredecompose(intent) {
return intent === "list_documents_by_counterparty" ||
intent === "bank_operations_by_counterparty" ||
intent === "list_contracts_by_counterparty";
}
function hasPredecomposeDiagnosticUncertaintyLead(text) { function hasPredecomposeDiagnosticUncertaintyLead(text) {
const normalized = compactWhitespace(repairAddressMojibake(String(text ?? "")).toLowerCase()); const normalized = compactWhitespace(repairAddressMojibake(String(text ?? "")).toLowerCase());
if (!normalized) { if (!normalized) {
@ -3442,8 +3447,16 @@ async function runAddressLlmPreDecompose(normalizerService, payload, userMessage
const sourceHasExplicitAccountAnchor = (0, addressIntentResolver_1.hasAccountNumberAnchor)(repairedSourceMessage || userMessage) || const sourceHasExplicitAccountAnchor = (0, addressIntentResolver_1.hasAccountNumberAnchor)(repairedSourceMessage || userMessage) ||
(0, addressIntentResolver_1.hasCompactAccountCodeToken)(repairedSourceMessage || userMessage); (0, addressIntentResolver_1.hasCompactAccountCodeToken)(repairedSourceMessage || userMessage);
const candidateInjectsAccountAnchor = Boolean(toNonEmptyString(candidatePredecomposeContract?.entities?.account)); const candidateInjectsAccountAnchor = Boolean(toNonEmptyString(candidatePredecomposeContract?.entities?.account));
if (sourceIntentResolution.intent === "inventory_on_hand_as_of_date" && const sourceAnchorQuality = evaluateAddressAnchorQuality(repairedSourceMessage || userMessage);
candidateIntentResolution.intent === "inventory_on_hand_as_of_date" && const candidateAccountInjectedIntoCounterpartyAnchor = isCounterpartyDrilldownIntentForPredecompose(sourceIntentResolution.intent) &&
sourceIntentResolution.intent === candidateIntentResolution.intent &&
sourceAnchorQuality.anchorType === "counterparty" &&
sourceAnchorQuality.quality >= 2 &&
!sourceHasExplicitAccountAnchor &&
candidateInjectsAccountAnchor;
if (((sourceIntentResolution.intent === "inventory_on_hand_as_of_date" &&
candidateIntentResolution.intent === "inventory_on_hand_as_of_date") ||
candidateAccountInjectedIntoCounterpartyAnchor) &&
!sourceHasExplicitAccountAnchor && !sourceHasExplicitAccountAnchor &&
candidateInjectsAccountAnchor) { candidateInjectsAccountAnchor) {
return attachAddressPredecomposeContract({ return attachAddressPredecomposeContract({
@ -3456,10 +3469,9 @@ async function runAddressLlmPreDecompose(normalizerService, payload, userMessage
reason: "normalized_fragment_rejected_anchor_injection", reason: "normalized_fragment_rejected_anchor_injection",
fallbackRuleHit: null, fallbackRuleHit: null,
sanitizedUserMessage, sanitizedUserMessage,
semanticHints: candidateMeta?.semanticHints ?? null semanticHints: null
}, userMessage); }, userMessage);
} }
const sourceAnchorQuality = evaluateAddressAnchorQuality(repairedSourceMessage || userMessage);
const candidateAnchorQuality = evaluateAddressAnchorQuality(candidate); const candidateAnchorQuality = evaluateAddressAnchorQuality(candidate);
const sameIntentForAnchorSafety = sourceAnchorQuality.intent !== "unknown" && sourceAnchorQuality.intent === candidateAnchorQuality.intent; const sameIntentForAnchorSafety = sourceAnchorQuality.intent !== "unknown" && sourceAnchorQuality.intent === candidateAnchorQuality.intent;
const sourceSelectedObjectItemAnchorValue = toNonEmptyString((0, addressFilterExtractor_1.extractSelectedObjectQuotedValue)(userMessage)) ?? const sourceSelectedObjectItemAnchorValue = toNonEmptyString((0, addressFilterExtractor_1.extractSelectedObjectQuotedValue)(userMessage)) ??

View File

@ -1,4 +1,4 @@
import { describe, expect, it, vi } from "vitest"; import { describe, expect, it, vi } from "vitest";
import { AssistantService } from "../src/services/assistantService"; import { AssistantService } from "../src/services/assistantService";
import { AssistantSessionStore } from "../src/services/assistantSessionStore"; import { AssistantSessionStore } from "../src/services/assistantSessionStore";
@ -1444,6 +1444,102 @@ describe("assistant address llm pre-decompose candidate preference", () => {
expect(response.debug?.fallback_rule_hit).toBe("documents_counterparty_year_rewrite"); expect(response.debug?.fallback_rule_hit).toBe("documents_counterparty_year_rewrite");
}); });
it("rejects account injection when LLM truncates a numeric counterparty suffix", async () => {
const calls: Array<{ message: string }> = [];
const addressQueryService = {
tryHandle: vi.fn(async (message: string) => {
calls.push({ message });
return buildAddressLaneResult(message);
})
} as any;
const normalizerService = {
normalize: vi.fn(async () => ({
trace_id: "norm-predecompose-counterparty-suffix-account-injection",
ok: true,
normalized: {
schema_version: "normalized_query_v2_0_2",
user_message_raw: "Покажи документы по Жуковке 51.",
message_in_scope: true,
scope_confidence: "high",
contains_multiple_tasks: false,
fragments: [
{
fragment_id: "F1",
raw_fragment_text: "Покажи документы по Жуковке 51.",
normalized_fragment_text: "Показать документы, связанные с контрагентом Жуковка по счету 51",
domain_relevance: "in_scope",
business_scope: "company_specific_accounting",
entity_hints: ["Жуковка"],
account_hints: ["51"],
document_hints: ["документы"],
register_hints: [],
semantic_hints: {
scope_target_kind: "counterparty",
scope_target_text: "Жуковка",
date_scope_kind: "implicit_current",
self_scope_detected: false,
selected_object_scope_detected: false
},
time_scope: { type: "unspecified", value: null, confidence: "low" },
flags: {
has_multi_entity_scope: false,
asks_for_chain_explanation: false,
asks_for_ranking_or_top: false,
asks_for_period_summary: false,
asks_for_rule_check: false,
asks_for_anomaly_scan: false,
asks_for_exact_object_trace: false,
asks_for_evidence: false,
mentions_period_close_context: false
},
candidate_labels: ["simple_factual"],
confidence: "high",
execution_readiness: "executable",
clarification_reason: null,
soft_assumption_used: [],
route_status: "routed",
no_route_reason: null
}
],
discarded_fragments: [],
global_notes: { needs_clarification: false, clarification_reason: null }
},
raw_model_output: null,
validation: { passed: true, errors: [] },
usage: { input_tokens: 1, output_tokens: 1, total_tokens: 2 },
latency_ms: 10,
prompt_version: "normalizer_v2_0_2",
schema_version: "v2_0_2",
request_count_for_case: 1
}))
} as any;
const sessions = new AssistantSessionStore();
const service = new AssistantService(
normalizerService,
sessions as any,
{} as any,
{ persistSession: vi.fn() } as any,
addressQueryService
);
const response = await service.handleMessage({
session_id: `asst-predecompose-counterparty-suffix-${Date.now()}`,
user_message: "Покажи документы по Жуковке 51.",
llmProvider: "local",
useMock: false
} as any);
expect(response.ok).toBe(true);
expect(calls).toHaveLength(1);
expect(calls[0].message).toBe("Покажи документы по Жуковке 51.");
expect(response.debug?.llm_decomposition_applied).toBe(false);
expect(response.debug?.llm_decomposition_reason).toBe("normalized_fragment_rejected_anchor_injection");
expect(response.debug?.llm_predecompose_contract?.entities?.account).toBeNull();
expect(response.debug?.llm_predecompose_contract?.entities?.counterparty).toBe("Жуковке 51");
});
it("rewrites payment-style counterparty phrasing to bank operations", async () => { it("rewrites payment-style counterparty phrasing to bank operations", async () => {
const calls: Array<{ message: string }> = []; const calls: Array<{ message: string }> = [];
const addressQueryService = { const addressQueryService = {

View File

@ -341,6 +341,62 @@ describe("assistant MCP discovery answer adapter", () => {
expect(draft.next_step_line).toContain("организац"); expect(draft.next_step_line).toContain("организац");
}); });
it("renders confirmed ranked value-flow without raw technical evidence lines", async () => {
const planner = planAssistantMcpDiscovery({
dataNeedGraph: {
schema_version: "assistant_data_need_graph_v1",
policy_owner: "assistantMcpDiscoveryDataNeedGraph",
subject_candidates: [],
business_fact_family: "value_flow",
action_family: "turnover",
aggregation_need: null,
time_scope_need: "explicit_period",
comparison_need: null,
ranking_need: "top_desc",
proof_expectation: "coverage_checked_fact",
clarification_gaps: [],
decomposition_candidates: ["collect_scoped_movements", "aggregate_ranked_axis_values", "probe_coverage"],
forbidden_overclaim_flags: ["no_raw_model_claims", "no_unchecked_fact_totals"],
reason_codes: ["data_need_graph_built", "data_need_graph_ranking_top_desc"]
},
turnMeaning: {
asked_domain_family: "counterparty_value",
asked_action_family: "turnover",
explicit_organization_scope: "\u041e\u041e\u041e \u0410\u043b\u044c\u0442\u0435\u0440\u043d\u0430\u0442\u0438\u0432\u0430 \u041f\u043b\u044e\u0441",
explicit_date_scope: "2020",
unsupported_but_understood_family: "counterparty_value_or_turnover"
}
});
const pilot = await executeAssistantMcpDiscoveryPilot(
planner,
buildDeps([
{
Period: "2020-01-15T00:00:00",
Amount: 12000,
Counterparty: "\u0421\u0411\u0415\u0420\u0411\u0410\u041d\u041a, \u041f\u0410\u041e",
Organization: "\u041e\u041e\u041e \u0410\u043b\u044c\u0442\u0435\u0440\u043d\u0430\u0442\u0438\u0432\u0430 \u041f\u043b\u044e\u0441"
},
{
Period: "2020-02-20T00:00:00",
Amount: 5000,
Counterparty: "\u0413\u0440\u0443\u043f\u043f\u0430 \u0421\u0412\u041a",
Organization: "\u041e\u041e\u041e \u0410\u043b\u044c\u0442\u0435\u0440\u043d\u0430\u0442\u0438\u0432\u0430 \u041f\u043b\u044e\u0441"
}
])
);
const draft = buildAssistantMcpDiscoveryAnswerDraft(pilot);
const userText = [...draft.confirmed_lines, ...draft.inference_lines, ...draft.unknown_lines].join("\n");
expect(draft.answer_mode).toBe("confirmed_with_bounded_inference");
expect(draft.confirmed_lines).toHaveLength(1);
expect(userText).toContain("\u0411\u043e\u043b\u044c\u0448\u0435 \u0432\u0441\u0435\u0433\u043e \u0434\u0435\u043d\u0435\u0433 \u043f\u0440\u0438\u043d\u0451\u0441 \u043a\u043e\u043d\u0442\u0440\u0430\u0433\u0435\u043d\u0442");
expect(userText).toContain("\u041e\u041e\u041e \u0410\u043b\u044c\u0442\u0435\u0440\u043d\u0430\u0442\u0438\u0432\u0430 \u041f\u043b\u044e\u0441");
expect(userText).not.toContain("1C incoming value-flow");
expect(userText).not.toContain("Full ranking outside");
expect(draft.unknown_lines[0]).toContain("\u041f\u043e\u043b\u043d\u044b\u0439 \u0440\u0435\u0439\u0442\u0438\u043d\u0433");
});
it("asks for both organization and period when an open total still misses both axes", async () => { it("asks for both organization and period when an open total still misses both axes", async () => {
const planner = planAssistantMcpDiscovery({ const planner = planAssistantMcpDiscovery({
dataNeedGraph: { dataNeedGraph: {

View File

@ -1426,6 +1426,45 @@ describe("assistant MCP discovery turn input adapter", () => {
); );
}); });
it("lets an explicit current entity search override stale ranking follow-up scope", () => {
const staleEntity = "\u043f\u0440\u0438\u043d\u0451\u0441 \u043d\u0430\u0438\u0431\u043e\u043b\u044c\u0448\u0443\u044e \u0432\u044b\u0440\u0443\u0447\u043a\u0443 \u043e\u0440\u0433\u0430\u043d\u0438\u0437\u0430\u0446\u0438\u0438 \u0432";
const result = buildAssistantMcpDiscoveryTurnInput({
userMessage:
"\u0422\u0435\u043f\u0435\u0440\u044c \u043e\u0442\u0434\u0435\u043b\u044c\u043d\u0430\u044f \u0442\u0435\u043c\u0430 \u043f\u043e \u043a\u043e\u043d\u043a\u0440\u0435\u0442\u043d\u043e\u043c\u0443 \u043a\u043e\u043d\u0442\u0440\u0430\u0433\u0435\u043d\u0442\u0443. \u041d\u0430\u0439\u0434\u0438 \u0432 1\u0421 \u0413\u0440\u0443\u043f\u043f\u0443 \u0421\u0412\u041a.",
assistantTurnMeaning: {
asked_domain_family: "entity_resolution",
asked_action_family: "search_business_entity",
explicit_entity_candidates: [staleEntity],
explicit_organization_scope: "\u041e\u041e\u041e \u0410\u043b\u044c\u0442\u0435\u0440\u043d\u0430\u0442\u0438\u0432\u0430 \u041f\u043b\u044e\u0441",
explicit_date_scope: "2021",
unsupported_but_understood_family: "entity_resolution",
stale_replay_forbidden: true
},
followupContext: {
previous_discovery_pilot_scope: "counterparty_value_flow_query_movements_v1",
previous_discovery_ranking_need: "top_desc",
previous_filters: {
organization: "\u041e\u041e\u041e \u0410\u043b\u044c\u0442\u0435\u0440\u043d\u0430\u0442\u0438\u0432\u0430 \u041f\u043b\u044e\u0441",
counterparty: staleEntity,
period_from: "2021-01-01",
period_to: "2021-12-31"
}
}
});
expect(result.adapter_status).toBe("ready");
expect(result.should_run_discovery).toBe(true);
expect(result.source_signal).toBe("raw_text");
expect(result.turn_meaning_ref?.explicit_entity_candidates).toEqual(["\u0413\u0440\u0443\u043f\u043f\u0430 \u0421\u0412\u041a"]);
expect(result.turn_meaning_ref?.metadata_scope_hint).toBeUndefined();
expect(result.turn_meaning_ref?.explicit_organization_scope).toBeUndefined();
expect(result.turn_meaning_ref?.explicit_date_scope).toBeUndefined();
expect(result.turn_meaning_ref?.seeded_ranking_need).toBeUndefined();
expect(result.data_need_graph?.subject_candidates).toEqual(["\u0413\u0440\u0443\u043f\u043f\u0430 \u0421\u0412\u041a"]);
expect(result.reason_codes).toContain("mcp_discovery_entity_scope_from_raw_entity_search");
expect(result.reason_codes).not.toContain("mcp_discovery_counterparty_from_followup_context");
});
it("marks top-value wording as a ranking data need without inventing a missing subject gap", () => { it("marks top-value wording as a ranking data need without inventing a missing subject gap", () => {
const result = buildAssistantMcpDiscoveryTurnInput({ const result = buildAssistantMcpDiscoveryTurnInput({
userMessage: "кто больше всего принес денег в 2020" userMessage: "кто больше всего принес денег в 2020"
@ -1444,6 +1483,46 @@ describe("assistant MCP discovery turn input adapter", () => {
]); ]);
}); });
it("keeps open organization ranking subjectless when assistant meaning invents a predicate-shaped entity", () => {
const result = buildAssistantMcpDiscoveryTurnInput({
userMessage:
"\u0418 \u043a\u0442\u043e \u0431\u043e\u043b\u044c\u0448\u0435 \u0432\u0441\u0435\u0433\u043e \u043f\u0440\u0438\u043d\u0435\u0441 \u0434\u0435\u043d\u0435\u0433 \u044d\u0442\u043e\u0439 \u043e\u0440\u0433\u0430\u043d\u0438\u0437\u0430\u0446\u0438\u0438 \u0432 2020 \u0433\u043e\u0434\u0443?",
assistantTurnMeaning: {
asked_domain_family: "counterparty_value",
asked_action_family: "turnover",
explicit_entity_candidates: [
"\u043f\u0440\u0438\u043d\u0451\u0441 \u043d\u0430\u0438\u0431\u043e\u043b\u044c\u0448\u0443\u044e \u0432\u044b\u0440\u0443\u0447\u043a\u0443 \u043e\u0440\u0433\u0430\u043d\u0438\u0437\u0430\u0446\u0438\u0438 \u0432"
],
explicit_organization_scope: "\u044d\u0442\u0430 \u043e\u0440\u0433\u0430\u043d\u0438\u0437\u0430\u0446\u0438\u044f",
explicit_date_scope: "2020",
unsupported_but_understood_family: "counterparty_value_or_turnover",
stale_replay_forbidden: true
},
predecomposeContract: {
entities: {
organization: "\u044d\u0442\u0430 \u043e\u0440\u0433\u0430\u043d\u0438\u0437\u0430\u0446\u0438\u044f"
}
},
followupContext: {
previous_discovery_pilot_scope: "counterparty_value_flow_query_movements_v1",
previous_filters: {
organization: "\u041e\u041e\u041e \u0410\u043b\u044c\u0442\u0435\u0440\u043d\u0430\u0442\u0438\u0432\u0430 \u041f\u043b\u044e\u0441"
}
}
});
expect(result.adapter_status).toBe("ready");
expect(result.should_run_discovery).toBe(true);
expect(result.turn_meaning_ref?.explicit_entity_candidates).toBeUndefined();
expect(result.turn_meaning_ref?.explicit_organization_scope).toBe(
"\u041e\u041e\u041e \u0410\u043b\u044c\u0442\u0435\u0440\u043d\u0430\u0442\u0438\u0432\u0430 \u041f\u043b\u044e\u0441"
);
expect(result.turn_meaning_ref?.explicit_date_scope).toBe("2020");
expect(result.data_need_graph?.subject_candidates).toEqual([]);
expect(result.data_need_graph?.ranking_need).toBe("top_desc");
expect(result.data_need_graph?.decomposition_candidates).toContain("aggregate_ranked_axis_values");
});
it("keeps organization as scope for open bidirectional comparison wording instead of inventing a subject candidate", () => { it("keeps organization as scope for open bidirectional comparison wording instead of inventing a subject candidate", () => {
const result = buildAssistantMcpDiscoveryTurnInput({ const result = buildAssistantMcpDiscoveryTurnInput({
userMessage: "что больше: входящие или исходящие деньги за 2020 год по ООО Альтернатива Плюс?", userMessage: "что больше: входящие или исходящие деньги за 2020 год по ООО Альтернатива Плюс?",

View File

@ -1,4 +1,172 @@
[ [
{
"generation_id": "gen-ag04241406-abe4d8",
"created_at": "2026-04-24T14:06:30+00:00",
"mode": "saved_user_sessions",
"title": "AGENT | Post-F cross-stage semantic integrity canary",
"count": 24,
"domain": "address_post_f_cross_stage_canary_agent",
"questions": [
"Мне нужно понять, где в 1С по НДС вообще лежат данные. Какие объекты стоит смотреть по НДС?",
"Хорошо, тогда покажи движения по ООО Альтернатива Плюс.",
"За 2020 год.",
"А теперь по документам?",
"А теперь за 2021 год?",
"А теперь за все время?",
"С НДС закончили. Новая тема: покажи документы по Жуковке 51.",
"Хорошо, а теперь платежи по нему тоже покажи.",
"А по нему договоры?",
"А по нему документы?",
"А за 2021?",
"С Жуковкой закончили. Теперь другая задача: быстрый денежный срез по одной организации. Если для ответа нужна организация, просто уточни ее. Сколько вообще входящих денег было за 2020 год?",
"По ООО Альтернатива Плюс.",
"Понял, тогда за все время.",
"Хорошо. А что по ООО Альтернатива Плюс больше в 2020 году: входящие или исходящие деньги?",
"И кто больше всего принес денег этой организации в 2020 году?",
"А в 2021 году?",
"Теперь отдельная тема по конкретному контрагенту. Найди в 1С Группу СВК.",
"Сколько получили по нему за 2020 год?",
"А теперь сколько заплатили?",
"А какое нетто?",
"А по документам?",
"А по движениям?",
"А теперь тот же смысл за 2021 год."
],
"generated_by": "codex_agent",
"saved_case_set_file": "assistant_autogen_saved_user_sessions_20260424140630_gen-ag04241406-abe4d8.json",
"context": {
"llm_provider": null,
"model": null,
"assistant_prompt_version": null,
"decomposition_prompt_version": null,
"prompt_fingerprint": null,
"autogen_personality_id": null,
"autogen_personality_prompt": null,
"source_session_id": null,
"saved_session_file": "assistant_saved_session_20260424140630_gen-ag04241406-abe4d8.json",
"saved_case_set_kind": "agent_semantic_scenario",
"agent_run": true,
"agent_focus": "cross-stage canary: VAT metadata, numeric counterparty suffix, open organization value-flow, SVK grounded reset",
"architecture_phase": "Post-F Semantic Integrity Hardening",
"source_spec_file": "X:\\1C\\NDC_1C\\docs\\orchestration\\address_truth_harness_post_f_cross_stage_canary_agent_20260424.json",
"scenario_id": "address_truth_harness_post_f_cross_stage_canary_agent_20260424",
"semantic_tags": [
"account_injection_guard",
"all_time_followup",
"bidirectional_value_flow",
"clarification_resume",
"contracts_followup",
"counterparty_pronoun_resolution",
"cross_stage_canary",
"document_lane_continuity",
"document_pivot_after_movement",
"documents_followup",
"documents_pivot",
"grounded_counterparty",
"incoming_value_flow",
"legacy_phase39",
"legacy_phase64",
"legacy_phase67",
"metadata_surface",
"movement_execution",
"movement_lane_after_metadata",
"movements_pivot",
"net_value_flow",
"numeric_counterparty_suffix",
"open_scope_total",
"organization_clarification",
"organization_scope",
"payments_followup",
"payout_value_flow",
"period_clarification_resume",
"post_f",
"repeated_pivot",
"scope_reuse",
"topic_reset",
"value_flow_ranking",
"vat_orientation",
"year_switch",
"year_switch_after_document_pivot",
"year_switch_after_repeated_pivot"
]
}
},
{
"generation_id": "gen-ag04231844-8e552a",
"created_at": "2026-04-23T18:44:25+00:00",
"mode": "saved_user_sessions",
"title": "AGENT | ARCH: Post-F Semantic Integrity Hardening | Смешанный живой диалог: repeated pivots, орг-срез и СВК",
"count": 19,
"domain": "address_phase82_human_mixed_integrity_status_dialog",
"questions": [
"Покажи документы по Жуковке 51.",
"Хорошо, а теперь платежи по нему тоже покажи.",
"А по нему договоры?",
"А по нему документы?",
"А по нему платежи?",
"А за 2021?",
"С Жуковкой закончили. Теперь нужна другая задача: быстрый денежный срез по одной организации. Если для ответа нужна организация, просто уточни ее. Сколько вообще входящих денег было за 2020 год?",
"По ООО Альтернатива Плюс.",
"Понял, тогда за все время.",
"Хорошо. А что по ООО Альтернатива Плюс больше в 2020 году: входящие или исходящие деньги?",
"А что по ООО Альтернатива Плюс больше уже за 2021 год: входящие или исходящие деньги?",
"И кто больше всего принес денег этой организации в 2020 году?",
"А в 2021 году?",
"Теперь отдельная тема по конкретному контрагенту. Найди в 1С Группу СВК.",
"Сколько получили по нему за 2020 год?",
"А теперь сколько заплатили?",
"А какое нетто?",
"А по документам?",
"А по движениям?"
],
"generated_by": "codex_agent",
"saved_case_set_file": "assistant_autogen_saved_user_sessions_20260423184425_gen-ag04231844-8e552a.json",
"context": {
"llm_provider": null,
"model": null,
"assistant_prompt_version": null,
"decomposition_prompt_version": null,
"prompt_fingerprint": null,
"autogen_personality_id": null,
"autogen_personality_prompt": null,
"source_session_id": null,
"saved_session_file": "assistant_saved_session_20260423184425_gen-ag04231844-8e552a.json",
"saved_case_set_kind": "agent_semantic_scenario",
"agent_run": true,
"agent_focus": "Post-F repeated pivots + open-scope organization money + grounded SVK counterparty chain",
"architecture_phase": "turnaround_11",
"source_spec_file": "X:\\1C\\NDC_1C\\docs\\orchestration\\address_truth_harness_phase82_human_mixed_integrity_status_dialog.json",
"scenario_id": "address_truth_harness_phase82_human_mixed_integrity_status_dialog",
"semantic_tags": [
"all_time_followup",
"contracts_followup",
"counterparty_pronoun_resolution",
"documents_by_counterparty",
"documents_followup",
"documents_pivot",
"fourth_pivot",
"grounded_counterparty",
"human_dialog",
"incoming_value_flow",
"movements_pivot",
"net_value_flow",
"open_scope_total",
"organization_clarification",
"organization_scope",
"payments_followup",
"payout_value_flow",
"pivot_seed",
"post_f_integrity_hardening",
"second_pivot",
"third_pivot",
"topic_reset",
"value_flow_comparison",
"value_flow_ranking",
"year_switch",
"year_switch_after_fourth_pivot"
]
}
},
{ {
"generation_id": "gen-ag04231336-3d4cc9", "generation_id": "gen-ag04231336-3d4cc9",
"created_at": "2026-04-23T13:36:22+00:00", "created_at": "2026-04-23T13:36:22+00:00",

View File

@ -0,0 +1,281 @@
{
"saved_at": "2026-04-23T18:44:25+00:00",
"generation_id": "gen-ag04231844-8e552a",
"mode": "saved_user_sessions",
"title": "AGENT | ARCH: Post-F Semantic Integrity Hardening | Смешанный живой диалог: repeated pivots, орг-срез и СВК",
"agent_run": true,
"questions": [
"Покажи документы по Жуковке 51.",
"Хорошо, а теперь платежи по нему тоже покажи.",
"А по нему договоры?",
"А по нему документы?",
"А по нему платежи?",
"А за 2021?",
"С Жуковкой закончили. Теперь нужна другая задача: быстрый денежный срез по одной организации. Если для ответа нужна организация, просто уточни ее. Сколько вообще входящих денег было за 2020 год?",
"По ООО Альтернатива Плюс.",
"Понял, тогда за все время.",
"Хорошо. А что по ООО Альтернатива Плюс больше в 2020 году: входящие или исходящие деньги?",
"А что по ООО Альтернатива Плюс больше уже за 2021 год: входящие или исходящие деньги?",
"И кто больше всего принес денег этой организации в 2020 году?",
"А в 2021 году?",
"Теперь отдельная тема по конкретному контрагенту. Найди в 1С Группу СВК.",
"Сколько получили по нему за 2020 год?",
"А теперь сколько заплатили?",
"А какое нетто?",
"А по документам?",
"А по движениям?"
],
"metadata": {
"assistant_prompt_version": null,
"decomposition_prompt_version": null,
"prompt_fingerprint": null,
"agent_focus": "Post-F repeated pivots + open-scope organization money + grounded SVK counterparty chain",
"architecture_phase": "turnaround_11",
"source_spec_file": "X:\\1C\\NDC_1C\\docs\\orchestration\\address_truth_harness_phase82_human_mixed_integrity_status_dialog.json",
"scenario_id": "address_truth_harness_phase82_human_mixed_integrity_status_dialog",
"semantic_tags": [
"all_time_followup",
"contracts_followup",
"counterparty_pronoun_resolution",
"documents_by_counterparty",
"documents_followup",
"documents_pivot",
"fourth_pivot",
"grounded_counterparty",
"human_dialog",
"incoming_value_flow",
"movements_pivot",
"net_value_flow",
"open_scope_total",
"organization_clarification",
"organization_scope",
"payments_followup",
"payout_value_flow",
"pivot_seed",
"post_f_integrity_hardening",
"second_pivot",
"third_pivot",
"topic_reset",
"value_flow_comparison",
"value_flow_ranking",
"year_switch",
"year_switch_after_fourth_pivot"
]
},
"source_session_id": null,
"session": {
"session_id": null,
"mode": "agent_semantic_run",
"items": [
{
"message_id": "agent-user-001",
"role": "user",
"text": "Покажи документы по Жуковке 51.",
"created_at": "2026-04-23T18:44:25+00:00",
"reply_type": null,
"trace_id": null,
"debug": null
},
{
"message_id": "agent-user-002",
"role": "user",
"text": "Хорошо, а теперь платежи по нему тоже покажи.",
"created_at": "2026-04-23T18:44:25+00:00",
"reply_type": null,
"trace_id": null,
"debug": null
},
{
"message_id": "agent-user-003",
"role": "user",
"text": "А по нему договоры?",
"created_at": "2026-04-23T18:44:25+00:00",
"reply_type": null,
"trace_id": null,
"debug": null
},
{
"message_id": "agent-user-004",
"role": "user",
"text": "А по нему документы?",
"created_at": "2026-04-23T18:44:25+00:00",
"reply_type": null,
"trace_id": null,
"debug": null
},
{
"message_id": "agent-user-005",
"role": "user",
"text": "А по нему платежи?",
"created_at": "2026-04-23T18:44:25+00:00",
"reply_type": null,
"trace_id": null,
"debug": null
},
{
"message_id": "agent-user-006",
"role": "user",
"text": "А за 2021?",
"created_at": "2026-04-23T18:44:25+00:00",
"reply_type": null,
"trace_id": null,
"debug": null
},
{
"message_id": "agent-user-007",
"role": "user",
"text": "С Жуковкой закончили. Теперь нужна другая задача: быстрый денежный срез по одной организации. Если для ответа нужна организация, просто уточни ее. Сколько вообще входящих денег было за 2020 год?",
"created_at": "2026-04-23T18:44:25+00:00",
"reply_type": null,
"trace_id": null,
"debug": null
},
{
"message_id": "agent-user-008",
"role": "user",
"text": "По ООО Альтернатива Плюс.",
"created_at": "2026-04-23T18:44:25+00:00",
"reply_type": null,
"trace_id": null,
"debug": null
},
{
"message_id": "agent-user-009",
"role": "user",
"text": "Понял, тогда за все время.",
"created_at": "2026-04-23T18:44:25+00:00",
"reply_type": null,
"trace_id": null,
"debug": null
},
{
"message_id": "agent-user-010",
"role": "user",
"text": "Хорошо. А что по ООО Альтернатива Плюс больше в 2020 году: входящие или исходящие деньги?",
"created_at": "2026-04-23T18:44:25+00:00",
"reply_type": null,
"trace_id": null,
"debug": null
},
{
"message_id": "agent-user-011",
"role": "user",
"text": "А что по ООО Альтернатива Плюс больше уже за 2021 год: входящие или исходящие деньги?",
"created_at": "2026-04-23T18:44:25+00:00",
"reply_type": null,
"trace_id": null,
"debug": null
},
{
"message_id": "agent-user-012",
"role": "user",
"text": "И кто больше всего принес денег этой организации в 2020 году?",
"created_at": "2026-04-23T18:44:25+00:00",
"reply_type": null,
"trace_id": null,
"debug": null
},
{
"message_id": "agent-user-013",
"role": "user",
"text": "А в 2021 году?",
"created_at": "2026-04-23T18:44:25+00:00",
"reply_type": null,
"trace_id": null,
"debug": null
},
{
"message_id": "agent-user-014",
"role": "user",
"text": "Теперь отдельная тема по конкретному контрагенту. Найди в 1С Группу СВК.",
"created_at": "2026-04-23T18:44:25+00:00",
"reply_type": null,
"trace_id": null,
"debug": null
},
{
"message_id": "agent-user-015",
"role": "user",
"text": "Сколько получили по нему за 2020 год?",
"created_at": "2026-04-23T18:44:25+00:00",
"reply_type": null,
"trace_id": null,
"debug": null
},
{
"message_id": "agent-user-016",
"role": "user",
"text": "А теперь сколько заплатили?",
"created_at": "2026-04-23T18:44:25+00:00",
"reply_type": null,
"trace_id": null,
"debug": null
},
{
"message_id": "agent-user-017",
"role": "user",
"text": "А какое нетто?",
"created_at": "2026-04-23T18:44:25+00:00",
"reply_type": null,
"trace_id": null,
"debug": null
},
{
"message_id": "agent-user-018",
"role": "user",
"text": "А по документам?",
"created_at": "2026-04-23T18:44:25+00:00",
"reply_type": null,
"trace_id": null,
"debug": null
},
{
"message_id": "agent-user-019",
"role": "user",
"text": "А по движениям?",
"created_at": "2026-04-23T18:44:25+00:00",
"reply_type": null,
"trace_id": null,
"debug": null
}
],
"agent_run": true,
"metadata": {
"assistant_prompt_version": null,
"decomposition_prompt_version": null,
"prompt_fingerprint": null,
"agent_focus": "Post-F repeated pivots + open-scope organization money + grounded SVK counterparty chain",
"architecture_phase": "turnaround_11",
"source_spec_file": "X:\\1C\\NDC_1C\\docs\\orchestration\\address_truth_harness_phase82_human_mixed_integrity_status_dialog.json",
"scenario_id": "address_truth_harness_phase82_human_mixed_integrity_status_dialog",
"semantic_tags": [
"all_time_followup",
"contracts_followup",
"counterparty_pronoun_resolution",
"documents_by_counterparty",
"documents_followup",
"documents_pivot",
"fourth_pivot",
"grounded_counterparty",
"human_dialog",
"incoming_value_flow",
"movements_pivot",
"net_value_flow",
"open_scope_total",
"organization_clarification",
"organization_scope",
"payments_followup",
"payout_value_flow",
"pivot_seed",
"post_f_integrity_hardening",
"second_pivot",
"third_pivot",
"topic_reset",
"value_flow_comparison",
"value_flow_ranking",
"year_switch",
"year_switch_after_fourth_pivot"
]
}
}
}

View File

@ -0,0 +1,353 @@
{
"saved_at": "2026-04-24T14:06:30+00:00",
"generation_id": "gen-ag04241406-abe4d8",
"mode": "saved_user_sessions",
"title": "AGENT | Post-F cross-stage semantic integrity canary",
"agent_run": true,
"questions": [
"Мне нужно понять, где в 1С по НДС вообще лежат данные. Какие объекты стоит смотреть по НДС?",
"Хорошо, тогда покажи движения по ООО Альтернатива Плюс.",
"За 2020 год.",
"А теперь по документам?",
"А теперь за 2021 год?",
"А теперь за все время?",
"С НДС закончили. Новая тема: покажи документы по Жуковке 51.",
"Хорошо, а теперь платежи по нему тоже покажи.",
"А по нему договоры?",
"А по нему документы?",
"А за 2021?",
"С Жуковкой закончили. Теперь другая задача: быстрый денежный срез по одной организации. Если для ответа нужна организация, просто уточни ее. Сколько вообще входящих денег было за 2020 год?",
"По ООО Альтернатива Плюс.",
"Понял, тогда за все время.",
"Хорошо. А что по ООО Альтернатива Плюс больше в 2020 году: входящие или исходящие деньги?",
"И кто больше всего принес денег этой организации в 2020 году?",
"А в 2021 году?",
"Теперь отдельная тема по конкретному контрагенту. Найди в 1С Группу СВК.",
"Сколько получили по нему за 2020 год?",
"А теперь сколько заплатили?",
"А какое нетто?",
"А по документам?",
"А по движениям?",
"А теперь тот же смысл за 2021 год."
],
"metadata": {
"assistant_prompt_version": null,
"decomposition_prompt_version": null,
"prompt_fingerprint": null,
"agent_focus": "cross-stage canary: VAT metadata, numeric counterparty suffix, open organization value-flow, SVK grounded reset",
"architecture_phase": "Post-F Semantic Integrity Hardening",
"source_spec_file": "X:\\1C\\NDC_1C\\docs\\orchestration\\address_truth_harness_post_f_cross_stage_canary_agent_20260424.json",
"scenario_id": "address_truth_harness_post_f_cross_stage_canary_agent_20260424",
"semantic_tags": [
"account_injection_guard",
"all_time_followup",
"bidirectional_value_flow",
"clarification_resume",
"contracts_followup",
"counterparty_pronoun_resolution",
"cross_stage_canary",
"document_lane_continuity",
"document_pivot_after_movement",
"documents_followup",
"documents_pivot",
"grounded_counterparty",
"incoming_value_flow",
"legacy_phase39",
"legacy_phase64",
"legacy_phase67",
"metadata_surface",
"movement_execution",
"movement_lane_after_metadata",
"movements_pivot",
"net_value_flow",
"numeric_counterparty_suffix",
"open_scope_total",
"organization_clarification",
"organization_scope",
"payments_followup",
"payout_value_flow",
"period_clarification_resume",
"post_f",
"repeated_pivot",
"scope_reuse",
"topic_reset",
"value_flow_ranking",
"vat_orientation",
"year_switch",
"year_switch_after_document_pivot",
"year_switch_after_repeated_pivot"
]
},
"source_session_id": null,
"session": {
"session_id": null,
"mode": "agent_semantic_run",
"items": [
{
"message_id": "agent-user-001",
"role": "user",
"text": "Мне нужно понять, где в 1С по НДС вообще лежат данные. Какие объекты стоит смотреть по НДС?",
"created_at": "2026-04-24T14:06:30+00:00",
"reply_type": null,
"trace_id": null,
"debug": null
},
{
"message_id": "agent-user-002",
"role": "user",
"text": "Хорошо, тогда покажи движения по ООО Альтернатива Плюс.",
"created_at": "2026-04-24T14:06:30+00:00",
"reply_type": null,
"trace_id": null,
"debug": null
},
{
"message_id": "agent-user-003",
"role": "user",
"text": "За 2020 год.",
"created_at": "2026-04-24T14:06:30+00:00",
"reply_type": null,
"trace_id": null,
"debug": null
},
{
"message_id": "agent-user-004",
"role": "user",
"text": "А теперь по документам?",
"created_at": "2026-04-24T14:06:30+00:00",
"reply_type": null,
"trace_id": null,
"debug": null
},
{
"message_id": "agent-user-005",
"role": "user",
"text": "А теперь за 2021 год?",
"created_at": "2026-04-24T14:06:30+00:00",
"reply_type": null,
"trace_id": null,
"debug": null
},
{
"message_id": "agent-user-006",
"role": "user",
"text": "А теперь за все время?",
"created_at": "2026-04-24T14:06:30+00:00",
"reply_type": null,
"trace_id": null,
"debug": null
},
{
"message_id": "agent-user-007",
"role": "user",
"text": "С НДС закончили. Новая тема: покажи документы по Жуковке 51.",
"created_at": "2026-04-24T14:06:30+00:00",
"reply_type": null,
"trace_id": null,
"debug": null
},
{
"message_id": "agent-user-008",
"role": "user",
"text": "Хорошо, а теперь платежи по нему тоже покажи.",
"created_at": "2026-04-24T14:06:30+00:00",
"reply_type": null,
"trace_id": null,
"debug": null
},
{
"message_id": "agent-user-009",
"role": "user",
"text": "А по нему договоры?",
"created_at": "2026-04-24T14:06:30+00:00",
"reply_type": null,
"trace_id": null,
"debug": null
},
{
"message_id": "agent-user-010",
"role": "user",
"text": "А по нему документы?",
"created_at": "2026-04-24T14:06:30+00:00",
"reply_type": null,
"trace_id": null,
"debug": null
},
{
"message_id": "agent-user-011",
"role": "user",
"text": "А за 2021?",
"created_at": "2026-04-24T14:06:30+00:00",
"reply_type": null,
"trace_id": null,
"debug": null
},
{
"message_id": "agent-user-012",
"role": "user",
"text": "С Жуковкой закончили. Теперь другая задача: быстрый денежный срез по одной организации. Если для ответа нужна организация, просто уточни ее. Сколько вообще входящих денег было за 2020 год?",
"created_at": "2026-04-24T14:06:30+00:00",
"reply_type": null,
"trace_id": null,
"debug": null
},
{
"message_id": "agent-user-013",
"role": "user",
"text": "По ООО Альтернатива Плюс.",
"created_at": "2026-04-24T14:06:30+00:00",
"reply_type": null,
"trace_id": null,
"debug": null
},
{
"message_id": "agent-user-014",
"role": "user",
"text": "Понял, тогда за все время.",
"created_at": "2026-04-24T14:06:30+00:00",
"reply_type": null,
"trace_id": null,
"debug": null
},
{
"message_id": "agent-user-015",
"role": "user",
"text": "Хорошо. А что по ООО Альтернатива Плюс больше в 2020 году: входящие или исходящие деньги?",
"created_at": "2026-04-24T14:06:30+00:00",
"reply_type": null,
"trace_id": null,
"debug": null
},
{
"message_id": "agent-user-016",
"role": "user",
"text": "И кто больше всего принес денег этой организации в 2020 году?",
"created_at": "2026-04-24T14:06:30+00:00",
"reply_type": null,
"trace_id": null,
"debug": null
},
{
"message_id": "agent-user-017",
"role": "user",
"text": "А в 2021 году?",
"created_at": "2026-04-24T14:06:30+00:00",
"reply_type": null,
"trace_id": null,
"debug": null
},
{
"message_id": "agent-user-018",
"role": "user",
"text": "Теперь отдельная тема по конкретному контрагенту. Найди в 1С Группу СВК.",
"created_at": "2026-04-24T14:06:30+00:00",
"reply_type": null,
"trace_id": null,
"debug": null
},
{
"message_id": "agent-user-019",
"role": "user",
"text": "Сколько получили по нему за 2020 год?",
"created_at": "2026-04-24T14:06:30+00:00",
"reply_type": null,
"trace_id": null,
"debug": null
},
{
"message_id": "agent-user-020",
"role": "user",
"text": "А теперь сколько заплатили?",
"created_at": "2026-04-24T14:06:30+00:00",
"reply_type": null,
"trace_id": null,
"debug": null
},
{
"message_id": "agent-user-021",
"role": "user",
"text": "А какое нетто?",
"created_at": "2026-04-24T14:06:30+00:00",
"reply_type": null,
"trace_id": null,
"debug": null
},
{
"message_id": "agent-user-022",
"role": "user",
"text": "А по документам?",
"created_at": "2026-04-24T14:06:30+00:00",
"reply_type": null,
"trace_id": null,
"debug": null
},
{
"message_id": "agent-user-023",
"role": "user",
"text": "А по движениям?",
"created_at": "2026-04-24T14:06:30+00:00",
"reply_type": null,
"trace_id": null,
"debug": null
},
{
"message_id": "agent-user-024",
"role": "user",
"text": "А теперь тот же смысл за 2021 год.",
"created_at": "2026-04-24T14:06:30+00:00",
"reply_type": null,
"trace_id": null,
"debug": null
}
],
"agent_run": true,
"metadata": {
"assistant_prompt_version": null,
"decomposition_prompt_version": null,
"prompt_fingerprint": null,
"agent_focus": "cross-stage canary: VAT metadata, numeric counterparty suffix, open organization value-flow, SVK grounded reset",
"architecture_phase": "Post-F Semantic Integrity Hardening",
"source_spec_file": "X:\\1C\\NDC_1C\\docs\\orchestration\\address_truth_harness_post_f_cross_stage_canary_agent_20260424.json",
"scenario_id": "address_truth_harness_post_f_cross_stage_canary_agent_20260424",
"semantic_tags": [
"account_injection_guard",
"all_time_followup",
"bidirectional_value_flow",
"clarification_resume",
"contracts_followup",
"counterparty_pronoun_resolution",
"cross_stage_canary",
"document_lane_continuity",
"document_pivot_after_movement",
"documents_followup",
"documents_pivot",
"grounded_counterparty",
"incoming_value_flow",
"legacy_phase39",
"legacy_phase64",
"legacy_phase67",
"metadata_surface",
"movement_execution",
"movement_lane_after_metadata",
"movements_pivot",
"net_value_flow",
"numeric_counterparty_suffix",
"open_scope_total",
"organization_clarification",
"organization_scope",
"payments_followup",
"payout_value_flow",
"period_clarification_resume",
"post_f",
"repeated_pivot",
"scope_reuse",
"topic_reset",
"value_flow_ranking",
"vat_orientation",
"year_switch",
"year_switch_after_document_pivot",
"year_switch_after_repeated_pivot"
]
}
}
}

View File

@ -0,0 +1,82 @@
{
"suite_id": "assistant_saved_session_gen-ag04231844-8e552a",
"suite_version": "0.1.0",
"schema_version": "assistant_saved_session_suite_v0_1",
"generated_at": "2026-04-23T18:44:25+00:00",
"generation_id": "gen-ag04231844-8e552a",
"mode": "saved_user_sessions",
"title": "AGENT | ARCH: Post-F Semantic Integrity Hardening | Смешанный живой диалог: repeated pivots, орг-срез и СВК",
"domain": "address_phase82_human_mixed_integrity_status_dialog",
"scenario_count": 1,
"case_ids": [
"SAVED-001"
],
"cases": [
{
"case_id": "SAVED-001",
"scenario_tag": "agent_saved_user_sessions",
"title": "AGENT | ARCH: Post-F Semantic Integrity Hardening | Смешанный живой диалог: repeated pivots, орг-срез и СВК",
"question_type": "followup",
"broadness_level": "medium",
"turns": [
{
"user_message": "Покажи документы по Жуковке 51."
},
{
"user_message": "Хорошо, а теперь платежи по нему тоже покажи."
},
{
"user_message": "А по нему договоры?"
},
{
"user_message": "А по нему документы?"
},
{
"user_message": "А по нему платежи?"
},
{
"user_message": "А за 2021?"
},
{
"user_message": "С Жуковкой закончили. Теперь нужна другая задача: быстрый денежный срез по одной организации. Если для ответа нужна организация, просто уточни ее. Сколько вообще входящих денег было за 2020 год?"
},
{
"user_message": "По ООО Альтернатива Плюс."
},
{
"user_message": "Понял, тогда за все время."
},
{
"user_message": "Хорошо. А что по ООО Альтернатива Плюс больше в 2020 году: входящие или исходящие деньги?"
},
{
"user_message": "А что по ООО Альтернатива Плюс больше уже за 2021 год: входящие или исходящие деньги?"
},
{
"user_message": "И кто больше всего принес денег этой организации в 2020 году?"
},
{
"user_message": "А в 2021 году?"
},
{
"user_message": "Теперь отдельная тема по конкретному контрагенту. Найди в 1С Группу СВК."
},
{
"user_message": "Сколько получили по нему за 2020 год?"
},
{
"user_message": "А теперь сколько заплатили?"
},
{
"user_message": "А какое нетто?"
},
{
"user_message": "А по документам?"
},
{
"user_message": "А по движениям?"
}
]
}
]
}

View File

@ -0,0 +1,97 @@
{
"suite_id": "assistant_saved_session_gen-ag04241406-abe4d8",
"suite_version": "0.1.0",
"schema_version": "assistant_saved_session_suite_v0_1",
"generated_at": "2026-04-24T14:06:30+00:00",
"generation_id": "gen-ag04241406-abe4d8",
"mode": "saved_user_sessions",
"title": "AGENT | Post-F cross-stage semantic integrity canary",
"domain": "address_post_f_cross_stage_canary_agent",
"scenario_count": 1,
"case_ids": [
"SAVED-001"
],
"cases": [
{
"case_id": "SAVED-001",
"scenario_tag": "agent_saved_user_sessions",
"title": "AGENT | Post-F cross-stage semantic integrity canary",
"question_type": "followup",
"broadness_level": "medium",
"turns": [
{
"user_message": "Мне нужно понять, где в 1С по НДС вообще лежат данные. Какие объекты стоит смотреть по НДС?"
},
{
"user_message": "Хорошо, тогда покажи движения по ООО Альтернатива Плюс."
},
{
"user_message": "За 2020 год."
},
{
"user_message": "А теперь по документам?"
},
{
"user_message": "А теперь за 2021 год?"
},
{
"user_message": "А теперь за все время?"
},
{
"user_message": "С НДС закончили. Новая тема: покажи документы по Жуковке 51."
},
{
"user_message": "Хорошо, а теперь платежи по нему тоже покажи."
},
{
"user_message": "А по нему договоры?"
},
{
"user_message": "А по нему документы?"
},
{
"user_message": "А за 2021?"
},
{
"user_message": "С Жуковкой закончили. Теперь другая задача: быстрый денежный срез по одной организации. Если для ответа нужна организация, просто уточни ее. Сколько вообще входящих денег было за 2020 год?"
},
{
"user_message": "По ООО Альтернатива Плюс."
},
{
"user_message": "Понял, тогда за все время."
},
{
"user_message": "Хорошо. А что по ООО Альтернатива Плюс больше в 2020 году: входящие или исходящие деньги?"
},
{
"user_message": "И кто больше всего принес денег этой организации в 2020 году?"
},
{
"user_message": "А в 2021 году?"
},
{
"user_message": "Теперь отдельная тема по конкретному контрагенту. Найди в 1С Группу СВК."
},
{
"user_message": "Сколько получили по нему за 2020 год?"
},
{
"user_message": "А теперь сколько заплатили?"
},
{
"user_message": "А какое нетто?"
},
{
"user_message": "А по документам?"
},
{
"user_message": "А по движениям?"
},
{
"user_message": "А теперь тот же смысл за 2021 год."
}
]
}
]
}

View File

@ -0,0 +1,78 @@
{
"suite_id": "assistant_saved_session_runtime_job-C4l-4Yfuqm",
"suite_version": "0.1.0",
"schema_version": "assistant_saved_session_runtime_v0_1",
"title": "AGENT | ARCH: Post-F Semantic Integrity Hardening | Смешанный живой диалог: repeated pivots, орг-срез и СВК",
"scenario_count": 1,
"case_ids": [
"SAVED-001"
],
"cases": [
{
"case_id": "SAVED-001",
"scenario_tag": "saved_user_sessions_runtime",
"title": "AGENT | ARCH: Post-F Semantic Integrity Hardening | Смешанный живой диалог: repeated pivots, орг-срез и СВК",
"question_type": "followup",
"broadness_level": "medium",
"turns": [
{
"user_message": "Покажи документы по Жуковке 51."
},
{
"user_message": "Хорошо, а теперь платежи по нему тоже покажи."
},
{
"user_message": "А по нему договоры?"
},
{
"user_message": "А по нему документы?"
},
{
"user_message": "А по нему платежи?"
},
{
"user_message": "А за 2021?"
},
{
"user_message": "С Жуковкой закончили. Теперь нужна другая задача: быстрый денежный срез по одной организации. Если для ответа нужна организация, просто уточни ее. Сколько вообще входящих денег было за 2020 год?"
},
{
"user_message": "По ООО Альтернатива Плюс."
},
{
"user_message": "Понял, тогда за все время."
},
{
"user_message": "Хорошо. А что по ООО Альтернатива Плюс больше в 2020 году: входящие или исходящие деньги?"
},
{
"user_message": "А что по ООО Альтернатива Плюс больше уже за 2021 год: входящие или исходящие деньги?"
},
{
"user_message": "И кто больше всего принес денег этой организации в 2020 году?"
},
{
"user_message": "А в 2021 году?"
},
{
"user_message": "Теперь отдельная тема по конкретному контрагенту. Найди в 1С Группу СВК."
},
{
"user_message": "Сколько получили по нему за 2020 год?"
},
{
"user_message": "А теперь сколько заплатили?"
},
{
"user_message": "А какое нетто?"
},
{
"user_message": "А по документам?"
},
{
"user_message": "А по движениям?"
}
]
}
]
}

View File

@ -0,0 +1,123 @@
{
"suite_id": "assistant_saved_session_runtime_job-FvKPQG152c",
"suite_version": "0.1.0",
"schema_version": "assistant_saved_session_runtime_v0_1",
"title": "БОЛЬШОЙ ОБЩИЙ Ручная сессия 16.04.2026, 21:26:06",
"scenario_count": 1,
"case_ids": [
"SAVED-001"
],
"cases": [
{
"case_id": "SAVED-001",
"scenario_tag": "saved_user_sessions_runtime",
"title": "БОЛЬШОЙ ОБЩИЙ Ручная сессия 16.04.2026, 21:26:06",
"question_type": "followup",
"broadness_level": "medium",
"turns": [
{
"user_message": "приветик - че как там дела"
},
{
"user_message": "расскажи что можешь интересного"
},
{
"user_message": "кайф - что там на складе по остаткам?"
},
{
"user_message": "АЛЬТЕРНАТИВА"
},
{
"user_message": "а исторические остатки на другие даты умеешь?"
},
{
"user_message": "давай на июль 2017"
},
{
"user_message": "март 2016"
},
{
"user_message": "По выбранному объекту \"Рабочая станция универсального специалиста (индивидуальное изготовление)\": где взяли это?"
},
{
"user_message": "а кому продали?"
},
{
"user_message": "у тебя написано кто контрагент: рабочая станция - это ошибка?"
},
{
"user_message": "ндс можешь прикинуть на дату покупки рабочей станции?"
},
{
"user_message": "а какой ндс мы должны сгрузить на март 2020?"
},
{
"user_message": "прикинь какой ндс нам надо заплатить на февраль 2017"
},
{
"user_message": "кто у нас самый доходный клиент за все время"
},
{
"user_message": "кто нам должен денег на май 2017"
},
{
"user_message": "а какой ндс мы должны примерно заплатить за этот период?"
},
{
"user_message": "мы должны комуто денег на сегодня?"
},
{
"user_message": "а нам?"
},
{
"user_message": "какой у нас самый доходный год"
},
{
"user_message": "а за 2017 мы скок заработали?"
},
{
"user_message": "сколько вообще денег мы заработали за все время?"
},
{
"user_message": "ты умеешь считать дельту по договорам?"
},
{
"user_message": "по чепурнову покажи все доки"
},
{
"user_message": "а по свк"
},
{
"user_message": "а сейчас у нас есть что на складе?"
},
{
"user_message": "что нам отгружал чепурнов? какой товар или услугу?"
},
{
"user_message": "какие остатки на складе на сегодня"
},
{
"user_message": "остатки на март 2016"
},
{
"user_message": "хвосты покажи по счету 60 на август 2022"
},
{
"user_message": "Есть ли остатки товара, которые закупались очень давно"
},
{
"user_message": "Какие конкретно номенклатуры формируют остаток по складу на май 2020"
},
{
"user_message": "а по Альтернативе Плюс сколько лет активности в базе 1С?"
},
{
"user_message": "Как ты оценишь деятельность компании?"
},
{
"user_message": "какое нетто по деньгам с Группа СВК за 2020 год: сколько получили и сколько заплатили?"
}
]
}
]
}

View File

@ -0,0 +1,42 @@
{
"suite_id": "assistant_saved_session_runtime_job-IYhHuIIG_n",
"suite_version": "0.1.0",
"schema_version": "assistant_saved_session_runtime_v0_1",
"title": "AGENT | Живой диалог по организации: денежный срез, сравнение и рейтинг",
"scenario_count": 1,
"case_ids": [
"SAVED-001"
],
"cases": [
{
"case_id": "SAVED-001",
"scenario_tag": "saved_user_sessions_runtime",
"title": "AGENT | Живой диалог по организации: денежный срез, сравнение и рейтинг",
"question_type": "followup",
"broadness_level": "medium",
"turns": [
{
"user_message": "Хочу быстрый денежный срез по одной организации без привязки к контрагенту. Сколько вообще входящих денег было за 2020 год?"
},
{
"user_message": "По ООО Альтернатива Плюс."
},
{
"user_message": "Понял, тогда за все время."
},
{
"user_message": "Хорошо. А что по ООО Альтернатива Плюс больше в 2020 году: входящие или исходящие деньги?"
},
{
"user_message": "А что по ООО Альтернатива Плюс больше уже за 2021 год: входящие или исходящие деньги?"
},
{
"user_message": "И кто больше всего принес денег этой организации в 2020 году?"
},
{
"user_message": "А в 2021 году?"
}
]
}
]
}

View File

@ -0,0 +1,123 @@
{
"suite_id": "assistant_saved_session_runtime_job-LndQtjGZU3",
"suite_version": "0.1.0",
"schema_version": "assistant_saved_session_runtime_v0_1",
"title": "БОЛЬШОЙ ОБЩИЙ Ручная сессия 16.04.2026, 21:26:06",
"scenario_count": 1,
"case_ids": [
"SAVED-001"
],
"cases": [
{
"case_id": "SAVED-001",
"scenario_tag": "saved_user_sessions_runtime",
"title": "БОЛЬШОЙ ОБЩИЙ Ручная сессия 16.04.2026, 21:26:06",
"question_type": "followup",
"broadness_level": "medium",
"turns": [
{
"user_message": "приветик - че как там дела"
},
{
"user_message": "расскажи что можешь интересного"
},
{
"user_message": "кайф - что там на складе по остаткам?"
},
{
"user_message": "АЛЬТЕРНАТИВА"
},
{
"user_message": "а исторические остатки на другие даты умеешь?"
},
{
"user_message": "давай на июль 2017"
},
{
"user_message": "март 2016"
},
{
"user_message": "По выбранному объекту \"Рабочая станция универсального специалиста (индивидуальное изготовление)\": где взяли это?"
},
{
"user_message": "а кому продали?"
},
{
"user_message": "у тебя написано кто контрагент: рабочая станция - это ошибка?"
},
{
"user_message": "ндс можешь прикинуть на дату покупки рабочей станции?"
},
{
"user_message": "а какой ндс мы должны сгрузить на март 2020?"
},
{
"user_message": "прикинь какой ндс нам надо заплатить на февраль 2017"
},
{
"user_message": "кто у нас самый доходный клиент за все время"
},
{
"user_message": "кто нам должен денег на май 2017"
},
{
"user_message": "а какой ндс мы должны примерно заплатить за этот период?"
},
{
"user_message": "мы должны комуто денег на сегодня?"
},
{
"user_message": "а нам?"
},
{
"user_message": "какой у нас самый доходный год"
},
{
"user_message": "а за 2017 мы скок заработали?"
},
{
"user_message": "сколько вообще денег мы заработали за все время?"
},
{
"user_message": "ты умеешь считать дельту по договорам?"
},
{
"user_message": "по чепурнову покажи все доки"
},
{
"user_message": "а по свк"
},
{
"user_message": "а сейчас у нас есть что на складе?"
},
{
"user_message": "что нам отгружал чепурнов? какой товар или услугу?"
},
{
"user_message": "какие остатки на складе на сегодня"
},
{
"user_message": "остатки на март 2016"
},
{
"user_message": "хвосты покажи по счету 60 на август 2022"
},
{
"user_message": "Есть ли остатки товара, которые закупались очень давно"
},
{
"user_message": "Какие конкретно номенклатуры формируют остаток по складу на май 2020"
},
{
"user_message": "а по Альтернативе Плюс сколько лет активности в базе 1С?"
},
{
"user_message": "Как ты оценишь деятельность компании?"
},
{
"user_message": "какое нетто по деньгам с Группа СВК за 2020 год: сколько получили и сколько заплатили?"
}
]
}
]
}

View File

@ -0,0 +1,42 @@
{
"suite_id": "assistant_saved_session_runtime_job-X1z7AFW_Nq",
"suite_version": "0.1.0",
"schema_version": "assistant_saved_session_runtime_v0_1",
"title": "AGENT | Живой диалог по СВК: деньги, нетто, документы и движения",
"scenario_count": 1,
"case_ids": [
"SAVED-001"
],
"cases": [
{
"case_id": "SAVED-001",
"scenario_tag": "saved_user_sessions_runtime",
"title": "AGENT | Живой диалог по СВК: деньги, нетто, документы и движения",
"question_type": "followup",
"broadness_level": "medium",
"turns": [
{
"user_message": "Хочу проверить одного контрагента. Найди в 1С Группу СВК."
},
{
"user_message": "Посмотри, сколько денег мы получили от него за 2020 год."
},
{
"user_message": "А теперь сколько мы ему заплатили?"
},
{
"user_message": "А какое получилось нетто?"
},
{
"user_message": "А по документам?"
},
{
"user_message": "А по движениям?"
},
{
"user_message": "А теперь за 2021 год?"
}
]
}
]
}