Compare commits
No commits in common. "4f078d649efd100c4539455dfb7235275fd28012" and "d24f8476b45f3ab26a3e84b4a6bd5ccaa2418104" have entirely different histories.
4f078d649e
...
d24f8476b4
|
|
@ -1,81 +0,0 @@
|
|||
{
|
||||
"schema_version": "domain_truth_harness_spec_v1",
|
||||
"scenario_id": "address_truth_harness_inventory_supplier_overlap_next_action",
|
||||
"domain": "inventory_supplier_overlap_next_action",
|
||||
"title": "Supplier-overlap limited answer includes a concrete next action",
|
||||
"description": "Targeted replay for the inventory supplier-overlap surface: when supplier attribution is limited by missing party-level proof, the answer must still give a concrete next action instead of stopping at a limitation.",
|
||||
"bindings": {},
|
||||
"steps": [
|
||||
{
|
||||
"step_id": "step_01_current_stock_anchor",
|
||||
"title": "Current stock anchor",
|
||||
"question": "какие остатки на складе на сентябрь 2021",
|
||||
"allowed_reply_types": [
|
||||
"factual"
|
||||
],
|
||||
"expected_intents": [
|
||||
"inventory_on_hand_as_of_date"
|
||||
],
|
||||
"required_filters": {
|
||||
"as_of_date": "2021-09-30",
|
||||
"period_from": "2021-09-01",
|
||||
"period_to": "2021-09-30"
|
||||
},
|
||||
"required_answer_patterns_all": [
|
||||
"(?i)30\\.09\\.2021|сентябр",
|
||||
"(?i)склад|остат"
|
||||
],
|
||||
"forbidden_answer_patterns": [
|
||||
"(?i)route_id|capability_id|runtime_|snapshot_items|answer_object|debug"
|
||||
]
|
||||
},
|
||||
{
|
||||
"step_id": "step_02_supplier_overlap_now",
|
||||
"title": "Supplier overlap now",
|
||||
"question": "У какого поставщика были куплены товары, которые лежали на складе Основной склад на 30.09.2021",
|
||||
"allowed_reply_types": [
|
||||
"factual"
|
||||
],
|
||||
"expected_intents": [
|
||||
"inventory_supplier_stock_overlap_as_of_date"
|
||||
],
|
||||
"required_filters": {
|
||||
"as_of_date": "2021-09-30"
|
||||
},
|
||||
"required_answer_patterns_all": [
|
||||
"(?i)30\\.09\\.2021",
|
||||
"(?i)поставщик|поставщиков|закупочн",
|
||||
"(?i)не подтвержден|ограничен|партионн",
|
||||
"(?i)следующий шаг|проверьте|детализац"
|
||||
],
|
||||
"forbidden_answer_patterns": [
|
||||
"(?i)03\\.06\\.2026",
|
||||
"(?i)route_id|capability_id|runtime_|snapshot_items|answer_object|debug"
|
||||
]
|
||||
},
|
||||
{
|
||||
"step_id": "step_03_supplier_residue_now",
|
||||
"title": "Supplier residue wording",
|
||||
"question": "По какому поставщику проходит товарный остаток на складе Основной склад на 30.09.2021",
|
||||
"allowed_reply_types": [
|
||||
"factual"
|
||||
],
|
||||
"expected_intents": [
|
||||
"inventory_supplier_stock_overlap_as_of_date"
|
||||
],
|
||||
"required_filters": {
|
||||
"as_of_date": "2021-09-30"
|
||||
},
|
||||
"required_answer_patterns_all": [
|
||||
"(?i)30\\.09\\.2021",
|
||||
"(?i)поставщик|поставщиков|закупочн",
|
||||
"(?i)не подтвержден|ограничен|партионн",
|
||||
"(?i)следующий шаг|проверьте|детализац"
|
||||
],
|
||||
"forbidden_answer_patterns": [
|
||||
"(?i)03\\.06\\.2026",
|
||||
"(?i)route_id|capability_id|runtime_|snapshot_items|answer_object|debug"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -1,80 +0,0 @@
|
|||
{
|
||||
"schema_version": "domain_truth_harness_spec_v1",
|
||||
"scenario_id": "address_truth_harness_inventory_supplier_overlap_temporal_carryover",
|
||||
"domain": "inventory_supplier_overlap_temporal_carryover",
|
||||
"title": "Supplier-overlap follow-up keeps the inventory root date",
|
||||
"description": "Targeted replay for temporal carryover: after an inventory root slice for September 2021, colloquial supplier-overlap follow-ups with 'сейчас' / 'текущий товарный остаток' must stay on the confirmed root slice date instead of drifting to the wall-clock date.",
|
||||
"bindings": {},
|
||||
"steps": [
|
||||
{
|
||||
"step_id": "step_01_current_stock_anchor",
|
||||
"title": "Historical inventory root anchor",
|
||||
"question": "какие остатки на складе на сентябрь 2021",
|
||||
"allowed_reply_types": [
|
||||
"factual"
|
||||
],
|
||||
"expected_intents": [
|
||||
"inventory_on_hand_as_of_date"
|
||||
],
|
||||
"required_filters": {
|
||||
"as_of_date": "2021-09-30",
|
||||
"period_from": "2021-09-01",
|
||||
"period_to": "2021-09-30"
|
||||
},
|
||||
"required_answer_patterns_all": [
|
||||
"(?i)30\\.09\\.2021",
|
||||
"(?i)склад|остат"
|
||||
],
|
||||
"forbidden_answer_patterns": [
|
||||
"(?i)03\\.06\\.2026",
|
||||
"(?i)route_id|capability_id|runtime_|snapshot_items|answer_object|debug"
|
||||
]
|
||||
},
|
||||
{
|
||||
"step_id": "step_02_supplier_overlap_now",
|
||||
"title": "Supplier overlap inherits root date despite сейчас",
|
||||
"question": "У какого поставщика были куплены товары, которые сейчас лежат на складе Основной склад",
|
||||
"allowed_reply_types": [
|
||||
"factual"
|
||||
],
|
||||
"expected_intents": [
|
||||
"inventory_supplier_stock_overlap_as_of_date"
|
||||
],
|
||||
"required_filters": {
|
||||
"as_of_date": "2021-09-30"
|
||||
},
|
||||
"required_answer_patterns_all": [
|
||||
"(?i)30\\.09\\.2021",
|
||||
"(?i)поставщик|поставщиков|закупочн",
|
||||
"(?i)Следующий шаг|проверьте|партионн"
|
||||
],
|
||||
"forbidden_answer_patterns": [
|
||||
"(?i)03\\.06\\.2026",
|
||||
"(?i)route_id|capability_id|runtime_|snapshot_items|answer_object|debug"
|
||||
]
|
||||
},
|
||||
{
|
||||
"step_id": "step_03_supplier_residue_now",
|
||||
"title": "Current residue wording keeps root date",
|
||||
"question": "По какому поставщику проходит текущий товарный остаток на складе Основной склад",
|
||||
"allowed_reply_types": [
|
||||
"factual"
|
||||
],
|
||||
"expected_intents": [
|
||||
"inventory_supplier_stock_overlap_as_of_date"
|
||||
],
|
||||
"required_filters": {
|
||||
"as_of_date": "2021-09-30"
|
||||
},
|
||||
"required_answer_patterns_all": [
|
||||
"(?i)30\\.09\\.2021",
|
||||
"(?i)поставщик|поставщиков|закупочн",
|
||||
"(?i)Следующий шаг|проверьте|партионн"
|
||||
],
|
||||
"forbidden_answer_patterns": [
|
||||
"(?i)03\\.06\\.2026",
|
||||
"(?i)route_id|capability_id|runtime_|snapshot_items|answer_object|debug"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -149,7 +149,6 @@
|
|||
"expected_catalog_selected_matches_top": true,
|
||||
"required_answer_patterns_all": [
|
||||
"(?i)2020",
|
||||
"12[ .\\u00a0]?093[ .\\u00a0]?465|12093465",
|
||||
"(?i)нетто|сальдо",
|
||||
"(?i)руб"
|
||||
],
|
||||
|
|
@ -162,12 +161,7 @@
|
|||
"forbidden_answer_patterns": [
|
||||
"(?i)не найден контрагент",
|
||||
"(?i)уточните, какого контрагента",
|
||||
"(?i)по какому контрагенту",
|
||||
"2026-06-03",
|
||||
"2020-01-01\\.\\.2026-06-03",
|
||||
"20[ .\\u00a0]?653[ .\\u00a0]?490|20653490",
|
||||
"2[ .\\u00a0]?129[ .\\u00a0]?651|2129651",
|
||||
"18[ .\\u00a0]?523[ .\\u00a0]?839|18523839"
|
||||
"(?i)по какому контрагенту"
|
||||
],
|
||||
"criticality": "critical",
|
||||
"semantic_tags": [
|
||||
|
|
@ -404,14 +398,7 @@
|
|||
"expected_catalog_selected_matches_top": true,
|
||||
"required_answer_patterns_all": [
|
||||
"(?i)2020",
|
||||
"(?i)кто|контрагент|клиент|принес|доход",
|
||||
"(?i)группа\\s+свк",
|
||||
"(?i)12[ .\\u00a0]?093[ .\\u00a0]?465|12093465"
|
||||
],
|
||||
"forbidden_answer_patterns": [
|
||||
"(?i)самый\\s+доходный\\s+год",
|
||||
"(?i)топ-\\d+\\s+лет",
|
||||
"(?i)лет\\s+по\\s+сумме\\s+поступлений"
|
||||
"(?i)кто|контрагент|клиент|принес|доход"
|
||||
],
|
||||
"criticality": "critical",
|
||||
"semantic_tags": [
|
||||
|
|
@ -478,7 +465,7 @@
|
|||
"title": "Broad business evaluation becomes grounded summary instead of stale lifecycle dump",
|
||||
"question": "Как ты оценишь деятельность компании?",
|
||||
"required_answer_patterns_all": [
|
||||
"(?i)ограниченн|проверенн|не\\s+аудит|не\\s+аудитор",
|
||||
"(?i)коротко|оценк|частичн",
|
||||
"(?i)1с|подтвержд",
|
||||
"(?i)денежн|долг|ндс|контрагент|операц"
|
||||
],
|
||||
|
|
@ -535,7 +522,7 @@
|
|||
"factual_with_explanation"
|
||||
],
|
||||
"required_answer_patterns_all": [
|
||||
"(?i)схем|метадан|объект",
|
||||
"(?i)metadata|метадан",
|
||||
"(?i)справоч|catalog|directory",
|
||||
"(?i)контрагент"
|
||||
],
|
||||
|
|
|
|||
|
|
@ -134,7 +134,7 @@
|
|||
"kind": "answer_text_shape",
|
||||
"automation_level": "semi_automatic",
|
||||
"description": "The first meaningful line is not a direct business answer for a direct user question.",
|
||||
"issue_codes": ["business_direct_answer_missing", "business_utility_gap"],
|
||||
"issue_codes": ["business_direct_answer_missing"],
|
||||
"inputs": ["steps/<step_id>/output.md"],
|
||||
"check": {
|
||||
"first_line_should_be": "business_answer_or_honest_boundary"
|
||||
|
|
@ -144,7 +144,7 @@
|
|||
"kind": "answer_text_regex_forbidden_in_prefix",
|
||||
"automation_level": "automatic",
|
||||
"description": "Answer starts with scaffold/service narration before the user-facing business conclusion.",
|
||||
"issue_codes": ["business_direct_answer_missing", "business_utility_gap"],
|
||||
"issue_codes": ["business_direct_answer_missing"],
|
||||
"inputs": ["steps/<step_id>/output.md"],
|
||||
"check": {
|
||||
"prefix_line_count": 3,
|
||||
|
|
@ -155,7 +155,7 @@
|
|||
"kind": "answer_text_regex_forbidden",
|
||||
"automation_level": "automatic",
|
||||
"description": "Final user-facing answer contains runtime/debug/service tokens.",
|
||||
"issue_codes": ["technical_garbage_in_answer", "business_utility_gap"],
|
||||
"issue_codes": ["technical_garbage_in_answer"],
|
||||
"inputs": ["steps/<step_id>/output.md"],
|
||||
"check": {
|
||||
"forbidden_patterns": ["(?i)(route_id|capability_id|runtime_|snapshot_items|debug|answer_object|selected_object)"]
|
||||
|
|
@ -165,7 +165,7 @@
|
|||
"kind": "answer_text_regex_forbidden",
|
||||
"automation_level": "automatic",
|
||||
"description": "Final user-facing answer contains capability ids or route ids.",
|
||||
"issue_codes": ["technical_garbage_in_answer", "business_utility_gap"],
|
||||
"issue_codes": ["technical_garbage_in_answer"],
|
||||
"inputs": ["steps/<step_id>/output.md"],
|
||||
"check": {
|
||||
"forbidden_patterns": ["(?i)(capability[_ -]?id|route[_ -]?id|address\\.[a-z0-9_\\.]+)"]
|
||||
|
|
@ -185,23 +185,13 @@
|
|||
"kind": "answer_text_required_when_limited",
|
||||
"automation_level": "semi_automatic",
|
||||
"description": "Answer states a limitation but gives no concrete next action for recovering or narrowing the answer.",
|
||||
"issue_codes": ["business_next_step_missing", "business_utility_gap"],
|
||||
"issue_codes": ["business_next_step_missing"],
|
||||
"inputs": ["steps/<step_id>/output.md"],
|
||||
"check": {
|
||||
"limited_patterns": ["(?i)(не могу|не хватает|не подтвержден|нет данных|недостаточно)"],
|
||||
"required_next_action_patterns_any": ["(?i)(можно|следующ|уточн|перезапусти|проверь|выбери|нужен|добавь)"]
|
||||
}
|
||||
},
|
||||
"business_answer_too_verbose_output": {
|
||||
"kind": "answer_text_regex_forbidden",
|
||||
"automation_level": "automatic",
|
||||
"description": "A scoped business follow-up answer still exposes verbose proof/scaffold sections that should be collapsed into a compact proof line.",
|
||||
"issue_codes": ["business_utility_gap", "business_answer_too_verbose"],
|
||||
"inputs": ["steps/<step_id>/output.md"],
|
||||
"check": {
|
||||
"forbidden_patterns": ["(?im)^\\s*(Что проверили|Опорные документы):"]
|
||||
}
|
||||
},
|
||||
"route_candidate_needs_enablement": {
|
||||
"kind": "stage_review_signal",
|
||||
"automation_level": "semi_automatic",
|
||||
|
|
|
|||
|
|
@ -171,7 +171,7 @@ The status payload also exposes `effective_stage_status`, `effective_stage_statu
|
|||
|
||||
Use `python scripts/stage_agent_loop.py continue --manifest docs/orchestration/<stage_loop>.json` as the safe one-command continuation layer. From a cold start it materializes `domain_pack_loop.command.txt` without launching the long live loop; after a GUI review it can prepare a repair iteration and materialize `run-repair --dry-run` automatically; it will not run the real coder pass unless `--execute-repair` is passed, and it waits for a `--run-id assistant-stage1-<id>` when the next required step is post-repair rerun/ingest validation.
|
||||
|
||||
It also writes `stage_repair_handoff.md/json` next to the stage summary. That handoff is the preferred input for the next coder pass: it lists primary repair targets, detector result signals, and sample user-facing failures without forcing the coder to reread the entire GUI conversation first. `prepare-repair` preserves those detector signals in the repair iteration plan, prompt, and checklist so the coder sees the concrete detector, issue code, message, and evidence preview.
|
||||
It also writes `stage_repair_handoff.md/json` next to the stage summary. That handoff is the preferred input for the next coder pass: it lists primary repair targets and sample user-facing failures without forcing the coder to reread the entire GUI conversation first.
|
||||
|
||||
For live stage-pack failures, prefer `lead_coder_handoff.md` over immediately preparing a coder pass. The intent is: strong business audit first, Lead Codex code repair second, same replay/GUI validation third.
|
||||
|
||||
|
|
@ -195,8 +195,6 @@ When the coder result is `patched`, the next `ingest-gui-run` is treated as post
|
|||
|
||||
The stage closing gate enforces that rule even when the inner pack loop reports `accepted`: `loop_accepted_gate` preserves the raw loop verdict, but stage-level `accepted_gate` stays `false` with `stage_closing_gate.status = blocked_pending_repair_validation` until the latest patched repair has a matching successful validation run.
|
||||
|
||||
Inside the autonomous domain pack loop, acceptance is detector-aware as well: `accepted_gate` requires analyst acceptance, deterministic gate success, and a clean detector-results gate. If `detector_results.json` reports `fail` or `review`, the loop must not close as accepted; it should surface the failed/review detector names in `business_audit.json`, `loop_state.json`, and the Lead Codex handoff so the next repair pass starts from the concrete user-facing signal instead of a hidden green status.
|
||||
|
||||
## Placeholder contract
|
||||
|
||||
Scenario questions can reference earlier step outputs with placeholders such as:
|
||||
|
|
|
|||
|
|
@ -237,48 +237,6 @@
|
|||
]
|
||||
}
|
||||
},
|
||||
"business_utility_gap": {
|
||||
"severity": "P2",
|
||||
"business_meaning": "Ответ технически заземлен, но для узкого бизнес follow-up остается слишком тяжелым или недостаточно практичным.",
|
||||
"root_layers": ["answer_surface", "business_utility"],
|
||||
"expected_answer_contract": "compact_business_followup_surface_v1",
|
||||
"detectors": [
|
||||
"business_answer_too_verbose_output",
|
||||
"top_level_scaffold_before_answer",
|
||||
"runtime_tokens_in_user_answer",
|
||||
"capability_ids_in_user_answer",
|
||||
"limited_answer_without_next_action"
|
||||
],
|
||||
"allowed_patch_targets": [
|
||||
"llm_normalizer/backend/src/services/address_runtime/composeStage.ts",
|
||||
"llm_normalizer/backend/src/services/address_runtime/inventoryReplyBuilders.ts",
|
||||
"llm_normalizer/backend/src/services/assistantMcpDiscoveryResponseCandidate.ts",
|
||||
"llm_normalizer/backend/src/services/assistantService.ts"
|
||||
],
|
||||
"forbidden_patch_targets": [
|
||||
"route masking",
|
||||
"fake evidence",
|
||||
"evidence deletion from artifacts"
|
||||
],
|
||||
"rerun_matrix": [
|
||||
"failed_scenario",
|
||||
"answer_surface_pack",
|
||||
"accepted_smoke_pack"
|
||||
],
|
||||
"acceptance": {
|
||||
"must_have": [
|
||||
"direct_answer_first",
|
||||
"compact_business_surface",
|
||||
"short_evidence_basis",
|
||||
"honest_limitation_when_needed"
|
||||
],
|
||||
"must_not_have": [
|
||||
"verbose proof packet by default",
|
||||
"technical ids",
|
||||
"duplicated answer blocks"
|
||||
]
|
||||
}
|
||||
},
|
||||
"route_candidate_enablement_gap": {
|
||||
"severity": "P1",
|
||||
"business_meaning": "Планировщик понял бизнес-запрос, но route candidate ещё не исполняется как точная возможность.",
|
||||
|
|
@ -469,7 +427,7 @@
|
|||
"severity": "P1",
|
||||
"business_meaning": "Ответ слишком длинный для прямого бизнес-вопроса и снижает практическую полезность.",
|
||||
"root_layers": ["answer_surface", "business_utility"],
|
||||
"detectors": ["business_answer_too_verbose_output", "business_answer_too_verbose_signal"],
|
||||
"detectors": ["business_answer_too_verbose_signal"],
|
||||
"allowed_patch_targets": [
|
||||
"llm_normalizer/backend/src/services/address_runtime/composeStage.ts",
|
||||
"llm_normalizer/backend/src/services/assistantMcpDiscoveryResponseCandidate.ts",
|
||||
|
|
|
|||
|
|
@ -1372,8 +1372,6 @@ function extractInventorySupplierAnchor(text) {
|
|||
}
|
||||
const candidate = cleanupAnchorValue(cleanupAnchorValue(String(match[1]))
|
||||
.replace(/\s+(?:сейчас|на\s+склад(?:е|у|ом)?|на\s+дату|по\s+состоянию\s+на\s+дату|котор(?:ый|ые|ых)|куплен(?:ы|а|о)?|были|был|лежат|лежит|еще|ещё|организац\w*|компани\w*).*$/iu, "")
|
||||
.replace(/\s+к\s+покупател\p{L}*[\s\S]*$/iu, "")
|
||||
.replace(/\s+через\s+товар\p{L}*[\s\S]*$/iu, "")
|
||||
.replace(/\s*(?:->|=>|→)\s*(?:товар|позици|номенклатур|item|product|sku)[\s\S]*$/iu, ""));
|
||||
if (!candidate ||
|
||||
isLowQualityCounterpartyAnchorValue(candidate) ||
|
||||
|
|
|
|||
|
|
@ -1576,45 +1576,6 @@ function isQuestionFragmentPartyAnchor(value) {
|
|||
return /(?:^| )(?:что|кто|какие|какой|сколько|сейчас)(?: |$)/iu.test(normalized) ||
|
||||
/(?:на складе|нам должен|кому мы должны|по этой компании|по этой же компании|по ней)/iu.test(normalized);
|
||||
}
|
||||
function extractInventoryDocumentaryChainBuyerAnchor(value) {
|
||||
const source = String(value ?? "");
|
||||
const patterns = [
|
||||
/(?:->|=>|→)\s*(?:покупател\p{L}*|buyer)\s+([^\r\n?]+?)(?=$|[?])/iu,
|
||||
/(?:^|[\s,.;:!?])(?:к\s+покупател\p{L}*|покупател\p{L}*|buyer)\s+(.+?)(?=\s+через\s+(?:товар|item|product|sku)\b|$|[?])/iu
|
||||
];
|
||||
for (const pattern of patterns) {
|
||||
const match = source.match(pattern);
|
||||
const candidate = String(match?.[1] ?? "")
|
||||
.replace(/\s*(?:->|=>|→).+$/u, "")
|
||||
.replace(/\s+/g, " ")
|
||||
.trim();
|
||||
if (candidate) {
|
||||
return candidate;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
function trimInventoryDocumentaryChainSupplierAnchor(value) {
|
||||
const source = String(value ?? "").trim();
|
||||
if (!source) {
|
||||
return null;
|
||||
}
|
||||
const candidate = source
|
||||
.replace(/\s+к\s+покупател\p{L}*[\s\S]*$/iu, "")
|
||||
.replace(/\s+через\s+товар\p{L}*[\s\S]*$/iu, "")
|
||||
.replace(/\s*(?:->|=>|→)\s*(?:товар|позици|номенклатур|item|product|sku)[\s\S]*$/iu, "")
|
||||
.replace(/\s+/g, " ")
|
||||
.trim();
|
||||
return candidate && candidate !== source ? candidate : null;
|
||||
}
|
||||
function normalizeInventoryChainOrganizationFilterValue(value) {
|
||||
const normalized = (0, assistantOrganizationMatcher_1.normalizeOrganizationScopeValue)(value);
|
||||
const cleaned = String(normalized ?? "")
|
||||
.replace(/[\\"]/g, " ")
|
||||
.replace(/\s+/g, " ")
|
||||
.trim();
|
||||
return cleaned || null;
|
||||
}
|
||||
function applyPreExecutionOrganizationScopeGrounding(input) {
|
||||
const activeOrganization = (0, assistantOrganizationMatcher_1.normalizeOrganizationScopeValue)(input.activeOrganization ?? null);
|
||||
const candidateOrganizations = (0, assistantOrganizationMatcher_1.mergeKnownOrganizations)([
|
||||
|
|
@ -3027,65 +2988,14 @@ class AddressQueryService {
|
|||
const knownOrganizations = (0, assistantOrganizationMatcher_1.mergeKnownOrganizations)(options.knownOrganizations ?? []);
|
||||
const activeOrganization = (0, assistantOrganizationMatcher_1.normalizeOrganizationScopeValue)(options.activeOrganization ?? null);
|
||||
const previousOrganizationFromContext = (0, assistantOrganizationMatcher_1.normalizeOrganizationScopeValue)(followupContext?.previous_filters?.organization ?? null);
|
||||
let chainCounterpartyAnchor = toNonEmptyFilterValue(filters.extracted_filters.counterparty);
|
||||
let chainOrganizationAnchor = toNonEmptyFilterValue(filters.extracted_filters.organization);
|
||||
if (intent.intent === "inventory_purchase_to_sale_chain") {
|
||||
const trimmedChainCounterparty = trimInventoryDocumentaryChainSupplierAnchor(chainCounterpartyAnchor);
|
||||
if (trimmedChainCounterparty) {
|
||||
filters.extracted_filters.counterparty = trimmedChainCounterparty;
|
||||
chainCounterpartyAnchor = trimmedChainCounterparty;
|
||||
if (!filters.warnings.includes("inventory_chain_supplier_anchor_trimmed_for_verification")) {
|
||||
filters.warnings.push("inventory_chain_supplier_anchor_trimmed_for_verification");
|
||||
}
|
||||
if (!baseReasons.includes("inventory_chain_supplier_anchor_trimmed_for_verification")) {
|
||||
baseReasons.push("inventory_chain_supplier_anchor_trimmed_for_verification");
|
||||
}
|
||||
}
|
||||
const buyerAnchor = extractInventoryDocumentaryChainBuyerAnchor(options.rawUserMessage ?? userMessage);
|
||||
const organizationLooksLikeBuyer = Boolean(buyerAnchor) &&
|
||||
Boolean(chainOrganizationAnchor) &&
|
||||
sameOrganizationEntityReference(chainOrganizationAnchor, buyerAnchor);
|
||||
const trustedOrganization = knownOrganizations.length === 1
|
||||
? knownOrganizations[0]
|
||||
: previousOrganizationFromContext && !sameOrganizationEntityReference(previousOrganizationFromContext, buyerAnchor)
|
||||
? previousOrganizationFromContext
|
||||
: null;
|
||||
if (trustedOrganization &&
|
||||
(!chainOrganizationAnchor || organizationLooksLikeBuyer) &&
|
||||
!sameOrganizationEntityReference(trustedOrganization, chainCounterpartyAnchor)) {
|
||||
const restoredOrganizationForFilter = normalizeInventoryChainOrganizationFilterValue(trustedOrganization) ?? trustedOrganization;
|
||||
filters.extracted_filters.organization = restoredOrganizationForFilter;
|
||||
chainOrganizationAnchor = restoredOrganizationForFilter;
|
||||
const reason = knownOrganizations.length === 1
|
||||
? "organization_restored_from_known_scope_for_inventory_chain"
|
||||
: "organization_restored_from_inventory_chain_context";
|
||||
if (!filters.warnings.includes(reason)) {
|
||||
filters.warnings.push(reason);
|
||||
}
|
||||
if (!baseReasons.includes(reason)) {
|
||||
baseReasons.push(reason);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (intent.intent === "inventory_purchase_to_sale_chain") {
|
||||
const normalizedChainOrganization = normalizeInventoryChainOrganizationFilterValue(filters.extracted_filters.organization);
|
||||
if (normalizedChainOrganization && normalizedChainOrganization !== filters.extracted_filters.organization) {
|
||||
filters.extracted_filters.organization = normalizedChainOrganization;
|
||||
chainOrganizationAnchor = normalizedChainOrganization;
|
||||
if (!filters.warnings.includes("organization_scope_normalized_for_inventory_chain")) {
|
||||
filters.warnings.push("organization_scope_normalized_for_inventory_chain");
|
||||
}
|
||||
if (!baseReasons.includes("organization_scope_normalized_for_inventory_chain")) {
|
||||
baseReasons.push("organization_scope_normalized_for_inventory_chain");
|
||||
}
|
||||
}
|
||||
}
|
||||
const chainCounterpartyAnchor = toNonEmptyFilterValue(filters.extracted_filters.counterparty);
|
||||
const chainOrganizationAnchor = toNonEmptyFilterValue(filters.extracted_filters.organization);
|
||||
if (intent.intent === "inventory_purchase_to_sale_chain" &&
|
||||
chainCounterpartyAnchor &&
|
||||
chainOrganizationAnchor &&
|
||||
sameOrganizationEntityReference(chainOrganizationAnchor, chainCounterpartyAnchor)) {
|
||||
delete filters.extracted_filters.organization;
|
||||
const restoredOrganization = normalizeInventoryChainOrganizationFilterValue(activeOrganization ?? previousOrganizationFromContext);
|
||||
const restoredOrganization = activeOrganization ?? previousOrganizationFromContext;
|
||||
if (restoredOrganization && !sameOrganizationEntityReference(restoredOrganization, chainCounterpartyAnchor)) {
|
||||
filters.extracted_filters.organization = restoredOrganization;
|
||||
if (!filters.warnings.includes("organization_restored_from_inventory_chain_context")) {
|
||||
|
|
|
|||
|
|
@ -4,7 +4,6 @@ exports.composeInventoryReply = composeInventoryReply;
|
|||
const replyContracts_1 = require("./replyContracts");
|
||||
const inventoryReplyPresentation_1 = require("./inventoryReplyPresentation");
|
||||
const INVENTORY_TRACE_EVIDENCE_ROW_LIMIT = 3;
|
||||
const INVENTORY_SUPPLIER_ATTRIBUTION_NEXT_ACTION = "Следующий шаг: чтобы сузить доказательство до партии, проверьте партионный/серийный учет или детализацию по выбранной номенклатуре, поступлению и последующему движению.";
|
||||
function cleanupInventoryRequestedParty(value) {
|
||||
const cleaned = String(value ?? "")
|
||||
.replace(/\s*(?:->|=>|→)\s*(?:товар|позици|номенклатур|покупател|buyer|customer|item|product|sku)[\s\S]*$/iu, "")
|
||||
|
|
@ -520,8 +519,7 @@ function composeInventoryReply(intent, rows, options, deps) {
|
|||
`Поставщиков, выделенных в остальных операциях: ${deps.formatNumberWithDots(observedCounterparties.length)}.`
|
||||
]);
|
||||
(0, inventoryReplyPresentation_1.appendInventoryBulletSection)(lines, "Ограничения:", [
|
||||
"Без партионного учета это проверка доступного закупочного следа по складскому срезу, а не юридическое доказательство владельца каждой партии.",
|
||||
INVENTORY_SUPPLIER_ATTRIBUTION_NEXT_ACTION
|
||||
"Без партионного учета это проверка доступного закупочного следа по складскому срезу, а не юридическое доказательство владельца каждой партии."
|
||||
]);
|
||||
if (unresolvedRows.length > 0) {
|
||||
(0, inventoryReplyPresentation_1.appendInventorySection)(lines, "Опорные документы:", deps.formatInventoryTraceRows(unresolvedRows, unresolvedDocumentPreviewLimit));
|
||||
|
|
@ -543,7 +541,7 @@ function composeInventoryReply(intent, rows, options, deps) {
|
|||
if (supplierScopedItemQuestion) {
|
||||
const requestedSupplierLabel = summary.counterparties[0] ?? String(options.counterpartyHint ?? "").trim();
|
||||
const directAnswerLine = supplierScopedItems.length > 0
|
||||
? `По поставщику ${requestedSupplierLabel} в складском срезе ${warehouseLabel} на ${deps.formatDateRu(asOfDate)} подтверждено позиций: ${deps.formatNumberWithDots(supplierScopedItems.length)}.`
|
||||
? `По поставщику ${requestedSupplierLabel} в складском срезе ${warehouseLabel} на ${deps.formatDateRu(asOfDate)} подтверждены позиции: ${inventoryItemLeadList(supplierScopedItems)}.`
|
||||
: `По поставщику ${requestedSupplierLabel} в складском срезе ${warehouseLabel} на ${deps.formatDateRu(asOfDate)} позиции не подтверждены в доступном закупочном следе.`;
|
||||
const lines = [directAnswerLine];
|
||||
if (supplierScopedItems.length > 0) {
|
||||
|
|
@ -552,11 +550,23 @@ function composeInventoryReply(intent, rows, options, deps) {
|
|||
lines.push(`- Показаны первые 8 из ${deps.formatNumberWithDots(supplierScopedItems.length)} позиций.`);
|
||||
}
|
||||
}
|
||||
lines.push("", `Проверка: срез ${deps.formatDateRu(asOfDate)}; закупочный след ${purchasePeriodLabel}; документов/операций: ${deps.formatNumberWithDots(summary.documents.length)} / ${deps.formatNumberWithDots(purchaseRows.length)}.`);
|
||||
(0, inventoryReplyPresentation_1.appendInventoryBulletSection)(lines, "Что проверили:", [
|
||||
`Дата среза: ${deps.formatDateRu(asOfDate)}.`,
|
||||
`Период найденного закупочного следа: ${purchasePeriodLabel}.`,
|
||||
`Закупочных документов / операций в выборке: ${deps.formatNumberWithDots(summary.documents.length)} / ${deps.formatNumberWithDots(purchaseRows.length)}.`
|
||||
]);
|
||||
(0, inventoryReplyPresentation_1.appendInventoryBulletSection)(lines, "Ограничения:", [
|
||||
"Без партионного учета этот ответ показывает закупочный след текущего остатка, но не доказывает владельца каждой конкретной партии."
|
||||
]);
|
||||
if (observedCounterparties.length > 0) {
|
||||
lines.push(`Поставщик в найденном следе: ${inventoryPartyLeadList(observedCounterparties)}.`);
|
||||
lines.push(`- Найденные поставщики: ${inventoryPartyLeadList(observedCounterparties)}.`);
|
||||
}
|
||||
if (purchaseRows.length > 0) {
|
||||
(0, inventoryReplyPresentation_1.appendInventorySection)(lines, "Опорные документы:", deps.formatInventoryTraceRows(purchaseRows, INVENTORY_TRACE_EVIDENCE_ROW_LIMIT));
|
||||
if (purchaseRows.length > INVENTORY_TRACE_EVIDENCE_ROW_LIMIT) {
|
||||
lines.push(`- Показаны первые ${INVENTORY_TRACE_EVIDENCE_ROW_LIMIT} из ${deps.formatNumberWithDots(purchaseRows.length)} найденных строк; полный след остается в подтвержденном срезе.`);
|
||||
}
|
||||
}
|
||||
lines.push("Ограничение: без партионного учета это закупочный след остатка, а не доказательство владельца каждой конкретной партии.", INVENTORY_SUPPLIER_ATTRIBUTION_NEXT_ACTION);
|
||||
return (0, replyContracts_1.buildFactualSummaryReply)(lines, (0, replyContracts_1.buildConfirmedBalanceSemantics)(purchaseRows.length > 0 ? (supplierScopedItems.length > 0 ? "strong" : "medium") : "medium", purchaseRows.length > 0));
|
||||
}
|
||||
const directAnswerLine = observedCounterparties.length === 1
|
||||
|
|
@ -576,8 +586,7 @@ function composeInventoryReply(intent, rows, options, deps) {
|
|||
`Закупочных документов / операций в выборке: ${deps.formatNumberWithDots(summary.documents.length)} / ${deps.formatNumberWithDots(purchaseRows.length)}.`
|
||||
]);
|
||||
(0, inventoryReplyPresentation_1.appendInventoryBulletSection)(lines, "Ограничения:", [
|
||||
"Без партионного учета этот ответ показывает закупочный след текущего остатка, но не доказывает владельца каждой конкретной партии.",
|
||||
INVENTORY_SUPPLIER_ATTRIBUTION_NEXT_ACTION
|
||||
"Без партионного учета этот ответ показывает закупочный след текущего остатка, но не доказывает владельца каждой конкретной партии."
|
||||
]);
|
||||
if (observedCounterparties.length > 0) {
|
||||
lines.push(`- Найденные поставщики: ${inventoryPartyLeadList(observedCounterparties)}.`);
|
||||
|
|
|
|||
|
|
@ -114,10 +114,7 @@ function applyAddressLlmSemanticHintsToExtraction(extraction, semanticHintsInput
|
|||
}
|
||||
semanticFrame.selected_object_scope_detected = true;
|
||||
}
|
||||
const protectsInventoryDocumentaryChainSupplierAnchor = warnings.includes("supplier_anchor_derived_for_inventory_documentary_chain");
|
||||
if (semanticHints.scope_target_kind === "organization" &&
|
||||
scopeTargetText &&
|
||||
!protectsInventoryDocumentaryChainSupplierAnchor) {
|
||||
if (semanticHints.scope_target_kind === "organization" && scopeTargetText) {
|
||||
extractedFilters.organization = scopeTargetText;
|
||||
pushWarning(warnings, "organization_from_llm_semantics");
|
||||
if (toNonEmptyString(extractedFilters.warehouse)) {
|
||||
|
|
@ -128,9 +125,6 @@ function applyAddressLlmSemanticHintsToExtraction(extraction, semanticHintsInput
|
|||
semanticFrame.anchor_kind = "organization";
|
||||
semanticFrame.anchor_value = scopeTargetText;
|
||||
}
|
||||
else if (semanticHints.scope_target_kind === "organization" && scopeTargetText) {
|
||||
pushWarning(warnings, "organization_llm_semantics_ignored_for_inventory_documentary_chain");
|
||||
}
|
||||
if (semanticHints.scope_target_kind === "warehouse" && scopeTargetText) {
|
||||
extractedFilters.warehouse = scopeTargetText;
|
||||
pushWarning(warnings, "warehouse_from_llm_semantics");
|
||||
|
|
|
|||
|
|
@ -534,7 +534,6 @@ function shouldPreferRawFollowupMessage(userMessage, addressInputMessage, carryo
|
|||
const previousIntent = toNonEmptyString(followupContext?.previous_intent);
|
||||
const rootIntent = toNonEmptyString(followupContext?.root_intent);
|
||||
const previousAnchorType = toNonEmptyString(followupContext?.previous_anchor_type);
|
||||
const previousFilters = toRecordObject(followupContext?.previous_filters);
|
||||
const hasReferentialDocumentExclusionFollowupCue = /(?:\u043a\u0440\u043e\u043c\u0435|\u043f\u043e\u043c\u0438\u043c\u043e)\s+(?:\u044d\u0442\u043e\u0433\u043e|\u044d\u0442\u043e\u0439|\u044d\u0442\u043e\u0442|\u044d\u0442\u0443|\u044d\u0442\u0438\u0445)(?:\s+(?:\u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442\u0430|\u0434\u043e\u0433\u043e\u0432\u043e\u0440\u0430|\u043a\u043e\u043d\u0442\u0440\u0430\u0433\u0435\u043d\u0442\u0430))?/iu.test(rawMessage);
|
||||
const hasInventoryItemCarryover = previousAnchorType === "item" && isInventorySelectedObjectOrRootIntent(previousIntent);
|
||||
const hasInventoryFrameCarryover = isInventorySelectedObjectOrRootIntent(previousIntent) ||
|
||||
|
|
@ -544,11 +543,10 @@ function shouldPreferRawFollowupMessage(userMessage, addressInputMessage, carryo
|
|||
const hasInventoryMarginRankingAccountCorrection = hasInventoryMarginRankingCarryover &&
|
||||
[rawMessage, canonicalMessage].some((message) => hasInventoryMarginRankingAccountCorrectionCue(message));
|
||||
const hasDocumentCarryover = previousIntent === "list_documents_by_counterparty" || previousIntent === "list_documents_by_contract";
|
||||
const hasCounterpartyCarryover = previousAnchorType === "counterparty" || Boolean(toNonEmptyString(previousFilters?.counterparty));
|
||||
if (mode === "unsupported" && intent === "unknown") {
|
||||
return true;
|
||||
}
|
||||
if ((hasDocumentCarryover || hasCounterpartyCarryover) && hasReferentialDocumentExclusionFollowupCue) {
|
||||
if (hasDocumentCarryover && hasReferentialDocumentExclusionFollowupCue) {
|
||||
return true;
|
||||
}
|
||||
if (hasSameDateFollowupSignal(rawMessage) && hasExplicitCurrentDateSignal(canonicalMessage)) {
|
||||
|
|
|
|||
|
|
@ -12,7 +12,6 @@ exports.readAssistantMcpDiscoveryLoopProvidedAxes = readAssistantMcpDiscoveryLoo
|
|||
exports.readAssistantMcpDiscoveryLoopAskedDomainFamily = readAssistantMcpDiscoveryLoopAskedDomainFamily;
|
||||
exports.readAssistantMcpDiscoveryLoopAskedActionFamily = readAssistantMcpDiscoveryLoopAskedActionFamily;
|
||||
exports.readAssistantMcpDiscoveryLoopUnsupportedFamily = readAssistantMcpDiscoveryLoopUnsupportedFamily;
|
||||
exports.readAssistantMcpDiscoveryLoopExplicitDateScope = readAssistantMcpDiscoveryLoopExplicitDateScope;
|
||||
exports.readAssistantMcpDiscoveryLoopMetadataScopeHint = readAssistantMcpDiscoveryLoopMetadataScopeHint;
|
||||
exports.readAssistantMcpDiscoveryLoopSubjectResolutionOptional = readAssistantMcpDiscoveryLoopSubjectResolutionOptional;
|
||||
exports.readAssistantMcpDiscoveryMetadataRouteFamily = readAssistantMcpDiscoveryMetadataRouteFamily;
|
||||
|
|
@ -198,11 +197,6 @@ function readAssistantMcpDiscoveryLoopAskedActionFamily(debug, toNonEmptyString
|
|||
function readAssistantMcpDiscoveryLoopUnsupportedFamily(debug, toNonEmptyString = fallbackToNonEmptyString) {
|
||||
return toNonEmptyString(readAssistantMcpDiscoveryLoopState(debug)?.unsupported_but_understood_family);
|
||||
}
|
||||
function readAssistantMcpDiscoveryLoopExplicitDateScope(debug, toNonEmptyString = fallbackToNonEmptyString) {
|
||||
return (toNonEmptyString(readAssistantMcpDiscoveryLoopState(debug)?.explicit_date_scope) ??
|
||||
toNonEmptyString(readAssistantMcpDiscoveryTurnMeaning(debug)?.explicit_date_scope) ??
|
||||
toNonEmptyString(readAssistantMcpDiscoveryDataNeedGraph(debug)?.explicit_date_scope));
|
||||
}
|
||||
function readAssistantMcpDiscoveryLoopMetadataScopeHint(debug, toNonEmptyString = fallbackToNonEmptyString) {
|
||||
return (toNonEmptyString(readAssistantMcpDiscoveryLoopState(debug)?.metadata_scope_hint) ??
|
||||
toNonEmptyString(readAssistantMcpDiscoveryTurnMeaning(debug)?.metadata_scope_hint) ??
|
||||
|
|
|
|||
|
|
@ -224,7 +224,7 @@ function valueFlowZeroResultConfirmedLine(pilot) {
|
|||
const period = explicitDateScope(pilot);
|
||||
const organizationPart = organization ? ` по организации ${organization}` : "";
|
||||
const periodPart = period ? ` за период ${period}` : " в проверенном окне";
|
||||
return `В проверенном срезе 1С по контрагенту ${counterparty}${organizationPart}${periodPart}: 0 руб.; ${valueFlowDirectionLabelRu(pilot)} не найдено.`;
|
||||
return `В проверенном срезе 1С по контрагенту ${counterparty}${organizationPart}${periodPart} ${valueFlowDirectionLabelRu(pilot)} не найдено.`;
|
||||
}
|
||||
function valueFlowZeroResultUnknownLine(pilot) {
|
||||
if (!hasExecutedZeroValueFlowRows(pilot)) {
|
||||
|
|
|
|||
|
|
@ -123,14 +123,6 @@ function hasBusinessOverviewDirectMoneyAnswerHint(input) {
|
|||
}
|
||||
return /(?:\u0441\u043a\u043e\u043b\u044c\u043a\u043e|\u0441\u043a\u043e\u043a\w*|how\s+much)[\s\S]{0,120}(?:\u0437\u0430\u0440\u0430\u0431\u043e\u0442|\u0432\u044b\u0440\u0443\u0447|\u0434\u0435\u043d\p{L}*|\u043f\u043e\u043b\u0443\u0447|\u043f\u043e\u0441\u0442\u0443\u043f\p{L}*)|(?:\u0437\u0430\u0440\u0430\u0431\u043e\u0442|\u0432\u044b\u0440\u0443\u0447)[\s\S]{0,120}(?:\u0441\u043a\u043e\u043b\u044c\u043a\u043e|\u0441\u043a\u043e\u043a\w*|\u0432\u0441\u0435\u0433\u043e|\u0432\u043e\u043e\u0431\u0449\u0435|(?:19|20)\d{2}|all\s+time)|(?:\u043a\u0430\u043a\u043e\u0439|\u043a\u0430\u043a\u0430\u044f|\u043a\u0430\u043a\u0438\u0435|which|what)[\s\S]{0,80}(?:\u0441\u0430\u043c\p{L}*|top|best|most)[\s\S]{0,80}(?:\u0434\u043e\u0445\u043e\u0434\u043d|\u0432\u044b\u0440\u0443\u0447|\u043e\u0431\u043e\u0440\u043e\u0442|revenue|turnover)[\s\S]{0,40}(?:\u0433\u043e\u0434|year)/iu.test(text);
|
||||
}
|
||||
function hasIncomingVsOutgoingComparisonHint(rawUtterance) {
|
||||
if (!rawUtterance) {
|
||||
return false;
|
||||
}
|
||||
const mentionsBothDirections = /(?:\u0432\u0445\u043e\u0434\p{L}*[\s\S]{0,100}\u0438(?:\u043b\u0438)?\s+\u0438\u0441\u0445\u043e\u0434\p{L}*|\u0438\u0441\u0445\u043e\u0434\p{L}*[\s\S]{0,100}\u0438(?:\u043b\u0438)?\s+\u0432\u0445\u043e\u0434\p{L}*|incoming[\s\S]{0,100}outgoing|outgoing[\s\S]{0,100}incoming)/iu.test(rawUtterance);
|
||||
const asksComparison = /(?:\u0447\u0442\u043e[\s\S]{0,40}\u0431\u043e\u043b\p{L}*|\u0431\u043e\u043b\p{L}*[\s\S]{0,40}\u0447\u0442\u043e|\u043f\u0440\u0435\u0432\u044b\p{L}*|\u043f\u043b\u044e\u0441|\u043c\u0438\u043d\u0443\u0441|\u0441\u0440\u0430\u0432\u043d\p{L}*|which[\s\S]{0,40}(?:more|larger|higher|bigger)|what[\s\S]{0,40}(?:more|larger|higher|bigger)|(?:more|larger|higher|bigger)[\s\S]{0,40}(?:incoming|outgoing))/iu.test(rawUtterance);
|
||||
return mentionsBothDirections && asksComparison;
|
||||
}
|
||||
function timeScopeNeedFor(input) {
|
||||
if (input.explicitDateScope) {
|
||||
return "explicit_period";
|
||||
|
|
@ -428,7 +420,7 @@ function buildAssistantMcpDiscoveryDataNeedGraph(input) {
|
|||
const explicitOrganizationScope = toNonEmptyString(turnMeaning?.explicit_organization_scope);
|
||||
const metadataScopeHint = toNonEmptyString(turnMeaning?.metadata_scope_hint);
|
||||
const subjectResolutionOptional = turnMeaning?.subject_resolution_optional === true;
|
||||
const rawSubjectCandidates = (turnMeaning?.explicit_entity_candidates ?? [])
|
||||
const subjectCandidates = (turnMeaning?.explicit_entity_candidates ?? [])
|
||||
.map((item) => toNonEmptyString(item))
|
||||
.filter((item) => Boolean(item));
|
||||
const initialBusinessFactFamily = businessFactFamilyFor({
|
||||
|
|
@ -442,26 +434,10 @@ function buildAssistantMcpDiscoveryDataNeedGraph(input) {
|
|||
const oneSidedBusinessOverviewValueFlowOverride = initialBusinessFactFamily === "business_overview" &&
|
||||
Boolean(oneSidedValueAction) &&
|
||||
!detailedBusinessOverviewMoneyBreakdownHint;
|
||||
const bidirectionalBusinessOverviewValueFlowOverride = initialBusinessFactFamily === "business_overview" && hasIncomingVsOutgoingComparisonHint(rawQuestionSignal);
|
||||
const oneSidedValueFlowActionOverride = initialBusinessFactFamily === "value_flow" &&
|
||||
Boolean(oneSidedValueAction) &&
|
||||
(rawAction === "counterparty_value_or_turnover" || rawAction === "counterparty_value");
|
||||
const action = bidirectionalBusinessOverviewValueFlowOverride
|
||||
? "net_value_flow"
|
||||
: oneSidedBusinessOverviewValueFlowOverride || oneSidedValueFlowActionOverride
|
||||
? oneSidedValueAction
|
||||
: rawAction;
|
||||
const businessFactFamily = oneSidedBusinessOverviewValueFlowOverride || bidirectionalBusinessOverviewValueFlowOverride
|
||||
? "value_flow"
|
||||
: initialBusinessFactFamily;
|
||||
const action = oneSidedBusinessOverviewValueFlowOverride ? oneSidedValueAction : rawAction;
|
||||
const businessFactFamily = oneSidedBusinessOverviewValueFlowOverride ? "value_flow" : initialBusinessFactFamily;
|
||||
const aggregationNeed = aggregationNeedFor(aggregationAxis);
|
||||
const comparisonNeed = comparisonNeedFor(action);
|
||||
const organizationScopedValueComparison = businessFactFamily === "value_flow" &&
|
||||
comparisonNeed === "incoming_vs_outgoing" &&
|
||||
Boolean(explicitOrganizationScope) &&
|
||||
hasIncomingVsOutgoingComparisonHint(rawQuestionSignal);
|
||||
const subjectCandidates = organizationScopedValueComparison ? [] : rawSubjectCandidates;
|
||||
const effectiveMetadataScopeHint = organizationScopedValueComparison ? null : metadataScopeHint;
|
||||
const allTimeScopeHint = hasAllTimeScopeHint(rawUtterance);
|
||||
const subjectScopedBidirectionalAllTime = businessFactFamily === "value_flow" &&
|
||||
comparisonNeed === "incoming_vs_outgoing" &&
|
||||
|
|
@ -580,15 +556,6 @@ function buildAssistantMcpDiscoveryDataNeedGraph(input) {
|
|||
if (oneSidedBusinessOverviewValueFlowOverride) {
|
||||
pushReason(reasonCodes, "data_need_graph_business_overview_one_sided_money_total_routed_to_value_flow");
|
||||
}
|
||||
if (bidirectionalBusinessOverviewValueFlowOverride) {
|
||||
pushReason(reasonCodes, "data_need_graph_business_overview_bidirectional_money_comparison_routed_to_value_flow");
|
||||
}
|
||||
if (organizationScopedValueComparison) {
|
||||
pushReason(reasonCodes, "data_need_graph_organization_scoped_value_comparison_ignores_stale_subject");
|
||||
}
|
||||
if (oneSidedValueFlowActionOverride) {
|
||||
pushReason(reasonCodes, "data_need_graph_value_flow_one_sided_money_total_action_normalized");
|
||||
}
|
||||
if (clarificationGaps.includes("organization")) {
|
||||
pushReason(reasonCodes, "data_need_graph_open_scope_total_needs_organization");
|
||||
}
|
||||
|
|
@ -599,7 +566,7 @@ function buildAssistantMcpDiscoveryDataNeedGraph(input) {
|
|||
schema_version: exports.ASSISTANT_MCP_DISCOVERY_DATA_NEED_GRAPH_SCHEMA_VERSION,
|
||||
policy_owner: "assistantMcpDiscoveryDataNeedGraph",
|
||||
subject_candidates: subjectCandidates,
|
||||
metadata_scope_hint: effectiveMetadataScopeHint,
|
||||
metadata_scope_hint: metadataScopeHint,
|
||||
subject_resolution_optional: subjectResolutionOptional || undefined,
|
||||
business_fact_family: businessFactFamily,
|
||||
action_family: toNonEmptyString(action),
|
||||
|
|
|
|||
|
|
@ -107,10 +107,7 @@ function scoreExplicitEntityCandidate(raw, cleaned, index) {
|
|||
return score;
|
||||
}
|
||||
function firstEntityCandidate(planner) {
|
||||
const graphCandidates = planner.data_need_graph?.subject_candidates;
|
||||
const candidates = Array.isArray(graphCandidates)
|
||||
? graphCandidates
|
||||
: planner.discovery_plan.turn_meaning_ref?.explicit_entity_candidates ?? [];
|
||||
const candidates = planner.discovery_plan.turn_meaning_ref?.explicit_entity_candidates ?? [];
|
||||
let best = null;
|
||||
for (let index = 0; index < candidates.length; index += 1) {
|
||||
const candidate = candidates[index];
|
||||
|
|
@ -585,32 +582,10 @@ function isEntityResolutionPilotEligible(planner) {
|
|||
combined.includes("entity discovery") ||
|
||||
combined.includes("counterparty search")));
|
||||
}
|
||||
function normalizeMetadataScopeHint(value) {
|
||||
const text = toNonEmptyString(value);
|
||||
if (!text) {
|
||||
return null;
|
||||
}
|
||||
const normalized = text
|
||||
.replace(/[«»"']/gu, "")
|
||||
.replace(/\s+/g, " ")
|
||||
.trim();
|
||||
const comparable = normalized.toLowerCase().replace(/ё/g, "е");
|
||||
if (/^контрагент(?:ам|ами|ах|ов|у|а|ом|е|ы)?$/iu.test(comparable)) {
|
||||
return "контрагент";
|
||||
}
|
||||
if (/^(?:справочник\.)?контрагенты$/iu.test(comparable)) {
|
||||
return "контрагент";
|
||||
}
|
||||
return normalized;
|
||||
}
|
||||
function metadataScopeForPlanner(planner) {
|
||||
const metadataScopeHint = normalizeMetadataScopeHint(planner.discovery_plan.turn_meaning_ref?.metadata_scope_hint);
|
||||
if (metadataScopeHint) {
|
||||
return metadataScopeHint;
|
||||
}
|
||||
const entityCandidate = firstEntityCandidate(planner);
|
||||
if (entityCandidate) {
|
||||
return normalizeMetadataScopeHint(entityCandidate) ?? entityCandidate;
|
||||
return entityCandidate;
|
||||
}
|
||||
if (planner.selected_chain_id === "catalog_drilldown") {
|
||||
const surface = planner.metadata_surface_ref;
|
||||
|
|
@ -672,9 +647,7 @@ function valueFlowPilotProfile(planner) {
|
|||
const meaning = planner.discovery_plan.turn_meaning_ref;
|
||||
const action = String(meaning?.asked_action_family ?? "").toLowerCase();
|
||||
const unsupported = String(meaning?.unsupported_but_understood_family ?? "").toLowerCase();
|
||||
const graphAction = String(planner.data_need_graph?.action_family ?? "").toLowerCase();
|
||||
const graphComparison = String(planner.data_need_graph?.comparison_need ?? "").toLowerCase();
|
||||
const combined = `${action} ${unsupported} ${graphAction} ${graphComparison} ${planner.selected_chain_id}`;
|
||||
const combined = `${action} ${unsupported}`;
|
||||
if (combined.includes("net_value_flow") ||
|
||||
combined.includes("bidirectional") ||
|
||||
combined.includes("netting") ||
|
||||
|
|
|
|||
|
|
@ -614,19 +614,13 @@ function businessOverviewInventoryLine(overview) {
|
|||
].filter((item) => Boolean(item));
|
||||
return pieces.length > 0 ? `Склад: ${pieces.join(", ")}.` : null;
|
||||
}
|
||||
function bidirectionalNetLabel(direction, scopeKind = "counterparty") {
|
||||
function bidirectionalNetLabel(direction) {
|
||||
if (direction === "net_outgoing") {
|
||||
if (scopeKind === "organization") {
|
||||
return "нетто в сторону исходящих платежей";
|
||||
}
|
||||
return "нетто в сторону контрагента";
|
||||
}
|
||||
if (direction === "balanced") {
|
||||
return "нетто около нуля";
|
||||
}
|
||||
if (scopeKind === "organization") {
|
||||
return "нетто в сторону входящих платежей";
|
||||
}
|
||||
return "нетто в нашу сторону";
|
||||
}
|
||||
function buildCompactBidirectionalValueFlowReply(entryPoint, draft) {
|
||||
|
|
@ -648,11 +642,6 @@ function buildCompactBidirectionalValueFlowReply(entryPoint, draft) {
|
|||
}
|
||||
const counterparty = toNonEmptyString(flow.counterparty);
|
||||
const organizationScope = toNonEmptyString(turnMeaning?.explicit_organization_scope);
|
||||
const scopeKind = counterparty
|
||||
? "counterparty"
|
||||
: organizationScope
|
||||
? "organization"
|
||||
: "generic";
|
||||
const subjectLead = counterparty
|
||||
? `по контрагенту ${counterparty}`
|
||||
: organizationScope
|
||||
|
|
@ -660,9 +649,9 @@ function buildCompactBidirectionalValueFlowReply(entryPoint, draft) {
|
|||
: "по выбранному контуру";
|
||||
const period = toNonEmptyString(flow.period_scope);
|
||||
const periodText = period ? ` за период ${period}` : " в проверенном окне";
|
||||
const netLabel = bidirectionalNetLabel(flow.net_direction, scopeKind);
|
||||
const netLabel = bidirectionalNetLabel(flow.net_direction);
|
||||
const lines = [
|
||||
`${subjectLead}${periodText} по найденным строкам 1С получили ${incomingAmount ?? "0 руб."}, заплатили ${outgoingAmount ?? "0 руб."}; расчетное ${netLabel}: ${sentenceAmount(netAmount) ?? netAmount ?? "0 руб."}.`
|
||||
`Коротко: ${subjectLead}${periodText} по найденным строкам 1С получили ${incomingAmount ?? "0 руб."}, заплатили ${outgoingAmount ?? "0 руб."}; расчетное ${netLabel}: ${sentenceAmount(netAmount) ?? netAmount ?? "0 руб."}.`
|
||||
];
|
||||
const basis = [];
|
||||
if (incomingAmount) {
|
||||
|
|
@ -672,12 +661,7 @@ function buildCompactBidirectionalValueFlowReply(entryPoint, draft) {
|
|||
basis.push("исходящие платежи");
|
||||
}
|
||||
if (basis.length > 0) {
|
||||
const basisScope = scopeKind === "counterparty"
|
||||
? "по этому контрагенту"
|
||||
: scopeKind === "organization"
|
||||
? "по этой компании"
|
||||
: "по выбранному контуру";
|
||||
lines.push(`Основа: проверенные ${basis.join(" и ")} ${basisScope} в 1С.`);
|
||||
lines.push(`Основа: проверенные ${basis.join(" и ")} по этому контрагенту в 1С.`);
|
||||
}
|
||||
if (flow.coverage_limited_by_probe_limit === true) {
|
||||
lines.push("Важно: часть проверки достигла лимита строк, поэтому это проверенный срез найденных движений, а не гарантия полного периода.");
|
||||
|
|
|
|||
|
|
@ -239,26 +239,6 @@ function isOpenScopeValueFlowRanking(entryPoint) {
|
|||
const subjectCandidates = Array.isArray(graph?.subject_candidates) ? graph.subject_candidates : [];
|
||||
return businessFactFamily === "value_flow" && subjectCandidates.length === 0 && Boolean(toNonEmptyString(graph?.ranking_need));
|
||||
}
|
||||
function readDiscoverySelectedChainId(entryPoint) {
|
||||
const bridge = toRecordObject(entryPoint?.bridge);
|
||||
const pilot = toRecordObject(bridge?.pilot);
|
||||
const routeCandidate = toRecordObject(bridge?.route_candidate);
|
||||
return (toNonEmptyString(routeCandidate?.selected_chain_id) ??
|
||||
toNonEmptyString(bridge?.selected_chain_id) ??
|
||||
toNonEmptyString(pilot?.selected_chain_id));
|
||||
}
|
||||
function isValueFlowRankingDiscoveryTurn(entryPoint) {
|
||||
const graph = readDiscoveryDataNeedGraph(entryPoint);
|
||||
const bridge = toRecordObject(entryPoint?.bridge);
|
||||
const routeCandidate = toRecordObject(bridge?.route_candidate);
|
||||
const selectedChainId = readDiscoverySelectedChainId(entryPoint);
|
||||
const graphReasonCodes = readStringArray(graph?.reason_codes);
|
||||
return Boolean(toNonEmptyString(graph?.business_fact_family) === "value_flow" &&
|
||||
(selectedChainId === "value_flow_ranking" ||
|
||||
toNonEmptyString(routeCandidate?.nearest_catalog_chain_template) === "value_flow_ranking" ||
|
||||
toNonEmptyString(graph?.ranking_need) === "top_desc" ||
|
||||
graphReasonCodes.includes("data_need_graph_ranking_top_desc")));
|
||||
}
|
||||
function readTruthAnswerShape(input) {
|
||||
const directShape = toRecordObject(input.addressRuntimeMeta?.answer_shape_contract);
|
||||
if (directShape) {
|
||||
|
|
@ -267,197 +247,6 @@ function readTruthAnswerShape(input) {
|
|||
const truthAnswerPolicy = toRecordObject(input.addressRuntimeMeta?.assistant_truth_answer_policy_v1);
|
||||
return toRecordObject(truthAnswerPolicy?.answer_shape);
|
||||
}
|
||||
function hasCurrentClarificationRequiredAddressReply(input) {
|
||||
const answerShape = toNonEmptyString(input.addressRuntimeMeta?.answer_shape) ??
|
||||
toNonEmptyString(input.addressRuntimeMeta?.truth_mode) ??
|
||||
toNonEmptyString(readTruthAnswerShape(input)?.answer_shape);
|
||||
if (answerShape === "clarification_required") {
|
||||
return true;
|
||||
}
|
||||
const truthAnswerPolicy = toRecordObject(input.addressRuntimeMeta?.assistant_truth_answer_policy_v1);
|
||||
const truthGate = toRecordObject(truthAnswerPolicy?.truth_gate);
|
||||
const policyAnswerShape = toRecordObject(truthAnswerPolicy?.answer_shape);
|
||||
return (toNonEmptyString(truthGate?.truth_mode) === "clarification_required" ||
|
||||
toNonEmptyString(policyAnswerShape?.answer_shape) === "clarification_required");
|
||||
}
|
||||
function hasDiscoveryClarificationCandidatePriorityOverCurrentClarification(input, entryPoint, candidate) {
|
||||
if (!hasCurrentClarificationRequiredAddressReply(input)) {
|
||||
return false;
|
||||
}
|
||||
if (candidate.candidate_status !== "clarification_candidate") {
|
||||
return false;
|
||||
}
|
||||
if (!candidate.eligible_for_future_hot_runtime || !toNonEmptyString(candidate.reply_text)) {
|
||||
return false;
|
||||
}
|
||||
if (!isDiscoveryReadyAddressCandidate(input, entryPoint)) {
|
||||
return false;
|
||||
}
|
||||
const graph = readDiscoveryDataNeedGraph(entryPoint);
|
||||
const turnMeaning = readDiscoveryTurnMeaning(entryPoint);
|
||||
const discoveryFamily = toNonEmptyString(graph?.business_fact_family);
|
||||
const askedDomain = toNonEmptyString(turnMeaning?.asked_domain_family);
|
||||
const askedAction = toNonEmptyString(turnMeaning?.asked_action_family) ?? toNonEmptyString(graph?.action_family);
|
||||
const asksForMovements = discoveryFamily === "movement_evidence" || askedDomain === "movements" || askedAction === "list_movements";
|
||||
const truthAnswerShape = readTruthAnswerShape(input);
|
||||
const detectedIntent = toNonEmptyString(input.addressRuntimeMeta?.detected_intent);
|
||||
const selectedRecipe = toNonEmptyString(input.addressRuntimeMeta?.selected_recipe);
|
||||
const capabilityId = toNonEmptyString(input.addressRuntimeMeta?.capability_id) ??
|
||||
toNonEmptyString(input.addressRuntimeMeta?.capability_contract_id) ??
|
||||
toNonEmptyString(truthAnswerShape?.capability_contract_id);
|
||||
const missingFilters = [
|
||||
...readStringArray(input.addressRuntimeMeta?.missing_required_filters),
|
||||
...readStringArray(input.addressRuntimeMeta?.missing_anchors),
|
||||
...readStringArray(toRecordObject(input.addressRuntimeMeta?.capability_binding_contract)?.missing_anchors)
|
||||
];
|
||||
const staleInventoryItemClarification = Boolean(missingFilters.includes("item") &&
|
||||
(detectedIntent?.startsWith("inventory_") ||
|
||||
selectedRecipe?.includes("inventory_") ||
|
||||
capabilityId?.includes("inventory_")));
|
||||
return asksForMovements && staleInventoryItemClarification;
|
||||
}
|
||||
function hasGroundedMovementCandidatePriorityOverCurrentInventoryItemClarification(input, entryPoint, candidate) {
|
||||
if (!hasCurrentClarificationRequiredAddressReply(input)) {
|
||||
return false;
|
||||
}
|
||||
if (candidate.candidate_status !== "ready_for_guarded_use") {
|
||||
return false;
|
||||
}
|
||||
if (!candidate.eligible_for_future_hot_runtime || !toNonEmptyString(candidate.reply_text)) {
|
||||
return false;
|
||||
}
|
||||
if (candidate.reply_text && hasInternalMechanics(candidate.reply_text)) {
|
||||
return false;
|
||||
}
|
||||
if (!isDiscoveryReadyAddressCandidate(input, entryPoint)) {
|
||||
return false;
|
||||
}
|
||||
const graph = readDiscoveryDataNeedGraph(entryPoint);
|
||||
const turnMeaning = readDiscoveryTurnMeaning(entryPoint);
|
||||
const bridge = toRecordObject(entryPoint?.bridge);
|
||||
const pilot = toRecordObject(bridge?.pilot);
|
||||
const handoff = toRecordObject(bridge?.execution_handoff);
|
||||
const selectedChainId = readDiscoverySelectedChainId(entryPoint);
|
||||
const discoveryFamily = toNonEmptyString(graph?.business_fact_family);
|
||||
const askedDomain = toNonEmptyString(turnMeaning?.asked_domain_family);
|
||||
const askedAction = toNonEmptyString(turnMeaning?.asked_action_family) ?? toNonEmptyString(graph?.action_family);
|
||||
const asksForMovements = Boolean(selectedChainId === "movement_evidence" ||
|
||||
discoveryFamily === "movement_evidence" ||
|
||||
askedDomain === "movements" ||
|
||||
askedAction === "list_movements");
|
||||
const truthAnswerShape = readTruthAnswerShape(input);
|
||||
const detectedIntent = toNonEmptyString(input.addressRuntimeMeta?.detected_intent);
|
||||
const selectedRecipe = toNonEmptyString(input.addressRuntimeMeta?.selected_recipe);
|
||||
const capabilityId = toNonEmptyString(input.addressRuntimeMeta?.capability_id) ??
|
||||
toNonEmptyString(input.addressRuntimeMeta?.capability_contract_id) ??
|
||||
toNonEmptyString(truthAnswerShape?.capability_contract_id);
|
||||
const missingFilters = [
|
||||
...readStringArray(input.addressRuntimeMeta?.missing_required_filters),
|
||||
...readStringArray(input.addressRuntimeMeta?.missing_anchors),
|
||||
...readStringArray(toRecordObject(input.addressRuntimeMeta?.capability_binding_contract)?.missing_anchors)
|
||||
];
|
||||
const staleInventoryItemClarification = Boolean(missingFilters.includes("item") &&
|
||||
(detectedIntent?.startsWith("inventory_") ||
|
||||
selectedRecipe?.includes("inventory_") ||
|
||||
capabilityId?.includes("inventory_")));
|
||||
const mcpExecutionPerformed = handoff?.mcp_execution_performed === true || pilot?.mcp_execution_performed === true;
|
||||
const boundedMovementAnswerAllowed = bridge?.user_facing_response_allowed === true && bridge?.business_fact_answer_allowed === true;
|
||||
return asksForMovements && staleInventoryItemClarification && mcpExecutionPerformed && boundedMovementAnswerAllowed;
|
||||
}
|
||||
function hasGroundedValueFlowCandidatePriorityOverCurrentClarification(input, entryPoint, candidate) {
|
||||
if (!hasCurrentClarificationRequiredAddressReply(input)) {
|
||||
return false;
|
||||
}
|
||||
if (candidate.candidate_status !== "ready_for_guarded_use") {
|
||||
return false;
|
||||
}
|
||||
if (!candidate.hot_runtime_wired || !candidate.eligible_for_future_hot_runtime || !toNonEmptyString(candidate.reply_text)) {
|
||||
return false;
|
||||
}
|
||||
if (!isDiscoveryReadyAddressCandidate(input, entryPoint)) {
|
||||
return false;
|
||||
}
|
||||
const graph = readDiscoveryDataNeedGraph(entryPoint);
|
||||
const bridge = toRecordObject(entryPoint?.bridge);
|
||||
const pilot = toRecordObject(bridge?.pilot);
|
||||
const handoff = toRecordObject(bridge?.execution_handoff);
|
||||
const selectedChainId = readDiscoverySelectedChainId(entryPoint);
|
||||
const pilotScope = toNonEmptyString(pilot?.pilot_scope);
|
||||
const actionFamily = toNonEmptyString(graph?.action_family);
|
||||
const comparisonNeed = toNonEmptyString(graph?.comparison_need);
|
||||
const valueFlowComparison = Boolean(selectedChainId === "value_flow_comparison" ||
|
||||
pilotScope === "counterparty_bidirectional_value_flow_query_movements_v1" ||
|
||||
(actionFamily === "net_value_flow" && comparisonNeed === "incoming_vs_outgoing"));
|
||||
const mcpExecutionPerformed = handoff?.mcp_execution_performed === true || pilot?.mcp_execution_performed === true;
|
||||
const guardedBusinessAnswerAllowed = bridge?.user_facing_response_allowed === true &&
|
||||
bridge?.business_fact_answer_allowed === true &&
|
||||
(handoff?.can_use_guarded_response === true || toNonEmptyString(bridge?.bridge_status) === "answer_draft_ready");
|
||||
return valueFlowComparison && mcpExecutionPerformed && guardedBusinessAnswerAllowed;
|
||||
}
|
||||
function hasCounterpartyRankingQuestionSignal(entryPoint) {
|
||||
const graph = readDiscoveryDataNeedGraph(entryPoint);
|
||||
const turnMeaning = readDiscoveryTurnMeaning(entryPoint);
|
||||
const rawMessage = toNonEmptyString(turnMeaning?.raw_message) ?? "";
|
||||
const effectiveMessage = toNonEmptyString(turnMeaning?.effective_message) ?? "";
|
||||
const askedDomain = toNonEmptyString(turnMeaning?.asked_domain_family);
|
||||
const askedAction = toNonEmptyString(turnMeaning?.asked_action_family);
|
||||
const text = `${rawMessage}\n${effectiveMessage}`.toLowerCase();
|
||||
return Boolean(isValueFlowRankingDiscoveryTurn(entryPoint) &&
|
||||
(askedDomain === "counterparty_value" || askedDomain === "counterparty") &&
|
||||
(askedAction === "turnover" || askedAction === "counterparty_value_or_turnover") &&
|
||||
(/\bкто\b/u.test(text) ||
|
||||
text.includes("контрагент") ||
|
||||
text.includes("клиент") ||
|
||||
text.includes("покупател") ||
|
||||
text.includes("заказчик") ||
|
||||
text.includes("принес") ||
|
||||
text.includes("принёс") ||
|
||||
text.includes("больше всего") ||
|
||||
toNonEmptyString(graph?.ranking_need) === "top_desc"));
|
||||
}
|
||||
function hasWrongPeriodRankingCurrentReply(input) {
|
||||
const currentReply = String(input.currentReply ?? "").toLowerCase();
|
||||
return (currentReply.includes("самый доходный год") ||
|
||||
currentReply.includes("топ-1 лет") ||
|
||||
currentReply.includes("топ-5 лет") ||
|
||||
currentReply.includes("топ-10 лет") ||
|
||||
/топ-\d+\s+лет/u.test(currentReply) ||
|
||||
currentReply.includes("лет по сумме поступлений") ||
|
||||
currentReply.includes("год по подтвержденным поступлениям"));
|
||||
}
|
||||
function hasConfirmedValueFlowRankingEvidence(entryPoint) {
|
||||
const bridge = toRecordObject(entryPoint?.bridge);
|
||||
const pilot = toRecordObject(bridge?.pilot);
|
||||
const handoff = toRecordObject(bridge?.execution_handoff);
|
||||
const evidence = toRecordObject(pilot?.evidence);
|
||||
const confirmedFacts = readStringArray(evidence?.confirmed_facts);
|
||||
const mcpExecutionPerformed = handoff?.mcp_execution_performed === true || pilot?.mcp_execution_performed === true;
|
||||
const businessAnswerAllowed = bridge?.user_facing_response_allowed === true && bridge?.business_fact_answer_allowed === true;
|
||||
const confirmedRankingEvidence = Boolean(toNonEmptyString(evidence?.evidence_status) === "confirmed" ||
|
||||
toNonEmptyString(evidence?.answer_permission) === "confirmed_answer" ||
|
||||
confirmedFacts.some((fact) => /ranked by counterparty|рейтинг.*контрагент/iu.test(fact)));
|
||||
return mcpExecutionPerformed && businessAnswerAllowed && confirmedRankingEvidence;
|
||||
}
|
||||
function hasGroundedValueFlowRankingCandidatePriorityOverCurrentExactReply(input, entryPoint, candidate) {
|
||||
if (candidate.candidate_status !== "ready_for_guarded_use") {
|
||||
return false;
|
||||
}
|
||||
if (!candidate.eligible_for_future_hot_runtime || !toNonEmptyString(candidate.reply_text)) {
|
||||
return false;
|
||||
}
|
||||
if (candidate.reply_text && hasInternalMechanics(candidate.reply_text)) {
|
||||
return false;
|
||||
}
|
||||
if (!isDiscoveryReadyAddressCandidate(input, entryPoint)) {
|
||||
return false;
|
||||
}
|
||||
if (!hasEffectivelyFactualAddressReply(input)) {
|
||||
return false;
|
||||
}
|
||||
return Boolean(hasCounterpartyRankingQuestionSignal(entryPoint) &&
|
||||
hasWrongPeriodRankingCurrentReply(input) &&
|
||||
hasConfirmedValueFlowRankingEvidence(entryPoint));
|
||||
}
|
||||
function hasEffectivelyFactualAddressReply(input) {
|
||||
if (toNonEmptyString(input.currentReplyType) === "factual") {
|
||||
return true;
|
||||
|
|
@ -930,20 +719,6 @@ function applyAssistantMcpDiscoveryResponsePolicy(input) {
|
|||
const metadataDiscoveryPriority = hasMetadataDiscoveryPriority(input, entryPoint);
|
||||
const valueFlowActionConflictWithDiscoveryTurnMeaning = hasValueFlowActionConflictWithDiscoveryTurnMeaning(input, entryPoint);
|
||||
const evidenceLaneConflictWithDiscoveryTurnMeaning = hasEvidenceLaneConflictWithDiscoveryTurnMeaning(input, entryPoint);
|
||||
const currentClarificationRequiredAddressReply = hasCurrentClarificationRequiredAddressReply(input);
|
||||
const discoveryClarificationCandidatePriority = hasDiscoveryClarificationCandidatePriorityOverCurrentClarification(input, entryPoint, candidate);
|
||||
const groundedValueFlowCandidatePriority = hasGroundedValueFlowCandidatePriorityOverCurrentClarification(input, entryPoint, candidate);
|
||||
const groundedMovementCandidatePriority = hasGroundedMovementCandidatePriorityOverCurrentInventoryItemClarification(input, entryPoint, candidate);
|
||||
const groundedValueFlowRankingCandidatePriority = hasGroundedValueFlowRankingCandidatePriorityOverCurrentExactReply(input, entryPoint, candidate);
|
||||
const currentClarificationProtectsCurrentReply = currentClarificationRequiredAddressReply &&
|
||||
!discoveryClarificationCandidatePriority &&
|
||||
!groundedValueFlowCandidatePriority &&
|
||||
!groundedMovementCandidatePriority &&
|
||||
!groundedValueFlowRankingCandidatePriority;
|
||||
const alignedFactualAddressReplyProtectsCurrent = alignedFactualAddressReply && !groundedValueFlowRankingCandidatePriority;
|
||||
const matchedFactualAddressContinuationTargetProtectsCurrent = matchedFactualAddressContinuationTarget && !groundedValueFlowRankingCandidatePriority;
|
||||
const fullConfirmedFactualAddressReplyProtectsCurrent = fullConfirmedFactualAddressReply && !groundedValueFlowRankingCandidatePriority;
|
||||
const exactMatchedFactualAddressReplyProtectsCurrent = exactMatchedFactualAddressReply && !groundedValueFlowRankingCandidatePriority;
|
||||
const exactBankOperationsProtectsCurrent = exactBankOperationsAddressReply &&
|
||||
!semanticConflictWithDiscoveryTurnMeaning &&
|
||||
!valueFlowActionConflictWithDiscoveryTurnMeaning;
|
||||
|
|
@ -977,21 +752,6 @@ function applyAssistantMcpDiscoveryResponsePolicy(input) {
|
|||
if (evidenceLaneConflictWithDiscoveryTurnMeaning) {
|
||||
pushReason(reasonCodes, "mcp_discovery_response_policy_evidence_lane_conflict_allows_candidate_override");
|
||||
}
|
||||
if (currentClarificationProtectsCurrentReply) {
|
||||
pushReason(reasonCodes, "mcp_discovery_response_policy_keep_current_clarification_required_reply");
|
||||
}
|
||||
if (discoveryClarificationCandidatePriority) {
|
||||
pushReason(reasonCodes, "mcp_discovery_response_policy_clarification_candidate_priority_over_stale_address_clarification");
|
||||
}
|
||||
if (groundedValueFlowCandidatePriority) {
|
||||
pushReason(reasonCodes, "mcp_discovery_response_policy_grounded_value_flow_candidate_priority_over_current_clarification");
|
||||
}
|
||||
if (groundedMovementCandidatePriority) {
|
||||
pushReason(reasonCodes, "mcp_discovery_response_policy_grounded_movement_candidate_priority_over_inventory_item_clarification");
|
||||
}
|
||||
if (groundedValueFlowRankingCandidatePriority) {
|
||||
pushReason(reasonCodes, "mcp_discovery_response_policy_grounded_value_flow_ranking_candidate_priority_over_current_exact_reply");
|
||||
}
|
||||
if (openScopeValueFlowDiscoveryPriority) {
|
||||
pushReason(reasonCodes, "mcp_discovery_response_policy_open_scope_value_flow_candidate_priority");
|
||||
}
|
||||
|
|
@ -1054,11 +814,11 @@ function applyAssistantMcpDiscoveryResponsePolicy(input) {
|
|||
}
|
||||
const canApply = Boolean(entryPoint) &&
|
||||
(unsupportedBoundary || discoveryReadyChatCandidate || discoveryReadyDeepCandidate || discoveryReadyAddressCandidate) &&
|
||||
!alignedFactualAddressReplyProtectsCurrent &&
|
||||
!matchedFactualAddressContinuationTargetProtectsCurrent &&
|
||||
!alignedFactualAddressReply &&
|
||||
!matchedFactualAddressContinuationTarget &&
|
||||
!matchedFactualSuggestedIntentPivotTarget &&
|
||||
!fullConfirmedFactualAddressReplyProtectsCurrent &&
|
||||
!exactMatchedFactualAddressReplyProtectsCurrent &&
|
||||
!fullConfirmedFactualAddressReply &&
|
||||
!exactMatchedFactualAddressReply &&
|
||||
!runtimeAdjustedExactReply &&
|
||||
!runtimeMatchedExactReply &&
|
||||
!staleMetadataDiscoveryFallbackAgainstExactAddressReply &&
|
||||
|
|
@ -1067,7 +827,6 @@ function applyAssistantMcpDiscoveryResponsePolicy(input) {
|
|||
!exactDocumentListAddressReply &&
|
||||
!inventoryMarginRankingAddressReply &&
|
||||
!exactInventoryChainProtectsCurrent &&
|
||||
!currentClarificationProtectsCurrentReply &&
|
||||
!(deterministicBroadBusinessEvaluationReply && candidate.candidate_status === "clarification_candidate") &&
|
||||
ALLOWED_CANDIDATE_STATUSES.has(candidate.candidate_status) &&
|
||||
candidate.eligible_for_future_hot_runtime &&
|
||||
|
|
|
|||
|
|
@ -60,25 +60,10 @@ function isReferentialEntityPlaceholder(value) {
|
|||
"этом"
|
||||
]).has((0, addressTextRepair_1.normalizeRussianComparableText)(value));
|
||||
}
|
||||
function isNegativeCounterpartyScopePlaceholder(value) {
|
||||
if (!value) {
|
||||
return false;
|
||||
}
|
||||
const text = compactLower((0, addressTextRepair_1.repairAddressMojibakeText)(value));
|
||||
if (!text) {
|
||||
return false;
|
||||
}
|
||||
return (/(?:без\s+(?:привязк\p{L}*|конкретн\p{L}*)\s+к\s+контрагент\p{L}*|без\s+контрагент\p{L}*)/iu.test(text) ||
|
||||
/(?:одн\p{L}*\s+организац\p{L}*\s+без\s+привязк\p{L}*)/iu.test(text) ||
|
||||
/(?:organization\s+without\s+(?:counterparty|specific\s+counterparty))/iu.test(text));
|
||||
}
|
||||
function isReferentialOrganizationPlaceholder(value) {
|
||||
if (!value) {
|
||||
return false;
|
||||
}
|
||||
if (isNegativeCounterpartyScopePlaceholder(value)) {
|
||||
return true;
|
||||
}
|
||||
return new Set([
|
||||
"эта организация",
|
||||
"этой организации",
|
||||
|
|
@ -144,12 +129,6 @@ function isGarbageSemanticAnchorCandidate(value) {
|
|||
"этими",
|
||||
"итогу",
|
||||
"итогам",
|
||||
"одной",
|
||||
"одному",
|
||||
"одним",
|
||||
"одна",
|
||||
"один",
|
||||
"одно",
|
||||
"всему",
|
||||
"всей",
|
||||
"всем",
|
||||
|
|
@ -189,8 +168,7 @@ function isGarbageSemanticAnchorCandidate(value) {
|
|||
]).has(compact)) {
|
||||
return true;
|
||||
}
|
||||
if (isNegativeCounterpartyScopePlaceholder(text) ||
|
||||
/^(?:по\s+)?(?:этим|этими)\s+данн\p{L}*$/iu.test(text) ||
|
||||
if (/^(?:по\s+)?(?:этим|этими)\s+данн\p{L}*$/iu.test(text) ||
|
||||
/^(?:и\s+)?кто\s+(?:главн\p{L}*|основн\p{L}*|крупн\p{L}*)\s+(?:клиент|покупател|поставщик|контрагент)(?:\s+в)?$/iu.test(text) ||
|
||||
/^(?:и\s+)?(?:главн\p{L}*|основн\p{L}*|крупн\p{L}*)\s+(?:клиент|покупател|поставщик|контрагент)(?:\s+в)?$/iu.test(text) ||
|
||||
/^(?:или\s+)?(?:обычн\p{L}*\s+)?(?:клиент|поставщик|покупател\p{L}*|заказчик|контрагент)(?:\s+или\s+(?:клиент|поставщик|покупател\p{L}*|заказчик|контрагент))?$/iu.test(text) ||
|
||||
|
|
@ -541,7 +519,6 @@ function collectFollowupDiscoverySeed(followupContext) {
|
|||
const loopAskedDomainFamily = toNonEmptyString(followupContext?.previous_discovery_loop_asked_domain_family);
|
||||
const loopAskedActionFamily = toNonEmptyString(followupContext?.previous_discovery_loop_asked_action_family);
|
||||
const loopUnsupportedFamily = toNonEmptyString(followupContext?.previous_discovery_loop_unsupported_family);
|
||||
const loopExplicitDateScope = toNonEmptyString(followupContext?.previous_discovery_loop_explicit_date_scope);
|
||||
const loopMetadataScopeHint = toNonEmptyString(followupContext?.previous_discovery_loop_metadata_scope_hint);
|
||||
const loopSubjectResolutionOptional = followupContext?.previous_discovery_loop_subject_resolution_optional === true;
|
||||
const previousIntent = toNonEmptyString(followupContext?.target_intent) ?? toNonEmptyString(followupContext?.previous_intent);
|
||||
|
|
@ -593,7 +570,7 @@ function collectFollowupDiscoverySeed(followupContext) {
|
|||
const loopProvidedAllTimeScope = loopProvidedAxes.includes("all_time_scope");
|
||||
const dateScope = loopProvidedAllTimeScope
|
||||
? "all_time_scope"
|
||||
: loopExplicitDateScope ?? collectDateScopeFromFilters(previousFilters) ?? collectDateScopeFromFilters(rootFilters);
|
||||
: collectDateScopeFromFilters(previousFilters) ?? collectDateScopeFromFilters(rootFilters);
|
||||
return {
|
||||
pilotScope: effectivePilotScope,
|
||||
domain: mapped.domain,
|
||||
|
|
@ -921,9 +898,6 @@ function normalizeLooseOrganizationAlias(value) {
|
|||
if (!text) {
|
||||
return null;
|
||||
}
|
||||
if (isNegativeCounterpartyScopePlaceholder(text)) {
|
||||
return null;
|
||||
}
|
||||
const normalized = text
|
||||
.replace(/^[«"'“”]+|[»"'“”]+$/gu, "")
|
||||
.replace(/\s+/g, " ")
|
||||
|
|
|
|||
|
|
@ -27,25 +27,6 @@ function createAssistantTransitionPolicy(deps) {
|
|||
return wantsFoundRows || account41Not01;
|
||||
});
|
||||
}
|
||||
function hasInventorySupplierOverlapRootFollowupSignal(userMessage, alternateMessage = null, sourceIntentHint = null, hasInventoryRootFrame = false) {
|
||||
if (!hasInventoryRootFrame ||
|
||||
(sourceIntentHint !== "inventory_on_hand_as_of_date" &&
|
||||
sourceIntentHint !== "inventory_supplier_stock_overlap_as_of_date")) {
|
||||
return false;
|
||||
}
|
||||
return [userMessage, alternateMessage]
|
||||
.filter((value) => deps.toNonEmptyString(value))
|
||||
.map((value) => normalizeFollowupText(value))
|
||||
.some((normalized) => {
|
||||
if (!normalized) {
|
||||
return false;
|
||||
}
|
||||
const hasSupplierCue = /(?:\u043f\u043e\u0441\u0442\u0430\u0432\u0449\u0438\u043a|supplier|vendor|\u0443\s+\u043a\u0430\u043a\u043e\u0433\u043e|\u0443\s+\u043a\u043e\u0433\u043e|\u043f\u043e\s+\u043a\u0430\u043a\u043e\u043c\u0443)/iu.test(normalized);
|
||||
const hasStockCue = /(?:\u043e\u0441\u0442\u0430\u0442|\u0441\u043a\u043b\u0430\u0434|\u0442\u043e\u0432\u0430\u0440|\u043f\u043e\u0437\u0438\u0446|\u043b\u0435\u0436|\u0432\u0438\u0441|\u0442\u0435\u043a\u0443\u0449)/iu.test(normalized);
|
||||
const hasPurchaseCue = /(?:\u043a\u0443\u043f\u0438\u043b|\u043a\u0443\u043f\u043b\u0435\u043d|\u043f\u043e\u043a\u0443\u043f|\u0437\u0430\u043a\u0443\u043f|\u043f\u043e\u0441\u0442\u0443\u043f\u043b|purchase|bought)/iu.test(normalized);
|
||||
return hasSupplierCue && (hasStockCue || hasPurchaseCue);
|
||||
});
|
||||
}
|
||||
function hasSamePeriodReferenceCue(...values) {
|
||||
return values
|
||||
.map((value) => normalizeFollowupText(value))
|
||||
|
|
@ -885,7 +866,6 @@ function createAssistantTransitionPolicy(deps) {
|
|||
const hasInventoryRootRestatementAlternate = deps.toNonEmptyString(alternateMessage)
|
||||
? hasInventoryRootRestatementLikeSignal(String(alternateMessage ?? ""), sourceIntentHint, Boolean(recentInventoryRootFrame))
|
||||
: false;
|
||||
const hasInventorySupplierOverlapRootFollowup = hasInventorySupplierOverlapRootFollowupSignal(userMessage, alternateMessage, sourceIntentHint, Boolean(recentInventoryRootFrame));
|
||||
const hasExplicitInventorySameDatePivotPrimary = hasExplicitInventorySameDatePivotSignal(userMessage);
|
||||
const hasExplicitInventorySameDatePivotAlternate = deps.toNonEmptyString(alternateMessage)
|
||||
? hasExplicitInventorySameDatePivotSignal(String(alternateMessage ?? ""))
|
||||
|
|
@ -902,7 +882,6 @@ function createAssistantTransitionPolicy(deps) {
|
|||
hasInventoryRootTemporalFollowupAlternate ||
|
||||
hasInventoryRootRestatementPrimary ||
|
||||
hasInventoryRootRestatementAlternate ||
|
||||
hasInventorySupplierOverlapRootFollowup ||
|
||||
inventoryPurchaseDateVatBridge ||
|
||||
explicitSummaryBundleReuseSignal ||
|
||||
Boolean(debtRoleSwapIntent) ||
|
||||
|
|
@ -928,7 +907,6 @@ function createAssistantTransitionPolicy(deps) {
|
|||
hasInventoryRootTemporalFollowupAlternate ||
|
||||
hasInventoryRootRestatementPrimary ||
|
||||
hasInventoryRootRestatementAlternate ||
|
||||
hasInventorySupplierOverlapRootFollowup ||
|
||||
inventoryPurchaseDateVatBridge ||
|
||||
explicitSummaryBundleReuseSignal ||
|
||||
Boolean(debtRoleSwapIntent) ||
|
||||
|
|
@ -960,7 +938,6 @@ function createAssistantTransitionPolicy(deps) {
|
|||
!hasInventoryRootTemporalFollowupAlternate &&
|
||||
!hasInventoryRootRestatementPrimary &&
|
||||
!hasInventoryRootRestatementAlternate &&
|
||||
!hasInventorySupplierOverlapRootFollowup &&
|
||||
!shortValueFlowRetargetPrimary &&
|
||||
!shortValueFlowRetargetAlternate &&
|
||||
!selectedCounterpartyDocumentFollowupSignal &&
|
||||
|
|
@ -978,7 +955,6 @@ function createAssistantTransitionPolicy(deps) {
|
|||
!hasInventoryRootTemporalFollowupAlternate &&
|
||||
!hasInventoryRootRestatementPrimary &&
|
||||
!hasInventoryRootRestatementAlternate &&
|
||||
!hasInventorySupplierOverlapRootFollowup &&
|
||||
!shortValueFlowRetargetPrimary &&
|
||||
!shortValueFlowRetargetAlternate &&
|
||||
!selectedCounterpartyDocumentFollowupSignal &&
|
||||
|
|
@ -1011,7 +987,6 @@ function createAssistantTransitionPolicy(deps) {
|
|||
const sourceDiscoveryLoopAskedDomainFamily = (0, assistantContinuityPolicy_1.readAssistantMcpDiscoveryLoopAskedDomainFamily)(carryoverSourceDebug, deps.toNonEmptyString);
|
||||
const sourceDiscoveryLoopAskedActionFamily = (0, assistantContinuityPolicy_1.readAssistantMcpDiscoveryLoopAskedActionFamily)(carryoverSourceDebug, deps.toNonEmptyString);
|
||||
const sourceDiscoveryLoopUnsupportedFamily = (0, assistantContinuityPolicy_1.readAssistantMcpDiscoveryLoopUnsupportedFamily)(carryoverSourceDebug, deps.toNonEmptyString);
|
||||
const sourceDiscoveryLoopExplicitDateScope = (0, assistantContinuityPolicy_1.readAssistantMcpDiscoveryLoopExplicitDateScope)(carryoverSourceDebug, deps.toNonEmptyString);
|
||||
const sourceDiscoveryLoopMetadataScopeHint = (0, assistantContinuityPolicy_1.readAssistantMcpDiscoveryLoopMetadataScopeHint)(carryoverSourceDebug, deps.toNonEmptyString);
|
||||
const sourceDiscoveryLoopSubjectResolutionOptional = (0, assistantContinuityPolicy_1.readAssistantMcpDiscoveryLoopSubjectResolutionOptional)(carryoverSourceDebug);
|
||||
const sourceDiscoveryRankingNeed = (0, assistantContinuityPolicy_1.readAssistantMcpDiscoveryRankingNeed)(carryoverSourceDebug, deps.toNonEmptyString);
|
||||
|
|
@ -1062,7 +1037,6 @@ function createAssistantTransitionPolicy(deps) {
|
|||
!hasInventoryRootTemporalFollowupAlternate &&
|
||||
!hasInventoryRootRestatementPrimary &&
|
||||
!hasInventoryRootRestatementAlternate &&
|
||||
!hasInventorySupplierOverlapRootFollowup &&
|
||||
!inventoryShortFollowupPrimary &&
|
||||
!inventoryShortFollowupAlternate &&
|
||||
!businessOverviewBoundaryFollowupPrimary &&
|
||||
|
|
@ -1130,7 +1104,6 @@ function createAssistantTransitionPolicy(deps) {
|
|||
inventoryMarginRankingFollowup ||
|
||||
explicitSummaryBundleReuseSignal ||
|
||||
hasInventoryRootTemporalFollowupPrimary ||
|
||||
hasInventorySupplierOverlapRootFollowup ||
|
||||
mcpDiscoveryOrganizationClarificationContinuation;
|
||||
hasAlternateFollowupSignal = deps.toNonEmptyString(alternateMessage)
|
||||
? deps.hasAddressFollowupContextSignal(alternateMessage) ||
|
||||
|
|
@ -1143,7 +1116,6 @@ function createAssistantTransitionPolicy(deps) {
|
|||
inventoryMarginRankingFollowup ||
|
||||
explicitSummaryBundleReuseSignal ||
|
||||
hasInventoryRootTemporalFollowupAlternate ||
|
||||
hasInventorySupplierOverlapRootFollowup ||
|
||||
mcpDiscoveryOrganizationClarificationContinuation
|
||||
: false;
|
||||
hasStrongFollowupReference =
|
||||
|
|
@ -1157,7 +1129,6 @@ function createAssistantTransitionPolicy(deps) {
|
|||
inventoryShortFollowupAlternate ||
|
||||
hasInventoryRootTemporalFollowupPrimary ||
|
||||
hasInventoryRootTemporalFollowupAlternate ||
|
||||
hasInventorySupplierOverlapRootFollowup ||
|
||||
inventoryPurchaseDateVatBridge ||
|
||||
explicitSummaryBundleReuseSignal ||
|
||||
Boolean(debtRoleSwapIntent) ||
|
||||
|
|
@ -1290,8 +1261,7 @@ function createAssistantTransitionPolicy(deps) {
|
|||
}
|
||||
const predecomposeIntent = deps.toNonEmptyString(llmPreDecomposeMeta?.predecomposeContract?.intent);
|
||||
const carryoverTargetIntentPreview = explicitIntent ?? predecomposeIntent ?? previousIntent;
|
||||
if (!selectedCounterpartyDocumentFollowupSignal &&
|
||||
shouldClearStaleDateScopeForCounterpartyDocsRoot(carryoverTargetIntentPreview, sourceIntentHint, userMessage, alternateMessage)) {
|
||||
if (shouldClearStaleDateScopeForCounterpartyDocsRoot(carryoverTargetIntentPreview, sourceIntentHint, userMessage, alternateMessage)) {
|
||||
delete previousFilters.as_of_date;
|
||||
delete previousFilters.period_from;
|
||||
delete previousFilters.period_to;
|
||||
|
|
@ -1310,7 +1280,6 @@ function createAssistantTransitionPolicy(deps) {
|
|||
inventoryShortFollowupAlternate ||
|
||||
hasInventoryRootTemporalFollowupPrimary ||
|
||||
hasInventoryRootTemporalFollowupAlternate ||
|
||||
hasInventorySupplierOverlapRootFollowup ||
|
||||
hasInventoryRootRestatementPrimary ||
|
||||
hasInventoryRootRestatementAlternate ||
|
||||
hasSelectedObjectInventorySignalPrimary ||
|
||||
|
|
@ -1347,7 +1316,6 @@ function createAssistantTransitionPolicy(deps) {
|
|||
previous_discovery_loop_asked_domain_family: sourceDiscoveryLoopAskedDomainFamily ?? undefined,
|
||||
previous_discovery_loop_asked_action_family: sourceDiscoveryLoopAskedActionFamily ?? undefined,
|
||||
previous_discovery_loop_unsupported_family: sourceDiscoveryLoopUnsupportedFamily ?? undefined,
|
||||
previous_discovery_loop_explicit_date_scope: sourceDiscoveryLoopExplicitDateScope ?? undefined,
|
||||
previous_discovery_loop_metadata_scope_hint: sourceDiscoveryLoopMetadataScopeHint ?? undefined,
|
||||
previous_discovery_loop_subject_resolution_optional: sourceDiscoveryLoopSubjectResolutionOptional || undefined,
|
||||
previous_discovery_ranking_need: sourceDiscoveryRankingNeed ?? undefined,
|
||||
|
|
|
|||
|
|
@ -1583,8 +1583,6 @@ function extractInventorySupplierAnchor(text: string): string | undefined {
|
|||
/\s+(?:сейчас|на\s+склад(?:е|у|ом)?|на\s+дату|по\s+состоянию\s+на\s+дату|котор(?:ый|ые|ых)|куплен(?:ы|а|о)?|были|был|лежат|лежит|еще|ещё|организац\w*|компани\w*).*$/iu,
|
||||
""
|
||||
)
|
||||
.replace(/\s+к\s+покупател\p{L}*[\s\S]*$/iu, "")
|
||||
.replace(/\s+через\s+товар\p{L}*[\s\S]*$/iu, "")
|
||||
.replace(/\s*(?:->|=>|→)\s*(?:товар|позици|номенклатур|item|product|sku)[\s\S]*$/iu, "")
|
||||
);
|
||||
if (
|
||||
|
|
|
|||
|
|
@ -1939,48 +1939,6 @@ function isQuestionFragmentPartyAnchor(value: string | null | undefined): boolea
|
|||
/(?:на складе|нам должен|кому мы должны|по этой компании|по этой же компании|по ней)/iu.test(normalized);
|
||||
}
|
||||
|
||||
function extractInventoryDocumentaryChainBuyerAnchor(value: string | null | undefined): string | null {
|
||||
const source = String(value ?? "");
|
||||
const patterns = [
|
||||
/(?:->|=>|→)\s*(?:покупател\p{L}*|buyer)\s+([^\r\n?]+?)(?=$|[?])/iu,
|
||||
/(?:^|[\s,.;:!?])(?:к\s+покупател\p{L}*|покупател\p{L}*|buyer)\s+(.+?)(?=\s+через\s+(?:товар|item|product|sku)\b|$|[?])/iu
|
||||
];
|
||||
for (const pattern of patterns) {
|
||||
const match = source.match(pattern);
|
||||
const candidate = String(match?.[1] ?? "")
|
||||
.replace(/\s*(?:->|=>|→).+$/u, "")
|
||||
.replace(/\s+/g, " ")
|
||||
.trim();
|
||||
if (candidate) {
|
||||
return candidate;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function trimInventoryDocumentaryChainSupplierAnchor(value: string | null | undefined): string | null {
|
||||
const source = String(value ?? "").trim();
|
||||
if (!source) {
|
||||
return null;
|
||||
}
|
||||
const candidate = source
|
||||
.replace(/\s+к\s+покупател\p{L}*[\s\S]*$/iu, "")
|
||||
.replace(/\s+через\s+товар\p{L}*[\s\S]*$/iu, "")
|
||||
.replace(/\s*(?:->|=>|→)\s*(?:товар|позици|номенклатур|item|product|sku)[\s\S]*$/iu, "")
|
||||
.replace(/\s+/g, " ")
|
||||
.trim();
|
||||
return candidate && candidate !== source ? candidate : null;
|
||||
}
|
||||
|
||||
function normalizeInventoryChainOrganizationFilterValue(value: string | null | undefined): string | null {
|
||||
const normalized = normalizeOrganizationScopeValue(value);
|
||||
const cleaned = String(normalized ?? "")
|
||||
.replace(/[\\"]/g, " ")
|
||||
.replace(/\s+/g, " ")
|
||||
.trim();
|
||||
return cleaned || null;
|
||||
}
|
||||
|
||||
function applyPreExecutionOrganizationScopeGrounding(input: {
|
||||
userMessage: string;
|
||||
filters: AddressFilterSet;
|
||||
|
|
@ -3758,66 +3716,8 @@ export class AddressQueryService {
|
|||
const knownOrganizations = mergeKnownOrganizations(options.knownOrganizations ?? []);
|
||||
const activeOrganization = normalizeOrganizationScopeValue(options.activeOrganization ?? null);
|
||||
const previousOrganizationFromContext = normalizeOrganizationScopeValue(followupContext?.previous_filters?.organization ?? null);
|
||||
let chainCounterpartyAnchor = toNonEmptyFilterValue(filters.extracted_filters.counterparty);
|
||||
let chainOrganizationAnchor = toNonEmptyFilterValue(filters.extracted_filters.organization);
|
||||
if (intent.intent === "inventory_purchase_to_sale_chain") {
|
||||
const trimmedChainCounterparty = trimInventoryDocumentaryChainSupplierAnchor(chainCounterpartyAnchor);
|
||||
if (trimmedChainCounterparty) {
|
||||
filters.extracted_filters.counterparty = trimmedChainCounterparty;
|
||||
chainCounterpartyAnchor = trimmedChainCounterparty;
|
||||
if (!filters.warnings.includes("inventory_chain_supplier_anchor_trimmed_for_verification")) {
|
||||
filters.warnings.push("inventory_chain_supplier_anchor_trimmed_for_verification");
|
||||
}
|
||||
if (!baseReasons.includes("inventory_chain_supplier_anchor_trimmed_for_verification")) {
|
||||
baseReasons.push("inventory_chain_supplier_anchor_trimmed_for_verification");
|
||||
}
|
||||
}
|
||||
const buyerAnchor = extractInventoryDocumentaryChainBuyerAnchor(options.rawUserMessage ?? userMessage);
|
||||
const organizationLooksLikeBuyer =
|
||||
Boolean(buyerAnchor) &&
|
||||
Boolean(chainOrganizationAnchor) &&
|
||||
sameOrganizationEntityReference(chainOrganizationAnchor, buyerAnchor);
|
||||
const trustedOrganization =
|
||||
knownOrganizations.length === 1
|
||||
? knownOrganizations[0]
|
||||
: previousOrganizationFromContext && !sameOrganizationEntityReference(previousOrganizationFromContext, buyerAnchor)
|
||||
? previousOrganizationFromContext
|
||||
: null;
|
||||
if (
|
||||
trustedOrganization &&
|
||||
(!chainOrganizationAnchor || organizationLooksLikeBuyer) &&
|
||||
!sameOrganizationEntityReference(trustedOrganization, chainCounterpartyAnchor)
|
||||
) {
|
||||
const restoredOrganizationForFilter =
|
||||
normalizeInventoryChainOrganizationFilterValue(trustedOrganization) ?? trustedOrganization;
|
||||
filters.extracted_filters.organization = restoredOrganizationForFilter;
|
||||
chainOrganizationAnchor = restoredOrganizationForFilter;
|
||||
const reason = knownOrganizations.length === 1
|
||||
? "organization_restored_from_known_scope_for_inventory_chain"
|
||||
: "organization_restored_from_inventory_chain_context";
|
||||
if (!filters.warnings.includes(reason)) {
|
||||
filters.warnings.push(reason);
|
||||
}
|
||||
if (!baseReasons.includes(reason)) {
|
||||
baseReasons.push(reason);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (intent.intent === "inventory_purchase_to_sale_chain") {
|
||||
const normalizedChainOrganization = normalizeInventoryChainOrganizationFilterValue(
|
||||
filters.extracted_filters.organization
|
||||
);
|
||||
if (normalizedChainOrganization && normalizedChainOrganization !== filters.extracted_filters.organization) {
|
||||
filters.extracted_filters.organization = normalizedChainOrganization;
|
||||
chainOrganizationAnchor = normalizedChainOrganization;
|
||||
if (!filters.warnings.includes("organization_scope_normalized_for_inventory_chain")) {
|
||||
filters.warnings.push("organization_scope_normalized_for_inventory_chain");
|
||||
}
|
||||
if (!baseReasons.includes("organization_scope_normalized_for_inventory_chain")) {
|
||||
baseReasons.push("organization_scope_normalized_for_inventory_chain");
|
||||
}
|
||||
}
|
||||
}
|
||||
const chainCounterpartyAnchor = toNonEmptyFilterValue(filters.extracted_filters.counterparty);
|
||||
const chainOrganizationAnchor = toNonEmptyFilterValue(filters.extracted_filters.organization);
|
||||
if (
|
||||
intent.intent === "inventory_purchase_to_sale_chain" &&
|
||||
chainCounterpartyAnchor &&
|
||||
|
|
@ -3825,9 +3725,7 @@ export class AddressQueryService {
|
|||
sameOrganizationEntityReference(chainOrganizationAnchor, chainCounterpartyAnchor)
|
||||
) {
|
||||
delete filters.extracted_filters.organization;
|
||||
const restoredOrganization = normalizeInventoryChainOrganizationFilterValue(
|
||||
activeOrganization ?? previousOrganizationFromContext
|
||||
);
|
||||
const restoredOrganization = activeOrganization ?? previousOrganizationFromContext;
|
||||
if (restoredOrganization && !sameOrganizationEntityReference(restoredOrganization, chainCounterpartyAnchor)) {
|
||||
filters.extracted_filters.organization = restoredOrganization;
|
||||
if (!filters.warnings.includes("organization_restored_from_inventory_chain_context")) {
|
||||
|
|
|
|||
|
|
@ -48,8 +48,6 @@ interface InventoryTraceSummary {
|
|||
}
|
||||
|
||||
const INVENTORY_TRACE_EVIDENCE_ROW_LIMIT = 3;
|
||||
const INVENTORY_SUPPLIER_ATTRIBUTION_NEXT_ACTION =
|
||||
"Следующий шаг: чтобы сузить доказательство до партии, проверьте партионный/серийный учет или детализацию по выбранной номенклатуре, поступлению и последующему движению.";
|
||||
|
||||
interface InventoryAgingByItemAggregate {
|
||||
item: string;
|
||||
|
|
@ -714,8 +712,7 @@ export function composeInventoryReply(
|
|||
`Поставщиков, выделенных в остальных операциях: ${deps.formatNumberWithDots(observedCounterparties.length)}.`
|
||||
]);
|
||||
appendInventoryBulletSection(lines, "Ограничения:", [
|
||||
"Без партионного учета это проверка доступного закупочного следа по складскому срезу, а не юридическое доказательство владельца каждой партии.",
|
||||
INVENTORY_SUPPLIER_ATTRIBUTION_NEXT_ACTION
|
||||
"Без партионного учета это проверка доступного закупочного следа по складскому срезу, а не юридическое доказательство владельца каждой партии."
|
||||
]);
|
||||
if (unresolvedRows.length > 0) {
|
||||
appendInventorySection(
|
||||
|
|
@ -747,7 +744,7 @@ export function composeInventoryReply(
|
|||
const requestedSupplierLabel = summary.counterparties[0] ?? String(options.counterpartyHint ?? "").trim();
|
||||
const directAnswerLine =
|
||||
supplierScopedItems.length > 0
|
||||
? `По поставщику ${requestedSupplierLabel} в складском срезе ${warehouseLabel} на ${deps.formatDateRu(asOfDate)} подтверждено позиций: ${deps.formatNumberWithDots(supplierScopedItems.length)}.`
|
||||
? `По поставщику ${requestedSupplierLabel} в складском срезе ${warehouseLabel} на ${deps.formatDateRu(asOfDate)} подтверждены позиции: ${inventoryItemLeadList(supplierScopedItems)}.`
|
||||
: `По поставщику ${requestedSupplierLabel} в складском срезе ${warehouseLabel} на ${deps.formatDateRu(asOfDate)} позиции не подтверждены в доступном закупочном следе.`;
|
||||
const lines: string[] = [directAnswerLine];
|
||||
if (supplierScopedItems.length > 0) {
|
||||
|
|
@ -760,17 +757,29 @@ export function composeInventoryReply(
|
|||
lines.push(`- Показаны первые 8 из ${deps.formatNumberWithDots(supplierScopedItems.length)} позиций.`);
|
||||
}
|
||||
}
|
||||
lines.push(
|
||||
"",
|
||||
`Проверка: срез ${deps.formatDateRu(asOfDate)}; закупочный след ${purchasePeriodLabel}; документов/операций: ${deps.formatNumberWithDots(summary.documents.length)} / ${deps.formatNumberWithDots(purchaseRows.length)}.`
|
||||
);
|
||||
appendInventoryBulletSection(lines, "Что проверили:", [
|
||||
`Дата среза: ${deps.formatDateRu(asOfDate)}.`,
|
||||
`Период найденного закупочного следа: ${purchasePeriodLabel}.`,
|
||||
`Закупочных документов / операций в выборке: ${deps.formatNumberWithDots(summary.documents.length)} / ${deps.formatNumberWithDots(purchaseRows.length)}.`
|
||||
]);
|
||||
appendInventoryBulletSection(lines, "Ограничения:", [
|
||||
"Без партионного учета этот ответ показывает закупочный след текущего остатка, но не доказывает владельца каждой конкретной партии."
|
||||
]);
|
||||
if (observedCounterparties.length > 0) {
|
||||
lines.push(`Поставщик в найденном следе: ${inventoryPartyLeadList(observedCounterparties)}.`);
|
||||
lines.push(`- Найденные поставщики: ${inventoryPartyLeadList(observedCounterparties)}.`);
|
||||
}
|
||||
if (purchaseRows.length > 0) {
|
||||
appendInventorySection(
|
||||
lines,
|
||||
"Опорные документы:",
|
||||
deps.formatInventoryTraceRows(purchaseRows, INVENTORY_TRACE_EVIDENCE_ROW_LIMIT)
|
||||
);
|
||||
if (purchaseRows.length > INVENTORY_TRACE_EVIDENCE_ROW_LIMIT) {
|
||||
lines.push(
|
||||
`- Показаны первые ${INVENTORY_TRACE_EVIDENCE_ROW_LIMIT} из ${deps.formatNumberWithDots(purchaseRows.length)} найденных строк; полный след остается в подтвержденном срезе.`
|
||||
);
|
||||
}
|
||||
}
|
||||
lines.push(
|
||||
"Ограничение: без партионного учета это закупочный след остатка, а не доказательство владельца каждой конкретной партии.",
|
||||
INVENTORY_SUPPLIER_ATTRIBUTION_NEXT_ACTION
|
||||
);
|
||||
return buildFactualSummaryReply(
|
||||
lines,
|
||||
buildConfirmedBalanceSemantics(
|
||||
|
|
@ -799,8 +808,7 @@ export function composeInventoryReply(
|
|||
`Закупочных документов / операций в выборке: ${deps.formatNumberWithDots(summary.documents.length)} / ${deps.formatNumberWithDots(purchaseRows.length)}.`
|
||||
]);
|
||||
appendInventoryBulletSection(lines, "Ограничения:", [
|
||||
"Без партионного учета этот ответ показывает закупочный след текущего остатка, но не доказывает владельца каждой конкретной партии.",
|
||||
INVENTORY_SUPPLIER_ATTRIBUTION_NEXT_ACTION
|
||||
"Без партионного учета этот ответ показывает закупочный след текущего остатка, но не доказывает владельца каждой конкретной партии."
|
||||
]);
|
||||
if (observedCounterparties.length > 0) {
|
||||
lines.push(`- Найденные поставщики: ${inventoryPartyLeadList(observedCounterparties)}.`);
|
||||
|
|
|
|||
|
|
@ -145,15 +145,7 @@ export function applyAddressLlmSemanticHintsToExtraction(
|
|||
semanticFrame.selected_object_scope_detected = true;
|
||||
}
|
||||
|
||||
const protectsInventoryDocumentaryChainSupplierAnchor = warnings.includes(
|
||||
"supplier_anchor_derived_for_inventory_documentary_chain"
|
||||
);
|
||||
|
||||
if (
|
||||
semanticHints.scope_target_kind === "organization" &&
|
||||
scopeTargetText &&
|
||||
!protectsInventoryDocumentaryChainSupplierAnchor
|
||||
) {
|
||||
if (semanticHints.scope_target_kind === "organization" && scopeTargetText) {
|
||||
extractedFilters.organization = scopeTargetText;
|
||||
pushWarning(warnings, "organization_from_llm_semantics");
|
||||
if (toNonEmptyString(extractedFilters.warehouse)) {
|
||||
|
|
@ -163,8 +155,6 @@ export function applyAddressLlmSemanticHintsToExtraction(
|
|||
semanticFrame.scope_kind = "explicit_anchor";
|
||||
semanticFrame.anchor_kind = "organization";
|
||||
semanticFrame.anchor_value = scopeTargetText;
|
||||
} else if (semanticHints.scope_target_kind === "organization" && scopeTargetText) {
|
||||
pushWarning(warnings, "organization_llm_semantics_ignored_for_inventory_documentary_chain");
|
||||
}
|
||||
|
||||
if (semanticHints.scope_target_kind === "warehouse" && scopeTargetText) {
|
||||
|
|
|
|||
|
|
@ -765,7 +765,6 @@ function shouldPreferRawFollowupMessage(
|
|||
const previousIntent = toNonEmptyString(followupContext?.previous_intent);
|
||||
const rootIntent = toNonEmptyString(followupContext?.root_intent);
|
||||
const previousAnchorType = toNonEmptyString(followupContext?.previous_anchor_type);
|
||||
const previousFilters = toRecordObject(followupContext?.previous_filters);
|
||||
const hasReferentialDocumentExclusionFollowupCue = /(?:\u043a\u0440\u043e\u043c\u0435|\u043f\u043e\u043c\u0438\u043c\u043e)\s+(?:\u044d\u0442\u043e\u0433\u043e|\u044d\u0442\u043e\u0439|\u044d\u0442\u043e\u0442|\u044d\u0442\u0443|\u044d\u0442\u0438\u0445)(?:\s+(?:\u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442\u0430|\u0434\u043e\u0433\u043e\u0432\u043e\u0440\u0430|\u043a\u043e\u043d\u0442\u0440\u0430\u0433\u0435\u043d\u0442\u0430))?/iu.test(
|
||||
rawMessage
|
||||
);
|
||||
|
|
@ -782,14 +781,12 @@ function shouldPreferRawFollowupMessage(
|
|||
[rawMessage, canonicalMessage].some((message) => hasInventoryMarginRankingAccountCorrectionCue(message));
|
||||
const hasDocumentCarryover =
|
||||
previousIntent === "list_documents_by_counterparty" || previousIntent === "list_documents_by_contract";
|
||||
const hasCounterpartyCarryover =
|
||||
previousAnchorType === "counterparty" || Boolean(toNonEmptyString(previousFilters?.counterparty));
|
||||
|
||||
if (mode === "unsupported" && intent === "unknown") {
|
||||
return true;
|
||||
}
|
||||
|
||||
if ((hasDocumentCarryover || hasCounterpartyCarryover) && hasReferentialDocumentExclusionFollowupCue) {
|
||||
if (hasDocumentCarryover && hasReferentialDocumentExclusionFollowupCue) {
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -330,17 +330,6 @@ export function readAssistantMcpDiscoveryLoopUnsupportedFamily(
|
|||
return toNonEmptyString(readAssistantMcpDiscoveryLoopState(debug)?.unsupported_but_understood_family);
|
||||
}
|
||||
|
||||
export function readAssistantMcpDiscoveryLoopExplicitDateScope(
|
||||
debug: Record<string, unknown> | null,
|
||||
toNonEmptyString: (value: unknown) => string | null = fallbackToNonEmptyString
|
||||
): string | null {
|
||||
return (
|
||||
toNonEmptyString(readAssistantMcpDiscoveryLoopState(debug)?.explicit_date_scope) ??
|
||||
toNonEmptyString(readAssistantMcpDiscoveryTurnMeaning(debug)?.explicit_date_scope) ??
|
||||
toNonEmptyString(readAssistantMcpDiscoveryDataNeedGraph(debug)?.explicit_date_scope)
|
||||
);
|
||||
}
|
||||
|
||||
export function readAssistantMcpDiscoveryLoopMetadataScopeHint(
|
||||
debug: Record<string, unknown> | null,
|
||||
toNonEmptyString: (value: unknown) => string | null = fallbackToNonEmptyString
|
||||
|
|
|
|||
|
|
@ -293,7 +293,7 @@ function valueFlowZeroResultConfirmedLine(pilot: AssistantMcpDiscoveryPilotExecu
|
|||
const period = explicitDateScope(pilot);
|
||||
const organizationPart = organization ? ` по организации ${organization}` : "";
|
||||
const periodPart = period ? ` за период ${period}` : " в проверенном окне";
|
||||
return `В проверенном срезе 1С по контрагенту ${counterparty}${organizationPart}${periodPart}: 0 руб.; ${valueFlowDirectionLabelRu(
|
||||
return `В проверенном срезе 1С по контрагенту ${counterparty}${organizationPart}${periodPart} ${valueFlowDirectionLabelRu(
|
||||
pilot
|
||||
)} не найдено.`;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -193,21 +193,6 @@ function hasBusinessOverviewDirectMoneyAnswerHint(input: {
|
|||
);
|
||||
}
|
||||
|
||||
function hasIncomingVsOutgoingComparisonHint(rawUtterance: string): boolean {
|
||||
if (!rawUtterance) {
|
||||
return false;
|
||||
}
|
||||
const mentionsBothDirections =
|
||||
/(?:\u0432\u0445\u043e\u0434\p{L}*[\s\S]{0,100}\u0438(?:\u043b\u0438)?\s+\u0438\u0441\u0445\u043e\u0434\p{L}*|\u0438\u0441\u0445\u043e\u0434\p{L}*[\s\S]{0,100}\u0438(?:\u043b\u0438)?\s+\u0432\u0445\u043e\u0434\p{L}*|incoming[\s\S]{0,100}outgoing|outgoing[\s\S]{0,100}incoming)/iu.test(
|
||||
rawUtterance
|
||||
);
|
||||
const asksComparison =
|
||||
/(?:\u0447\u0442\u043e[\s\S]{0,40}\u0431\u043e\u043b\p{L}*|\u0431\u043e\u043b\p{L}*[\s\S]{0,40}\u0447\u0442\u043e|\u043f\u0440\u0435\u0432\u044b\p{L}*|\u043f\u043b\u044e\u0441|\u043c\u0438\u043d\u0443\u0441|\u0441\u0440\u0430\u0432\u043d\p{L}*|which[\s\S]{0,40}(?:more|larger|higher|bigger)|what[\s\S]{0,40}(?:more|larger|higher|bigger)|(?:more|larger|higher|bigger)[\s\S]{0,40}(?:incoming|outgoing))/iu.test(
|
||||
rawUtterance
|
||||
);
|
||||
return mentionsBothDirections && asksComparison;
|
||||
}
|
||||
|
||||
function timeScopeNeedFor(input: {
|
||||
family: string | null;
|
||||
explicitDateScope: string | null;
|
||||
|
|
@ -572,7 +557,7 @@ export function buildAssistantMcpDiscoveryDataNeedGraph(
|
|||
const explicitOrganizationScope = toNonEmptyString(turnMeaning?.explicit_organization_scope);
|
||||
const metadataScopeHint = toNonEmptyString(turnMeaning?.metadata_scope_hint);
|
||||
const subjectResolutionOptional = turnMeaning?.subject_resolution_optional === true;
|
||||
const rawSubjectCandidates = (turnMeaning?.explicit_entity_candidates ?? [])
|
||||
const subjectCandidates = (turnMeaning?.explicit_entity_candidates ?? [])
|
||||
.map((item) => toNonEmptyString(item))
|
||||
.filter((item): item is string => Boolean(item));
|
||||
const initialBusinessFactFamily = businessFactFamilyFor({
|
||||
|
|
@ -587,31 +572,10 @@ export function buildAssistantMcpDiscoveryDataNeedGraph(
|
|||
initialBusinessFactFamily === "business_overview" &&
|
||||
Boolean(oneSidedValueAction) &&
|
||||
!detailedBusinessOverviewMoneyBreakdownHint;
|
||||
const bidirectionalBusinessOverviewValueFlowOverride =
|
||||
initialBusinessFactFamily === "business_overview" && hasIncomingVsOutgoingComparisonHint(rawQuestionSignal);
|
||||
const oneSidedValueFlowActionOverride =
|
||||
initialBusinessFactFamily === "value_flow" &&
|
||||
Boolean(oneSidedValueAction) &&
|
||||
(rawAction === "counterparty_value_or_turnover" || rawAction === "counterparty_value");
|
||||
const action =
|
||||
bidirectionalBusinessOverviewValueFlowOverride
|
||||
? "net_value_flow"
|
||||
: oneSidedBusinessOverviewValueFlowOverride || oneSidedValueFlowActionOverride
|
||||
? oneSidedValueAction!
|
||||
: rawAction;
|
||||
const businessFactFamily =
|
||||
oneSidedBusinessOverviewValueFlowOverride || bidirectionalBusinessOverviewValueFlowOverride
|
||||
? "value_flow"
|
||||
: initialBusinessFactFamily;
|
||||
const action = oneSidedBusinessOverviewValueFlowOverride ? oneSidedValueAction! : rawAction;
|
||||
const businessFactFamily = oneSidedBusinessOverviewValueFlowOverride ? "value_flow" : initialBusinessFactFamily;
|
||||
const aggregationNeed = aggregationNeedFor(aggregationAxis);
|
||||
const comparisonNeed = comparisonNeedFor(action);
|
||||
const organizationScopedValueComparison =
|
||||
businessFactFamily === "value_flow" &&
|
||||
comparisonNeed === "incoming_vs_outgoing" &&
|
||||
Boolean(explicitOrganizationScope) &&
|
||||
hasIncomingVsOutgoingComparisonHint(rawQuestionSignal);
|
||||
const subjectCandidates = organizationScopedValueComparison ? [] : rawSubjectCandidates;
|
||||
const effectiveMetadataScopeHint = organizationScopedValueComparison ? null : metadataScopeHint;
|
||||
const allTimeScopeHint = hasAllTimeScopeHint(rawUtterance);
|
||||
const subjectScopedBidirectionalAllTime =
|
||||
businessFactFamily === "value_flow" &&
|
||||
|
|
@ -741,15 +705,6 @@ export function buildAssistantMcpDiscoveryDataNeedGraph(
|
|||
if (oneSidedBusinessOverviewValueFlowOverride) {
|
||||
pushReason(reasonCodes, "data_need_graph_business_overview_one_sided_money_total_routed_to_value_flow");
|
||||
}
|
||||
if (bidirectionalBusinessOverviewValueFlowOverride) {
|
||||
pushReason(reasonCodes, "data_need_graph_business_overview_bidirectional_money_comparison_routed_to_value_flow");
|
||||
}
|
||||
if (organizationScopedValueComparison) {
|
||||
pushReason(reasonCodes, "data_need_graph_organization_scoped_value_comparison_ignores_stale_subject");
|
||||
}
|
||||
if (oneSidedValueFlowActionOverride) {
|
||||
pushReason(reasonCodes, "data_need_graph_value_flow_one_sided_money_total_action_normalized");
|
||||
}
|
||||
if (clarificationGaps.includes("organization")) {
|
||||
pushReason(reasonCodes, "data_need_graph_open_scope_total_needs_organization");
|
||||
}
|
||||
|
|
@ -761,7 +716,7 @@ export function buildAssistantMcpDiscoveryDataNeedGraph(
|
|||
schema_version: ASSISTANT_MCP_DISCOVERY_DATA_NEED_GRAPH_SCHEMA_VERSION,
|
||||
policy_owner: "assistantMcpDiscoveryDataNeedGraph",
|
||||
subject_candidates: subjectCandidates,
|
||||
metadata_scope_hint: effectiveMetadataScopeHint,
|
||||
metadata_scope_hint: metadataScopeHint,
|
||||
subject_resolution_optional: subjectResolutionOptional || undefined,
|
||||
business_fact_family: businessFactFamily,
|
||||
action_family: toNonEmptyString(action),
|
||||
|
|
|
|||
|
|
@ -752,10 +752,7 @@ function scoreExplicitEntityCandidate(raw: string, cleaned: string, index: numbe
|
|||
}
|
||||
|
||||
function firstEntityCandidate(planner: AssistantMcpDiscoveryPlannerContract): string | null {
|
||||
const graphCandidates = planner.data_need_graph?.subject_candidates;
|
||||
const candidates = Array.isArray(graphCandidates)
|
||||
? graphCandidates
|
||||
: planner.discovery_plan.turn_meaning_ref?.explicit_entity_candidates ?? [];
|
||||
const candidates = planner.discovery_plan.turn_meaning_ref?.explicit_entity_candidates ?? [];
|
||||
let best: { text: string; score: number } | null = null;
|
||||
for (let index = 0; index < candidates.length; index += 1) {
|
||||
const candidate = candidates[index];
|
||||
|
|
@ -1299,33 +1296,10 @@ function isEntityResolutionPilotEligible(planner: AssistantMcpDiscoveryPlannerCo
|
|||
);
|
||||
}
|
||||
|
||||
function normalizeMetadataScopeHint(value: unknown): string | null {
|
||||
const text = toNonEmptyString(value);
|
||||
if (!text) {
|
||||
return null;
|
||||
}
|
||||
const normalized = text
|
||||
.replace(/[«»"']/gu, "")
|
||||
.replace(/\s+/g, " ")
|
||||
.trim();
|
||||
const comparable = normalized.toLowerCase().replace(/ё/g, "е");
|
||||
if (/^контрагент(?:ам|ами|ах|ов|у|а|ом|е|ы)?$/iu.test(comparable)) {
|
||||
return "контрагент";
|
||||
}
|
||||
if (/^(?:справочник\.)?контрагенты$/iu.test(comparable)) {
|
||||
return "контрагент";
|
||||
}
|
||||
return normalized;
|
||||
}
|
||||
|
||||
function metadataScopeForPlanner(planner: AssistantMcpDiscoveryPlannerContract): string | null {
|
||||
const metadataScopeHint = normalizeMetadataScopeHint(planner.discovery_plan.turn_meaning_ref?.metadata_scope_hint);
|
||||
if (metadataScopeHint) {
|
||||
return metadataScopeHint;
|
||||
}
|
||||
const entityCandidate = firstEntityCandidate(planner);
|
||||
if (entityCandidate) {
|
||||
return normalizeMetadataScopeHint(entityCandidate) ?? entityCandidate;
|
||||
return entityCandidate;
|
||||
}
|
||||
if (planner.selected_chain_id === "catalog_drilldown") {
|
||||
const surface = planner.metadata_surface_ref;
|
||||
|
|
@ -1401,9 +1375,7 @@ function valueFlowPilotProfile(planner: AssistantMcpDiscoveryPlannerContract): V
|
|||
const meaning = planner.discovery_plan.turn_meaning_ref;
|
||||
const action = String(meaning?.asked_action_family ?? "").toLowerCase();
|
||||
const unsupported = String(meaning?.unsupported_but_understood_family ?? "").toLowerCase();
|
||||
const graphAction = String(planner.data_need_graph?.action_family ?? "").toLowerCase();
|
||||
const graphComparison = String(planner.data_need_graph?.comparison_need ?? "").toLowerCase();
|
||||
const combined = `${action} ${unsupported} ${graphAction} ${graphComparison} ${planner.selected_chain_id}`;
|
||||
const combined = `${action} ${unsupported}`;
|
||||
if (
|
||||
combined.includes("net_value_flow") ||
|
||||
combined.includes("bidirectional") ||
|
||||
|
|
|
|||
|
|
@ -736,19 +736,13 @@ function businessOverviewInventoryLine(overview: Record<string, unknown>): strin
|
|||
return pieces.length > 0 ? `Склад: ${pieces.join(", ")}.` : null;
|
||||
}
|
||||
|
||||
function bidirectionalNetLabel(direction: unknown, scopeKind: "counterparty" | "organization" | "generic" = "counterparty"): string {
|
||||
function bidirectionalNetLabel(direction: unknown): string {
|
||||
if (direction === "net_outgoing") {
|
||||
if (scopeKind === "organization") {
|
||||
return "нетто в сторону исходящих платежей";
|
||||
}
|
||||
return "нетто в сторону контрагента";
|
||||
}
|
||||
if (direction === "balanced") {
|
||||
return "нетто около нуля";
|
||||
}
|
||||
if (scopeKind === "organization") {
|
||||
return "нетто в сторону входящих платежей";
|
||||
}
|
||||
return "нетто в нашу сторону";
|
||||
}
|
||||
|
||||
|
|
@ -776,11 +770,6 @@ function buildCompactBidirectionalValueFlowReply(
|
|||
|
||||
const counterparty = toNonEmptyString(flow.counterparty);
|
||||
const organizationScope = toNonEmptyString(turnMeaning?.explicit_organization_scope);
|
||||
const scopeKind: "counterparty" | "organization" | "generic" = counterparty
|
||||
? "counterparty"
|
||||
: organizationScope
|
||||
? "organization"
|
||||
: "generic";
|
||||
const subjectLead = counterparty
|
||||
? `по контрагенту ${counterparty}`
|
||||
: organizationScope
|
||||
|
|
@ -788,9 +777,9 @@ function buildCompactBidirectionalValueFlowReply(
|
|||
: "по выбранному контуру";
|
||||
const period = toNonEmptyString(flow.period_scope);
|
||||
const periodText = period ? ` за период ${period}` : " в проверенном окне";
|
||||
const netLabel = bidirectionalNetLabel(flow.net_direction, scopeKind);
|
||||
const netLabel = bidirectionalNetLabel(flow.net_direction);
|
||||
const lines = [
|
||||
`${subjectLead}${periodText} по найденным строкам 1С получили ${incomingAmount ?? "0 руб."}, заплатили ${outgoingAmount ?? "0 руб."}; расчетное ${netLabel}: ${sentenceAmount(netAmount) ?? netAmount ?? "0 руб."}.`
|
||||
`Коротко: ${subjectLead}${periodText} по найденным строкам 1С получили ${incomingAmount ?? "0 руб."}, заплатили ${outgoingAmount ?? "0 руб."}; расчетное ${netLabel}: ${sentenceAmount(netAmount) ?? netAmount ?? "0 руб."}.`
|
||||
];
|
||||
|
||||
const basis: string[] = [];
|
||||
|
|
@ -801,13 +790,7 @@ function buildCompactBidirectionalValueFlowReply(
|
|||
basis.push("исходящие платежи");
|
||||
}
|
||||
if (basis.length > 0) {
|
||||
const basisScope =
|
||||
scopeKind === "counterparty"
|
||||
? "по этому контрагенту"
|
||||
: scopeKind === "organization"
|
||||
? "по этой компании"
|
||||
: "по выбранному контуру";
|
||||
lines.push(`Основа: проверенные ${basis.join(" и ")} ${basisScope} в 1С.`);
|
||||
lines.push(`Основа: проверенные ${basis.join(" и ")} по этому контрагенту в 1С.`);
|
||||
}
|
||||
if (flow.coverage_limited_by_probe_limit === true) {
|
||||
lines.push("Важно: часть проверки достигла лимита строк, поэтому это проверенный срез найденных движений, а не гарантия полного периода.");
|
||||
|
|
|
|||
|
|
@ -358,36 +358,6 @@ function isOpenScopeValueFlowRanking(
|
|||
return businessFactFamily === "value_flow" && subjectCandidates.length === 0 && Boolean(toNonEmptyString(graph?.ranking_need));
|
||||
}
|
||||
|
||||
function readDiscoverySelectedChainId(
|
||||
entryPoint: AssistantMcpDiscoveryRuntimeEntryPointContract | null
|
||||
): string | null {
|
||||
const bridge = toRecordObject(entryPoint?.bridge);
|
||||
const pilot = toRecordObject(bridge?.pilot);
|
||||
const routeCandidate = toRecordObject(bridge?.route_candidate);
|
||||
return (
|
||||
toNonEmptyString(routeCandidate?.selected_chain_id) ??
|
||||
toNonEmptyString(bridge?.selected_chain_id) ??
|
||||
toNonEmptyString(pilot?.selected_chain_id)
|
||||
);
|
||||
}
|
||||
|
||||
function isValueFlowRankingDiscoveryTurn(
|
||||
entryPoint: AssistantMcpDiscoveryRuntimeEntryPointContract | null
|
||||
): boolean {
|
||||
const graph = readDiscoveryDataNeedGraph(entryPoint);
|
||||
const bridge = toRecordObject(entryPoint?.bridge);
|
||||
const routeCandidate = toRecordObject(bridge?.route_candidate);
|
||||
const selectedChainId = readDiscoverySelectedChainId(entryPoint);
|
||||
const graphReasonCodes = readStringArray(graph?.reason_codes);
|
||||
return Boolean(
|
||||
toNonEmptyString(graph?.business_fact_family) === "value_flow" &&
|
||||
(selectedChainId === "value_flow_ranking" ||
|
||||
toNonEmptyString(routeCandidate?.nearest_catalog_chain_template) === "value_flow_ranking" ||
|
||||
toNonEmptyString(graph?.ranking_need) === "top_desc" ||
|
||||
graphReasonCodes.includes("data_need_graph_ranking_top_desc"))
|
||||
);
|
||||
}
|
||||
|
||||
function readTruthAnswerShape(input: ApplyAssistantMcpDiscoveryResponsePolicyInput): Record<string, unknown> | null {
|
||||
const directShape = toRecordObject(input.addressRuntimeMeta?.answer_shape_contract);
|
||||
if (directShape) {
|
||||
|
|
@ -397,260 +367,6 @@ function readTruthAnswerShape(input: ApplyAssistantMcpDiscoveryResponsePolicyInp
|
|||
return toRecordObject(truthAnswerPolicy?.answer_shape);
|
||||
}
|
||||
|
||||
function hasCurrentClarificationRequiredAddressReply(input: ApplyAssistantMcpDiscoveryResponsePolicyInput): boolean {
|
||||
const answerShape =
|
||||
toNonEmptyString(input.addressRuntimeMeta?.answer_shape) ??
|
||||
toNonEmptyString(input.addressRuntimeMeta?.truth_mode) ??
|
||||
toNonEmptyString(readTruthAnswerShape(input)?.answer_shape);
|
||||
if (answerShape === "clarification_required") {
|
||||
return true;
|
||||
}
|
||||
const truthAnswerPolicy = toRecordObject(input.addressRuntimeMeta?.assistant_truth_answer_policy_v1);
|
||||
const truthGate = toRecordObject(truthAnswerPolicy?.truth_gate);
|
||||
const policyAnswerShape = toRecordObject(truthAnswerPolicy?.answer_shape);
|
||||
return (
|
||||
toNonEmptyString(truthGate?.truth_mode) === "clarification_required" ||
|
||||
toNonEmptyString(policyAnswerShape?.answer_shape) === "clarification_required"
|
||||
);
|
||||
}
|
||||
|
||||
function hasDiscoveryClarificationCandidatePriorityOverCurrentClarification(
|
||||
input: ApplyAssistantMcpDiscoveryResponsePolicyInput,
|
||||
entryPoint: AssistantMcpDiscoveryRuntimeEntryPointContract | null,
|
||||
candidate: AssistantMcpDiscoveryResponseCandidateContract
|
||||
): boolean {
|
||||
if (!hasCurrentClarificationRequiredAddressReply(input)) {
|
||||
return false;
|
||||
}
|
||||
if (candidate.candidate_status !== "clarification_candidate") {
|
||||
return false;
|
||||
}
|
||||
if (!candidate.eligible_for_future_hot_runtime || !toNonEmptyString(candidate.reply_text)) {
|
||||
return false;
|
||||
}
|
||||
if (!isDiscoveryReadyAddressCandidate(input, entryPoint)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const graph = readDiscoveryDataNeedGraph(entryPoint);
|
||||
const turnMeaning = readDiscoveryTurnMeaning(entryPoint);
|
||||
const discoveryFamily = toNonEmptyString(graph?.business_fact_family);
|
||||
const askedDomain = toNonEmptyString(turnMeaning?.asked_domain_family);
|
||||
const askedAction =
|
||||
toNonEmptyString(turnMeaning?.asked_action_family) ?? toNonEmptyString(graph?.action_family);
|
||||
const asksForMovements =
|
||||
discoveryFamily === "movement_evidence" || askedDomain === "movements" || askedAction === "list_movements";
|
||||
|
||||
const truthAnswerShape = readTruthAnswerShape(input);
|
||||
const detectedIntent = toNonEmptyString(input.addressRuntimeMeta?.detected_intent);
|
||||
const selectedRecipe = toNonEmptyString(input.addressRuntimeMeta?.selected_recipe);
|
||||
const capabilityId =
|
||||
toNonEmptyString(input.addressRuntimeMeta?.capability_id) ??
|
||||
toNonEmptyString(input.addressRuntimeMeta?.capability_contract_id) ??
|
||||
toNonEmptyString(truthAnswerShape?.capability_contract_id);
|
||||
const missingFilters = [
|
||||
...readStringArray(input.addressRuntimeMeta?.missing_required_filters),
|
||||
...readStringArray(input.addressRuntimeMeta?.missing_anchors),
|
||||
...readStringArray(toRecordObject(input.addressRuntimeMeta?.capability_binding_contract)?.missing_anchors)
|
||||
];
|
||||
const staleInventoryItemClarification = Boolean(
|
||||
missingFilters.includes("item") &&
|
||||
(detectedIntent?.startsWith("inventory_") ||
|
||||
selectedRecipe?.includes("inventory_") ||
|
||||
capabilityId?.includes("inventory_"))
|
||||
);
|
||||
|
||||
return asksForMovements && staleInventoryItemClarification;
|
||||
}
|
||||
|
||||
function hasGroundedMovementCandidatePriorityOverCurrentInventoryItemClarification(
|
||||
input: ApplyAssistantMcpDiscoveryResponsePolicyInput,
|
||||
entryPoint: AssistantMcpDiscoveryRuntimeEntryPointContract | null,
|
||||
candidate: AssistantMcpDiscoveryResponseCandidateContract
|
||||
): boolean {
|
||||
if (!hasCurrentClarificationRequiredAddressReply(input)) {
|
||||
return false;
|
||||
}
|
||||
if (candidate.candidate_status !== "ready_for_guarded_use") {
|
||||
return false;
|
||||
}
|
||||
if (!candidate.eligible_for_future_hot_runtime || !toNonEmptyString(candidate.reply_text)) {
|
||||
return false;
|
||||
}
|
||||
if (candidate.reply_text && hasInternalMechanics(candidate.reply_text)) {
|
||||
return false;
|
||||
}
|
||||
if (!isDiscoveryReadyAddressCandidate(input, entryPoint)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const graph = readDiscoveryDataNeedGraph(entryPoint);
|
||||
const turnMeaning = readDiscoveryTurnMeaning(entryPoint);
|
||||
const bridge = toRecordObject(entryPoint?.bridge);
|
||||
const pilot = toRecordObject(bridge?.pilot);
|
||||
const handoff = toRecordObject(bridge?.execution_handoff);
|
||||
const selectedChainId = readDiscoverySelectedChainId(entryPoint);
|
||||
const discoveryFamily = toNonEmptyString(graph?.business_fact_family);
|
||||
const askedDomain = toNonEmptyString(turnMeaning?.asked_domain_family);
|
||||
const askedAction =
|
||||
toNonEmptyString(turnMeaning?.asked_action_family) ?? toNonEmptyString(graph?.action_family);
|
||||
const asksForMovements = Boolean(
|
||||
selectedChainId === "movement_evidence" ||
|
||||
discoveryFamily === "movement_evidence" ||
|
||||
askedDomain === "movements" ||
|
||||
askedAction === "list_movements"
|
||||
);
|
||||
|
||||
const truthAnswerShape = readTruthAnswerShape(input);
|
||||
const detectedIntent = toNonEmptyString(input.addressRuntimeMeta?.detected_intent);
|
||||
const selectedRecipe = toNonEmptyString(input.addressRuntimeMeta?.selected_recipe);
|
||||
const capabilityId =
|
||||
toNonEmptyString(input.addressRuntimeMeta?.capability_id) ??
|
||||
toNonEmptyString(input.addressRuntimeMeta?.capability_contract_id) ??
|
||||
toNonEmptyString(truthAnswerShape?.capability_contract_id);
|
||||
const missingFilters = [
|
||||
...readStringArray(input.addressRuntimeMeta?.missing_required_filters),
|
||||
...readStringArray(input.addressRuntimeMeta?.missing_anchors),
|
||||
...readStringArray(toRecordObject(input.addressRuntimeMeta?.capability_binding_contract)?.missing_anchors)
|
||||
];
|
||||
const staleInventoryItemClarification = Boolean(
|
||||
missingFilters.includes("item") &&
|
||||
(detectedIntent?.startsWith("inventory_") ||
|
||||
selectedRecipe?.includes("inventory_") ||
|
||||
capabilityId?.includes("inventory_"))
|
||||
);
|
||||
const mcpExecutionPerformed = handoff?.mcp_execution_performed === true || pilot?.mcp_execution_performed === true;
|
||||
const boundedMovementAnswerAllowed =
|
||||
bridge?.user_facing_response_allowed === true && bridge?.business_fact_answer_allowed === true;
|
||||
|
||||
return asksForMovements && staleInventoryItemClarification && mcpExecutionPerformed && boundedMovementAnswerAllowed;
|
||||
}
|
||||
|
||||
function hasGroundedValueFlowCandidatePriorityOverCurrentClarification(
|
||||
input: ApplyAssistantMcpDiscoveryResponsePolicyInput,
|
||||
entryPoint: AssistantMcpDiscoveryRuntimeEntryPointContract | null,
|
||||
candidate: AssistantMcpDiscoveryResponseCandidateContract
|
||||
): boolean {
|
||||
if (!hasCurrentClarificationRequiredAddressReply(input)) {
|
||||
return false;
|
||||
}
|
||||
if (candidate.candidate_status !== "ready_for_guarded_use") {
|
||||
return false;
|
||||
}
|
||||
if (!candidate.hot_runtime_wired || !candidate.eligible_for_future_hot_runtime || !toNonEmptyString(candidate.reply_text)) {
|
||||
return false;
|
||||
}
|
||||
if (!isDiscoveryReadyAddressCandidate(input, entryPoint)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const graph = readDiscoveryDataNeedGraph(entryPoint);
|
||||
const bridge = toRecordObject(entryPoint?.bridge);
|
||||
const pilot = toRecordObject(bridge?.pilot);
|
||||
const handoff = toRecordObject(bridge?.execution_handoff);
|
||||
const selectedChainId = readDiscoverySelectedChainId(entryPoint);
|
||||
const pilotScope = toNonEmptyString(pilot?.pilot_scope);
|
||||
const actionFamily = toNonEmptyString(graph?.action_family);
|
||||
const comparisonNeed = toNonEmptyString(graph?.comparison_need);
|
||||
const valueFlowComparison = Boolean(
|
||||
selectedChainId === "value_flow_comparison" ||
|
||||
pilotScope === "counterparty_bidirectional_value_flow_query_movements_v1" ||
|
||||
(actionFamily === "net_value_flow" && comparisonNeed === "incoming_vs_outgoing")
|
||||
);
|
||||
const mcpExecutionPerformed = handoff?.mcp_execution_performed === true || pilot?.mcp_execution_performed === true;
|
||||
const guardedBusinessAnswerAllowed =
|
||||
bridge?.user_facing_response_allowed === true &&
|
||||
bridge?.business_fact_answer_allowed === true &&
|
||||
(handoff?.can_use_guarded_response === true || toNonEmptyString(bridge?.bridge_status) === "answer_draft_ready");
|
||||
|
||||
return valueFlowComparison && mcpExecutionPerformed && guardedBusinessAnswerAllowed;
|
||||
}
|
||||
|
||||
function hasCounterpartyRankingQuestionSignal(
|
||||
entryPoint: AssistantMcpDiscoveryRuntimeEntryPointContract | null
|
||||
): boolean {
|
||||
const graph = readDiscoveryDataNeedGraph(entryPoint);
|
||||
const turnMeaning = readDiscoveryTurnMeaning(entryPoint);
|
||||
const rawMessage = toNonEmptyString(turnMeaning?.raw_message) ?? "";
|
||||
const effectiveMessage = toNonEmptyString(turnMeaning?.effective_message) ?? "";
|
||||
const askedDomain = toNonEmptyString(turnMeaning?.asked_domain_family);
|
||||
const askedAction = toNonEmptyString(turnMeaning?.asked_action_family);
|
||||
const text = `${rawMessage}\n${effectiveMessage}`.toLowerCase();
|
||||
return Boolean(
|
||||
isValueFlowRankingDiscoveryTurn(entryPoint) &&
|
||||
(askedDomain === "counterparty_value" || askedDomain === "counterparty") &&
|
||||
(askedAction === "turnover" || askedAction === "counterparty_value_or_turnover") &&
|
||||
(/\bкто\b/u.test(text) ||
|
||||
text.includes("контрагент") ||
|
||||
text.includes("клиент") ||
|
||||
text.includes("покупател") ||
|
||||
text.includes("заказчик") ||
|
||||
text.includes("принес") ||
|
||||
text.includes("принёс") ||
|
||||
text.includes("больше всего") ||
|
||||
toNonEmptyString(graph?.ranking_need) === "top_desc")
|
||||
);
|
||||
}
|
||||
|
||||
function hasWrongPeriodRankingCurrentReply(input: ApplyAssistantMcpDiscoveryResponsePolicyInput): boolean {
|
||||
const currentReply = String(input.currentReply ?? "").toLowerCase();
|
||||
return (
|
||||
currentReply.includes("самый доходный год") ||
|
||||
currentReply.includes("топ-1 лет") ||
|
||||
currentReply.includes("топ-5 лет") ||
|
||||
currentReply.includes("топ-10 лет") ||
|
||||
/топ-\d+\s+лет/u.test(currentReply) ||
|
||||
currentReply.includes("лет по сумме поступлений") ||
|
||||
currentReply.includes("год по подтвержденным поступлениям")
|
||||
);
|
||||
}
|
||||
|
||||
function hasConfirmedValueFlowRankingEvidence(
|
||||
entryPoint: AssistantMcpDiscoveryRuntimeEntryPointContract | null
|
||||
): boolean {
|
||||
const bridge = toRecordObject(entryPoint?.bridge);
|
||||
const pilot = toRecordObject(bridge?.pilot);
|
||||
const handoff = toRecordObject(bridge?.execution_handoff);
|
||||
const evidence = toRecordObject(pilot?.evidence);
|
||||
const confirmedFacts = readStringArray(evidence?.confirmed_facts);
|
||||
const mcpExecutionPerformed = handoff?.mcp_execution_performed === true || pilot?.mcp_execution_performed === true;
|
||||
const businessAnswerAllowed =
|
||||
bridge?.user_facing_response_allowed === true && bridge?.business_fact_answer_allowed === true;
|
||||
const confirmedRankingEvidence = Boolean(
|
||||
toNonEmptyString(evidence?.evidence_status) === "confirmed" ||
|
||||
toNonEmptyString(evidence?.answer_permission) === "confirmed_answer" ||
|
||||
confirmedFacts.some((fact) => /ranked by counterparty|рейтинг.*контрагент/iu.test(fact))
|
||||
);
|
||||
return mcpExecutionPerformed && businessAnswerAllowed && confirmedRankingEvidence;
|
||||
}
|
||||
|
||||
function hasGroundedValueFlowRankingCandidatePriorityOverCurrentExactReply(
|
||||
input: ApplyAssistantMcpDiscoveryResponsePolicyInput,
|
||||
entryPoint: AssistantMcpDiscoveryRuntimeEntryPointContract | null,
|
||||
candidate: AssistantMcpDiscoveryResponseCandidateContract
|
||||
): boolean {
|
||||
if (candidate.candidate_status !== "ready_for_guarded_use") {
|
||||
return false;
|
||||
}
|
||||
if (!candidate.eligible_for_future_hot_runtime || !toNonEmptyString(candidate.reply_text)) {
|
||||
return false;
|
||||
}
|
||||
if (candidate.reply_text && hasInternalMechanics(candidate.reply_text)) {
|
||||
return false;
|
||||
}
|
||||
if (!isDiscoveryReadyAddressCandidate(input, entryPoint)) {
|
||||
return false;
|
||||
}
|
||||
if (!hasEffectivelyFactualAddressReply(input)) {
|
||||
return false;
|
||||
}
|
||||
return Boolean(
|
||||
hasCounterpartyRankingQuestionSignal(entryPoint) &&
|
||||
hasWrongPeriodRankingCurrentReply(input) &&
|
||||
hasConfirmedValueFlowRankingEvidence(entryPoint)
|
||||
);
|
||||
}
|
||||
|
||||
function hasEffectivelyFactualAddressReply(input: ApplyAssistantMcpDiscoveryResponsePolicyInput): boolean {
|
||||
if (toNonEmptyString(input.currentReplyType) === "factual") {
|
||||
return true;
|
||||
|
|
@ -1255,35 +971,6 @@ export function applyAssistantMcpDiscoveryResponsePolicy(
|
|||
input,
|
||||
entryPoint
|
||||
);
|
||||
const currentClarificationRequiredAddressReply = hasCurrentClarificationRequiredAddressReply(input);
|
||||
const discoveryClarificationCandidatePriority = hasDiscoveryClarificationCandidatePriorityOverCurrentClarification(
|
||||
input,
|
||||
entryPoint,
|
||||
candidate
|
||||
);
|
||||
const groundedValueFlowCandidatePriority = hasGroundedValueFlowCandidatePriorityOverCurrentClarification(
|
||||
input,
|
||||
entryPoint,
|
||||
candidate
|
||||
);
|
||||
const groundedMovementCandidatePriority =
|
||||
hasGroundedMovementCandidatePriorityOverCurrentInventoryItemClarification(input, entryPoint, candidate);
|
||||
const groundedValueFlowRankingCandidatePriority =
|
||||
hasGroundedValueFlowRankingCandidatePriorityOverCurrentExactReply(input, entryPoint, candidate);
|
||||
const currentClarificationProtectsCurrentReply =
|
||||
currentClarificationRequiredAddressReply &&
|
||||
!discoveryClarificationCandidatePriority &&
|
||||
!groundedValueFlowCandidatePriority &&
|
||||
!groundedMovementCandidatePriority &&
|
||||
!groundedValueFlowRankingCandidatePriority;
|
||||
const alignedFactualAddressReplyProtectsCurrent =
|
||||
alignedFactualAddressReply && !groundedValueFlowRankingCandidatePriority;
|
||||
const matchedFactualAddressContinuationTargetProtectsCurrent =
|
||||
matchedFactualAddressContinuationTarget && !groundedValueFlowRankingCandidatePriority;
|
||||
const fullConfirmedFactualAddressReplyProtectsCurrent =
|
||||
fullConfirmedFactualAddressReply && !groundedValueFlowRankingCandidatePriority;
|
||||
const exactMatchedFactualAddressReplyProtectsCurrent =
|
||||
exactMatchedFactualAddressReply && !groundedValueFlowRankingCandidatePriority;
|
||||
const exactBankOperationsProtectsCurrent =
|
||||
exactBankOperationsAddressReply &&
|
||||
!semanticConflictWithDiscoveryTurnMeaning &&
|
||||
|
|
@ -1320,33 +1007,6 @@ export function applyAssistantMcpDiscoveryResponsePolicy(
|
|||
if (evidenceLaneConflictWithDiscoveryTurnMeaning) {
|
||||
pushReason(reasonCodes, "mcp_discovery_response_policy_evidence_lane_conflict_allows_candidate_override");
|
||||
}
|
||||
if (currentClarificationProtectsCurrentReply) {
|
||||
pushReason(reasonCodes, "mcp_discovery_response_policy_keep_current_clarification_required_reply");
|
||||
}
|
||||
if (discoveryClarificationCandidatePriority) {
|
||||
pushReason(
|
||||
reasonCodes,
|
||||
"mcp_discovery_response_policy_clarification_candidate_priority_over_stale_address_clarification"
|
||||
);
|
||||
}
|
||||
if (groundedValueFlowCandidatePriority) {
|
||||
pushReason(
|
||||
reasonCodes,
|
||||
"mcp_discovery_response_policy_grounded_value_flow_candidate_priority_over_current_clarification"
|
||||
);
|
||||
}
|
||||
if (groundedMovementCandidatePriority) {
|
||||
pushReason(
|
||||
reasonCodes,
|
||||
"mcp_discovery_response_policy_grounded_movement_candidate_priority_over_inventory_item_clarification"
|
||||
);
|
||||
}
|
||||
if (groundedValueFlowRankingCandidatePriority) {
|
||||
pushReason(
|
||||
reasonCodes,
|
||||
"mcp_discovery_response_policy_grounded_value_flow_ranking_candidate_priority_over_current_exact_reply"
|
||||
);
|
||||
}
|
||||
if (openScopeValueFlowDiscoveryPriority) {
|
||||
pushReason(reasonCodes, "mcp_discovery_response_policy_open_scope_value_flow_candidate_priority");
|
||||
}
|
||||
|
|
@ -1429,11 +1089,11 @@ export function applyAssistantMcpDiscoveryResponsePolicy(
|
|||
const canApply =
|
||||
Boolean(entryPoint) &&
|
||||
(unsupportedBoundary || discoveryReadyChatCandidate || discoveryReadyDeepCandidate || discoveryReadyAddressCandidate) &&
|
||||
!alignedFactualAddressReplyProtectsCurrent &&
|
||||
!matchedFactualAddressContinuationTargetProtectsCurrent &&
|
||||
!alignedFactualAddressReply &&
|
||||
!matchedFactualAddressContinuationTarget &&
|
||||
!matchedFactualSuggestedIntentPivotTarget &&
|
||||
!fullConfirmedFactualAddressReplyProtectsCurrent &&
|
||||
!exactMatchedFactualAddressReplyProtectsCurrent &&
|
||||
!fullConfirmedFactualAddressReply &&
|
||||
!exactMatchedFactualAddressReply &&
|
||||
!runtimeAdjustedExactReply &&
|
||||
!runtimeMatchedExactReply &&
|
||||
!staleMetadataDiscoveryFallbackAgainstExactAddressReply &&
|
||||
|
|
@ -1442,7 +1102,6 @@ export function applyAssistantMcpDiscoveryResponsePolicy(
|
|||
!exactDocumentListAddressReply &&
|
||||
!inventoryMarginRankingAddressReply &&
|
||||
!exactInventoryChainProtectsCurrent &&
|
||||
!currentClarificationProtectsCurrentReply &&
|
||||
!(deterministicBroadBusinessEvaluationReply && candidate.candidate_status === "clarification_candidate") &&
|
||||
ALLOWED_CANDIDATE_STATUSES.has(candidate.candidate_status) &&
|
||||
candidate.eligible_for_future_hot_runtime &&
|
||||
|
|
|
|||
|
|
@ -104,28 +104,10 @@ function isReferentialEntityPlaceholder(value: string): boolean {
|
|||
]).has(normalizeRussianComparableText(value));
|
||||
}
|
||||
|
||||
function isNegativeCounterpartyScopePlaceholder(value: string | null): boolean {
|
||||
if (!value) {
|
||||
return false;
|
||||
}
|
||||
const text = compactLower(repairAddressMojibakeText(value));
|
||||
if (!text) {
|
||||
return false;
|
||||
}
|
||||
return (
|
||||
/(?:без\s+(?:привязк\p{L}*|конкретн\p{L}*)\s+к\s+контрагент\p{L}*|без\s+контрагент\p{L}*)/iu.test(text) ||
|
||||
/(?:одн\p{L}*\s+организац\p{L}*\s+без\s+привязк\p{L}*)/iu.test(text) ||
|
||||
/(?:organization\s+without\s+(?:counterparty|specific\s+counterparty))/iu.test(text)
|
||||
);
|
||||
}
|
||||
|
||||
function isReferentialOrganizationPlaceholder(value: string | null): boolean {
|
||||
if (!value) {
|
||||
return false;
|
||||
}
|
||||
if (isNegativeCounterpartyScopePlaceholder(value)) {
|
||||
return true;
|
||||
}
|
||||
return new Set([
|
||||
"эта организация",
|
||||
"этой организации",
|
||||
|
|
@ -198,12 +180,6 @@ function isGarbageSemanticAnchorCandidate(value: string | null): boolean {
|
|||
"этими",
|
||||
"итогу",
|
||||
"итогам",
|
||||
"одной",
|
||||
"одному",
|
||||
"одним",
|
||||
"одна",
|
||||
"один",
|
||||
"одно",
|
||||
"всему",
|
||||
"всей",
|
||||
"всем",
|
||||
|
|
@ -245,7 +221,6 @@ function isGarbageSemanticAnchorCandidate(value: string | null): boolean {
|
|||
return true;
|
||||
}
|
||||
if (
|
||||
isNegativeCounterpartyScopePlaceholder(text) ||
|
||||
/^(?:по\s+)?(?:этим|этими)\s+данн\p{L}*$/iu.test(text) ||
|
||||
/^(?:и\s+)?кто\s+(?:главн\p{L}*|основн\p{L}*|крупн\p{L}*)\s+(?:клиент|покупател|поставщик|контрагент)(?:\s+в)?$/iu.test(
|
||||
text
|
||||
|
|
@ -699,7 +674,6 @@ function collectFollowupDiscoverySeed(followupContext: Record<string, unknown> |
|
|||
const loopAskedDomainFamily = toNonEmptyString(followupContext?.previous_discovery_loop_asked_domain_family);
|
||||
const loopAskedActionFamily = toNonEmptyString(followupContext?.previous_discovery_loop_asked_action_family);
|
||||
const loopUnsupportedFamily = toNonEmptyString(followupContext?.previous_discovery_loop_unsupported_family);
|
||||
const loopExplicitDateScope = toNonEmptyString(followupContext?.previous_discovery_loop_explicit_date_scope);
|
||||
const loopMetadataScopeHint = toNonEmptyString(followupContext?.previous_discovery_loop_metadata_scope_hint);
|
||||
const loopSubjectResolutionOptional =
|
||||
followupContext?.previous_discovery_loop_subject_resolution_optional === true;
|
||||
|
|
@ -764,7 +738,7 @@ function collectFollowupDiscoverySeed(followupContext: Record<string, unknown> |
|
|||
const loopProvidedAllTimeScope = loopProvidedAxes.includes("all_time_scope");
|
||||
const dateScope = loopProvidedAllTimeScope
|
||||
? "all_time_scope"
|
||||
: loopExplicitDateScope ?? collectDateScopeFromFilters(previousFilters) ?? collectDateScopeFromFilters(rootFilters);
|
||||
: collectDateScopeFromFilters(previousFilters) ?? collectDateScopeFromFilters(rootFilters);
|
||||
return {
|
||||
pilotScope: effectivePilotScope,
|
||||
domain: mapped.domain,
|
||||
|
|
@ -1320,9 +1294,6 @@ function normalizeLooseOrganizationAlias(value: string | null): string | null {
|
|||
if (!text) {
|
||||
return null;
|
||||
}
|
||||
if (isNegativeCounterpartyScopePlaceholder(text)) {
|
||||
return null;
|
||||
}
|
||||
const normalized = text
|
||||
.replace(/^[«"'“”]+|[»"'“”]+$/gu, "")
|
||||
.replace(/\s+/g, " ")
|
||||
|
|
|
|||
|
|
@ -28,7 +28,6 @@ import {
|
|||
readAssistantMcpDiscoveryLoopAskedDomainFamily,
|
||||
readAssistantMcpDiscoveryLoopAskedActionFamily,
|
||||
readAssistantMcpDiscoveryLoopUnsupportedFamily,
|
||||
readAssistantMcpDiscoveryLoopExplicitDateScope,
|
||||
readAssistantMcpDiscoveryLoopMetadataScopeHint,
|
||||
readAssistantMcpDiscoveryLoopSubjectResolutionOptional,
|
||||
readAddressDebugTemporalScope,
|
||||
|
|
@ -73,42 +72,6 @@ export function createAssistantTransitionPolicy(deps) {
|
|||
});
|
||||
}
|
||||
|
||||
function hasInventorySupplierOverlapRootFollowupSignal(
|
||||
userMessage,
|
||||
alternateMessage = null,
|
||||
sourceIntentHint = null,
|
||||
hasInventoryRootFrame = false
|
||||
) {
|
||||
if (
|
||||
!hasInventoryRootFrame ||
|
||||
(sourceIntentHint !== "inventory_on_hand_as_of_date" &&
|
||||
sourceIntentHint !== "inventory_supplier_stock_overlap_as_of_date")
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
return [userMessage, alternateMessage]
|
||||
.filter((value) => deps.toNonEmptyString(value))
|
||||
.map((value) => normalizeFollowupText(value))
|
||||
.some((normalized) => {
|
||||
if (!normalized) {
|
||||
return false;
|
||||
}
|
||||
const hasSupplierCue =
|
||||
/(?:\u043f\u043e\u0441\u0442\u0430\u0432\u0449\u0438\u043a|supplier|vendor|\u0443\s+\u043a\u0430\u043a\u043e\u0433\u043e|\u0443\s+\u043a\u043e\u0433\u043e|\u043f\u043e\s+\u043a\u0430\u043a\u043e\u043c\u0443)/iu.test(
|
||||
normalized
|
||||
);
|
||||
const hasStockCue =
|
||||
/(?:\u043e\u0441\u0442\u0430\u0442|\u0441\u043a\u043b\u0430\u0434|\u0442\u043e\u0432\u0430\u0440|\u043f\u043e\u0437\u0438\u0446|\u043b\u0435\u0436|\u0432\u0438\u0441|\u0442\u0435\u043a\u0443\u0449)/iu.test(
|
||||
normalized
|
||||
);
|
||||
const hasPurchaseCue =
|
||||
/(?:\u043a\u0443\u043f\u0438\u043b|\u043a\u0443\u043f\u043b\u0435\u043d|\u043f\u043e\u043a\u0443\u043f|\u0437\u0430\u043a\u0443\u043f|\u043f\u043e\u0441\u0442\u0443\u043f\u043b|purchase|bought)/iu.test(
|
||||
normalized
|
||||
);
|
||||
return hasSupplierCue && (hasStockCue || hasPurchaseCue);
|
||||
});
|
||||
}
|
||||
|
||||
function hasSamePeriodReferenceCue(...values) {
|
||||
return values
|
||||
.map((value) => normalizeFollowupText(value))
|
||||
|
|
@ -1217,12 +1180,6 @@ export function createAssistantTransitionPolicy(deps) {
|
|||
Boolean(recentInventoryRootFrame)
|
||||
)
|
||||
: false;
|
||||
const hasInventorySupplierOverlapRootFollowup = hasInventorySupplierOverlapRootFollowupSignal(
|
||||
userMessage,
|
||||
alternateMessage,
|
||||
sourceIntentHint,
|
||||
Boolean(recentInventoryRootFrame)
|
||||
);
|
||||
const hasExplicitInventorySameDatePivotPrimary = hasExplicitInventorySameDatePivotSignal(userMessage);
|
||||
const hasExplicitInventorySameDatePivotAlternate = deps.toNonEmptyString(alternateMessage)
|
||||
? hasExplicitInventorySameDatePivotSignal(String(alternateMessage ?? ""))
|
||||
|
|
@ -1240,7 +1197,6 @@ export function createAssistantTransitionPolicy(deps) {
|
|||
hasInventoryRootTemporalFollowupAlternate ||
|
||||
hasInventoryRootRestatementPrimary ||
|
||||
hasInventoryRootRestatementAlternate ||
|
||||
hasInventorySupplierOverlapRootFollowup ||
|
||||
inventoryPurchaseDateVatBridge ||
|
||||
explicitSummaryBundleReuseSignal ||
|
||||
Boolean(debtRoleSwapIntent) ||
|
||||
|
|
@ -1267,7 +1223,6 @@ export function createAssistantTransitionPolicy(deps) {
|
|||
hasInventoryRootTemporalFollowupAlternate ||
|
||||
hasInventoryRootRestatementPrimary ||
|
||||
hasInventoryRootRestatementAlternate ||
|
||||
hasInventorySupplierOverlapRootFollowup ||
|
||||
inventoryPurchaseDateVatBridge ||
|
||||
explicitSummaryBundleReuseSignal ||
|
||||
Boolean(debtRoleSwapIntent) ||
|
||||
|
|
@ -1304,7 +1259,6 @@ export function createAssistantTransitionPolicy(deps) {
|
|||
!hasInventoryRootTemporalFollowupAlternate &&
|
||||
!hasInventoryRootRestatementPrimary &&
|
||||
!hasInventoryRootRestatementAlternate &&
|
||||
!hasInventorySupplierOverlapRootFollowup &&
|
||||
!shortValueFlowRetargetPrimary &&
|
||||
!shortValueFlowRetargetAlternate &&
|
||||
!selectedCounterpartyDocumentFollowupSignal &&
|
||||
|
|
@ -1324,7 +1278,6 @@ export function createAssistantTransitionPolicy(deps) {
|
|||
!hasInventoryRootTemporalFollowupAlternate &&
|
||||
!hasInventoryRootRestatementPrimary &&
|
||||
!hasInventoryRootRestatementAlternate &&
|
||||
!hasInventorySupplierOverlapRootFollowup &&
|
||||
!shortValueFlowRetargetPrimary &&
|
||||
!shortValueFlowRetargetAlternate &&
|
||||
!selectedCounterpartyDocumentFollowupSignal &&
|
||||
|
|
@ -1389,10 +1342,6 @@ export function createAssistantTransitionPolicy(deps) {
|
|||
carryoverSourceDebug,
|
||||
deps.toNonEmptyString
|
||||
);
|
||||
const sourceDiscoveryLoopExplicitDateScope = readAssistantMcpDiscoveryLoopExplicitDateScope(
|
||||
carryoverSourceDebug,
|
||||
deps.toNonEmptyString
|
||||
);
|
||||
const sourceDiscoveryLoopMetadataScopeHint = readAssistantMcpDiscoveryLoopMetadataScopeHint(
|
||||
carryoverSourceDebug,
|
||||
deps.toNonEmptyString
|
||||
|
|
@ -1463,7 +1412,6 @@ export function createAssistantTransitionPolicy(deps) {
|
|||
!hasInventoryRootTemporalFollowupAlternate &&
|
||||
!hasInventoryRootRestatementPrimary &&
|
||||
!hasInventoryRootRestatementAlternate &&
|
||||
!hasInventorySupplierOverlapRootFollowup &&
|
||||
!inventoryShortFollowupPrimary &&
|
||||
!inventoryShortFollowupAlternate &&
|
||||
!businessOverviewBoundaryFollowupPrimary &&
|
||||
|
|
@ -1535,7 +1483,6 @@ export function createAssistantTransitionPolicy(deps) {
|
|||
inventoryMarginRankingFollowup ||
|
||||
explicitSummaryBundleReuseSignal ||
|
||||
hasInventoryRootTemporalFollowupPrimary ||
|
||||
hasInventorySupplierOverlapRootFollowup ||
|
||||
mcpDiscoveryOrganizationClarificationContinuation;
|
||||
hasAlternateFollowupSignal = deps.toNonEmptyString(alternateMessage)
|
||||
? deps.hasAddressFollowupContextSignal(alternateMessage) ||
|
||||
|
|
@ -1548,7 +1495,6 @@ export function createAssistantTransitionPolicy(deps) {
|
|||
inventoryMarginRankingFollowup ||
|
||||
explicitSummaryBundleReuseSignal ||
|
||||
hasInventoryRootTemporalFollowupAlternate ||
|
||||
hasInventorySupplierOverlapRootFollowup ||
|
||||
mcpDiscoveryOrganizationClarificationContinuation
|
||||
: false;
|
||||
hasStrongFollowupReference =
|
||||
|
|
@ -1562,7 +1508,6 @@ export function createAssistantTransitionPolicy(deps) {
|
|||
inventoryShortFollowupAlternate ||
|
||||
hasInventoryRootTemporalFollowupPrimary ||
|
||||
hasInventoryRootTemporalFollowupAlternate ||
|
||||
hasInventorySupplierOverlapRootFollowup ||
|
||||
inventoryPurchaseDateVatBridge ||
|
||||
explicitSummaryBundleReuseSignal ||
|
||||
Boolean(debtRoleSwapIntent) ||
|
||||
|
|
@ -1783,7 +1728,6 @@ export function createAssistantTransitionPolicy(deps) {
|
|||
const predecomposeIntent = deps.toNonEmptyString(llmPreDecomposeMeta?.predecomposeContract?.intent);
|
||||
const carryoverTargetIntentPreview = explicitIntent ?? predecomposeIntent ?? previousIntent;
|
||||
if (
|
||||
!selectedCounterpartyDocumentFollowupSignal &&
|
||||
shouldClearStaleDateScopeForCounterpartyDocsRoot(
|
||||
carryoverTargetIntentPreview,
|
||||
sourceIntentHint,
|
||||
|
|
@ -1810,7 +1754,6 @@ export function createAssistantTransitionPolicy(deps) {
|
|||
inventoryShortFollowupAlternate ||
|
||||
hasInventoryRootTemporalFollowupPrimary ||
|
||||
hasInventoryRootTemporalFollowupAlternate ||
|
||||
hasInventorySupplierOverlapRootFollowup ||
|
||||
hasInventoryRootRestatementPrimary ||
|
||||
hasInventoryRootRestatementAlternate ||
|
||||
hasSelectedObjectInventorySignalPrimary ||
|
||||
|
|
@ -1864,7 +1807,6 @@ export function createAssistantTransitionPolicy(deps) {
|
|||
previous_discovery_loop_asked_domain_family: sourceDiscoveryLoopAskedDomainFamily ?? undefined,
|
||||
previous_discovery_loop_asked_action_family: sourceDiscoveryLoopAskedActionFamily ?? undefined,
|
||||
previous_discovery_loop_unsupported_family: sourceDiscoveryLoopUnsupportedFamily ?? undefined,
|
||||
previous_discovery_loop_explicit_date_scope: sourceDiscoveryLoopExplicitDateScope ?? undefined,
|
||||
previous_discovery_loop_metadata_scope_hint: sourceDiscoveryLoopMetadataScopeHint ?? undefined,
|
||||
previous_discovery_loop_subject_resolution_optional:
|
||||
sourceDiscoveryLoopSubjectResolutionOptional || undefined,
|
||||
|
|
|
|||
|
|
@ -1147,84 +1147,12 @@ describe("inventory selected-object follow-up", () => {
|
|||
expect(result?.response_type).toBe("FACTUAL_SUMMARY");
|
||||
expect(result?.debug.detected_intent).toBe("inventory_purchase_to_sale_chain");
|
||||
expect(result?.debug.extracted_filters?.organization).toBe("ООО Альтернатива Плюс");
|
||||
expect(result?.debug.reasons).toContain("organization_scope_normalized_for_inventory_chain");
|
||||
expect(result?.debug.reasons).toContain("organization_restored_from_inventory_chain_context");
|
||||
expect(result?.debug.reasons).toContain("inventory_chain_counterparty_anchor_kept_for_verification");
|
||||
expect(String(result?.reply_text ?? "").split("\n")[0]).toContain("полностью не подтверждена");
|
||||
expect(String(result?.reply_text ?? "")).toContain("ЭталонМебель");
|
||||
});
|
||||
|
||||
it("restores known organization when live chain semantics misclassify buyer as active organization", async () => {
|
||||
executeAddressMcpQueryMock.mockResolvedValueOnce({
|
||||
fetched_rows: 2,
|
||||
matched_rows: 2,
|
||||
raw_rows: [
|
||||
{
|
||||
Период: "2019-12-10T00:00:00Z",
|
||||
Регистратор: "Поступление товаров и услуг 00000000111 от 10.12.2019 12:00:01",
|
||||
СчетДт: "41.01",
|
||||
СчетКт: "",
|
||||
Сумма: 855000,
|
||||
Номенклатура: "Шкаф картотечный 1000*400*2100",
|
||||
Контрагент: "ЭталонМебель",
|
||||
Организация: "ООО \\Альтернатива Плюс\\",
|
||||
Количество: 15
|
||||
},
|
||||
{
|
||||
Период: "2020-04-01T00:00:00Z",
|
||||
Регистратор: "Реализация товаров и услуг 00000000001 от 01.04.2020 0:00:00",
|
||||
СчетДт: "",
|
||||
СчетКт: "41.01",
|
||||
Сумма: 855000,
|
||||
Номенклатура: "Шкаф картотечный 1000*400*2100",
|
||||
Контрагент: "ЭталонМебель",
|
||||
Организация: "ООО \\Альтернатива Плюс\\",
|
||||
Количество: 15
|
||||
}
|
||||
],
|
||||
rows: [],
|
||||
error: null
|
||||
});
|
||||
|
||||
const service = new AddressQueryService();
|
||||
const result = await service.tryHandle(
|
||||
"Есть ли документально подтвержденная цепочка: поставщик Гамма-мебель, ООО -> товар Шкаф картотечный 1000*400*2100 -> покупатель Департамент капитального ремонта города Москвы",
|
||||
{
|
||||
activeOrganization: "Департамент капитального ремонта города Москвы",
|
||||
knownOrganizations: ["ООО \"Альтернатива Плюс\""],
|
||||
analysisDateHint: "2020-03-31",
|
||||
llmSemanticHints: {
|
||||
scope_target_kind: "organization",
|
||||
scope_target_text: "Департамент капитального ремонта города Москвы",
|
||||
date_scope_kind: "missing",
|
||||
self_scope_detected: false,
|
||||
selected_object_scope_detected: false
|
||||
},
|
||||
followupContext: {
|
||||
previous_intent: "inventory_purchase_to_sale_chain",
|
||||
previous_filters: {
|
||||
as_of_date: "2020-03-31",
|
||||
item: "Шкаф картотечный 1000*400*2100",
|
||||
organization: "ООО \"Альтернатива Плюс\""
|
||||
},
|
||||
previous_anchor_type: "item",
|
||||
previous_anchor_value: "Шкаф картотечный 1000*400*2100"
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
expect(result?.handled).toBe(true);
|
||||
expect(result?.response_type).toBe("FACTUAL_SUMMARY");
|
||||
expect(result?.debug.detected_intent).toBe("inventory_purchase_to_sale_chain");
|
||||
expect(result?.debug.extracted_filters?.counterparty).toBe("Гамма-мебель, ООО");
|
||||
expect(result?.debug.extracted_filters?.organization).toBe("ООО Альтернатива Плюс");
|
||||
expect(result?.debug.reasons).toContain("organization_scope_normalized_for_inventory_chain");
|
||||
expect(result?.debug.reasons).toContain("inventory_chain_counterparty_anchor_kept_for_verification");
|
||||
expect(String(result?.reply_text ?? "").split("\n")[0]).toContain("полностью не подтверждена");
|
||||
expect(String(result?.reply_text ?? "").split("\n")[0]).toContain("Гамма-мебель, ООО");
|
||||
expect(String(result?.reply_text ?? "").split("\n")[0]).toContain("Департамент капитального ремонта города Москвы");
|
||||
expect(String(result?.reply_text ?? "")).toContain("ЭталонМебель");
|
||||
});
|
||||
|
||||
it.skip("keeps the full selected item when sale trace is asked in canonical wording after provenance", async () => {
|
||||
executeAddressMcpQueryMock.mockResolvedValueOnce({
|
||||
fetched_rows: 1,
|
||||
|
|
|
|||
|
|
@ -186,35 +186,6 @@ describe("address query shape classifier", () => {
|
|||
expect(extraction.warnings).toContain("supplier_anchor_derived_for_inventory_documentary_chain");
|
||||
});
|
||||
|
||||
it("extracts supplier anchor from normalized supplier-buyer chain without swallowing buyer and item tail", () => {
|
||||
const extraction = extractAddressFilters(
|
||||
"Проверить наличие документально подтвержденной цепочки поставки от поставщика Гамма-мебель, ООО к покупателю Департамент капитального ремонта города Москвы через товар Шкаф картотечный 1000*400*2100",
|
||||
"inventory_purchase_to_sale_chain"
|
||||
);
|
||||
|
||||
expect(extraction.extracted_filters.counterparty).toBe("Гамма-мебель, ООО");
|
||||
expect(extraction.extracted_filters.item).toBe("Шкаф картотечный 1000*400*2100");
|
||||
expect(extraction.warnings).toContain("supplier_anchor_derived_for_inventory_documentary_chain");
|
||||
});
|
||||
|
||||
it("ignores buyer-as-organization llm semantics for supplier-item-buyer inventory chain", () => {
|
||||
const result = runAddressDecomposeStage(
|
||||
"Проверить наличие документально подтвержденной цепочки поставки от поставщика Гамма-мебель, ООО к покупателю Департамент капитального ремонта города Москвы через товар Шкаф картотечный 1000*400*2100",
|
||||
null,
|
||||
{
|
||||
scope_target_kind: "organization",
|
||||
scope_target_text: "Департамент капитального ремонта города Москвы",
|
||||
date_scope_kind: "missing",
|
||||
self_scope_detected: false,
|
||||
selected_object_scope_detected: false
|
||||
}
|
||||
);
|
||||
|
||||
expect(result?.filters.extracted_filters.counterparty).toBe("Гамма-мебель, ООО");
|
||||
expect(result?.filters.extracted_filters.organization).toBeUndefined();
|
||||
expect(result?.filters.warnings).toContain("organization_llm_semantics_ignored_for_inventory_documentary_chain");
|
||||
});
|
||||
|
||||
it("cuts inventory item anchor before purchase-doc residue tail", () => {
|
||||
const filters = extractAddressFilters(
|
||||
"По каким документам был куплен товар Диван трехместный для остатка на складе Основной склад",
|
||||
|
|
@ -6337,18 +6308,11 @@ it("routes old purchase residue questions to aging-by-purchase-date", () => {
|
|||
);
|
||||
|
||||
const firstLine = reply.text.split("\n")[0] ?? "";
|
||||
expect(firstLine).toContain("подтверждено позиций: 2");
|
||||
expect(firstLine).toContain("подтверждены позиции");
|
||||
expect(firstLine).toContain("Столешница 600*3050*26 альмандин");
|
||||
expect(firstLine).toContain("Столешница 600*3050*26 дуб ниагара");
|
||||
expect(firstLine).not.toContain("выявлен поставщик закупочного следа");
|
||||
expect(reply.text).toContain("Позиции:");
|
||||
expect(reply.text.indexOf("Позиции:")).toBeLessThan(reply.text.indexOf("Проверка:"));
|
||||
expect(reply.text).toContain("Столешница 600*3050*26 альмандин");
|
||||
expect(reply.text).toContain("Столешница 600*3050*26 дуб ниагара");
|
||||
expect(reply.text).toContain("Проверка:");
|
||||
expect(reply.text).toContain("Ограничение:");
|
||||
expect(reply.text).toContain("Следующий шаг:");
|
||||
expect(reply.text).not.toContain("Что проверили:");
|
||||
expect(reply.text).not.toContain("Опорные документы:");
|
||||
expect(reply.text).not.toContain("Поступление товаров и услуг");
|
||||
});
|
||||
|
||||
it("routes inventory provenance questions to a dedicated intent", () => {
|
||||
|
|
|
|||
|
|
@ -900,8 +900,6 @@ describe("address reply builders regressions", () => {
|
|||
expect(firstLine).toContain("ВИЗАНТИЯ");
|
||||
expect(text.indexOf(firstLine)).toBeLessThan(text.indexOf("Что проверили:"));
|
||||
expect(text).toContain("однозначная атрибуция части текущего остатка не подтверждена: 1 закупочных операций без явно выделенного поставщика.");
|
||||
expect(text).toContain("Следующий шаг:");
|
||||
expect(text).toContain("проверьте партионный/серийный учет");
|
||||
expect(text).not.toContain("Часть закупочных операций");
|
||||
expect(text.match(/^\d+\./gmu)?.length ?? 0).toBeLessThanOrEqual(3);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -117,28 +117,6 @@ describe("assistant MCP discovery answer adapter", () => {
|
|||
expect(draft.must_not_claim).toContain("Do not claim a confirmed business fact when confirmed_facts is empty.");
|
||||
});
|
||||
|
||||
it("states zero rubles for checked payout searches with no matched rows", async () => {
|
||||
const planner = planAssistantMcpDiscovery({
|
||||
turnMeaning: {
|
||||
asked_domain_family: "counterparty_value",
|
||||
asked_action_family: "payout",
|
||||
explicit_entity_candidates: ["Группа СВК"],
|
||||
explicit_date_scope: "2020",
|
||||
unsupported_but_understood_family: "counterparty_payouts_or_outflow"
|
||||
}
|
||||
});
|
||||
const pilot = await executeAssistantMcpDiscoveryPilot(planner, buildDeps([]));
|
||||
|
||||
const draft = buildAssistantMcpDiscoveryAnswerDraft(pilot);
|
||||
const answerText = [draft.headline, ...draft.confirmed_lines, ...draft.unknown_lines].join("\n");
|
||||
|
||||
expect(draft.answer_mode).toBe("checked_sources_only");
|
||||
expect(answerText).toContain("0 руб.");
|
||||
expect(answerText).toContain("исходящих платежей/списаний не найдено");
|
||||
expect(answerText).toContain("Группа СВК");
|
||||
expect(answerText).toContain("2020");
|
||||
});
|
||||
|
||||
it("turns generic document evidence into a bounded document answer draft", async () => {
|
||||
const planner = planAssistantMcpDiscovery({
|
||||
turnMeaning: {
|
||||
|
|
|
|||
|
|
@ -367,42 +367,6 @@ describe("assistant MCP discovery data need graph", () => {
|
|||
);
|
||||
});
|
||||
|
||||
it("routes broad-overview incoming-vs-outgoing money comparison to value-flow comparison", () => {
|
||||
const result = buildAssistantMcpDiscoveryDataNeedGraph({
|
||||
semanticDataNeed: "business overview evidence with bounded analyst interpretation",
|
||||
rawUtterance:
|
||||
"Хорошо. А что по ООО Альтернатива Плюс больше в 2020 году: входящие или исходящие деньги?",
|
||||
turnMeaning: {
|
||||
asked_domain_family: "business_overview",
|
||||
asked_action_family: "broad_evaluation",
|
||||
unsupported_but_understood_family: "broad_business_evaluation",
|
||||
explicit_entity_candidates: ["Группа СВК", "ООО Альтернатива Плюс больше"],
|
||||
metadata_scope_hint: "Группа СВК",
|
||||
explicit_date_scope: "2020",
|
||||
explicit_organization_scope: "ООО Альтернатива Плюс"
|
||||
}
|
||||
});
|
||||
|
||||
expect(result.business_fact_family).toBe("value_flow");
|
||||
expect(result.subject_candidates).toEqual([]);
|
||||
expect(result.metadata_scope_hint).toBeNull();
|
||||
expect(result.action_family).toBe("net_value_flow");
|
||||
expect(result.comparison_need).toBe("incoming_vs_outgoing");
|
||||
expect(result.clarification_gaps).toEqual([]);
|
||||
expect(result.decomposition_candidates).toEqual([
|
||||
"collect_incoming_movements",
|
||||
"collect_outgoing_movements",
|
||||
"probe_coverage"
|
||||
]);
|
||||
expect(result.reason_codes).toContain(
|
||||
"data_need_graph_business_overview_bidirectional_money_comparison_routed_to_value_flow"
|
||||
);
|
||||
expect(result.reason_codes).toContain(
|
||||
"data_need_graph_organization_scoped_value_comparison_ignores_stale_subject"
|
||||
);
|
||||
expect(result.reason_codes).toContain("data_need_graph_comparison_incoming_vs_outgoing");
|
||||
});
|
||||
|
||||
it("treats a generic incoming total as an understood open-scope ask that still needs organization", () => {
|
||||
const result = buildAssistantMcpDiscoveryDataNeedGraph({
|
||||
semanticDataNeed: "counterparty value-flow evidence",
|
||||
|
|
|
|||
|
|
@ -1170,41 +1170,6 @@ describe("assistant MCP discovery pilot executor", () => {
|
|||
});
|
||||
});
|
||||
|
||||
it("prefers normalized metadata scope hint over inflected entity candidate", async () => {
|
||||
const planner = planAssistantMcpDiscovery({
|
||||
turnMeaning: {
|
||||
asked_domain_family: "metadata",
|
||||
asked_action_family: "inspect_catalog",
|
||||
explicit_entity_candidates: ["контрагентам", "контрагент"],
|
||||
metadata_scope_hint: "контрагент",
|
||||
unsupported_but_understood_family: "1c_metadata_surface"
|
||||
}
|
||||
});
|
||||
const deps = buildMetadataDeps([
|
||||
{
|
||||
FullName: "Справочник.Контрагенты",
|
||||
MetaType: "Справочник",
|
||||
attributes: [{ Name: "Наименование" }, { Name: "ИНН" }]
|
||||
}
|
||||
]);
|
||||
|
||||
const result = await executeAssistantMcpDiscoveryPilot(planner, deps);
|
||||
|
||||
expect(result.evidence.evidence_status).toBe("confirmed");
|
||||
expect(result.derived_metadata_surface).toMatchObject({
|
||||
metadata_scope: "контрагент",
|
||||
requested_meta_types: ["Справочник"],
|
||||
matched_rows: 1,
|
||||
available_entity_sets: ["Справочник"],
|
||||
matched_objects: ["Справочник.Контрагенты"]
|
||||
});
|
||||
expect(deps.executeAddressMcpMetadata).toHaveBeenCalledTimes(1);
|
||||
expect(deps.executeAddressMcpMetadata.mock.calls[0]?.[0]).toMatchObject({
|
||||
meta_type: ["Справочник"],
|
||||
name_mask: "контрагент"
|
||||
});
|
||||
});
|
||||
|
||||
it("executes catalog drilldown through a narrowed metadata probe seeded from the confirmed surface object", async () => {
|
||||
const planner = planAssistantMcpDiscovery({
|
||||
metadataSurface: {
|
||||
|
|
@ -1785,75 +1750,6 @@ describe("assistant MCP discovery pilot executor", () => {
|
|||
expect(deps.executeAddressMcpQuery).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
|
||||
it("executes organization-scoped value-flow comparison without stale counterparty subject", async () => {
|
||||
const planner = planAssistantMcpDiscovery({
|
||||
dataNeedGraph: {
|
||||
schema_version: "assistant_data_need_graph_v1",
|
||||
policy_owner: "assistantMcpDiscoveryDataNeedGraph",
|
||||
subject_candidates: [],
|
||||
metadata_scope_hint: null,
|
||||
business_fact_family: "value_flow",
|
||||
action_family: "net_value_flow",
|
||||
aggregation_need: null,
|
||||
time_scope_need: "explicit_period",
|
||||
comparison_need: "incoming_vs_outgoing",
|
||||
ranking_need: null,
|
||||
proof_expectation: "coverage_checked_fact",
|
||||
clarification_gaps: [],
|
||||
decomposition_candidates: ["collect_incoming_movements", "collect_outgoing_movements", "probe_coverage"],
|
||||
forbidden_overclaim_flags: ["no_raw_model_claims", "no_unchecked_fact_totals"],
|
||||
reason_codes: [
|
||||
"data_need_graph_built",
|
||||
"data_need_graph_family_value_flow",
|
||||
"data_need_graph_comparison_incoming_vs_outgoing",
|
||||
"data_need_graph_organization_scoped_value_comparison_ignores_stale_subject"
|
||||
]
|
||||
},
|
||||
turnMeaning: {
|
||||
asked_domain_family: "business_overview",
|
||||
asked_action_family: "broad_evaluation",
|
||||
explicit_entity_candidates: ["SVK", "OOO Alternative Plus more"],
|
||||
metadata_scope_hint: "SVK",
|
||||
explicit_organization_scope: "OOO Alternative Plus",
|
||||
explicit_date_scope: "2020",
|
||||
unsupported_but_understood_family: "broad_business_evaluation"
|
||||
}
|
||||
});
|
||||
const deps = buildSequentialDeps([
|
||||
{
|
||||
rows: [{ Period: "2020-01-15T00:00:00", Amount: 10000, Organization: "OOO Alternative Plus" }]
|
||||
},
|
||||
{
|
||||
rows: [{ Period: "2020-03-10T00:00:00", Amount: 4000, Organization: "OOO Alternative Plus" }]
|
||||
}
|
||||
]);
|
||||
|
||||
const result = await executeAssistantMcpDiscoveryPilot(planner, deps);
|
||||
|
||||
expect(result.pilot_scope).toBe("counterparty_bidirectional_value_flow_query_movements_v1");
|
||||
expect(result.derived_value_flow).toBeNull();
|
||||
expect(result.derived_bidirectional_value_flow).toMatchObject({
|
||||
counterparty: null,
|
||||
period_scope: "2020",
|
||||
net_amount: 6000,
|
||||
net_direction: "net_incoming",
|
||||
incoming_customer_revenue: {
|
||||
rows_matched: 1,
|
||||
total_amount: 10000
|
||||
},
|
||||
outgoing_supplier_payout: {
|
||||
rows_matched: 1,
|
||||
total_amount: 4000
|
||||
}
|
||||
});
|
||||
expect(result.reason_codes).toContain("pilot_bidirectional_value_flow_recipes_selected");
|
||||
expect(deps.executeAddressMcpQuery).toHaveBeenCalledTimes(2);
|
||||
const serializedCalls = deps.executeAddressMcpQuery.mock.calls
|
||||
.map((call) => JSON.stringify(call[0]))
|
||||
.join("\n");
|
||||
expect(serializedCalls).not.toContain("SVK");
|
||||
});
|
||||
|
||||
it("prefers clean counterparty candidates for bidirectional net value-flow probes", async () => {
|
||||
const planner = planAssistantMcpDiscovery({
|
||||
turnMeaning: {
|
||||
|
|
|
|||
|
|
@ -1280,10 +1280,6 @@ describe("assistant MCP discovery response candidate", () => {
|
|||
);
|
||||
|
||||
expect(candidate.reply_text?.split("\n")[0]).toContain("по компании ООО Альтернатива Плюс за период 2020");
|
||||
expect(candidate.reply_text).toContain("Основа: проверенные входящие платежи и исходящие платежи по этой компании в 1С.");
|
||||
expect(candidate.reply_text).toContain("нетто в сторону входящих платежей: 3 865 501,50 руб.");
|
||||
expect(candidate.reply_text).not.toContain("Коротко:");
|
||||
expect(candidate.reply_text).not.toContain("по этому контрагенту");
|
||||
expect(candidate.reply_text).not.toContain("по контрагенту запрошенному контрагенту");
|
||||
});
|
||||
|
||||
|
|
@ -1922,7 +1918,7 @@ describe("assistant MCP discovery response candidate", () => {
|
|||
);
|
||||
|
||||
const firstLine = candidate.reply_text?.split("\n")[0] ?? "";
|
||||
expect(firstLine).not.toContain("Коротко:");
|
||||
expect(firstLine).toContain("Коротко:");
|
||||
expect(firstLine).toContain("SVK");
|
||||
expect(firstLine).toContain("получили 12 500,50 руб.");
|
||||
expect(firstLine).toContain("заплатили 4 000 руб.");
|
||||
|
|
|
|||
|
|
@ -57,7 +57,6 @@ describe("assistant MCP discovery response policy", () => {
|
|||
mcpDiscoveryRuntimeEntryPoint: entryPoint({
|
||||
bridge: {
|
||||
bridge_status: "answer_draft_ready",
|
||||
hot_runtime_wired: true,
|
||||
user_facing_response_allowed: true,
|
||||
business_fact_answer_allowed: true,
|
||||
requires_user_clarification: false,
|
||||
|
|
@ -80,126 +79,6 @@ describe("assistant MCP discovery response policy", () => {
|
|||
expect(result.reply_text).not.toContain("Коротко:");
|
||||
});
|
||||
|
||||
it("keeps current organization clarification over a checked-sources value-flow candidate", () => {
|
||||
const result = applyAssistantMcpDiscoveryResponsePolicy({
|
||||
currentReply:
|
||||
"Нужно уточнить организацию, чтобы не смешивать компании в одном ответе.\nСейчас в доступном контуре вижу такие организации:\n- ООО Альтернатива Плюс\n- ООО Ромашка",
|
||||
currentReplySource: "address_query_runtime_v1",
|
||||
currentReplyType: "partial_coverage",
|
||||
addressRuntimeMeta: {
|
||||
truth_mode: "clarification_required",
|
||||
answer_shape: "clarification_required",
|
||||
answer_shape_contract: {
|
||||
answer_shape: "clarification_required",
|
||||
reply_type: "partial_coverage"
|
||||
},
|
||||
assistant_mcp_discovery_entry_point_v1: entryPoint({
|
||||
turn_input: {
|
||||
adapter_status: "ready",
|
||||
should_run_discovery: true,
|
||||
data_need_graph: {
|
||||
business_fact_family: "value_flow",
|
||||
subject_candidates: [],
|
||||
clarification_gaps: ["organization"],
|
||||
reason_codes: ["data_need_graph_open_scope_total_needs_organization"]
|
||||
}
|
||||
},
|
||||
bridge: {
|
||||
bridge_status: "checked_sources_only",
|
||||
user_facing_response_allowed: true,
|
||||
business_fact_answer_allowed: false,
|
||||
requires_user_clarification: true,
|
||||
answer_draft: {
|
||||
answer_mode: "checked_sources_only",
|
||||
headline: "В проверенном срезе 1С входящих денежных поступлений не найдено.",
|
||||
confirmed_lines: ["В проверенном срезе 1С входящих денежных поступлений не найдено."],
|
||||
inference_lines: [],
|
||||
unknown_lines: [],
|
||||
limitation_lines: [],
|
||||
next_step_line: null
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
});
|
||||
|
||||
expect(result.applied).toBe(false);
|
||||
expect(result.reply_text).toContain("Нужно уточнить организацию");
|
||||
expect(result.reply_text).toContain("ООО Альтернатива Плюс");
|
||||
expect(result.reply_text).not.toContain("не найдено");
|
||||
expect(result.reason_codes).toContain("mcp_discovery_response_policy_keep_current_clarification_required_reply");
|
||||
});
|
||||
|
||||
it("applies a grounded movement candidate over a stale inventory item clarification", () => {
|
||||
const result = applyAssistantMcpDiscoveryResponsePolicy({
|
||||
currentReply: "Need an item before I can continue.",
|
||||
currentReplySource: "address_query_runtime_v1",
|
||||
currentReplyType: "partial_coverage",
|
||||
addressRuntimeMeta: {
|
||||
truth_mode: "clarification_required",
|
||||
answer_shape: "clarification_required",
|
||||
answer_shape_contract: {
|
||||
answer_shape: "clarification_required",
|
||||
reply_type: "partial_coverage",
|
||||
capability_contract_id: "inventory_inventory_purchase_documents_for_item"
|
||||
},
|
||||
detected_intent: "inventory_purchase_documents_for_item",
|
||||
selected_recipe: "address_inventory_purchase_documents_for_item_v1",
|
||||
capability_id: "inventory_inventory_purchase_documents_for_item",
|
||||
missing_required_filters: ["item"],
|
||||
assistant_mcp_discovery_entry_point_v1: entryPoint({
|
||||
turn_input: {
|
||||
adapter_status: "ready",
|
||||
should_run_discovery: true,
|
||||
turn_meaning_ref: {
|
||||
asked_domain_family: "movements",
|
||||
asked_action_family: "list_movements",
|
||||
explicit_date_scope: "2020"
|
||||
},
|
||||
data_need_graph: {
|
||||
business_fact_family: "movement_evidence",
|
||||
action_family: "list_movements"
|
||||
}
|
||||
},
|
||||
bridge: {
|
||||
bridge_status: "answer_draft_ready",
|
||||
user_facing_response_allowed: true,
|
||||
business_fact_answer_allowed: true,
|
||||
requires_user_clarification: false,
|
||||
route_candidate: {
|
||||
selected_chain_id: "movement_evidence"
|
||||
},
|
||||
pilot: {
|
||||
selected_chain_id: "movement_evidence",
|
||||
mcp_execution_performed: true
|
||||
},
|
||||
execution_handoff: {
|
||||
mcp_execution_performed: true,
|
||||
can_use_guarded_response: false
|
||||
},
|
||||
answer_draft: {
|
||||
answer_mode: "bounded_inference_only",
|
||||
headline: "Movement evidence for Group SVK in 2020 is bounded.",
|
||||
confirmed_lines: ["No confirmed movement rows were found in the checked 2020 window."],
|
||||
inference_lines: [],
|
||||
unknown_lines: [],
|
||||
limitation_lines: ["Full movement coverage outside the checked window is not confirmed."],
|
||||
next_step_line: null
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
});
|
||||
|
||||
expect(result.applied).toBe(true);
|
||||
expect(result.reply_text).toContain("Movement evidence for Group SVK in 2020 is bounded.");
|
||||
expect(result.reply_text).not.toContain("Need an item");
|
||||
expect(result.reason_codes).toContain(
|
||||
"mcp_discovery_response_policy_grounded_movement_candidate_priority_over_inventory_item_clarification"
|
||||
);
|
||||
expect(result.reason_codes).toContain("mcp_discovery_response_policy_candidate_applied");
|
||||
});
|
||||
|
||||
it("keeps the current reply when the turn is not an unsupported boundary", () => {
|
||||
const result = applyAssistantMcpDiscoveryResponsePolicy({
|
||||
currentReply: "regular chat",
|
||||
|
|
@ -357,7 +236,6 @@ describe("assistant MCP discovery response policy", () => {
|
|||
},
|
||||
bridge: {
|
||||
bridge_status: "answer_draft_ready",
|
||||
hot_runtime_wired: true,
|
||||
user_facing_response_allowed: true,
|
||||
business_fact_answer_allowed: true,
|
||||
requires_user_clarification: false,
|
||||
|
|
@ -463,68 +341,6 @@ describe("assistant MCP discovery response policy", () => {
|
|||
expect(result.reason_codes).not.toContain("mcp_discovery_response_policy_keep_exact_matched_factual_address_reply");
|
||||
});
|
||||
|
||||
it("uses a movement clarification candidate over a stale inventory-item clarification", () => {
|
||||
const result = applyAssistantMcpDiscoveryResponsePolicy({
|
||||
currentReply: "Need item before I can continue.",
|
||||
currentReplySource: "address_query_runtime_v1",
|
||||
currentReplyType: "partial_coverage",
|
||||
addressRuntimeMeta: {
|
||||
detected_intent: "inventory_purchase_documents_for_item",
|
||||
selected_recipe: "address_inventory_purchase_documents_for_item_v1",
|
||||
capability_id: "inventory_inventory_purchase_documents_for_item",
|
||||
truth_mode: "clarification_required",
|
||||
answer_shape: "clarification_required",
|
||||
missing_required_filters: ["item"],
|
||||
answer_shape_contract: {
|
||||
answer_shape: "clarification_required",
|
||||
reply_type: "partial_coverage",
|
||||
capability_contract_id: "inventory_inventory_purchase_documents_for_item"
|
||||
},
|
||||
assistant_mcp_discovery_entry_point_v1: entryPoint({
|
||||
turn_input: {
|
||||
adapter_status: "ready",
|
||||
should_run_discovery: true,
|
||||
turn_meaning_ref: {
|
||||
asked_domain_family: "movements",
|
||||
asked_action_family: "list_movements",
|
||||
explicit_entity_candidates: ["Group SVK"],
|
||||
unsupported_but_understood_family: "movement_evidence"
|
||||
},
|
||||
data_need_graph: {
|
||||
business_fact_family: "movement_evidence",
|
||||
action_family: "list_movements",
|
||||
clarification_gaps: ["period"]
|
||||
}
|
||||
},
|
||||
bridge: {
|
||||
bridge_status: "needs_clarification",
|
||||
user_facing_response_allowed: true,
|
||||
business_fact_answer_allowed: false,
|
||||
requires_user_clarification: true,
|
||||
answer_draft: {
|
||||
answer_mode: "needs_clarification",
|
||||
headline: "I can continue by movements for Group SVK, but I need a period.",
|
||||
confirmed_lines: [],
|
||||
inference_lines: [],
|
||||
unknown_lines: ["The period is required before searching movements."],
|
||||
limitation_lines: [],
|
||||
next_step_line: "Specify the period and I will search movements for Group SVK."
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
});
|
||||
|
||||
expect(result.applied).toBe(true);
|
||||
expect(result.decision).toBe("apply_candidate");
|
||||
expect(result.reply_text).toContain("movements for Group SVK");
|
||||
expect(result.reply_text).not.toContain("Need item");
|
||||
expect(result.reason_codes).toContain(
|
||||
"mcp_discovery_response_policy_clarification_candidate_priority_over_stale_address_clarification"
|
||||
);
|
||||
expect(result.reason_codes).not.toContain("mcp_discovery_response_policy_keep_current_clarification_required_reply");
|
||||
});
|
||||
|
||||
it("overrides exact document-list replies for explicit counterparty received-paid-net questions", () => {
|
||||
const result = applyAssistantMcpDiscoveryResponsePolicy({
|
||||
currentReply: "Контрагент: Группа СВК. Найдено документов: 19.",
|
||||
|
|
@ -1322,107 +1138,6 @@ describe("assistant MCP discovery response policy", () => {
|
|||
expect(result.reason_codes).toContain("mcp_discovery_response_policy_open_scope_value_flow_candidate_priority");
|
||||
});
|
||||
|
||||
it("overrides a year-ranking exact reply when the user asked who brought the most money", () => {
|
||||
const result = applyAssistantMcpDiscoveryResponsePolicy({
|
||||
currentReply:
|
||||
"Самый доходный год по подтвержденным поступлениям: 2020 (47.628.853,03 ₽ по 44 операциям).\nТоп-1 лет по сумме поступлений:\n1. 2020 | сумма: 47.628.853,03 ₽ | операций: 44 | контрагентов: 13",
|
||||
currentReplySource: "address_query_runtime_v1",
|
||||
currentReplyType: "factual",
|
||||
addressRuntimeMeta: {
|
||||
detected_intent: "customer_revenue_and_payments",
|
||||
selected_recipe: "address_customer_revenue_and_payments_v1",
|
||||
mcp_call_status: "matched_non_empty",
|
||||
truth_mode: "confirmed",
|
||||
capability_binding_status: "bound",
|
||||
capability_binding_violations: [],
|
||||
dialogContinuationContract: {
|
||||
target_intent: "customer_revenue_and_payments"
|
||||
},
|
||||
truth_gate_contract_status: "full_confirmed",
|
||||
assistant_truth_answer_policy_v1: {
|
||||
truth_gate: {
|
||||
coverage_status: "full",
|
||||
grounding_status: "grounded",
|
||||
source_truth_gate_status: "full_confirmed"
|
||||
}
|
||||
},
|
||||
assistant_mcp_discovery_entry_point_v1: entryPoint({
|
||||
turn_input: {
|
||||
adapter_status: "ready",
|
||||
should_run_discovery: true,
|
||||
data_need_graph: {
|
||||
business_fact_family: "value_flow",
|
||||
subject_candidates: ["Группа СВК"],
|
||||
ranking_need: "top_desc",
|
||||
reason_codes: ["data_need_graph_built", "data_need_graph_ranking_top_desc"]
|
||||
},
|
||||
turn_meaning_ref: {
|
||||
raw_message: "И кто больше всего принес денег этой организации в 2020 году?",
|
||||
effective_message: "Определить, какой контрагент принёс наибольшую выручку организации в 2020 году",
|
||||
asked_domain_family: "counterparty_value",
|
||||
asked_action_family: "turnover",
|
||||
explicit_entity_candidates: ["Группа СВК"],
|
||||
explicit_organization_scope: "ООО Альтернатива Плюс",
|
||||
explicit_date_scope: "2020"
|
||||
}
|
||||
},
|
||||
bridge: {
|
||||
bridge_status: "answer_draft_ready",
|
||||
user_facing_response_allowed: true,
|
||||
business_fact_answer_allowed: true,
|
||||
requires_user_clarification: false,
|
||||
route_candidate: {
|
||||
selected_chain_id: "value_flow_ranking",
|
||||
nearest_catalog_chain_template: "value_flow_ranking"
|
||||
},
|
||||
execution_handoff: {
|
||||
selected_chain_id: "value_flow_ranking",
|
||||
mcp_execution_performed: true,
|
||||
can_use_guarded_response: false,
|
||||
must_keep_internal_mechanics_hidden: true
|
||||
},
|
||||
pilot: {
|
||||
pilot_scope: "counterparty_value_flow_query_movements_v1",
|
||||
mcp_execution_performed: true,
|
||||
evidence: {
|
||||
evidence_status: "confirmed",
|
||||
coverage_status: "full",
|
||||
answer_permission: "confirmed_answer",
|
||||
confirmed_facts: [
|
||||
"1C incoming value-flow rows were ranked by counterparty for the checked scope; leader=Группа СВК, rows_with_amount=13"
|
||||
]
|
||||
}
|
||||
},
|
||||
answer_draft: {
|
||||
answer_mode: "confirmed_with_bounded_inference",
|
||||
headline: "По данным 1С можно построить ограниченный рейтинг по контрагентам.",
|
||||
confirmed_lines: [
|
||||
"В проверенных входящих поступлениях найден один контрагент Группа СВК по организации ООО Альтернатива Плюс за период 2020: 12 093 465 руб. по 13 строкам с суммой."
|
||||
],
|
||||
inference_lines: [
|
||||
"Рейтинг по контрагентам по организации ООО Альтернатива Плюс за период 2020 рассчитан только по подтвержденным строкам 1С."
|
||||
],
|
||||
unknown_lines: ["Полный рейтинг контрагентов вне периода 2020 этим поиском не подтвержден."],
|
||||
limitation_lines: [],
|
||||
next_step_line: null
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
});
|
||||
|
||||
expect(result.applied).toBe(true);
|
||||
expect(result.decision).toBe("apply_candidate");
|
||||
expect(result.reply_text).toContain("Группа СВК");
|
||||
expect(result.reply_text).toContain("12 093 465 руб.");
|
||||
expect(result.reply_text).not.toContain("Самый доходный год");
|
||||
expect(result.reply_text).not.toContain("Топ-1 лет");
|
||||
expect(result.reason_codes).toContain(
|
||||
"mcp_discovery_response_policy_grounded_value_flow_ranking_candidate_priority_over_current_exact_reply"
|
||||
);
|
||||
expect(result.reason_codes).toContain("mcp_discovery_response_policy_candidate_applied");
|
||||
});
|
||||
|
||||
it("keeps full-confirmed factual address replies even when discovery has a guarded candidate", () => {
|
||||
const result = applyAssistantMcpDiscoveryResponsePolicy({
|
||||
currentReply: "ООО Ромашка | сумма: 128000 | операций: 3",
|
||||
|
|
@ -1794,95 +1509,4 @@ describe("assistant MCP discovery response policy", () => {
|
|||
"mcp_discovery_response_policy_keep_broad_business_summary_over_clarification_candidate"
|
||||
);
|
||||
});
|
||||
|
||||
it("lets a grounded bidirectional value-flow candidate override a stale address clarification reply", () => {
|
||||
const result = applyAssistantMcpDiscoveryResponsePolicy({
|
||||
currentReply:
|
||||
"Проверка данных за 2020 год по контрагенту «Группа СВК» не удалась — такой точный или аналогичный вариант имени не найден.",
|
||||
currentReplySource: "address_query_runtime_v1",
|
||||
currentReplyType: "partial_coverage",
|
||||
addressRuntimeMeta: {
|
||||
truth_mode: "clarification_required",
|
||||
answer_shape: "clarification_required",
|
||||
detected_intent: "counterparty_population_and_roles",
|
||||
selected_recipe: "address_counterparty_population_roles_v1",
|
||||
assistant_mcp_discovery_entry_point_v1: entryPoint({
|
||||
hot_runtime_wired: true,
|
||||
turn_input: {
|
||||
adapter_status: "ready",
|
||||
should_run_discovery: true,
|
||||
turn_meaning_ref: {
|
||||
asked_domain_family: "counterparty_value",
|
||||
asked_action_family: "net_value_flow",
|
||||
explicit_entity_candidates: ["Группа СВК"],
|
||||
explicit_date_scope: "2020",
|
||||
unsupported_but_understood_family: "counterparty_bidirectional_value_flow_or_netting"
|
||||
},
|
||||
data_need_graph: {
|
||||
business_fact_family: "value_flow",
|
||||
action_family: "net_value_flow",
|
||||
comparison_need: "incoming_vs_outgoing",
|
||||
clarification_gaps: []
|
||||
}
|
||||
},
|
||||
bridge: {
|
||||
bridge_status: "answer_draft_ready",
|
||||
hot_runtime_wired: true,
|
||||
user_facing_response_allowed: true,
|
||||
business_fact_answer_allowed: true,
|
||||
requires_user_clarification: false,
|
||||
route_candidate: {
|
||||
selected_chain_id: "value_flow_comparison"
|
||||
},
|
||||
execution_handoff: {
|
||||
mcp_execution_performed: true,
|
||||
can_use_guarded_response: true,
|
||||
must_keep_internal_mechanics_hidden: true
|
||||
},
|
||||
pilot: {
|
||||
pilot_scope: "counterparty_bidirectional_value_flow_query_movements_v1",
|
||||
mcp_execution_performed: true,
|
||||
derived_bidirectional_value_flow: {
|
||||
counterparty: "Группа СВК",
|
||||
period_scope: "2020",
|
||||
net_amount_human_ru: "12 093 465 руб.",
|
||||
net_direction: "net_incoming",
|
||||
coverage_limited_by_probe_limit: false,
|
||||
incoming_customer_revenue: {
|
||||
total_amount_human_ru: "12 093 465 руб."
|
||||
},
|
||||
outgoing_supplier_payout: {
|
||||
total_amount_human_ru: "0 руб."
|
||||
}
|
||||
}
|
||||
},
|
||||
answer_draft: {
|
||||
answer_mode: "confirmed_with_bounded_inference",
|
||||
headline: "Двусторонний денежный поток подтвержден.",
|
||||
confirmed_lines: [
|
||||
"1C bidirectional value-flow rows were checked for counterparty Группа СВК: incoming=found, outgoing=found"
|
||||
],
|
||||
inference_lines: [
|
||||
"Counterparty net value-flow was calculated as incoming confirmed 1C rows minus outgoing confirmed 1C rows"
|
||||
],
|
||||
unknown_lines: ["Full bidirectional value-flow outside the checked period is not proven by this MCP discovery pilot"],
|
||||
limitation_lines: [],
|
||||
next_step_line: null
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
});
|
||||
|
||||
expect(result.applied).toBe(true);
|
||||
expect(result.decision).toBe("apply_candidate");
|
||||
expect(result.reply_text).toContain("Группа СВК");
|
||||
expect(result.reply_text).toContain("получили 12 093 465 руб.");
|
||||
expect(result.reply_text).toContain("заплатили 0 руб.");
|
||||
expect(result.reply_text).not.toContain("не найден");
|
||||
expect(result.reason_codes).toContain(
|
||||
"mcp_discovery_response_policy_grounded_value_flow_candidate_priority_over_current_clarification"
|
||||
);
|
||||
expect(result.reason_codes).not.toContain("mcp_discovery_response_policy_keep_current_clarification_required_reply");
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -88,36 +88,6 @@ describe("assistant MCP discovery turn input adapter", () => {
|
|||
expect(result.turn_meaning_ref?.explicit_organization_scope).toBeUndefined();
|
||||
});
|
||||
|
||||
it("does not treat no-counterparty organization scope as a counterparty entity", () => {
|
||||
const result = buildAssistantMcpDiscoveryTurnInput({
|
||||
userMessage:
|
||||
"Хочу быстрый денежный срез по одной организации без привязки к контрагенту. Сколько вообще входящих денег было за 2020 год?",
|
||||
assistantTurnMeaning: {
|
||||
asked_domain_family: "counterparty_value",
|
||||
asked_action_family: "counterparty_value_or_turnover",
|
||||
explicit_entity_candidates: ["одной организации без привязки"],
|
||||
explicit_organization_scope: "без привязки к контрагенту",
|
||||
explicit_date_scope: "2020",
|
||||
unsupported_but_understood_family: "counterparty_value_or_turnover",
|
||||
stale_replay_forbidden: true
|
||||
},
|
||||
predecomposeContract: {
|
||||
entities: {},
|
||||
period: { period_from: "2020-01-01", period_to: "2020-12-31" }
|
||||
},
|
||||
knownOrganizations: ["ООО Альтернатива Плюс", "ООО Ромашка"]
|
||||
});
|
||||
|
||||
expect(result.adapter_status).toBe("ready");
|
||||
expect(result.data_need_graph?.business_fact_family).toBe("value_flow");
|
||||
expect(result.data_need_graph?.subject_candidates).toEqual([]);
|
||||
expect(result.data_need_graph?.clarification_gaps).toContain("organization");
|
||||
expect(result.turn_meaning_ref?.explicit_entity_candidates).toBeUndefined();
|
||||
expect(result.turn_meaning_ref?.explicit_organization_scope).toBeUndefined();
|
||||
expect(result.turn_meaning_ref?.explicit_date_scope).toBe("2020");
|
||||
expect(result.reason_codes).not.toContain("mcp_discovery_counterparty_from_raw_scope");
|
||||
});
|
||||
|
||||
it("keeps payout wording as outgoing supplier-payout discovery", () => {
|
||||
const result = buildAssistantMcpDiscoveryTurnInput({
|
||||
userMessage: "сколько мы заплатили Группа СВК за 2020 год?",
|
||||
|
|
@ -838,54 +808,6 @@ describe("assistant MCP discovery turn input adapter", () => {
|
|||
expect(result.reason_codes).not.toContain("mcp_discovery_not_applicable_for_supported_exact_turn");
|
||||
});
|
||||
|
||||
it("prefers semantic loop date scope over technical current-date filters for net value-flow follow-ups", () => {
|
||||
const counterpartyName = "\u0413\u0440\u0443\u043f\u043f\u0430 \u0421\u0412\u041a";
|
||||
const organizationName =
|
||||
"\u041e\u041e\u041e \u0410\u043b\u044c\u0442\u0435\u0440\u043d\u0430\u0442\u0438\u0432\u0430 \u041f\u043b\u044e\u0441";
|
||||
const result = buildAssistantMcpDiscoveryTurnInput({
|
||||
userMessage: "\u0430 \u043a\u0430\u043a\u043e\u0435 \u043d\u0435\u0442\u0442\u043e?",
|
||||
assistantTurnMeaning: {
|
||||
asked_domain_family: "counterparty",
|
||||
asked_action_family: "turnover",
|
||||
explicit_intent_candidate: "customer_revenue_and_payments"
|
||||
},
|
||||
followupContext: {
|
||||
previous_discovery_pilot_scope: "counterparty_supplier_payout_query_movements_v1",
|
||||
previous_discovery_loop_status: "ready_for_next_hop",
|
||||
previous_discovery_loop_selected_chain_id: "value_flow",
|
||||
previous_discovery_loop_asked_domain_family: "counterparty_value",
|
||||
previous_discovery_loop_asked_action_family: "payout",
|
||||
previous_discovery_loop_explicit_date_scope: "2020",
|
||||
previous_discovery_loop_metadata_scope_hint: counterpartyName,
|
||||
previous_filters: {
|
||||
counterparty: counterpartyName,
|
||||
organization: organizationName,
|
||||
period_from: "2020-01-01",
|
||||
period_to: "2026-06-03"
|
||||
},
|
||||
previous_anchor_type: "counterparty",
|
||||
previous_anchor_value: counterpartyName
|
||||
}
|
||||
});
|
||||
|
||||
expect(result.adapter_status).toBe("ready");
|
||||
expect(result.should_run_discovery).toBe(true);
|
||||
expect(result.semantic_data_need).toBe("counterparty value-flow evidence");
|
||||
expect(result.turn_meaning_ref).toMatchObject({
|
||||
asked_domain_family: "counterparty_value",
|
||||
asked_action_family: "net_value_flow",
|
||||
explicit_entity_candidates: [counterpartyName],
|
||||
explicit_organization_scope: organizationName,
|
||||
explicit_date_scope: "2020",
|
||||
unsupported_but_understood_family: "counterparty_bidirectional_value_flow_or_netting",
|
||||
stale_replay_forbidden: true
|
||||
});
|
||||
expect(result.data_need_graph?.comparison_need).toBe("incoming_vs_outgoing");
|
||||
expect(result.reason_codes).toContain("mcp_discovery_grounded_value_flow_followup");
|
||||
expect(result.reason_codes).toContain("mcp_discovery_date_scope_from_followup_context");
|
||||
expect(result.reason_codes).toContain("mcp_discovery_bidirectional_value_flow_signal_detected");
|
||||
});
|
||||
|
||||
it("prefers grounded discovery metadata scope over stale conflicting counterparty in a short net follow-up", () => {
|
||||
const result = buildAssistantMcpDiscoveryTurnInput({
|
||||
userMessage: "а какое нетто?",
|
||||
|
|
|
|||
|
|
@ -153,133 +153,6 @@ describe("assistantTransitionPolicy", () => {
|
|||
expect(carryover?.followupContext?.root_intent).toBe("inventory_on_hand_as_of_date");
|
||||
});
|
||||
|
||||
it("carries inventory root date into supplier-overlap current-stock follow-up", () => {
|
||||
const organization = "\u041e\u041e\u041e \"\u0410\u043b\u044c\u0442\u0435\u0440\u043d\u0430\u0442\u0438\u0432\u0430 \u041f\u043b\u044e\u0441\"";
|
||||
const warehouse = "\u041e\u0441\u043d\u043e\u0432\u043d\u043e\u0439 \u0441\u043a\u043b\u0430\u0434";
|
||||
const rootFilters = {
|
||||
period_from: "2021-09-01",
|
||||
period_to: "2021-09-30",
|
||||
as_of_date: "2021-09-30",
|
||||
organization,
|
||||
warehouse
|
||||
};
|
||||
const policy = buildPolicy({
|
||||
findLastAddressAssistantItem: () => ({
|
||||
text: "\u041d\u0430 30.09.2021 \u043f\u043e \u0441\u043a\u043b\u0430\u0434\u0443 \u041e\u0441\u043d\u043e\u0432\u043d\u043e\u0439 \u0441\u043a\u043b\u0430\u0434 \u043f\u043e\u0434\u0442\u0432\u0435\u0440\u0436\u0434\u0435\u043d\u044b \u043e\u0441\u0442\u0430\u0442\u043a\u0438.",
|
||||
debug: {
|
||||
detected_intent: "inventory_on_hand_as_of_date",
|
||||
selected_recipe: "address_inventory_on_hand_as_of_date_v1",
|
||||
extracted_filters: rootFilters,
|
||||
anchor_type: "warehouse",
|
||||
anchor_value_resolved: warehouse
|
||||
}
|
||||
}),
|
||||
findRecentInventoryRootFrame: () => ({
|
||||
intent: "inventory_on_hand_as_of_date",
|
||||
filters: rootFilters,
|
||||
anchorType: "warehouse",
|
||||
anchorValue: warehouse,
|
||||
currentFrameKind: "inventory_root"
|
||||
}),
|
||||
hasAddressFollowupContextSignal: () => false,
|
||||
hasStandaloneAddressTopicSignal: () => true,
|
||||
resolveAddressIntent: () => ({ intent: "inventory_supplier_stock_overlap_as_of_date" }),
|
||||
resolveAddressIntentFamily: (intent: unknown) =>
|
||||
String(intent ?? "").startsWith("inventory_") ? "inventory" : null
|
||||
});
|
||||
|
||||
const carryover = policy.resolveAddressFollowupCarryoverContext(
|
||||
"\u0423 \u043a\u0430\u043a\u043e\u0433\u043e \u043f\u043e\u0441\u0442\u0430\u0432\u0449\u0438\u043a\u0430 \u0431\u044b\u043b\u0438 \u043a\u0443\u043f\u043b\u0435\u043d\u044b \u0442\u043e\u0432\u0430\u0440\u044b, \u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u0441\u0435\u0439\u0447\u0430\u0441 \u043b\u0435\u0436\u0430\u0442 \u043d\u0430 \u0441\u043a\u043b\u0430\u0434\u0435 \u041e\u0441\u043d\u043e\u0432\u043d\u043e\u0439 \u0441\u043a\u043b\u0430\u0434",
|
||||
[],
|
||||
"\u041e\u043f\u0440\u0435\u0434\u0435\u043b\u0438\u0442\u044c \u043f\u043e\u0441\u0442\u0430\u0432\u0449\u0438\u043a\u0430 \u0442\u043e\u0432\u0430\u0440\u043e\u0432, \u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u043d\u0430\u0445\u043e\u0434\u044f\u0442\u0441\u044f \u043d\u0430 \u0441\u043a\u043b\u0430\u0434\u0435 \u041e\u0441\u043d\u043e\u0432\u043d\u043e\u0439 \u0441\u043a\u043b\u0430\u0434 \u0432 \u0442\u0435\u043a\u0443\u0449\u0438\u0439 \u043c\u043e\u043c\u0435\u043d\u0442",
|
||||
{
|
||||
predecomposeContract: {
|
||||
intent: "inventory_supplier_stock_overlap_as_of_date"
|
||||
}
|
||||
},
|
||||
{
|
||||
session_context: {
|
||||
date_scope: rootFilters,
|
||||
organization_scope: organization,
|
||||
active_focus_object: {
|
||||
object_type: "organization",
|
||||
label: organization
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
expect(carryover?.followupContext?.previous_intent).toBe("inventory_on_hand_as_of_date");
|
||||
expect(carryover?.followupContext?.target_intent).toBe("inventory_supplier_stock_overlap_as_of_date");
|
||||
expect(carryover?.followupContext?.previous_filters).toMatchObject(rootFilters);
|
||||
expect(carryover?.followupContext?.root_intent).toBe("inventory_on_hand_as_of_date");
|
||||
expect(carryover?.followupContext?.root_filters).toMatchObject(rootFilters);
|
||||
expect(carryover?.followupContext?.current_frame_kind).toBe("inventory_root");
|
||||
});
|
||||
|
||||
it("keeps supplier-overlap date on repeated current-residue supplier wording", () => {
|
||||
const organization = "\u041e\u041e\u041e \"\u0410\u043b\u044c\u0442\u0435\u0440\u043d\u0430\u0442\u0438\u0432\u0430 \u041f\u043b\u044e\u0441\"";
|
||||
const warehouse = "\u041e\u0441\u043d\u043e\u0432\u043d\u043e\u0439 \u0441\u043a\u043b\u0430\u0434";
|
||||
const rootFilters = {
|
||||
period_from: "2021-09-01",
|
||||
period_to: "2021-09-30",
|
||||
as_of_date: "2021-09-30",
|
||||
organization,
|
||||
warehouse
|
||||
};
|
||||
const policy = buildPolicy({
|
||||
findLastAddressAssistantItem: () => ({
|
||||
text: "\u041f\u043e \u0441\u043a\u043b\u0430\u0434\u0441\u043a\u043e\u043c\u0443 \u043e\u0441\u0442\u0430\u0442\u043a\u0443 \u043d\u0430 30.09.2021 \u043d\u0430\u0439\u0434\u0435\u043d\u044b \u043f\u043e\u0441\u0442\u0430\u0432\u0449\u0438\u043a\u0438.",
|
||||
debug: {
|
||||
detected_intent: "inventory_supplier_stock_overlap_as_of_date",
|
||||
selected_recipe: "address_inventory_supplier_stock_overlap_as_of_date_v1",
|
||||
extracted_filters: rootFilters,
|
||||
anchor_type: "warehouse",
|
||||
anchor_value_resolved: warehouse
|
||||
}
|
||||
}),
|
||||
findRecentInventoryRootFrame: () => ({
|
||||
intent: "inventory_on_hand_as_of_date",
|
||||
filters: rootFilters,
|
||||
anchorType: "warehouse",
|
||||
anchorValue: warehouse,
|
||||
currentFrameKind: "inventory_root"
|
||||
}),
|
||||
hasAddressFollowupContextSignal: () => false,
|
||||
hasStandaloneAddressTopicSignal: () => true,
|
||||
resolveAddressIntent: () => ({ intent: "inventory_supplier_stock_overlap_as_of_date" }),
|
||||
resolveAddressIntentFamily: (intent: unknown) =>
|
||||
String(intent ?? "").startsWith("inventory_") ? "inventory" : null
|
||||
});
|
||||
|
||||
const carryover = policy.resolveAddressFollowupCarryoverContext(
|
||||
"\u041f\u043e \u043a\u0430\u043a\u043e\u043c\u0443 \u043f\u043e\u0441\u0442\u0430\u0432\u0449\u0438\u043a\u0443 \u043f\u0440\u043e\u0445\u043e\u0434\u0438\u0442 \u0442\u0435\u043a\u0443\u0449\u0438\u0439 \u0442\u043e\u0432\u0430\u0440\u043d\u044b\u0439 \u043e\u0441\u0442\u0430\u0442\u043e\u043a \u043d\u0430 \u0441\u043a\u043b\u0430\u0434\u0435 \u041e\u0441\u043d\u043e\u0432\u043d\u043e\u0439 \u0441\u043a\u043b\u0430\u0434",
|
||||
[],
|
||||
"\u041e\u043f\u0440\u0435\u0434\u0435\u043b\u0438\u0442\u044c \u043f\u043e\u0441\u0442\u0430\u0432\u0449\u0438\u043a\u0430, \u0441 \u043a\u043e\u0442\u043e\u0440\u043e\u0433\u043e \u043f\u043e\u0441\u0442\u0443\u043f\u0438\u043b\u0438 \u0442\u043e\u0432\u0430\u0440\u044b, \u043e\u0431\u0440\u0430\u0437\u0443\u044e\u0449\u0438\u0435 \u0442\u0435\u043a\u0443\u0449\u0438\u0439 \u043e\u0441\u0442\u0430\u0442\u043e\u043a \u043d\u0430 \u0441\u043a\u043b\u0430\u0434\u0435 \u041e\u0441\u043d\u043e\u0432\u043d\u043e\u0439 \u0441\u043a\u043b\u0430\u0434",
|
||||
{
|
||||
predecomposeContract: {
|
||||
intent: "inventory_supplier_stock_overlap_as_of_date"
|
||||
}
|
||||
},
|
||||
{
|
||||
session_context: {
|
||||
date_scope: rootFilters,
|
||||
organization_scope: organization,
|
||||
active_focus_object: {
|
||||
object_type: "warehouse",
|
||||
label: warehouse
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
expect(carryover?.followupContext?.previous_intent).toBe("inventory_supplier_stock_overlap_as_of_date");
|
||||
expect(carryover?.followupContext?.target_intent).toBe("inventory_supplier_stock_overlap_as_of_date");
|
||||
expect(carryover?.followupContext?.previous_filters).toMatchObject(rootFilters);
|
||||
expect(carryover?.followupContext?.root_intent).toBe("inventory_on_hand_as_of_date");
|
||||
expect(carryover?.followupContext?.current_frame_kind).toBe("inventory_root");
|
||||
});
|
||||
|
||||
it("builds continuation contract from extracted root carryover", () => {
|
||||
const policy = buildPolicy();
|
||||
|
||||
|
|
@ -1903,7 +1776,6 @@ describe("assistantTransitionPolicy", () => {
|
|||
expect(carryover?.followupContext?.previous_discovery_loop_unsupported_family).toBe(
|
||||
"counterparty_value_or_turnover"
|
||||
);
|
||||
expect(carryover?.followupContext?.previous_discovery_loop_explicit_date_scope).toBe("2020");
|
||||
});
|
||||
|
||||
it("treats a plain organization reply as continuation of a pending route-candidate organization scope", () => {
|
||||
|
|
@ -1964,7 +1836,6 @@ describe("assistantTransitionPolicy", () => {
|
|||
expect(carryover?.followupContext?.previous_discovery_loop_status).toBe("awaiting_clarification");
|
||||
expect(carryover?.followupContext?.previous_discovery_loop_selected_chain_id).toBe("value_flow_ranking");
|
||||
expect(carryover?.followupContext?.previous_discovery_loop_pending_axes).toEqual(["organization"]);
|
||||
expect(carryover?.followupContext?.previous_discovery_loop_explicit_date_scope).toBe("2020");
|
||||
expect(carryover?.followupContext?.target_intent).toBe("customer_revenue_and_payments");
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -1,304 +1,4 @@
|
|||
[
|
||||
{
|
||||
"generation_id": "gen-ag06031328-3e1dd1",
|
||||
"created_at": "2026-06-03T13:28:09+00:00",
|
||||
"mode": "saved_user_sessions",
|
||||
"title": "AGENT | Phase 83 mixed planner-brain replay for catalog alignment, pivots, and legacy continuity",
|
||||
"count": 20,
|
||||
"domain": "planner_autonomy_consolidation",
|
||||
"questions": [
|
||||
"привет, ты на связи?",
|
||||
"найди в 1С контрагента СВК",
|
||||
"сколько получили по нему за 2020 год",
|
||||
"а теперь сколько заплатили?",
|
||||
"а какое нетто?",
|
||||
"а по документам?",
|
||||
"а по движениям?",
|
||||
"Хочу быстрый денежный срез по одной организации без привязки к контрагенту. Сколько вообще входящих денег было за 2020 год?",
|
||||
"По ООО Альтернатива Плюс.",
|
||||
"Понял, тогда за все время.",
|
||||
"Хорошо. А что по ООО Альтернатива Плюс больше в 2020 году: входящие или исходящие деньги?",
|
||||
"А что по ООО Альтернатива Плюс больше уже за 2021 год: входящие или исходящие деньги?",
|
||||
"И кто больше всего принес денег этой организации в 2020 году?",
|
||||
"А в 2021 году?",
|
||||
"а по Альтернативе Плюс сколько лет активности в базе 1С?",
|
||||
"Как ты оценишь деятельность компании?",
|
||||
"какое нетто по деньгам с Группа СВК за 2020 год: сколько получили и сколько заплатили?",
|
||||
"какие справочники 1С есть по контрагентам?",
|
||||
"давай дальше",
|
||||
"а чем капибара отличается от утки?"
|
||||
],
|
||||
"generated_by": "codex_agent",
|
||||
"saved_case_set_file": "assistant_autogen_saved_user_sessions_20260603132809_gen-ag06031328-3e1dd1.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_20260603132809_gen-ag06031328-3e1dd1.json",
|
||||
"saved_case_set_kind": "agent_semantic_scenario",
|
||||
"agent_run": true,
|
||||
"agent_focus": "Mixed AGENT replay for Planner Autonomy Consolidation. The pack interleaves selected-counterparty catalog-alignment probes, open-organization money flow, ranking, broad-evaluation continuity, metadata drilldown, and off-domain living-chat safety.",
|
||||
"architecture_phase": "turnaround_11",
|
||||
"source_spec_file": "X:\\1C\\NDC_1C\\docs\\orchestration\\address_truth_harness_phase83_planner_brain_alignment_mix.json",
|
||||
"scenario_id": "address_truth_harness_phase83_planner_brain_alignment_mix",
|
||||
"semantic_tags": [
|
||||
"alias_grounding",
|
||||
"all_time_followup",
|
||||
"broad_business_evaluation",
|
||||
"broad_eval_bridge_preserved",
|
||||
"catalog_drilldown",
|
||||
"catalog_metadata_surface",
|
||||
"company_activity_lifecycle",
|
||||
"counterparty_catalog_scope",
|
||||
"counterparty_net_cash_flow",
|
||||
"date_carryover",
|
||||
"document_evidence",
|
||||
"document_pivot",
|
||||
"entity_resolution",
|
||||
"followup_anchor",
|
||||
"followup_reuse",
|
||||
"grounded_context_seed",
|
||||
"grounded_summary",
|
||||
"human_answer",
|
||||
"human_dialog",
|
||||
"incoming_value_flow",
|
||||
"mcp_discovery_gate_sanity",
|
||||
"meta_smalltalk",
|
||||
"movement_evidence",
|
||||
"net_value_flow",
|
||||
"neutral_followup",
|
||||
"off_domain_living_chat",
|
||||
"open_scope_total",
|
||||
"organization_clarification",
|
||||
"organization_scope",
|
||||
"payout_switch",
|
||||
"planner_catalog_alignment",
|
||||
"stale_replay_forbidden",
|
||||
"value_flow_comparison",
|
||||
"value_flow_pivot",
|
||||
"value_flow_ranking",
|
||||
"year_switch"
|
||||
],
|
||||
"validation_status": "accepted_live_replay",
|
||||
"validated_run_dir": "artifacts\\domain_runs\\phase83_planner_brain_alignment_mix_20260603_live9",
|
||||
"saved_after_validated_replay": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"generation_id": "gen-ag06031054-1bd955",
|
||||
"created_at": "2026-06-03T10:54:28+00:00",
|
||||
"mode": "saved_user_sessions",
|
||||
"title": "AGENT | Phase105 mixed schema primitive closure live8",
|
||||
"count": 13,
|
||||
"domain": "address_phase105_mixed_schema_primitive_closure",
|
||||
"questions": [
|
||||
"кайф - что там на складе по остаткам?",
|
||||
"АЛЬТЕРНАТИВА",
|
||||
"а исторические остатки на другие даты умеешь?",
|
||||
"давай на июнь 2017",
|
||||
"Теперь дай взрослый обзор за 2020 по компании: входящие, исходящие, нетто, топы, но банк в топах отдельно объясни как финансовый поток, если по назначению он не обычный клиент или поставщик.",
|
||||
"А отдельно по СБЕРБАНКУ: он для нас клиент, поставщик или финансовый поток? Дай коротко по подтвержденным строкам.",
|
||||
"А теперь по поставщику Группа СВК за 2020: сколько мы ему заплатили и какой общий денежный смысл?",
|
||||
"А теперь по Группа СВК за 2020: сколько денег получили, сколько заплатили и какое нетто?",
|
||||
"кому мы должны на конец 2020?",
|
||||
"а нам кто должен на конец 2020?",
|
||||
"сколько НДС надо заплатить в налоговую за декабрь 2019?",
|
||||
"скока денег альтернатива заработала за 20 год?",
|
||||
"а это чистая прибыль?"
|
||||
],
|
||||
"generated_by": "codex_agent",
|
||||
"saved_case_set_file": "assistant_autogen_saved_user_sessions_20260603105428_gen-ag06031054-1bd955.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_20260603105428_gen-ag06031054-1bd955.json",
|
||||
"saved_case_set_kind": "agent_semantic_scenario",
|
||||
"agent_run": true,
|
||||
"agent_focus": "cross-contour canary after inventory and truth-harness hardening",
|
||||
"architecture_phase": "assistant-stage1 mixed MCP schema primitive closure",
|
||||
"source_spec_file": "X:\\1C\\NDC_1C\\docs\\orchestration\\address_truth_harness_phase105_mixed_schema_primitive_closure.json",
|
||||
"scenario_id": "address_truth_harness_phase105_mixed_schema_primitive_closure",
|
||||
"semantic_tags": [
|
||||
"bank_like_counterparty",
|
||||
"business_overview",
|
||||
"business_overview_followup",
|
||||
"counterparty_net_cash_flow",
|
||||
"date_followup",
|
||||
"debt_polarity",
|
||||
"earnings_wording",
|
||||
"financial_role_purpose",
|
||||
"generic_role_tail_anchor_hygiene",
|
||||
"historical_inventory",
|
||||
"inventory_capability_meta",
|
||||
"inventory_root",
|
||||
"organization_scope",
|
||||
"payables",
|
||||
"profit_boundary",
|
||||
"receivables",
|
||||
"redundant_scope_selection",
|
||||
"resolved_organization_scope",
|
||||
"stale_scope_guard",
|
||||
"supplier_prefix_canary",
|
||||
"tax_period",
|
||||
"value_flow",
|
||||
"vat_continuity",
|
||||
"warehouse_not_required"
|
||||
],
|
||||
"validation_status": "accepted_live_replay",
|
||||
"validated_run_dir": "artifacts\\domain_runs\\phase105_mixed_schema_primitive_closure_20260603_live8",
|
||||
"saved_after_validated_replay": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"generation_id": "gen-ag06031037-e4c3f0",
|
||||
"created_at": "2026-06-03T10:37:12+00:00",
|
||||
"mode": "saved_user_sessions",
|
||||
"title": "AGENT | inventory stock supplier provenance p28 accepted",
|
||||
"count": 34,
|
||||
"domain": "inventory_stock",
|
||||
"questions": [
|
||||
"Какие товары сейчас лежат на складе",
|
||||
"покажи остатки на складе на март 2019",
|
||||
"что лежит у нас на складе в марте 2019",
|
||||
"Из каких товаров состоит остаток по 41 счету",
|
||||
"Какие товары числятся на 41 счете на дату 2019-03-31",
|
||||
"Какие конкретно номенклатуры формируют остаток по складу Основной склад на дату 2019-03-31",
|
||||
"По выбранному объекту \"Столешница 600*3050*26 дуб ниагара\": от какого поставщика куплен товар",
|
||||
"По выбранному объекту \"Столешница 600*3050*26 дуб ниагара\": кто это поставил нам",
|
||||
"По выбранному объекту \"Столешница 600*3050*26 дуб ниагара\": у кого купили",
|
||||
"По выбранному объекту \"Столешница 600*3050*26 дуб ниагара\": у кого куплено",
|
||||
"По выбранному объекту \"Столешница 600*3050*26 дуб ниагара\": где мы купили это",
|
||||
"По выбранному объекту \"Столешница 600*3050*26 дуб ниагара\": где куплено!!",
|
||||
"кто это поставил нам",
|
||||
"От какого поставщика куплен товар Столешница 600*3050*26 дуб ниагара",
|
||||
"Когда был куплен товар Столешница 600*3050*26 дуб ниагара",
|
||||
"когда",
|
||||
"По выбранному объекту \"Столешница 600*3050*26 дуб ниагара\": по каким документам это купили",
|
||||
"покажи документы по этой позиции",
|
||||
"По каким документам был куплен товар Столешница 600*3050*26 дуб ниагара",
|
||||
"какие остатки на складе на сентябрь 2021",
|
||||
"У какого поставщика были куплены товары, которые сейчас лежат на складе Основной склад",
|
||||
"По какому поставщику проходит текущий товарный остаток на складе Основной склад",
|
||||
"Какие товары от поставщика Торговый дом \\Союз сейчас еще лежат на складе Основной склад",
|
||||
"Какие товары по состоянию на дату 2019-03-31 были куплены у поставщика Торговый дом \\Союз",
|
||||
"По выбранному объекту \"Четки Пост (84*117)\": кто это поставил нам",
|
||||
"Какие остатки по товарам на эту дату относятся к старым закупкам",
|
||||
"Есть ли остатки товара, которые закупались очень давно",
|
||||
"Какие товары сейчас висят в остатке без понятной привязки к поставщику",
|
||||
"Какие товары числятся на 41 счете на дату 2020-03-31",
|
||||
"По выбранному объекту \"Шкаф картотечный 1000*400*2100\": кому был продан товар",
|
||||
"Кому был продан товар Шкаф картотечный 1000*400*2100",
|
||||
"По выбранному объекту \"Шкаф картотечный 1000*400*2100\": через какие документы прошел путь товара: закупка -> склад -> продажа",
|
||||
"Через какие документы прошел путь товара Шкаф картотечный 1000*400*2100: закупка -> склад -> продажа",
|
||||
"Есть ли документально подтвержденная цепочка: поставщик Гамма-мебель, ООО -> товар Шкаф картотечный 1000*400*2100 -> покупатель Департамент капитального ремонта города Москвы"
|
||||
],
|
||||
"generated_by": "codex_agent",
|
||||
"saved_case_set_file": "assistant_autogen_saved_user_sessions_20260603103712_gen-ag06031037-e4c3f0.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_20260603103712_gen-ag06031037-e4c3f0.json",
|
||||
"saved_case_set_kind": "agent_semantic_scenario",
|
||||
"agent_run": true,
|
||||
"agent_focus": "supplier-item-buyer documentary chain and route candidate triage",
|
||||
"architecture_phase": "assistant-stage1 inventory stock hardening",
|
||||
"source_spec_file": "X:\\1C\\NDC_1C\\artifacts\\domain_runs\\inventory_stock_active_contract_full_20260603_p28_loop\\iterations\\iteration_00\\pack_output\\pack_run\\pack_manifest.json",
|
||||
"scenario_id": null,
|
||||
"semantic_tags": [],
|
||||
"validation_status": "accepted_domain_case_loop_pack",
|
||||
"validated_run_dir": "artifacts\\domain_runs\\inventory_stock_active_contract_full_20260603_p28_loop\\iterations\\iteration_00\\pack_output\\pack_run",
|
||||
"saved_after_validated_replay": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"generation_id": "gen-ag06030918-32dc7c",
|
||||
"created_at": "2026-06-03T09:18:35+00:00",
|
||||
"mode": "saved_user_sessions",
|
||||
"title": "AGENT | Supplier-overlap follow-up keeps the inventory root date",
|
||||
"count": 3,
|
||||
"domain": "inventory_supplier_overlap_temporal_carryover",
|
||||
"questions": [
|
||||
"какие остатки на складе на сентябрь 2021",
|
||||
"У какого поставщика были куплены товары, которые сейчас лежат на складе Основной склад",
|
||||
"По какому поставщику проходит текущий товарный остаток на складе Основной склад"
|
||||
],
|
||||
"generated_by": "codex_agent",
|
||||
"saved_case_set_file": "assistant_autogen_saved_user_sessions_20260603091835_gen-ag06030918-32dc7c.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_20260603091835_gen-ag06030918-32dc7c.json",
|
||||
"saved_case_set_kind": "agent_semantic_scenario",
|
||||
"agent_run": true,
|
||||
"agent_focus": "Targeted replay for temporal carryover: after an inventory root slice for September 2021, colloquial supplier-overlap follow-ups with 'сейчас' / 'текущий товарный остаток' must stay on the confirmed root slice date instead of drifting to the wall-clock date.",
|
||||
"architecture_phase": "turnaround_11",
|
||||
"source_spec_file": "X:\\1C\\NDC_1C\\docs\\orchestration\\address_truth_harness_inventory_supplier_overlap_temporal_carryover.json",
|
||||
"scenario_id": "address_truth_harness_inventory_supplier_overlap_temporal_carryover",
|
||||
"semantic_tags": [],
|
||||
"validation_status": "accepted_live_replay",
|
||||
"validated_run_dir": "artifacts\\domain_runs\\inventory_supplier_overlap_temporal_carryover_p2",
|
||||
"saved_after_validated_replay": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"generation_id": "gen-ag06030841-d61275",
|
||||
"created_at": "2026-06-03T08:41:17+00:00",
|
||||
"mode": "saved_user_sessions",
|
||||
"title": "AGENT | Supplier-overlap limited answer includes a concrete next action",
|
||||
"count": 3,
|
||||
"domain": "inventory_supplier_overlap_next_action",
|
||||
"questions": [
|
||||
"какие остатки на складе на сентябрь 2021",
|
||||
"У какого поставщика были куплены товары, которые лежали на складе Основной склад на 30.09.2021",
|
||||
"По какому поставщику проходит товарный остаток на складе Основной склад на 30.09.2021"
|
||||
],
|
||||
"generated_by": "codex_agent",
|
||||
"saved_case_set_file": "assistant_autogen_saved_user_sessions_20260603084117_gen-ag06030841-d61275.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_20260603084117_gen-ag06030841-d61275.json",
|
||||
"saved_case_set_kind": "agent_semantic_scenario",
|
||||
"agent_run": true,
|
||||
"agent_focus": "Targeted replay for the inventory supplier-overlap surface: when supplier attribution is limited by missing party-level proof, the answer must still give a concrete next action instead of stopping at a limitation.",
|
||||
"architecture_phase": "turnaround_11",
|
||||
"source_spec_file": "X:\\1C\\NDC_1C\\docs\\orchestration\\address_truth_harness_inventory_supplier_overlap_next_action.json",
|
||||
"scenario_id": "address_truth_harness_inventory_supplier_overlap_next_action",
|
||||
"semantic_tags": [],
|
||||
"validation_status": "accepted_live_replay",
|
||||
"validated_run_dir": "artifacts\\domain_runs\\inventory_supplier_overlap_next_action_p2",
|
||||
"saved_after_validated_replay": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"generation_id": "gen-ag06030440-256da0",
|
||||
"created_at": "2026-06-03T04:40:01+00:00",
|
||||
|
|
|
|||
|
|
@ -1,133 +0,0 @@
|
|||
{
|
||||
"saved_at": "2026-06-03T08:41:17+00:00",
|
||||
"generation_id": "gen-ag06030841-d61275",
|
||||
"mode": "saved_user_sessions",
|
||||
"title": "AGENT | Supplier-overlap limited answer includes a concrete next action",
|
||||
"agent_run": true,
|
||||
"questions": [
|
||||
"какие остатки на складе на сентябрь 2021",
|
||||
"У какого поставщика были куплены товары, которые лежали на складе Основной склад на 30.09.2021",
|
||||
"По какому поставщику проходит товарный остаток на складе Основной склад на 30.09.2021"
|
||||
],
|
||||
"metadata": {
|
||||
"assistant_prompt_version": null,
|
||||
"decomposition_prompt_version": null,
|
||||
"prompt_fingerprint": null,
|
||||
"agent_focus": "Targeted replay for the inventory supplier-overlap surface: when supplier attribution is limited by missing party-level proof, the answer must still give a concrete next action instead of stopping at a limitation.",
|
||||
"architecture_phase": "turnaround_11",
|
||||
"source_spec_file": "X:\\1C\\NDC_1C\\docs\\orchestration\\address_truth_harness_inventory_supplier_overlap_next_action.json",
|
||||
"scenario_id": "address_truth_harness_inventory_supplier_overlap_next_action",
|
||||
"semantic_tags": [],
|
||||
"validation_status": "accepted_live_replay",
|
||||
"validated_run_dir": "artifacts\\domain_runs\\inventory_supplier_overlap_next_action_p2",
|
||||
"saved_after_validated_replay": true,
|
||||
"save_gate": {
|
||||
"schema_version": "agent_semantic_save_gate_v1",
|
||||
"validation_status": "accepted_live_replay",
|
||||
"validated_run_dir": "artifacts\\domain_runs\\inventory_supplier_overlap_next_action_p2",
|
||||
"final_status": "accepted",
|
||||
"review_overall_status": "pass",
|
||||
"business_overall_status": "pass",
|
||||
"steps_total": 3,
|
||||
"steps_passed": 3,
|
||||
"steps_failed": 0,
|
||||
"steps_with_business_failures": 0,
|
||||
"steps_with_business_warnings": 0,
|
||||
"acceptance_gate_passed": true,
|
||||
"effective_runtime": {
|
||||
"manifest_path": "artifacts\\domain_runs\\inventory_supplier_overlap_next_action_p2\\effective_runtime.json",
|
||||
"runner": "domain_truth_harness.run-live",
|
||||
"git_sha": "c8a58f6163423ac334dc293aeb8542d19e676d71",
|
||||
"backend_url": "http://127.0.0.1:8787",
|
||||
"mcp_proxy_url": "http://127.0.0.1:6003",
|
||||
"llm_provider": "local",
|
||||
"llm_model": "unsloth/qwen3-30b-a3b-instruct-2507",
|
||||
"temperature": 0.8,
|
||||
"max_output_tokens": 900,
|
||||
"prompt_version": "normalizer_v2_0_2",
|
||||
"prompt_source": "file",
|
||||
"prompt_hash": "f36e36a5e491cd24511b380e0c0059cb01aea5e2af738fb9f01671cd291735c1",
|
||||
"prompt_registry_status": "pass"
|
||||
},
|
||||
"saved_after_validated_replay": true
|
||||
}
|
||||
},
|
||||
"source_session_id": null,
|
||||
"session": {
|
||||
"session_id": null,
|
||||
"mode": "agent_semantic_run",
|
||||
"items": [
|
||||
{
|
||||
"message_id": "agent-user-001",
|
||||
"role": "user",
|
||||
"text": "какие остатки на складе на сентябрь 2021",
|
||||
"created_at": "2026-06-03T08:41:17+00:00",
|
||||
"reply_type": null,
|
||||
"trace_id": null,
|
||||
"debug": null
|
||||
},
|
||||
{
|
||||
"message_id": "agent-user-002",
|
||||
"role": "user",
|
||||
"text": "У какого поставщика были куплены товары, которые лежали на складе Основной склад на 30.09.2021",
|
||||
"created_at": "2026-06-03T08:41:17+00:00",
|
||||
"reply_type": null,
|
||||
"trace_id": null,
|
||||
"debug": null
|
||||
},
|
||||
{
|
||||
"message_id": "agent-user-003",
|
||||
"role": "user",
|
||||
"text": "По какому поставщику проходит товарный остаток на складе Основной склад на 30.09.2021",
|
||||
"created_at": "2026-06-03T08:41:17+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": "Targeted replay for the inventory supplier-overlap surface: when supplier attribution is limited by missing party-level proof, the answer must still give a concrete next action instead of stopping at a limitation.",
|
||||
"architecture_phase": "turnaround_11",
|
||||
"source_spec_file": "X:\\1C\\NDC_1C\\docs\\orchestration\\address_truth_harness_inventory_supplier_overlap_next_action.json",
|
||||
"scenario_id": "address_truth_harness_inventory_supplier_overlap_next_action",
|
||||
"semantic_tags": [],
|
||||
"validation_status": "accepted_live_replay",
|
||||
"validated_run_dir": "artifacts\\domain_runs\\inventory_supplier_overlap_next_action_p2",
|
||||
"saved_after_validated_replay": true,
|
||||
"save_gate": {
|
||||
"schema_version": "agent_semantic_save_gate_v1",
|
||||
"validation_status": "accepted_live_replay",
|
||||
"validated_run_dir": "artifacts\\domain_runs\\inventory_supplier_overlap_next_action_p2",
|
||||
"final_status": "accepted",
|
||||
"review_overall_status": "pass",
|
||||
"business_overall_status": "pass",
|
||||
"steps_total": 3,
|
||||
"steps_passed": 3,
|
||||
"steps_failed": 0,
|
||||
"steps_with_business_failures": 0,
|
||||
"steps_with_business_warnings": 0,
|
||||
"acceptance_gate_passed": true,
|
||||
"effective_runtime": {
|
||||
"manifest_path": "artifacts\\domain_runs\\inventory_supplier_overlap_next_action_p2\\effective_runtime.json",
|
||||
"runner": "domain_truth_harness.run-live",
|
||||
"git_sha": "c8a58f6163423ac334dc293aeb8542d19e676d71",
|
||||
"backend_url": "http://127.0.0.1:8787",
|
||||
"mcp_proxy_url": "http://127.0.0.1:6003",
|
||||
"llm_provider": "local",
|
||||
"llm_model": "unsloth/qwen3-30b-a3b-instruct-2507",
|
||||
"temperature": 0.8,
|
||||
"max_output_tokens": 900,
|
||||
"prompt_version": "normalizer_v2_0_2",
|
||||
"prompt_source": "file",
|
||||
"prompt_hash": "f36e36a5e491cd24511b380e0c0059cb01aea5e2af738fb9f01671cd291735c1",
|
||||
"prompt_registry_status": "pass"
|
||||
},
|
||||
"saved_after_validated_replay": true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,133 +0,0 @@
|
|||
{
|
||||
"saved_at": "2026-06-03T09:18:35+00:00",
|
||||
"generation_id": "gen-ag06030918-32dc7c",
|
||||
"mode": "saved_user_sessions",
|
||||
"title": "AGENT | Supplier-overlap follow-up keeps the inventory root date",
|
||||
"agent_run": true,
|
||||
"questions": [
|
||||
"какие остатки на складе на сентябрь 2021",
|
||||
"У какого поставщика были куплены товары, которые сейчас лежат на складе Основной склад",
|
||||
"По какому поставщику проходит текущий товарный остаток на складе Основной склад"
|
||||
],
|
||||
"metadata": {
|
||||
"assistant_prompt_version": null,
|
||||
"decomposition_prompt_version": null,
|
||||
"prompt_fingerprint": null,
|
||||
"agent_focus": "Targeted replay for temporal carryover: after an inventory root slice for September 2021, colloquial supplier-overlap follow-ups with 'сейчас' / 'текущий товарный остаток' must stay on the confirmed root slice date instead of drifting to the wall-clock date.",
|
||||
"architecture_phase": "turnaround_11",
|
||||
"source_spec_file": "X:\\1C\\NDC_1C\\docs\\orchestration\\address_truth_harness_inventory_supplier_overlap_temporal_carryover.json",
|
||||
"scenario_id": "address_truth_harness_inventory_supplier_overlap_temporal_carryover",
|
||||
"semantic_tags": [],
|
||||
"validation_status": "accepted_live_replay",
|
||||
"validated_run_dir": "artifacts\\domain_runs\\inventory_supplier_overlap_temporal_carryover_p2",
|
||||
"saved_after_validated_replay": true,
|
||||
"save_gate": {
|
||||
"schema_version": "agent_semantic_save_gate_v1",
|
||||
"validation_status": "accepted_live_replay",
|
||||
"validated_run_dir": "artifacts\\domain_runs\\inventory_supplier_overlap_temporal_carryover_p2",
|
||||
"final_status": "accepted",
|
||||
"review_overall_status": "pass",
|
||||
"business_overall_status": "pass",
|
||||
"steps_total": 3,
|
||||
"steps_passed": 3,
|
||||
"steps_failed": 0,
|
||||
"steps_with_business_failures": 0,
|
||||
"steps_with_business_warnings": 0,
|
||||
"acceptance_gate_passed": true,
|
||||
"effective_runtime": {
|
||||
"manifest_path": "artifacts\\domain_runs\\inventory_supplier_overlap_temporal_carryover_p2\\effective_runtime.json",
|
||||
"runner": "domain_truth_harness.run-live",
|
||||
"git_sha": "f0b84822d3e9283b5a8b823bd22ed571322a905f",
|
||||
"backend_url": "http://127.0.0.1:8787",
|
||||
"mcp_proxy_url": "http://127.0.0.1:6003",
|
||||
"llm_provider": "local",
|
||||
"llm_model": "unsloth/qwen3-30b-a3b-instruct-2507",
|
||||
"temperature": 0.8,
|
||||
"max_output_tokens": 900,
|
||||
"prompt_version": "normalizer_v2_0_2",
|
||||
"prompt_source": "file",
|
||||
"prompt_hash": "f36e36a5e491cd24511b380e0c0059cb01aea5e2af738fb9f01671cd291735c1",
|
||||
"prompt_registry_status": "pass"
|
||||
},
|
||||
"saved_after_validated_replay": true
|
||||
}
|
||||
},
|
||||
"source_session_id": null,
|
||||
"session": {
|
||||
"session_id": null,
|
||||
"mode": "agent_semantic_run",
|
||||
"items": [
|
||||
{
|
||||
"message_id": "agent-user-001",
|
||||
"role": "user",
|
||||
"text": "какие остатки на складе на сентябрь 2021",
|
||||
"created_at": "2026-06-03T09:18:35+00:00",
|
||||
"reply_type": null,
|
||||
"trace_id": null,
|
||||
"debug": null
|
||||
},
|
||||
{
|
||||
"message_id": "agent-user-002",
|
||||
"role": "user",
|
||||
"text": "У какого поставщика были куплены товары, которые сейчас лежат на складе Основной склад",
|
||||
"created_at": "2026-06-03T09:18:35+00:00",
|
||||
"reply_type": null,
|
||||
"trace_id": null,
|
||||
"debug": null
|
||||
},
|
||||
{
|
||||
"message_id": "agent-user-003",
|
||||
"role": "user",
|
||||
"text": "По какому поставщику проходит текущий товарный остаток на складе Основной склад",
|
||||
"created_at": "2026-06-03T09:18:35+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": "Targeted replay for temporal carryover: after an inventory root slice for September 2021, colloquial supplier-overlap follow-ups with 'сейчас' / 'текущий товарный остаток' must stay on the confirmed root slice date instead of drifting to the wall-clock date.",
|
||||
"architecture_phase": "turnaround_11",
|
||||
"source_spec_file": "X:\\1C\\NDC_1C\\docs\\orchestration\\address_truth_harness_inventory_supplier_overlap_temporal_carryover.json",
|
||||
"scenario_id": "address_truth_harness_inventory_supplier_overlap_temporal_carryover",
|
||||
"semantic_tags": [],
|
||||
"validation_status": "accepted_live_replay",
|
||||
"validated_run_dir": "artifacts\\domain_runs\\inventory_supplier_overlap_temporal_carryover_p2",
|
||||
"saved_after_validated_replay": true,
|
||||
"save_gate": {
|
||||
"schema_version": "agent_semantic_save_gate_v1",
|
||||
"validation_status": "accepted_live_replay",
|
||||
"validated_run_dir": "artifacts\\domain_runs\\inventory_supplier_overlap_temporal_carryover_p2",
|
||||
"final_status": "accepted",
|
||||
"review_overall_status": "pass",
|
||||
"business_overall_status": "pass",
|
||||
"steps_total": 3,
|
||||
"steps_passed": 3,
|
||||
"steps_failed": 0,
|
||||
"steps_with_business_failures": 0,
|
||||
"steps_with_business_warnings": 0,
|
||||
"acceptance_gate_passed": true,
|
||||
"effective_runtime": {
|
||||
"manifest_path": "artifacts\\domain_runs\\inventory_supplier_overlap_temporal_carryover_p2\\effective_runtime.json",
|
||||
"runner": "domain_truth_harness.run-live",
|
||||
"git_sha": "f0b84822d3e9283b5a8b823bd22ed571322a905f",
|
||||
"backend_url": "http://127.0.0.1:8787",
|
||||
"mcp_proxy_url": "http://127.0.0.1:6003",
|
||||
"llm_provider": "local",
|
||||
"llm_model": "unsloth/qwen3-30b-a3b-instruct-2507",
|
||||
"temperature": 0.8,
|
||||
"max_output_tokens": 900,
|
||||
"prompt_version": "normalizer_v2_0_2",
|
||||
"prompt_source": "file",
|
||||
"prompt_hash": "f36e36a5e491cd24511b380e0c0059cb01aea5e2af738fb9f01671cd291735c1",
|
||||
"prompt_registry_status": "pass"
|
||||
},
|
||||
"saved_after_validated_replay": true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,447 +0,0 @@
|
|||
{
|
||||
"saved_at": "2026-06-03T10:37:12+00:00",
|
||||
"generation_id": "gen-ag06031037-e4c3f0",
|
||||
"mode": "saved_user_sessions",
|
||||
"title": "AGENT | inventory stock supplier provenance p28 accepted",
|
||||
"agent_run": true,
|
||||
"questions": [
|
||||
"Какие товары сейчас лежат на складе",
|
||||
"покажи остатки на складе на март 2019",
|
||||
"что лежит у нас на складе в марте 2019",
|
||||
"Из каких товаров состоит остаток по 41 счету",
|
||||
"Какие товары числятся на 41 счете на дату 2019-03-31",
|
||||
"Какие конкретно номенклатуры формируют остаток по складу Основной склад на дату 2019-03-31",
|
||||
"По выбранному объекту \"Столешница 600*3050*26 дуб ниагара\": от какого поставщика куплен товар",
|
||||
"По выбранному объекту \"Столешница 600*3050*26 дуб ниагара\": кто это поставил нам",
|
||||
"По выбранному объекту \"Столешница 600*3050*26 дуб ниагара\": у кого купили",
|
||||
"По выбранному объекту \"Столешница 600*3050*26 дуб ниагара\": у кого куплено",
|
||||
"По выбранному объекту \"Столешница 600*3050*26 дуб ниагара\": где мы купили это",
|
||||
"По выбранному объекту \"Столешница 600*3050*26 дуб ниагара\": где куплено!!",
|
||||
"кто это поставил нам",
|
||||
"От какого поставщика куплен товар Столешница 600*3050*26 дуб ниагара",
|
||||
"Когда был куплен товар Столешница 600*3050*26 дуб ниагара",
|
||||
"когда",
|
||||
"По выбранному объекту \"Столешница 600*3050*26 дуб ниагара\": по каким документам это купили",
|
||||
"покажи документы по этой позиции",
|
||||
"По каким документам был куплен товар Столешница 600*3050*26 дуб ниагара",
|
||||
"какие остатки на складе на сентябрь 2021",
|
||||
"У какого поставщика были куплены товары, которые сейчас лежат на складе Основной склад",
|
||||
"По какому поставщику проходит текущий товарный остаток на складе Основной склад",
|
||||
"Какие товары от поставщика Торговый дом \\Союз сейчас еще лежат на складе Основной склад",
|
||||
"Какие товары по состоянию на дату 2019-03-31 были куплены у поставщика Торговый дом \\Союз",
|
||||
"По выбранному объекту \"Четки Пост (84*117)\": кто это поставил нам",
|
||||
"Какие остатки по товарам на эту дату относятся к старым закупкам",
|
||||
"Есть ли остатки товара, которые закупались очень давно",
|
||||
"Какие товары сейчас висят в остатке без понятной привязки к поставщику",
|
||||
"Какие товары числятся на 41 счете на дату 2020-03-31",
|
||||
"По выбранному объекту \"Шкаф картотечный 1000*400*2100\": кому был продан товар",
|
||||
"Кому был продан товар Шкаф картотечный 1000*400*2100",
|
||||
"По выбранному объекту \"Шкаф картотечный 1000*400*2100\": через какие документы прошел путь товара: закупка -> склад -> продажа",
|
||||
"Через какие документы прошел путь товара Шкаф картотечный 1000*400*2100: закупка -> склад -> продажа",
|
||||
"Есть ли документально подтвержденная цепочка: поставщик Гамма-мебель, ООО -> товар Шкаф картотечный 1000*400*2100 -> покупатель Департамент капитального ремонта города Москвы"
|
||||
],
|
||||
"metadata": {
|
||||
"assistant_prompt_version": null,
|
||||
"decomposition_prompt_version": null,
|
||||
"prompt_fingerprint": null,
|
||||
"agent_focus": "supplier-item-buyer documentary chain and route candidate triage",
|
||||
"architecture_phase": "assistant-stage1 inventory stock hardening",
|
||||
"source_spec_file": "X:\\1C\\NDC_1C\\artifacts\\domain_runs\\inventory_stock_active_contract_full_20260603_p28_loop\\iterations\\iteration_00\\pack_output\\pack_run\\pack_manifest.json",
|
||||
"scenario_id": null,
|
||||
"semantic_tags": [],
|
||||
"validation_status": "accepted_domain_case_loop_pack",
|
||||
"validated_run_dir": "artifacts\\domain_runs\\inventory_stock_active_contract_full_20260603_p28_loop\\iterations\\iteration_00\\pack_output\\pack_run",
|
||||
"saved_after_validated_replay": true,
|
||||
"save_gate": {
|
||||
"schema_version": "agent_semantic_save_gate_v1",
|
||||
"validation_status": "accepted_domain_case_loop_pack",
|
||||
"validated_run_dir": "artifacts\\domain_runs\\inventory_stock_active_contract_full_20260603_p28_loop\\iterations\\iteration_00\\pack_output\\pack_run",
|
||||
"final_status": "accepted",
|
||||
"acceptance_status": "accepted",
|
||||
"execution_status": "exact",
|
||||
"pack_id": "pack_run",
|
||||
"scenario_count": 5,
|
||||
"repair_target_count": 0,
|
||||
"repair_target_severity_counts": {
|
||||
"P0": 0,
|
||||
"P1": 0,
|
||||
"P2": 0
|
||||
},
|
||||
"effective_runtime": {
|
||||
"manifest_path": "artifacts\\domain_runs\\inventory_stock_active_contract_full_20260603_p28_loop\\iterations\\iteration_00\\pack_output\\pack_run\\effective_runtime.json",
|
||||
"runner": "domain_case_loop.run-pack",
|
||||
"git_sha": "ed44c6428afc07c49d54e3311567be2bb0a7d196",
|
||||
"backend_url": "http://127.0.0.1:8787",
|
||||
"mcp_proxy_url": "http://127.0.0.1:6003",
|
||||
"llm_provider": "local",
|
||||
"llm_model": "unsloth/qwen3-30b-a3b-instruct-2507",
|
||||
"temperature": 0.8,
|
||||
"max_output_tokens": 900,
|
||||
"prompt_version": "normalizer_v2_0_2",
|
||||
"prompt_source": "file",
|
||||
"prompt_hash": "f36e36a5e491cd24511b380e0c0059cb01aea5e2af738fb9f01671cd291735c1",
|
||||
"prompt_registry_status": "pass"
|
||||
},
|
||||
"saved_after_validated_replay": true
|
||||
}
|
||||
},
|
||||
"source_session_id": null,
|
||||
"session": {
|
||||
"session_id": null,
|
||||
"mode": "agent_semantic_run",
|
||||
"items": [
|
||||
{
|
||||
"message_id": "agent-user-001",
|
||||
"role": "user",
|
||||
"text": "Какие товары сейчас лежат на складе",
|
||||
"created_at": "2026-06-03T10:37:12+00:00",
|
||||
"reply_type": null,
|
||||
"trace_id": null,
|
||||
"debug": null
|
||||
},
|
||||
{
|
||||
"message_id": "agent-user-002",
|
||||
"role": "user",
|
||||
"text": "покажи остатки на складе на март 2019",
|
||||
"created_at": "2026-06-03T10:37:12+00:00",
|
||||
"reply_type": null,
|
||||
"trace_id": null,
|
||||
"debug": null
|
||||
},
|
||||
{
|
||||
"message_id": "agent-user-003",
|
||||
"role": "user",
|
||||
"text": "что лежит у нас на складе в марте 2019",
|
||||
"created_at": "2026-06-03T10:37:12+00:00",
|
||||
"reply_type": null,
|
||||
"trace_id": null,
|
||||
"debug": null
|
||||
},
|
||||
{
|
||||
"message_id": "agent-user-004",
|
||||
"role": "user",
|
||||
"text": "Из каких товаров состоит остаток по 41 счету",
|
||||
"created_at": "2026-06-03T10:37:12+00:00",
|
||||
"reply_type": null,
|
||||
"trace_id": null,
|
||||
"debug": null
|
||||
},
|
||||
{
|
||||
"message_id": "agent-user-005",
|
||||
"role": "user",
|
||||
"text": "Какие товары числятся на 41 счете на дату 2019-03-31",
|
||||
"created_at": "2026-06-03T10:37:12+00:00",
|
||||
"reply_type": null,
|
||||
"trace_id": null,
|
||||
"debug": null
|
||||
},
|
||||
{
|
||||
"message_id": "agent-user-006",
|
||||
"role": "user",
|
||||
"text": "Какие конкретно номенклатуры формируют остаток по складу Основной склад на дату 2019-03-31",
|
||||
"created_at": "2026-06-03T10:37:12+00:00",
|
||||
"reply_type": null,
|
||||
"trace_id": null,
|
||||
"debug": null
|
||||
},
|
||||
{
|
||||
"message_id": "agent-user-007",
|
||||
"role": "user",
|
||||
"text": "По выбранному объекту \"Столешница 600*3050*26 дуб ниагара\": от какого поставщика куплен товар",
|
||||
"created_at": "2026-06-03T10:37:12+00:00",
|
||||
"reply_type": null,
|
||||
"trace_id": null,
|
||||
"debug": null
|
||||
},
|
||||
{
|
||||
"message_id": "agent-user-008",
|
||||
"role": "user",
|
||||
"text": "По выбранному объекту \"Столешница 600*3050*26 дуб ниагара\": кто это поставил нам",
|
||||
"created_at": "2026-06-03T10:37:12+00:00",
|
||||
"reply_type": null,
|
||||
"trace_id": null,
|
||||
"debug": null
|
||||
},
|
||||
{
|
||||
"message_id": "agent-user-009",
|
||||
"role": "user",
|
||||
"text": "По выбранному объекту \"Столешница 600*3050*26 дуб ниагара\": у кого купили",
|
||||
"created_at": "2026-06-03T10:37:12+00:00",
|
||||
"reply_type": null,
|
||||
"trace_id": null,
|
||||
"debug": null
|
||||
},
|
||||
{
|
||||
"message_id": "agent-user-010",
|
||||
"role": "user",
|
||||
"text": "По выбранному объекту \"Столешница 600*3050*26 дуб ниагара\": у кого куплено",
|
||||
"created_at": "2026-06-03T10:37:12+00:00",
|
||||
"reply_type": null,
|
||||
"trace_id": null,
|
||||
"debug": null
|
||||
},
|
||||
{
|
||||
"message_id": "agent-user-011",
|
||||
"role": "user",
|
||||
"text": "По выбранному объекту \"Столешница 600*3050*26 дуб ниагара\": где мы купили это",
|
||||
"created_at": "2026-06-03T10:37:12+00:00",
|
||||
"reply_type": null,
|
||||
"trace_id": null,
|
||||
"debug": null
|
||||
},
|
||||
{
|
||||
"message_id": "agent-user-012",
|
||||
"role": "user",
|
||||
"text": "По выбранному объекту \"Столешница 600*3050*26 дуб ниагара\": где куплено!!",
|
||||
"created_at": "2026-06-03T10:37:12+00:00",
|
||||
"reply_type": null,
|
||||
"trace_id": null,
|
||||
"debug": null
|
||||
},
|
||||
{
|
||||
"message_id": "agent-user-013",
|
||||
"role": "user",
|
||||
"text": "кто это поставил нам",
|
||||
"created_at": "2026-06-03T10:37:12+00:00",
|
||||
"reply_type": null,
|
||||
"trace_id": null,
|
||||
"debug": null
|
||||
},
|
||||
{
|
||||
"message_id": "agent-user-014",
|
||||
"role": "user",
|
||||
"text": "От какого поставщика куплен товар Столешница 600*3050*26 дуб ниагара",
|
||||
"created_at": "2026-06-03T10:37:12+00:00",
|
||||
"reply_type": null,
|
||||
"trace_id": null,
|
||||
"debug": null
|
||||
},
|
||||
{
|
||||
"message_id": "agent-user-015",
|
||||
"role": "user",
|
||||
"text": "Когда был куплен товар Столешница 600*3050*26 дуб ниагара",
|
||||
"created_at": "2026-06-03T10:37:12+00:00",
|
||||
"reply_type": null,
|
||||
"trace_id": null,
|
||||
"debug": null
|
||||
},
|
||||
{
|
||||
"message_id": "agent-user-016",
|
||||
"role": "user",
|
||||
"text": "когда",
|
||||
"created_at": "2026-06-03T10:37:12+00:00",
|
||||
"reply_type": null,
|
||||
"trace_id": null,
|
||||
"debug": null
|
||||
},
|
||||
{
|
||||
"message_id": "agent-user-017",
|
||||
"role": "user",
|
||||
"text": "По выбранному объекту \"Столешница 600*3050*26 дуб ниагара\": по каким документам это купили",
|
||||
"created_at": "2026-06-03T10:37:12+00:00",
|
||||
"reply_type": null,
|
||||
"trace_id": null,
|
||||
"debug": null
|
||||
},
|
||||
{
|
||||
"message_id": "agent-user-018",
|
||||
"role": "user",
|
||||
"text": "покажи документы по этой позиции",
|
||||
"created_at": "2026-06-03T10:37:12+00:00",
|
||||
"reply_type": null,
|
||||
"trace_id": null,
|
||||
"debug": null
|
||||
},
|
||||
{
|
||||
"message_id": "agent-user-019",
|
||||
"role": "user",
|
||||
"text": "По каким документам был куплен товар Столешница 600*3050*26 дуб ниагара",
|
||||
"created_at": "2026-06-03T10:37:12+00:00",
|
||||
"reply_type": null,
|
||||
"trace_id": null,
|
||||
"debug": null
|
||||
},
|
||||
{
|
||||
"message_id": "agent-user-020",
|
||||
"role": "user",
|
||||
"text": "какие остатки на складе на сентябрь 2021",
|
||||
"created_at": "2026-06-03T10:37:12+00:00",
|
||||
"reply_type": null,
|
||||
"trace_id": null,
|
||||
"debug": null
|
||||
},
|
||||
{
|
||||
"message_id": "agent-user-021",
|
||||
"role": "user",
|
||||
"text": "У какого поставщика были куплены товары, которые сейчас лежат на складе Основной склад",
|
||||
"created_at": "2026-06-03T10:37:12+00:00",
|
||||
"reply_type": null,
|
||||
"trace_id": null,
|
||||
"debug": null
|
||||
},
|
||||
{
|
||||
"message_id": "agent-user-022",
|
||||
"role": "user",
|
||||
"text": "По какому поставщику проходит текущий товарный остаток на складе Основной склад",
|
||||
"created_at": "2026-06-03T10:37:12+00:00",
|
||||
"reply_type": null,
|
||||
"trace_id": null,
|
||||
"debug": null
|
||||
},
|
||||
{
|
||||
"message_id": "agent-user-023",
|
||||
"role": "user",
|
||||
"text": "Какие товары от поставщика Торговый дом \\Союз сейчас еще лежат на складе Основной склад",
|
||||
"created_at": "2026-06-03T10:37:12+00:00",
|
||||
"reply_type": null,
|
||||
"trace_id": null,
|
||||
"debug": null
|
||||
},
|
||||
{
|
||||
"message_id": "agent-user-024",
|
||||
"role": "user",
|
||||
"text": "Какие товары по состоянию на дату 2019-03-31 были куплены у поставщика Торговый дом \\Союз",
|
||||
"created_at": "2026-06-03T10:37:12+00:00",
|
||||
"reply_type": null,
|
||||
"trace_id": null,
|
||||
"debug": null
|
||||
},
|
||||
{
|
||||
"message_id": "agent-user-025",
|
||||
"role": "user",
|
||||
"text": "По выбранному объекту \"Четки Пост (84*117)\": кто это поставил нам",
|
||||
"created_at": "2026-06-03T10:37:12+00:00",
|
||||
"reply_type": null,
|
||||
"trace_id": null,
|
||||
"debug": null
|
||||
},
|
||||
{
|
||||
"message_id": "agent-user-026",
|
||||
"role": "user",
|
||||
"text": "Какие остатки по товарам на эту дату относятся к старым закупкам",
|
||||
"created_at": "2026-06-03T10:37:12+00:00",
|
||||
"reply_type": null,
|
||||
"trace_id": null,
|
||||
"debug": null
|
||||
},
|
||||
{
|
||||
"message_id": "agent-user-027",
|
||||
"role": "user",
|
||||
"text": "Есть ли остатки товара, которые закупались очень давно",
|
||||
"created_at": "2026-06-03T10:37:12+00:00",
|
||||
"reply_type": null,
|
||||
"trace_id": null,
|
||||
"debug": null
|
||||
},
|
||||
{
|
||||
"message_id": "agent-user-028",
|
||||
"role": "user",
|
||||
"text": "Какие товары сейчас висят в остатке без понятной привязки к поставщику",
|
||||
"created_at": "2026-06-03T10:37:12+00:00",
|
||||
"reply_type": null,
|
||||
"trace_id": null,
|
||||
"debug": null
|
||||
},
|
||||
{
|
||||
"message_id": "agent-user-029",
|
||||
"role": "user",
|
||||
"text": "Какие товары числятся на 41 счете на дату 2020-03-31",
|
||||
"created_at": "2026-06-03T10:37:12+00:00",
|
||||
"reply_type": null,
|
||||
"trace_id": null,
|
||||
"debug": null
|
||||
},
|
||||
{
|
||||
"message_id": "agent-user-030",
|
||||
"role": "user",
|
||||
"text": "По выбранному объекту \"Шкаф картотечный 1000*400*2100\": кому был продан товар",
|
||||
"created_at": "2026-06-03T10:37:12+00:00",
|
||||
"reply_type": null,
|
||||
"trace_id": null,
|
||||
"debug": null
|
||||
},
|
||||
{
|
||||
"message_id": "agent-user-031",
|
||||
"role": "user",
|
||||
"text": "Кому был продан товар Шкаф картотечный 1000*400*2100",
|
||||
"created_at": "2026-06-03T10:37:12+00:00",
|
||||
"reply_type": null,
|
||||
"trace_id": null,
|
||||
"debug": null
|
||||
},
|
||||
{
|
||||
"message_id": "agent-user-032",
|
||||
"role": "user",
|
||||
"text": "По выбранному объекту \"Шкаф картотечный 1000*400*2100\": через какие документы прошел путь товара: закупка -> склад -> продажа",
|
||||
"created_at": "2026-06-03T10:37:12+00:00",
|
||||
"reply_type": null,
|
||||
"trace_id": null,
|
||||
"debug": null
|
||||
},
|
||||
{
|
||||
"message_id": "agent-user-033",
|
||||
"role": "user",
|
||||
"text": "Через какие документы прошел путь товара Шкаф картотечный 1000*400*2100: закупка -> склад -> продажа",
|
||||
"created_at": "2026-06-03T10:37:12+00:00",
|
||||
"reply_type": null,
|
||||
"trace_id": null,
|
||||
"debug": null
|
||||
},
|
||||
{
|
||||
"message_id": "agent-user-034",
|
||||
"role": "user",
|
||||
"text": "Есть ли документально подтвержденная цепочка: поставщик Гамма-мебель, ООО -> товар Шкаф картотечный 1000*400*2100 -> покупатель Департамент капитального ремонта города Москвы",
|
||||
"created_at": "2026-06-03T10:37:12+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": "supplier-item-buyer documentary chain and route candidate triage",
|
||||
"architecture_phase": "assistant-stage1 inventory stock hardening",
|
||||
"source_spec_file": "X:\\1C\\NDC_1C\\artifacts\\domain_runs\\inventory_stock_active_contract_full_20260603_p28_loop\\iterations\\iteration_00\\pack_output\\pack_run\\pack_manifest.json",
|
||||
"scenario_id": null,
|
||||
"semantic_tags": [],
|
||||
"validation_status": "accepted_domain_case_loop_pack",
|
||||
"validated_run_dir": "artifacts\\domain_runs\\inventory_stock_active_contract_full_20260603_p28_loop\\iterations\\iteration_00\\pack_output\\pack_run",
|
||||
"saved_after_validated_replay": true,
|
||||
"save_gate": {
|
||||
"schema_version": "agent_semantic_save_gate_v1",
|
||||
"validation_status": "accepted_domain_case_loop_pack",
|
||||
"validated_run_dir": "artifacts\\domain_runs\\inventory_stock_active_contract_full_20260603_p28_loop\\iterations\\iteration_00\\pack_output\\pack_run",
|
||||
"final_status": "accepted",
|
||||
"acceptance_status": "accepted",
|
||||
"execution_status": "exact",
|
||||
"pack_id": "pack_run",
|
||||
"scenario_count": 5,
|
||||
"repair_target_count": 0,
|
||||
"repair_target_severity_counts": {
|
||||
"P0": 0,
|
||||
"P1": 0,
|
||||
"P2": 0
|
||||
},
|
||||
"effective_runtime": {
|
||||
"manifest_path": "artifacts\\domain_runs\\inventory_stock_active_contract_full_20260603_p28_loop\\iterations\\iteration_00\\pack_output\\pack_run\\effective_runtime.json",
|
||||
"runner": "domain_case_loop.run-pack",
|
||||
"git_sha": "ed44c6428afc07c49d54e3311567be2bb0a7d196",
|
||||
"backend_url": "http://127.0.0.1:8787",
|
||||
"mcp_proxy_url": "http://127.0.0.1:6003",
|
||||
"llm_provider": "local",
|
||||
"llm_model": "unsloth/qwen3-30b-a3b-instruct-2507",
|
||||
"temperature": 0.8,
|
||||
"max_output_tokens": 900,
|
||||
"prompt_version": "normalizer_v2_0_2",
|
||||
"prompt_source": "file",
|
||||
"prompt_hash": "f36e36a5e491cd24511b380e0c0059cb01aea5e2af738fb9f01671cd291735c1",
|
||||
"prompt_registry_status": "pass"
|
||||
},
|
||||
"saved_after_validated_replay": true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,283 +0,0 @@
|
|||
{
|
||||
"saved_at": "2026-06-03T10:54:28+00:00",
|
||||
"generation_id": "gen-ag06031054-1bd955",
|
||||
"mode": "saved_user_sessions",
|
||||
"title": "AGENT | Phase105 mixed schema primitive closure live8",
|
||||
"agent_run": true,
|
||||
"questions": [
|
||||
"кайф - что там на складе по остаткам?",
|
||||
"АЛЬТЕРНАТИВА",
|
||||
"а исторические остатки на другие даты умеешь?",
|
||||
"давай на июнь 2017",
|
||||
"Теперь дай взрослый обзор за 2020 по компании: входящие, исходящие, нетто, топы, но банк в топах отдельно объясни как финансовый поток, если по назначению он не обычный клиент или поставщик.",
|
||||
"А отдельно по СБЕРБАНКУ: он для нас клиент, поставщик или финансовый поток? Дай коротко по подтвержденным строкам.",
|
||||
"А теперь по поставщику Группа СВК за 2020: сколько мы ему заплатили и какой общий денежный смысл?",
|
||||
"А теперь по Группа СВК за 2020: сколько денег получили, сколько заплатили и какое нетто?",
|
||||
"кому мы должны на конец 2020?",
|
||||
"а нам кто должен на конец 2020?",
|
||||
"сколько НДС надо заплатить в налоговую за декабрь 2019?",
|
||||
"скока денег альтернатива заработала за 20 год?",
|
||||
"а это чистая прибыль?"
|
||||
],
|
||||
"metadata": {
|
||||
"assistant_prompt_version": null,
|
||||
"decomposition_prompt_version": null,
|
||||
"prompt_fingerprint": null,
|
||||
"agent_focus": "cross-contour canary after inventory and truth-harness hardening",
|
||||
"architecture_phase": "assistant-stage1 mixed MCP schema primitive closure",
|
||||
"source_spec_file": "X:\\1C\\NDC_1C\\docs\\orchestration\\address_truth_harness_phase105_mixed_schema_primitive_closure.json",
|
||||
"scenario_id": "address_truth_harness_phase105_mixed_schema_primitive_closure",
|
||||
"semantic_tags": [
|
||||
"bank_like_counterparty",
|
||||
"business_overview",
|
||||
"business_overview_followup",
|
||||
"counterparty_net_cash_flow",
|
||||
"date_followup",
|
||||
"debt_polarity",
|
||||
"earnings_wording",
|
||||
"financial_role_purpose",
|
||||
"generic_role_tail_anchor_hygiene",
|
||||
"historical_inventory",
|
||||
"inventory_capability_meta",
|
||||
"inventory_root",
|
||||
"organization_scope",
|
||||
"payables",
|
||||
"profit_boundary",
|
||||
"receivables",
|
||||
"redundant_scope_selection",
|
||||
"resolved_organization_scope",
|
||||
"stale_scope_guard",
|
||||
"supplier_prefix_canary",
|
||||
"tax_period",
|
||||
"value_flow",
|
||||
"vat_continuity",
|
||||
"warehouse_not_required"
|
||||
],
|
||||
"validation_status": "accepted_live_replay",
|
||||
"validated_run_dir": "artifacts\\domain_runs\\phase105_mixed_schema_primitive_closure_20260603_live8",
|
||||
"saved_after_validated_replay": true,
|
||||
"save_gate": {
|
||||
"schema_version": "agent_semantic_save_gate_v1",
|
||||
"validation_status": "accepted_live_replay",
|
||||
"validated_run_dir": "artifacts\\domain_runs\\phase105_mixed_schema_primitive_closure_20260603_live8",
|
||||
"final_status": "accepted",
|
||||
"review_overall_status": "pass",
|
||||
"business_overall_status": "pass",
|
||||
"steps_total": 13,
|
||||
"steps_passed": 13,
|
||||
"steps_failed": 0,
|
||||
"steps_with_business_failures": 0,
|
||||
"steps_with_business_warnings": 0,
|
||||
"acceptance_gate_passed": true,
|
||||
"effective_runtime": {
|
||||
"manifest_path": "artifacts\\domain_runs\\phase105_mixed_schema_primitive_closure_20260603_live8\\effective_runtime.json",
|
||||
"runner": "domain_truth_harness.run-live",
|
||||
"git_sha": "66d5f6827016452e25da3f4becae0ee841382046",
|
||||
"backend_url": "http://127.0.0.1:8787",
|
||||
"mcp_proxy_url": "http://127.0.0.1:6003",
|
||||
"llm_provider": "local",
|
||||
"llm_model": "unsloth/qwen3-30b-a3b-instruct-2507",
|
||||
"temperature": 0.8,
|
||||
"max_output_tokens": 900,
|
||||
"prompt_version": "normalizer_v2_0_2",
|
||||
"prompt_source": "file",
|
||||
"prompt_hash": "f36e36a5e491cd24511b380e0c0059cb01aea5e2af738fb9f01671cd291735c1",
|
||||
"prompt_registry_status": "pass"
|
||||
},
|
||||
"saved_after_validated_replay": true
|
||||
}
|
||||
},
|
||||
"source_session_id": null,
|
||||
"session": {
|
||||
"session_id": null,
|
||||
"mode": "agent_semantic_run",
|
||||
"items": [
|
||||
{
|
||||
"message_id": "agent-user-001",
|
||||
"role": "user",
|
||||
"text": "кайф - что там на складе по остаткам?",
|
||||
"created_at": "2026-06-03T10:54:28+00:00",
|
||||
"reply_type": null,
|
||||
"trace_id": null,
|
||||
"debug": null
|
||||
},
|
||||
{
|
||||
"message_id": "agent-user-002",
|
||||
"role": "user",
|
||||
"text": "АЛЬТЕРНАТИВА",
|
||||
"created_at": "2026-06-03T10:54:28+00:00",
|
||||
"reply_type": null,
|
||||
"trace_id": null,
|
||||
"debug": null
|
||||
},
|
||||
{
|
||||
"message_id": "agent-user-003",
|
||||
"role": "user",
|
||||
"text": "а исторические остатки на другие даты умеешь?",
|
||||
"created_at": "2026-06-03T10:54:28+00:00",
|
||||
"reply_type": null,
|
||||
"trace_id": null,
|
||||
"debug": null
|
||||
},
|
||||
{
|
||||
"message_id": "agent-user-004",
|
||||
"role": "user",
|
||||
"text": "давай на июнь 2017",
|
||||
"created_at": "2026-06-03T10:54:28+00:00",
|
||||
"reply_type": null,
|
||||
"trace_id": null,
|
||||
"debug": null
|
||||
},
|
||||
{
|
||||
"message_id": "agent-user-005",
|
||||
"role": "user",
|
||||
"text": "Теперь дай взрослый обзор за 2020 по компании: входящие, исходящие, нетто, топы, но банк в топах отдельно объясни как финансовый поток, если по назначению он не обычный клиент или поставщик.",
|
||||
"created_at": "2026-06-03T10:54:28+00:00",
|
||||
"reply_type": null,
|
||||
"trace_id": null,
|
||||
"debug": null
|
||||
},
|
||||
{
|
||||
"message_id": "agent-user-006",
|
||||
"role": "user",
|
||||
"text": "А отдельно по СБЕРБАНКУ: он для нас клиент, поставщик или финансовый поток? Дай коротко по подтвержденным строкам.",
|
||||
"created_at": "2026-06-03T10:54:28+00:00",
|
||||
"reply_type": null,
|
||||
"trace_id": null,
|
||||
"debug": null
|
||||
},
|
||||
{
|
||||
"message_id": "agent-user-007",
|
||||
"role": "user",
|
||||
"text": "А теперь по поставщику Группа СВК за 2020: сколько мы ему заплатили и какой общий денежный смысл?",
|
||||
"created_at": "2026-06-03T10:54:28+00:00",
|
||||
"reply_type": null,
|
||||
"trace_id": null,
|
||||
"debug": null
|
||||
},
|
||||
{
|
||||
"message_id": "agent-user-008",
|
||||
"role": "user",
|
||||
"text": "А теперь по Группа СВК за 2020: сколько денег получили, сколько заплатили и какое нетто?",
|
||||
"created_at": "2026-06-03T10:54:28+00:00",
|
||||
"reply_type": null,
|
||||
"trace_id": null,
|
||||
"debug": null
|
||||
},
|
||||
{
|
||||
"message_id": "agent-user-009",
|
||||
"role": "user",
|
||||
"text": "кому мы должны на конец 2020?",
|
||||
"created_at": "2026-06-03T10:54:28+00:00",
|
||||
"reply_type": null,
|
||||
"trace_id": null,
|
||||
"debug": null
|
||||
},
|
||||
{
|
||||
"message_id": "agent-user-010",
|
||||
"role": "user",
|
||||
"text": "а нам кто должен на конец 2020?",
|
||||
"created_at": "2026-06-03T10:54:28+00:00",
|
||||
"reply_type": null,
|
||||
"trace_id": null,
|
||||
"debug": null
|
||||
},
|
||||
{
|
||||
"message_id": "agent-user-011",
|
||||
"role": "user",
|
||||
"text": "сколько НДС надо заплатить в налоговую за декабрь 2019?",
|
||||
"created_at": "2026-06-03T10:54:28+00:00",
|
||||
"reply_type": null,
|
||||
"trace_id": null,
|
||||
"debug": null
|
||||
},
|
||||
{
|
||||
"message_id": "agent-user-012",
|
||||
"role": "user",
|
||||
"text": "скока денег альтернатива заработала за 20 год?",
|
||||
"created_at": "2026-06-03T10:54:28+00:00",
|
||||
"reply_type": null,
|
||||
"trace_id": null,
|
||||
"debug": null
|
||||
},
|
||||
{
|
||||
"message_id": "agent-user-013",
|
||||
"role": "user",
|
||||
"text": "а это чистая прибыль?",
|
||||
"created_at": "2026-06-03T10:54:28+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-contour canary after inventory and truth-harness hardening",
|
||||
"architecture_phase": "assistant-stage1 mixed MCP schema primitive closure",
|
||||
"source_spec_file": "X:\\1C\\NDC_1C\\docs\\orchestration\\address_truth_harness_phase105_mixed_schema_primitive_closure.json",
|
||||
"scenario_id": "address_truth_harness_phase105_mixed_schema_primitive_closure",
|
||||
"semantic_tags": [
|
||||
"bank_like_counterparty",
|
||||
"business_overview",
|
||||
"business_overview_followup",
|
||||
"counterparty_net_cash_flow",
|
||||
"date_followup",
|
||||
"debt_polarity",
|
||||
"earnings_wording",
|
||||
"financial_role_purpose",
|
||||
"generic_role_tail_anchor_hygiene",
|
||||
"historical_inventory",
|
||||
"inventory_capability_meta",
|
||||
"inventory_root",
|
||||
"organization_scope",
|
||||
"payables",
|
||||
"profit_boundary",
|
||||
"receivables",
|
||||
"redundant_scope_selection",
|
||||
"resolved_organization_scope",
|
||||
"stale_scope_guard",
|
||||
"supplier_prefix_canary",
|
||||
"tax_period",
|
||||
"value_flow",
|
||||
"vat_continuity",
|
||||
"warehouse_not_required"
|
||||
],
|
||||
"validation_status": "accepted_live_replay",
|
||||
"validated_run_dir": "artifacts\\domain_runs\\phase105_mixed_schema_primitive_closure_20260603_live8",
|
||||
"saved_after_validated_replay": true,
|
||||
"save_gate": {
|
||||
"schema_version": "agent_semantic_save_gate_v1",
|
||||
"validation_status": "accepted_live_replay",
|
||||
"validated_run_dir": "artifacts\\domain_runs\\phase105_mixed_schema_primitive_closure_20260603_live8",
|
||||
"final_status": "accepted",
|
||||
"review_overall_status": "pass",
|
||||
"business_overall_status": "pass",
|
||||
"steps_total": 13,
|
||||
"steps_passed": 13,
|
||||
"steps_failed": 0,
|
||||
"steps_with_business_failures": 0,
|
||||
"steps_with_business_warnings": 0,
|
||||
"acceptance_gate_passed": true,
|
||||
"effective_runtime": {
|
||||
"manifest_path": "artifacts\\domain_runs\\phase105_mixed_schema_primitive_closure_20260603_live8\\effective_runtime.json",
|
||||
"runner": "domain_truth_harness.run-live",
|
||||
"git_sha": "66d5f6827016452e25da3f4becae0ee841382046",
|
||||
"backend_url": "http://127.0.0.1:8787",
|
||||
"mcp_proxy_url": "http://127.0.0.1:6003",
|
||||
"llm_provider": "local",
|
||||
"llm_model": "unsloth/qwen3-30b-a3b-instruct-2507",
|
||||
"temperature": 0.8,
|
||||
"max_output_tokens": 900,
|
||||
"prompt_version": "normalizer_v2_0_2",
|
||||
"prompt_source": "file",
|
||||
"prompt_hash": "f36e36a5e491cd24511b380e0c0059cb01aea5e2af738fb9f01671cd291735c1",
|
||||
"prompt_registry_status": "pass"
|
||||
},
|
||||
"saved_after_validated_replay": true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,377 +0,0 @@
|
|||
{
|
||||
"saved_at": "2026-06-03T13:28:09+00:00",
|
||||
"generation_id": "gen-ag06031328-3e1dd1",
|
||||
"mode": "saved_user_sessions",
|
||||
"title": "AGENT | Phase 83 mixed planner-brain replay for catalog alignment, pivots, and legacy continuity",
|
||||
"agent_run": true,
|
||||
"questions": [
|
||||
"привет, ты на связи?",
|
||||
"найди в 1С контрагента СВК",
|
||||
"сколько получили по нему за 2020 год",
|
||||
"а теперь сколько заплатили?",
|
||||
"а какое нетто?",
|
||||
"а по документам?",
|
||||
"а по движениям?",
|
||||
"Хочу быстрый денежный срез по одной организации без привязки к контрагенту. Сколько вообще входящих денег было за 2020 год?",
|
||||
"По ООО Альтернатива Плюс.",
|
||||
"Понял, тогда за все время.",
|
||||
"Хорошо. А что по ООО Альтернатива Плюс больше в 2020 году: входящие или исходящие деньги?",
|
||||
"А что по ООО Альтернатива Плюс больше уже за 2021 год: входящие или исходящие деньги?",
|
||||
"И кто больше всего принес денег этой организации в 2020 году?",
|
||||
"А в 2021 году?",
|
||||
"а по Альтернативе Плюс сколько лет активности в базе 1С?",
|
||||
"Как ты оценишь деятельность компании?",
|
||||
"какое нетто по деньгам с Группа СВК за 2020 год: сколько получили и сколько заплатили?",
|
||||
"какие справочники 1С есть по контрагентам?",
|
||||
"давай дальше",
|
||||
"а чем капибара отличается от утки?"
|
||||
],
|
||||
"metadata": {
|
||||
"assistant_prompt_version": null,
|
||||
"decomposition_prompt_version": null,
|
||||
"prompt_fingerprint": null,
|
||||
"agent_focus": "Mixed AGENT replay for Planner Autonomy Consolidation. The pack interleaves selected-counterparty catalog-alignment probes, open-organization money flow, ranking, broad-evaluation continuity, metadata drilldown, and off-domain living-chat safety.",
|
||||
"architecture_phase": "turnaround_11",
|
||||
"source_spec_file": "X:\\1C\\NDC_1C\\docs\\orchestration\\address_truth_harness_phase83_planner_brain_alignment_mix.json",
|
||||
"scenario_id": "address_truth_harness_phase83_planner_brain_alignment_mix",
|
||||
"semantic_tags": [
|
||||
"alias_grounding",
|
||||
"all_time_followup",
|
||||
"broad_business_evaluation",
|
||||
"broad_eval_bridge_preserved",
|
||||
"catalog_drilldown",
|
||||
"catalog_metadata_surface",
|
||||
"company_activity_lifecycle",
|
||||
"counterparty_catalog_scope",
|
||||
"counterparty_net_cash_flow",
|
||||
"date_carryover",
|
||||
"document_evidence",
|
||||
"document_pivot",
|
||||
"entity_resolution",
|
||||
"followup_anchor",
|
||||
"followup_reuse",
|
||||
"grounded_context_seed",
|
||||
"grounded_summary",
|
||||
"human_answer",
|
||||
"human_dialog",
|
||||
"incoming_value_flow",
|
||||
"mcp_discovery_gate_sanity",
|
||||
"meta_smalltalk",
|
||||
"movement_evidence",
|
||||
"net_value_flow",
|
||||
"neutral_followup",
|
||||
"off_domain_living_chat",
|
||||
"open_scope_total",
|
||||
"organization_clarification",
|
||||
"organization_scope",
|
||||
"payout_switch",
|
||||
"planner_catalog_alignment",
|
||||
"stale_replay_forbidden",
|
||||
"value_flow_comparison",
|
||||
"value_flow_pivot",
|
||||
"value_flow_ranking",
|
||||
"year_switch"
|
||||
],
|
||||
"validation_status": "accepted_live_replay",
|
||||
"validated_run_dir": "artifacts\\domain_runs\\phase83_planner_brain_alignment_mix_20260603_live9",
|
||||
"saved_after_validated_replay": true,
|
||||
"save_gate": {
|
||||
"schema_version": "agent_semantic_save_gate_v1",
|
||||
"validation_status": "accepted_live_replay",
|
||||
"validated_run_dir": "artifacts\\domain_runs\\phase83_planner_brain_alignment_mix_20260603_live9",
|
||||
"final_status": "accepted",
|
||||
"review_overall_status": "pass",
|
||||
"business_overall_status": "pass",
|
||||
"steps_total": 20,
|
||||
"steps_passed": 20,
|
||||
"steps_failed": 0,
|
||||
"steps_with_business_failures": 0,
|
||||
"steps_with_business_warnings": 0,
|
||||
"acceptance_gate_passed": true,
|
||||
"effective_runtime": {
|
||||
"manifest_path": "artifacts\\domain_runs\\phase83_planner_brain_alignment_mix_20260603_live9\\effective_runtime.json",
|
||||
"runner": "domain_truth_harness.run-live",
|
||||
"git_sha": "5ed25bad8115842e835b08b35a0edf623a757d9b",
|
||||
"backend_url": "http://127.0.0.1:8787",
|
||||
"mcp_proxy_url": "http://127.0.0.1:6003",
|
||||
"llm_provider": "local",
|
||||
"llm_model": "unsloth/qwen3-30b-a3b-instruct-2507",
|
||||
"temperature": 0.8,
|
||||
"max_output_tokens": 900,
|
||||
"prompt_version": "normalizer_v2_0_2",
|
||||
"prompt_source": "file",
|
||||
"prompt_hash": "f36e36a5e491cd24511b380e0c0059cb01aea5e2af738fb9f01671cd291735c1",
|
||||
"prompt_registry_status": "pass"
|
||||
},
|
||||
"saved_after_validated_replay": true
|
||||
}
|
||||
},
|
||||
"source_session_id": null,
|
||||
"session": {
|
||||
"session_id": null,
|
||||
"mode": "agent_semantic_run",
|
||||
"items": [
|
||||
{
|
||||
"message_id": "agent-user-001",
|
||||
"role": "user",
|
||||
"text": "привет, ты на связи?",
|
||||
"created_at": "2026-06-03T13:28:09+00:00",
|
||||
"reply_type": null,
|
||||
"trace_id": null,
|
||||
"debug": null
|
||||
},
|
||||
{
|
||||
"message_id": "agent-user-002",
|
||||
"role": "user",
|
||||
"text": "найди в 1С контрагента СВК",
|
||||
"created_at": "2026-06-03T13:28:09+00:00",
|
||||
"reply_type": null,
|
||||
"trace_id": null,
|
||||
"debug": null
|
||||
},
|
||||
{
|
||||
"message_id": "agent-user-003",
|
||||
"role": "user",
|
||||
"text": "сколько получили по нему за 2020 год",
|
||||
"created_at": "2026-06-03T13:28:09+00:00",
|
||||
"reply_type": null,
|
||||
"trace_id": null,
|
||||
"debug": null
|
||||
},
|
||||
{
|
||||
"message_id": "agent-user-004",
|
||||
"role": "user",
|
||||
"text": "а теперь сколько заплатили?",
|
||||
"created_at": "2026-06-03T13:28:09+00:00",
|
||||
"reply_type": null,
|
||||
"trace_id": null,
|
||||
"debug": null
|
||||
},
|
||||
{
|
||||
"message_id": "agent-user-005",
|
||||
"role": "user",
|
||||
"text": "а какое нетто?",
|
||||
"created_at": "2026-06-03T13:28:09+00:00",
|
||||
"reply_type": null,
|
||||
"trace_id": null,
|
||||
"debug": null
|
||||
},
|
||||
{
|
||||
"message_id": "agent-user-006",
|
||||
"role": "user",
|
||||
"text": "а по документам?",
|
||||
"created_at": "2026-06-03T13:28:09+00:00",
|
||||
"reply_type": null,
|
||||
"trace_id": null,
|
||||
"debug": null
|
||||
},
|
||||
{
|
||||
"message_id": "agent-user-007",
|
||||
"role": "user",
|
||||
"text": "а по движениям?",
|
||||
"created_at": "2026-06-03T13:28:09+00:00",
|
||||
"reply_type": null,
|
||||
"trace_id": null,
|
||||
"debug": null
|
||||
},
|
||||
{
|
||||
"message_id": "agent-user-008",
|
||||
"role": "user",
|
||||
"text": "Хочу быстрый денежный срез по одной организации без привязки к контрагенту. Сколько вообще входящих денег было за 2020 год?",
|
||||
"created_at": "2026-06-03T13:28:09+00:00",
|
||||
"reply_type": null,
|
||||
"trace_id": null,
|
||||
"debug": null
|
||||
},
|
||||
{
|
||||
"message_id": "agent-user-009",
|
||||
"role": "user",
|
||||
"text": "По ООО Альтернатива Плюс.",
|
||||
"created_at": "2026-06-03T13:28:09+00:00",
|
||||
"reply_type": null,
|
||||
"trace_id": null,
|
||||
"debug": null
|
||||
},
|
||||
{
|
||||
"message_id": "agent-user-010",
|
||||
"role": "user",
|
||||
"text": "Понял, тогда за все время.",
|
||||
"created_at": "2026-06-03T13:28:09+00:00",
|
||||
"reply_type": null,
|
||||
"trace_id": null,
|
||||
"debug": null
|
||||
},
|
||||
{
|
||||
"message_id": "agent-user-011",
|
||||
"role": "user",
|
||||
"text": "Хорошо. А что по ООО Альтернатива Плюс больше в 2020 году: входящие или исходящие деньги?",
|
||||
"created_at": "2026-06-03T13:28:09+00:00",
|
||||
"reply_type": null,
|
||||
"trace_id": null,
|
||||
"debug": null
|
||||
},
|
||||
{
|
||||
"message_id": "agent-user-012",
|
||||
"role": "user",
|
||||
"text": "А что по ООО Альтернатива Плюс больше уже за 2021 год: входящие или исходящие деньги?",
|
||||
"created_at": "2026-06-03T13:28:09+00:00",
|
||||
"reply_type": null,
|
||||
"trace_id": null,
|
||||
"debug": null
|
||||
},
|
||||
{
|
||||
"message_id": "agent-user-013",
|
||||
"role": "user",
|
||||
"text": "И кто больше всего принес денег этой организации в 2020 году?",
|
||||
"created_at": "2026-06-03T13:28:09+00:00",
|
||||
"reply_type": null,
|
||||
"trace_id": null,
|
||||
"debug": null
|
||||
},
|
||||
{
|
||||
"message_id": "agent-user-014",
|
||||
"role": "user",
|
||||
"text": "А в 2021 году?",
|
||||
"created_at": "2026-06-03T13:28:09+00:00",
|
||||
"reply_type": null,
|
||||
"trace_id": null,
|
||||
"debug": null
|
||||
},
|
||||
{
|
||||
"message_id": "agent-user-015",
|
||||
"role": "user",
|
||||
"text": "а по Альтернативе Плюс сколько лет активности в базе 1С?",
|
||||
"created_at": "2026-06-03T13:28:09+00:00",
|
||||
"reply_type": null,
|
||||
"trace_id": null,
|
||||
"debug": null
|
||||
},
|
||||
{
|
||||
"message_id": "agent-user-016",
|
||||
"role": "user",
|
||||
"text": "Как ты оценишь деятельность компании?",
|
||||
"created_at": "2026-06-03T13:28:09+00:00",
|
||||
"reply_type": null,
|
||||
"trace_id": null,
|
||||
"debug": null
|
||||
},
|
||||
{
|
||||
"message_id": "agent-user-017",
|
||||
"role": "user",
|
||||
"text": "какое нетто по деньгам с Группа СВК за 2020 год: сколько получили и сколько заплатили?",
|
||||
"created_at": "2026-06-03T13:28:09+00:00",
|
||||
"reply_type": null,
|
||||
"trace_id": null,
|
||||
"debug": null
|
||||
},
|
||||
{
|
||||
"message_id": "agent-user-018",
|
||||
"role": "user",
|
||||
"text": "какие справочники 1С есть по контрагентам?",
|
||||
"created_at": "2026-06-03T13:28:09+00:00",
|
||||
"reply_type": null,
|
||||
"trace_id": null,
|
||||
"debug": null
|
||||
},
|
||||
{
|
||||
"message_id": "agent-user-019",
|
||||
"role": "user",
|
||||
"text": "давай дальше",
|
||||
"created_at": "2026-06-03T13:28:09+00:00",
|
||||
"reply_type": null,
|
||||
"trace_id": null,
|
||||
"debug": null
|
||||
},
|
||||
{
|
||||
"message_id": "agent-user-020",
|
||||
"role": "user",
|
||||
"text": "а чем капибара отличается от утки?",
|
||||
"created_at": "2026-06-03T13:28:09+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": "Mixed AGENT replay for Planner Autonomy Consolidation. The pack interleaves selected-counterparty catalog-alignment probes, open-organization money flow, ranking, broad-evaluation continuity, metadata drilldown, and off-domain living-chat safety.",
|
||||
"architecture_phase": "turnaround_11",
|
||||
"source_spec_file": "X:\\1C\\NDC_1C\\docs\\orchestration\\address_truth_harness_phase83_planner_brain_alignment_mix.json",
|
||||
"scenario_id": "address_truth_harness_phase83_planner_brain_alignment_mix",
|
||||
"semantic_tags": [
|
||||
"alias_grounding",
|
||||
"all_time_followup",
|
||||
"broad_business_evaluation",
|
||||
"broad_eval_bridge_preserved",
|
||||
"catalog_drilldown",
|
||||
"catalog_metadata_surface",
|
||||
"company_activity_lifecycle",
|
||||
"counterparty_catalog_scope",
|
||||
"counterparty_net_cash_flow",
|
||||
"date_carryover",
|
||||
"document_evidence",
|
||||
"document_pivot",
|
||||
"entity_resolution",
|
||||
"followup_anchor",
|
||||
"followup_reuse",
|
||||
"grounded_context_seed",
|
||||
"grounded_summary",
|
||||
"human_answer",
|
||||
"human_dialog",
|
||||
"incoming_value_flow",
|
||||
"mcp_discovery_gate_sanity",
|
||||
"meta_smalltalk",
|
||||
"movement_evidence",
|
||||
"net_value_flow",
|
||||
"neutral_followup",
|
||||
"off_domain_living_chat",
|
||||
"open_scope_total",
|
||||
"organization_clarification",
|
||||
"organization_scope",
|
||||
"payout_switch",
|
||||
"planner_catalog_alignment",
|
||||
"stale_replay_forbidden",
|
||||
"value_flow_comparison",
|
||||
"value_flow_pivot",
|
||||
"value_flow_ranking",
|
||||
"year_switch"
|
||||
],
|
||||
"validation_status": "accepted_live_replay",
|
||||
"validated_run_dir": "artifacts\\domain_runs\\phase83_planner_brain_alignment_mix_20260603_live9",
|
||||
"saved_after_validated_replay": true,
|
||||
"save_gate": {
|
||||
"schema_version": "agent_semantic_save_gate_v1",
|
||||
"validation_status": "accepted_live_replay",
|
||||
"validated_run_dir": "artifacts\\domain_runs\\phase83_planner_brain_alignment_mix_20260603_live9",
|
||||
"final_status": "accepted",
|
||||
"review_overall_status": "pass",
|
||||
"business_overall_status": "pass",
|
||||
"steps_total": 20,
|
||||
"steps_passed": 20,
|
||||
"steps_failed": 0,
|
||||
"steps_with_business_failures": 0,
|
||||
"steps_with_business_warnings": 0,
|
||||
"acceptance_gate_passed": true,
|
||||
"effective_runtime": {
|
||||
"manifest_path": "artifacts\\domain_runs\\phase83_planner_brain_alignment_mix_20260603_live9\\effective_runtime.json",
|
||||
"runner": "domain_truth_harness.run-live",
|
||||
"git_sha": "5ed25bad8115842e835b08b35a0edf623a757d9b",
|
||||
"backend_url": "http://127.0.0.1:8787",
|
||||
"mcp_proxy_url": "http://127.0.0.1:6003",
|
||||
"llm_provider": "local",
|
||||
"llm_model": "unsloth/qwen3-30b-a3b-instruct-2507",
|
||||
"temperature": 0.8,
|
||||
"max_output_tokens": 900,
|
||||
"prompt_version": "normalizer_v2_0_2",
|
||||
"prompt_source": "file",
|
||||
"prompt_hash": "f36e36a5e491cd24511b380e0c0059cb01aea5e2af738fb9f01671cd291735c1",
|
||||
"prompt_registry_status": "pass"
|
||||
},
|
||||
"saved_after_validated_replay": true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,34 +0,0 @@
|
|||
{
|
||||
"suite_id": "assistant_saved_session_gen-ag06030841-d61275",
|
||||
"suite_version": "0.1.0",
|
||||
"schema_version": "assistant_saved_session_suite_v0_1",
|
||||
"generated_at": "2026-06-03T08:41:17+00:00",
|
||||
"generation_id": "gen-ag06030841-d61275",
|
||||
"mode": "saved_user_sessions",
|
||||
"title": "AGENT | Supplier-overlap limited answer includes a concrete next action",
|
||||
"domain": "inventory_supplier_overlap_next_action",
|
||||
"scenario_count": 1,
|
||||
"case_ids": [
|
||||
"SAVED-001"
|
||||
],
|
||||
"cases": [
|
||||
{
|
||||
"case_id": "SAVED-001",
|
||||
"scenario_tag": "agent_saved_user_sessions",
|
||||
"title": "AGENT | Supplier-overlap limited answer includes a concrete next action",
|
||||
"question_type": "followup",
|
||||
"broadness_level": "medium",
|
||||
"turns": [
|
||||
{
|
||||
"user_message": "какие остатки на складе на сентябрь 2021"
|
||||
},
|
||||
{
|
||||
"user_message": "У какого поставщика были куплены товары, которые лежали на складе Основной склад на 30.09.2021"
|
||||
},
|
||||
{
|
||||
"user_message": "По какому поставщику проходит товарный остаток на складе Основной склад на 30.09.2021"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -1,34 +0,0 @@
|
|||
{
|
||||
"suite_id": "assistant_saved_session_gen-ag06030918-32dc7c",
|
||||
"suite_version": "0.1.0",
|
||||
"schema_version": "assistant_saved_session_suite_v0_1",
|
||||
"generated_at": "2026-06-03T09:18:35+00:00",
|
||||
"generation_id": "gen-ag06030918-32dc7c",
|
||||
"mode": "saved_user_sessions",
|
||||
"title": "AGENT | Supplier-overlap follow-up keeps the inventory root date",
|
||||
"domain": "inventory_supplier_overlap_temporal_carryover",
|
||||
"scenario_count": 1,
|
||||
"case_ids": [
|
||||
"SAVED-001"
|
||||
],
|
||||
"cases": [
|
||||
{
|
||||
"case_id": "SAVED-001",
|
||||
"scenario_tag": "agent_saved_user_sessions",
|
||||
"title": "AGENT | Supplier-overlap follow-up keeps the inventory root date",
|
||||
"question_type": "followup",
|
||||
"broadness_level": "medium",
|
||||
"turns": [
|
||||
{
|
||||
"user_message": "какие остатки на складе на сентябрь 2021"
|
||||
},
|
||||
{
|
||||
"user_message": "У какого поставщика были куплены товары, которые сейчас лежат на складе Основной склад"
|
||||
},
|
||||
{
|
||||
"user_message": "По какому поставщику проходит текущий товарный остаток на складе Основной склад"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -1,127 +0,0 @@
|
|||
{
|
||||
"suite_id": "assistant_saved_session_gen-ag06031037-e4c3f0",
|
||||
"suite_version": "0.1.0",
|
||||
"schema_version": "assistant_saved_session_suite_v0_1",
|
||||
"generated_at": "2026-06-03T10:37:12+00:00",
|
||||
"generation_id": "gen-ag06031037-e4c3f0",
|
||||
"mode": "saved_user_sessions",
|
||||
"title": "AGENT | inventory stock supplier provenance p28 accepted",
|
||||
"domain": "inventory_stock",
|
||||
"scenario_count": 1,
|
||||
"case_ids": [
|
||||
"SAVED-001"
|
||||
],
|
||||
"cases": [
|
||||
{
|
||||
"case_id": "SAVED-001",
|
||||
"scenario_tag": "agent_saved_user_sessions",
|
||||
"title": "AGENT | inventory stock supplier provenance p28 accepted",
|
||||
"question_type": "followup",
|
||||
"broadness_level": "medium",
|
||||
"turns": [
|
||||
{
|
||||
"user_message": "Какие товары сейчас лежат на складе"
|
||||
},
|
||||
{
|
||||
"user_message": "покажи остатки на складе на март 2019"
|
||||
},
|
||||
{
|
||||
"user_message": "что лежит у нас на складе в марте 2019"
|
||||
},
|
||||
{
|
||||
"user_message": "Из каких товаров состоит остаток по 41 счету"
|
||||
},
|
||||
{
|
||||
"user_message": "Какие товары числятся на 41 счете на дату 2019-03-31"
|
||||
},
|
||||
{
|
||||
"user_message": "Какие конкретно номенклатуры формируют остаток по складу Основной склад на дату 2019-03-31"
|
||||
},
|
||||
{
|
||||
"user_message": "По выбранному объекту \"Столешница 600*3050*26 дуб ниагара\": от какого поставщика куплен товар"
|
||||
},
|
||||
{
|
||||
"user_message": "По выбранному объекту \"Столешница 600*3050*26 дуб ниагара\": кто это поставил нам"
|
||||
},
|
||||
{
|
||||
"user_message": "По выбранному объекту \"Столешница 600*3050*26 дуб ниагара\": у кого купили"
|
||||
},
|
||||
{
|
||||
"user_message": "По выбранному объекту \"Столешница 600*3050*26 дуб ниагара\": у кого куплено"
|
||||
},
|
||||
{
|
||||
"user_message": "По выбранному объекту \"Столешница 600*3050*26 дуб ниагара\": где мы купили это"
|
||||
},
|
||||
{
|
||||
"user_message": "По выбранному объекту \"Столешница 600*3050*26 дуб ниагара\": где куплено!!"
|
||||
},
|
||||
{
|
||||
"user_message": "кто это поставил нам"
|
||||
},
|
||||
{
|
||||
"user_message": "От какого поставщика куплен товар Столешница 600*3050*26 дуб ниагара"
|
||||
},
|
||||
{
|
||||
"user_message": "Когда был куплен товар Столешница 600*3050*26 дуб ниагара"
|
||||
},
|
||||
{
|
||||
"user_message": "когда"
|
||||
},
|
||||
{
|
||||
"user_message": "По выбранному объекту \"Столешница 600*3050*26 дуб ниагара\": по каким документам это купили"
|
||||
},
|
||||
{
|
||||
"user_message": "покажи документы по этой позиции"
|
||||
},
|
||||
{
|
||||
"user_message": "По каким документам был куплен товар Столешница 600*3050*26 дуб ниагара"
|
||||
},
|
||||
{
|
||||
"user_message": "какие остатки на складе на сентябрь 2021"
|
||||
},
|
||||
{
|
||||
"user_message": "У какого поставщика были куплены товары, которые сейчас лежат на складе Основной склад"
|
||||
},
|
||||
{
|
||||
"user_message": "По какому поставщику проходит текущий товарный остаток на складе Основной склад"
|
||||
},
|
||||
{
|
||||
"user_message": "Какие товары от поставщика Торговый дом \\Союз сейчас еще лежат на складе Основной склад"
|
||||
},
|
||||
{
|
||||
"user_message": "Какие товары по состоянию на дату 2019-03-31 были куплены у поставщика Торговый дом \\Союз"
|
||||
},
|
||||
{
|
||||
"user_message": "По выбранному объекту \"Четки Пост (84*117)\": кто это поставил нам"
|
||||
},
|
||||
{
|
||||
"user_message": "Какие остатки по товарам на эту дату относятся к старым закупкам"
|
||||
},
|
||||
{
|
||||
"user_message": "Есть ли остатки товара, которые закупались очень давно"
|
||||
},
|
||||
{
|
||||
"user_message": "Какие товары сейчас висят в остатке без понятной привязки к поставщику"
|
||||
},
|
||||
{
|
||||
"user_message": "Какие товары числятся на 41 счете на дату 2020-03-31"
|
||||
},
|
||||
{
|
||||
"user_message": "По выбранному объекту \"Шкаф картотечный 1000*400*2100\": кому был продан товар"
|
||||
},
|
||||
{
|
||||
"user_message": "Кому был продан товар Шкаф картотечный 1000*400*2100"
|
||||
},
|
||||
{
|
||||
"user_message": "По выбранному объекту \"Шкаф картотечный 1000*400*2100\": через какие документы прошел путь товара: закупка -> склад -> продажа"
|
||||
},
|
||||
{
|
||||
"user_message": "Через какие документы прошел путь товара Шкаф картотечный 1000*400*2100: закупка -> склад -> продажа"
|
||||
},
|
||||
{
|
||||
"user_message": "Есть ли документально подтвержденная цепочка: поставщик Гамма-мебель, ООО -> товар Шкаф картотечный 1000*400*2100 -> покупатель Департамент капитального ремонта города Москвы"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -1,64 +0,0 @@
|
|||
{
|
||||
"suite_id": "assistant_saved_session_gen-ag06031054-1bd955",
|
||||
"suite_version": "0.1.0",
|
||||
"schema_version": "assistant_saved_session_suite_v0_1",
|
||||
"generated_at": "2026-06-03T10:54:28+00:00",
|
||||
"generation_id": "gen-ag06031054-1bd955",
|
||||
"mode": "saved_user_sessions",
|
||||
"title": "AGENT | Phase105 mixed schema primitive closure live8",
|
||||
"domain": "address_phase105_mixed_schema_primitive_closure",
|
||||
"scenario_count": 1,
|
||||
"case_ids": [
|
||||
"SAVED-001"
|
||||
],
|
||||
"cases": [
|
||||
{
|
||||
"case_id": "SAVED-001",
|
||||
"scenario_tag": "agent_saved_user_sessions",
|
||||
"title": "AGENT | Phase105 mixed schema primitive closure live8",
|
||||
"question_type": "followup",
|
||||
"broadness_level": "medium",
|
||||
"turns": [
|
||||
{
|
||||
"user_message": "кайф - что там на складе по остаткам?"
|
||||
},
|
||||
{
|
||||
"user_message": "АЛЬТЕРНАТИВА"
|
||||
},
|
||||
{
|
||||
"user_message": "а исторические остатки на другие даты умеешь?"
|
||||
},
|
||||
{
|
||||
"user_message": "давай на июнь 2017"
|
||||
},
|
||||
{
|
||||
"user_message": "Теперь дай взрослый обзор за 2020 по компании: входящие, исходящие, нетто, топы, но банк в топах отдельно объясни как финансовый поток, если по назначению он не обычный клиент или поставщик."
|
||||
},
|
||||
{
|
||||
"user_message": "А отдельно по СБЕРБАНКУ: он для нас клиент, поставщик или финансовый поток? Дай коротко по подтвержденным строкам."
|
||||
},
|
||||
{
|
||||
"user_message": "А теперь по поставщику Группа СВК за 2020: сколько мы ему заплатили и какой общий денежный смысл?"
|
||||
},
|
||||
{
|
||||
"user_message": "А теперь по Группа СВК за 2020: сколько денег получили, сколько заплатили и какое нетто?"
|
||||
},
|
||||
{
|
||||
"user_message": "кому мы должны на конец 2020?"
|
||||
},
|
||||
{
|
||||
"user_message": "а нам кто должен на конец 2020?"
|
||||
},
|
||||
{
|
||||
"user_message": "сколько НДС надо заплатить в налоговую за декабрь 2019?"
|
||||
},
|
||||
{
|
||||
"user_message": "скока денег альтернатива заработала за 20 год?"
|
||||
},
|
||||
{
|
||||
"user_message": "а это чистая прибыль?"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -1,85 +0,0 @@
|
|||
{
|
||||
"suite_id": "assistant_saved_session_gen-ag06031328-3e1dd1",
|
||||
"suite_version": "0.1.0",
|
||||
"schema_version": "assistant_saved_session_suite_v0_1",
|
||||
"generated_at": "2026-06-03T13:28:09+00:00",
|
||||
"generation_id": "gen-ag06031328-3e1dd1",
|
||||
"mode": "saved_user_sessions",
|
||||
"title": "AGENT | Phase 83 mixed planner-brain replay for catalog alignment, pivots, and legacy continuity",
|
||||
"domain": "planner_autonomy_consolidation",
|
||||
"scenario_count": 1,
|
||||
"case_ids": [
|
||||
"SAVED-001"
|
||||
],
|
||||
"cases": [
|
||||
{
|
||||
"case_id": "SAVED-001",
|
||||
"scenario_tag": "agent_saved_user_sessions",
|
||||
"title": "AGENT | Phase 83 mixed planner-brain replay for catalog alignment, pivots, and legacy continuity",
|
||||
"question_type": "followup",
|
||||
"broadness_level": "medium",
|
||||
"turns": [
|
||||
{
|
||||
"user_message": "привет, ты на связи?"
|
||||
},
|
||||
{
|
||||
"user_message": "найди в 1С контрагента СВК"
|
||||
},
|
||||
{
|
||||
"user_message": "сколько получили по нему за 2020 год"
|
||||
},
|
||||
{
|
||||
"user_message": "а теперь сколько заплатили?"
|
||||
},
|
||||
{
|
||||
"user_message": "а какое нетто?"
|
||||
},
|
||||
{
|
||||
"user_message": "а по документам?"
|
||||
},
|
||||
{
|
||||
"user_message": "а по движениям?"
|
||||
},
|
||||
{
|
||||
"user_message": "Хочу быстрый денежный срез по одной организации без привязки к контрагенту. Сколько вообще входящих денег было за 2020 год?"
|
||||
},
|
||||
{
|
||||
"user_message": "По ООО Альтернатива Плюс."
|
||||
},
|
||||
{
|
||||
"user_message": "Понял, тогда за все время."
|
||||
},
|
||||
{
|
||||
"user_message": "Хорошо. А что по ООО Альтернатива Плюс больше в 2020 году: входящие или исходящие деньги?"
|
||||
},
|
||||
{
|
||||
"user_message": "А что по ООО Альтернатива Плюс больше уже за 2021 год: входящие или исходящие деньги?"
|
||||
},
|
||||
{
|
||||
"user_message": "И кто больше всего принес денег этой организации в 2020 году?"
|
||||
},
|
||||
{
|
||||
"user_message": "А в 2021 году?"
|
||||
},
|
||||
{
|
||||
"user_message": "а по Альтернативе Плюс сколько лет активности в базе 1С?"
|
||||
},
|
||||
{
|
||||
"user_message": "Как ты оценишь деятельность компании?"
|
||||
},
|
||||
{
|
||||
"user_message": "какое нетто по деньгам с Группа СВК за 2020 год: сколько получили и сколько заплатили?"
|
||||
},
|
||||
{
|
||||
"user_message": "какие справочники 1С есть по контрагентам?"
|
||||
},
|
||||
{
|
||||
"user_message": "давай дальше"
|
||||
},
|
||||
{
|
||||
"user_message": "а чем капибара отличается от утки?"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -238,11 +238,7 @@ def collect_output_artifacts(artifact_dir: Path) -> list[dict[str, Any]]:
|
|||
for path in sorted(artifact_dir.rglob("*.md")):
|
||||
if path in seen:
|
||||
continue
|
||||
# scenario_output.md is an aggregate technical export and often contains
|
||||
# debug payloads that are not part of the user-facing assistant answer.
|
||||
if path.name == "scenario_output.md":
|
||||
continue
|
||||
if path.name == "output.md" or path.name.endswith("_output.md"):
|
||||
if path.name == "output.md" or path.name in {"scenario_output.md"} or path.name.endswith("_output.md"):
|
||||
seen.add(path)
|
||||
turn_path = output_turn_path(path)
|
||||
outputs.append(
|
||||
|
|
|
|||
|
|
@ -4592,22 +4592,6 @@ def compact_route_candidate_handoff(
|
|||
}
|
||||
|
||||
|
||||
def route_candidate_requires_handoff(step_output: dict[str, Any]) -> bool:
|
||||
status = str(step_output.get("mcp_discovery_route_candidate_status") or "").strip()
|
||||
if status in {"needs_route_enablement", "needs_user_scope"}:
|
||||
return True
|
||||
if status != "ready_for_reviewed_execution":
|
||||
return False
|
||||
acceptance_status = str(step_output.get("acceptance_status") or step_output.get("status") or "").strip()
|
||||
if acceptance_status not in {"validated", "accepted"}:
|
||||
return True
|
||||
if normalize_string_list(step_output.get("violated_invariants")):
|
||||
return True
|
||||
if normalize_string_list(step_output.get("warnings")):
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def build_route_candidate_focus_signature(candidate: dict[str, Any]) -> str:
|
||||
status = str(candidate.get("candidate_status") or "unknown").strip() or "unknown"
|
||||
selected_chain_id = str(candidate.get("selected_chain_id") or "no_chain").strip() or "no_chain"
|
||||
|
|
@ -4826,7 +4810,6 @@ def build_deterministic_repair_targets(
|
|||
) -> dict[str, Any]:
|
||||
targets: list[dict[str, Any]] = []
|
||||
route_candidates: list[dict[str, Any]] = []
|
||||
reviewed_route_candidates: list[dict[str, Any]] = []
|
||||
step_validation_index: dict[str, dict[str, Any]] = {}
|
||||
for scenario_artifact in scenario_artifacts:
|
||||
scenario_id = str(scenario_artifact.get("scenario_id") or "").strip()
|
||||
|
|
@ -4849,10 +4832,7 @@ def build_deterministic_repair_targets(
|
|||
step_output=raw_step_output,
|
||||
)
|
||||
if route_candidate:
|
||||
if route_candidate_requires_handoff(raw_step_output):
|
||||
route_candidates.append(route_candidate)
|
||||
else:
|
||||
reviewed_route_candidates.append(route_candidate)
|
||||
route_candidates.append(route_candidate)
|
||||
target = build_step_repair_target(
|
||||
scenario_id=scenario_id,
|
||||
scenario_title=scenario_title,
|
||||
|
|
@ -4907,11 +4887,6 @@ def build_deterministic_repair_targets(
|
|||
for candidate in route_candidates:
|
||||
status = str(candidate.get("candidate_status") or "unknown").strip() or "unknown"
|
||||
route_candidate_status_counts[status] = route_candidate_status_counts.get(status, 0) + 1
|
||||
reviewed_route_candidate_groups = build_route_candidate_handoff_groups(reviewed_route_candidates)
|
||||
reviewed_route_candidate_status_counts: dict[str, int] = {}
|
||||
for candidate in reviewed_route_candidates:
|
||||
status = str(candidate.get("candidate_status") or "unknown").strip() or "unknown"
|
||||
reviewed_route_candidate_status_counts[status] = reviewed_route_candidate_status_counts.get(status, 0) + 1
|
||||
return {
|
||||
"schema_version": "domain_pack_repair_targets_v1",
|
||||
"pack_id": pack_state.get("pack_id"),
|
||||
|
|
@ -4933,9 +4908,6 @@ def build_deterministic_repair_targets(
|
|||
"route_candidate_handoff_count": len(route_candidates),
|
||||
"route_candidate_status_counts": route_candidate_status_counts,
|
||||
"route_candidate_groups": route_candidate_groups,
|
||||
"reviewed_route_candidate_count": len(reviewed_route_candidates),
|
||||
"reviewed_route_candidate_status_counts": reviewed_route_candidate_status_counts,
|
||||
"reviewed_route_candidate_groups": reviewed_route_candidate_groups,
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -5583,8 +5555,6 @@ def build_repair_targets_summary(repair_targets: dict[str, Any]) -> str:
|
|||
f"- severity_counts: `{dump_json(repair_targets.get('severity_counts') or {})}`",
|
||||
f"- route_candidate_handoff_count: `{repair_targets.get('route_candidate_handoff_count') if 'route_candidate_handoff_count' in repair_targets else 0}`",
|
||||
f"- route_candidate_status_counts: `{dump_json(repair_targets.get('route_candidate_status_counts') or {})}`",
|
||||
f"- reviewed_route_candidate_count: `{repair_targets.get('reviewed_route_candidate_count') if 'reviewed_route_candidate_count' in repair_targets else 0}`",
|
||||
f"- reviewed_route_candidate_status_counts: `{dump_json(repair_targets.get('reviewed_route_candidate_status_counts') or {})}`",
|
||||
]
|
||||
route_candidate_groups = repair_targets.get("route_candidate_groups") or []
|
||||
if isinstance(route_candidate_groups, list) and route_candidate_groups:
|
||||
|
|
@ -5603,23 +5573,6 @@ def build_repair_targets_summary(repair_targets: dict[str, Any]) -> str:
|
|||
f" target_ids: `{', '.join(normalize_string_list(group.get('target_ids'))) or 'n/a'}`",
|
||||
]
|
||||
)
|
||||
reviewed_route_candidate_groups = repair_targets.get("reviewed_route_candidate_groups") or []
|
||||
if isinstance(reviewed_route_candidate_groups, list) and reviewed_route_candidate_groups:
|
||||
lines.extend(["", "## Reviewed Route Candidates (No Handoff)"])
|
||||
for group in reviewed_route_candidate_groups:
|
||||
if not isinstance(group, dict):
|
||||
continue
|
||||
lines.extend(
|
||||
[
|
||||
f"- group_id: `{group.get('group_id') or 'n/a'}`",
|
||||
f" candidate_count: `{group.get('candidate_count') or 0}`",
|
||||
f" status: `{group.get('candidate_status') or 'n/a'}`",
|
||||
f" selected_chain_id: `{group.get('selected_chain_id') or 'n/a'}`",
|
||||
f" missing_axes: `{', '.join(normalize_string_list(group.get('missing_axes'))) or 'n/a'}`",
|
||||
f" executable_now: `{group.get('executable_now')}`",
|
||||
f" target_ids: `{', '.join(normalize_string_list(group.get('target_ids'))) or 'n/a'}`",
|
||||
]
|
||||
)
|
||||
priority_foci = repair_targets.get("priority_foci") or []
|
||||
if isinstance(priority_foci, list) and priority_foci:
|
||||
lines.extend(
|
||||
|
|
@ -6057,56 +6010,6 @@ def detector_evidence_paths_for_target(target: dict[str, Any]) -> list[str]:
|
|||
return [str(step_state.with_name("output.md")), str(step_state.with_name("turn.json"))]
|
||||
|
||||
|
||||
ACCEPTED_PACK_SMOKE_DETECTORS = [
|
||||
{
|
||||
"issue_code": "technical_garbage_in_answer",
|
||||
"detector": "runtime_tokens_in_user_answer",
|
||||
"severity": "P1",
|
||||
},
|
||||
{
|
||||
"issue_code": "technical_garbage_in_answer",
|
||||
"detector": "capability_ids_in_user_answer",
|
||||
"severity": "P1",
|
||||
},
|
||||
{
|
||||
"issue_code": "business_direct_answer_missing",
|
||||
"detector": "top_level_scaffold_before_answer",
|
||||
"severity": "P1",
|
||||
},
|
||||
{
|
||||
"issue_code": "business_next_step_missing",
|
||||
"detector": "limited_answer_without_next_action",
|
||||
"severity": "P2",
|
||||
},
|
||||
]
|
||||
|
||||
|
||||
def should_add_accepted_pack_smoke_detectors(
|
||||
repair_targets: dict[str, Any],
|
||||
existing_candidates: list[dict[str, Any]],
|
||||
) -> bool:
|
||||
if existing_candidates:
|
||||
return False
|
||||
if int(repair_targets.get("target_count") or 0) != 0:
|
||||
return False
|
||||
accepted_statuses = {
|
||||
str(repair_targets.get("acceptance_status") or "").strip(),
|
||||
str(repair_targets.get("final_status") or "").strip(),
|
||||
}
|
||||
return "accepted" in accepted_statuses
|
||||
|
||||
|
||||
def build_accepted_pack_smoke_detector_candidates() -> list[dict[str, Any]]:
|
||||
return [
|
||||
{
|
||||
**candidate,
|
||||
"sample_target_id": "accepted_pack_smoke",
|
||||
"evidence_paths": [],
|
||||
}
|
||||
for candidate in ACCEPTED_PACK_SMOKE_DETECTORS
|
||||
]
|
||||
|
||||
|
||||
def build_detector_candidates(repair_targets: dict[str, Any], catalog: dict[str, Any] | None = None) -> dict[str, Any]:
|
||||
source = catalog if isinstance(catalog, dict) else load_issue_catalog()
|
||||
issues = source.get("issues") if isinstance(source.get("issues"), dict) else {}
|
||||
|
|
@ -6135,8 +6038,6 @@ def build_detector_candidates(repair_targets: dict[str, Any], catalog: dict[str,
|
|||
"evidence_paths": evidence_paths,
|
||||
}
|
||||
)
|
||||
if should_add_accepted_pack_smoke_detectors(repair_targets, candidates):
|
||||
candidates.extend(build_accepted_pack_smoke_detector_candidates())
|
||||
return {
|
||||
"schema_version": "detector_candidates_v1",
|
||||
"candidate_count": len(candidates),
|
||||
|
|
@ -6186,19 +6087,6 @@ def summarize_detector_results(detector_results: dict[str, Any] | None, *, limit
|
|||
}
|
||||
|
||||
|
||||
def evaluate_detector_results_gate(detector_results_summary: dict[str, Any]) -> tuple[bool, str]:
|
||||
status = str(detector_results_summary.get("status") or "not_run").strip()
|
||||
if status == "fail":
|
||||
failed = normalize_string_list(detector_results_summary.get("failed_detectors"))
|
||||
suffix = ",".join(failed) if failed else str(detector_results_summary.get("fail") or 0)
|
||||
return False, f"detector_results_failed:{suffix}"
|
||||
if status == "review":
|
||||
review = normalize_string_list(detector_results_summary.get("review_detectors"))
|
||||
suffix = ",".join(review) if review else str(detector_results_summary.get("review") or 0)
|
||||
return False, f"detector_results_review_required:{suffix}"
|
||||
return True, f"detector_results_{status or 'not_run'}"
|
||||
|
||||
|
||||
def build_blocking_issue_contract(target: dict[str, Any], catalog: dict[str, Any]) -> dict[str, Any]:
|
||||
issue_code = str(target.get("issue_code") or target.get("problem_type") or "other").strip()
|
||||
entry = issue_catalog_entry(issue_code, catalog)
|
||||
|
|
@ -6253,8 +6141,6 @@ def build_business_audit_contract(
|
|||
if isinstance(target, dict) and str(target.get("severity") or "").upper() in {"P0", "P1"}
|
||||
]
|
||||
rerun_matrix = collect_rerun_matrix(repair_targets)
|
||||
detector_results_summary = summarize_detector_results(detector_results)
|
||||
detector_results_gate_ok, detector_results_gate_reason = evaluate_detector_results_gate(detector_results_summary)
|
||||
result = {
|
||||
"schema_version": "business_audit_contract_v1",
|
||||
"created_at": datetime.now(timezone.utc).replace(microsecond=0).isoformat(),
|
||||
|
|
@ -6266,8 +6152,6 @@ def build_business_audit_contract(
|
|||
"accepted_gate": accepted_gate,
|
||||
"deterministic_gate_ok": deterministic_gate_ok,
|
||||
"deterministic_gate_reason": deterministic_gate_reason,
|
||||
"detector_results_gate_ok": detector_results_gate_ok,
|
||||
"detector_results_gate_reason": detector_results_gate_reason,
|
||||
"human_meaning": {
|
||||
"user_intent_summary": analyst_verdict.get("user_intent_summary"),
|
||||
"expected_direct_answer": analyst_verdict.get("expected_direct_answer"),
|
||||
|
|
@ -6289,7 +6173,7 @@ def build_business_audit_contract(
|
|||
"severity_counts": repair_targets.get("severity_counts") or {},
|
||||
"priority_foci": _limited_dict_items(repair_targets.get("priority_foci"), limit=8),
|
||||
},
|
||||
"detector_results_summary": detector_results_summary,
|
||||
"detector_results_summary": summarize_detector_results(detector_results),
|
||||
"rerun_matrix": rerun_matrix,
|
||||
"artifact_refs": {
|
||||
"business_audit_md": repo_relative(business_audit_markdown_path),
|
||||
|
|
@ -6436,8 +6320,6 @@ def build_lead_coder_handoff(
|
|||
for item in repair_items
|
||||
if isinstance(item, dict) and str(item.get("target_source") or "") == "route_candidate_enablement"
|
||||
]
|
||||
detector_results_summary = summarize_detector_results(detector_results)
|
||||
detector_results_gate_ok, detector_results_gate_reason = evaluate_detector_results_gate(detector_results_summary)
|
||||
candidate_files = [repo_relative(path) for path in build_coder_snapshot_paths(repair_targets)]
|
||||
artifact_refs = {
|
||||
"pack_dir": repo_relative(pack_dir),
|
||||
|
|
@ -6474,15 +6356,13 @@ def build_lead_coder_handoff(
|
|||
"accepted_gate": accepted_gate,
|
||||
"deterministic_gate_ok": deterministic_gate_ok,
|
||||
"deterministic_gate_reason": deterministic_gate_reason,
|
||||
"detector_results_gate_ok": detector_results_gate_ok,
|
||||
"detector_results_gate_reason": detector_results_gate_reason,
|
||||
"requires_user_decision": requires_user_decision,
|
||||
"user_decision_type": user_decision_type,
|
||||
"user_decision_prompt": user_decision_prompt,
|
||||
"artifact_refs": artifact_refs,
|
||||
"issue_codes": issue_codes,
|
||||
"rerun_matrix": rerun_matrix,
|
||||
"detector_results_summary": detector_results_summary,
|
||||
"detector_results_summary": summarize_detector_results(detector_results),
|
||||
"human_meaning": {
|
||||
"user_intent_summary": analyst_verdict.get("user_intent_summary"),
|
||||
"expected_direct_answer": analyst_verdict.get("expected_direct_answer"),
|
||||
|
|
@ -6527,8 +6407,6 @@ def build_lead_coder_handoff_markdown(handoff: dict[str, Any]) -> str:
|
|||
f"- loop_decision: `{handoff.get('loop_decision')}`",
|
||||
f"- deterministic_gate_ok: `{handoff.get('deterministic_gate_ok')}`",
|
||||
f"- deterministic_gate_reason: `{handoff.get('deterministic_gate_reason') or 'n/a'}`",
|
||||
f"- detector_results_gate_ok: `{handoff.get('detector_results_gate_ok')}`",
|
||||
f"- detector_results_gate_reason: `{handoff.get('detector_results_gate_reason') or 'n/a'}`",
|
||||
"",
|
||||
"## Read First",
|
||||
f"- business_audit: `{artifact_refs.get('business_audit')}`",
|
||||
|
|
@ -7020,6 +6898,7 @@ def handle_run_pack_loop(args: argparse.Namespace) -> int:
|
|||
target_score=target_score,
|
||||
),
|
||||
)
|
||||
accepted_gate = analyst_accepted_gate and deterministic_gate_ok
|
||||
issue_catalog_snapshot = build_issue_catalog_snapshot(repair_targets)
|
||||
rerun_matrix_contract = {
|
||||
"schema_version": "rerun_matrix_v1",
|
||||
|
|
@ -7035,8 +6914,6 @@ def handle_run_pack_loop(args: argparse.Namespace) -> int:
|
|||
)
|
||||
write_json(detector_results_path, detector_results)
|
||||
detector_results_summary = summarize_detector_results(detector_results)
|
||||
detector_results_gate_ok, detector_results_gate_reason = evaluate_detector_results_gate(detector_results_summary)
|
||||
accepted_gate = analyst_accepted_gate and deterministic_gate_ok and detector_results_gate_ok
|
||||
business_audit_contract = build_business_audit_contract(
|
||||
analyst_verdict=analyst_verdict,
|
||||
repair_targets=repair_targets,
|
||||
|
|
@ -7084,8 +6961,6 @@ def handle_run_pack_loop(args: argparse.Namespace) -> int:
|
|||
"accepted_gate": accepted_gate,
|
||||
"deterministic_gate_ok": deterministic_gate_ok,
|
||||
"deterministic_gate_reason": deterministic_gate_reason,
|
||||
"detector_results_gate_ok": detector_results_gate_ok,
|
||||
"detector_results_gate_reason": detector_results_gate_reason,
|
||||
"requires_user_decision": requires_user_decision,
|
||||
"user_decision_type": user_decision_type,
|
||||
"user_decision_prompt": user_decision_prompt,
|
||||
|
|
|
|||
|
|
@ -976,7 +976,7 @@ def evaluate_truth_step(
|
|||
expected=expected_value,
|
||||
)
|
||||
continue
|
||||
if not dcl.validation_filter_values_match(filter_key, actual_value, expected_value):
|
||||
if actual_value != expected_value:
|
||||
append_finding(
|
||||
findings,
|
||||
step,
|
||||
|
|
@ -1006,10 +1006,7 @@ def evaluate_truth_step(
|
|||
)
|
||||
for filter_key, forbidden_values in forbidden_filter_values.items():
|
||||
actual_value = normalize_actual_filter_value(filter_key, extracted_filters.get(filter_key))
|
||||
if actual_value and any(
|
||||
dcl.validation_filter_values_match(filter_key, actual_value, forbidden_value)
|
||||
for forbidden_value in forbidden_values
|
||||
):
|
||||
if actual_value and actual_value in forbidden_values:
|
||||
append_finding(
|
||||
findings,
|
||||
step,
|
||||
|
|
|
|||
|
|
@ -1354,52 +1354,6 @@ def load_gui_review_detector_gate(review_dir: Path) -> dict[str, Any]:
|
|||
}
|
||||
|
||||
|
||||
def load_detector_results_for_stage_handoff(latest_gui_review: dict[str, Any]) -> dict[str, Any]:
|
||||
raw_path = str(latest_gui_review.get("detector_results_json") or "").strip()
|
||||
if not raw_path:
|
||||
return {}
|
||||
path = repo_path(raw_path)
|
||||
if not path.exists():
|
||||
return {}
|
||||
try:
|
||||
return load_json_object(path, "Stage GUI detector_results.json")
|
||||
except (OSError, json.JSONDecodeError, RuntimeError):
|
||||
return {}
|
||||
|
||||
|
||||
def build_detector_result_signals(
|
||||
detector_results: dict[str, Any],
|
||||
*,
|
||||
issue_codes: list[str],
|
||||
limit: int = 8,
|
||||
) -> list[dict[str, Any]]:
|
||||
results = detector_results.get("results") if isinstance(detector_results.get("results"), list) else []
|
||||
wanted_issue_codes = set(issue_codes)
|
||||
signals: list[dict[str, Any]] = []
|
||||
for item in results:
|
||||
if not isinstance(item, dict):
|
||||
continue
|
||||
status = str(item.get("status") or "").strip()
|
||||
if status not in {"fail", "review"}:
|
||||
continue
|
||||
item_issue_codes = dcl.normalize_string_list(item.get("issue_codes"))
|
||||
if wanted_issue_codes and not wanted_issue_codes.intersection(item_issue_codes):
|
||||
continue
|
||||
evidence = item.get("evidence") if isinstance(item.get("evidence"), list) else []
|
||||
signals.append(
|
||||
{
|
||||
"detector": item.get("detector"),
|
||||
"status": status,
|
||||
"issue_codes": item_issue_codes,
|
||||
"message": item.get("message"),
|
||||
"evidence_preview": evidence[:3],
|
||||
}
|
||||
)
|
||||
if len(signals) >= limit:
|
||||
break
|
||||
return signals
|
||||
|
||||
|
||||
def unique_strings(values: list[str]) -> list[str]:
|
||||
result: list[str] = []
|
||||
for value in values:
|
||||
|
|
@ -1728,10 +1682,7 @@ def build_stage_repair_handoff(summary: dict[str, Any], review: dict[str, Any])
|
|||
detector_results_summary = (
|
||||
latest.get("detector_results_summary") if isinstance(latest.get("detector_results_summary"), dict) else {}
|
||||
)
|
||||
detector_results = load_detector_results_for_stage_handoff(latest)
|
||||
if detector_results:
|
||||
detector_results_summary = dcl.summarize_detector_results(detector_results)
|
||||
detector_results_for_gate = detector_results if detector_results else {"summary": detector_results_summary, "results": []}
|
||||
detector_results_for_gate = {"summary": detector_results_summary, "results": []}
|
||||
assigned_focus = (
|
||||
repair_contract.get("priority_foci", [None])[0]
|
||||
if isinstance(repair_contract.get("priority_foci"), list) and repair_contract.get("priority_foci")
|
||||
|
|
@ -1763,10 +1714,6 @@ def build_stage_repair_handoff(summary: dict[str, Any], review: dict[str, Any])
|
|||
"candidate_files": repair_contract.get("candidate_files"),
|
||||
"repair_contract": repair_contract,
|
||||
"auto_propose_gate": auto_propose_gate,
|
||||
"detector_result_signals": build_detector_result_signals(
|
||||
detector_results_for_gate,
|
||||
issue_codes=dcl.normalize_string_list(repair_contract.get("issue_codes")),
|
||||
),
|
||||
"primary_repair_targets": primary_targets,
|
||||
"sample_findings": sample_findings,
|
||||
}
|
||||
|
|
@ -1801,22 +1748,6 @@ def build_stage_repair_handoff_markdown(handoff: dict[str, Any]) -> str:
|
|||
"",
|
||||
]
|
||||
)
|
||||
detector_signals = (
|
||||
handoff.get("detector_result_signals") if isinstance(handoff.get("detector_result_signals"), list) else []
|
||||
)
|
||||
if detector_signals:
|
||||
lines.extend(["## Detector Result Signals"])
|
||||
for signal in detector_signals:
|
||||
if not isinstance(signal, dict):
|
||||
continue
|
||||
lines.extend(
|
||||
[
|
||||
f"- `{signal.get('status')}` `{signal.get('detector')}` "
|
||||
f"`{', '.join(dcl.normalize_string_list(signal.get('issue_codes'))) or 'n/a'}`",
|
||||
f" message: {str(signal.get('message') or '').strip()}",
|
||||
]
|
||||
)
|
||||
lines.append("")
|
||||
auto_gate = handoff.get("auto_propose_gate") if isinstance(handoff.get("auto_propose_gate"), dict) else {}
|
||||
if auto_gate:
|
||||
lines.extend(
|
||||
|
|
@ -1899,9 +1830,6 @@ def build_stage_repair_iteration_plan(
|
|||
if isinstance(target, dict)
|
||||
]
|
||||
sample_findings = handoff.get("sample_findings") if isinstance(handoff.get("sample_findings"), list) else []
|
||||
detector_result_signals = (
|
||||
handoff.get("detector_result_signals") if isinstance(handoff.get("detector_result_signals"), list) else []
|
||||
)
|
||||
candidate_files = list(
|
||||
dict.fromkeys(
|
||||
path
|
||||
|
|
@ -1940,9 +1868,6 @@ def build_stage_repair_iteration_plan(
|
|||
"auto_propose_gate": (
|
||||
handoff.get("auto_propose_gate") if isinstance(handoff.get("auto_propose_gate"), dict) else {}
|
||||
),
|
||||
"detector_result_signals": [
|
||||
signal for signal in detector_result_signals if isinstance(signal, dict)
|
||||
][:8],
|
||||
"primary_repair_targets": enriched_targets,
|
||||
"candidate_files": candidate_files,
|
||||
"sample_findings": [finding for finding in sample_findings if isinstance(finding, dict)][:8],
|
||||
|
|
@ -2015,19 +1940,6 @@ def build_stage_repair_checklist(plan: dict[str, Any]) -> str:
|
|||
f"- `{target.get('severity')}` `{target.get('problem_layer')}` / `{target.get('issue_code')}`: "
|
||||
f"{target.get('occurrences')} occurrence(s)"
|
||||
)
|
||||
detector_signals = (
|
||||
plan.get("detector_result_signals") if isinstance(plan.get("detector_result_signals"), list) else []
|
||||
)
|
||||
if detector_signals:
|
||||
lines.extend(["", "## Detector Signals"])
|
||||
for signal in detector_signals:
|
||||
if not isinstance(signal, dict):
|
||||
continue
|
||||
issue_codes = ", ".join(dcl.normalize_string_list(signal.get("issue_codes"))) or "n/a"
|
||||
lines.append(
|
||||
f"- `{signal.get('status')}` `{signal.get('detector')}` `{issue_codes}`: "
|
||||
f"{str(signal.get('message') or '').strip()}"
|
||||
)
|
||||
lines.extend(["", "## Acceptance"])
|
||||
acceptance = plan.get("acceptance_rerun") if isinstance(plan.get("acceptance_rerun"), dict) else {}
|
||||
for item in acceptance.get("after_patch", []) if isinstance(acceptance.get("after_patch"), list) else []:
|
||||
|
|
|
|||
|
|
@ -273,22 +273,6 @@ class AgentDetectorRunnerTests(unittest.TestCase):
|
|||
self.assertEqual(results["summary"]["status"], "pass")
|
||||
self.assertEqual(results["results"][0]["status"], "pass")
|
||||
|
||||
def test_answer_text_outputs_ignore_aggregate_scenario_export(self) -> None:
|
||||
with tempfile.TemporaryDirectory() as tmp:
|
||||
root = Path(tmp)
|
||||
artifact_dir = root / "run"
|
||||
write_text(
|
||||
artifact_dir / "scenarios" / "inventory" / "scenario_output.md",
|
||||
"# Technical export\n\ncapability_id: address.inventory.debug\nruntime_debug: true\n",
|
||||
)
|
||||
step_output = artifact_dir / "scenarios" / "inventory" / "steps" / "s01" / "output.md"
|
||||
write_text(step_output, "Clean user-facing answer.")
|
||||
|
||||
outputs = runner.collect_output_artifacts(artifact_dir)
|
||||
|
||||
artifact_paths = [item["artifact_path"].replace("\\", "/") for item in outputs]
|
||||
self.assertEqual(artifact_paths, ["scenarios/inventory/steps/s01/output.md"])
|
||||
|
||||
def test_trace_guard_uses_current_route_fields_not_full_turn_history(self) -> None:
|
||||
with tempfile.TemporaryDirectory() as tmp:
|
||||
root = Path(tmp)
|
||||
|
|
|
|||
|
|
@ -104,8 +104,6 @@ class DomainCaseLoopLeadHandoffTests(unittest.TestCase):
|
|||
self.assertIn("detector_results", saved["artifact_refs"])
|
||||
self.assertEqual(saved["detector_results_summary"]["status"], "fail")
|
||||
self.assertEqual(saved["detector_results_summary"]["failed_detectors"], ["first_line_not_direct_answer"])
|
||||
self.assertFalse(saved["detector_results_gate_ok"])
|
||||
self.assertEqual(saved["detector_results_gate_reason"], "detector_results_failed:first_line_not_direct_answer")
|
||||
self.assertIn("business_direct_answer_missing", saved["issue_codes"])
|
||||
self.assertIn("failed_scenario", saved["rerun_matrix"])
|
||||
self.assertTrue(latest_handoff_exists)
|
||||
|
|
@ -165,94 +163,6 @@ class DomainCaseLoopLeadHandoffTests(unittest.TestCase):
|
|||
self.assertIn("detector_results_json", contract["artifact_refs"])
|
||||
self.assertEqual(contract["detector_results_summary"]["status"], "review")
|
||||
self.assertEqual(contract["detector_results_summary"]["review_detectors"], ["missing_revenue_cogs_margin_fields"])
|
||||
self.assertFalse(contract["detector_results_gate_ok"])
|
||||
self.assertEqual(
|
||||
contract["detector_results_gate_reason"],
|
||||
"detector_results_review_required:missing_revenue_cogs_margin_fields",
|
||||
)
|
||||
|
||||
def test_accepted_pack_without_repair_targets_gets_smoke_detector_candidates(self) -> None:
|
||||
candidates = dcl.build_detector_candidates(
|
||||
{
|
||||
"target_count": 0,
|
||||
"acceptance_status": "accepted",
|
||||
"final_status": "accepted",
|
||||
"targets": [],
|
||||
}
|
||||
)
|
||||
|
||||
detector_names = [item["detector"] for item in candidates["candidates"]]
|
||||
|
||||
self.assertEqual(candidates["candidate_count"], 4)
|
||||
self.assertIn("runtime_tokens_in_user_answer", detector_names)
|
||||
self.assertIn("capability_ids_in_user_answer", detector_names)
|
||||
self.assertIn("top_level_scaffold_before_answer", detector_names)
|
||||
self.assertIn("limited_answer_without_next_action", detector_names)
|
||||
|
||||
def test_targeted_repair_candidates_do_not_add_broad_smoke_detectors(self) -> None:
|
||||
candidates = dcl.build_detector_candidates(
|
||||
{
|
||||
"target_count": 1,
|
||||
"acceptance_status": "partial",
|
||||
"final_status": "partial",
|
||||
"targets": [
|
||||
{
|
||||
"target_id": "pack:s01",
|
||||
"issue_code": "business_direct_answer_missing",
|
||||
"severity": "P0",
|
||||
"evidence_paths": ["artifacts/domain_runs/pack/steps/s01/output.md"],
|
||||
}
|
||||
],
|
||||
}
|
||||
)
|
||||
|
||||
detector_names = [item["detector"] for item in candidates["candidates"]]
|
||||
|
||||
self.assertIn("first_line_not_direct_answer", detector_names)
|
||||
self.assertNotIn("runtime_tokens_in_user_answer", detector_names)
|
||||
|
||||
def test_business_utility_gap_uses_known_surface_detectors(self) -> None:
|
||||
candidates = dcl.build_detector_candidates(
|
||||
{
|
||||
"target_count": 1,
|
||||
"acceptance_status": "partial",
|
||||
"final_status": "partial",
|
||||
"targets": [
|
||||
{
|
||||
"target_id": "inventory_supplier_overlap:step_04_supplier_items_on_stock",
|
||||
"issue_code": "business_utility_gap",
|
||||
"severity": "P2",
|
||||
"evidence_paths": [
|
||||
"artifacts/domain_runs/pack/scenarios/inventory_supplier_overlap/steps/step_04_supplier_items_on_stock/output.md"
|
||||
],
|
||||
}
|
||||
],
|
||||
}
|
||||
)
|
||||
|
||||
detector_names = [item["detector"] for item in candidates["candidates"]]
|
||||
|
||||
self.assertIn("business_answer_too_verbose_output", detector_names)
|
||||
self.assertIn("top_level_scaffold_before_answer", detector_names)
|
||||
self.assertIn("runtime_tokens_in_user_answer", detector_names)
|
||||
self.assertIn("limited_answer_without_next_action", detector_names)
|
||||
self.assertNotIn("business_utility_gap_detector", detector_names)
|
||||
|
||||
def test_detector_results_gate_allows_pass_and_blocks_fail_or_review(self) -> None:
|
||||
pass_ok, pass_reason = dcl.evaluate_detector_results_gate({"status": "pass"})
|
||||
fail_ok, fail_reason = dcl.evaluate_detector_results_gate(
|
||||
{"status": "fail", "failed_detectors": ["runtime_tokens_in_user_answer"]}
|
||||
)
|
||||
review_ok, review_reason = dcl.evaluate_detector_results_gate(
|
||||
{"status": "review", "review_detectors": ["required_contract_fields_missing"]}
|
||||
)
|
||||
|
||||
self.assertTrue(pass_ok)
|
||||
self.assertEqual(pass_reason, "detector_results_pass")
|
||||
self.assertFalse(fail_ok)
|
||||
self.assertEqual(fail_reason, "detector_results_failed:runtime_tokens_in_user_answer")
|
||||
self.assertFalse(review_ok)
|
||||
self.assertEqual(review_reason, "detector_results_review_required:required_contract_fields_missing")
|
||||
|
||||
def test_auto_coder_gate_blocks_non_allowlisted_issue_codes(self) -> None:
|
||||
repair_targets = {
|
||||
|
|
|
|||
|
|
@ -286,86 +286,6 @@ class DomainCaseLoopStepStateTests(unittest.TestCase):
|
|||
self.assertIn("## Route Candidate Handoff Groups", markdown)
|
||||
self.assertIn("route_candidate_demo:step_01", markdown)
|
||||
|
||||
def test_validated_ready_route_candidates_are_reviewed_without_repair_handoff(self) -> None:
|
||||
repair_targets = dcl.build_deterministic_repair_targets(
|
||||
{"pack_id": "route_candidate_pack", "domain": "inventory_stock", "final_status": "accepted"},
|
||||
[
|
||||
{
|
||||
"scenario_id": "inventory_sale_trace",
|
||||
"title": "Sale trace",
|
||||
"artifact_dir": "artifacts/domain_runs/inventory_sale_trace",
|
||||
"scenario_state": {
|
||||
"step_outputs": {
|
||||
"step_04": {
|
||||
"status": "validated",
|
||||
"acceptance_status": "validated",
|
||||
"execution_status": "exact",
|
||||
"question_resolved": "trace purchase to sale",
|
||||
"reply_type": "factual",
|
||||
"mcp_discovery_selected_chain_id": "metadata_inspection",
|
||||
"mcp_discovery_route_candidate_status": "ready_for_reviewed_execution",
|
||||
"mcp_discovery_route_candidate_fact_family": "schema_surface",
|
||||
"mcp_discovery_route_candidate_action_family": "inspect_surface",
|
||||
"mcp_discovery_route_candidate_missing_axes": ["metadata_scope"],
|
||||
"mcp_discovery_route_candidate_executable_now": True,
|
||||
"violated_invariants": [],
|
||||
"warnings": [],
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
],
|
||||
)
|
||||
|
||||
self.assertEqual(repair_targets["target_count"], 0)
|
||||
self.assertEqual(repair_targets["route_candidate_handoff_count"], 0)
|
||||
self.assertEqual(repair_targets["route_candidate_status_counts"], {})
|
||||
self.assertEqual(repair_targets["route_candidate_groups"], [])
|
||||
self.assertEqual(repair_targets["reviewed_route_candidate_count"], 1)
|
||||
self.assertEqual(repair_targets["reviewed_route_candidate_status_counts"], {"ready_for_reviewed_execution": 1})
|
||||
self.assertEqual(
|
||||
repair_targets["reviewed_route_candidate_groups"][0]["group_id"],
|
||||
"ready_for_reviewed_execution|metadata_inspection|metadata_scope",
|
||||
)
|
||||
|
||||
def test_nonvalidated_ready_route_candidates_stay_in_repair_handoff(self) -> None:
|
||||
repair_targets = dcl.build_deterministic_repair_targets(
|
||||
{"pack_id": "route_candidate_pack", "domain": "inventory_stock", "final_status": "partial"},
|
||||
[
|
||||
{
|
||||
"scenario_id": "inventory_sale_trace",
|
||||
"title": "Sale trace",
|
||||
"artifact_dir": "artifacts/domain_runs/inventory_sale_trace",
|
||||
"scenario_state": {
|
||||
"step_outputs": {
|
||||
"step_04": {
|
||||
"status": "rejected",
|
||||
"acceptance_status": "rejected",
|
||||
"execution_status": "partial",
|
||||
"question_resolved": "trace purchase to sale",
|
||||
"reply_type": "partial_coverage",
|
||||
"mcp_discovery_selected_chain_id": "metadata_inspection",
|
||||
"mcp_discovery_route_candidate_status": "ready_for_reviewed_execution",
|
||||
"mcp_discovery_route_candidate_fact_family": "schema_surface",
|
||||
"mcp_discovery_route_candidate_action_family": "inspect_surface",
|
||||
"mcp_discovery_route_candidate_missing_axes": ["metadata_scope"],
|
||||
"mcp_discovery_route_candidate_executable_now": True,
|
||||
"violated_invariants": ["answer_shape_mismatch"],
|
||||
"warnings": [],
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
],
|
||||
)
|
||||
|
||||
self.assertEqual(repair_targets["route_candidate_handoff_count"], 1)
|
||||
self.assertEqual(repair_targets["reviewed_route_candidate_count"], 0)
|
||||
self.assertEqual(
|
||||
repair_targets["route_candidate_groups"][0]["group_id"],
|
||||
"ready_for_reviewed_execution|metadata_inspection|metadata_scope",
|
||||
)
|
||||
|
||||
def test_analysis_context_date_is_not_implicit_business_filter(self) -> None:
|
||||
step_state = dcl.build_scenario_step_state(
|
||||
scenario_id="stage_pack_demo",
|
||||
|
|
|
|||
|
|
@ -1,80 +0,0 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import sys
|
||||
import unittest
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
sys.path.insert(0, str(Path(__file__).resolve().parent))
|
||||
|
||||
import domain_truth_harness as dth
|
||||
|
||||
|
||||
class DomainTruthHarnessTests(unittest.TestCase):
|
||||
def test_required_organization_filter_accepts_quoted_legal_name(self) -> None:
|
||||
step = dth.normalize_step_spec(
|
||||
1,
|
||||
{
|
||||
"step_id": "step_01",
|
||||
"title": "Inventory root",
|
||||
"question_template": "что там на складе по остаткам",
|
||||
"required_filters": {"organization": "ООО Альтернатива Плюс"},
|
||||
},
|
||||
)
|
||||
reviewed = dth.evaluate_truth_step(
|
||||
step=step,
|
||||
step_state={
|
||||
"question_resolved": "что там на складе по остаткам",
|
||||
"reply_type": "factual",
|
||||
"assistant_text": "На складе подтверждено 11 позиций.",
|
||||
"extracted_filters": {"organization": 'ООО "Альтернатива Плюс"'},
|
||||
},
|
||||
step_results={},
|
||||
bindings={},
|
||||
runtime_bindings={},
|
||||
)
|
||||
|
||||
self.assertEqual(reviewed["review_status"], "pass")
|
||||
self.assertFalse(
|
||||
[
|
||||
finding
|
||||
for finding in reviewed["review_findings"]
|
||||
if finding.get("code") == "wrong_filter:organization"
|
||||
]
|
||||
)
|
||||
|
||||
def test_forbidden_organization_filter_matches_quoted_legal_name(self) -> None:
|
||||
step = dth.normalize_step_spec(
|
||||
1,
|
||||
{
|
||||
"step_id": "step_01",
|
||||
"title": "Wrong organization scope",
|
||||
"question_template": "что там на складе по остаткам",
|
||||
"forbidden_filter_values": {"organization": ["ООО Альтернатива Плюс"]},
|
||||
},
|
||||
)
|
||||
reviewed = dth.evaluate_truth_step(
|
||||
step=step,
|
||||
step_state={
|
||||
"question_resolved": "что там на складе по остаткам",
|
||||
"reply_type": "factual",
|
||||
"assistant_text": "На складе подтверждено 11 позиций.",
|
||||
"extracted_filters": {"organization": 'ООО "Альтернатива Плюс"'},
|
||||
},
|
||||
step_results={},
|
||||
bindings={},
|
||||
runtime_bindings={},
|
||||
)
|
||||
|
||||
self.assertEqual(reviewed["review_status"], "fail")
|
||||
self.assertTrue(
|
||||
[
|
||||
finding
|
||||
for finding in reviewed["review_findings"]
|
||||
if finding.get("code") == "forbidden_filter_value:organization"
|
||||
]
|
||||
)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
|
|
@ -761,80 +761,6 @@ class StageAgentLoopTests(unittest.TestCase):
|
|||
self.assertTrue(handoff["auto_propose_gate"]["allowed"])
|
||||
self.assertEqual(handoff["auto_propose_gate"]["detector_results_summary"]["status"], "fail")
|
||||
|
||||
def test_stage_repair_handoff_loads_detector_result_signals_from_json(self) -> None:
|
||||
with tempfile.TemporaryDirectory() as tmp:
|
||||
detector_results_path = Path(tmp) / "detector_results.json"
|
||||
write_json(
|
||||
detector_results_path,
|
||||
{
|
||||
"schema_version": "agent_detector_results_v1",
|
||||
"created_at": "2026-06-03T00:00:00+00:00",
|
||||
"artifact_dir": str(Path(tmp)),
|
||||
"registry_path": "docs/orchestration/detector_registry.json",
|
||||
"selected_detectors": ["business_direct_answer_missing_signal"],
|
||||
"artifact_counts": {},
|
||||
"summary": {
|
||||
"status": "fail",
|
||||
"detector_count": 1,
|
||||
"pass": 0,
|
||||
"fail": 1,
|
||||
"review": 0,
|
||||
"skipped": 0,
|
||||
},
|
||||
"results": [
|
||||
{
|
||||
"detector": "business_direct_answer_missing_signal",
|
||||
"kind": "semantic",
|
||||
"automation_level": "deterministic",
|
||||
"status": "fail",
|
||||
"issue_codes": ["business_direct_answer_missing"],
|
||||
"message": "Direct answer is missing on the first line.",
|
||||
"evidence": [{"turn_index": 19, "first_line": "Коротко: общий обзор"}],
|
||||
}
|
||||
],
|
||||
},
|
||||
)
|
||||
summary = {
|
||||
"stage_id": "agent_loop",
|
||||
"next_action": "continue_repair_from_gui_review_p0",
|
||||
"latest_gui_review": {
|
||||
"run_id": "assistant-stage1-test",
|
||||
"overall_business_status": "fail",
|
||||
"p0_findings": 1,
|
||||
"p1_findings": 0,
|
||||
"question_quality_score": 100,
|
||||
"review_json": "artifacts/domain_runs/gui_run_reviews/assistant-stage1-test/run_review.json",
|
||||
"review_markdown": "artifacts/domain_runs/gui_run_reviews/assistant-stage1-test/run_review.md",
|
||||
"repair_targets_json": "artifacts/domain_runs/gui_run_reviews/assistant-stage1-test/repair_targets.json",
|
||||
"conversation_pairs_json": "artifacts/domain_runs/gui_run_reviews/assistant-stage1-test/conversation_pairs.json",
|
||||
"detector_results_json": str(detector_results_path),
|
||||
"detector_results_status": "fail",
|
||||
"detector_results_summary": {"status": "fail", "detector_count": 1, "fail": 1},
|
||||
},
|
||||
}
|
||||
review = {
|
||||
"run_id": "assistant-stage1-test",
|
||||
"repair_targets": [
|
||||
{
|
||||
"problem_layer": "answer_shape_mismatch",
|
||||
"issue_code": "business_direct_answer_missing",
|
||||
"severity": "P0",
|
||||
"occurrences": 1,
|
||||
}
|
||||
],
|
||||
"findings": [],
|
||||
}
|
||||
|
||||
handoff = stage_loop.build_stage_repair_handoff(summary, review)
|
||||
|
||||
self.assertTrue(handoff["auto_propose_gate"]["allowed"])
|
||||
self.assertEqual(
|
||||
handoff["auto_propose_gate"]["detector_results_summary"]["failed_detectors"],
|
||||
["business_direct_answer_missing_signal"],
|
||||
)
|
||||
self.assertEqual(handoff["detector_result_signals"][0]["detector"], "business_direct_answer_missing_signal")
|
||||
self.assertIn("Direct answer is missing", handoff["detector_result_signals"][0]["message"])
|
||||
|
||||
def test_stage_repair_handoff_blocks_auto_propose_for_non_allowlisted_gui_issue(self) -> None:
|
||||
summary = {
|
||||
"stage_id": "agent_loop",
|
||||
|
|
@ -900,14 +826,6 @@ class StageAgentLoopTests(unittest.TestCase):
|
|||
"issue_codes": ["business_direct_answer_missing"],
|
||||
"rerun_matrix": ["failed_scenario", "direct_answer_surface_pack", "accepted_smoke_pack"],
|
||||
"auto_propose_gate": {"allowed": True, "reason": "auto_coder_gate_passed"},
|
||||
"detector_result_signals": [
|
||||
{
|
||||
"detector": "business_direct_answer_missing_signal",
|
||||
"status": "fail",
|
||||
"issue_codes": ["business_direct_answer_missing"],
|
||||
"message": "Direct answer is missing on the first line.",
|
||||
}
|
||||
],
|
||||
"primary_repair_targets": [
|
||||
{
|
||||
"problem_layer": "answer_shape_mismatch",
|
||||
|
|
@ -945,7 +863,6 @@ class StageAgentLoopTests(unittest.TestCase):
|
|||
self.assertIn("business_direct_answer_missing", plan["issue_codes"])
|
||||
self.assertIn("direct_answer_surface_pack", plan["rerun_matrix"])
|
||||
self.assertTrue(plan["auto_propose_gate"]["allowed"])
|
||||
self.assertEqual(plan["detector_result_signals"][0]["detector"], "business_direct_answer_missing_signal")
|
||||
self.assertIn("ingest-gui-run", plan["acceptance_rerun"]["ingest_command_template"])
|
||||
|
||||
def test_handle_prepare_repair_materializes_prompt_and_checklist(self) -> None:
|
||||
|
|
@ -974,14 +891,6 @@ class StageAgentLoopTests(unittest.TestCase):
|
|||
"question_quality_score": 100,
|
||||
"review_markdown": "review.md",
|
||||
"repair_targets_json": "repair_targets.json",
|
||||
"detector_result_signals": [
|
||||
{
|
||||
"detector": "business_direct_answer_missing_signal",
|
||||
"status": "fail",
|
||||
"issue_codes": ["business_direct_answer_missing"],
|
||||
"message": "Direct answer is missing on the first line.",
|
||||
}
|
||||
],
|
||||
"primary_repair_targets": [
|
||||
{
|
||||
"problem_layer": "business_utility_gap",
|
||||
|
|
@ -1005,13 +914,12 @@ class StageAgentLoopTests(unittest.TestCase):
|
|||
iteration_dir = stage_dir / "repair_iterations" / "repair_001"
|
||||
plan_exists = (iteration_dir / "repair_iteration_plan.json").exists()
|
||||
prompt = (iteration_dir / "repair_prompt.md").read_text(encoding="utf-8")
|
||||
checklist = (iteration_dir / "repair_checklist.md").read_text(encoding="utf-8")
|
||||
checklist_exists = (iteration_dir / "repair_checklist.md").exists()
|
||||
|
||||
self.assertEqual(exit_code, 0)
|
||||
self.assertTrue(plan_exists)
|
||||
self.assertTrue(checklist_exists)
|
||||
self.assertIn("business_answer_too_verbose", prompt)
|
||||
self.assertIn("business_direct_answer_missing_signal", prompt)
|
||||
self.assertIn("Direct answer is missing", checklist)
|
||||
|
||||
def test_handle_run_repair_dry_run_materializes_coder_command(self) -> None:
|
||||
with tempfile.TemporaryDirectory() as tmp:
|
||||
|
|
|
|||
Loading…
Reference in New Issue