Добавить next action для складской атрибуции поставщика

This commit is contained in:
dctouch 2026-06-03 11:45:12 +03:00
parent c8a58f6163
commit f0b84822d3
7 changed files with 301 additions and 6 deletions

View File

@ -0,0 +1,81 @@
{
"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"
]
}
]
}

View File

@ -4,6 +4,7 @@ exports.composeInventoryReply = composeInventoryReply;
const replyContracts_1 = require("./replyContracts"); const replyContracts_1 = require("./replyContracts");
const inventoryReplyPresentation_1 = require("./inventoryReplyPresentation"); const inventoryReplyPresentation_1 = require("./inventoryReplyPresentation");
const INVENTORY_TRACE_EVIDENCE_ROW_LIMIT = 3; const INVENTORY_TRACE_EVIDENCE_ROW_LIMIT = 3;
const INVENTORY_SUPPLIER_ATTRIBUTION_NEXT_ACTION = "Следующий шаг: чтобы сузить доказательство до партии, проверьте партионный/серийный учет или детализацию по выбранной номенклатуре, поступлению и последующему движению.";
function cleanupInventoryRequestedParty(value) { function cleanupInventoryRequestedParty(value) {
const cleaned = String(value ?? "") const cleaned = String(value ?? "")
.replace(/\s*(?:->|=>|→)\s*(?:товар|позици|номенклатур|покупател|buyer|customer|item|product|sku)[\s\S]*$/iu, "") .replace(/\s*(?:->|=>|→)\s*(?:товар|позици|номенклатур|покупател|buyer|customer|item|product|sku)[\s\S]*$/iu, "")
@ -519,7 +520,8 @@ function composeInventoryReply(intent, rows, options, deps) {
`Поставщиков, выделенных в остальных операциях: ${deps.formatNumberWithDots(observedCounterparties.length)}.` `Поставщиков, выделенных в остальных операциях: ${deps.formatNumberWithDots(observedCounterparties.length)}.`
]); ]);
(0, inventoryReplyPresentation_1.appendInventoryBulletSection)(lines, "Ограничения:", [ (0, inventoryReplyPresentation_1.appendInventoryBulletSection)(lines, "Ограничения:", [
"Без партионного учета это проверка доступного закупочного следа по складскому срезу, а не юридическое доказательство владельца каждой партии." "Без партионного учета это проверка доступного закупочного следа по складскому срезу, а не юридическое доказательство владельца каждой партии.",
INVENTORY_SUPPLIER_ATTRIBUTION_NEXT_ACTION
]); ]);
if (unresolvedRows.length > 0) { if (unresolvedRows.length > 0) {
(0, inventoryReplyPresentation_1.appendInventorySection)(lines, "Опорные документы:", deps.formatInventoryTraceRows(unresolvedRows, unresolvedDocumentPreviewLimit)); (0, inventoryReplyPresentation_1.appendInventorySection)(lines, "Опорные документы:", deps.formatInventoryTraceRows(unresolvedRows, unresolvedDocumentPreviewLimit));
@ -556,7 +558,8 @@ function composeInventoryReply(intent, rows, options, deps) {
`Закупочных документов / операций в выборке: ${deps.formatNumberWithDots(summary.documents.length)} / ${deps.formatNumberWithDots(purchaseRows.length)}.` `Закупочных документов / операций в выборке: ${deps.formatNumberWithDots(summary.documents.length)} / ${deps.formatNumberWithDots(purchaseRows.length)}.`
]); ]);
(0, inventoryReplyPresentation_1.appendInventoryBulletSection)(lines, "Ограничения:", [ (0, inventoryReplyPresentation_1.appendInventoryBulletSection)(lines, "Ограничения:", [
"Без партионного учета этот ответ показывает закупочный след текущего остатка, но не доказывает владельца каждой конкретной партии." "Без партионного учета этот ответ показывает закупочный след текущего остатка, но не доказывает владельца каждой конкретной партии.",
INVENTORY_SUPPLIER_ATTRIBUTION_NEXT_ACTION
]); ]);
if (observedCounterparties.length > 0) { if (observedCounterparties.length > 0) {
lines.push(`- Найденные поставщики: ${inventoryPartyLeadList(observedCounterparties)}.`); lines.push(`- Найденные поставщики: ${inventoryPartyLeadList(observedCounterparties)}.`);
@ -586,7 +589,8 @@ function composeInventoryReply(intent, rows, options, deps) {
`Закупочных документов / операций в выборке: ${deps.formatNumberWithDots(summary.documents.length)} / ${deps.formatNumberWithDots(purchaseRows.length)}.` `Закупочных документов / операций в выборке: ${deps.formatNumberWithDots(summary.documents.length)} / ${deps.formatNumberWithDots(purchaseRows.length)}.`
]); ]);
(0, inventoryReplyPresentation_1.appendInventoryBulletSection)(lines, "Ограничения:", [ (0, inventoryReplyPresentation_1.appendInventoryBulletSection)(lines, "Ограничения:", [
"Без партионного учета этот ответ показывает закупочный след текущего остатка, но не доказывает владельца каждой конкретной партии." "Без партионного учета этот ответ показывает закупочный след текущего остатка, но не доказывает владельца каждой конкретной партии.",
INVENTORY_SUPPLIER_ATTRIBUTION_NEXT_ACTION
]); ]);
if (observedCounterparties.length > 0) { if (observedCounterparties.length > 0) {
lines.push(`- Найденные поставщики: ${inventoryPartyLeadList(observedCounterparties)}.`); lines.push(`- Найденные поставщики: ${inventoryPartyLeadList(observedCounterparties)}.`);

View File

@ -48,6 +48,8 @@ interface InventoryTraceSummary {
} }
const INVENTORY_TRACE_EVIDENCE_ROW_LIMIT = 3; const INVENTORY_TRACE_EVIDENCE_ROW_LIMIT = 3;
const INVENTORY_SUPPLIER_ATTRIBUTION_NEXT_ACTION =
"Следующий шаг: чтобы сузить доказательство до партии, проверьте партионный/серийный учет или детализацию по выбранной номенклатуре, поступлению и последующему движению.";
interface InventoryAgingByItemAggregate { interface InventoryAgingByItemAggregate {
item: string; item: string;
@ -712,7 +714,8 @@ export function composeInventoryReply(
`Поставщиков, выделенных в остальных операциях: ${deps.formatNumberWithDots(observedCounterparties.length)}.` `Поставщиков, выделенных в остальных операциях: ${deps.formatNumberWithDots(observedCounterparties.length)}.`
]); ]);
appendInventoryBulletSection(lines, "Ограничения:", [ appendInventoryBulletSection(lines, "Ограничения:", [
"Без партионного учета это проверка доступного закупочного следа по складскому срезу, а не юридическое доказательство владельца каждой партии." "Без партионного учета это проверка доступного закупочного следа по складскому срезу, а не юридическое доказательство владельца каждой партии.",
INVENTORY_SUPPLIER_ATTRIBUTION_NEXT_ACTION
]); ]);
if (unresolvedRows.length > 0) { if (unresolvedRows.length > 0) {
appendInventorySection( appendInventorySection(
@ -763,7 +766,8 @@ export function composeInventoryReply(
`Закупочных документов / операций в выборке: ${deps.formatNumberWithDots(summary.documents.length)} / ${deps.formatNumberWithDots(purchaseRows.length)}.` `Закупочных документов / операций в выборке: ${deps.formatNumberWithDots(summary.documents.length)} / ${deps.formatNumberWithDots(purchaseRows.length)}.`
]); ]);
appendInventoryBulletSection(lines, "Ограничения:", [ appendInventoryBulletSection(lines, "Ограничения:", [
"Без партионного учета этот ответ показывает закупочный след текущего остатка, но не доказывает владельца каждой конкретной партии." "Без партионного учета этот ответ показывает закупочный след текущего остатка, но не доказывает владельца каждой конкретной партии.",
INVENTORY_SUPPLIER_ATTRIBUTION_NEXT_ACTION
]); ]);
if (observedCounterparties.length > 0) { if (observedCounterparties.length > 0) {
lines.push(`- Найденные поставщики: ${inventoryPartyLeadList(observedCounterparties)}.`); lines.push(`- Найденные поставщики: ${inventoryPartyLeadList(observedCounterparties)}.`);
@ -808,7 +812,8 @@ export function composeInventoryReply(
`Закупочных документов / операций в выборке: ${deps.formatNumberWithDots(summary.documents.length)} / ${deps.formatNumberWithDots(purchaseRows.length)}.` `Закупочных документов / операций в выборке: ${deps.formatNumberWithDots(summary.documents.length)} / ${deps.formatNumberWithDots(purchaseRows.length)}.`
]); ]);
appendInventoryBulletSection(lines, "Ограничения:", [ appendInventoryBulletSection(lines, "Ограничения:", [
"Без партионного учета этот ответ показывает закупочный след текущего остатка, но не доказывает владельца каждой конкретной партии." "Без партионного учета этот ответ показывает закупочный след текущего остатка, но не доказывает владельца каждой конкретной партии.",
INVENTORY_SUPPLIER_ATTRIBUTION_NEXT_ACTION
]); ]);
if (observedCounterparties.length > 0) { if (observedCounterparties.length > 0) {
lines.push(`- Найденные поставщики: ${inventoryPartyLeadList(observedCounterparties)}.`); lines.push(`- Найденные поставщики: ${inventoryPartyLeadList(observedCounterparties)}.`);

View File

@ -900,6 +900,8 @@ describe("address reply builders regressions", () => {
expect(firstLine).toContain("ВИЗАНТИЯ"); expect(firstLine).toContain("ВИЗАНТИЯ");
expect(text.indexOf(firstLine)).toBeLessThan(text.indexOf("Что проверили:")); expect(text.indexOf(firstLine)).toBeLessThan(text.indexOf("Что проверили:"));
expect(text).toContain("однозначная атрибуция части текущего остатка не подтверждена: 1 закупочных операций без явно выделенного поставщика."); expect(text).toContain("однозначная атрибуция части текущего остатка не подтверждена: 1 закупочных операций без явно выделенного поставщика.");
expect(text).toContain("Следующий шаг:");
expect(text).toContain("проверьте партионный/серийный учет");
expect(text).not.toContain("Часть закупочных операций"); expect(text).not.toContain("Часть закупочных операций");
expect(text.match(/^\d+\./gmu)?.length ?? 0).toBeLessThanOrEqual(3); expect(text.match(/^\d+\./gmu)?.length ?? 0).toBeLessThanOrEqual(3);
}); });

View File

@ -1,4 +1,40 @@
[ [
{
"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", "generation_id": "gen-ag06030440-256da0",
"created_at": "2026-06-03T04:40:01+00:00", "created_at": "2026-06-03T04:40:01+00:00",

View File

@ -0,0 +1,133 @@
{
"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
}
}
}
}

View File

@ -0,0 +1,34 @@
{
"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"
}
]
}
]
}