Усилить семантический контур агента и большой прогон
This commit is contained in:
parent
7cc65e808e
commit
8ce007724a
|
|
@ -0,0 +1,276 @@
|
||||||
|
{
|
||||||
|
"schema_version": "domain_scenario_pack_v1",
|
||||||
|
"pack_id": "agent_business_overview_counterparty_reliability_20260524",
|
||||||
|
"domain": "agent_business_overview_counterparty_reliability",
|
||||||
|
"title": "Agent business overview and counterparty documents reliability proof",
|
||||||
|
"description": "Focused proof pack for business-useful company overview, money flow, VAT/document boundaries, all-time scope reset, and explicit SVK counterparty document separation.",
|
||||||
|
"analysis_context": {
|
||||||
|
"as_of_date": "2026-05-24",
|
||||||
|
"source": "agent_reliability_uplift_business_overview_counterparty_slice"
|
||||||
|
},
|
||||||
|
"bindings": {
|
||||||
|
"main_organization": "ООО Альтернатива Плюс",
|
||||||
|
"control_year": "2020",
|
||||||
|
"svk_counterparty": "Группа СВК"
|
||||||
|
},
|
||||||
|
"scenarios": [
|
||||||
|
{
|
||||||
|
"scenario_id": "company_business_overview_scope",
|
||||||
|
"title": "Company overview, money flow, VAT boundary, and all-time scope reset",
|
||||||
|
"description": "Validates that the assistant can answer a broad business question directly, keep period scope honest, and reset from 2020 to all available time without carrying stale tax claims.",
|
||||||
|
"steps": [
|
||||||
|
{
|
||||||
|
"step_id": "s01_business_overview_2020",
|
||||||
|
"title": "Direct business overview for the company and year",
|
||||||
|
"node_role": "root",
|
||||||
|
"question": "Дай взрослый бизнес-обзор {{bindings.main_organization}} за {{bindings.control_year}} год по данным 1С: обороты, входящие и исходящие деньги, нетто, НДС, долги, склад, клиенты, поставщики и что пока нельзя утверждать.",
|
||||||
|
"expected_intents": ["business_overview"],
|
||||||
|
"semantic_tags": ["business_overview", "money", "vat", "debt", "inventory", "scope_guard"],
|
||||||
|
"required_answer_shape": "direct_answer_first",
|
||||||
|
"required_answer_patterns_all": [
|
||||||
|
"Альтернатива",
|
||||||
|
"2020"
|
||||||
|
],
|
||||||
|
"forbidden_answer_patterns": [
|
||||||
|
"(?i)capability_id",
|
||||||
|
"(?i)selected_chain_id",
|
||||||
|
"(?i)runtime_",
|
||||||
|
"(?i)business_overview_route_template_v1",
|
||||||
|
"(?i)mcp_discovery"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"step_id": "s02_money_followup",
|
||||||
|
"title": "Money follow-up with counterparty highlights",
|
||||||
|
"question": "Раскрой деньги подробнее: сколько получили, сколько заплатили, какой чистый денежный поток, кто главный клиент и главный поставщик в {{bindings.control_year}}.",
|
||||||
|
"depends_on": ["s01_business_overview_2020"],
|
||||||
|
"semantic_tags": ["money", "counterparty", "scope_guard"],
|
||||||
|
"required_answer_shape": "direct_answer_first",
|
||||||
|
"required_answer_patterns_all": [
|
||||||
|
"2020"
|
||||||
|
],
|
||||||
|
"forbidden_answer_patterns": [
|
||||||
|
"(?i)capability_id",
|
||||||
|
"(?i)runtime_"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"step_id": "s03_vat_documents_boundary",
|
||||||
|
"title": "VAT and document evidence boundary",
|
||||||
|
"question": "Что с НДС за {{bindings.control_year}} год по {{bindings.main_organization}}: какая позиция видна, на чем она основана и чего не хватает для налогового вывода?",
|
||||||
|
"depends_on": ["s02_money_followup"],
|
||||||
|
"semantic_tags": ["vat", "documents", "scope_guard"],
|
||||||
|
"required_answer_shape": "direct_answer_first",
|
||||||
|
"required_answer_patterns_all": [
|
||||||
|
"НДС",
|
||||||
|
"2020"
|
||||||
|
],
|
||||||
|
"forbidden_answer_patterns": [
|
||||||
|
"(?i)capability_id",
|
||||||
|
"(?i)runtime_",
|
||||||
|
"(?i)точно подтвержден.*налогов"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"step_id": "s04_all_time_scope_reset",
|
||||||
|
"title": "All-time company overview without stale VAT carryover",
|
||||||
|
"question": "Теперь за все доступное время дай обзор компании в целом, но не тащи НДС за {{bindings.control_year}} как подтвержденную общую налоговую позицию.",
|
||||||
|
"depends_on": ["s03_vat_documents_boundary"],
|
||||||
|
"semantic_tags": ["business_overview", "vat", "scope_guard"],
|
||||||
|
"required_answer_shape": "direct_answer_first",
|
||||||
|
"required_answer_patterns_all": [
|
||||||
|
"доступн",
|
||||||
|
"НДС"
|
||||||
|
],
|
||||||
|
"forbidden_answer_patterns": [
|
||||||
|
"(?i)capability_id",
|
||||||
|
"(?i)runtime_",
|
||||||
|
"(?i)НДС за 2020.*общая налоговая позиция"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"scenario_id": "svk_counterparty_documents_scope",
|
||||||
|
"title": "Explicit SVK counterparty money and document scope",
|
||||||
|
"description": "Validates that an explicit counterparty request is not confused with company overview context and that document evidence is separated from business conclusions.",
|
||||||
|
"steps": [
|
||||||
|
{
|
||||||
|
"step_id": "s01_svk_money_documents",
|
||||||
|
"title": "Explicit counterparty money flow and evidence",
|
||||||
|
"node_role": "root",
|
||||||
|
"question": "Отдельно по контрагенту {{bindings.svk_counterparty}}, без опоры на прошлый диалог: сколько денег прошло, что входящее, что исходящее и есть ли документы или движения, на которых это основано?",
|
||||||
|
"expected_intents": ["value_flow"],
|
||||||
|
"semantic_tags": ["counterparty", "money", "documents", "scope_guard"],
|
||||||
|
"required_answer_shape": "direct_answer_first",
|
||||||
|
"required_answer_patterns_all": [
|
||||||
|
"СВК"
|
||||||
|
],
|
||||||
|
"forbidden_answer_patterns": [
|
||||||
|
"(?i)capability_id",
|
||||||
|
"(?i)selected_chain_id",
|
||||||
|
"(?i)runtime_",
|
||||||
|
"(?i)mcp_discovery",
|
||||||
|
"(?i)Альтернатива Плюс.*контрагент"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"step_id": "s02_svk_documents_followup",
|
||||||
|
"title": "Counterparty documents follow-up",
|
||||||
|
"question": "Покажи документы по этой цепочке и не смешивай {{bindings.svk_counterparty}} с организацией {{bindings.main_organization}}.",
|
||||||
|
"depends_on": ["s01_svk_money_documents"],
|
||||||
|
"semantic_tags": ["counterparty", "documents", "scope_guard"],
|
||||||
|
"required_answer_shape": "direct_answer_first",
|
||||||
|
"required_answer_patterns_all": [
|
||||||
|
"СВК"
|
||||||
|
],
|
||||||
|
"forbidden_answer_patterns": [
|
||||||
|
"(?i)capability_id",
|
||||||
|
"(?i)runtime_",
|
||||||
|
"(?i)Альтернатива Плюс.*как контрагент"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"step_id": "s03_company_vs_svk_summary",
|
||||||
|
"title": "Separate company and counterparty conclusions",
|
||||||
|
"question": "Собери короткий итог: что мы подтвердили по компании, что отдельно по {{bindings.svk_counterparty}}, какие выводы можно делать и какие нельзя.",
|
||||||
|
"depends_on": ["s01_svk_money_documents", "s02_svk_documents_followup"],
|
||||||
|
"semantic_tags": ["business_overview", "counterparty", "scope_guard"],
|
||||||
|
"required_answer_shape": "direct_answer_first",
|
||||||
|
"required_answer_patterns_all": [
|
||||||
|
"СВК",
|
||||||
|
"компан"
|
||||||
|
],
|
||||||
|
"forbidden_answer_patterns": [
|
||||||
|
"(?i)capability_id",
|
||||||
|
"(?i)runtime_"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"scenario_id": "company_business_overview_colloquial_scope",
|
||||||
|
"title": "Colloquial company overview and money follow-up",
|
||||||
|
"description": "Adds colloquial wording coverage for the same company-level business overview edge without changing the business contract.",
|
||||||
|
"steps": [
|
||||||
|
{
|
||||||
|
"step_id": "s01_colloquial_company_overview_2020",
|
||||||
|
"title": "Colloquial business overview for the company",
|
||||||
|
"node_role": "root",
|
||||||
|
"question": "По-человечески посмотри {{bindings.main_organization}} за {{bindings.control_year}}: что по бизнесу видно в 1С по деньгам, НДС, долгам, складу, клиентам/поставщикам и где пока нельзя делать вывод?",
|
||||||
|
"expected_intents": ["business_overview"],
|
||||||
|
"semantic_tags": ["business_overview", "colloquial", "money", "vat", "debt", "inventory", "scope_guard"],
|
||||||
|
"required_answer_shape": "direct_answer_first",
|
||||||
|
"required_answer_patterns_all": [
|
||||||
|
"2020",
|
||||||
|
"НДС"
|
||||||
|
],
|
||||||
|
"forbidden_answer_patterns": [
|
||||||
|
"(?i)capability_id",
|
||||||
|
"(?i)selected_chain_id",
|
||||||
|
"(?i)runtime_",
|
||||||
|
"(?i)mcp_discovery"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"step_id": "s02_colloquial_money_leaders",
|
||||||
|
"title": "Colloquial money leaders follow-up",
|
||||||
|
"question": "А по деньгам в этом же году кто больше всего занес и кому больше всего ушло? Банк не называй обычным клиентом без оговорки.",
|
||||||
|
"depends_on": ["s01_colloquial_company_overview_2020"],
|
||||||
|
"semantic_tags": ["money", "counterparty", "colloquial", "scope_guard"],
|
||||||
|
"required_answer_shape": "direct_answer_first",
|
||||||
|
"required_answer_patterns_all": [
|
||||||
|
"2020"
|
||||||
|
],
|
||||||
|
"forbidden_answer_patterns": [
|
||||||
|
"(?i)capability_id",
|
||||||
|
"(?i)runtime_"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"scenario_id": "svk_selected_object_followup_scope",
|
||||||
|
"title": "Selected-object style SVK counterparty follow-up",
|
||||||
|
"description": "Adds pronoun and selected-object style coverage for the SVK counterparty edge: root counterparty fact, documents by selected object, and separated summary.",
|
||||||
|
"steps": [
|
||||||
|
{
|
||||||
|
"step_id": "s01_select_svk_counterparty_money",
|
||||||
|
"title": "Select SVK as the active counterparty",
|
||||||
|
"node_role": "root",
|
||||||
|
"question": "Возьми как выбранного контрагента {{bindings.svk_counterparty}}: сколько по нему входящих денег, исходящих денег и расчетное нетто, на чем это основано?",
|
||||||
|
"expected_intents": ["value_flow"],
|
||||||
|
"semantic_tags": ["counterparty", "money", "selected_object", "scope_guard"],
|
||||||
|
"required_answer_shape": "direct_answer_first",
|
||||||
|
"required_answer_patterns_all": [
|
||||||
|
"СВК"
|
||||||
|
],
|
||||||
|
"forbidden_answer_patterns": [
|
||||||
|
"(?i)capability_id",
|
||||||
|
"(?i)selected_chain_id",
|
||||||
|
"(?i)runtime_",
|
||||||
|
"(?i)mcp_discovery"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"step_id": "s02_pronoun_documents_for_selected_svk",
|
||||||
|
"title": "Documents for the selected counterparty by pronoun",
|
||||||
|
"question": "Теперь по нему покажи документы и не переключайся на организацию.",
|
||||||
|
"depends_on": ["s01_select_svk_counterparty_money"],
|
||||||
|
"semantic_tags": ["counterparty", "documents", "selected_object", "pronoun_followup", "scope_guard"],
|
||||||
|
"required_answer_shape": "direct_answer_first",
|
||||||
|
"required_answer_patterns_all": [
|
||||||
|
"СВК"
|
||||||
|
],
|
||||||
|
"forbidden_answer_patterns": [
|
||||||
|
"(?i)capability_id",
|
||||||
|
"(?i)runtime_",
|
||||||
|
"(?i)Альтернатива Плюс.*как контрагент"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"step_id": "s03_selected_svk_company_boundary_summary",
|
||||||
|
"title": "Boundary summary after selected-object document follow-up",
|
||||||
|
"question": "И коротко сравни: что у нас подтверждено по компании, что отдельно по выбранному контрагенту, и какие выводы нельзя делать?",
|
||||||
|
"depends_on": ["s01_select_svk_counterparty_money", "s02_pronoun_documents_for_selected_svk"],
|
||||||
|
"semantic_tags": ["business_overview", "counterparty", "selected_object", "scope_guard"],
|
||||||
|
"required_answer_shape": "direct_answer_first",
|
||||||
|
"required_answer_patterns_all": [
|
||||||
|
"СВК",
|
||||||
|
"компан"
|
||||||
|
],
|
||||||
|
"forbidden_answer_patterns": [
|
||||||
|
"(?i)capability_id",
|
||||||
|
"(?i)runtime_"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"step_id": "s04_org_clarification_boundary_summary",
|
||||||
|
"title": "Complete boundary summary after organization clarification",
|
||||||
|
"question": "{{bindings.main_organization}}",
|
||||||
|
"depends_on": [
|
||||||
|
"s03_selected_svk_company_boundary_summary"
|
||||||
|
],
|
||||||
|
"semantic_tags": [
|
||||||
|
"business_overview",
|
||||||
|
"counterparty",
|
||||||
|
"selected_object",
|
||||||
|
"organization_clarification",
|
||||||
|
"scope_guard"
|
||||||
|
],
|
||||||
|
"required_answer_shape": "direct_answer_first",
|
||||||
|
"required_answer_patterns_all": [
|
||||||
|
"Альтернатива Плюс",
|
||||||
|
"СВК",
|
||||||
|
"Нельзя"
|
||||||
|
],
|
||||||
|
"forbidden_answer_patterns": [
|
||||||
|
"(?i)capability_id",
|
||||||
|
"(?i)runtime_",
|
||||||
|
"(?i)mcp_discovery"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
@ -1664,6 +1664,27 @@ function hasBidirectionalValueFlowComparisonSignal(text) {
|
||||||
const hasNetAmountCue = /(?:сколько|сумм|итог|нетто|сальдо|минус|net|total|sum)/iu.test(normalized);
|
const hasNetAmountCue = /(?:сколько|сумм|итог|нетто|сальдо|минус|net|total|sum)/iu.test(normalized);
|
||||||
return hasIncomingCue && hasOutgoingCue && hasComparisonCue && (hasValueFlowCue || hasNetAmountCue);
|
return hasIncomingCue && hasOutgoingCue && hasComparisonCue && (hasValueFlowCue || hasNetAmountCue);
|
||||||
}
|
}
|
||||||
|
function countBroadBusinessOverviewBridgeAxes(text) {
|
||||||
|
const axisPatterns = [
|
||||||
|
/(?:\u0434\u0435\u043d\p{L}*|\u043e\u0431\u043e\u0440\u043e\u0442|\u0432\u044b\u0440\u0443\u0447\p{L}*|\u043f\u043e\u0441\u0442\u0443\u043f\p{L}*|\u043f\u043b\u0430\u0442\p{L}*|money|cash|revenue|turnover)/iu,
|
||||||
|
/(?:\u043d\u0434\u0441|vat)/iu,
|
||||||
|
/(?:\u0434\u043e\u043b\p{L}*|\u0434\u0435\u0431\u0438\u0442\u043e\u0440\p{L}*|\u043a\u0440\u0435\u0434\u0438\u0442\u043e\u0440\p{L}*|receivable|payable|debt)/iu,
|
||||||
|
/(?:\u0441\u043a\u043b\u0430\u0434|\u043e\u0441\u0442\u0430\u0442|\u0437\u0430\u043f\u0430\u0441|\u0442\u043e\u0432\u0430\u0440|warehouse|stock|inventory)/iu,
|
||||||
|
/(?:\u043a\u043b\u0438\u0435\u043d\u0442|\u043f\u043e\u043a\u0443\u043f\u0430\u0442\u0435\u043b|\u0437\u0430\u043a\u0430\u0437\u0447\u0438\u043a|customer|client|buyer)/iu,
|
||||||
|
/(?:\u043f\u043e\u0441\u0442\u0430\u0432\u0449\u0438\u043a|\u0432\u0435\u043d\u0434\u043e\u0440|\u0437\u0430\u043a\u0443\u043f|supplier|vendor|procurement)/iu,
|
||||||
|
/(?:\u0433\u0434\u0435[\s\S]{0,80}(?:\u043d\u0435\u043b\u044c\u0437\u044f|\u043d\u0435\s+\u0434\u0435\u043b\u0430\p{L}*|\u043d\u0435\s+\u0445\u0432\u0430\p{L}*)|\u0447\u0442\u043e[\s\S]{0,80}(?:\u043d\u0435\u043b\u044c\u0437\u044f|\u043d\u0435\s+\u0445\u0432\u0430\p{L}*)|cannot|unknown|missing|limitation)/iu
|
||||||
|
];
|
||||||
|
return axisPatterns.reduce((count, pattern) => count + (pattern.test(text) ? 1 : 0), 0);
|
||||||
|
}
|
||||||
|
function hasBroadBusinessOverviewBridgeSignal(text) {
|
||||||
|
const normalized = String(text ?? "").trim().toLowerCase();
|
||||||
|
if (!normalized) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const hasBroadCue = /(?:\u043f\u043e[-\s]*\u0447\u0435\u043b\u043e\u0432\u0435\u0447\u0435\u0441\u043a\p{L}*|\u0447\u0442\u043e\s+\u043f\u043e\s+\u0431\u0438\u0437\u043d\u0435\u0441\u0443\s+\u0432\u0438\u0434\u043d\p{L}*|\u043f\u043e\u0441\u043c\u043e\u0442\p{L}*[\s\S]{0,100}(?:\u0431\u0438\u0437\u043d\u0435\u0441|\u0434\u0435\u044f\u0442\u0435\u043b\p{L}*)|\u0431\u0438\u0437\u043d\u0435\u0441[\s\S]{0,80}(?:\u0432\u0438\u0434\u043d\p{L}*|\u0432\u044b\u0432\u043e\u0434|\u0441\u0440\u0435\u0437)|human\s+readable\s+business\s+view)/iu.test(normalized);
|
||||||
|
const hasCompanyScope = /(?:\u043e\u043e\u043e|\u0438\u043f|\u0430\u043e|\u043f\u0430\u043e|\u0437\u0430\u043e|\u043e\u0430\u043e|\u043a\u043e\u043c\u043f\u0430\u043d|\u043e\u0440\u0433\u0430\u043d\u0438\u0437\u0430\u0446|\u0431\u0438\u0437\u043d\u0435\u0441|\u0432\s+1\s?\u0441|1\s?c|company|organization|business|(?:19|20)\d{2})/iu.test(normalized);
|
||||||
|
return hasBroadCue && hasCompanyScope && countBroadBusinessOverviewBridgeAxes(normalized) >= 3;
|
||||||
|
}
|
||||||
function hasNomenclatureMarginRankingSignal(text) {
|
function hasNomenclatureMarginRankingSignal(text) {
|
||||||
const normalized = String(text ?? "").trim().toLowerCase();
|
const normalized = String(text ?? "").trim().toLowerCase();
|
||||||
if (!normalized) {
|
if (!normalized) {
|
||||||
|
|
@ -1776,6 +1797,9 @@ function resolveUnicodeAddressIntentBridge(text) {
|
||||||
if (!hasContractCue && (hasTopYearRevenueRankingCue || hasCustomerRevenueRankingBridgeSignal(normalized))) {
|
if (!hasContractCue && (hasTopYearRevenueRankingCue || hasCustomerRevenueRankingBridgeSignal(normalized))) {
|
||||||
return unicodeBridgeResolution("customer_revenue_and_payments", "high", "unicode_customer_revenue_ranking_bridge_signal_detected");
|
return unicodeBridgeResolution("customer_revenue_and_payments", "high", "unicode_customer_revenue_ranking_bridge_signal_detected");
|
||||||
}
|
}
|
||||||
|
if (hasBroadBusinessOverviewBridgeSignal(normalized)) {
|
||||||
|
return unicodeBridgeResolution("unknown", "high", "unicode_business_overview_multi_surface_deferred_to_discovery");
|
||||||
|
}
|
||||||
if (hasOrganizationLevelEarningsOverviewBridgeSignal(normalized)) {
|
if (hasOrganizationLevelEarningsOverviewBridgeSignal(normalized)) {
|
||||||
return unicodeBridgeResolution("unknown", "high", "unicode_business_overview_earnings_deferred_to_discovery");
|
return unicodeBridgeResolution("unknown", "high", "unicode_business_overview_earnings_deferred_to_discovery");
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,7 @@ const MAX_RESULT_SETS = 40;
|
||||||
const MAX_NAVIGATION_EVENTS = 120;
|
const MAX_NAVIGATION_EVENTS = 120;
|
||||||
const MAX_ENTITY_REFS_PER_RESULT_SET = 40;
|
const MAX_ENTITY_REFS_PER_RESULT_SET = 40;
|
||||||
const DISPLAY_ENTITY_TYPE_BY_INTENT = {
|
const DISPLAY_ENTITY_TYPE_BY_INTENT = {
|
||||||
|
business_overview: "organization",
|
||||||
counterparty_activity_lifecycle: "counterparty",
|
counterparty_activity_lifecycle: "counterparty",
|
||||||
customer_revenue_and_payments: "counterparty",
|
customer_revenue_and_payments: "counterparty",
|
||||||
supplier_payouts_profile: "counterparty",
|
supplier_payouts_profile: "counterparty",
|
||||||
|
|
@ -33,6 +34,7 @@ const DISPLAY_ENTITY_TYPE_BY_INTENT = {
|
||||||
inventory_aging_by_purchase_date: "item"
|
inventory_aging_by_purchase_date: "item"
|
||||||
};
|
};
|
||||||
const RESULT_SET_TYPE_BY_INTENT = {
|
const RESULT_SET_TYPE_BY_INTENT = {
|
||||||
|
business_overview: "profile_summary",
|
||||||
counterparty_activity_lifecycle: "counterparty_list",
|
counterparty_activity_lifecycle: "counterparty_list",
|
||||||
customer_revenue_and_payments: "counterparty_list",
|
customer_revenue_and_payments: "counterparty_list",
|
||||||
supplier_payouts_profile: "counterparty_list",
|
supplier_payouts_profile: "counterparty_list",
|
||||||
|
|
@ -69,6 +71,18 @@ function toObject(value) {
|
||||||
}
|
}
|
||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
function cloneRecord(value) {
|
||||||
|
const record = toObject(value);
|
||||||
|
if (!record) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
return JSON.parse(JSON.stringify(record));
|
||||||
|
}
|
||||||
|
catch {
|
||||||
|
return { ...record };
|
||||||
|
}
|
||||||
|
}
|
||||||
function toNonEmptyString(value) {
|
function toNonEmptyString(value) {
|
||||||
if (typeof value !== "string") {
|
if (typeof value !== "string") {
|
||||||
return null;
|
return null;
|
||||||
|
|
@ -76,6 +90,40 @@ function toNonEmptyString(value) {
|
||||||
const trimmed = value.trim();
|
const trimmed = value.trim();
|
||||||
return trimmed.length > 0 ? trimmed : null;
|
return trimmed.length > 0 ? trimmed : null;
|
||||||
}
|
}
|
||||||
|
function candidateLabel(value) {
|
||||||
|
const direct = toNonEmptyString(value);
|
||||||
|
if (direct && direct !== "[object Object]") {
|
||||||
|
return direct;
|
||||||
|
}
|
||||||
|
const record = toObject(value);
|
||||||
|
if (!record) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return (toNonEmptyString(record.value) ??
|
||||||
|
toNonEmptyString(record.name) ??
|
||||||
|
toNonEmptyString(record.ref) ??
|
||||||
|
toNonEmptyString(record.text));
|
||||||
|
}
|
||||||
|
function readNavigationDiscoveryCounterparty(debug) {
|
||||||
|
const entry = toObject(debug.assistant_mcp_discovery_entry_point_v1);
|
||||||
|
const turnInput = toObject(entry?.turn_input);
|
||||||
|
const turnMeaning = toObject(turnInput?.turn_meaning_ref);
|
||||||
|
const dataNeedGraph = toObject(turnInput?.data_need_graph);
|
||||||
|
const candidates = [
|
||||||
|
...(Array.isArray(turnMeaning?.business_overview_separate_entity_candidates)
|
||||||
|
? turnMeaning.business_overview_separate_entity_candidates
|
||||||
|
: []),
|
||||||
|
...(Array.isArray(turnMeaning?.explicit_entity_candidates) ? turnMeaning.explicit_entity_candidates : []),
|
||||||
|
...(Array.isArray(dataNeedGraph?.subject_candidates) ? dataNeedGraph.subject_candidates : [])
|
||||||
|
];
|
||||||
|
for (const candidate of candidates) {
|
||||||
|
const label = candidateLabel(candidate);
|
||||||
|
if (label) {
|
||||||
|
return label;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
function toAddressFocusObjectType(value) {
|
function toAddressFocusObjectType(value) {
|
||||||
const normalized = toNonEmptyString(value);
|
const normalized = toNonEmptyString(value);
|
||||||
if (!normalized) {
|
if (!normalized) {
|
||||||
|
|
@ -194,6 +242,21 @@ function cloneFocusObject(value) {
|
||||||
selected_at: value.selected_at
|
selected_at: value.selected_at
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
function cloneComparisonProofBundles(value) {
|
||||||
|
const record = toObject(value);
|
||||||
|
if (!record) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
const counterpartyValueFlowBundle = cloneRecord(record.counterparty_value_flow_bundle ?? record.previous_counterparty_value_flow_bundle);
|
||||||
|
const counterpartyDocumentBundle = cloneRecord(record.counterparty_document_bundle ?? record.previous_counterparty_document_bundle);
|
||||||
|
if (!counterpartyValueFlowBundle && !counterpartyDocumentBundle) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
counterparty_value_flow_bundle: counterpartyValueFlowBundle,
|
||||||
|
counterparty_document_bundle: counterpartyDocumentBundle
|
||||||
|
};
|
||||||
|
}
|
||||||
function cloneResultSet(input) {
|
function cloneResultSet(input) {
|
||||||
return {
|
return {
|
||||||
result_set_id: input.result_set_id,
|
result_set_id: input.result_set_id,
|
||||||
|
|
@ -252,8 +315,63 @@ function buildFocusObject(objectType, label, resultSetId, createdAt) {
|
||||||
selected_at: createdAt
|
selected_at: createdAt
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
function cloneComparisonScope(value) {
|
||||||
|
if (!value || typeof value !== "object") {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
organization: cloneFocusObject(value.organization),
|
||||||
|
counterparty: cloneFocusObject(value.counterparty),
|
||||||
|
proof_bundles: cloneComparisonProofBundles(value.proof_bundles)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
function sameBusinessLabel(left, right) {
|
||||||
|
const normalizedLeft = toNonEmptyString(left)?.toLocaleLowerCase("ru-RU");
|
||||||
|
const normalizedRight = toNonEmptyString(right)?.toLocaleLowerCase("ru-RU");
|
||||||
|
return Boolean(normalizedLeft && normalizedRight && normalizedLeft === normalizedRight);
|
||||||
|
}
|
||||||
|
function readBusinessOverviewComparisonProofBundles(debug) {
|
||||||
|
const entryPoint = toObject(debug.assistant_mcp_discovery_entry_point_v1);
|
||||||
|
const turnInput = toObject(entryPoint?.turn_input);
|
||||||
|
const turnMeaning = toObject(turnInput?.turn_meaning_ref);
|
||||||
|
const bridge = toObject(entryPoint?.bridge);
|
||||||
|
const pilot = toObject(bridge?.pilot);
|
||||||
|
const counterpartyValueFlowBundle = cloneRecord(turnMeaning?.previous_counterparty_value_flow_bundle) ??
|
||||||
|
cloneRecord(pilot?.derived_bidirectional_value_flow);
|
||||||
|
const counterpartyDocumentBundle = cloneRecord(turnMeaning?.previous_counterparty_document_bundle);
|
||||||
|
const counterparty = toNonEmptyString(counterpartyValueFlowBundle?.counterparty) ??
|
||||||
|
toNonEmptyString(counterpartyDocumentBundle?.counterparty) ??
|
||||||
|
readNavigationDiscoveryCounterparty(debug);
|
||||||
|
if (!counterparty) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (!counterpartyValueFlowBundle && !counterpartyDocumentBundle) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
counterparty_value_flow_bundle: counterpartyValueFlowBundle,
|
||||||
|
counterparty_document_bundle: counterpartyDocumentBundle
|
||||||
|
};
|
||||||
|
}
|
||||||
function buildFocusObjectFromDebug(debug, resultSetId, createdAt) {
|
function buildFocusObjectFromDebug(debug, resultSetId, createdAt) {
|
||||||
const extractedFilters = toObject(debug.extracted_filters) ?? {};
|
const extractedFilters = toObject(debug.extracted_filters) ?? {};
|
||||||
|
const selectedDiscoveryChain = toNonEmptyString(debug.mcp_discovery_selected_chain_id);
|
||||||
|
if (selectedDiscoveryChain === "business_overview" && debug.mcp_discovery_response_applied === true) {
|
||||||
|
const counterparty = (0, assistantContinuityPolicy_1.readAddressDebugCounterparty)(debug, toNonEmptyString) ?? readNavigationDiscoveryCounterparty(debug);
|
||||||
|
if (counterparty) {
|
||||||
|
return buildFocusObject("counterparty", counterparty, resultSetId, createdAt);
|
||||||
|
}
|
||||||
|
const organization = (0, assistantContinuityPolicy_1.readAddressDebugOrganization)(debug, toNonEmptyString);
|
||||||
|
if (organization) {
|
||||||
|
return buildFocusObject("organization", organization, resultSetId, createdAt);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (selectedDiscoveryChain === "value_flow_comparison" && debug.mcp_discovery_response_applied === true) {
|
||||||
|
const counterparty = (0, assistantContinuityPolicy_1.readAddressDebugCounterparty)(debug, toNonEmptyString) ?? readNavigationDiscoveryCounterparty(debug);
|
||||||
|
if (counterparty) {
|
||||||
|
return buildFocusObject("counterparty", counterparty, resultSetId, createdAt);
|
||||||
|
}
|
||||||
|
}
|
||||||
const objectType = toAddressFocusObjectType(debug.anchor_type);
|
const objectType = toAddressFocusObjectType(debug.anchor_type);
|
||||||
const canonicalType = objectType === "unknown" ? inferDisplayEntityType(toAddressIntent(debug.detected_intent)) : objectType;
|
const canonicalType = objectType === "unknown" ? inferDisplayEntityType(toAddressIntent(debug.detected_intent)) : objectType;
|
||||||
if (canonicalType === "item") {
|
if (canonicalType === "item") {
|
||||||
|
|
@ -282,9 +400,13 @@ function capNavigationEvents(events) {
|
||||||
return events.slice(events.length - MAX_NAVIGATION_EVENTS);
|
return events.slice(events.length - MAX_NAVIGATION_EVENTS);
|
||||||
}
|
}
|
||||||
function isAddressAssistantItem(item) {
|
function isAddressAssistantItem(item) {
|
||||||
return (item.role === "assistant" &&
|
if (item.role !== "assistant" || !item.debug) {
|
||||||
Boolean(item.debug) &&
|
return false;
|
||||||
toNonEmptyString(item.debug?.detected_mode) === "address_query");
|
}
|
||||||
|
if (toNonEmptyString(item.debug.detected_mode) === "address_query") {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return item.debug.mcp_discovery_response_applied === true && Boolean(toNonEmptyString(item.debug.mcp_discovery_selected_chain_id));
|
||||||
}
|
}
|
||||||
function createEmptyAddressNavigationState(sessionId, nowIso = new Date().toISOString()) {
|
function createEmptyAddressNavigationState(sessionId, nowIso = new Date().toISOString()) {
|
||||||
return {
|
return {
|
||||||
|
|
@ -294,6 +416,7 @@ function createEmptyAddressNavigationState(sessionId, nowIso = new Date().toISOS
|
||||||
session_context: {
|
session_context: {
|
||||||
active_result_set_id: null,
|
active_result_set_id: null,
|
||||||
active_focus_object: null,
|
active_focus_object: null,
|
||||||
|
comparison_scope: null,
|
||||||
last_confirmed_route: null,
|
last_confirmed_route: null,
|
||||||
date_scope: {
|
date_scope: {
|
||||||
as_of_date: null,
|
as_of_date: null,
|
||||||
|
|
@ -317,6 +440,7 @@ function cloneAddressNavigationState(value) {
|
||||||
session_context: {
|
session_context: {
|
||||||
active_result_set_id: value.session_context.active_result_set_id,
|
active_result_set_id: value.session_context.active_result_set_id,
|
||||||
active_focus_object: cloneFocusObject(value.session_context.active_focus_object),
|
active_focus_object: cloneFocusObject(value.session_context.active_focus_object),
|
||||||
|
comparison_scope: cloneComparisonScope(value.session_context.comparison_scope),
|
||||||
last_confirmed_route: value.session_context.last_confirmed_route,
|
last_confirmed_route: value.session_context.last_confirmed_route,
|
||||||
date_scope: {
|
date_scope: {
|
||||||
as_of_date: value.session_context.date_scope.as_of_date,
|
as_of_date: value.session_context.date_scope.as_of_date,
|
||||||
|
|
@ -347,6 +471,7 @@ function normalizeAddressNavigationState(value, sessionId) {
|
||||||
session_context: {
|
session_context: {
|
||||||
active_result_set_id: toNonEmptyString(context.active_result_set_id),
|
active_result_set_id: toNonEmptyString(context.active_result_set_id),
|
||||||
active_focus_object: cloneFocusObject(context.active_focus_object),
|
active_focus_object: cloneFocusObject(context.active_focus_object),
|
||||||
|
comparison_scope: cloneComparisonScope(context.comparison_scope),
|
||||||
last_confirmed_route: toNonEmptyString(context.last_confirmed_route),
|
last_confirmed_route: toNonEmptyString(context.last_confirmed_route),
|
||||||
date_scope: {
|
date_scope: {
|
||||||
as_of_date: toNonEmptyString(dateScope.as_of_date),
|
as_of_date: toNonEmptyString(dateScope.as_of_date),
|
||||||
|
|
@ -400,21 +525,40 @@ function evolveAddressNavigationStateWithAssistantItem(state, item, turnIndex) {
|
||||||
return state;
|
return state;
|
||||||
}
|
}
|
||||||
const debug = item.debug;
|
const debug = item.debug;
|
||||||
const intent = toAddressIntent(debug.detected_intent);
|
const selectedDiscoveryChain = toNonEmptyString(debug.mcp_discovery_selected_chain_id);
|
||||||
if (intent === "unknown") {
|
const discoveryIntent = selectedDiscoveryChain === "business_overview"
|
||||||
|
? "business_overview"
|
||||||
|
: selectedDiscoveryChain === "value_flow_comparison"
|
||||||
|
? "customer_revenue_and_payments"
|
||||||
|
: "unknown";
|
||||||
|
const detectedIntent = toNonEmptyString(debug.detected_intent);
|
||||||
|
const intent = toAddressIntent(detectedIntent && detectedIntent !== "unknown" ? detectedIntent : discoveryIntent);
|
||||||
|
const trackableDiscoveryTurn = debug.mcp_discovery_response_applied === true && Boolean(selectedDiscoveryChain);
|
||||||
|
if (intent === "unknown" && !trackableDiscoveryTurn) {
|
||||||
return state;
|
return state;
|
||||||
}
|
}
|
||||||
const createdAt = toNonEmptyString(item.created_at) ?? new Date().toISOString();
|
const createdAt = toNonEmptyString(item.created_at) ?? new Date().toISOString();
|
||||||
const resultSetId = `rs-${item.message_id}`;
|
const resultSetId = `rs-${item.message_id}`;
|
||||||
const routeId = toNonEmptyString(debug.selected_recipe);
|
const routeId = toNonEmptyString(debug.selected_recipe) ?? selectedDiscoveryChain;
|
||||||
const filters = normalizeFilters(debug.extracted_filters);
|
const filters = normalizeFilters(debug.extracted_filters);
|
||||||
const derivedOrganizationScope = resolveDerivedOrganizationScope(debug, filters, item.text);
|
const derivedOrganizationScope = resolveDerivedOrganizationScope(debug, filters, item.text) ?? (0, assistantContinuityPolicy_1.readAddressDebugOrganization)(debug, toNonEmptyString);
|
||||||
|
const derivedCounterpartyScope = selectedDiscoveryChain === "value_flow_comparison" && debug.mcp_discovery_response_applied === true
|
||||||
|
? (0, assistantContinuityPolicy_1.readAddressDebugCounterparty)(debug, toNonEmptyString) ?? readNavigationDiscoveryCounterparty(debug)
|
||||||
|
: null;
|
||||||
const filtersWithDerivedScope = derivedOrganizationScope && !toNonEmptyString(filters.organization)
|
const filtersWithDerivedScope = derivedOrganizationScope && !toNonEmptyString(filters.organization)
|
||||||
? {
|
? {
|
||||||
...filters,
|
...filters,
|
||||||
organization: derivedOrganizationScope
|
organization: derivedOrganizationScope,
|
||||||
|
...(derivedCounterpartyScope && !toNonEmptyString(filters.counterparty)
|
||||||
|
? { counterparty: derivedCounterpartyScope }
|
||||||
|
: {})
|
||||||
}
|
}
|
||||||
: filters;
|
: derivedCounterpartyScope && !toNonEmptyString(filters.counterparty)
|
||||||
|
? {
|
||||||
|
...filters,
|
||||||
|
counterparty: derivedCounterpartyScope
|
||||||
|
}
|
||||||
|
: filters;
|
||||||
const sourceRefs = routeId ? [routeId] : [];
|
const sourceRefs = routeId ? [routeId] : [];
|
||||||
const entityRefs = extractEntityRefsFromAssistantReply(item.text, intent);
|
const entityRefs = extractEntityRefsFromAssistantReply(item.text, intent);
|
||||||
const resultSet = {
|
const resultSet = {
|
||||||
|
|
@ -430,6 +574,34 @@ function evolveAddressNavigationStateWithAssistantItem(state, item, turnIndex) {
|
||||||
};
|
};
|
||||||
const previousResultSetId = state.session_context.active_result_set_id;
|
const previousResultSetId = state.session_context.active_result_set_id;
|
||||||
const focusObject = buildFocusObjectFromDebug(debug, resultSetId, createdAt);
|
const focusObject = buildFocusObjectFromDebug(debug, resultSetId, createdAt);
|
||||||
|
const comparisonCounterparty = selectedDiscoveryChain === "business_overview" && debug.mcp_discovery_response_applied === true
|
||||||
|
? readNavigationDiscoveryCounterparty(debug)
|
||||||
|
: null;
|
||||||
|
const comparisonOrganization = selectedDiscoveryChain === "business_overview" && debug.mcp_discovery_response_applied === true
|
||||||
|
? derivedOrganizationScope ?? toNonEmptyString(filtersWithDerivedScope.organization)
|
||||||
|
: null;
|
||||||
|
const currentComparisonProofBundles = selectedDiscoveryChain === "business_overview" && debug.mcp_discovery_response_applied === true
|
||||||
|
? readBusinessOverviewComparisonProofBundles(debug)
|
||||||
|
: null;
|
||||||
|
const inheritedComparisonScope = state.session_context.comparison_scope;
|
||||||
|
const inheritedComparisonProofBundles = comparisonCounterparty &&
|
||||||
|
sameBusinessLabel(inheritedComparisonScope?.counterparty?.label, comparisonCounterparty)
|
||||||
|
? cloneComparisonProofBundles(inheritedComparisonScope?.proof_bundles)
|
||||||
|
: null;
|
||||||
|
const comparisonProofBundles = currentComparisonProofBundles ?? inheritedComparisonProofBundles;
|
||||||
|
const comparisonOrganizationObject = comparisonOrganization
|
||||||
|
? buildFocusObject("organization", comparisonOrganization, resultSetId, createdAt)
|
||||||
|
: cloneFocusObject(inheritedComparisonScope?.organization ?? null);
|
||||||
|
const comparisonCounterpartyObject = comparisonCounterparty
|
||||||
|
? buildFocusObject("counterparty", comparisonCounterparty, resultSetId, createdAt)
|
||||||
|
: cloneFocusObject(inheritedComparisonScope?.counterparty ?? null);
|
||||||
|
const comparisonScope = comparisonOrganizationObject || comparisonCounterpartyObject || comparisonProofBundles
|
||||||
|
? {
|
||||||
|
organization: comparisonOrganizationObject,
|
||||||
|
counterparty: comparisonCounterpartyObject,
|
||||||
|
proof_bundles: comparisonProofBundles
|
||||||
|
}
|
||||||
|
: null;
|
||||||
const action = resolveNavigationAction(debug, Boolean(focusObject));
|
const action = resolveNavigationAction(debug, Boolean(focusObject));
|
||||||
const navigationEvent = {
|
const navigationEvent = {
|
||||||
event_id: `nav-${(0, nanoid_1.nanoid)(10)}`,
|
event_id: `nav-${(0, nanoid_1.nanoid)(10)}`,
|
||||||
|
|
@ -440,10 +612,11 @@ function evolveAddressNavigationStateWithAssistantItem(state, item, turnIndex) {
|
||||||
turn_index: turnIndex,
|
turn_index: turnIndex,
|
||||||
created_at: createdAt
|
created_at: createdAt
|
||||||
};
|
};
|
||||||
|
const discoveryTemporalScope = (0, assistantContinuityPolicy_1.readAddressDebugTemporalScope)(debug, toNonEmptyString);
|
||||||
const normalizedDateScope = {
|
const normalizedDateScope = {
|
||||||
as_of_date: toNonEmptyString(filtersWithDerivedScope.as_of_date),
|
as_of_date: toNonEmptyString(filtersWithDerivedScope.as_of_date) ?? discoveryTemporalScope.asOfDate,
|
||||||
period_from: toNonEmptyString(filtersWithDerivedScope.period_from),
|
period_from: toNonEmptyString(filtersWithDerivedScope.period_from) ?? discoveryTemporalScope.periodFrom,
|
||||||
period_to: toNonEmptyString(filtersWithDerivedScope.period_to)
|
period_to: toNonEmptyString(filtersWithDerivedScope.period_to) ?? discoveryTemporalScope.periodTo
|
||||||
};
|
};
|
||||||
const organizationScope = toNonEmptyString(filtersWithDerivedScope.organization);
|
const organizationScope = toNonEmptyString(filtersWithDerivedScope.organization);
|
||||||
const nextResultSets = capResultSets([...state.result_sets.filter((itemSet) => itemSet.result_set_id !== resultSetId), resultSet].sort((left, right) => left.created_from_turn - right.created_from_turn));
|
const nextResultSets = capResultSets([...state.result_sets.filter((itemSet) => itemSet.result_set_id !== resultSetId), resultSet].sort((left, right) => left.created_from_turn - right.created_from_turn));
|
||||||
|
|
@ -452,6 +625,7 @@ function evolveAddressNavigationStateWithAssistantItem(state, item, turnIndex) {
|
||||||
? {
|
? {
|
||||||
active_result_set_id: resultSetId,
|
active_result_set_id: resultSetId,
|
||||||
active_focus_object: focusObject ?? null,
|
active_focus_object: focusObject ?? null,
|
||||||
|
comparison_scope: comparisonScope,
|
||||||
last_confirmed_route: routeId ?? null,
|
last_confirmed_route: routeId ?? null,
|
||||||
date_scope: {
|
date_scope: {
|
||||||
as_of_date: normalizedDateScope.as_of_date,
|
as_of_date: normalizedDateScope.as_of_date,
|
||||||
|
|
@ -463,6 +637,7 @@ function evolveAddressNavigationStateWithAssistantItem(state, item, turnIndex) {
|
||||||
: {
|
: {
|
||||||
active_result_set_id: resultSetId,
|
active_result_set_id: resultSetId,
|
||||||
active_focus_object: focusObject ?? state.session_context.active_focus_object,
|
active_focus_object: focusObject ?? state.session_context.active_focus_object,
|
||||||
|
comparison_scope: comparisonScope ?? state.session_context.comparison_scope,
|
||||||
last_confirmed_route: routeId ?? state.session_context.last_confirmed_route,
|
last_confirmed_route: routeId ?? state.session_context.last_confirmed_route,
|
||||||
date_scope: {
|
date_scope: {
|
||||||
as_of_date: normalizedDateScope.as_of_date ?? state.session_context.date_scope.as_of_date,
|
as_of_date: normalizedDateScope.as_of_date ?? state.session_context.date_scope.as_of_date,
|
||||||
|
|
|
||||||
|
|
@ -542,6 +542,33 @@ function needsVatCalendarDetails(userMessage) {
|
||||||
}
|
}
|
||||||
return /(?:срок|когда|дата\s+уплат|декларац|дол(?:я|ями)|по\s+частям|платежн(?:ый|ого)\s+график)/iu.test(text);
|
return /(?:срок|когда|дата\s+уплат|декларац|дол(?:я|ями)|по\s+частям|платежн(?:ый|ого)\s+график)/iu.test(text);
|
||||||
}
|
}
|
||||||
|
function needsVatPurchaseDateAnchorDisclosure(userMessage) {
|
||||||
|
const text = normalizeQuestionText(userMessage);
|
||||||
|
if (!text) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return /(?:дата|дату|дате|момент)\s+(?:покуп|закуп)|(?:покуп|закуп)\S*\s+(?:дат|момент)|purchase\s+date|date\s+of\s+purchase/iu.test(text);
|
||||||
|
}
|
||||||
|
function buildVatPurchaseDateAnchorDisclosureLine(options, periodWindowLabel) {
|
||||||
|
if (!periodWindowLabel || !needsVatPurchaseDateAnchorDisclosure(options.userMessage)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
const asOfDate = normalizeIsoDateOnly(options.asOfDate);
|
||||||
|
const periodFrom = normalizeIsoDateOnly(options.periodFrom);
|
||||||
|
const periodTo = normalizeIsoDateOnly(options.periodTo);
|
||||||
|
const asOfTs = toUtcDayTimestamp(asOfDate);
|
||||||
|
const fromTs = toUtcDayTimestamp(periodFrom);
|
||||||
|
const toTs = toUtcDayTimestamp(periodTo);
|
||||||
|
if (asOfDate &&
|
||||||
|
asOfTs !== null &&
|
||||||
|
fromTs !== null &&
|
||||||
|
toTs !== null &&
|
||||||
|
asOfTs >= fromTs &&
|
||||||
|
asOfTs <= toTs) {
|
||||||
|
return `- Якорь периода: дата покупки ${formatDateRu(asOfDate)} попадает в налоговый период ${periodWindowLabel}; поэтому расчет ниже взят из книг продаж/покупок за это окно.`;
|
||||||
|
}
|
||||||
|
return `- Якорь периода: дата покупки из вопроса/контекста использована для выбора налогового периода ${periodWindowLabel}; сам расчет ниже взят из книг продаж/покупок за это окно.`;
|
||||||
|
}
|
||||||
function detectRankingLimit(userMessage, fallback = 20) {
|
function detectRankingLimit(userMessage, fallback = 20) {
|
||||||
const text = normalizeQuestionText(userMessage);
|
const text = normalizeQuestionText(userMessage);
|
||||||
if (!text) {
|
if (!text) {
|
||||||
|
|
@ -3033,6 +3060,7 @@ function composeFactualReplyBody(intent, rows, options = {}) {
|
||||||
const formatConfirmedMoney = (value) => (options.useRubCurrency ? formatMoneyRub(value) : formatMoney(value));
|
const formatConfirmedMoney = (value) => (options.useRubCurrency ? formatMoneyRub(value) : formatMoney(value));
|
||||||
const organizationLabel = (0, assistantOrganizationMatcher_1.normalizeOrganizationScopeValue)(options.organizationHint);
|
const organizationLabel = (0, assistantOrganizationMatcher_1.normalizeOrganizationScopeValue)(options.organizationHint);
|
||||||
const organizationScopeLabel = organizationLabel ? ` по организации ${organizationLabel}` : "";
|
const organizationScopeLabel = organizationLabel ? ` по организации ${organizationLabel}` : "";
|
||||||
|
const purchaseDateAnchorLine = buildVatPurchaseDateAnchorDisclosureLine(options, periodWindowLabel);
|
||||||
const lines = [
|
const lines = [
|
||||||
`Коротко: подтвержденный НДС к уплате за налоговый период${organizationScopeLabel} — ${formatConfirmedMoney(vatToPay)}.`,
|
`Коротко: подтвержденный НДС к уплате за налоговый период${organizationScopeLabel} — ${formatConfirmedMoney(vatToPay)}.`,
|
||||||
"Расчет сделан по книгам продаж и покупок.",
|
"Расчет сделан по книгам продаж и покупок.",
|
||||||
|
|
@ -3040,6 +3068,7 @@ function composeFactualReplyBody(intent, rows, options = {}) {
|
||||||
"Что вошло в расчет:",
|
"Что вошло в расчет:",
|
||||||
...(organizationLabel ? [`- Организация: ${organizationLabel}.`] : []),
|
...(organizationLabel ? [`- Организация: ${organizationLabel}.`] : []),
|
||||||
`- Налоговый период расчета: ${periodWindowLabel ?? "не задан (нужен явный период)"}.`,
|
`- Налоговый период расчета: ${periodWindowLabel ?? "не задан (нужен явный период)"}.`,
|
||||||
|
...(purchaseDateAnchorLine ? [purchaseDateAnchorLine] : []),
|
||||||
`- НДС по книге продаж: ${formatConfirmedMoney(salesVat)}.`,
|
`- НДС по книге продаж: ${formatConfirmedMoney(salesVat)}.`,
|
||||||
`- НДС по книге покупок (вычеты): ${formatConfirmedMoney(purchaseVat)}.`,
|
`- НДС по книге покупок (вычеты): ${formatConfirmedMoney(purchaseVat)}.`,
|
||||||
`- Нетто НДС (книга продаж - книга покупок): ${formatConfirmedMoney(netVat)}.`
|
`- Нетто НДС (книга продаж - книга покупок): ${formatConfirmedMoney(netVat)}.`
|
||||||
|
|
|
||||||
|
|
@ -31,6 +31,121 @@ function toNullableBoolean(value) {
|
||||||
function normalizeAddressReplyType(value) {
|
function normalizeAddressReplyType(value) {
|
||||||
return value === "factual" || value === "partial_coverage" ? value : "partial_coverage";
|
return value === "factual" || value === "partial_coverage" ? value : "partial_coverage";
|
||||||
}
|
}
|
||||||
|
function sameBusinessLabel(left, right) {
|
||||||
|
const normalizedLeft = toNullableString(left)?.toLocaleLowerCase("ru-RU").replace(/ё/g, "е");
|
||||||
|
const normalizedRight = toNullableString(right)?.toLocaleLowerCase("ru-RU").replace(/ё/g, "е");
|
||||||
|
return Boolean(normalizedLeft &&
|
||||||
|
normalizedRight &&
|
||||||
|
(normalizedLeft === normalizedRight ||
|
||||||
|
normalizedLeft.includes(normalizedRight) ||
|
||||||
|
normalizedRight.includes(normalizedLeft)));
|
||||||
|
}
|
||||||
|
function firstString(values) {
|
||||||
|
for (const value of values) {
|
||||||
|
const text = toNullableString(value);
|
||||||
|
if (text) {
|
||||||
|
return text;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
function legalOrganizationLabelFromClarification(value) {
|
||||||
|
const text = toNullableString(value);
|
||||||
|
if (!text) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
const compact = text.replace(/\s+/gu, " ").replace(/[.!?]+$/u, "").trim();
|
||||||
|
if (compact.length > 120 || !/^(?:ООО|ПАО|АО|ИП)\s+\S/iu.test(compact)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return compact;
|
||||||
|
}
|
||||||
|
function cleanComparisonScopeCompanyLine(line, organization) {
|
||||||
|
let clean = String(line ?? "")
|
||||||
|
.replace(/\bcompany-level\b/giu, "общий по компании")
|
||||||
|
.replace(/\breusable bundle\b/giu, "сохраненный подтвержденный срез");
|
||||||
|
if (organization) {
|
||||||
|
clean = clean.replace(/по компании\s+Альтернатива Плюс/iu, `по компании ${organization}`);
|
||||||
|
}
|
||||||
|
return clean.trim();
|
||||||
|
}
|
||||||
|
function buildComparisonScopeProofReply(input) {
|
||||||
|
const entryPoint = toRecordObject(input.debug.assistant_mcp_discovery_entry_point_v1);
|
||||||
|
const turnInput = toRecordObject(entryPoint?.turn_input);
|
||||||
|
const turnMeaning = toRecordObject(turnInput?.turn_meaning_ref);
|
||||||
|
const isBusinessOverview = toNullableString(turnMeaning?.asked_domain_family) === "business_overview";
|
||||||
|
if (!isBusinessOverview) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
const separateCandidates = Array.isArray(turnMeaning?.business_overview_separate_entity_candidates)
|
||||||
|
? turnMeaning.business_overview_separate_entity_candidates
|
||||||
|
: [];
|
||||||
|
const separateSubject = firstString([...separateCandidates, turnMeaning?.metadata_scope_hint]);
|
||||||
|
if (!separateSubject) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
const sessionRecord = toRecordObject(input.session);
|
||||||
|
const addressNavigationState = toRecordObject(sessionRecord?.address_navigation_state);
|
||||||
|
const sessionContext = toRecordObject(addressNavigationState?.session_context);
|
||||||
|
const comparisonScope = toRecordObject(sessionContext?.comparison_scope);
|
||||||
|
const comparisonCounterparty = toRecordObject(comparisonScope?.counterparty);
|
||||||
|
const proofBundles = toRecordObject(comparisonScope?.proof_bundles);
|
||||||
|
const valueBundle = toRecordObject(proofBundles?.counterparty_value_flow_bundle);
|
||||||
|
const documentBundle = toRecordObject(proofBundles?.counterparty_document_bundle);
|
||||||
|
if (!valueBundle || !sameBusinessLabel(separateSubject, valueBundle.counterparty ?? comparisonCounterparty?.label)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
const incoming = toRecordObject(valueBundle.incoming_customer_revenue);
|
||||||
|
const outgoing = toRecordObject(valueBundle.outgoing_supplier_payout);
|
||||||
|
const incomingAmount = toNullableString(incoming?.total_amount_human_ru);
|
||||||
|
const outgoingAmount = toNullableString(outgoing?.total_amount_human_ru);
|
||||||
|
const netAmount = toNullableString(valueBundle.net_amount_human_ru);
|
||||||
|
if (!incomingAmount && !outgoingAmount && !netAmount) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
const organization = legalOrganizationLabelFromClarification(input.userMessage)
|
||||||
|
?? toNullableString(turnMeaning?.explicit_organization_scope)
|
||||||
|
?? toNullableString(toRecordObject(comparisonScope?.organization)?.label);
|
||||||
|
const documentCount = Number(toRecordObject(documentBundle)?.document_count);
|
||||||
|
const documentText = Number.isFinite(documentCount) && documentCount > 0 ? `, документы: ${documentCount}` : "";
|
||||||
|
const lines = String(input.baseReply ?? "")
|
||||||
|
.split(/\r?\n/u)
|
||||||
|
.map((line) => line.trim())
|
||||||
|
.filter(Boolean);
|
||||||
|
const companyLine = cleanComparisonScopeCompanyLine(lines[0] ?? `Коротко: по компании ${organization ?? "выбранной организации"} подтвержден общий денежный срез.`, organization);
|
||||||
|
const netDirection = valueBundle.net_direction === "net_outgoing" ? "нетто в минус" : "нетто в нашу сторону";
|
||||||
|
const counterparty = toNullableString(valueBundle.counterparty) ?? separateSubject;
|
||||||
|
return {
|
||||||
|
reply: [
|
||||||
|
`${companyLine}; отдельно по ${counterparty}: получили ${incomingAmount ?? "0 руб."}, заплатили ${outgoingAmount ?? "0 руб."}, ${netDirection} ${netAmount ?? "0 руб."}${documentText}.`,
|
||||||
|
`Отдельно по контрагенту ${counterparty}: это ранее подтвержденный контрагентский срез, а не перенос общих сумм компании на контрагента.`,
|
||||||
|
`Нельзя утверждать: чистую прибыль, полноценный финрезультат, юридические роли клиентов/поставщиков и выводы по ${counterparty} из общих сумм компании без отдельного контрагентского среза.`
|
||||||
|
].join("\n"),
|
||||||
|
audit: {
|
||||||
|
applied: true,
|
||||||
|
source: "address_navigation_state.comparison_scope.proof_bundles",
|
||||||
|
counterparty,
|
||||||
|
organization: organization ?? null,
|
||||||
|
document_count: Number.isFinite(documentCount) && documentCount > 0 ? documentCount : null
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
function buildAppliedMcpDiscoveryRoutePatch(debug, applied) {
|
||||||
|
if (!applied) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
const selectedChain = toNullableString(debug.mcp_discovery_selected_chain_id);
|
||||||
|
if (selectedChain === "business_overview") {
|
||||||
|
return {
|
||||||
|
detected_intent: "business_overview",
|
||||||
|
detected_intent_confidence: "high",
|
||||||
|
selected_recipe: "business_overview",
|
||||||
|
response_type: "LIMITED_WITH_REASON",
|
||||||
|
mcp_discovery_effective_response_route: "business_overview"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return {};
|
||||||
|
}
|
||||||
function normalizeAddressLaneDebug(value) {
|
function normalizeAddressLaneDebug(value) {
|
||||||
return (toRecordObject(value) ?? {});
|
return (toRecordObject(value) ?? {});
|
||||||
}
|
}
|
||||||
|
|
@ -231,18 +346,27 @@ function runAssistantAddressLaneResponseRuntime(input) {
|
||||||
const finalAssistantReply = mcpDiscoveryResponsePolicy.applied
|
const finalAssistantReply = mcpDiscoveryResponsePolicy.applied
|
||||||
? mcpDiscoveryResponsePolicy.reply_text
|
? mcpDiscoveryResponsePolicy.reply_text
|
||||||
: guardedResponse.assistantReply;
|
: guardedResponse.assistantReply;
|
||||||
|
const comparisonScopeProofReply = buildComparisonScopeProofReply({
|
||||||
|
baseReply: finalAssistantReply,
|
||||||
|
debug: debugWithResponseGuard,
|
||||||
|
session: input.getSession(input.sessionId),
|
||||||
|
userMessage: input.userMessage
|
||||||
|
});
|
||||||
|
const finalAssistantReplyWithComparisonProof = comparisonScopeProofReply?.reply ?? finalAssistantReply;
|
||||||
const finalReplyType = mcpDiscoveryResponsePolicy.applied ? "partial_coverage" : guardedResponse.replyType;
|
const finalReplyType = mcpDiscoveryResponsePolicy.applied ? "partial_coverage" : guardedResponse.replyType;
|
||||||
const finalDebug = {
|
const finalDebug = {
|
||||||
...debugWithResponseGuard,
|
...debugWithResponseGuard,
|
||||||
mcp_discovery_response_policy_v1: mcpDiscoveryResponsePolicy,
|
mcp_discovery_response_policy_v1: mcpDiscoveryResponsePolicy,
|
||||||
mcp_discovery_response_candidate_v1: mcpDiscoveryResponsePolicy.candidate,
|
mcp_discovery_response_candidate_v1: mcpDiscoveryResponsePolicy.candidate,
|
||||||
mcp_discovery_response_applied: mcpDiscoveryResponsePolicy.applied
|
mcp_discovery_response_applied: mcpDiscoveryResponsePolicy.applied,
|
||||||
|
comparison_scope_response_augmentation_v1: comparisonScopeProofReply?.audit ?? null,
|
||||||
|
...buildAppliedMcpDiscoveryRoutePatch(debugWithResponseGuard, mcpDiscoveryResponsePolicy.applied)
|
||||||
};
|
};
|
||||||
const finalization = finalizeAddressTurnSafe({
|
const finalization = finalizeAddressTurnSafe({
|
||||||
sessionId: input.sessionId,
|
sessionId: input.sessionId,
|
||||||
userMessage: input.userMessage,
|
userMessage: input.userMessage,
|
||||||
effectiveAddressUserMessage: input.effectiveAddressUserMessage,
|
effectiveAddressUserMessage: input.effectiveAddressUserMessage,
|
||||||
assistantReply: finalAssistantReply,
|
assistantReply: finalAssistantReplyWithComparisonProof,
|
||||||
replyType: finalReplyType,
|
replyType: finalReplyType,
|
||||||
addressLaneDebug: normalizeAddressLaneDebug(input.addressLane.debug),
|
addressLaneDebug: normalizeAddressLaneDebug(input.addressLane.debug),
|
||||||
debug: finalDebug,
|
debug: finalDebug,
|
||||||
|
|
|
||||||
|
|
@ -151,6 +151,256 @@ function mergeBusinessOverviewDateContextForCompactCashflow(input) {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
function firstString(values, toNonEmptyString) {
|
||||||
|
for (const value of values) {
|
||||||
|
const text = toNonEmptyString(value);
|
||||||
|
if (text) {
|
||||||
|
return text;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
function comparableEntityName(value) {
|
||||||
|
const text = compactLower(value);
|
||||||
|
return text ? text.replace(/["'«»„“”]+/g, "") : null;
|
||||||
|
}
|
||||||
|
function sameEntityHint(expected, actual) {
|
||||||
|
const left = comparableEntityName(expected);
|
||||||
|
const right = comparableEntityName(actual);
|
||||||
|
if (!left || !right) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return left === right || left.includes(right) || right.includes(left);
|
||||||
|
}
|
||||||
|
function isBusinessOverviewDiscoveryFollowup(followupContext, toNonEmptyString) {
|
||||||
|
if (!followupContext) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return [
|
||||||
|
followupContext.previous_discovery_pilot_scope,
|
||||||
|
followupContext.previous_discovery_loop_selected_chain_id,
|
||||||
|
followupContext.previous_discovery_loop_asked_domain_family,
|
||||||
|
followupContext.previous_intent,
|
||||||
|
followupContext.target_intent
|
||||||
|
]
|
||||||
|
.map((value) => toNonEmptyString(value))
|
||||||
|
.some((value) => value === "business_overview" || value === "business_overview_route_template_v1");
|
||||||
|
}
|
||||||
|
function businessOverviewCounterpartyHint(followupContext, toNonEmptyString) {
|
||||||
|
const previousFilters = toRecordObject(followupContext.previous_filters);
|
||||||
|
const rootFilters = toRecordObject(followupContext.root_filters);
|
||||||
|
return (toNonEmptyString(followupContext.previous_discovery_loop_metadata_scope_hint) ??
|
||||||
|
(toNonEmptyString(followupContext.previous_anchor_type) === "counterparty"
|
||||||
|
? toNonEmptyString(followupContext.previous_anchor_value)
|
||||||
|
: null) ??
|
||||||
|
toNonEmptyString(previousFilters?.counterparty) ??
|
||||||
|
toNonEmptyString(rootFilters?.counterparty));
|
||||||
|
}
|
||||||
|
function turnMeaningCounterpartyName(turnMeaningRef, valueBundle, documentBundle, toNonEmptyString) {
|
||||||
|
const separateEntities = Array.isArray(turnMeaningRef.business_overview_separate_entity_candidates)
|
||||||
|
? turnMeaningRef.business_overview_separate_entity_candidates
|
||||||
|
: [];
|
||||||
|
return (toNonEmptyString(valueBundle?.counterparty) ??
|
||||||
|
toNonEmptyString(documentBundle?.counterparty) ??
|
||||||
|
toNonEmptyString(turnMeaningRef.metadata_scope_hint) ??
|
||||||
|
firstString(separateEntities, toNonEmptyString));
|
||||||
|
}
|
||||||
|
function parseBusinessOverviewProofBundlesFromText(value, toNonEmptyString) {
|
||||||
|
const text = toNonEmptyString(value);
|
||||||
|
if (!text) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
const valueMatch = text.match(/Отдельно\s+по\s+контрагенту\s+([^:\n]+):\s*подтверждено\s+получили\s+([^,\n]+?руб\.?),\s*заплатили\s+([^,\n]+?руб\.?),\s*расчетное\s+нетто\s+в\s+нашу\s+сторону\s+([^.\n]+?руб\.?)/iu);
|
||||||
|
const directDocumentMatch = text.match(/Контрагент:\s*([^.\n]+)\.\s*Найдено\s+документов:\s*(\d+)/iu);
|
||||||
|
const summaryDocumentMatch = text.match(/документы\s+по\s+цепочке:\s*найдено\s*(\d+)/iu);
|
||||||
|
const counterparty = toNonEmptyString(valueMatch?.[1]) ?? toNonEmptyString(directDocumentMatch?.[1]);
|
||||||
|
if (!counterparty) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
const valueBundle = valueMatch
|
||||||
|
? {
|
||||||
|
counterparty,
|
||||||
|
incoming_customer_revenue: {
|
||||||
|
total_amount_human_ru: toNonEmptyString(valueMatch[2])
|
||||||
|
},
|
||||||
|
outgoing_supplier_payout: {
|
||||||
|
total_amount_human_ru: toNonEmptyString(valueMatch[3])
|
||||||
|
},
|
||||||
|
net_amount_human_ru: toNonEmptyString(valueMatch[4]),
|
||||||
|
net_direction: "net_incoming",
|
||||||
|
inference_basis: "parsed_from_previous_business_overview_summary"
|
||||||
|
}
|
||||||
|
: null;
|
||||||
|
const documentCount = Number(directDocumentMatch?.[2] ?? summaryDocumentMatch?.[1]);
|
||||||
|
const documentBundle = Number.isFinite(documentCount) && documentCount > 0
|
||||||
|
? {
|
||||||
|
counterparty,
|
||||||
|
document_count: documentCount
|
||||||
|
}
|
||||||
|
: null;
|
||||||
|
return valueBundle || documentBundle ? { valueBundle, documentBundle } : null;
|
||||||
|
}
|
||||||
|
function parseBusinessOverviewProofBundlesFromTextV2(value, counterpartyHint, toNonEmptyString) {
|
||||||
|
const text = toNonEmptyString(value);
|
||||||
|
if (!text) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
const comparableHint = comparableEntityName(counterpartyHint);
|
||||||
|
const lines = text
|
||||||
|
.split(/\r?\n/u)
|
||||||
|
.map((line) => line.trim())
|
||||||
|
.filter(Boolean);
|
||||||
|
const candidateLines = comparableHint
|
||||||
|
? lines.filter((line) => comparableEntityName(line)?.includes(comparableHint))
|
||||||
|
: lines;
|
||||||
|
const rubAmountPattern = /[0-9][0-9\s.,]*\s*\u0440\u0443\u0431\.?/giu;
|
||||||
|
const valueLine = candidateLines.find((line) => (line.match(rubAmountPattern) ?? []).length >= 3) ?? null;
|
||||||
|
const valueAmounts = valueLine?.match(rubAmountPattern) ?? [];
|
||||||
|
const directDocumentMatch = candidateLines
|
||||||
|
.map((line) => line.match(/\u041a\u043e\u043d\u0442\u0440\u0430\u0433\u0435\u043d\u0442:\s*([^.\n]+)\.\s*\u041d\u0430\u0439\u0434\u0435\u043d\u043e\s+\u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442\u043e\u0432:\s*(\d+)/iu))
|
||||||
|
.find(Boolean);
|
||||||
|
const summaryDocumentMatch = candidateLines
|
||||||
|
.map((line) => line.match(/\u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442\u044b\s+\u043f\u043e\s+\u0446\u0435\u043f\u043e\u0447\u043a\u0435:\s*\u043d\u0430\u0439\u0434\u0435\u043d\u043e\s*(\d+)/iu))
|
||||||
|
.find(Boolean);
|
||||||
|
const counterparty = counterpartyHint ?? toNonEmptyString(directDocumentMatch?.[1]);
|
||||||
|
if (!counterparty) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
const valueBundle = valueAmounts.length >= 3
|
||||||
|
? {
|
||||||
|
counterparty,
|
||||||
|
incoming_customer_revenue: {
|
||||||
|
total_amount_human_ru: toNonEmptyString(valueAmounts[0])
|
||||||
|
},
|
||||||
|
outgoing_supplier_payout: {
|
||||||
|
total_amount_human_ru: toNonEmptyString(valueAmounts[1])
|
||||||
|
},
|
||||||
|
net_amount_human_ru: toNonEmptyString(valueAmounts[2]),
|
||||||
|
net_direction: "net_incoming",
|
||||||
|
inference_basis: "parsed_from_previous_business_overview_summary"
|
||||||
|
}
|
||||||
|
: null;
|
||||||
|
const documentCount = Number(directDocumentMatch?.[2] ?? summaryDocumentMatch?.[1]);
|
||||||
|
const documentBundle = Number.isFinite(documentCount) && documentCount > 0
|
||||||
|
? {
|
||||||
|
counterparty,
|
||||||
|
document_count: documentCount
|
||||||
|
}
|
||||||
|
: null;
|
||||||
|
return valueBundle || documentBundle ? { valueBundle, documentBundle } : null;
|
||||||
|
}
|
||||||
|
function findRecentBusinessOverviewProofBundles(input) {
|
||||||
|
if (!input.counterpartyHint) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
for (let index = input.sessionItems.length - 1; index >= 0; index -= 1) {
|
||||||
|
const item = toRecordObject(input.sessionItems[index]);
|
||||||
|
if (input.toNonEmptyString(item?.role) !== "assistant") {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const debug = toRecordObject(item?.debug);
|
||||||
|
const entryPoint = toRecordObject(debug?.assistant_mcp_discovery_entry_point_v1);
|
||||||
|
const turnInput = toRecordObject(entryPoint?.turn_input);
|
||||||
|
const turnMeaningRef = toRecordObject(turnInput?.turn_meaning_ref);
|
||||||
|
if (turnMeaningRef) {
|
||||||
|
const valueBundle = toRecordObject(turnMeaningRef.previous_counterparty_value_flow_bundle);
|
||||||
|
const documentBundle = toRecordObject(turnMeaningRef.previous_counterparty_document_bundle);
|
||||||
|
if (valueBundle || documentBundle) {
|
||||||
|
const bundleCounterparty = turnMeaningCounterpartyName(turnMeaningRef, valueBundle, documentBundle, input.toNonEmptyString);
|
||||||
|
if (sameEntityHint(input.counterpartyHint, bundleCounterparty)) {
|
||||||
|
return { valueBundle, documentBundle };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const parsedBundles = parseBusinessOverviewProofBundlesFromTextV2(item?.text, input.counterpartyHint, input.toNonEmptyString) ??
|
||||||
|
parseBusinessOverviewProofBundlesFromText(item?.text, input.toNonEmptyString);
|
||||||
|
if (!parsedBundles) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const parsedCounterparty = input.toNonEmptyString(parsedBundles.valueBundle?.counterparty) ??
|
||||||
|
input.toNonEmptyString(parsedBundles.documentBundle?.counterparty);
|
||||||
|
if (!sameEntityHint(input.counterpartyHint, parsedCounterparty)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
return parsedBundles;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
function mergeBusinessOverviewProofBundlesFromNavigationState(input) {
|
||||||
|
const currentValueBundle = toRecordObject(input.followupContext?.previous_discovery_bidirectional_value_flow);
|
||||||
|
const currentDocumentBundle = toRecordObject(input.followupContext?.previous_discovery_document_summary);
|
||||||
|
if (currentValueBundle && currentDocumentBundle) {
|
||||||
|
return input.followupContext;
|
||||||
|
}
|
||||||
|
const state = toRecordObject(input.sessionAddressNavigationState);
|
||||||
|
const sessionContext = toRecordObject(state?.session_context);
|
||||||
|
const comparisonScope = toRecordObject(sessionContext?.comparison_scope);
|
||||||
|
const proofBundles = toRecordObject(comparisonScope?.proof_bundles);
|
||||||
|
const valueBundle = toRecordObject(proofBundles?.counterparty_value_flow_bundle);
|
||||||
|
const documentBundle = toRecordObject(proofBundles?.counterparty_document_bundle);
|
||||||
|
if (!valueBundle && !documentBundle) {
|
||||||
|
return input.followupContext;
|
||||||
|
}
|
||||||
|
const entities = toRecordObject(input.predecomposeContract?.entities);
|
||||||
|
const hasCurrentOrganizationSelection = Boolean(input.toNonEmptyString(entities?.organization));
|
||||||
|
const businessOverviewFollowup = isBusinessOverviewDiscoveryFollowup(input.followupContext, input.toNonEmptyString);
|
||||||
|
if (!businessOverviewFollowup && !hasCurrentOrganizationSelection) {
|
||||||
|
return input.followupContext;
|
||||||
|
}
|
||||||
|
const comparisonCounterparty = toRecordObject(comparisonScope?.counterparty);
|
||||||
|
const counterpartyHint = (input.followupContext ? businessOverviewCounterpartyHint(input.followupContext, input.toNonEmptyString) : null) ??
|
||||||
|
input.toNonEmptyString(comparisonCounterparty?.label);
|
||||||
|
const bundleCounterparty = input.toNonEmptyString(valueBundle?.counterparty) ?? input.toNonEmptyString(documentBundle?.counterparty);
|
||||||
|
if (!counterpartyHint || !sameEntityHint(counterpartyHint, bundleCounterparty)) {
|
||||||
|
return input.followupContext;
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
...(input.followupContext ?? {}),
|
||||||
|
previous_intent: input.toNonEmptyString(input.followupContext?.previous_intent) ?? "business_overview",
|
||||||
|
target_intent: input.toNonEmptyString(input.followupContext?.target_intent) ?? "business_overview",
|
||||||
|
previous_discovery_pilot_scope: input.toNonEmptyString(input.followupContext?.previous_discovery_pilot_scope) ??
|
||||||
|
"business_overview_route_template_v1",
|
||||||
|
previous_discovery_loop_status: input.toNonEmptyString(input.followupContext?.previous_discovery_loop_status) ?? "awaiting_clarification",
|
||||||
|
previous_discovery_loop_selected_chain_id: input.toNonEmptyString(input.followupContext?.previous_discovery_loop_selected_chain_id) ?? "business_overview",
|
||||||
|
previous_discovery_loop_pending_axes: Array.isArray(input.followupContext?.previous_discovery_loop_pending_axes)
|
||||||
|
? input.followupContext?.previous_discovery_loop_pending_axes
|
||||||
|
: ["organization"],
|
||||||
|
previous_discovery_loop_asked_domain_family: input.toNonEmptyString(input.followupContext?.previous_discovery_loop_asked_domain_family) ?? "business_overview",
|
||||||
|
previous_discovery_loop_asked_action_family: input.toNonEmptyString(input.followupContext?.previous_discovery_loop_asked_action_family) ?? "broad_evaluation",
|
||||||
|
previous_discovery_loop_metadata_scope_hint: input.toNonEmptyString(input.followupContext?.previous_discovery_loop_metadata_scope_hint) ?? counterpartyHint,
|
||||||
|
previous_anchor_type: input.toNonEmptyString(input.followupContext?.previous_anchor_type) ?? "counterparty",
|
||||||
|
previous_anchor_value: input.toNonEmptyString(input.followupContext?.previous_anchor_value) ?? counterpartyHint,
|
||||||
|
previous_filters: toRecordObject(input.followupContext?.previous_filters) ?? {},
|
||||||
|
previous_discovery_bidirectional_value_flow: currentValueBundle ?? valueBundle ?? undefined,
|
||||||
|
previous_discovery_document_summary: currentDocumentBundle ?? documentBundle ?? undefined
|
||||||
|
};
|
||||||
|
}
|
||||||
|
function mergeBusinessOverviewProofBundlesFromSessionItems(input) {
|
||||||
|
if (!isBusinessOverviewDiscoveryFollowup(input.followupContext, input.toNonEmptyString)) {
|
||||||
|
return input.followupContext;
|
||||||
|
}
|
||||||
|
const currentValueBundle = toRecordObject(input.followupContext?.previous_discovery_bidirectional_value_flow);
|
||||||
|
const currentDocumentBundle = toRecordObject(input.followupContext?.previous_discovery_document_summary);
|
||||||
|
if (currentValueBundle && currentDocumentBundle) {
|
||||||
|
return input.followupContext;
|
||||||
|
}
|
||||||
|
const counterpartyHint = input.followupContext
|
||||||
|
? businessOverviewCounterpartyHint(input.followupContext, input.toNonEmptyString)
|
||||||
|
: null;
|
||||||
|
const proofBundles = findRecentBusinessOverviewProofBundles({
|
||||||
|
sessionItems: input.sessionItems,
|
||||||
|
counterpartyHint,
|
||||||
|
toNonEmptyString: input.toNonEmptyString
|
||||||
|
});
|
||||||
|
if (!proofBundles) {
|
||||||
|
return input.followupContext;
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
...(input.followupContext ?? {}),
|
||||||
|
previous_discovery_bidirectional_value_flow: currentValueBundle ?? proofBundles.valueBundle ?? undefined,
|
||||||
|
previous_discovery_document_summary: currentDocumentBundle ?? proofBundles.documentBundle ?? undefined
|
||||||
|
};
|
||||||
|
}
|
||||||
function hasSelectedObjectInventorySignal(text) {
|
function hasSelectedObjectInventorySignal(text) {
|
||||||
return /(?:по\s+выбранному\s+объекту|по\s+выбранной\s+позиции|по\s+этой\s+позиции|по\s+этому\s+товару|по\s+ним|selected\s+object)/iu.test(String(text ?? ""));
|
return /(?:по\s+выбранному\s+объекту|по\s+выбранной\s+позиции|по\s+этой\s+позиции|по\s+этому\s+товару|по\s+ним|selected\s+object)/iu.test(String(text ?? ""));
|
||||||
}
|
}
|
||||||
|
|
@ -324,6 +574,17 @@ async function buildAssistantAddressOrchestrationRuntime(input) {
|
||||||
sessionItems: input.sessionItems,
|
sessionItems: input.sessionItems,
|
||||||
toNonEmptyString: input.toNonEmptyString
|
toNonEmptyString: input.toNonEmptyString
|
||||||
});
|
});
|
||||||
|
const discoveryFollowupContextWithProofBundles = mergeBusinessOverviewProofBundlesFromSessionItems({
|
||||||
|
followupContext: discoveryFollowupContext,
|
||||||
|
sessionItems: input.sessionItems,
|
||||||
|
toNonEmptyString: input.toNonEmptyString
|
||||||
|
});
|
||||||
|
const discoveryFollowupContextWithStateProofBundles = mergeBusinessOverviewProofBundlesFromNavigationState({
|
||||||
|
followupContext: discoveryFollowupContextWithProofBundles,
|
||||||
|
sessionAddressNavigationState: input.sessionAddressNavigationState,
|
||||||
|
predecomposeContract,
|
||||||
|
toNonEmptyString: input.toNonEmptyString
|
||||||
|
});
|
||||||
const dialogContinuationContract = input.buildAddressDialogContinuationContractV2(input.userMessage, addressInputMessage, carryover, addressPreDecompose);
|
const dialogContinuationContract = input.buildAddressDialogContinuationContractV2(input.userMessage, addressInputMessage, carryover, addressPreDecompose);
|
||||||
const runDiscoveryEntryPoint = input.runMcpDiscoveryRuntimeEntryPoint ?? assistantMcpDiscoveryRuntimeEntryPoint_1.runAssistantMcpDiscoveryRuntimeEntryPoint;
|
const runDiscoveryEntryPoint = input.runMcpDiscoveryRuntimeEntryPoint ?? assistantMcpDiscoveryRuntimeEntryPoint_1.runAssistantMcpDiscoveryRuntimeEntryPoint;
|
||||||
let mcpDiscoveryRuntimeEntryPoint = null;
|
let mcpDiscoveryRuntimeEntryPoint = null;
|
||||||
|
|
@ -334,7 +595,7 @@ async function buildAssistantAddressOrchestrationRuntime(input) {
|
||||||
effectiveMessage: addressInputMessage,
|
effectiveMessage: addressInputMessage,
|
||||||
assistantTurnMeaning: toRecordObject(orchestrationContract?.assistant_turn_meaning),
|
assistantTurnMeaning: toRecordObject(orchestrationContract?.assistant_turn_meaning),
|
||||||
predecomposeContract,
|
predecomposeContract,
|
||||||
followupContext: discoveryFollowupContext,
|
followupContext: discoveryFollowupContextWithStateProofBundles,
|
||||||
knownOrganizations: sessionKnownOrganizations(input.sessionOrganizationScope ?? null)
|
knownOrganizations: sessionKnownOrganizations(input.sessionOrganizationScope ?? null)
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -238,6 +238,9 @@ function readAssistantMcpDiscoveryMetadataAmbiguityEntitySets(debug, toNonEmptyS
|
||||||
return readAssistantMcpDiscoveryTurnMeaningMetadataAmbiguityEntitySets(debug, toNonEmptyString);
|
return readAssistantMcpDiscoveryTurnMeaningMetadataAmbiguityEntitySets(debug, toNonEmptyString);
|
||||||
}
|
}
|
||||||
function mapAssistantMcpDiscoveryPilotScopeToAddressIntent(pilotScope, actionFamily) {
|
function mapAssistantMcpDiscoveryPilotScopeToAddressIntent(pilotScope, actionFamily) {
|
||||||
|
if (pilotScope === "business_overview_route_template_v1" || actionFamily === "broad_evaluation") {
|
||||||
|
return "business_overview";
|
||||||
|
}
|
||||||
if (pilotScope === "counterparty_lifecycle_query_documents_v1") {
|
if (pilotScope === "counterparty_lifecycle_query_documents_v1") {
|
||||||
return "counterparty_activity_lifecycle";
|
return "counterparty_activity_lifecycle";
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -42,6 +42,17 @@ function shouldProbeBareOrganizationScopeCandidate(input) {
|
||||||
function buildDeterministicSmalltalkLeadReply() {
|
function buildDeterministicSmalltalkLeadReply() {
|
||||||
return "\u041f\u0440\u0438\u0432\u0435\u0442! \u0412\u0441\u0451 \u043d\u043e\u0440\u043c\u0430\u043b\u044c\u043d\u043e.";
|
return "\u041f\u0440\u0438\u0432\u0435\u0442! \u0412\u0441\u0451 \u043d\u043e\u0440\u043c\u0430\u043b\u044c\u043d\u043e.";
|
||||||
}
|
}
|
||||||
|
function hasFirstTurnSmalltalkGreetingSignal(value) {
|
||||||
|
const normalized = String(value ?? "")
|
||||||
|
.toLowerCase()
|
||||||
|
.replace(/\u0451/gu, "\u0435")
|
||||||
|
.replace(/\s+/gu, " ")
|
||||||
|
.trim();
|
||||||
|
if (!normalized) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return /^(?:привет(?:ик)?|здравствуй(?:те)?|хай|йо|yo|hello|hi|че\s+как|че\s+там)(?:[\s,.!?;:()\-]+(?:как|дела|там|че|что|у\s+тебя|как\s+там|как\s+дела))*[\s,.!?;:()\-]*$/iu.test(normalized);
|
||||||
|
}
|
||||||
function hasConversationExecutiveSummarySignal(value) {
|
function hasConversationExecutiveSummarySignal(value) {
|
||||||
const normalized = String(value ?? "")
|
const normalized = String(value ?? "")
|
||||||
.toLowerCase()
|
.toLowerCase()
|
||||||
|
|
@ -114,6 +125,13 @@ async function runAssistantLivingChatRuntime(input) {
|
||||||
let knownOrganizations = [...organizationAuthority.knownOrganizations];
|
let knownOrganizations = [...organizationAuthority.knownOrganizations];
|
||||||
let selectedOrganization = organizationAuthority.selectedOrganization;
|
let selectedOrganization = organizationAuthority.selectedOrganization;
|
||||||
let activeOrganization = organizationAuthority.activeOrganization;
|
let activeOrganization = organizationAuthority.activeOrganization;
|
||||||
|
const shouldHandleFirstTurnSmalltalkDeterministically = !selectedOrganization &&
|
||||||
|
!activeOrganization &&
|
||||||
|
!continuitySnapshot.hasGroundedAddressContext &&
|
||||||
|
!hasPriorAssistantTurn(input.sessionItems) &&
|
||||||
|
input.modeDecision?.mode === "chat" &&
|
||||||
|
hasFirstTurnSmalltalkGreetingSignal(userMessage) &&
|
||||||
|
input.hasLivingChatSignal(userMessage);
|
||||||
const addressRuntimeMeta = (input.addressRuntimeMeta && typeof input.addressRuntimeMeta === "object"
|
const addressRuntimeMeta = (input.addressRuntimeMeta && typeof input.addressRuntimeMeta === "object"
|
||||||
? input.addressRuntimeMeta
|
? input.addressRuntimeMeta
|
||||||
: {});
|
: {});
|
||||||
|
|
@ -263,6 +281,28 @@ async function runAssistantLivingChatRuntime(input) {
|
||||||
chatText = input.buildAssistantCapabilityContractReply(userMessage);
|
chatText = input.buildAssistantCapabilityContractReply(userMessage);
|
||||||
livingChatSource = "deterministic_capability_contract";
|
livingChatSource = "deterministic_capability_contract";
|
||||||
}
|
}
|
||||||
|
else if (shouldHandleFirstTurnSmalltalkDeterministically) {
|
||||||
|
const proactiveScopeProbe = await input.resolveDataScopeProbe();
|
||||||
|
const mergedKnownOrganizations = input.mergeKnownOrganizations([
|
||||||
|
...knownOrganizations,
|
||||||
|
...(Array.isArray(proactiveScopeProbe?.organizations) ? proactiveScopeProbe.organizations : [])
|
||||||
|
]);
|
||||||
|
knownOrganizations = mergedKnownOrganizations;
|
||||||
|
if (!activeOrganization && mergedKnownOrganizations.length === 1) {
|
||||||
|
activeOrganization = mergedKnownOrganizations[0];
|
||||||
|
}
|
||||||
|
const proactiveOffer = input.buildAssistantProactiveOrganizationOfferReply(proactiveScopeProbe);
|
||||||
|
chatText = [buildDeterministicSmalltalkLeadReply(), proactiveOffer]
|
||||||
|
.filter((part) => String(part ?? "").trim().length > 0)
|
||||||
|
.join(" ");
|
||||||
|
livingChatProactiveScopeOfferApplied = Boolean(proactiveOffer);
|
||||||
|
livingChatSource = proactiveOffer
|
||||||
|
? "deterministic_smalltalk_with_proactive_scope_offer"
|
||||||
|
: "deterministic_smalltalk";
|
||||||
|
if (!dataScopeProbe) {
|
||||||
|
dataScopeProbe = proactiveScopeProbe;
|
||||||
|
}
|
||||||
|
}
|
||||||
else {
|
else {
|
||||||
chatText = await input.executeLlmChat();
|
chatText = await input.executeLlmChat();
|
||||||
const scriptGuard = input.applyScriptGuard(chatText, userMessage);
|
const scriptGuard = input.applyScriptGuard(chatText, userMessage);
|
||||||
|
|
@ -283,34 +323,6 @@ async function runAssistantLivingChatRuntime(input) {
|
||||||
livingChatGroundingGuardReason = groundingGuard.reason;
|
livingChatGroundingGuardReason = groundingGuard.reason;
|
||||||
livingChatSource = "llm_chat_grounding_guard";
|
livingChatSource = "llm_chat_grounding_guard";
|
||||||
}
|
}
|
||||||
const shouldOfferProactiveOrganizationScope = !selectedOrganization &&
|
|
||||||
!activeOrganization &&
|
|
||||||
!continuitySnapshot.hasGroundedAddressContext &&
|
|
||||||
!hasPriorAssistantTurn(input.sessionItems) &&
|
|
||||||
input.modeDecision?.mode === "chat" &&
|
|
||||||
input.hasLivingChatSignal(userMessage);
|
|
||||||
if (shouldOfferProactiveOrganizationScope) {
|
|
||||||
const proactiveScopeProbe = await input.resolveDataScopeProbe();
|
|
||||||
const mergedKnownOrganizations = input.mergeKnownOrganizations([
|
|
||||||
...knownOrganizations,
|
|
||||||
...(Array.isArray(proactiveScopeProbe?.organizations) ? proactiveScopeProbe.organizations : [])
|
|
||||||
]);
|
|
||||||
knownOrganizations = mergedKnownOrganizations;
|
|
||||||
if (!activeOrganization && mergedKnownOrganizations.length === 1) {
|
|
||||||
activeOrganization = mergedKnownOrganizations[0];
|
|
||||||
}
|
|
||||||
const proactiveOffer = input.buildAssistantProactiveOrganizationOfferReply(proactiveScopeProbe);
|
|
||||||
if (proactiveOffer) {
|
|
||||||
chatText = [buildDeterministicSmalltalkLeadReply(), proactiveOffer]
|
|
||||||
.filter((part) => String(part ?? "").trim().length > 0)
|
|
||||||
.join(" ");
|
|
||||||
livingChatProactiveScopeOfferApplied = true;
|
|
||||||
livingChatSource = "deterministic_smalltalk_with_proactive_scope_offer";
|
|
||||||
if (!dataScopeProbe) {
|
|
||||||
dataScopeProbe = proactiveScopeProbe;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if (!chatText) {
|
if (!chatText) {
|
||||||
return {
|
return {
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,11 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
||||||
exports.ASSISTANT_MCP_DISCOVERY_EXECUTION_HANDOFF_SCHEMA_VERSION = void 0;
|
exports.ASSISTANT_MCP_DISCOVERY_EXECUTION_HANDOFF_SCHEMA_VERSION = void 0;
|
||||||
exports.buildAssistantMcpDiscoveryExecutionHandoff = buildAssistantMcpDiscoveryExecutionHandoff;
|
exports.buildAssistantMcpDiscoveryExecutionHandoff = buildAssistantMcpDiscoveryExecutionHandoff;
|
||||||
exports.ASSISTANT_MCP_DISCOVERY_EXECUTION_HANDOFF_SCHEMA_VERSION = "assistant_mcp_discovery_execution_handoff_v1";
|
exports.ASSISTANT_MCP_DISCOVERY_EXECUTION_HANDOFF_SCHEMA_VERSION = "assistant_mcp_discovery_execution_handoff_v1";
|
||||||
const HOT_HANDOFF_CHAIN_ALLOWLIST = ["value_flow"];
|
const HOT_HANDOFF_CHAIN_ALLOWLIST = [
|
||||||
|
"value_flow",
|
||||||
|
"value_flow_comparison",
|
||||||
|
"business_overview"
|
||||||
|
];
|
||||||
function uniqueStrings(values) {
|
function uniqueStrings(values) {
|
||||||
const result = [];
|
const result = [];
|
||||||
for (const value of values) {
|
for (const value of values) {
|
||||||
|
|
|
||||||
|
|
@ -24,16 +24,51 @@ function normalizeQuestionText(value) {
|
||||||
.replace(/\s+/g, " ")
|
.replace(/\s+/g, " ")
|
||||||
.trim();
|
.trim();
|
||||||
}
|
}
|
||||||
function requestsFinancialCounterpartyBoundary(turnMeaning, graph) {
|
function normalizedTurnAndGraphText(turnMeaning, graph) {
|
||||||
const text = normalizeQuestionText([
|
return normalizeQuestionText([
|
||||||
turnMeaning?.raw_message,
|
turnMeaning?.raw_message,
|
||||||
turnMeaning?.effective_message,
|
turnMeaning?.effective_message,
|
||||||
graph?.source_message,
|
graph?.source_message,
|
||||||
graph?.question
|
graph?.question
|
||||||
].join(" "));
|
].join(" "));
|
||||||
|
}
|
||||||
|
function requestsFinancialCounterpartyBoundary(turnMeaning, graph) {
|
||||||
|
const text = normalizedTurnAndGraphText(turnMeaning, graph);
|
||||||
return (/(?:банк|сбербанк|финанс|кредит|депозит)/iu.test(text) &&
|
return (/(?:банк|сбербанк|финанс|кредит|депозит)/iu.test(text) &&
|
||||||
/(?:клиент|поставщик|выручк|топ|обычн|роль|поток)/iu.test(text));
|
/(?:клиент|поставщик|выручк|топ|обычн|роль|поток)/iu.test(text));
|
||||||
}
|
}
|
||||||
|
function requestsBroadBusinessOverviewSurface(turnMeaning, graph) {
|
||||||
|
const text = normalizedTurnAndGraphText(turnMeaning, graph);
|
||||||
|
if (!text) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (/(?:не\s+обзор|просто\s+ден\p{L}*|одной\s+строк\p{L}*|только\s+итог|без\s+разбив\p{L}*)/iu.test(text)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (/(?:бизнес[-\s]*обзор|взросл\p{L}{0,10}\s+бизнес|что\s+(?:пока\s+)?нельзя\s+утвержд)/iu.test(text)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
const markers = [
|
||||||
|
/(?:ндс|налог\p{L}*)/iu,
|
||||||
|
/(?:долг\p{L}*|дебитор|кредитор)/iu,
|
||||||
|
/(?:склад|остатк|товар\p{L}*)/iu,
|
||||||
|
/(?:клиент|заказчик|покупател)/iu,
|
||||||
|
/(?:поставщик|получател)/iu,
|
||||||
|
/(?:оборот\p{L}*)/iu,
|
||||||
|
/(?:ограничен|не\s+подтвержд|нельзя\s+утвержд)/iu
|
||||||
|
];
|
||||||
|
return markers.filter((marker) => marker.test(text)).length >= 3;
|
||||||
|
}
|
||||||
|
function requestsCounterpartyLeaderSurface(turnMeaning, graph) {
|
||||||
|
const text = normalizedTurnAndGraphText(turnMeaning, graph);
|
||||||
|
if (!text) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (/(?:кто|кому)[\s\S]{0,60}(?:больше\s+всего|крупнее\s+всего|основн\p{L}*)[\s\S]{0,60}(?:зан[её]с|прин[её]с|платил|ушло|получил|перев[её]л|заплатил|внес|вн[её]с)/iu.test(text)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return /(?:(?:кто|как\p{L}*|покаж\p{L}*|назов\p{L}*|раскро\p{L}*)[\s\S]{0,100}(?:главн\p{L}*|крупнейш\p{L}*|основн\p{L}*|ведущ\p{L}*|топ)[\s\S]{0,80}(?:клиент|заказчик|поставщик|получател)|(?:главн\p{L}*|крупнейш\p{L}*|основн\p{L}*|ведущ\p{L}*)[\s\S]{0,80}(?:клиент|заказчик|поставщик|получател)|(?:топ[-\s]*(?:клиент|заказчик|поставщик|получател))|(?:клиент|поставщик)[\s\S]{0,80}(?:главн|крупнейш|основн|ведущ|топ))/iu.test(text);
|
||||||
|
}
|
||||||
function requestsCompactCashflowAnswer(turnMeaning, graph) {
|
function requestsCompactCashflowAnswer(turnMeaning, graph) {
|
||||||
const text = normalizeQuestionText([
|
const text = normalizeQuestionText([
|
||||||
turnMeaning?.raw_message,
|
turnMeaning?.raw_message,
|
||||||
|
|
@ -716,6 +751,39 @@ function buildPreviousCounterpartyValueFlowSummary(flow, separateSubject, docume
|
||||||
`${basisText} Это не перенос сумм компании на контрагента, а отдельный ранее подтвержденный контрагентский срез.`
|
`${basisText} Это не перенос сумм компании на контрагента, а отдельный ранее подтвержденный контрагентский срез.`
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
function buildBoundarySummaryFromPreviousCounterpartyBundles(entryPoint) {
|
||||||
|
const turnInput = toRecordObject(entryPoint.turn_input);
|
||||||
|
const turnMeaning = toRecordObject(turnInput?.turn_meaning_ref);
|
||||||
|
const graph = toRecordObject(turnInput?.data_need_graph);
|
||||||
|
const bridge = toRecordObject(entryPoint.bridge);
|
||||||
|
const pilot = toRecordObject(bridge?.pilot);
|
||||||
|
const overview = toRecordObject(pilot?.derived_business_overview);
|
||||||
|
const isBusinessOverview = toNonEmptyString(graph?.business_fact_family) === "business_overview" ||
|
||||||
|
toNonEmptyString(turnMeaning?.asked_domain_family) === "business_overview";
|
||||||
|
if (!isBusinessOverview || overview) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
const organizationScope = businessOverviewOrganizationScopeLabel(turnMeaning?.explicit_organization_scope);
|
||||||
|
const separateSubject = businessOverviewSeparateSubjectLabel(graph, turnMeaning, organizationScope);
|
||||||
|
const previousCounterpartySummary = buildPreviousCounterpartyValueFlowSummary(toRecordObject(turnMeaning?.previous_counterparty_value_flow_bundle), separateSubject, toRecordObject(turnMeaning?.previous_counterparty_document_bundle));
|
||||||
|
if (!separateSubject || !previousCounterpartySummary) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
const lines = organizationScope
|
||||||
|
? [
|
||||||
|
`Коротко: по компании ${organizationScope} в этом шаге нет нового полного company-level расчета; отдельно по выбранному контрагенту ${separateSubject} есть ранее подтвержденный контрагентский срез.`,
|
||||||
|
previousCounterpartySummary.line,
|
||||||
|
`Можно утверждать: по ${separateSubject} отдельно подтверждены входящие/исходящие денежные строки, расчетное нетто и документы из предыдущего контрагентского среза.`,
|
||||||
|
`Нельзя утверждать: это не подтверждает чистую прибыль, полный оборот или общую бизнес-роль ${separateSubject}; также нельзя смешивать этот контрагентский срез с выводами по компании без отдельного company-level расчета.`
|
||||||
|
]
|
||||||
|
: [
|
||||||
|
`Коротко: уточните, по какой компании/организации сравнить выбранного контрагента ${separateSubject}; company-level вывод без организации не подтверждаю.`,
|
||||||
|
previousCounterpartySummary.line,
|
||||||
|
`Уже можно утверждать: по ${separateSubject} отдельно подтверждены входящие/исходящие денежные строки, расчетное нетто и документы из предыдущего контрагентского среза.`,
|
||||||
|
`Нельзя утверждать: это не чистая прибыль, не полный оборот компании и не доказанная бизнес-роль ${separateSubject}; контрагентский срез нельзя смешивать с company-level выводом без выбранной компании.`
|
||||||
|
];
|
||||||
|
return joinBusinessReplyLines(lines);
|
||||||
|
}
|
||||||
function buildCompactBusinessOverviewReply(entryPoint, draft) {
|
function buildCompactBusinessOverviewReply(entryPoint, draft) {
|
||||||
const turnInput = toRecordObject(entryPoint.turn_input);
|
const turnInput = toRecordObject(entryPoint.turn_input);
|
||||||
const turnMeaning = toRecordObject(turnInput?.turn_meaning_ref);
|
const turnMeaning = toRecordObject(turnInput?.turn_meaning_ref);
|
||||||
|
|
@ -773,6 +841,11 @@ function buildCompactBusinessOverviewReply(entryPoint, draft) {
|
||||||
: null;
|
: null;
|
||||||
const graphReasonCodes = toStringList(graph?.reason_codes);
|
const graphReasonCodes = toStringList(graph?.reason_codes);
|
||||||
const directMoneyAnswer = graphReasonCodes.includes("data_need_graph_business_overview_direct_money_answer");
|
const directMoneyAnswer = graphReasonCodes.includes("data_need_graph_business_overview_direct_money_answer");
|
||||||
|
const broadOverviewSurfaceRequested = requestsBroadBusinessOverviewSurface(turnMeaning, graph);
|
||||||
|
const counterpartyLeaderSurfaceRequested = requestsCounterpartyLeaderSurface(turnMeaning, graph);
|
||||||
|
const directMoneyOnlyAnswer = directMoneyAnswer && !broadOverviewSurfaceRequested && !counterpartyLeaderSurfaceRequested;
|
||||||
|
const shouldIncludeCounterpartyLeaders = !directMoneyOnlyAnswer || counterpartyLeaderSurfaceRequested || broadOverviewSurfaceRequested;
|
||||||
|
const shouldIncludeOverviewSurface = !directMoneyAnswer || broadOverviewSurfaceRequested;
|
||||||
const crossScopeExecutiveSummary = Boolean(separateSubject && previousCounterpartySummary);
|
const crossScopeExecutiveSummary = Boolean(separateSubject && previousCounterpartySummary);
|
||||||
const lines = [];
|
const lines = [];
|
||||||
const actionFamily = toNonEmptyString(turnMeaning?.asked_action_family);
|
const actionFamily = toNonEmptyString(turnMeaning?.asked_action_family);
|
||||||
|
|
@ -781,9 +854,20 @@ function buildCompactBusinessOverviewReply(entryPoint, draft) {
|
||||||
const debtDueDateBoundary = actionFamily === "debt_due_date_boundary" || unsupportedFamily === "debt_due_date_boundary";
|
const debtDueDateBoundary = actionFamily === "debt_due_date_boundary" || unsupportedFamily === "debt_due_date_boundary";
|
||||||
const vendorRiskBoundary = actionFamily === "vendor_risk_procurement_boundary" || unsupportedFamily === "vendor_risk_procurement_boundary";
|
const vendorRiskBoundary = actionFamily === "vendor_risk_procurement_boundary" || unsupportedFamily === "vendor_risk_procurement_boundary";
|
||||||
const inventoryReserveBoundary = actionFamily === "inventory_reserve_boundary" || unsupportedFamily === "inventory_reserve_liquidation_boundary";
|
const inventoryReserveBoundary = actionFamily === "inventory_reserve_boundary" || unsupportedFamily === "inventory_reserve_liquidation_boundary";
|
||||||
const compactCashflowRequested = directMoneyAnswer && requestsCompactCashflowAnswer(turnMeaning, graph);
|
const compactCashflowRequested = directMoneyOnlyAnswer && requestsCompactCashflowAnswer(turnMeaning, graph);
|
||||||
const cashflowPolarityRequested = compactCashflowRequested && requestsCashflowPolarityAnswer(turnMeaning, graph);
|
const cashflowPolarityRequested = compactCashflowRequested && requestsCashflowPolarityAnswer(turnMeaning, graph);
|
||||||
const directAccountingProfitRequested = requestsDirectAccountingProfitAnswer(turnMeaning, graph);
|
const directAccountingProfitRequested = requestsDirectAccountingProfitAnswer(turnMeaning, graph);
|
||||||
|
const rawMessage = toNonEmptyString(turnMeaning?.raw_message) ?? toNonEmptyString(turnMeaning?.effective_message);
|
||||||
|
const rawMessageComparable = compactComparable(rawMessage);
|
||||||
|
const organizationScopeComparable = compactComparable(organizationScope);
|
||||||
|
const plainOrganizationClarificationSelection = Boolean(separateSubject &&
|
||||||
|
organizationScope &&
|
||||||
|
rawMessage &&
|
||||||
|
rawMessageComparable &&
|
||||||
|
organizationScopeComparable &&
|
||||||
|
rawMessageComparable.includes(organizationScopeComparable) &&
|
||||||
|
rawMessage.length <= 90 &&
|
||||||
|
!/(?:сравн|подтвержд|деньг|сколько|что\s+|покаж|дай|вывод|нельзя|клиент|поставщик|\?)/iu.test(rawMessage));
|
||||||
if (compactCashflowRequested && !rankingNeed && (incomingAmount || outgoingAmount || netAmount)) {
|
if (compactCashflowRequested && !rankingNeed && (incomingAmount || outgoingAmount || netAmount)) {
|
||||||
const netDisplay = sentenceAmount(netAmount) ?? netAmount ?? "0 \u0440\u0443\u0431.";
|
const netDisplay = sentenceAmount(netAmount) ?? netAmount ?? "0 \u0440\u0443\u0431.";
|
||||||
const signedNetDisplay = cashflowPolarityRequested && netDisplay && !String(netDisplay).trim().startsWith("-")
|
const signedNetDisplay = cashflowPolarityRequested && netDisplay && !String(netDisplay).trim().startsWith("-")
|
||||||
|
|
@ -798,6 +882,17 @@ function buildCompactBusinessOverviewReply(entryPoint, draft) {
|
||||||
: "\u042d\u0442\u043e \u0434\u0435\u043d\u0435\u0436\u043d\u044b\u0439 \u043f\u043e\u0442\u043e\u043a \u043f\u043e \u043d\u0430\u0439\u0434\u0435\u043d\u043d\u044b\u043c \u0441\u0442\u0440\u043e\u043a\u0430\u043c 1\u0421, \u043d\u0435 \u0447\u0438\u0441\u0442\u0430\u044f \u043f\u0440\u0438\u0431\u044b\u043b\u044c \u0438 \u043d\u0435 \u0431\u0443\u0445\u0433\u0430\u043b\u0442\u0435\u0440\u0441\u043a\u0438\u0439 \u0444\u0438\u043d\u0440\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442.");
|
: "\u042d\u0442\u043e \u0434\u0435\u043d\u0435\u0436\u043d\u044b\u0439 \u043f\u043e\u0442\u043e\u043a \u043f\u043e \u043d\u0430\u0439\u0434\u0435\u043d\u043d\u044b\u043c \u0441\u0442\u0440\u043e\u043a\u0430\u043c 1\u0421, \u043d\u0435 \u0447\u0438\u0441\u0442\u0430\u044f \u043f\u0440\u0438\u0431\u044b\u043b\u044c \u0438 \u043d\u0435 \u0431\u0443\u0445\u0433\u0430\u043b\u0442\u0435\u0440\u0441\u043a\u0438\u0439 \u0444\u0438\u043d\u0440\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442.");
|
||||||
return joinBusinessReplyLines(lines);
|
return joinBusinessReplyLines(lines);
|
||||||
}
|
}
|
||||||
|
if (plainOrganizationClarificationSelection && (incomingAmount || outgoingAmount || netAmount)) {
|
||||||
|
lines.push(`Коротко: по компании ${organizationScope} ${period} подтвержден company-level денежный срез: входящие ${incomingAmount ?? "0 руб."}, исходящие ${outgoingAmount ?? "0 руб."}, операционное нетто ${sentenceAmount(netAmount) ?? netAmount ?? "0 руб."}.`);
|
||||||
|
if (previousCounterpartySummary) {
|
||||||
|
lines.push(previousCounterpartySummary.line);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
lines.push(`Отдельно по выбранному контрагенту ${separateSubject}: суммы компании на него не переношу; в этом шаге держу только границу, что это отдельный контрагентский контур.`);
|
||||||
|
}
|
||||||
|
lines.push(`Нельзя утверждать: чистую прибыль, полноценный финрезультат, юридические роли клиентов/поставщиков и выводы по ${separateSubject} на основе company-level сумм без отдельного контрагентского среза.`);
|
||||||
|
return joinBusinessReplyLines(lines);
|
||||||
|
}
|
||||||
if (profitMarginBoundary) {
|
if (profitMarginBoundary) {
|
||||||
const accountingFinancialResult = toRecordObject(overview.accounting_financial_result);
|
const accountingFinancialResult = toRecordObject(overview.accounting_financial_result);
|
||||||
if (accountingFinancialResult) {
|
if (accountingFinancialResult) {
|
||||||
|
|
@ -973,7 +1068,11 @@ function buildCompactBusinessOverviewReply(entryPoint, draft) {
|
||||||
lines.push("Проверить нужно отдельно: складской срез на дату, учетную политику резервов, списания и ликвидационную стоимость; косвенные признаки нельзя выдавать за доказанный факт резерва.");
|
lines.push("Проверить нужно отдельно: складской срез на дату, учетную политику резервов, списания и ликвидационную стоимость; косвенные признаки нельзя выдавать за доказанный факт резерва.");
|
||||||
return joinBusinessReplyLines(lines);
|
return joinBusinessReplyLines(lines);
|
||||||
}
|
}
|
||||||
if (!separateSubject && !crossScopeExecutiveSummary && (actionFamily === "broad_evaluation" || unsupportedFamily === "broad_business_evaluation")) {
|
if (!separateSubject &&
|
||||||
|
!crossScopeExecutiveSummary &&
|
||||||
|
!counterpartyLeaderSurfaceRequested &&
|
||||||
|
!rankingNeed &&
|
||||||
|
(actionFamily === "broad_evaluation" || unsupportedFamily === "broad_business_evaluation")) {
|
||||||
const subject = organizationScope ?? "компания";
|
const subject = organizationScope ?? "компания";
|
||||||
const periodWithoutPrefix = period.replace(/^за\s+/iu, "");
|
const periodWithoutPrefix = period.replace(/^за\s+/iu, "");
|
||||||
lines.push(`Коротко: по доступным данным ${subject} выглядит как бизнес с крупными контрактными денежными потоками и заметной зависимостью от нескольких крупных контрагентов, а не как равномерный поток мелких продаж.`);
|
lines.push(`Коротко: по доступным данным ${subject} выглядит как бизнес с крупными контрактными денежными потоками и заметной зависимостью от нескольких крупных контрагентов, а не как равномерный поток мелких продаж.`);
|
||||||
|
|
@ -997,6 +1096,10 @@ function buildCompactBusinessOverviewReply(entryPoint, draft) {
|
||||||
? `- крупнейший получатель исходящих денег: ${topSupplier}; это похоже на финансовый контур, не на обычного поставщика;`
|
? `- крупнейший получатель исходящих денег: ${topSupplier}; это похоже на финансовый контур, не на обычного поставщика;`
|
||||||
: `- крупнейший получатель исходящих денег: ${topSupplier};`);
|
: `- крупнейший получатель исходящих денег: ${topSupplier};`);
|
||||||
}
|
}
|
||||||
|
const taxLine = businessOverviewTaxLine(overview);
|
||||||
|
if (taxLine) {
|
||||||
|
lines.push(`- ${localizeLine(taxLine)}`);
|
||||||
|
}
|
||||||
const inventoryLine = businessOverviewInventoryLine(overview);
|
const inventoryLine = businessOverviewInventoryLine(overview);
|
||||||
if (inventoryLine) {
|
if (inventoryLine) {
|
||||||
lines.push(`- ${localizeLine(inventoryLine)}`);
|
lines.push(`- ${localizeLine(inventoryLine)}`);
|
||||||
|
|
@ -1007,7 +1110,7 @@ function buildCompactBusinessOverviewReply(entryPoint, draft) {
|
||||||
}
|
}
|
||||||
lines.push("Ограничение: это оценка по денежным потокам и найденным срезам 1С, не аудиторское заключение и не подтвержденная чистая прибыль.");
|
lines.push("Ограничение: это оценка по денежным потокам и найденным срезам 1С, не аудиторское заключение и не подтвержденная чистая прибыль.");
|
||||||
const missingOverviewFamilies = [];
|
const missingOverviewFamilies = [];
|
||||||
if (!businessOverviewTaxLine(overview)) {
|
if (!taxLine) {
|
||||||
missingOverviewFamilies.push("НДС/налоговая позиция без отдельного точного расчета");
|
missingOverviewFamilies.push("НДС/налоговая позиция без отдельного точного расчета");
|
||||||
}
|
}
|
||||||
if (!debtLine) {
|
if (!debtLine) {
|
||||||
|
|
@ -1040,7 +1143,22 @@ function buildCompactBusinessOverviewReply(entryPoint, draft) {
|
||||||
!/(?:все\s+доступное|все\s+время|all\s+time)/iu.test(period) &&
|
!/(?:все\s+доступное|все\s+время|all\s+time)/iu.test(period) &&
|
||||||
(incomingAmount || outgoingAmount || netAmount);
|
(incomingAmount || outgoingAmount || netAmount);
|
||||||
if (explicitPeriodRankingOverview) {
|
if (explicitPeriodRankingOverview) {
|
||||||
lines.push(`Коротко: ${organizationPrefix}${period} денежная картина подтверждена по найденным строкам 1С.`);
|
if (counterpartyLeaderSurfaceRequested) {
|
||||||
|
const incomingLeaderText = customerName && customerAmount
|
||||||
|
? topCustomerLooksFinancial
|
||||||
|
? `${customerName} — ${sentenceAmount(customerAmount) ?? customerAmount}; это банк/финансовый контур, не называю его обычной клиентской выручкой без назначения платежа${nonFinancialCustomer ? `; крупнейший небанковский входящий контрагент: ${nonFinancialCustomer}` : ""}`
|
||||||
|
: `${customerName} — ${sentenceAmount(customerAmount) ?? customerAmount}`
|
||||||
|
: "не распознан";
|
||||||
|
const outgoingLeaderText = topSupplier
|
||||||
|
? topSupplierLooksFinancial
|
||||||
|
? `${topSupplier}; это банк/финансовый контур, не называю его обычным поставщиком без назначения платежа/договора${nonFinancialSupplier ? `; крупнейший небанковский получатель исходящих денег: ${nonFinancialSupplier}` : ""}`
|
||||||
|
: topSupplier
|
||||||
|
: "не распознан";
|
||||||
|
lines.push(`Коротко: ${organizationPrefix}${period} больше всего занес ${incomingLeaderText}; больше всего ушло ${outgoingLeaderText}.`);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
lines.push(`Коротко: ${organizationPrefix}${period} денежная картина подтверждена по найденным строкам 1С.`);
|
||||||
|
}
|
||||||
lines.push(`Деньги: входящие ${incomingAmount ?? "0 руб."}, исходящие ${outgoingAmount ?? "0 руб."}, расчетное операционное нетто ${sentenceAmount(netAmount) ?? netAmount ?? "0 руб."}.`);
|
lines.push(`Деньги: входящие ${incomingAmount ?? "0 руб."}, исходящие ${outgoingAmount ?? "0 руб."}, расчетное операционное нетто ${sentenceAmount(netAmount) ?? netAmount ?? "0 руб."}.`);
|
||||||
if (customerName && customerAmount) {
|
if (customerName && customerAmount) {
|
||||||
lines.push(topCustomerLooksFinancial
|
lines.push(topCustomerLooksFinancial
|
||||||
|
|
@ -1093,7 +1211,7 @@ function buildCompactBusinessOverviewReply(entryPoint, draft) {
|
||||||
else if (incomingAmount || outgoingAmount || netAmount) {
|
else if (incomingAmount || outgoingAmount || netAmount) {
|
||||||
lines.push(`Коротко: ${organizationPrefix}${period} по подтвержденным строкам 1С получили ${incomingAmount ?? "0 руб."}; исходящие платежи/списания ${outgoingAmount ?? "0 руб."}; ${netDirection} ${sentenceAmount(netAmount) ?? netAmount ?? "0 руб"}${topCustomerLead}${topSupplierLead}${roleBoundaryLead}${separateSubjectLead}.`);
|
lines.push(`Коротко: ${organizationPrefix}${period} по подтвержденным строкам 1С получили ${incomingAmount ?? "0 руб."}; исходящие платежи/списания ${outgoingAmount ?? "0 руб."}; ${netDirection} ${sentenceAmount(netAmount) ?? netAmount ?? "0 руб"}${topCustomerLead}${topSupplierLead}${roleBoundaryLead}${separateSubjectLead}.`);
|
||||||
lines.push('Метод: "заработали" здесь считаю как операционный денежный показатель по 1С; это не чистая прибыль и не финрезультат.');
|
lines.push('Метод: "заработали" здесь считаю как операционный денежный показатель по 1С; это не чистая прибыль и не финрезультат.');
|
||||||
if (!directMoneyAnswer && customerName && customerAmount) {
|
if (shouldIncludeCounterpartyLeaders && customerName && customerAmount) {
|
||||||
lines.push(topCustomerLooksFinancial
|
lines.push(topCustomerLooksFinancial
|
||||||
? `Крупнейший входящий денежный источник в этом срезе: ${customerName} — ${sentenceAmount(customerAmount) ?? customerAmount}. По названию это банк/финансовая организация, поэтому без назначения платежа не называю это клиентской выручкой.${nonFinancialCustomer ? ` Крупнейший небанковский входящий контрагент: ${nonFinancialCustomer}.` : ""}`
|
? `Крупнейший входящий денежный источник в этом срезе: ${customerName} — ${sentenceAmount(customerAmount) ?? customerAmount}. По названию это банк/финансовая организация, поэтому без назначения платежа не называю это клиентской выручкой.${nonFinancialCustomer ? ` Крупнейший небанковский входящий контрагент: ${nonFinancialCustomer}.` : ""}`
|
||||||
: `Крупнейший подтвержденный источник входящих денег в этом срезе: ${customerName} — ${sentenceAmount(customerAmount) ?? customerAmount}.`);
|
: `Крупнейший подтвержденный источник входящих денег в этом срезе: ${customerName} — ${sentenceAmount(customerAmount) ?? customerAmount}.`);
|
||||||
|
|
@ -1109,17 +1227,17 @@ function buildCompactBusinessOverviewReply(entryPoint, draft) {
|
||||||
lines.push(previousCounterpartySummary?.line ??
|
lines.push(previousCounterpartySummary?.line ??
|
||||||
`Отдельно по контрагенту ${separateSubject}: этот итог не переносит суммы компании на контрагента. Можно утверждать только разделение контура; нельзя делать вывод о выручке, долге или прибыльности ${separateSubject} без отдельного контрагентского среза документов и движений.`);
|
`Отдельно по контрагенту ${separateSubject}: этот итог не переносит суммы компании на контрагента. Можно утверждать только разделение контура; нельзя делать вывод о выручке, долге или прибыльности ${separateSubject} без отдельного контрагентского среза документов и движений.`);
|
||||||
}
|
}
|
||||||
if (!directMoneyAnswer && topSupplier) {
|
if (shouldIncludeCounterpartyLeaders && topSupplier) {
|
||||||
lines.push(topSupplierLooksFinancial
|
lines.push(topSupplierLooksFinancial
|
||||||
? `Крупнейший получатель исходящих денег: ${topSupplier}. По названию это банк/финансовая организация, поэтому без назначения платежа/договора не считаю это обычным поставщиком.${nonFinancialSupplier ? ` Крупнейший небанковский получатель исходящих денег: ${nonFinancialSupplier}.` : ""}`
|
? `Крупнейший получатель исходящих денег: ${topSupplier}. По названию это банк/финансовая организация, поэтому без назначения платежа/договора не считаю это обычным поставщиком.${nonFinancialSupplier ? ` Крупнейший небанковский получатель исходящих денег: ${nonFinancialSupplier}.` : ""}`
|
||||||
: `Крупнейший подтвержденный получатель исходящих денег: ${topSupplier}.`);
|
: `Крупнейший подтвержденный получатель исходящих денег: ${topSupplier}.`);
|
||||||
}
|
}
|
||||||
if (!directMoneyAnswer && (topCustomer || topSupplier)) {
|
if (shouldIncludeCounterpartyLeaders && (topCustomer || topSupplier)) {
|
||||||
lines.push(topCustomerLooksFinancial || topSupplierLooksFinancial
|
lines.push(topCustomerLooksFinancial || topSupplierLooksFinancial
|
||||||
? "Важно по ролям: текущий денежный срез подтверждает источники и получателей денег, но банковские контрагенты требуют проверки назначения платежа/счетов и не доказывают роль клиента или поставщика."
|
? "Важно по ролям: текущий денежный срез подтверждает источники и получателей денег, но банковские контрагенты требуют проверки назначения платежа/счетов и не доказывают роль клиента или поставщика."
|
||||||
: "Важно по ролям: текущий денежный срез подтверждает денежные источники и получателей, но не доказывает, что это главный клиент или главный поставщик как бизнес-роль.");
|
: "Важно по ролям: текущий денежный срез подтверждает денежные источники и получателей, но не доказывает, что это главный клиент или главный поставщик как бизнес-роль.");
|
||||||
}
|
}
|
||||||
if (!directMoneyAnswer) {
|
if (shouldIncludeOverviewSurface) {
|
||||||
lines.push(`Что подтверждено: денежный срез по компании${organizationScope ? ` ${organizationScope}` : ""}${period ? ` ${period}` : ""}${topCustomer ? ", крупнейший источник входящих денег" : ""}${topSupplier ? ", крупнейший получатель исходящих денег" : ""}.`);
|
lines.push(`Что подтверждено: денежный срез по компании${organizationScope ? ` ${organizationScope}` : ""}${period ? ` ${period}` : ""}${topCustomer ? ", крупнейший источник входящих денег" : ""}${topSupplier ? ", крупнейший получатель исходящих денег" : ""}.`);
|
||||||
const taxLine = businessOverviewTaxLine(overview);
|
const taxLine = businessOverviewTaxLine(overview);
|
||||||
if (taxLine) {
|
if (taxLine) {
|
||||||
|
|
@ -1205,6 +1323,10 @@ function buildReplyText(entryPoint, status) {
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
const previousCounterpartyBoundaryReply = buildBoundarySummaryFromPreviousCounterpartyBundles(entryPoint);
|
||||||
|
if (previousCounterpartyBoundaryReply) {
|
||||||
|
return previousCounterpartyBoundaryReply;
|
||||||
|
}
|
||||||
const compactBidirectionalValueFlowReply = buildCompactBidirectionalValueFlowReply(entryPoint, draft);
|
const compactBidirectionalValueFlowReply = buildCompactBidirectionalValueFlowReply(entryPoint, draft);
|
||||||
if (compactBidirectionalValueFlowReply) {
|
if (compactBidirectionalValueFlowReply) {
|
||||||
return compactBidirectionalValueFlowReply;
|
return compactBidirectionalValueFlowReply;
|
||||||
|
|
|
||||||
|
|
@ -361,6 +361,11 @@ function hasExactDocumentListAddressReply(input, entryPoint) {
|
||||||
if (source !== "address_query_runtime_v1" && source !== "address_exact" && source !== "address_lane") {
|
if (source !== "address_query_runtime_v1" && source !== "address_exact" && source !== "address_lane") {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
if (hasValueFlowActionConflictWithDiscoveryTurnMeaning(input, entryPoint) ||
|
||||||
|
hasEvidenceLaneConflictWithDiscoveryTurnMeaning(input, entryPoint) ||
|
||||||
|
hasSemanticConflictWithDiscoveryTurnMeaning(input, entryPoint)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
const detectedIntent = toNonEmptyString(input.addressRuntimeMeta?.detected_intent);
|
const detectedIntent = toNonEmptyString(input.addressRuntimeMeta?.detected_intent);
|
||||||
const selectedRecipe = toNonEmptyString(input.addressRuntimeMeta?.selected_recipe);
|
const selectedRecipe = toNonEmptyString(input.addressRuntimeMeta?.selected_recipe);
|
||||||
const isDocumentIntent = detectedIntent === "list_documents_by_counterparty" || detectedIntent === "list_documents_by_contract";
|
const isDocumentIntent = detectedIntent === "list_documents_by_counterparty" || detectedIntent === "list_documents_by_contract";
|
||||||
|
|
|
||||||
|
|
@ -271,7 +271,9 @@ async function runAssistantMcpDiscoveryRuntimeBridge(input) {
|
||||||
});
|
});
|
||||||
const reasonCodes = uniqueStrings([...planner.reason_codes, ...pilot.reason_codes, ...answerDraft.reason_codes]);
|
const reasonCodes = uniqueStrings([...planner.reason_codes, ...pilot.reason_codes, ...answerDraft.reason_codes]);
|
||||||
pushReason(reasonCodes, `runtime_bridge_status_${bridgeStatus}`);
|
pushReason(reasonCodes, `runtime_bridge_status_${bridgeStatus}`);
|
||||||
pushReason(reasonCodes, "runtime_bridge_not_wired_to_hot_assistant_answer");
|
pushReason(reasonCodes, executionHandoff.can_use_guarded_response
|
||||||
|
? "runtime_bridge_wired_to_guarded_hot_assistant_answer"
|
||||||
|
: "runtime_bridge_not_wired_to_hot_assistant_answer");
|
||||||
pushReason(reasonCodes, `runtime_bridge_loop_state_${loopState.loop_status}`);
|
pushReason(reasonCodes, `runtime_bridge_loop_state_${loopState.loop_status}`);
|
||||||
pushReason(reasonCodes, "runtime_bridge_route_candidate_built");
|
pushReason(reasonCodes, "runtime_bridge_route_candidate_built");
|
||||||
pushReason(reasonCodes, `runtime_bridge_route_candidate_${routeCandidate.candidate_status}`);
|
pushReason(reasonCodes, `runtime_bridge_route_candidate_${routeCandidate.candidate_status}`);
|
||||||
|
|
|
||||||
|
|
@ -73,6 +73,7 @@ function isReferentialOrganizationPlaceholder(value) {
|
||||||
"этой компании",
|
"этой компании",
|
||||||
"этой компанией",
|
"этой компанией",
|
||||||
"эту компанию",
|
"эту компанию",
|
||||||
|
"в целом",
|
||||||
"наша организация",
|
"наша организация",
|
||||||
"нашей организации",
|
"нашей организации",
|
||||||
"нашей компанией"
|
"нашей компанией"
|
||||||
|
|
@ -188,7 +189,10 @@ function normalizeFollowupCounterpartyCandidate(value) {
|
||||||
if (!text || isInvalidEntityCandidate(text)) {
|
if (!text || isInvalidEntityCandidate(text)) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
return text;
|
const cleaned = text
|
||||||
|
.replace(/^(?:\u043f\u043e\s+)?\u043a\u043e\u043d\u0442\u0440\u0430\u0433\u0435\u043d\u0442(?:\u0443|\u0430|\u043e\u043c|\u0435|\u044b|\u0430\u043c|\u0430\u043c\u0438|\u0430\u0445)?\s+/iu, "")
|
||||||
|
.trim();
|
||||||
|
return cleaned && !isInvalidEntityCandidate(cleaned) ? cleaned : text;
|
||||||
}
|
}
|
||||||
function pushScopedEntityCandidate(target, value, groundedFollowupEntity) {
|
function pushScopedEntityCandidate(target, value, groundedFollowupEntity) {
|
||||||
const text = candidateValue(value);
|
const text = candidateValue(value);
|
||||||
|
|
@ -543,10 +547,11 @@ function collectFollowupDiscoverySeed(followupContext) {
|
||||||
const normalizedDiscoveryEntities = discoveryEntities
|
const normalizedDiscoveryEntities = discoveryEntities
|
||||||
.map((entity) => normalizeFollowupCounterpartyCandidate(entity))
|
.map((entity) => normalizeFollowupCounterpartyCandidate(entity))
|
||||||
.filter((entity) => Boolean(entity));
|
.filter((entity) => Boolean(entity));
|
||||||
|
const normalizedLoopMetadataScopeHint = normalizeFollowupCounterpartyCandidate(loopMetadataScopeHint);
|
||||||
const groundedDiscoveryCounterparty = ambiguityBlocksImplicitGrounding || metadataPilotCarriesScopeOnly
|
const groundedDiscoveryCounterparty = ambiguityBlocksImplicitGrounding || metadataPilotCarriesScopeOnly
|
||||||
? null
|
? null
|
||||||
: normalizedDiscoveryEntities[0] ?? normalizeFollowupCounterpartyCandidate(loopMetadataScopeHint);
|
: normalizedDiscoveryEntities[0] ?? normalizedLoopMetadataScopeHint;
|
||||||
const metadataScopeHint = loopMetadataScopeHint ??
|
const metadataScopeHint = normalizedLoopMetadataScopeHint ??
|
||||||
(loopSubjectResolutionOptional ? normalizedDiscoveryEntities[0] ?? null : null);
|
(loopSubjectResolutionOptional ? normalizedDiscoveryEntities[0] ?? null : null);
|
||||||
const previousFiltersCounterparty = normalizeFollowupCounterpartyCandidate(previousFilters?.counterparty);
|
const previousFiltersCounterparty = normalizeFollowupCounterpartyCandidate(previousFilters?.counterparty);
|
||||||
const rootFiltersCounterparty = normalizeFollowupCounterpartyCandidate(rootFilters?.counterparty);
|
const rootFiltersCounterparty = normalizeFollowupCounterpartyCandidate(rootFilters?.counterparty);
|
||||||
|
|
@ -561,8 +566,10 @@ function collectFollowupDiscoverySeed(followupContext) {
|
||||||
(toNonEmptyString(followupContext?.previous_anchor_type) === "organization"
|
(toNonEmptyString(followupContext?.previous_anchor_type) === "organization"
|
||||||
? toNonEmptyString(followupContext?.previous_anchor_value)
|
? toNonEmptyString(followupContext?.previous_anchor_value)
|
||||||
: null);
|
: null);
|
||||||
const dateScope = collectDateScopeFromFilters(previousFilters) ??
|
const loopProvidedAllTimeScope = loopProvidedAxes.includes("all_time_scope");
|
||||||
collectDateScopeFromFilters(rootFilters);
|
const dateScope = loopProvidedAllTimeScope
|
||||||
|
? "all_time_scope"
|
||||||
|
: collectDateScopeFromFilters(previousFilters) ?? collectDateScopeFromFilters(rootFilters);
|
||||||
return {
|
return {
|
||||||
pilotScope: effectivePilotScope,
|
pilotScope: effectivePilotScope,
|
||||||
domain: mapped.domain,
|
domain: mapped.domain,
|
||||||
|
|
@ -713,7 +720,7 @@ function hasOrganizationLevelSupplierQualityOverviewSignal(text) {
|
||||||
return hasSupplierScopeCue && hasSupplierQualityCue && hasCompanyScopeCue;
|
return hasSupplierScopeCue && hasSupplierQualityCue && hasCompanyScopeCue;
|
||||||
}
|
}
|
||||||
function hasCrossScopeExecutiveSummarySignal(text) {
|
function hasCrossScopeExecutiveSummarySignal(text) {
|
||||||
return (/(?:\u0441\u043e\u0431\u0435\u0440\p{L}*\s+(?:\u043a\u043e\u0440\u043e\u0442\u043a\p{L}*\s+)?\u0438\u0442\u043e\u0433|\u044d\u043a\u0437\u0435\u043a\u044c\u044e\u0442\u0438\u0432\p{L}*\s+\u0441\u0430\u043c\u043c\u0430\u0440\u0438|executive\s+summary|final\s+summary)/iu.test(text) &&
|
return (/(?:\u0441\u043e\u0431\u0435\u0440\p{L}*\s+(?:\u043a\u043e\u0440\u043e\u0442\u043a\p{L}*\s+)?\u0438\u0442\u043e\u0433|(?:\u043a\u043e\u0440\u043e\u0442\u043a\p{L}*\s+)?\u0441\u0440\u0430\u0432\u043d\p{L}*|\u044d\u043a\u0437\u0435\u043a\u044c\u044e\u0442\u0438\u0432\p{L}*\s+\u0441\u0430\u043c\u043c\u0430\u0440\u0438|executive\s+summary|final\s+summary|brief(?:ly)?\s+compare)/iu.test(text) &&
|
||||||
/(?:\u0447\u0442\u043e\s+(?:\u043c\u044b\s+)?\u043f\u043e\u0434\u0442\u0432\u0435\u0440\p{L}*|\u043f\u043e\s+\u043a\u043e\u043c\u043f\u0430\u043d\p{L}*|\u043f\u043e\s+\u043e\u0440\u0433\u0430\u043d\u0438\u0437\u0430\u0446\p{L}*|confirmed|company|organization)/iu.test(text) &&
|
/(?:\u0447\u0442\u043e\s+(?:\u043c\u044b\s+)?\u043f\u043e\u0434\u0442\u0432\u0435\u0440\p{L}*|\u043f\u043e\s+\u043a\u043e\u043c\u043f\u0430\u043d\p{L}*|\u043f\u043e\s+\u043e\u0440\u0433\u0430\u043d\u0438\u0437\u0430\u0446\p{L}*|confirmed|company|organization)/iu.test(text) &&
|
||||||
/(?:\u043e\u0442\u0434\u0435\u043b\u044c\u043d\p{L}*\s+\u043f\u043e|\u043f\u043e\s+\u043a\u043e\u043d\u0442\u0440\u0430\u0433\u0435\u043d\u0442\p{L}*|\u043a\u043e\u043d\u0442\u0440\u0430\u0433\u0435\u043d\u0442\p{L}*|\u0433\u0440\u0443\u043f\u043f\p{L}*\s+\u0441\u0432\u043a|\u0441\u0432\u043a|counterpart(?:y|ies)?)/iu.test(text) &&
|
/(?:\u043e\u0442\u0434\u0435\u043b\u044c\u043d\p{L}*\s+\u043f\u043e|\u043f\u043e\s+\u043a\u043e\u043d\u0442\u0440\u0430\u0433\u0435\u043d\u0442\p{L}*|\u043a\u043e\u043d\u0442\u0440\u0430\u0433\u0435\u043d\u0442\p{L}*|\u0433\u0440\u0443\u043f\u043f\p{L}*\s+\u0441\u0432\u043a|\u0441\u0432\u043a|counterpart(?:y|ies)?)/iu.test(text) &&
|
||||||
/(?:\u0447\u0442\u043e\s+\u043c\u043e\u0436\u043d\p{L}*|\u0447\u0442\u043e\s+\u043d\u0435\u043b\u044c\u0437\p{L}*|\u0432\u044b\u0432\u043e\u0434\p{L}*|allowed|forbidden|cannot|can\s+say)/iu.test(text));
|
/(?:\u0447\u0442\u043e\s+\u043c\u043e\u0436\u043d\p{L}*|\u0447\u0442\u043e\s+\u043d\u0435\u043b\u044c\u0437\p{L}*|\u0432\u044b\u0432\u043e\u0434\p{L}*|allowed|forbidden|cannot|can\s+say)/iu.test(text));
|
||||||
|
|
@ -723,6 +730,27 @@ function hasPlainBusinessOverviewSignal(text) {
|
||||||
const hasCompanyOrOperatingScopeCue = /(?:\u043e\u043e\u043e|\u0438\u043f|\u0430\u043e|\u043f\u0430\u043e|\u043a\u043e\u043c\u043f\u0430\u043d|\u043e\u0440\u0433\u0430\u043d\u0438\u0437\u0430\u0446|\u0431\u0438\u0437\u043d\u0435\u0441|\u0443\s+\u043d\u0430\u0441|\u043d\u0430\u0448\p{L}*|(?:19|20)\d{2}|company|organization|business)/iu.test(text);
|
const hasCompanyOrOperatingScopeCue = /(?:\u043e\u043e\u043e|\u0438\u043f|\u0430\u043e|\u043f\u0430\u043e|\u043a\u043e\u043c\u043f\u0430\u043d|\u043e\u0440\u0433\u0430\u043d\u0438\u0437\u0430\u0446|\u0431\u0438\u0437\u043d\u0435\u0441|\u0443\s+\u043d\u0430\u0441|\u043d\u0430\u0448\p{L}*|(?:19|20)\d{2}|company|organization|business)/iu.test(text);
|
||||||
return hasPlainOverviewCue && hasCompanyOrOperatingScopeCue;
|
return hasPlainOverviewCue && hasCompanyOrOperatingScopeCue;
|
||||||
}
|
}
|
||||||
|
function countBroadBusinessOverviewAxes(text) {
|
||||||
|
const axisPatterns = [
|
||||||
|
/(?:\u0434\u0435\u043d\p{L}*|\u043e\u0431\u043e\u0440\u043e\u0442|\u0432\u044b\u0440\u0443\u0447\p{L}*|\u043f\u043e\u0441\u0442\u0443\u043f\p{L}*|\u043f\u043b\u0430\u0442\p{L}*|money|cash|revenue|turnover)/iu,
|
||||||
|
/(?:\u043d\u0434\u0441|vat)/iu,
|
||||||
|
/(?:\u0434\u043e\u043b\p{L}*|\u0434\u0435\u0431\u0438\u0442\u043e\u0440\p{L}*|\u043a\u0440\u0435\u0434\u0438\u0442\u043e\u0440\p{L}*|receivable|payable|debt)/iu,
|
||||||
|
/(?:\u0441\u043a\u043b\u0430\u0434|\u043e\u0441\u0442\u0430\u0442|\u0437\u0430\u043f\u0430\u0441|\u0442\u043e\u0432\u0430\u0440|warehouse|stock|inventory)/iu,
|
||||||
|
/(?:\u043a\u043b\u0438\u0435\u043d\u0442|\u043f\u043e\u043a\u0443\u043f\u0430\u0442\u0435\u043b|\u0437\u0430\u043a\u0430\u0437\u0447\u0438\u043a|customer|client|buyer)/iu,
|
||||||
|
/(?:\u043f\u043e\u0441\u0442\u0430\u0432\u0449\u0438\u043a|\u0432\u0435\u043d\u0434\u043e\u0440|\u0437\u0430\u043a\u0443\u043f|supplier|vendor|procurement)/iu,
|
||||||
|
/(?:\u0433\u0434\u0435[\s\S]{0,80}(?:\u043d\u0435\u043b\u044c\u0437\u044f|\u043d\u0435\s+\u0434\u0435\u043b\u0430\p{L}*|\u043d\u0435\s+\u0445\u0432\u0430\p{L}*)|\u0447\u0442\u043e[\s\S]{0,80}(?:\u043d\u0435\u043b\u044c\u0437\u044f|\u043d\u0435\s+\u0445\u0432\u0430\p{L}*)|cannot|unknown|missing|limitation)/iu
|
||||||
|
];
|
||||||
|
return axisPatterns.reduce((count, pattern) => count + (pattern.test(text) ? 1 : 0), 0);
|
||||||
|
}
|
||||||
|
function hasBroadBusinessOverviewSurfaceSignal(text) {
|
||||||
|
const normalized = compactLower(text);
|
||||||
|
if (!normalized) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const hasBroadCue = /(?:\u043f\u043e[-\s]*\u0447\u0435\u043b\u043e\u0432\u0435\u0447\u0435\u0441\u043a\p{L}*|\u0447\u0442\u043e\s+\u043f\u043e\s+\u0431\u0438\u0437\u043d\u0435\u0441\u0443\s+\u0432\u0438\u0434\u043d\p{L}*|\u043f\u043e\u0441\u043c\u043e\u0442\p{L}*[\s\S]{0,100}(?:\u0431\u0438\u0437\u043d\u0435\u0441|\u0434\u0435\u044f\u0442\u0435\u043b\p{L}*)|\u0431\u0438\u0437\u043d\u0435\u0441[\s\S]{0,80}(?:\u0432\u0438\u0434\u043d\p{L}*|\u0432\u044b\u0432\u043e\u0434|\u0441\u0440\u0435\u0437)|human\s+readable\s+business\s+view)/iu.test(normalized);
|
||||||
|
const hasCompanyScope = /(?:\u043e\u043e\u043e|\u0438\u043f|\u0430\u043e|\u043f\u0430\u043e|\u0437\u0430\u043e|\u043e\u0430\u043e|\u043a\u043e\u043c\u043f\u0430\u043d|\u043e\u0440\u0433\u0430\u043d\u0438\u0437\u0430\u0446|\u0431\u0438\u0437\u043d\u0435\u0441|\u0432\s+1\s?\u0441|1\s?c|company|organization|business|(?:19|20)\d{2})/iu.test(normalized);
|
||||||
|
return hasBroadCue && hasCompanyScope && countBroadBusinessOverviewAxes(normalized) >= 3;
|
||||||
|
}
|
||||||
function hasBusinessOverviewSignal(text) {
|
function hasBusinessOverviewSignal(text) {
|
||||||
if (hasCrossScopeExecutiveSummarySignal(text) ||
|
if (hasCrossScopeExecutiveSummarySignal(text) ||
|
||||||
hasOrganizationLevelEarningsOverviewSignal(text) ||
|
hasOrganizationLevelEarningsOverviewSignal(text) ||
|
||||||
|
|
@ -730,6 +758,7 @@ function hasBusinessOverviewSignal(text) {
|
||||||
hasOrganizationLevelDebtDueDateOverviewSignal(text) ||
|
hasOrganizationLevelDebtDueDateOverviewSignal(text) ||
|
||||||
hasOrganizationLevelInventoryReserveLiquidationOverviewSignal(text) ||
|
hasOrganizationLevelInventoryReserveLiquidationOverviewSignal(text) ||
|
||||||
hasPlainBusinessOverviewSignal(text) ||
|
hasPlainBusinessOverviewSignal(text) ||
|
||||||
|
hasBroadBusinessOverviewSurfaceSignal(text) ||
|
||||||
hasOrganizationLevelSupplierQualityOverviewSignal(text)) {
|
hasOrganizationLevelSupplierQualityOverviewSignal(text)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
@ -774,6 +803,12 @@ function hasBusinessOverviewSeparateCounterpartySignal(text) {
|
||||||
return (/(?:\u043e\u0442\u0434\u0435\u043b\u044c\u043d\p{L}*\s+\u043f\u043e|\u043f\u043e\s+\u043a\u043e\u043d\u0442\u0440\u0430\u0433\u0435\u043d\u0442\p{L}*|\u043a\u043e\u043d\u0442\u0440\u0430\u0433\u0435\u043d\u0442\p{L}*|counterpart(?:y|ies)?)/iu.test(text) &&
|
return (/(?:\u043e\u0442\u0434\u0435\u043b\u044c\u043d\p{L}*\s+\u043f\u043e|\u043f\u043e\s+\u043a\u043e\u043d\u0442\u0440\u0430\u0433\u0435\u043d\u0442\p{L}*|\u043a\u043e\u043d\u0442\u0440\u0430\u0433\u0435\u043d\u0442\p{L}*|counterpart(?:y|ies)?)/iu.test(text) &&
|
||||||
/(?:\u043a\u043e\u043c\u043f\u0430\u043d\p{L}*|\u043e\u0440\u0433\u0430\u043d\u0438\u0437\u0430\u0446\p{L}*|company|organization|\u0438\u0442\u043e\u0433|summary|\u0432\u044b\u0432\u043e\u0434\p{L}*)/iu.test(text));
|
/(?:\u043a\u043e\u043c\u043f\u0430\u043d\p{L}*|\u043e\u0440\u0433\u0430\u043d\u0438\u0437\u0430\u0446\p{L}*|company|organization|\u0438\u0442\u043e\u0433|summary|\u0432\u044b\u0432\u043e\u0434\p{L}*)/iu.test(text));
|
||||||
}
|
}
|
||||||
|
function isGenericSelectedCounterpartyReference(value) {
|
||||||
|
if (!value) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return /^(?:(?:\u0432\u044b\u0431\u0440\u0430\u043d\p{L}*|\u044d\u0442\p{L}*|\u0434\u0430\u043d\p{L}*|\u0442\u0435\u043a\u0443\u0449\p{L}*)\s+)?\u043a\u043e\u043d\u0442\u0440\u0430\u0433\u0435\u043d\u0442\p{L}*$|^(?:selected|chosen|current|this)\s+counterpart(?:y|ies)?$/iu.test(value);
|
||||||
|
}
|
||||||
function businessOverviewSeparateCounterpartyCandidateFromText(text) {
|
function businessOverviewSeparateCounterpartyCandidateFromText(text) {
|
||||||
const source = (0, addressTextRepair_1.repairAddressMojibakeText)(String(text ?? ""));
|
const source = (0, addressTextRepair_1.repairAddressMojibakeText)(String(text ?? ""));
|
||||||
const patterns = [
|
const patterns = [
|
||||||
|
|
@ -782,7 +817,7 @@ function businessOverviewSeparateCounterpartyCandidateFromText(text) {
|
||||||
];
|
];
|
||||||
for (const pattern of patterns) {
|
for (const pattern of patterns) {
|
||||||
const candidate = normalizeFollowupCounterpartyCandidate(source.match(pattern)?.[1]);
|
const candidate = normalizeFollowupCounterpartyCandidate(source.match(pattern)?.[1]);
|
||||||
if (candidate && !isInvalidEntityCandidate(candidate)) {
|
if (candidate && !isInvalidEntityCandidate(candidate) && !isGenericSelectedCounterpartyReference(candidate)) {
|
||||||
return candidate;
|
return candidate;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -868,6 +903,9 @@ function normalizeLooseOrganizationAlias(value) {
|
||||||
if (hasYearOnlyTimeTail) {
|
if (hasYearOnlyTimeTail) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
if (new Set(["в целом", "компания в целом", "организация в целом"]).has(comparable)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
if (/^(?:\u0438|\u0432|\u0432\u043e|\u0437\u0430|\u043d\u0430|\u043f\u043e|\u043a\u0442\u043e|\u0447\u0442\u043e|\u043a\u0430\u043a(?:\u043e\u0439|\u0430\u044f|\u0438\u0435)?|\u0433\u043b\u0430\u0432\u043d\p{L}*)\b/iu.test(comparable)) {
|
if (/^(?:\u0438|\u0432|\u0432\u043e|\u0437\u0430|\u043d\u0430|\u043f\u043e|\u043a\u0442\u043e|\u0447\u0442\u043e|\u043a\u0430\u043a(?:\u043e\u0439|\u0430\u044f|\u0438\u0435)?|\u0433\u043b\u0430\u0432\u043d\p{L}*)\b/iu.test(comparable)) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
@ -1370,10 +1408,18 @@ function buildAssistantMcpDiscoveryTurnInput(input) {
|
||||||
: "broad_business_evaluation";
|
: "broad_business_evaluation";
|
||||||
const businessOverviewSignal = !businessOverviewCounterpartyValueFlowPivot &&
|
const businessOverviewSignal = !businessOverviewCounterpartyValueFlowPivot &&
|
||||||
(rawBusinessOverviewSignal || seededBusinessOverviewSignal);
|
(rawBusinessOverviewSignal || seededBusinessOverviewSignal);
|
||||||
|
const organizationClarificationBusinessOverviewLoop = Boolean(followupSeed.loopStatus === "awaiting_clarification" &&
|
||||||
|
followupSeed.loopSelectedChainId === "business_overview" &&
|
||||||
|
followupSeed.loopPendingAxes.includes("organization") &&
|
||||||
|
currentTurnOrganizationScope &&
|
||||||
|
!rawLifecycleSignal &&
|
||||||
|
!rawMetadataSignal);
|
||||||
const businessOverviewSeparateCounterpartySignal = Boolean(businessOverviewSignal && hasBusinessOverviewSeparateCounterpartySignal(rawText));
|
const businessOverviewSeparateCounterpartySignal = Boolean(businessOverviewSignal && hasBusinessOverviewSeparateCounterpartySignal(rawText));
|
||||||
const businessOverviewSeparateCounterpartyCandidate = businessOverviewSeparateCounterpartySignal
|
const businessOverviewSeparateCounterpartyCandidate = businessOverviewSeparateCounterpartySignal
|
||||||
? businessOverviewSeparateCounterpartyCandidateFromText(rawText)
|
? businessOverviewSeparateCounterpartyCandidateFromText(rawText)
|
||||||
: null;
|
: organizationClarificationBusinessOverviewLoop
|
||||||
|
? followupSeed.counterparty ?? followupSeed.discoveryEntity ?? followupSeed.metadataScopeHint
|
||||||
|
: null;
|
||||||
const explicitIntentCandidate = toNonEmptyString(assistantTurnMeaning?.explicit_intent_candidate);
|
const explicitIntentCandidate = toNonEmptyString(assistantTurnMeaning?.explicit_intent_candidate);
|
||||||
const currentTurnDocumentLaneSignal = rawAction === "list_documents";
|
const currentTurnDocumentLaneSignal = rawAction === "list_documents";
|
||||||
const currentTurnMovementLaneSignal = rawAction === "list_movements";
|
const currentTurnMovementLaneSignal = rawAction === "list_movements";
|
||||||
|
|
@ -1397,6 +1443,7 @@ function buildAssistantMcpDiscoveryTurnInput(input) {
|
||||||
sameScopedName(followupSeed.counterparty, currentTurnOrganizationScope)));
|
sameScopedName(followupSeed.counterparty, currentTurnOrganizationScope)));
|
||||||
const businessOverviewSuppressesFollowupCounterparty = Boolean(businessOverviewSignal &&
|
const businessOverviewSuppressesFollowupCounterparty = Boolean(businessOverviewSignal &&
|
||||||
!businessOverviewSeparateCounterpartySignal &&
|
!businessOverviewSeparateCounterpartySignal &&
|
||||||
|
!organizationClarificationBusinessOverviewLoop &&
|
||||||
(rawBusinessOverviewSignal ||
|
(rawBusinessOverviewSignal ||
|
||||||
businessOverviewContinuationSignal ||
|
businessOverviewContinuationSignal ||
|
||||||
broadBusinessEvaluationUnsupported ||
|
broadBusinessEvaluationUnsupported ||
|
||||||
|
|
@ -1831,7 +1878,8 @@ function buildAssistantMcpDiscoveryTurnInput(input) {
|
||||||
}
|
}
|
||||||
pushScopedEntityCandidate(entityCandidates, rawEntityCandidate, groundedFollowupEntity);
|
pushScopedEntityCandidate(entityCandidates, rawEntityCandidate, groundedFollowupEntity);
|
||||||
}
|
}
|
||||||
const businessOverviewSeparateCounterpartyDisplayCandidate = businessOverviewSeparateCounterpartySignal
|
const shouldPreserveBusinessOverviewSeparateCounterparty = businessOverviewSeparateCounterpartySignal || organizationClarificationBusinessOverviewLoop;
|
||||||
|
const businessOverviewSeparateCounterpartyDisplayCandidate = shouldPreserveBusinessOverviewSeparateCounterparty
|
||||||
? preferredScopedDisplayName(businessOverviewSeparateCounterpartyCandidate, [
|
? preferredScopedDisplayName(businessOverviewSeparateCounterpartyCandidate, [
|
||||||
groundedFollowupEntity,
|
groundedFollowupEntity,
|
||||||
effectiveFollowupCounterparty,
|
effectiveFollowupCounterparty,
|
||||||
|
|
@ -1840,7 +1888,21 @@ function buildAssistantMcpDiscoveryTurnInput(input) {
|
||||||
rawScopedEntityCandidate,
|
rawScopedEntityCandidate,
|
||||||
rawEntityCandidate,
|
rawEntityCandidate,
|
||||||
...entityCandidates
|
...entityCandidates
|
||||||
])
|
]) ??
|
||||||
|
preferredScopedDisplayName(groundedFollowupEntity ??
|
||||||
|
effectiveFollowupCounterparty ??
|
||||||
|
followupSeed.discoveryEntity ??
|
||||||
|
normalizedPredecomposeCounterparty ??
|
||||||
|
rawScopedEntityCandidate ??
|
||||||
|
rawEntityCandidate, [
|
||||||
|
groundedFollowupEntity,
|
||||||
|
effectiveFollowupCounterparty,
|
||||||
|
followupSeed.discoveryEntity,
|
||||||
|
normalizedPredecomposeCounterparty,
|
||||||
|
rawScopedEntityCandidate,
|
||||||
|
rawEntityCandidate,
|
||||||
|
...entityCandidates
|
||||||
|
])
|
||||||
: null;
|
: null;
|
||||||
const businessOverviewSeparateEntityCandidates = businessOverviewSeparateCounterpartyDisplayCandidate
|
const businessOverviewSeparateEntityCandidates = businessOverviewSeparateCounterpartyDisplayCandidate
|
||||||
? [businessOverviewSeparateCounterpartyDisplayCandidate]
|
? [businessOverviewSeparateCounterpartyDisplayCandidate]
|
||||||
|
|
@ -1936,6 +1998,7 @@ function buildAssistantMcpDiscoveryTurnInput(input) {
|
||||||
: predecomposeDateScope;
|
: predecomposeDateScope;
|
||||||
const normalizedAssistantTurnMeaningDateScope = rawEntitySearchOverridesStaleScope ||
|
const normalizedAssistantTurnMeaningDateScope = rawEntitySearchOverridesStaleScope ||
|
||||||
suppressNegatedTaxOnlyDateScope ||
|
suppressNegatedTaxOnlyDateScope ||
|
||||||
|
(organizationClarificationBusinessOverviewLoop && !currentTurnCarriesExplicitPeriod) ||
|
||||||
(suppressImplicitCurrentDateScope && isImplicitCurrentDateScope(assistantTurnMeaningDateScope))
|
(suppressImplicitCurrentDateScope && isImplicitCurrentDateScope(assistantTurnMeaningDateScope))
|
||||||
? null
|
? null
|
||||||
: assistantTurnMeaningDateScope;
|
: assistantTurnMeaningDateScope;
|
||||||
|
|
@ -1950,7 +2013,8 @@ function buildAssistantMcpDiscoveryTurnInput(input) {
|
||||||
/^\d{4}$/.test(rawDateScope) &&
|
/^\d{4}$/.test(rawDateScope) &&
|
||||||
normalizedPredecomposeDateScope &&
|
normalizedPredecomposeDateScope &&
|
||||||
normalizedPredecomposeDateScope.startsWith(`${rawDateScope}-`));
|
normalizedPredecomposeDateScope.startsWith(`${rawDateScope}-`));
|
||||||
const explicitDateScope = rawAllTimeScopeSignal
|
const followupAllTimeScopeApplied = normalizedFollowupDateScope === "all_time_scope";
|
||||||
|
const explicitDateScope = rawAllTimeScopeSignal || followupAllTimeScopeApplied
|
||||||
? null
|
? null
|
||||||
: normalizedAssistantTurnMeaningDateScope ??
|
: normalizedAssistantTurnMeaningDateScope ??
|
||||||
(businessOverviewRawYearOverridesPredecomposeAsOf ? rawDateScope : normalizedPredecomposeDateScope) ??
|
(businessOverviewRawYearOverridesPredecomposeAsOf ? rawDateScope : normalizedPredecomposeDateScope) ??
|
||||||
|
|
@ -1960,7 +2024,8 @@ function buildAssistantMcpDiscoveryTurnInput(input) {
|
||||||
!normalizedAssistantTurnMeaningDateScope &&
|
!normalizedAssistantTurnMeaningDateScope &&
|
||||||
!normalizedPredecomposeDateScope &&
|
!normalizedPredecomposeDateScope &&
|
||||||
!rawDateScope &&
|
!rawDateScope &&
|
||||||
normalizedFollowupDateScope);
|
normalizedFollowupDateScope &&
|
||||||
|
normalizedFollowupDateScope !== "all_time_scope");
|
||||||
const clarificationLoopSeedApplied = Boolean(followupSeed.loopStatus === "awaiting_clarification" && followupSeed.loopSelectedChainId);
|
const clarificationLoopSeedApplied = Boolean(followupSeed.loopStatus === "awaiting_clarification" && followupSeed.loopSelectedChainId);
|
||||||
const turnMeaning = {
|
const turnMeaning = {
|
||||||
raw_message: repairedUserText ?? rawUserText ?? null,
|
raw_message: repairedUserText ?? rawUserText ?? null,
|
||||||
|
|
@ -2007,7 +2072,7 @@ function buildAssistantMcpDiscoveryTurnInput(input) {
|
||||||
seeded_ranking_need: valueFlowSignal && followupSeed.rankingNeed && !rawEntitySearchOverridesStaleScope
|
seeded_ranking_need: valueFlowSignal && followupSeed.rankingNeed && !rawEntitySearchOverridesStaleScope
|
||||||
? followupSeed.rankingNeed
|
? followupSeed.rankingNeed
|
||||||
: undefined,
|
: undefined,
|
||||||
explicit_entity_candidates: businessOverviewSignal ? [] : entityCandidates,
|
explicit_entity_candidates: businessOverviewSignal || shouldPreserveBusinessOverviewSeparateCounterparty ? [] : entityCandidates,
|
||||||
business_overview_separate_entity_candidates: businessOverviewSeparateEntityCandidates,
|
business_overview_separate_entity_candidates: businessOverviewSeparateEntityCandidates,
|
||||||
previous_counterparty_value_flow_bundle: businessOverviewSignal && followupSeed.previousBidirectionalValueFlow
|
previous_counterparty_value_flow_bundle: businessOverviewSignal && followupSeed.previousBidirectionalValueFlow
|
||||||
? followupSeed.previousBidirectionalValueFlow
|
? followupSeed.previousBidirectionalValueFlow
|
||||||
|
|
@ -2222,6 +2287,9 @@ function buildAssistantMcpDiscoveryTurnInput(input) {
|
||||||
if (rawAllTimeScopeSignal) {
|
if (rawAllTimeScopeSignal) {
|
||||||
pushReason(reasonCodes, "mcp_discovery_all_time_scope_signal_detected");
|
pushReason(reasonCodes, "mcp_discovery_all_time_scope_signal_detected");
|
||||||
}
|
}
|
||||||
|
if (followupAllTimeScopeApplied) {
|
||||||
|
pushReason(reasonCodes, "mcp_discovery_all_time_scope_from_followup_context");
|
||||||
|
}
|
||||||
if (suppressNegatedTaxOnlyDateScope) {
|
if (suppressNegatedTaxOnlyDateScope) {
|
||||||
pushReason(reasonCodes, "mcp_discovery_negated_tax_period_scope_suppressed");
|
pushReason(reasonCodes, "mcp_discovery_negated_tax_period_scope_suppressed");
|
||||||
}
|
}
|
||||||
|
|
@ -2324,7 +2392,7 @@ function buildAssistantMcpDiscoveryTurnInput(input) {
|
||||||
if (businessOverviewSuppressesFollowupCounterparty) {
|
if (businessOverviewSuppressesFollowupCounterparty) {
|
||||||
pushReason(reasonCodes, "mcp_discovery_business_overview_suppressed_stale_counterparty");
|
pushReason(reasonCodes, "mcp_discovery_business_overview_suppressed_stale_counterparty");
|
||||||
}
|
}
|
||||||
if (businessOverviewSeparateCounterpartySignal) {
|
if (shouldPreserveBusinessOverviewSeparateCounterparty) {
|
||||||
pushReason(reasonCodes, "mcp_discovery_business_overview_preserved_explicit_counterparty_summary_scope");
|
pushReason(reasonCodes, "mcp_discovery_business_overview_preserved_explicit_counterparty_summary_scope");
|
||||||
}
|
}
|
||||||
if (businessOverviewSeparateCounterpartyCandidate) {
|
if (businessOverviewSeparateCounterpartyCandidate) {
|
||||||
|
|
@ -2346,7 +2414,7 @@ function buildAssistantMcpDiscoveryTurnInput(input) {
|
||||||
normalizedPredecomposeCounterparty) {
|
normalizedPredecomposeCounterparty) {
|
||||||
pushReason(reasonCodes, "mcp_discovery_counterparty_from_predecompose");
|
pushReason(reasonCodes, "mcp_discovery_counterparty_from_predecompose");
|
||||||
}
|
}
|
||||||
if (rawScopedEntityCandidate && !normalizedPredecomposeCounterparty) {
|
if (rawScopedEntityCandidate && !normalizedPredecomposeCounterparty && !businessOverviewSignal) {
|
||||||
pushReason(reasonCodes, "mcp_discovery_counterparty_from_raw_scope");
|
pushReason(reasonCodes, "mcp_discovery_counterparty_from_raw_scope");
|
||||||
}
|
}
|
||||||
if (effectiveFollowupCounterparty &&
|
if (effectiveFollowupCounterparty &&
|
||||||
|
|
|
||||||
|
|
@ -75,6 +75,19 @@ function createAssistantTransitionPolicy(deps) {
|
||||||
}
|
}
|
||||||
return /(?:документ|счет|счет-фактур|накладн|акт|реализац|document|invoice|receipt)/iu.test(normalized);
|
return /(?:документ|счет|счет-фактур|накладн|акт|реализац|document|invoice|receipt)/iu.test(normalized);
|
||||||
}
|
}
|
||||||
|
function hasSelectedCounterpartyDocumentFollowupSignal(userMessage, alternateMessage = null) {
|
||||||
|
return [userMessage, alternateMessage]
|
||||||
|
.filter((value) => deps.toNonEmptyString(value))
|
||||||
|
.map((value) => normalizeFollowupText(value).replace(/С‘/g, "Рµ"))
|
||||||
|
.some((normalized) => {
|
||||||
|
if (!normalized) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const hasDocumentCue = /(?:\u0434\u043e\u043a\p{L}*|\u0441\u0447\p{L}*|\u043d\u0430\u043a\u043b\u0430\u0434\p{L}*|\u0430\u043a\u0442|document|docs?|invoice|receipt)/iu.test(normalized) || hasReadableDocumentsPivotCue(normalized);
|
||||||
|
const hasSelectedCounterpartyCue = /(?:\u043f\u043e\s+\u043d(?:\u0435\u043c\u0443|\u0435\u0439)|\u043f\u043e\s+\u044d\u0442(?:\u043e\u043c\u0443|\u043e\u0439)|\u0442\u0435\u043a\u0443\u0449\p{L}*\s+\u043e\u0431\u044a\u0435\u043a\p{L}*|\u0432\u044b\u0431\u0440\u0430\u043d\p{L}*\s+(?:\u043a\u043e\u043d\u0442\u0440\u0430\u0433\u0435\u043d\u0442|\u043e\u0431\u044a\u0435\u043a\u0442)|selected\s+(?:counterparty|object)|current\s+object)/iu.test(normalized);
|
||||||
|
return hasDocumentCue && hasSelectedCounterpartyCue;
|
||||||
|
});
|
||||||
|
}
|
||||||
function selectSuggestedIntentByPivotCue(suggestedIntents, userMessage, alternateMessage = null) {
|
function selectSuggestedIntentByPivotCue(suggestedIntents, userMessage, alternateMessage = null) {
|
||||||
if (!Array.isArray(suggestedIntents) || suggestedIntents.length === 0) {
|
if (!Array.isArray(suggestedIntents) || suggestedIntents.length === 0) {
|
||||||
return null;
|
return null;
|
||||||
|
|
@ -298,32 +311,115 @@ function createAssistantTransitionPolicy(deps) {
|
||||||
}
|
}
|
||||||
return flow;
|
return flow;
|
||||||
}
|
}
|
||||||
|
function readMcpDiscoveryPreviousCounterpartyValueFlowBundle(debug) {
|
||||||
|
const entryPoint = debug?.assistant_mcp_discovery_entry_point_v1;
|
||||||
|
const bundle = entryPoint?.turn_input?.turn_meaning_ref?.previous_counterparty_value_flow_bundle;
|
||||||
|
if (!bundle || typeof bundle !== "object" || Array.isArray(bundle)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return bundle;
|
||||||
|
}
|
||||||
|
function readMcpDiscoveryPreviousCounterpartyDocumentBundle(debug) {
|
||||||
|
const entryPoint = debug?.assistant_mcp_discovery_entry_point_v1;
|
||||||
|
const bundle = entryPoint?.turn_input?.turn_meaning_ref?.previous_counterparty_document_bundle;
|
||||||
|
if (!bundle || typeof bundle !== "object" || Array.isArray(bundle)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return bundle;
|
||||||
|
}
|
||||||
function readCounterpartyDocumentSummaryFromItem(item) {
|
function readCounterpartyDocumentSummaryFromItem(item) {
|
||||||
const text = deps.toNonEmptyString(item?.text);
|
const text = deps.toNonEmptyString(item?.text);
|
||||||
if (!text) {
|
if (!text) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
const firstLine = text.split(/\r?\n/).map((line) => line.trim()).find(Boolean) ?? "";
|
const firstLine = text.split(/\r?\n/).map((line) => line.trim()).find(Boolean) ?? "";
|
||||||
const match = firstLine.match(/Контрагент:\s*([^.\n]+)\.\s*Найдено документов:\s*(\d+)/iu);
|
const directMatch = firstLine.match(/Контрагент:\s*([^.\n]+)\.\s*Найдено документов:\s*(\d+)/iu);
|
||||||
if (!match?.[1] || !match?.[2]) {
|
if (directMatch?.[1] && directMatch?.[2]) {
|
||||||
|
return {
|
||||||
|
counterparty: deps.toNonEmptyString(directMatch[1]),
|
||||||
|
document_count: Number(directMatch[2]),
|
||||||
|
direct_answer: firstLine
|
||||||
|
};
|
||||||
|
}
|
||||||
|
const summaryMatch = text.match(/Отдельно\s+по\s+контрагенту\s+([^:\n]+):[\s\S]{0,260}документы\s+по\s+цепочке:\s*найдено\s*(\d+)/iu);
|
||||||
|
if (!summaryMatch?.[1] || !summaryMatch?.[2]) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
counterparty: deps.toNonEmptyString(summaryMatch[1]),
|
||||||
|
document_count: Number(summaryMatch[2]),
|
||||||
|
direct_answer: summaryMatch[0].replace(/\s+/g, " ").trim()
|
||||||
|
};
|
||||||
|
}
|
||||||
|
function readCounterpartyValueFlowSummaryFromItem(item) {
|
||||||
|
const text = deps.toNonEmptyString(item?.text);
|
||||||
|
if (!text) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
const match = text.match(/Отдельно\s+по\s+контрагенту\s+([^:\n]+):\s*подтверждено\s+получили\s+([^,\n]+?руб\.?),\s*заплатили\s+([^,\n]+?руб\.?),\s*расчетное\s+нетто\s+в\s+нашу\s+сторону\s+([^.\n]+?руб\.?)/iu);
|
||||||
|
if (!match?.[1] || !match?.[2] || !match?.[3] || !match?.[4]) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
counterparty: deps.toNonEmptyString(match[1]),
|
counterparty: deps.toNonEmptyString(match[1]),
|
||||||
document_count: Number(match[2]),
|
incoming_customer_revenue: {
|
||||||
direct_answer: firstLine
|
total_amount_human_ru: deps.toNonEmptyString(match[2])
|
||||||
|
},
|
||||||
|
outgoing_supplier_payout: {
|
||||||
|
total_amount_human_ru: deps.toNonEmptyString(match[3])
|
||||||
|
},
|
||||||
|
net_amount_human_ru: deps.toNonEmptyString(match[4]),
|
||||||
|
net_direction: "net_incoming",
|
||||||
|
inference_basis: "parsed_from_previous_confirmed_counterparty_boundary_summary"
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
function findRecentDiscoveryValueFlowBundle(items) {
|
function findRecentDiscoveryValueFlowBundle(items) {
|
||||||
for (let index = Array.isArray(items) ? items.length - 1 : -1; index >= 0; index -= 1) {
|
for (let index = Array.isArray(items) ? items.length - 1 : -1; index >= 0; index -= 1) {
|
||||||
const item = items[index];
|
const item = items[index];
|
||||||
const debug = item?.debug;
|
const debug = item?.debug;
|
||||||
if (!item || item.role !== "assistant" || !debug || typeof debug !== "object") {
|
const isAssistantItem = item?.role === "assistant" || item?.kind === "assistant";
|
||||||
|
if (!item || !isAssistantItem) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
const flow = readMcpDiscoveryBidirectionalValueFlow(debug);
|
if (debug && typeof debug === "object") {
|
||||||
if (flow) {
|
const flow = readMcpDiscoveryBidirectionalValueFlow(debug);
|
||||||
return flow;
|
if (flow) {
|
||||||
|
return flow;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const parsedFlow = readCounterpartyValueFlowSummaryFromItem(item);
|
||||||
|
if (parsedFlow) {
|
||||||
|
return parsedFlow;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
function sameCounterpartyHint(expected, actual) {
|
||||||
|
const left = normalizeFollowupText(expected);
|
||||||
|
const right = normalizeFollowupText(actual);
|
||||||
|
if (!left || !right) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return left === right || left.includes(right) || right.includes(left);
|
||||||
|
}
|
||||||
|
function findRecentPreviousCounterpartyValueFlowBundle(items, counterpartyHint = null) {
|
||||||
|
const expectedCounterparty = deps.toNonEmptyString(counterpartyHint);
|
||||||
|
if (!expectedCounterparty) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
for (let index = Array.isArray(items) ? items.length - 1 : -1; index >= 0; index -= 1) {
|
||||||
|
const item = items[index];
|
||||||
|
const debug = item?.debug;
|
||||||
|
const isAssistantItem = item?.role === "assistant" || item?.kind === "assistant";
|
||||||
|
if (!item || !isAssistantItem || !debug || typeof debug !== "object") {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const bundle = readMcpDiscoveryPreviousCounterpartyValueFlowBundle(debug);
|
||||||
|
if (!bundle) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (sameCounterpartyHint(expectedCounterparty, deps.toNonEmptyString(bundle.counterparty))) {
|
||||||
|
return bundle;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
|
|
@ -331,7 +427,8 @@ function createAssistantTransitionPolicy(deps) {
|
||||||
function findRecentCounterpartyDocumentBundle(items) {
|
function findRecentCounterpartyDocumentBundle(items) {
|
||||||
for (let index = Array.isArray(items) ? items.length - 1 : -1; index >= 0; index -= 1) {
|
for (let index = Array.isArray(items) ? items.length - 1 : -1; index >= 0; index -= 1) {
|
||||||
const item = items[index];
|
const item = items[index];
|
||||||
if (!item || item.role !== "assistant" || !item.debug || typeof item.debug !== "object") {
|
const isAssistantItem = item?.role === "assistant" || item?.kind === "assistant";
|
||||||
|
if (!item || !isAssistantItem) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
const summary = readCounterpartyDocumentSummaryFromItem(item);
|
const summary = readCounterpartyDocumentSummaryFromItem(item);
|
||||||
|
|
@ -341,6 +438,28 @@ function createAssistantTransitionPolicy(deps) {
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
function findRecentPreviousCounterpartyDocumentBundle(items, counterpartyHint = null) {
|
||||||
|
const expectedCounterparty = deps.toNonEmptyString(counterpartyHint);
|
||||||
|
if (!expectedCounterparty) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
for (let index = Array.isArray(items) ? items.length - 1 : -1; index >= 0; index -= 1) {
|
||||||
|
const item = items[index];
|
||||||
|
const debug = item?.debug;
|
||||||
|
const isAssistantItem = item?.role === "assistant" || item?.kind === "assistant";
|
||||||
|
if (!item || !isAssistantItem || !debug || typeof debug !== "object") {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const bundle = readMcpDiscoveryPreviousCounterpartyDocumentBundle(debug);
|
||||||
|
if (!bundle) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (sameCounterpartyHint(expectedCounterparty, deps.toNonEmptyString(bundle.counterparty))) {
|
||||||
|
return bundle;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
function hasInventoryPurchaseDateVatBridgeSignal(userMessage, alternateMessage, sourceIntentHint, hasInventoryItemFocusHint) {
|
function hasInventoryPurchaseDateVatBridgeSignal(userMessage, alternateMessage, sourceIntentHint, hasInventoryItemFocusHint) {
|
||||||
if (sourceIntentHint !== "inventory_purchase_provenance_for_item" &&
|
if (sourceIntentHint !== "inventory_purchase_provenance_for_item" &&
|
||||||
!hasInventoryItemFocusHint &&
|
!hasInventoryItemFocusHint &&
|
||||||
|
|
@ -504,9 +623,15 @@ function createAssistantTransitionPolicy(deps) {
|
||||||
? hasShortValueFlowRetargetCue(String(alternateMessage ?? "")) ||
|
? hasShortValueFlowRetargetCue(String(alternateMessage ?? "")) ||
|
||||||
hasCompactCashflowFollowupCue(String(alternateMessage ?? ""))
|
hasCompactCashflowFollowupCue(String(alternateMessage ?? ""))
|
||||||
: false);
|
: false);
|
||||||
|
const earlyNavigationSessionState = (0, assistantContinuityPolicy_1.resolveNavigationSessionContextState)(addressNavigationState, deps.toNonEmptyString, deps.normalizeOrganizationScopeValue);
|
||||||
|
const earlyNavigationFocusObject = earlyNavigationSessionState.focusObject;
|
||||||
|
const selectedCounterpartyDocumentFollowupSignal = Boolean(deps.toNonEmptyString(earlyNavigationFocusObject?.label) &&
|
||||||
|
deps.toNonEmptyString(earlyNavigationFocusObject?.objectType) === "counterparty" &&
|
||||||
|
hasSelectedCounterpartyDocumentFollowupSignal(userMessage, alternateMessage));
|
||||||
if (assistantTurnMeaning?.stale_replay_forbidden === true &&
|
if (assistantTurnMeaning?.stale_replay_forbidden === true &&
|
||||||
!hasExplicitSummaryBundleReuseSignal(userMessage, alternateMessage) &&
|
!hasExplicitSummaryBundleReuseSignal(userMessage, alternateMessage) &&
|
||||||
!compactCashflowFollowupSignal) {
|
!compactCashflowFollowupSignal &&
|
||||||
|
!selectedCounterpartyDocumentFollowupSignal) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
const latestAddressItem = deps.findLastAddressAssistantItem(items);
|
const latestAddressItem = deps.findLastAddressAssistantItem(items);
|
||||||
|
|
@ -574,7 +699,7 @@ function createAssistantTransitionPolicy(deps) {
|
||||||
sourceDiscoveryPilotScopeHint === "counterparty_bidirectional_value_flow_query_movements_v1" ||
|
sourceDiscoveryPilotScopeHint === "counterparty_bidirectional_value_flow_query_movements_v1" ||
|
||||||
sourceDiscoveryPilotScopeHint === "business_overview_route_template_v1";
|
sourceDiscoveryPilotScopeHint === "business_overview_route_template_v1";
|
||||||
const hasBusinessOverviewCarryoverSourceHint = sourceDiscoveryPilotScopeHint === "business_overview_route_template_v1";
|
const hasBusinessOverviewCarryoverSourceHint = sourceDiscoveryPilotScopeHint === "business_overview_route_template_v1";
|
||||||
const navigationSessionState = (0, assistantContinuityPolicy_1.resolveNavigationSessionContextState)(addressNavigationState, deps.toNonEmptyString, deps.normalizeOrganizationScopeValue);
|
const navigationSessionState = earlyNavigationSessionState;
|
||||||
const navigationFocusObjectHint = navigationSessionState.focusObject;
|
const navigationFocusObjectHint = navigationSessionState.focusObject;
|
||||||
const hasNavigationInventoryItemFocusHint = Boolean(deps.toNonEmptyString(navigationFocusObjectHint?.label) &&
|
const hasNavigationInventoryItemFocusHint = Boolean(deps.toNonEmptyString(navigationFocusObjectHint?.label) &&
|
||||||
deps.toNonEmptyString(navigationFocusObjectHint?.objectType) === "item" &&
|
deps.toNonEmptyString(navigationFocusObjectHint?.objectType) === "item" &&
|
||||||
|
|
@ -662,6 +787,7 @@ function createAssistantTransitionPolicy(deps) {
|
||||||
Boolean(debtRoleSwapIntent) ||
|
Boolean(debtRoleSwapIntent) ||
|
||||||
shortValueFlowRetargetPrimary ||
|
shortValueFlowRetargetPrimary ||
|
||||||
shortValueFlowRetargetAlternate ||
|
shortValueFlowRetargetAlternate ||
|
||||||
|
selectedCounterpartyDocumentFollowupSignal ||
|
||||||
businessOverviewBoundaryFollowupPrimary ||
|
businessOverviewBoundaryFollowupPrimary ||
|
||||||
businessOverviewBoundaryFollowupAlternate ||
|
businessOverviewBoundaryFollowupAlternate ||
|
||||||
inventoryMarginRankingFollowup ||
|
inventoryMarginRankingFollowup ||
|
||||||
|
|
@ -686,6 +812,7 @@ function createAssistantTransitionPolicy(deps) {
|
||||||
Boolean(debtRoleSwapIntent) ||
|
Boolean(debtRoleSwapIntent) ||
|
||||||
shortValueFlowRetargetPrimary ||
|
shortValueFlowRetargetPrimary ||
|
||||||
shortValueFlowRetargetAlternate ||
|
shortValueFlowRetargetAlternate ||
|
||||||
|
selectedCounterpartyDocumentFollowupSignal ||
|
||||||
businessOverviewBoundaryFollowupPrimary ||
|
businessOverviewBoundaryFollowupPrimary ||
|
||||||
businessOverviewBoundaryFollowupAlternate ||
|
businessOverviewBoundaryFollowupAlternate ||
|
||||||
inventoryMarginRankingFollowup ||
|
inventoryMarginRankingFollowup ||
|
||||||
|
|
@ -713,6 +840,7 @@ function createAssistantTransitionPolicy(deps) {
|
||||||
!hasInventoryRootRestatementAlternate &&
|
!hasInventoryRootRestatementAlternate &&
|
||||||
!shortValueFlowRetargetPrimary &&
|
!shortValueFlowRetargetPrimary &&
|
||||||
!shortValueFlowRetargetAlternate &&
|
!shortValueFlowRetargetAlternate &&
|
||||||
|
!selectedCounterpartyDocumentFollowupSignal &&
|
||||||
!hasImplicitContinuationSignal &&
|
!hasImplicitContinuationSignal &&
|
||||||
!hasSuggestedIntentPivotSignal &&
|
!hasSuggestedIntentPivotSignal &&
|
||||||
!hasOrganizationClarificationContinuation &&
|
!hasOrganizationClarificationContinuation &&
|
||||||
|
|
@ -729,6 +857,7 @@ function createAssistantTransitionPolicy(deps) {
|
||||||
!hasInventoryRootRestatementAlternate &&
|
!hasInventoryRootRestatementAlternate &&
|
||||||
!shortValueFlowRetargetPrimary &&
|
!shortValueFlowRetargetPrimary &&
|
||||||
!shortValueFlowRetargetAlternate &&
|
!shortValueFlowRetargetAlternate &&
|
||||||
|
!selectedCounterpartyDocumentFollowupSignal &&
|
||||||
!hasImplicitContinuationSignal &&
|
!hasImplicitContinuationSignal &&
|
||||||
!hasSuggestedIntentPivotSignal &&
|
!hasSuggestedIntentPivotSignal &&
|
||||||
!hasOrganizationClarificationContinuation &&
|
!hasOrganizationClarificationContinuation &&
|
||||||
|
|
@ -762,8 +891,19 @@ function createAssistantTransitionPolicy(deps) {
|
||||||
const sourceDiscoveryLoopSubjectResolutionOptional = (0, assistantContinuityPolicy_1.readAssistantMcpDiscoveryLoopSubjectResolutionOptional)(carryoverSourceDebug);
|
const sourceDiscoveryLoopSubjectResolutionOptional = (0, assistantContinuityPolicy_1.readAssistantMcpDiscoveryLoopSubjectResolutionOptional)(carryoverSourceDebug);
|
||||||
const sourceDiscoveryRankingNeed = (0, assistantContinuityPolicy_1.readAssistantMcpDiscoveryRankingNeed)(carryoverSourceDebug, deps.toNonEmptyString);
|
const sourceDiscoveryRankingNeed = (0, assistantContinuityPolicy_1.readAssistantMcpDiscoveryRankingNeed)(carryoverSourceDebug, deps.toNonEmptyString);
|
||||||
const sourceDiscoveryEntityAmbiguityCandidates = (0, assistantContinuityPolicy_1.readAssistantMcpDiscoveryEntityAmbiguityCandidates)(carryoverSourceDebug, deps.toNonEmptyString);
|
const sourceDiscoveryEntityAmbiguityCandidates = (0, assistantContinuityPolicy_1.readAssistantMcpDiscoveryEntityAmbiguityCandidates)(carryoverSourceDebug, deps.toNonEmptyString);
|
||||||
const sourceDiscoveryBidirectionalValueFlow = readMcpDiscoveryBidirectionalValueFlow(carryoverSourceDebug) ?? findRecentDiscoveryValueFlowBundle(items);
|
const sourceDiscoveryCounterpartyHint = sourceDiscoveryLoopMetadataScopeHint ??
|
||||||
const sourceDiscoveryDocumentSummary = findRecentCounterpartyDocumentBundle(items);
|
(deps.toNonEmptyString(earlyNavigationFocusObject?.objectType) === "counterparty"
|
||||||
|
? deps.toNonEmptyString(earlyNavigationFocusObject?.label)
|
||||||
|
: null);
|
||||||
|
const sourceDiscoveryBidirectionalValueFlow = readMcpDiscoveryBidirectionalValueFlow(carryoverSourceDebug) ??
|
||||||
|
readMcpDiscoveryPreviousCounterpartyValueFlowBundle(carryoverSourceDebug) ??
|
||||||
|
findRecentPreviousCounterpartyValueFlowBundle(items, sourceDiscoveryCounterpartyHint) ??
|
||||||
|
readCounterpartyValueFlowSummaryFromItem(previousAddressItem) ??
|
||||||
|
findRecentDiscoveryValueFlowBundle(items);
|
||||||
|
const sourceDiscoveryDocumentSummary = readMcpDiscoveryPreviousCounterpartyDocumentBundle(carryoverSourceDebug) ??
|
||||||
|
findRecentPreviousCounterpartyDocumentBundle(items, sourceDiscoveryCounterpartyHint) ??
|
||||||
|
readCounterpartyDocumentSummaryFromItem(previousAddressItem) ??
|
||||||
|
findRecentCounterpartyDocumentBundle(items);
|
||||||
const llmExplicitIntent = deps.toNonEmptyString(llmPreDecomposeMeta?.predecomposeContract?.intent);
|
const llmExplicitIntent = deps.toNonEmptyString(llmPreDecomposeMeta?.predecomposeContract?.intent);
|
||||||
const llmSelectedObjectScopeDetected = llmPreDecomposeMeta?.predecomposeContract?.semantics?.selected_object_scope_detected === true;
|
const llmSelectedObjectScopeDetected = llmPreDecomposeMeta?.predecomposeContract?.semantics?.selected_object_scope_detected === true;
|
||||||
const resolvedPrimaryIntent = deps.resolveAddressIntent(deps.repairAddressMojibake(String(userMessage ?? ""))).intent;
|
const resolvedPrimaryIntent = deps.resolveAddressIntent(deps.repairAddressMojibake(String(userMessage ?? ""))).intent;
|
||||||
|
|
@ -917,6 +1057,14 @@ function createAssistantTransitionPolicy(deps) {
|
||||||
let resolvedCounterpartyFromDisplay = false;
|
let resolvedCounterpartyFromDisplay = false;
|
||||||
let displayedEntityTargetIntent = null;
|
let displayedEntityTargetIntent = null;
|
||||||
let previousFilters = (0, assistantContinuityPolicy_1.resolveAddressDebugCarryoverFilters)(carryoverSourceDebug, deps.toNonEmptyString);
|
let previousFilters = (0, assistantContinuityPolicy_1.resolveAddressDebugCarryoverFilters)(carryoverSourceDebug, deps.toNonEmptyString);
|
||||||
|
const navigationCounterpartyFocus = navigationFocusObjectType === "counterparty" ? navigationFocusObjectLabel : null;
|
||||||
|
const hasNavigationCounterpartyFocusCarryover = Boolean(navigationCounterpartyFocus &&
|
||||||
|
(hasValueFlowCarryoverSourceHint ||
|
||||||
|
sourceIntentHint === "list_contracts_by_counterparty" ||
|
||||||
|
sourceIntentHint === "list_documents_by_counterparty" ||
|
||||||
|
sourceIntentHint === "bank_operations_by_counterparty" ||
|
||||||
|
sourceIntentHint === "open_items_by_counterparty_or_contract" ||
|
||||||
|
sourceDiscoveryLoopSelectedChainIdHint === "value_flow_comparison"));
|
||||||
const shouldBackfillHistoricalPartyAnchors = sourceIntentHint === "list_contracts_by_counterparty" ||
|
const shouldBackfillHistoricalPartyAnchors = sourceIntentHint === "list_contracts_by_counterparty" ||
|
||||||
sourceIntentHint === "list_documents_by_counterparty" ||
|
sourceIntentHint === "list_documents_by_counterparty" ||
|
||||||
sourceIntentHint === "bank_operations_by_counterparty" ||
|
sourceIntentHint === "bank_operations_by_counterparty" ||
|
||||||
|
|
@ -924,6 +1072,15 @@ function createAssistantTransitionPolicy(deps) {
|
||||||
sourceIntentHint === "bank_operations_by_contract" ||
|
sourceIntentHint === "bank_operations_by_contract" ||
|
||||||
sourceIntentHint === "open_items_by_counterparty_or_contract";
|
sourceIntentHint === "open_items_by_counterparty_or_contract";
|
||||||
previousFilters = (0, assistantContinuityPolicy_1.applyHistoricalPartyCarryoverFilters)(previousFilters, shouldBackfillHistoricalPartyAnchors, deps.findRecentAddressFilterValue(items, "contract"), deps.findRecentAddressFilterValue(items, "counterparty"), deps.toNonEmptyString);
|
previousFilters = (0, assistantContinuityPolicy_1.applyHistoricalPartyCarryoverFilters)(previousFilters, shouldBackfillHistoricalPartyAnchors, deps.findRecentAddressFilterValue(items, "contract"), deps.findRecentAddressFilterValue(items, "counterparty"), deps.toNonEmptyString);
|
||||||
|
if (hasNavigationCounterpartyFocusCarryover && navigationCounterpartyFocus) {
|
||||||
|
if (!previousAnchor) {
|
||||||
|
previousAnchorType = "counterparty";
|
||||||
|
previousAnchor = navigationCounterpartyFocus;
|
||||||
|
}
|
||||||
|
if (!deps.toNonEmptyString(previousFilters.counterparty)) {
|
||||||
|
previousFilters.counterparty = navigationCounterpartyFocus;
|
||||||
|
}
|
||||||
|
}
|
||||||
const historicalOrganization = deps.findRecentAddressFilterValue(items, "organization");
|
const historicalOrganization = deps.findRecentAddressFilterValue(items, "organization");
|
||||||
const authorityActiveOrganization = deps.normalizeOrganizationScopeValue(organizationAuthority.activeOrganization) ??
|
const authorityActiveOrganization = deps.normalizeOrganizationScopeValue(organizationAuthority.activeOrganization) ??
|
||||||
deps.normalizeOrganizationScopeValue(organizationAuthority.continuityActiveOrganization);
|
deps.normalizeOrganizationScopeValue(organizationAuthority.continuityActiveOrganization);
|
||||||
|
|
|
||||||
|
|
@ -2152,6 +2152,35 @@ function hasBidirectionalValueFlowComparisonSignal(text: string): boolean {
|
||||||
return hasIncomingCue && hasOutgoingCue && hasComparisonCue && (hasValueFlowCue || hasNetAmountCue);
|
return hasIncomingCue && hasOutgoingCue && hasComparisonCue && (hasValueFlowCue || hasNetAmountCue);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function countBroadBusinessOverviewBridgeAxes(text: string): number {
|
||||||
|
const axisPatterns = [
|
||||||
|
/(?:\u0434\u0435\u043d\p{L}*|\u043e\u0431\u043e\u0440\u043e\u0442|\u0432\u044b\u0440\u0443\u0447\p{L}*|\u043f\u043e\u0441\u0442\u0443\u043f\p{L}*|\u043f\u043b\u0430\u0442\p{L}*|money|cash|revenue|turnover)/iu,
|
||||||
|
/(?:\u043d\u0434\u0441|vat)/iu,
|
||||||
|
/(?:\u0434\u043e\u043b\p{L}*|\u0434\u0435\u0431\u0438\u0442\u043e\u0440\p{L}*|\u043a\u0440\u0435\u0434\u0438\u0442\u043e\u0440\p{L}*|receivable|payable|debt)/iu,
|
||||||
|
/(?:\u0441\u043a\u043b\u0430\u0434|\u043e\u0441\u0442\u0430\u0442|\u0437\u0430\u043f\u0430\u0441|\u0442\u043e\u0432\u0430\u0440|warehouse|stock|inventory)/iu,
|
||||||
|
/(?:\u043a\u043b\u0438\u0435\u043d\u0442|\u043f\u043e\u043a\u0443\u043f\u0430\u0442\u0435\u043b|\u0437\u0430\u043a\u0430\u0437\u0447\u0438\u043a|customer|client|buyer)/iu,
|
||||||
|
/(?:\u043f\u043e\u0441\u0442\u0430\u0432\u0449\u0438\u043a|\u0432\u0435\u043d\u0434\u043e\u0440|\u0437\u0430\u043a\u0443\u043f|supplier|vendor|procurement)/iu,
|
||||||
|
/(?:\u0433\u0434\u0435[\s\S]{0,80}(?:\u043d\u0435\u043b\u044c\u0437\u044f|\u043d\u0435\s+\u0434\u0435\u043b\u0430\p{L}*|\u043d\u0435\s+\u0445\u0432\u0430\p{L}*)|\u0447\u0442\u043e[\s\S]{0,80}(?:\u043d\u0435\u043b\u044c\u0437\u044f|\u043d\u0435\s+\u0445\u0432\u0430\p{L}*)|cannot|unknown|missing|limitation)/iu
|
||||||
|
];
|
||||||
|
return axisPatterns.reduce((count, pattern) => count + (pattern.test(text) ? 1 : 0), 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
function hasBroadBusinessOverviewBridgeSignal(text: string): boolean {
|
||||||
|
const normalized = String(text ?? "").trim().toLowerCase();
|
||||||
|
if (!normalized) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const hasBroadCue =
|
||||||
|
/(?:\u043f\u043e[-\s]*\u0447\u0435\u043b\u043e\u0432\u0435\u0447\u0435\u0441\u043a\p{L}*|\u0447\u0442\u043e\s+\u043f\u043e\s+\u0431\u0438\u0437\u043d\u0435\u0441\u0443\s+\u0432\u0438\u0434\u043d\p{L}*|\u043f\u043e\u0441\u043c\u043e\u0442\p{L}*[\s\S]{0,100}(?:\u0431\u0438\u0437\u043d\u0435\u0441|\u0434\u0435\u044f\u0442\u0435\u043b\p{L}*)|\u0431\u0438\u0437\u043d\u0435\u0441[\s\S]{0,80}(?:\u0432\u0438\u0434\u043d\p{L}*|\u0432\u044b\u0432\u043e\u0434|\u0441\u0440\u0435\u0437)|human\s+readable\s+business\s+view)/iu.test(
|
||||||
|
normalized
|
||||||
|
);
|
||||||
|
const hasCompanyScope =
|
||||||
|
/(?:\u043e\u043e\u043e|\u0438\u043f|\u0430\u043e|\u043f\u0430\u043e|\u0437\u0430\u043e|\u043e\u0430\u043e|\u043a\u043e\u043c\u043f\u0430\u043d|\u043e\u0440\u0433\u0430\u043d\u0438\u0437\u0430\u0446|\u0431\u0438\u0437\u043d\u0435\u0441|\u0432\s+1\s?\u0441|1\s?c|company|organization|business|(?:19|20)\d{2})/iu.test(
|
||||||
|
normalized
|
||||||
|
);
|
||||||
|
return hasBroadCue && hasCompanyScope && countBroadBusinessOverviewBridgeAxes(normalized) >= 3;
|
||||||
|
}
|
||||||
|
|
||||||
function hasNomenclatureMarginRankingSignal(text: string): boolean {
|
function hasNomenclatureMarginRankingSignal(text: string): boolean {
|
||||||
const normalized = String(text ?? "").trim().toLowerCase();
|
const normalized = String(text ?? "").trim().toLowerCase();
|
||||||
if (!normalized) {
|
if (!normalized) {
|
||||||
|
|
@ -2357,6 +2386,10 @@ function resolveUnicodeAddressIntentBridge(text: string): AddressIntentResolutio
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (hasBroadBusinessOverviewBridgeSignal(normalized)) {
|
||||||
|
return unicodeBridgeResolution("unknown", "high", "unicode_business_overview_multi_surface_deferred_to_discovery");
|
||||||
|
}
|
||||||
|
|
||||||
if (hasOrganizationLevelEarningsOverviewBridgeSignal(normalized)) {
|
if (hasOrganizationLevelEarningsOverviewBridgeSignal(normalized)) {
|
||||||
return unicodeBridgeResolution("unknown", "high", "unicode_business_overview_earnings_deferred_to_discovery");
|
return unicodeBridgeResolution("unknown", "high", "unicode_business_overview_earnings_deferred_to_discovery");
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,9 @@ import type { AssistantConversationItem } from "../types/assistant";
|
||||||
import type { AddressIntent } from "../types/addressQuery";
|
import type { AddressIntent } from "../types/addressQuery";
|
||||||
import {
|
import {
|
||||||
readAddressDebugCounterparty,
|
readAddressDebugCounterparty,
|
||||||
readAddressDebugItem
|
readAddressDebugItem,
|
||||||
|
readAddressDebugOrganization,
|
||||||
|
readAddressDebugTemporalScope
|
||||||
} from "./assistantContinuityPolicy";
|
} from "./assistantContinuityPolicy";
|
||||||
import {
|
import {
|
||||||
ADDRESS_NAVIGATION_STATE_SCHEMA_VERSION,
|
ADDRESS_NAVIGATION_STATE_SCHEMA_VERSION,
|
||||||
|
|
@ -21,7 +23,11 @@ const MAX_RESULT_SETS = 40;
|
||||||
const MAX_NAVIGATION_EVENTS = 120;
|
const MAX_NAVIGATION_EVENTS = 120;
|
||||||
const MAX_ENTITY_REFS_PER_RESULT_SET = 40;
|
const MAX_ENTITY_REFS_PER_RESULT_SET = 40;
|
||||||
|
|
||||||
|
type AddressComparisonScope = NonNullable<AddressNavigationState["session_context"]["comparison_scope"]>;
|
||||||
|
type AddressComparisonProofBundles = NonNullable<AddressComparisonScope["proof_bundles"]>;
|
||||||
|
|
||||||
const DISPLAY_ENTITY_TYPE_BY_INTENT: Partial<Record<AddressIntent, AddressFocusObjectType>> = {
|
const DISPLAY_ENTITY_TYPE_BY_INTENT: Partial<Record<AddressIntent, AddressFocusObjectType>> = {
|
||||||
|
business_overview: "organization",
|
||||||
counterparty_activity_lifecycle: "counterparty",
|
counterparty_activity_lifecycle: "counterparty",
|
||||||
customer_revenue_and_payments: "counterparty",
|
customer_revenue_and_payments: "counterparty",
|
||||||
supplier_payouts_profile: "counterparty",
|
supplier_payouts_profile: "counterparty",
|
||||||
|
|
@ -45,6 +51,7 @@ const DISPLAY_ENTITY_TYPE_BY_INTENT: Partial<Record<AddressIntent, AddressFocusO
|
||||||
};
|
};
|
||||||
|
|
||||||
const RESULT_SET_TYPE_BY_INTENT: Partial<Record<AddressIntent, AddressResultSetType>> = {
|
const RESULT_SET_TYPE_BY_INTENT: Partial<Record<AddressIntent, AddressResultSetType>> = {
|
||||||
|
business_overview: "profile_summary",
|
||||||
counterparty_activity_lifecycle: "counterparty_list",
|
counterparty_activity_lifecycle: "counterparty_list",
|
||||||
customer_revenue_and_payments: "counterparty_list",
|
customer_revenue_and_payments: "counterparty_list",
|
||||||
supplier_payouts_profile: "counterparty_list",
|
supplier_payouts_profile: "counterparty_list",
|
||||||
|
|
@ -83,6 +90,18 @@ function toObject(value: unknown): Record<string, unknown> | null {
|
||||||
return value as Record<string, unknown>;
|
return value as Record<string, unknown>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function cloneRecord(value: unknown): Record<string, unknown> | null {
|
||||||
|
const record = toObject(value);
|
||||||
|
if (!record) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
return JSON.parse(JSON.stringify(record)) as Record<string, unknown>;
|
||||||
|
} catch {
|
||||||
|
return { ...record };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function toNonEmptyString(value: unknown): string | null {
|
function toNonEmptyString(value: unknown): string | null {
|
||||||
if (typeof value !== "string") {
|
if (typeof value !== "string") {
|
||||||
return null;
|
return null;
|
||||||
|
|
@ -91,6 +110,44 @@ function toNonEmptyString(value: unknown): string | null {
|
||||||
return trimmed.length > 0 ? trimmed : null;
|
return trimmed.length > 0 ? trimmed : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function candidateLabel(value: unknown): string | null {
|
||||||
|
const direct = toNonEmptyString(value);
|
||||||
|
if (direct && direct !== "[object Object]") {
|
||||||
|
return direct;
|
||||||
|
}
|
||||||
|
const record = toObject(value);
|
||||||
|
if (!record) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
toNonEmptyString(record.value) ??
|
||||||
|
toNonEmptyString(record.name) ??
|
||||||
|
toNonEmptyString(record.ref) ??
|
||||||
|
toNonEmptyString(record.text)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function readNavigationDiscoveryCounterparty(debug: Record<string, unknown>): string | null {
|
||||||
|
const entry = toObject(debug.assistant_mcp_discovery_entry_point_v1);
|
||||||
|
const turnInput = toObject(entry?.turn_input);
|
||||||
|
const turnMeaning = toObject(turnInput?.turn_meaning_ref);
|
||||||
|
const dataNeedGraph = toObject(turnInput?.data_need_graph);
|
||||||
|
const candidates = [
|
||||||
|
...(Array.isArray(turnMeaning?.business_overview_separate_entity_candidates)
|
||||||
|
? turnMeaning.business_overview_separate_entity_candidates
|
||||||
|
: []),
|
||||||
|
...(Array.isArray(turnMeaning?.explicit_entity_candidates) ? turnMeaning.explicit_entity_candidates : []),
|
||||||
|
...(Array.isArray(dataNeedGraph?.subject_candidates) ? dataNeedGraph.subject_candidates : [])
|
||||||
|
];
|
||||||
|
for (const candidate of candidates) {
|
||||||
|
const label = candidateLabel(candidate);
|
||||||
|
if (label) {
|
||||||
|
return label;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
function toAddressFocusObjectType(value: unknown): AddressFocusObjectType {
|
function toAddressFocusObjectType(value: unknown): AddressFocusObjectType {
|
||||||
const normalized = toNonEmptyString(value);
|
const normalized = toNonEmptyString(value);
|
||||||
if (!normalized) {
|
if (!normalized) {
|
||||||
|
|
@ -228,6 +285,26 @@ function cloneFocusObject(value: AddressFocusObject | null): AddressFocusObject
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function cloneComparisonProofBundles(value: unknown): AddressComparisonProofBundles | null {
|
||||||
|
const record = toObject(value);
|
||||||
|
if (!record) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
const counterpartyValueFlowBundle = cloneRecord(
|
||||||
|
record.counterparty_value_flow_bundle ?? record.previous_counterparty_value_flow_bundle
|
||||||
|
);
|
||||||
|
const counterpartyDocumentBundle = cloneRecord(
|
||||||
|
record.counterparty_document_bundle ?? record.previous_counterparty_document_bundle
|
||||||
|
);
|
||||||
|
if (!counterpartyValueFlowBundle && !counterpartyDocumentBundle) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
counterparty_value_flow_bundle: counterpartyValueFlowBundle,
|
||||||
|
counterparty_document_bundle: counterpartyDocumentBundle
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
function cloneResultSet(input: AddressResultSet): AddressResultSet {
|
function cloneResultSet(input: AddressResultSet): AddressResultSet {
|
||||||
return {
|
return {
|
||||||
result_set_id: input.result_set_id,
|
result_set_id: input.result_set_id,
|
||||||
|
|
@ -296,8 +373,71 @@ function buildFocusObject(
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function cloneComparisonScope(
|
||||||
|
value: AddressNavigationState["session_context"]["comparison_scope"]
|
||||||
|
): AddressNavigationState["session_context"]["comparison_scope"] {
|
||||||
|
if (!value || typeof value !== "object") {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
organization: cloneFocusObject(value.organization),
|
||||||
|
counterparty: cloneFocusObject(value.counterparty),
|
||||||
|
proof_bundles: cloneComparisonProofBundles(value.proof_bundles)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function sameBusinessLabel(left: unknown, right: unknown): boolean {
|
||||||
|
const normalizedLeft = toNonEmptyString(left)?.toLocaleLowerCase("ru-RU");
|
||||||
|
const normalizedRight = toNonEmptyString(right)?.toLocaleLowerCase("ru-RU");
|
||||||
|
return Boolean(normalizedLeft && normalizedRight && normalizedLeft === normalizedRight);
|
||||||
|
}
|
||||||
|
|
||||||
|
function readBusinessOverviewComparisonProofBundles(debug: Record<string, unknown>): AddressComparisonProofBundles | null {
|
||||||
|
const entryPoint = toObject(debug.assistant_mcp_discovery_entry_point_v1);
|
||||||
|
const turnInput = toObject(entryPoint?.turn_input);
|
||||||
|
const turnMeaning = toObject(turnInput?.turn_meaning_ref);
|
||||||
|
const bridge = toObject(entryPoint?.bridge);
|
||||||
|
const pilot = toObject(bridge?.pilot);
|
||||||
|
const counterpartyValueFlowBundle =
|
||||||
|
cloneRecord(turnMeaning?.previous_counterparty_value_flow_bundle) ??
|
||||||
|
cloneRecord(pilot?.derived_bidirectional_value_flow);
|
||||||
|
const counterpartyDocumentBundle = cloneRecord(turnMeaning?.previous_counterparty_document_bundle);
|
||||||
|
const counterparty =
|
||||||
|
toNonEmptyString(counterpartyValueFlowBundle?.counterparty) ??
|
||||||
|
toNonEmptyString(counterpartyDocumentBundle?.counterparty) ??
|
||||||
|
readNavigationDiscoveryCounterparty(debug);
|
||||||
|
if (!counterparty) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (!counterpartyValueFlowBundle && !counterpartyDocumentBundle) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
counterparty_value_flow_bundle: counterpartyValueFlowBundle,
|
||||||
|
counterparty_document_bundle: counterpartyDocumentBundle
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
function buildFocusObjectFromDebug(debug: Record<string, unknown>, resultSetId: string, createdAt: string): AddressFocusObject | null {
|
function buildFocusObjectFromDebug(debug: Record<string, unknown>, resultSetId: string, createdAt: string): AddressFocusObject | null {
|
||||||
const extractedFilters = toObject(debug.extracted_filters) ?? {};
|
const extractedFilters = toObject(debug.extracted_filters) ?? {};
|
||||||
|
const selectedDiscoveryChain = toNonEmptyString(debug.mcp_discovery_selected_chain_id);
|
||||||
|
if (selectedDiscoveryChain === "business_overview" && debug.mcp_discovery_response_applied === true) {
|
||||||
|
const counterparty = readAddressDebugCounterparty(debug, toNonEmptyString) ?? readNavigationDiscoveryCounterparty(debug);
|
||||||
|
if (counterparty) {
|
||||||
|
return buildFocusObject("counterparty", counterparty, resultSetId, createdAt);
|
||||||
|
}
|
||||||
|
const organization = readAddressDebugOrganization(debug, toNonEmptyString);
|
||||||
|
if (organization) {
|
||||||
|
return buildFocusObject("organization", organization, resultSetId, createdAt);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (selectedDiscoveryChain === "value_flow_comparison" && debug.mcp_discovery_response_applied === true) {
|
||||||
|
const counterparty =
|
||||||
|
readAddressDebugCounterparty(debug, toNonEmptyString) ?? readNavigationDiscoveryCounterparty(debug);
|
||||||
|
if (counterparty) {
|
||||||
|
return buildFocusObject("counterparty", counterparty, resultSetId, createdAt);
|
||||||
|
}
|
||||||
|
}
|
||||||
const objectType = toAddressFocusObjectType(debug.anchor_type);
|
const objectType = toAddressFocusObjectType(debug.anchor_type);
|
||||||
const canonicalType = objectType === "unknown" ? inferDisplayEntityType(toAddressIntent(debug.detected_intent)) : objectType;
|
const canonicalType = objectType === "unknown" ? inferDisplayEntityType(toAddressIntent(debug.detected_intent)) : objectType;
|
||||||
if (canonicalType === "item") {
|
if (canonicalType === "item") {
|
||||||
|
|
@ -330,11 +470,13 @@ function capNavigationEvents(events: AddressNavigationEvent[]): AddressNavigatio
|
||||||
}
|
}
|
||||||
|
|
||||||
function isAddressAssistantItem(item: AssistantConversationItem): boolean {
|
function isAddressAssistantItem(item: AssistantConversationItem): boolean {
|
||||||
return (
|
if (item.role !== "assistant" || !item.debug) {
|
||||||
item.role === "assistant" &&
|
return false;
|
||||||
Boolean(item.debug) &&
|
}
|
||||||
toNonEmptyString(item.debug?.detected_mode) === "address_query"
|
if (toNonEmptyString(item.debug.detected_mode) === "address_query") {
|
||||||
);
|
return true;
|
||||||
|
}
|
||||||
|
return item.debug.mcp_discovery_response_applied === true && Boolean(toNonEmptyString(item.debug.mcp_discovery_selected_chain_id));
|
||||||
}
|
}
|
||||||
|
|
||||||
export function createEmptyAddressNavigationState(
|
export function createEmptyAddressNavigationState(
|
||||||
|
|
@ -348,6 +490,7 @@ export function createEmptyAddressNavigationState(
|
||||||
session_context: {
|
session_context: {
|
||||||
active_result_set_id: null,
|
active_result_set_id: null,
|
||||||
active_focus_object: null,
|
active_focus_object: null,
|
||||||
|
comparison_scope: null,
|
||||||
last_confirmed_route: null,
|
last_confirmed_route: null,
|
||||||
date_scope: {
|
date_scope: {
|
||||||
as_of_date: null,
|
as_of_date: null,
|
||||||
|
|
@ -372,6 +515,7 @@ export function cloneAddressNavigationState(value: AddressNavigationState | null
|
||||||
session_context: {
|
session_context: {
|
||||||
active_result_set_id: value.session_context.active_result_set_id,
|
active_result_set_id: value.session_context.active_result_set_id,
|
||||||
active_focus_object: cloneFocusObject(value.session_context.active_focus_object),
|
active_focus_object: cloneFocusObject(value.session_context.active_focus_object),
|
||||||
|
comparison_scope: cloneComparisonScope(value.session_context.comparison_scope),
|
||||||
last_confirmed_route: value.session_context.last_confirmed_route,
|
last_confirmed_route: value.session_context.last_confirmed_route,
|
||||||
date_scope: {
|
date_scope: {
|
||||||
as_of_date: value.session_context.date_scope.as_of_date,
|
as_of_date: value.session_context.date_scope.as_of_date,
|
||||||
|
|
@ -406,6 +550,9 @@ export function normalizeAddressNavigationState(
|
||||||
session_context: {
|
session_context: {
|
||||||
active_result_set_id: toNonEmptyString(context.active_result_set_id),
|
active_result_set_id: toNonEmptyString(context.active_result_set_id),
|
||||||
active_focus_object: cloneFocusObject(context.active_focus_object as AddressFocusObject | null),
|
active_focus_object: cloneFocusObject(context.active_focus_object as AddressFocusObject | null),
|
||||||
|
comparison_scope: cloneComparisonScope(
|
||||||
|
context.comparison_scope as AddressNavigationState["session_context"]["comparison_scope"]
|
||||||
|
),
|
||||||
last_confirmed_route: toNonEmptyString(context.last_confirmed_route),
|
last_confirmed_route: toNonEmptyString(context.last_confirmed_route),
|
||||||
date_scope: {
|
date_scope: {
|
||||||
as_of_date: toNonEmptyString(dateScope.as_of_date),
|
as_of_date: toNonEmptyString(dateScope.as_of_date),
|
||||||
|
|
@ -464,20 +611,42 @@ export function evolveAddressNavigationStateWithAssistantItem(
|
||||||
return state;
|
return state;
|
||||||
}
|
}
|
||||||
const debug = item.debug as unknown as Record<string, unknown>;
|
const debug = item.debug as unknown as Record<string, unknown>;
|
||||||
const intent = toAddressIntent(debug.detected_intent);
|
const selectedDiscoveryChain = toNonEmptyString(debug.mcp_discovery_selected_chain_id);
|
||||||
if (intent === "unknown") {
|
const discoveryIntent =
|
||||||
|
selectedDiscoveryChain === "business_overview"
|
||||||
|
? "business_overview"
|
||||||
|
: selectedDiscoveryChain === "value_flow_comparison"
|
||||||
|
? "customer_revenue_and_payments"
|
||||||
|
: "unknown";
|
||||||
|
const detectedIntent = toNonEmptyString(debug.detected_intent);
|
||||||
|
const intent = toAddressIntent(detectedIntent && detectedIntent !== "unknown" ? detectedIntent : discoveryIntent);
|
||||||
|
const trackableDiscoveryTurn = debug.mcp_discovery_response_applied === true && Boolean(selectedDiscoveryChain);
|
||||||
|
if (intent === "unknown" && !trackableDiscoveryTurn) {
|
||||||
return state;
|
return state;
|
||||||
}
|
}
|
||||||
const createdAt = toNonEmptyString(item.created_at) ?? new Date().toISOString();
|
const createdAt = toNonEmptyString(item.created_at) ?? new Date().toISOString();
|
||||||
const resultSetId = `rs-${item.message_id}`;
|
const resultSetId = `rs-${item.message_id}`;
|
||||||
const routeId = toNonEmptyString(debug.selected_recipe);
|
const routeId = toNonEmptyString(debug.selected_recipe) ?? selectedDiscoveryChain;
|
||||||
const filters = normalizeFilters(debug.extracted_filters);
|
const filters = normalizeFilters(debug.extracted_filters);
|
||||||
const derivedOrganizationScope = resolveDerivedOrganizationScope(debug, filters, item.text);
|
const derivedOrganizationScope =
|
||||||
|
resolveDerivedOrganizationScope(debug, filters, item.text) ?? readAddressDebugOrganization(debug, toNonEmptyString);
|
||||||
|
const derivedCounterpartyScope =
|
||||||
|
selectedDiscoveryChain === "value_flow_comparison" && debug.mcp_discovery_response_applied === true
|
||||||
|
? readAddressDebugCounterparty(debug, toNonEmptyString) ?? readNavigationDiscoveryCounterparty(debug)
|
||||||
|
: null;
|
||||||
const filtersWithDerivedScope =
|
const filtersWithDerivedScope =
|
||||||
derivedOrganizationScope && !toNonEmptyString(filters.organization)
|
derivedOrganizationScope && !toNonEmptyString(filters.organization)
|
||||||
? {
|
? {
|
||||||
...filters,
|
...filters,
|
||||||
organization: derivedOrganizationScope
|
organization: derivedOrganizationScope,
|
||||||
|
...(derivedCounterpartyScope && !toNonEmptyString(filters.counterparty)
|
||||||
|
? { counterparty: derivedCounterpartyScope }
|
||||||
|
: {})
|
||||||
|
}
|
||||||
|
: derivedCounterpartyScope && !toNonEmptyString(filters.counterparty)
|
||||||
|
? {
|
||||||
|
...filters,
|
||||||
|
counterparty: derivedCounterpartyScope
|
||||||
}
|
}
|
||||||
: filters;
|
: filters;
|
||||||
const sourceRefs = routeId ? [routeId] : [];
|
const sourceRefs = routeId ? [routeId] : [];
|
||||||
|
|
@ -495,6 +664,41 @@ export function evolveAddressNavigationStateWithAssistantItem(
|
||||||
};
|
};
|
||||||
const previousResultSetId = state.session_context.active_result_set_id;
|
const previousResultSetId = state.session_context.active_result_set_id;
|
||||||
const focusObject = buildFocusObjectFromDebug(debug, resultSetId, createdAt);
|
const focusObject = buildFocusObjectFromDebug(debug, resultSetId, createdAt);
|
||||||
|
const comparisonCounterparty =
|
||||||
|
selectedDiscoveryChain === "business_overview" && debug.mcp_discovery_response_applied === true
|
||||||
|
? readNavigationDiscoveryCounterparty(debug)
|
||||||
|
: null;
|
||||||
|
const comparisonOrganization =
|
||||||
|
selectedDiscoveryChain === "business_overview" && debug.mcp_discovery_response_applied === true
|
||||||
|
? derivedOrganizationScope ?? toNonEmptyString(filtersWithDerivedScope.organization)
|
||||||
|
: null;
|
||||||
|
const currentComparisonProofBundles =
|
||||||
|
selectedDiscoveryChain === "business_overview" && debug.mcp_discovery_response_applied === true
|
||||||
|
? readBusinessOverviewComparisonProofBundles(debug)
|
||||||
|
: null;
|
||||||
|
const inheritedComparisonScope = state.session_context.comparison_scope;
|
||||||
|
const inheritedComparisonProofBundles =
|
||||||
|
comparisonCounterparty &&
|
||||||
|
sameBusinessLabel(inheritedComparisonScope?.counterparty?.label, comparisonCounterparty)
|
||||||
|
? cloneComparisonProofBundles(inheritedComparisonScope?.proof_bundles)
|
||||||
|
: null;
|
||||||
|
const comparisonProofBundles = currentComparisonProofBundles ?? inheritedComparisonProofBundles;
|
||||||
|
const comparisonOrganizationObject =
|
||||||
|
comparisonOrganization
|
||||||
|
? buildFocusObject("organization", comparisonOrganization, resultSetId, createdAt)
|
||||||
|
: cloneFocusObject(inheritedComparisonScope?.organization ?? null);
|
||||||
|
const comparisonCounterpartyObject =
|
||||||
|
comparisonCounterparty
|
||||||
|
? buildFocusObject("counterparty", comparisonCounterparty, resultSetId, createdAt)
|
||||||
|
: cloneFocusObject(inheritedComparisonScope?.counterparty ?? null);
|
||||||
|
const comparisonScope =
|
||||||
|
comparisonOrganizationObject || comparisonCounterpartyObject || comparisonProofBundles
|
||||||
|
? {
|
||||||
|
organization: comparisonOrganizationObject,
|
||||||
|
counterparty: comparisonCounterpartyObject,
|
||||||
|
proof_bundles: comparisonProofBundles
|
||||||
|
}
|
||||||
|
: null;
|
||||||
const action = resolveNavigationAction(debug, Boolean(focusObject));
|
const action = resolveNavigationAction(debug, Boolean(focusObject));
|
||||||
const navigationEvent: AddressNavigationEvent = {
|
const navigationEvent: AddressNavigationEvent = {
|
||||||
event_id: `nav-${nanoid(10)}`,
|
event_id: `nav-${nanoid(10)}`,
|
||||||
|
|
@ -505,10 +709,11 @@ export function evolveAddressNavigationStateWithAssistantItem(
|
||||||
turn_index: turnIndex,
|
turn_index: turnIndex,
|
||||||
created_at: createdAt
|
created_at: createdAt
|
||||||
};
|
};
|
||||||
|
const discoveryTemporalScope = readAddressDebugTemporalScope(debug, toNonEmptyString);
|
||||||
const normalizedDateScope = {
|
const normalizedDateScope = {
|
||||||
as_of_date: toNonEmptyString(filtersWithDerivedScope.as_of_date),
|
as_of_date: toNonEmptyString(filtersWithDerivedScope.as_of_date) ?? discoveryTemporalScope.asOfDate,
|
||||||
period_from: toNonEmptyString(filtersWithDerivedScope.period_from),
|
period_from: toNonEmptyString(filtersWithDerivedScope.period_from) ?? discoveryTemporalScope.periodFrom,
|
||||||
period_to: toNonEmptyString(filtersWithDerivedScope.period_to)
|
period_to: toNonEmptyString(filtersWithDerivedScope.period_to) ?? discoveryTemporalScope.periodTo
|
||||||
};
|
};
|
||||||
const organizationScope = toNonEmptyString(filtersWithDerivedScope.organization);
|
const organizationScope = toNonEmptyString(filtersWithDerivedScope.organization);
|
||||||
const nextResultSets = capResultSets(
|
const nextResultSets = capResultSets(
|
||||||
|
|
@ -522,6 +727,7 @@ export function evolveAddressNavigationStateWithAssistantItem(
|
||||||
? {
|
? {
|
||||||
active_result_set_id: resultSetId,
|
active_result_set_id: resultSetId,
|
||||||
active_focus_object: focusObject ?? null,
|
active_focus_object: focusObject ?? null,
|
||||||
|
comparison_scope: comparisonScope,
|
||||||
last_confirmed_route: routeId ?? null,
|
last_confirmed_route: routeId ?? null,
|
||||||
date_scope: {
|
date_scope: {
|
||||||
as_of_date: normalizedDateScope.as_of_date,
|
as_of_date: normalizedDateScope.as_of_date,
|
||||||
|
|
@ -533,6 +739,7 @@ export function evolveAddressNavigationStateWithAssistantItem(
|
||||||
: {
|
: {
|
||||||
active_result_set_id: resultSetId,
|
active_result_set_id: resultSetId,
|
||||||
active_focus_object: focusObject ?? state.session_context.active_focus_object,
|
active_focus_object: focusObject ?? state.session_context.active_focus_object,
|
||||||
|
comparison_scope: comparisonScope ?? state.session_context.comparison_scope,
|
||||||
last_confirmed_route: routeId ?? state.session_context.last_confirmed_route,
|
last_confirmed_route: routeId ?? state.session_context.last_confirmed_route,
|
||||||
date_scope: {
|
date_scope: {
|
||||||
as_of_date: normalizedDateScope.as_of_date ?? state.session_context.date_scope.as_of_date,
|
as_of_date: normalizedDateScope.as_of_date ?? state.session_context.date_scope.as_of_date,
|
||||||
|
|
|
||||||
|
|
@ -733,6 +733,44 @@ function needsVatCalendarDetails(userMessage: string | null | undefined): boolea
|
||||||
return /(?:срок|когда|дата\s+уплат|декларац|дол(?:я|ями)|по\s+частям|платежн(?:ый|ого)\s+график)/iu.test(text);
|
return /(?:срок|когда|дата\s+уплат|декларац|дол(?:я|ями)|по\s+частям|платежн(?:ый|ого)\s+график)/iu.test(text);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function needsVatPurchaseDateAnchorDisclosure(userMessage: string | null | undefined): boolean {
|
||||||
|
const text = normalizeQuestionText(userMessage);
|
||||||
|
if (!text) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return /(?:дата|дату|дате|момент)\s+(?:покуп|закуп)|(?:покуп|закуп)\S*\s+(?:дат|момент)|purchase\s+date|date\s+of\s+purchase/iu.test(
|
||||||
|
text
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildVatPurchaseDateAnchorDisclosureLine(
|
||||||
|
options: ComposeFactualReplyOptions,
|
||||||
|
periodWindowLabel: string | null
|
||||||
|
): string | null {
|
||||||
|
if (!periodWindowLabel || !needsVatPurchaseDateAnchorDisclosure(options.userMessage)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const asOfDate = normalizeIsoDateOnly(options.asOfDate);
|
||||||
|
const periodFrom = normalizeIsoDateOnly(options.periodFrom);
|
||||||
|
const periodTo = normalizeIsoDateOnly(options.periodTo);
|
||||||
|
const asOfTs = toUtcDayTimestamp(asOfDate);
|
||||||
|
const fromTs = toUtcDayTimestamp(periodFrom);
|
||||||
|
const toTs = toUtcDayTimestamp(periodTo);
|
||||||
|
if (
|
||||||
|
asOfDate &&
|
||||||
|
asOfTs !== null &&
|
||||||
|
fromTs !== null &&
|
||||||
|
toTs !== null &&
|
||||||
|
asOfTs >= fromTs &&
|
||||||
|
asOfTs <= toTs
|
||||||
|
) {
|
||||||
|
return `- Якорь периода: дата покупки ${formatDateRu(asOfDate)} попадает в налоговый период ${periodWindowLabel}; поэтому расчет ниже взят из книг продаж/покупок за это окно.`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return `- Якорь периода: дата покупки из вопроса/контекста использована для выбора налогового периода ${periodWindowLabel}; сам расчет ниже взят из книг продаж/покупок за это окно.`;
|
||||||
|
}
|
||||||
|
|
||||||
function detectRankingLimit(userMessage: string | null | undefined, fallback = 20): number {
|
function detectRankingLimit(userMessage: string | null | undefined, fallback = 20): number {
|
||||||
const text = normalizeQuestionText(userMessage);
|
const text = normalizeQuestionText(userMessage);
|
||||||
if (!text) {
|
if (!text) {
|
||||||
|
|
@ -3896,6 +3934,7 @@ function composeFactualReplyBody(
|
||||||
const formatConfirmedMoney = (value: number): string => (options.useRubCurrency ? formatMoneyRub(value) : formatMoney(value));
|
const formatConfirmedMoney = (value: number): string => (options.useRubCurrency ? formatMoneyRub(value) : formatMoney(value));
|
||||||
const organizationLabel = normalizeOrganizationScopeValue(options.organizationHint);
|
const organizationLabel = normalizeOrganizationScopeValue(options.organizationHint);
|
||||||
const organizationScopeLabel = organizationLabel ? ` по организации ${organizationLabel}` : "";
|
const organizationScopeLabel = organizationLabel ? ` по организации ${organizationLabel}` : "";
|
||||||
|
const purchaseDateAnchorLine = buildVatPurchaseDateAnchorDisclosureLine(options, periodWindowLabel);
|
||||||
|
|
||||||
const lines = [
|
const lines = [
|
||||||
`Коротко: подтвержденный НДС к уплате за налоговый период${organizationScopeLabel} — ${formatConfirmedMoney(vatToPay)}.`,
|
`Коротко: подтвержденный НДС к уплате за налоговый период${organizationScopeLabel} — ${formatConfirmedMoney(vatToPay)}.`,
|
||||||
|
|
@ -3904,6 +3943,7 @@ function composeFactualReplyBody(
|
||||||
"Что вошло в расчет:",
|
"Что вошло в расчет:",
|
||||||
...(organizationLabel ? [`- Организация: ${organizationLabel}.`] : []),
|
...(organizationLabel ? [`- Организация: ${organizationLabel}.`] : []),
|
||||||
`- Налоговый период расчета: ${periodWindowLabel ?? "не задан (нужен явный период)"}.`,
|
`- Налоговый период расчета: ${periodWindowLabel ?? "не задан (нужен явный период)"}.`,
|
||||||
|
...(purchaseDateAnchorLine ? [purchaseDateAnchorLine] : []),
|
||||||
`- НДС по книге продаж: ${formatConfirmedMoney(salesVat)}.`,
|
`- НДС по книге продаж: ${formatConfirmedMoney(salesVat)}.`,
|
||||||
`- НДС по книге покупок (вычеты): ${formatConfirmedMoney(purchaseVat)}.`,
|
`- НДС по книге покупок (вычеты): ${formatConfirmedMoney(purchaseVat)}.`,
|
||||||
`- Нетто НДС (книга продаж - книга покупок): ${formatConfirmedMoney(netVat)}.`
|
`- Нетто НДС (книга продаж - книга покупок): ${formatConfirmedMoney(netVat)}.`
|
||||||
|
|
|
||||||
|
|
@ -76,6 +76,141 @@ function normalizeAddressReplyType(value: unknown): AssistantReplyType {
|
||||||
return value === "factual" || value === "partial_coverage" ? value : "partial_coverage";
|
return value === "factual" || value === "partial_coverage" ? value : "partial_coverage";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function sameBusinessLabel(left: unknown, right: unknown): boolean {
|
||||||
|
const normalizedLeft = toNullableString(left)?.toLocaleLowerCase("ru-RU").replace(/ё/g, "е");
|
||||||
|
const normalizedRight = toNullableString(right)?.toLocaleLowerCase("ru-RU").replace(/ё/g, "е");
|
||||||
|
return Boolean(
|
||||||
|
normalizedLeft &&
|
||||||
|
normalizedRight &&
|
||||||
|
(normalizedLeft === normalizedRight ||
|
||||||
|
normalizedLeft.includes(normalizedRight) ||
|
||||||
|
normalizedRight.includes(normalizedLeft))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function firstString(values: unknown[]): string | null {
|
||||||
|
for (const value of values) {
|
||||||
|
const text = toNullableString(value);
|
||||||
|
if (text) {
|
||||||
|
return text;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function legalOrganizationLabelFromClarification(value: unknown): string | null {
|
||||||
|
const text = toNullableString(value);
|
||||||
|
if (!text) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
const compact = text.replace(/\s+/gu, " ").replace(/[.!?]+$/u, "").trim();
|
||||||
|
if (compact.length > 120 || !/^(?:ООО|ПАО|АО|ИП)\s+\S/iu.test(compact)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return compact;
|
||||||
|
}
|
||||||
|
|
||||||
|
function cleanComparisonScopeCompanyLine(line: string, organization: string | null): string {
|
||||||
|
let clean = String(line ?? "")
|
||||||
|
.replace(/\bcompany-level\b/giu, "общий по компании")
|
||||||
|
.replace(/\breusable bundle\b/giu, "сохраненный подтвержденный срез");
|
||||||
|
if (organization) {
|
||||||
|
clean = clean.replace(/по компании\s+Альтернатива Плюс/iu, `по компании ${organization}`);
|
||||||
|
}
|
||||||
|
return clean.trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildComparisonScopeProofReply(input: {
|
||||||
|
baseReply: string;
|
||||||
|
debug: Record<string, unknown>;
|
||||||
|
session: unknown;
|
||||||
|
userMessage?: unknown;
|
||||||
|
}): { reply: string; audit: Record<string, unknown> } | null {
|
||||||
|
const entryPoint = toRecordObject(input.debug.assistant_mcp_discovery_entry_point_v1);
|
||||||
|
const turnInput = toRecordObject(entryPoint?.turn_input);
|
||||||
|
const turnMeaning = toRecordObject(turnInput?.turn_meaning_ref);
|
||||||
|
const isBusinessOverview = toNullableString(turnMeaning?.asked_domain_family) === "business_overview";
|
||||||
|
if (!isBusinessOverview) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
const separateCandidates = Array.isArray(turnMeaning?.business_overview_separate_entity_candidates)
|
||||||
|
? turnMeaning.business_overview_separate_entity_candidates
|
||||||
|
: [];
|
||||||
|
const separateSubject = firstString([...separateCandidates, turnMeaning?.metadata_scope_hint]);
|
||||||
|
if (!separateSubject) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
const sessionRecord = toRecordObject(input.session);
|
||||||
|
const addressNavigationState = toRecordObject(sessionRecord?.address_navigation_state);
|
||||||
|
const sessionContext = toRecordObject(addressNavigationState?.session_context);
|
||||||
|
const comparisonScope = toRecordObject(sessionContext?.comparison_scope);
|
||||||
|
const comparisonCounterparty = toRecordObject(comparisonScope?.counterparty);
|
||||||
|
const proofBundles = toRecordObject(comparisonScope?.proof_bundles);
|
||||||
|
const valueBundle = toRecordObject(proofBundles?.counterparty_value_flow_bundle);
|
||||||
|
const documentBundle = toRecordObject(proofBundles?.counterparty_document_bundle);
|
||||||
|
if (!valueBundle || !sameBusinessLabel(separateSubject, valueBundle.counterparty ?? comparisonCounterparty?.label)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
const incoming = toRecordObject(valueBundle.incoming_customer_revenue);
|
||||||
|
const outgoing = toRecordObject(valueBundle.outgoing_supplier_payout);
|
||||||
|
const incomingAmount = toNullableString(incoming?.total_amount_human_ru);
|
||||||
|
const outgoingAmount = toNullableString(outgoing?.total_amount_human_ru);
|
||||||
|
const netAmount = toNullableString(valueBundle.net_amount_human_ru);
|
||||||
|
if (!incomingAmount && !outgoingAmount && !netAmount) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
const organization = legalOrganizationLabelFromClarification(input.userMessage)
|
||||||
|
?? toNullableString(turnMeaning?.explicit_organization_scope)
|
||||||
|
?? toNullableString(toRecordObject(comparisonScope?.organization)?.label);
|
||||||
|
const documentCount = Number(toRecordObject(documentBundle)?.document_count);
|
||||||
|
const documentText = Number.isFinite(documentCount) && documentCount > 0 ? `, документы: ${documentCount}` : "";
|
||||||
|
const lines = String(input.baseReply ?? "")
|
||||||
|
.split(/\r?\n/u)
|
||||||
|
.map((line) => line.trim())
|
||||||
|
.filter(Boolean);
|
||||||
|
const companyLine = cleanComparisonScopeCompanyLine(
|
||||||
|
lines[0] ?? `Коротко: по компании ${organization ?? "выбранной организации"} подтвержден общий денежный срез.`,
|
||||||
|
organization
|
||||||
|
);
|
||||||
|
const netDirection =
|
||||||
|
valueBundle.net_direction === "net_outgoing" ? "нетто в минус" : "нетто в нашу сторону";
|
||||||
|
const counterparty = toNullableString(valueBundle.counterparty) ?? separateSubject;
|
||||||
|
return {
|
||||||
|
reply: [
|
||||||
|
`${companyLine}; отдельно по ${counterparty}: получили ${incomingAmount ?? "0 руб."}, заплатили ${outgoingAmount ?? "0 руб."}, ${netDirection} ${netAmount ?? "0 руб."}${documentText}.`,
|
||||||
|
`Отдельно по контрагенту ${counterparty}: это ранее подтвержденный контрагентский срез, а не перенос общих сумм компании на контрагента.`,
|
||||||
|
`Нельзя утверждать: чистую прибыль, полноценный финрезультат, юридические роли клиентов/поставщиков и выводы по ${counterparty} из общих сумм компании без отдельного контрагентского среза.`
|
||||||
|
].join("\n"),
|
||||||
|
audit: {
|
||||||
|
applied: true,
|
||||||
|
source: "address_navigation_state.comparison_scope.proof_bundles",
|
||||||
|
counterparty,
|
||||||
|
organization: organization ?? null,
|
||||||
|
document_count: Number.isFinite(documentCount) && documentCount > 0 ? documentCount : null
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildAppliedMcpDiscoveryRoutePatch(
|
||||||
|
debug: Record<string, unknown>,
|
||||||
|
applied: boolean
|
||||||
|
): Record<string, unknown> {
|
||||||
|
if (!applied) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
const selectedChain = toNullableString(debug.mcp_discovery_selected_chain_id);
|
||||||
|
if (selectedChain === "business_overview") {
|
||||||
|
return {
|
||||||
|
detected_intent: "business_overview",
|
||||||
|
detected_intent_confidence: "high",
|
||||||
|
selected_recipe: "business_overview",
|
||||||
|
response_type: "LIMITED_WITH_REASON",
|
||||||
|
mcp_discovery_effective_response_route: "business_overview"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
function normalizeAddressLaneDebug(value: unknown): AddressExecutionDebug {
|
function normalizeAddressLaneDebug(value: unknown): AddressExecutionDebug {
|
||||||
return (toRecordObject(value) ?? {}) as unknown as AddressExecutionDebug;
|
return (toRecordObject(value) ?? {}) as unknown as AddressExecutionDebug;
|
||||||
}
|
}
|
||||||
|
|
@ -290,18 +425,27 @@ export function runAssistantAddressLaneResponseRuntime<ResponseType = AssistantM
|
||||||
const finalAssistantReply = mcpDiscoveryResponsePolicy.applied
|
const finalAssistantReply = mcpDiscoveryResponsePolicy.applied
|
||||||
? mcpDiscoveryResponsePolicy.reply_text
|
? mcpDiscoveryResponsePolicy.reply_text
|
||||||
: guardedResponse.assistantReply;
|
: guardedResponse.assistantReply;
|
||||||
|
const comparisonScopeProofReply = buildComparisonScopeProofReply({
|
||||||
|
baseReply: finalAssistantReply,
|
||||||
|
debug: debugWithResponseGuard,
|
||||||
|
session: input.getSession(input.sessionId),
|
||||||
|
userMessage: input.userMessage
|
||||||
|
});
|
||||||
|
const finalAssistantReplyWithComparisonProof = comparisonScopeProofReply?.reply ?? finalAssistantReply;
|
||||||
const finalReplyType = mcpDiscoveryResponsePolicy.applied ? "partial_coverage" : guardedResponse.replyType;
|
const finalReplyType = mcpDiscoveryResponsePolicy.applied ? "partial_coverage" : guardedResponse.replyType;
|
||||||
const finalDebug = {
|
const finalDebug = {
|
||||||
...debugWithResponseGuard,
|
...debugWithResponseGuard,
|
||||||
mcp_discovery_response_policy_v1: mcpDiscoveryResponsePolicy,
|
mcp_discovery_response_policy_v1: mcpDiscoveryResponsePolicy,
|
||||||
mcp_discovery_response_candidate_v1: mcpDiscoveryResponsePolicy.candidate,
|
mcp_discovery_response_candidate_v1: mcpDiscoveryResponsePolicy.candidate,
|
||||||
mcp_discovery_response_applied: mcpDiscoveryResponsePolicy.applied
|
mcp_discovery_response_applied: mcpDiscoveryResponsePolicy.applied,
|
||||||
|
comparison_scope_response_augmentation_v1: comparisonScopeProofReply?.audit ?? null,
|
||||||
|
...buildAppliedMcpDiscoveryRoutePatch(debugWithResponseGuard, mcpDiscoveryResponsePolicy.applied)
|
||||||
};
|
};
|
||||||
const finalization = finalizeAddressTurnSafe({
|
const finalization = finalizeAddressTurnSafe({
|
||||||
sessionId: input.sessionId,
|
sessionId: input.sessionId,
|
||||||
userMessage: input.userMessage,
|
userMessage: input.userMessage,
|
||||||
effectiveAddressUserMessage: input.effectiveAddressUserMessage,
|
effectiveAddressUserMessage: input.effectiveAddressUserMessage,
|
||||||
assistantReply: finalAssistantReply,
|
assistantReply: finalAssistantReplyWithComparisonProof,
|
||||||
replyType: finalReplyType,
|
replyType: finalReplyType,
|
||||||
addressLaneDebug: normalizeAddressLaneDebug(input.addressLane.debug),
|
addressLaneDebug: normalizeAddressLaneDebug(input.addressLane.debug),
|
||||||
debug: finalDebug,
|
debug: finalDebug,
|
||||||
|
|
|
||||||
|
|
@ -254,6 +254,332 @@ function mergeBusinessOverviewDateContextForCompactCashflow(input: {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function firstString(
|
||||||
|
values: unknown[],
|
||||||
|
toNonEmptyString: BuildAssistantAddressOrchestrationRuntimeInput["toNonEmptyString"]
|
||||||
|
): string | null {
|
||||||
|
for (const value of values) {
|
||||||
|
const text = toNonEmptyString(value);
|
||||||
|
if (text) {
|
||||||
|
return text;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function comparableEntityName(value: string | null): string | null {
|
||||||
|
const text = compactLower(value);
|
||||||
|
return text ? text.replace(/["'«»„“”]+/g, "") : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function sameEntityHint(expected: string | null, actual: string | null): boolean {
|
||||||
|
const left = comparableEntityName(expected);
|
||||||
|
const right = comparableEntityName(actual);
|
||||||
|
if (!left || !right) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return left === right || left.includes(right) || right.includes(left);
|
||||||
|
}
|
||||||
|
|
||||||
|
function isBusinessOverviewDiscoveryFollowup(
|
||||||
|
followupContext: Record<string, unknown> | null,
|
||||||
|
toNonEmptyString: BuildAssistantAddressOrchestrationRuntimeInput["toNonEmptyString"]
|
||||||
|
): boolean {
|
||||||
|
if (!followupContext) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return [
|
||||||
|
followupContext.previous_discovery_pilot_scope,
|
||||||
|
followupContext.previous_discovery_loop_selected_chain_id,
|
||||||
|
followupContext.previous_discovery_loop_asked_domain_family,
|
||||||
|
followupContext.previous_intent,
|
||||||
|
followupContext.target_intent
|
||||||
|
]
|
||||||
|
.map((value) => toNonEmptyString(value))
|
||||||
|
.some((value) => value === "business_overview" || value === "business_overview_route_template_v1");
|
||||||
|
}
|
||||||
|
|
||||||
|
function businessOverviewCounterpartyHint(
|
||||||
|
followupContext: Record<string, unknown>,
|
||||||
|
toNonEmptyString: BuildAssistantAddressOrchestrationRuntimeInput["toNonEmptyString"]
|
||||||
|
): string | null {
|
||||||
|
const previousFilters = toRecordObject(followupContext.previous_filters);
|
||||||
|
const rootFilters = toRecordObject(followupContext.root_filters);
|
||||||
|
return (
|
||||||
|
toNonEmptyString(followupContext.previous_discovery_loop_metadata_scope_hint) ??
|
||||||
|
(toNonEmptyString(followupContext.previous_anchor_type) === "counterparty"
|
||||||
|
? toNonEmptyString(followupContext.previous_anchor_value)
|
||||||
|
: null) ??
|
||||||
|
toNonEmptyString(previousFilters?.counterparty) ??
|
||||||
|
toNonEmptyString(rootFilters?.counterparty)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function turnMeaningCounterpartyName(
|
||||||
|
turnMeaningRef: Record<string, unknown>,
|
||||||
|
valueBundle: Record<string, unknown> | null,
|
||||||
|
documentBundle: Record<string, unknown> | null,
|
||||||
|
toNonEmptyString: BuildAssistantAddressOrchestrationRuntimeInput["toNonEmptyString"]
|
||||||
|
): string | null {
|
||||||
|
const separateEntities = Array.isArray(turnMeaningRef.business_overview_separate_entity_candidates)
|
||||||
|
? turnMeaningRef.business_overview_separate_entity_candidates
|
||||||
|
: [];
|
||||||
|
return (
|
||||||
|
toNonEmptyString(valueBundle?.counterparty) ??
|
||||||
|
toNonEmptyString(documentBundle?.counterparty) ??
|
||||||
|
toNonEmptyString(turnMeaningRef.metadata_scope_hint) ??
|
||||||
|
firstString(separateEntities, toNonEmptyString)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseBusinessOverviewProofBundlesFromText(
|
||||||
|
value: unknown,
|
||||||
|
toNonEmptyString: BuildAssistantAddressOrchestrationRuntimeInput["toNonEmptyString"]
|
||||||
|
): { valueBundle: Record<string, unknown> | null; documentBundle: Record<string, unknown> | null } | null {
|
||||||
|
const text = toNonEmptyString(value);
|
||||||
|
if (!text) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
const valueMatch = text.match(
|
||||||
|
/Отдельно\s+по\s+контрагенту\s+([^:\n]+):\s*подтверждено\s+получили\s+([^,\n]+?руб\.?),\s*заплатили\s+([^,\n]+?руб\.?),\s*расчетное\s+нетто\s+в\s+нашу\s+сторону\s+([^.\n]+?руб\.?)/iu
|
||||||
|
);
|
||||||
|
const directDocumentMatch = text.match(/Контрагент:\s*([^.\n]+)\.\s*Найдено\s+документов:\s*(\d+)/iu);
|
||||||
|
const summaryDocumentMatch = text.match(/документы\s+по\s+цепочке:\s*найдено\s*(\d+)/iu);
|
||||||
|
const counterparty =
|
||||||
|
toNonEmptyString(valueMatch?.[1]) ?? toNonEmptyString(directDocumentMatch?.[1]);
|
||||||
|
if (!counterparty) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
const valueBundle = valueMatch
|
||||||
|
? {
|
||||||
|
counterparty,
|
||||||
|
incoming_customer_revenue: {
|
||||||
|
total_amount_human_ru: toNonEmptyString(valueMatch[2])
|
||||||
|
},
|
||||||
|
outgoing_supplier_payout: {
|
||||||
|
total_amount_human_ru: toNonEmptyString(valueMatch[3])
|
||||||
|
},
|
||||||
|
net_amount_human_ru: toNonEmptyString(valueMatch[4]),
|
||||||
|
net_direction: "net_incoming",
|
||||||
|
inference_basis: "parsed_from_previous_business_overview_summary"
|
||||||
|
}
|
||||||
|
: null;
|
||||||
|
const documentCount = Number(directDocumentMatch?.[2] ?? summaryDocumentMatch?.[1]);
|
||||||
|
const documentBundle = Number.isFinite(documentCount) && documentCount > 0
|
||||||
|
? {
|
||||||
|
counterparty,
|
||||||
|
document_count: documentCount
|
||||||
|
}
|
||||||
|
: null;
|
||||||
|
return valueBundle || documentBundle ? { valueBundle, documentBundle } : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseBusinessOverviewProofBundlesFromTextV2(
|
||||||
|
value: unknown,
|
||||||
|
counterpartyHint: string | null,
|
||||||
|
toNonEmptyString: BuildAssistantAddressOrchestrationRuntimeInput["toNonEmptyString"]
|
||||||
|
): { valueBundle: Record<string, unknown> | null; documentBundle: Record<string, unknown> | null } | null {
|
||||||
|
const text = toNonEmptyString(value);
|
||||||
|
if (!text) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
const comparableHint = comparableEntityName(counterpartyHint);
|
||||||
|
const lines = text
|
||||||
|
.split(/\r?\n/u)
|
||||||
|
.map((line) => line.trim())
|
||||||
|
.filter(Boolean);
|
||||||
|
const candidateLines = comparableHint
|
||||||
|
? lines.filter((line) => comparableEntityName(line)?.includes(comparableHint))
|
||||||
|
: lines;
|
||||||
|
const rubAmountPattern = /[0-9][0-9\s.,]*\s*\u0440\u0443\u0431\.?/giu;
|
||||||
|
const valueLine = candidateLines.find((line) => (line.match(rubAmountPattern) ?? []).length >= 3) ?? null;
|
||||||
|
const valueAmounts = valueLine?.match(rubAmountPattern) ?? [];
|
||||||
|
const directDocumentMatch = candidateLines
|
||||||
|
.map((line) =>
|
||||||
|
line.match(
|
||||||
|
/\u041a\u043e\u043d\u0442\u0440\u0430\u0433\u0435\u043d\u0442:\s*([^.\n]+)\.\s*\u041d\u0430\u0439\u0434\u0435\u043d\u043e\s+\u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442\u043e\u0432:\s*(\d+)/iu
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.find(Boolean);
|
||||||
|
const summaryDocumentMatch = candidateLines
|
||||||
|
.map((line) =>
|
||||||
|
line.match(
|
||||||
|
/\u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442\u044b\s+\u043f\u043e\s+\u0446\u0435\u043f\u043e\u0447\u043a\u0435:\s*\u043d\u0430\u0439\u0434\u0435\u043d\u043e\s*(\d+)/iu
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.find(Boolean);
|
||||||
|
const counterparty = counterpartyHint ?? toNonEmptyString(directDocumentMatch?.[1]);
|
||||||
|
if (!counterparty) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
const valueBundle = valueAmounts.length >= 3
|
||||||
|
? {
|
||||||
|
counterparty,
|
||||||
|
incoming_customer_revenue: {
|
||||||
|
total_amount_human_ru: toNonEmptyString(valueAmounts[0])
|
||||||
|
},
|
||||||
|
outgoing_supplier_payout: {
|
||||||
|
total_amount_human_ru: toNonEmptyString(valueAmounts[1])
|
||||||
|
},
|
||||||
|
net_amount_human_ru: toNonEmptyString(valueAmounts[2]),
|
||||||
|
net_direction: "net_incoming",
|
||||||
|
inference_basis: "parsed_from_previous_business_overview_summary"
|
||||||
|
}
|
||||||
|
: null;
|
||||||
|
const documentCount = Number(directDocumentMatch?.[2] ?? summaryDocumentMatch?.[1]);
|
||||||
|
const documentBundle = Number.isFinite(documentCount) && documentCount > 0
|
||||||
|
? {
|
||||||
|
counterparty,
|
||||||
|
document_count: documentCount
|
||||||
|
}
|
||||||
|
: null;
|
||||||
|
return valueBundle || documentBundle ? { valueBundle, documentBundle } : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function findRecentBusinessOverviewProofBundles(input: {
|
||||||
|
sessionItems: unknown[];
|
||||||
|
counterpartyHint: string | null;
|
||||||
|
toNonEmptyString: BuildAssistantAddressOrchestrationRuntimeInput["toNonEmptyString"];
|
||||||
|
}): { valueBundle: Record<string, unknown> | null; documentBundle: Record<string, unknown> | null } | null {
|
||||||
|
if (!input.counterpartyHint) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
for (let index = input.sessionItems.length - 1; index >= 0; index -= 1) {
|
||||||
|
const item = toRecordObject(input.sessionItems[index]);
|
||||||
|
if (input.toNonEmptyString(item?.role) !== "assistant") {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const debug = toRecordObject(item?.debug);
|
||||||
|
const entryPoint = toRecordObject(debug?.assistant_mcp_discovery_entry_point_v1);
|
||||||
|
const turnInput = toRecordObject(entryPoint?.turn_input);
|
||||||
|
const turnMeaningRef = toRecordObject(turnInput?.turn_meaning_ref);
|
||||||
|
if (turnMeaningRef) {
|
||||||
|
const valueBundle = toRecordObject(turnMeaningRef.previous_counterparty_value_flow_bundle);
|
||||||
|
const documentBundle = toRecordObject(turnMeaningRef.previous_counterparty_document_bundle);
|
||||||
|
if (valueBundle || documentBundle) {
|
||||||
|
const bundleCounterparty = turnMeaningCounterpartyName(
|
||||||
|
turnMeaningRef,
|
||||||
|
valueBundle,
|
||||||
|
documentBundle,
|
||||||
|
input.toNonEmptyString
|
||||||
|
);
|
||||||
|
if (sameEntityHint(input.counterpartyHint, bundleCounterparty)) {
|
||||||
|
return { valueBundle, documentBundle };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const parsedBundles =
|
||||||
|
parseBusinessOverviewProofBundlesFromTextV2(item?.text, input.counterpartyHint, input.toNonEmptyString) ??
|
||||||
|
parseBusinessOverviewProofBundlesFromText(item?.text, input.toNonEmptyString);
|
||||||
|
if (!parsedBundles) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const parsedCounterparty =
|
||||||
|
input.toNonEmptyString(parsedBundles.valueBundle?.counterparty) ??
|
||||||
|
input.toNonEmptyString(parsedBundles.documentBundle?.counterparty);
|
||||||
|
if (!sameEntityHint(input.counterpartyHint, parsedCounterparty)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
return parsedBundles;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function mergeBusinessOverviewProofBundlesFromNavigationState(input: {
|
||||||
|
followupContext: Record<string, unknown> | null;
|
||||||
|
sessionAddressNavigationState: unknown;
|
||||||
|
predecomposeContract: Record<string, unknown> | null;
|
||||||
|
toNonEmptyString: BuildAssistantAddressOrchestrationRuntimeInput["toNonEmptyString"];
|
||||||
|
}): Record<string, unknown> | null {
|
||||||
|
const currentValueBundle = toRecordObject(input.followupContext?.previous_discovery_bidirectional_value_flow);
|
||||||
|
const currentDocumentBundle = toRecordObject(input.followupContext?.previous_discovery_document_summary);
|
||||||
|
if (currentValueBundle && currentDocumentBundle) {
|
||||||
|
return input.followupContext;
|
||||||
|
}
|
||||||
|
const state = toRecordObject(input.sessionAddressNavigationState);
|
||||||
|
const sessionContext = toRecordObject(state?.session_context);
|
||||||
|
const comparisonScope = toRecordObject(sessionContext?.comparison_scope);
|
||||||
|
const proofBundles = toRecordObject(comparisonScope?.proof_bundles);
|
||||||
|
const valueBundle = toRecordObject(proofBundles?.counterparty_value_flow_bundle);
|
||||||
|
const documentBundle = toRecordObject(proofBundles?.counterparty_document_bundle);
|
||||||
|
if (!valueBundle && !documentBundle) {
|
||||||
|
return input.followupContext;
|
||||||
|
}
|
||||||
|
const entities = toRecordObject(input.predecomposeContract?.entities);
|
||||||
|
const hasCurrentOrganizationSelection = Boolean(input.toNonEmptyString(entities?.organization));
|
||||||
|
const businessOverviewFollowup = isBusinessOverviewDiscoveryFollowup(input.followupContext, input.toNonEmptyString);
|
||||||
|
if (!businessOverviewFollowup && !hasCurrentOrganizationSelection) {
|
||||||
|
return input.followupContext;
|
||||||
|
}
|
||||||
|
const comparisonCounterparty = toRecordObject(comparisonScope?.counterparty);
|
||||||
|
const counterpartyHint =
|
||||||
|
(input.followupContext ? businessOverviewCounterpartyHint(input.followupContext, input.toNonEmptyString) : null) ??
|
||||||
|
input.toNonEmptyString(comparisonCounterparty?.label);
|
||||||
|
const bundleCounterparty =
|
||||||
|
input.toNonEmptyString(valueBundle?.counterparty) ?? input.toNonEmptyString(documentBundle?.counterparty);
|
||||||
|
if (!counterpartyHint || !sameEntityHint(counterpartyHint, bundleCounterparty)) {
|
||||||
|
return input.followupContext;
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
...(input.followupContext ?? {}),
|
||||||
|
previous_intent: input.toNonEmptyString(input.followupContext?.previous_intent) ?? "business_overview",
|
||||||
|
target_intent: input.toNonEmptyString(input.followupContext?.target_intent) ?? "business_overview",
|
||||||
|
previous_discovery_pilot_scope:
|
||||||
|
input.toNonEmptyString(input.followupContext?.previous_discovery_pilot_scope) ??
|
||||||
|
"business_overview_route_template_v1",
|
||||||
|
previous_discovery_loop_status:
|
||||||
|
input.toNonEmptyString(input.followupContext?.previous_discovery_loop_status) ?? "awaiting_clarification",
|
||||||
|
previous_discovery_loop_selected_chain_id:
|
||||||
|
input.toNonEmptyString(input.followupContext?.previous_discovery_loop_selected_chain_id) ?? "business_overview",
|
||||||
|
previous_discovery_loop_pending_axes: Array.isArray(input.followupContext?.previous_discovery_loop_pending_axes)
|
||||||
|
? input.followupContext?.previous_discovery_loop_pending_axes
|
||||||
|
: ["organization"],
|
||||||
|
previous_discovery_loop_asked_domain_family:
|
||||||
|
input.toNonEmptyString(input.followupContext?.previous_discovery_loop_asked_domain_family) ?? "business_overview",
|
||||||
|
previous_discovery_loop_asked_action_family:
|
||||||
|
input.toNonEmptyString(input.followupContext?.previous_discovery_loop_asked_action_family) ?? "broad_evaluation",
|
||||||
|
previous_discovery_loop_metadata_scope_hint:
|
||||||
|
input.toNonEmptyString(input.followupContext?.previous_discovery_loop_metadata_scope_hint) ?? counterpartyHint,
|
||||||
|
previous_anchor_type: input.toNonEmptyString(input.followupContext?.previous_anchor_type) ?? "counterparty",
|
||||||
|
previous_anchor_value: input.toNonEmptyString(input.followupContext?.previous_anchor_value) ?? counterpartyHint,
|
||||||
|
previous_filters: toRecordObject(input.followupContext?.previous_filters) ?? {},
|
||||||
|
previous_discovery_bidirectional_value_flow: currentValueBundle ?? valueBundle ?? undefined,
|
||||||
|
previous_discovery_document_summary: currentDocumentBundle ?? documentBundle ?? undefined
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function mergeBusinessOverviewProofBundlesFromSessionItems(input: {
|
||||||
|
followupContext: Record<string, unknown> | null;
|
||||||
|
sessionItems: unknown[];
|
||||||
|
toNonEmptyString: BuildAssistantAddressOrchestrationRuntimeInput["toNonEmptyString"];
|
||||||
|
}): Record<string, unknown> | null {
|
||||||
|
if (!isBusinessOverviewDiscoveryFollowup(input.followupContext, input.toNonEmptyString)) {
|
||||||
|
return input.followupContext;
|
||||||
|
}
|
||||||
|
const currentValueBundle = toRecordObject(input.followupContext?.previous_discovery_bidirectional_value_flow);
|
||||||
|
const currentDocumentBundle = toRecordObject(input.followupContext?.previous_discovery_document_summary);
|
||||||
|
if (currentValueBundle && currentDocumentBundle) {
|
||||||
|
return input.followupContext;
|
||||||
|
}
|
||||||
|
const counterpartyHint = input.followupContext
|
||||||
|
? businessOverviewCounterpartyHint(input.followupContext, input.toNonEmptyString)
|
||||||
|
: null;
|
||||||
|
const proofBundles = findRecentBusinessOverviewProofBundles({
|
||||||
|
sessionItems: input.sessionItems,
|
||||||
|
counterpartyHint,
|
||||||
|
toNonEmptyString: input.toNonEmptyString
|
||||||
|
});
|
||||||
|
if (!proofBundles) {
|
||||||
|
return input.followupContext;
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
...(input.followupContext ?? {}),
|
||||||
|
previous_discovery_bidirectional_value_flow:
|
||||||
|
currentValueBundle ?? proofBundles.valueBundle ?? undefined,
|
||||||
|
previous_discovery_document_summary: currentDocumentBundle ?? proofBundles.documentBundle ?? undefined
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
function hasSelectedObjectInventorySignal(text: string | null): boolean {
|
function hasSelectedObjectInventorySignal(text: string | null): boolean {
|
||||||
return /(?:по\s+выбранному\s+объекту|по\s+выбранной\s+позиции|по\s+этой\s+позиции|по\s+этому\s+товару|по\s+ним|selected\s+object)/iu.test(
|
return /(?:по\s+выбранному\s+объекту|по\s+выбранной\s+позиции|по\s+этой\s+позиции|по\s+этому\s+товару|по\s+ним|selected\s+object)/iu.test(
|
||||||
String(text ?? "")
|
String(text ?? "")
|
||||||
|
|
@ -525,6 +851,17 @@ export async function buildAssistantAddressOrchestrationRuntime(
|
||||||
sessionItems: input.sessionItems,
|
sessionItems: input.sessionItems,
|
||||||
toNonEmptyString: input.toNonEmptyString
|
toNonEmptyString: input.toNonEmptyString
|
||||||
});
|
});
|
||||||
|
const discoveryFollowupContextWithProofBundles = mergeBusinessOverviewProofBundlesFromSessionItems({
|
||||||
|
followupContext: discoveryFollowupContext,
|
||||||
|
sessionItems: input.sessionItems,
|
||||||
|
toNonEmptyString: input.toNonEmptyString
|
||||||
|
});
|
||||||
|
const discoveryFollowupContextWithStateProofBundles = mergeBusinessOverviewProofBundlesFromNavigationState({
|
||||||
|
followupContext: discoveryFollowupContextWithProofBundles,
|
||||||
|
sessionAddressNavigationState: input.sessionAddressNavigationState,
|
||||||
|
predecomposeContract,
|
||||||
|
toNonEmptyString: input.toNonEmptyString
|
||||||
|
});
|
||||||
const dialogContinuationContract = input.buildAddressDialogContinuationContractV2(
|
const dialogContinuationContract = input.buildAddressDialogContinuationContractV2(
|
||||||
input.userMessage,
|
input.userMessage,
|
||||||
addressInputMessage,
|
addressInputMessage,
|
||||||
|
|
@ -540,7 +877,7 @@ export async function buildAssistantAddressOrchestrationRuntime(
|
||||||
effectiveMessage: addressInputMessage,
|
effectiveMessage: addressInputMessage,
|
||||||
assistantTurnMeaning: toRecordObject(orchestrationContract?.assistant_turn_meaning),
|
assistantTurnMeaning: toRecordObject(orchestrationContract?.assistant_turn_meaning),
|
||||||
predecomposeContract,
|
predecomposeContract,
|
||||||
followupContext: discoveryFollowupContext,
|
followupContext: discoveryFollowupContextWithStateProofBundles,
|
||||||
knownOrganizations: sessionKnownOrganizations(input.sessionOrganizationScope ?? null)
|
knownOrganizations: sessionKnownOrganizations(input.sessionOrganizationScope ?? null)
|
||||||
})) as Record<string, unknown>;
|
})) as Record<string, unknown>;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|
|
||||||
|
|
@ -414,6 +414,9 @@ function mapAssistantMcpDiscoveryPilotScopeToAddressIntent(
|
||||||
pilotScope: string | null,
|
pilotScope: string | null,
|
||||||
actionFamily: string | null
|
actionFamily: string | null
|
||||||
): string | null {
|
): string | null {
|
||||||
|
if (pilotScope === "business_overview_route_template_v1" || actionFamily === "broad_evaluation") {
|
||||||
|
return "business_overview";
|
||||||
|
}
|
||||||
if (pilotScope === "counterparty_lifecycle_query_documents_v1") {
|
if (pilotScope === "counterparty_lifecycle_query_documents_v1") {
|
||||||
return "counterparty_activity_lifecycle";
|
return "counterparty_activity_lifecycle";
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -131,6 +131,20 @@ function buildDeterministicSmalltalkLeadReply(): string {
|
||||||
return "\u041f\u0440\u0438\u0432\u0435\u0442! \u0412\u0441\u0451 \u043d\u043e\u0440\u043c\u0430\u043b\u044c\u043d\u043e.";
|
return "\u041f\u0440\u0438\u0432\u0435\u0442! \u0412\u0441\u0451 \u043d\u043e\u0440\u043c\u0430\u043b\u044c\u043d\u043e.";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function hasFirstTurnSmalltalkGreetingSignal(value: unknown): boolean {
|
||||||
|
const normalized = String(value ?? "")
|
||||||
|
.toLowerCase()
|
||||||
|
.replace(/\u0451/gu, "\u0435")
|
||||||
|
.replace(/\s+/gu, " ")
|
||||||
|
.trim();
|
||||||
|
if (!normalized) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return /^(?:привет(?:ик)?|здравствуй(?:те)?|хай|йо|yo|hello|hi|че\s+как|че\s+там)(?:[\s,.!?;:()\-]+(?:как|дела|там|че|что|у\s+тебя|как\s+там|как\s+дела))*[\s,.!?;:()\-]*$/iu.test(
|
||||||
|
normalized
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
function hasConversationExecutiveSummarySignal(value: unknown): boolean {
|
function hasConversationExecutiveSummarySignal(value: unknown): boolean {
|
||||||
const normalized = String(value ?? "")
|
const normalized = String(value ?? "")
|
||||||
.toLowerCase()
|
.toLowerCase()
|
||||||
|
|
@ -215,6 +229,14 @@ export async function runAssistantLivingChatRuntime(
|
||||||
let knownOrganizations = [...organizationAuthority.knownOrganizations];
|
let knownOrganizations = [...organizationAuthority.knownOrganizations];
|
||||||
let selectedOrganization = organizationAuthority.selectedOrganization;
|
let selectedOrganization = organizationAuthority.selectedOrganization;
|
||||||
let activeOrganization = organizationAuthority.activeOrganization;
|
let activeOrganization = organizationAuthority.activeOrganization;
|
||||||
|
const shouldHandleFirstTurnSmalltalkDeterministically =
|
||||||
|
!selectedOrganization &&
|
||||||
|
!activeOrganization &&
|
||||||
|
!continuitySnapshot.hasGroundedAddressContext &&
|
||||||
|
!hasPriorAssistantTurn(input.sessionItems) &&
|
||||||
|
input.modeDecision?.mode === "chat" &&
|
||||||
|
hasFirstTurnSmalltalkGreetingSignal(userMessage) &&
|
||||||
|
input.hasLivingChatSignal(userMessage);
|
||||||
const addressRuntimeMeta = (input.addressRuntimeMeta && typeof input.addressRuntimeMeta === "object"
|
const addressRuntimeMeta = (input.addressRuntimeMeta && typeof input.addressRuntimeMeta === "object"
|
||||||
? input.addressRuntimeMeta
|
? input.addressRuntimeMeta
|
||||||
: {}) as Record<string, unknown>;
|
: {}) as Record<string, unknown>;
|
||||||
|
|
@ -365,6 +387,27 @@ export async function runAssistantLivingChatRuntime(
|
||||||
} else if (capabilityMetaQuery) {
|
} else if (capabilityMetaQuery) {
|
||||||
chatText = input.buildAssistantCapabilityContractReply(userMessage);
|
chatText = input.buildAssistantCapabilityContractReply(userMessage);
|
||||||
livingChatSource = "deterministic_capability_contract";
|
livingChatSource = "deterministic_capability_contract";
|
||||||
|
} else if (shouldHandleFirstTurnSmalltalkDeterministically) {
|
||||||
|
const proactiveScopeProbe = await input.resolveDataScopeProbe();
|
||||||
|
const mergedKnownOrganizations = input.mergeKnownOrganizations([
|
||||||
|
...knownOrganizations,
|
||||||
|
...(Array.isArray(proactiveScopeProbe?.organizations) ? (proactiveScopeProbe.organizations as unknown[]) : [])
|
||||||
|
]);
|
||||||
|
knownOrganizations = mergedKnownOrganizations;
|
||||||
|
if (!activeOrganization && mergedKnownOrganizations.length === 1) {
|
||||||
|
activeOrganization = mergedKnownOrganizations[0];
|
||||||
|
}
|
||||||
|
const proactiveOffer = input.buildAssistantProactiveOrganizationOfferReply(proactiveScopeProbe);
|
||||||
|
chatText = [buildDeterministicSmalltalkLeadReply(), proactiveOffer]
|
||||||
|
.filter((part) => String(part ?? "").trim().length > 0)
|
||||||
|
.join(" ");
|
||||||
|
livingChatProactiveScopeOfferApplied = Boolean(proactiveOffer);
|
||||||
|
livingChatSource = proactiveOffer
|
||||||
|
? "deterministic_smalltalk_with_proactive_scope_offer"
|
||||||
|
: "deterministic_smalltalk";
|
||||||
|
if (!dataScopeProbe) {
|
||||||
|
dataScopeProbe = proactiveScopeProbe;
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
chatText = await input.executeLlmChat();
|
chatText = await input.executeLlmChat();
|
||||||
const scriptGuard = input.applyScriptGuard(chatText, userMessage);
|
const scriptGuard = input.applyScriptGuard(chatText, userMessage);
|
||||||
|
|
@ -385,36 +428,6 @@ export async function runAssistantLivingChatRuntime(
|
||||||
livingChatGroundingGuardReason = groundingGuard.reason;
|
livingChatGroundingGuardReason = groundingGuard.reason;
|
||||||
livingChatSource = "llm_chat_grounding_guard";
|
livingChatSource = "llm_chat_grounding_guard";
|
||||||
}
|
}
|
||||||
|
|
||||||
const shouldOfferProactiveOrganizationScope =
|
|
||||||
!selectedOrganization &&
|
|
||||||
!activeOrganization &&
|
|
||||||
!continuitySnapshot.hasGroundedAddressContext &&
|
|
||||||
!hasPriorAssistantTurn(input.sessionItems) &&
|
|
||||||
input.modeDecision?.mode === "chat" &&
|
|
||||||
input.hasLivingChatSignal(userMessage);
|
|
||||||
if (shouldOfferProactiveOrganizationScope) {
|
|
||||||
const proactiveScopeProbe = await input.resolveDataScopeProbe();
|
|
||||||
const mergedKnownOrganizations = input.mergeKnownOrganizations([
|
|
||||||
...knownOrganizations,
|
|
||||||
...(Array.isArray(proactiveScopeProbe?.organizations) ? (proactiveScopeProbe.organizations as unknown[]) : [])
|
|
||||||
]);
|
|
||||||
knownOrganizations = mergedKnownOrganizations;
|
|
||||||
if (!activeOrganization && mergedKnownOrganizations.length === 1) {
|
|
||||||
activeOrganization = mergedKnownOrganizations[0];
|
|
||||||
}
|
|
||||||
const proactiveOffer = input.buildAssistantProactiveOrganizationOfferReply(proactiveScopeProbe);
|
|
||||||
if (proactiveOffer) {
|
|
||||||
chatText = [buildDeterministicSmalltalkLeadReply(), proactiveOffer]
|
|
||||||
.filter((part) => String(part ?? "").trim().length > 0)
|
|
||||||
.join(" ");
|
|
||||||
livingChatProactiveScopeOfferApplied = true;
|
|
||||||
livingChatSource = "deterministic_smalltalk_with_proactive_scope_offer";
|
|
||||||
if (!dataScopeProbe) {
|
|
||||||
dataScopeProbe = proactiveScopeProbe;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!chatText) {
|
if (!chatText) {
|
||||||
|
|
|
||||||
|
|
@ -42,7 +42,11 @@ export interface AssistantMcpDiscoveryExecutionHandoffContract {
|
||||||
reason_codes: string[];
|
reason_codes: string[];
|
||||||
}
|
}
|
||||||
|
|
||||||
const HOT_HANDOFF_CHAIN_ALLOWLIST: AssistantMcpDiscoveryChainId[] = ["value_flow"];
|
const HOT_HANDOFF_CHAIN_ALLOWLIST: AssistantMcpDiscoveryChainId[] = [
|
||||||
|
"value_flow",
|
||||||
|
"value_flow_comparison",
|
||||||
|
"business_overview"
|
||||||
|
];
|
||||||
|
|
||||||
function uniqueStrings(values: string[]): string[] {
|
function uniqueStrings(values: string[]): string[] {
|
||||||
const result: string[] = [];
|
const result: string[] = [];
|
||||||
|
|
|
||||||
|
|
@ -47,19 +47,66 @@ function normalizeQuestionText(value: unknown): string {
|
||||||
.trim();
|
.trim();
|
||||||
}
|
}
|
||||||
|
|
||||||
function requestsFinancialCounterpartyBoundary(turnMeaning: Record<string, unknown> | null, graph: Record<string, unknown> | null): boolean {
|
function normalizedTurnAndGraphText(
|
||||||
const text = normalizeQuestionText([
|
turnMeaning: Record<string, unknown> | null,
|
||||||
|
graph: Record<string, unknown> | null
|
||||||
|
): string {
|
||||||
|
return normalizeQuestionText([
|
||||||
turnMeaning?.raw_message,
|
turnMeaning?.raw_message,
|
||||||
turnMeaning?.effective_message,
|
turnMeaning?.effective_message,
|
||||||
graph?.source_message,
|
graph?.source_message,
|
||||||
graph?.question
|
graph?.question
|
||||||
].join(" "));
|
].join(" "));
|
||||||
|
}
|
||||||
|
|
||||||
|
function requestsFinancialCounterpartyBoundary(turnMeaning: Record<string, unknown> | null, graph: Record<string, unknown> | null): boolean {
|
||||||
|
const text = normalizedTurnAndGraphText(turnMeaning, graph);
|
||||||
return (
|
return (
|
||||||
/(?:банк|сбербанк|финанс|кредит|депозит)/iu.test(text) &&
|
/(?:банк|сбербанк|финанс|кредит|депозит)/iu.test(text) &&
|
||||||
/(?:клиент|поставщик|выручк|топ|обычн|роль|поток)/iu.test(text)
|
/(?:клиент|поставщик|выручк|топ|обычн|роль|поток)/iu.test(text)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function requestsBroadBusinessOverviewSurface(
|
||||||
|
turnMeaning: Record<string, unknown> | null,
|
||||||
|
graph: Record<string, unknown> | null
|
||||||
|
): boolean {
|
||||||
|
const text = normalizedTurnAndGraphText(turnMeaning, graph);
|
||||||
|
if (!text) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (/(?:не\s+обзор|просто\s+ден\p{L}*|одной\s+строк\p{L}*|только\s+итог|без\s+разбив\p{L}*)/iu.test(text)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (/(?:бизнес[-\s]*обзор|взросл\p{L}{0,10}\s+бизнес|что\s+(?:пока\s+)?нельзя\s+утвержд)/iu.test(text)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
const markers = [
|
||||||
|
/(?:ндс|налог\p{L}*)/iu,
|
||||||
|
/(?:долг\p{L}*|дебитор|кредитор)/iu,
|
||||||
|
/(?:склад|остатк|товар\p{L}*)/iu,
|
||||||
|
/(?:клиент|заказчик|покупател)/iu,
|
||||||
|
/(?:поставщик|получател)/iu,
|
||||||
|
/(?:оборот\p{L}*)/iu,
|
||||||
|
/(?:ограничен|не\s+подтвержд|нельзя\s+утвержд)/iu
|
||||||
|
];
|
||||||
|
return markers.filter((marker) => marker.test(text)).length >= 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
function requestsCounterpartyLeaderSurface(
|
||||||
|
turnMeaning: Record<string, unknown> | null,
|
||||||
|
graph: Record<string, unknown> | null
|
||||||
|
): boolean {
|
||||||
|
const text = normalizedTurnAndGraphText(turnMeaning, graph);
|
||||||
|
if (!text) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (/(?:кто|кому)[\s\S]{0,60}(?:больше\s+всего|крупнее\s+всего|основн\p{L}*)[\s\S]{0,60}(?:зан[её]с|прин[её]с|платил|ушло|получил|перев[её]л|заплатил|внес|вн[её]с)/iu.test(text)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return /(?:(?:кто|как\p{L}*|покаж\p{L}*|назов\p{L}*|раскро\p{L}*)[\s\S]{0,100}(?:главн\p{L}*|крупнейш\p{L}*|основн\p{L}*|ведущ\p{L}*|топ)[\s\S]{0,80}(?:клиент|заказчик|поставщик|получател)|(?:главн\p{L}*|крупнейш\p{L}*|основн\p{L}*|ведущ\p{L}*)[\s\S]{0,80}(?:клиент|заказчик|поставщик|получател)|(?:топ[-\s]*(?:клиент|заказчик|поставщик|получател))|(?:клиент|поставщик)[\s\S]{0,80}(?:главн|крупнейш|основн|ведущ|топ))/iu.test(text);
|
||||||
|
}
|
||||||
|
|
||||||
function requestsCompactCashflowAnswer(
|
function requestsCompactCashflowAnswer(
|
||||||
turnMeaning: Record<string, unknown> | null,
|
turnMeaning: Record<string, unknown> | null,
|
||||||
graph: Record<string, unknown> | null
|
graph: Record<string, unknown> | null
|
||||||
|
|
@ -850,6 +897,49 @@ function buildPreviousCounterpartyValueFlowSummary(
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function buildBoundarySummaryFromPreviousCounterpartyBundles(
|
||||||
|
entryPoint: AssistantMcpDiscoveryRuntimeEntryPointContract
|
||||||
|
): string | null {
|
||||||
|
const turnInput = toRecordObject(entryPoint.turn_input);
|
||||||
|
const turnMeaning = toRecordObject(turnInput?.turn_meaning_ref);
|
||||||
|
const graph = toRecordObject(turnInput?.data_need_graph);
|
||||||
|
const bridge = toRecordObject(entryPoint.bridge);
|
||||||
|
const pilot = toRecordObject(bridge?.pilot);
|
||||||
|
const overview = toRecordObject(pilot?.derived_business_overview);
|
||||||
|
const isBusinessOverview =
|
||||||
|
toNonEmptyString(graph?.business_fact_family) === "business_overview" ||
|
||||||
|
toNonEmptyString(turnMeaning?.asked_domain_family) === "business_overview";
|
||||||
|
if (!isBusinessOverview || overview) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const organizationScope = businessOverviewOrganizationScopeLabel(turnMeaning?.explicit_organization_scope);
|
||||||
|
const separateSubject = businessOverviewSeparateSubjectLabel(graph, turnMeaning, organizationScope);
|
||||||
|
const previousCounterpartySummary = buildPreviousCounterpartyValueFlowSummary(
|
||||||
|
toRecordObject(turnMeaning?.previous_counterparty_value_flow_bundle),
|
||||||
|
separateSubject,
|
||||||
|
toRecordObject(turnMeaning?.previous_counterparty_document_bundle)
|
||||||
|
);
|
||||||
|
if (!separateSubject || !previousCounterpartySummary) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const lines = organizationScope
|
||||||
|
? [
|
||||||
|
`Коротко: по компании ${organizationScope} в этом шаге нет нового полного company-level расчета; отдельно по выбранному контрагенту ${separateSubject} есть ранее подтвержденный контрагентский срез.`,
|
||||||
|
previousCounterpartySummary.line,
|
||||||
|
`Можно утверждать: по ${separateSubject} отдельно подтверждены входящие/исходящие денежные строки, расчетное нетто и документы из предыдущего контрагентского среза.`,
|
||||||
|
`Нельзя утверждать: это не подтверждает чистую прибыль, полный оборот или общую бизнес-роль ${separateSubject}; также нельзя смешивать этот контрагентский срез с выводами по компании без отдельного company-level расчета.`
|
||||||
|
]
|
||||||
|
: [
|
||||||
|
`Коротко: уточните, по какой компании/организации сравнить выбранного контрагента ${separateSubject}; company-level вывод без организации не подтверждаю.`,
|
||||||
|
previousCounterpartySummary.line,
|
||||||
|
`Уже можно утверждать: по ${separateSubject} отдельно подтверждены входящие/исходящие денежные строки, расчетное нетто и документы из предыдущего контрагентского среза.`,
|
||||||
|
`Нельзя утверждать: это не чистая прибыль, не полный оборот компании и не доказанная бизнес-роль ${separateSubject}; контрагентский срез нельзя смешивать с company-level выводом без выбранной компании.`
|
||||||
|
];
|
||||||
|
return joinBusinessReplyLines(lines);
|
||||||
|
}
|
||||||
|
|
||||||
function buildCompactBusinessOverviewReply(
|
function buildCompactBusinessOverviewReply(
|
||||||
entryPoint: AssistantMcpDiscoveryRuntimeEntryPointContract,
|
entryPoint: AssistantMcpDiscoveryRuntimeEntryPointContract,
|
||||||
draft: Record<string, unknown>
|
draft: Record<string, unknown>
|
||||||
|
|
@ -921,6 +1011,13 @@ function buildCompactBusinessOverviewReply(
|
||||||
: null;
|
: null;
|
||||||
const graphReasonCodes = toStringList(graph?.reason_codes);
|
const graphReasonCodes = toStringList(graph?.reason_codes);
|
||||||
const directMoneyAnswer = graphReasonCodes.includes("data_need_graph_business_overview_direct_money_answer");
|
const directMoneyAnswer = graphReasonCodes.includes("data_need_graph_business_overview_direct_money_answer");
|
||||||
|
const broadOverviewSurfaceRequested = requestsBroadBusinessOverviewSurface(turnMeaning, graph);
|
||||||
|
const counterpartyLeaderSurfaceRequested = requestsCounterpartyLeaderSurface(turnMeaning, graph);
|
||||||
|
const directMoneyOnlyAnswer =
|
||||||
|
directMoneyAnswer && !broadOverviewSurfaceRequested && !counterpartyLeaderSurfaceRequested;
|
||||||
|
const shouldIncludeCounterpartyLeaders =
|
||||||
|
!directMoneyOnlyAnswer || counterpartyLeaderSurfaceRequested || broadOverviewSurfaceRequested;
|
||||||
|
const shouldIncludeOverviewSurface = !directMoneyAnswer || broadOverviewSurfaceRequested;
|
||||||
const crossScopeExecutiveSummary = Boolean(separateSubject && previousCounterpartySummary);
|
const crossScopeExecutiveSummary = Boolean(separateSubject && previousCounterpartySummary);
|
||||||
const lines: string[] = [];
|
const lines: string[] = [];
|
||||||
const actionFamily = toNonEmptyString(turnMeaning?.asked_action_family);
|
const actionFamily = toNonEmptyString(turnMeaning?.asked_action_family);
|
||||||
|
|
@ -931,9 +1028,22 @@ function buildCompactBusinessOverviewReply(
|
||||||
actionFamily === "vendor_risk_procurement_boundary" || unsupportedFamily === "vendor_risk_procurement_boundary";
|
actionFamily === "vendor_risk_procurement_boundary" || unsupportedFamily === "vendor_risk_procurement_boundary";
|
||||||
const inventoryReserveBoundary =
|
const inventoryReserveBoundary =
|
||||||
actionFamily === "inventory_reserve_boundary" || unsupportedFamily === "inventory_reserve_liquidation_boundary";
|
actionFamily === "inventory_reserve_boundary" || unsupportedFamily === "inventory_reserve_liquidation_boundary";
|
||||||
const compactCashflowRequested = directMoneyAnswer && requestsCompactCashflowAnswer(turnMeaning, graph);
|
const compactCashflowRequested = directMoneyOnlyAnswer && requestsCompactCashflowAnswer(turnMeaning, graph);
|
||||||
const cashflowPolarityRequested = compactCashflowRequested && requestsCashflowPolarityAnswer(turnMeaning, graph);
|
const cashflowPolarityRequested = compactCashflowRequested && requestsCashflowPolarityAnswer(turnMeaning, graph);
|
||||||
const directAccountingProfitRequested = requestsDirectAccountingProfitAnswer(turnMeaning, graph);
|
const directAccountingProfitRequested = requestsDirectAccountingProfitAnswer(turnMeaning, graph);
|
||||||
|
const rawMessage = toNonEmptyString(turnMeaning?.raw_message) ?? toNonEmptyString(turnMeaning?.effective_message);
|
||||||
|
const rawMessageComparable = compactComparable(rawMessage);
|
||||||
|
const organizationScopeComparable = compactComparable(organizationScope);
|
||||||
|
const plainOrganizationClarificationSelection = Boolean(
|
||||||
|
separateSubject &&
|
||||||
|
organizationScope &&
|
||||||
|
rawMessage &&
|
||||||
|
rawMessageComparable &&
|
||||||
|
organizationScopeComparable &&
|
||||||
|
rawMessageComparable.includes(organizationScopeComparable) &&
|
||||||
|
rawMessage.length <= 90 &&
|
||||||
|
!/(?:сравн|подтвержд|деньг|сколько|что\s+|покаж|дай|вывод|нельзя|клиент|поставщик|\?)/iu.test(rawMessage)
|
||||||
|
);
|
||||||
|
|
||||||
if (compactCashflowRequested && !rankingNeed && (incomingAmount || outgoingAmount || netAmount)) {
|
if (compactCashflowRequested && !rankingNeed && (incomingAmount || outgoingAmount || netAmount)) {
|
||||||
const netDisplay = sentenceAmount(netAmount) ?? netAmount ?? "0 \u0440\u0443\u0431.";
|
const netDisplay = sentenceAmount(netAmount) ?? netAmount ?? "0 \u0440\u0443\u0431.";
|
||||||
|
|
@ -955,6 +1065,23 @@ function buildCompactBusinessOverviewReply(
|
||||||
return joinBusinessReplyLines(lines);
|
return joinBusinessReplyLines(lines);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (plainOrganizationClarificationSelection && (incomingAmount || outgoingAmount || netAmount)) {
|
||||||
|
lines.push(
|
||||||
|
`Коротко: по компании ${organizationScope} ${period} подтвержден company-level денежный срез: входящие ${incomingAmount ?? "0 руб."}, исходящие ${outgoingAmount ?? "0 руб."}, операционное нетто ${sentenceAmount(netAmount) ?? netAmount ?? "0 руб."}.`
|
||||||
|
);
|
||||||
|
if (previousCounterpartySummary) {
|
||||||
|
lines.push(previousCounterpartySummary.line);
|
||||||
|
} else {
|
||||||
|
lines.push(
|
||||||
|
`Отдельно по выбранному контрагенту ${separateSubject}: суммы компании на него не переношу; в этом шаге держу только границу, что это отдельный контрагентский контур.`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
lines.push(
|
||||||
|
`Нельзя утверждать: чистую прибыль, полноценный финрезультат, юридические роли клиентов/поставщиков и выводы по ${separateSubject} на основе company-level сумм без отдельного контрагентского среза.`
|
||||||
|
);
|
||||||
|
return joinBusinessReplyLines(lines);
|
||||||
|
}
|
||||||
|
|
||||||
if (profitMarginBoundary) {
|
if (profitMarginBoundary) {
|
||||||
const accountingFinancialResult = toRecordObject(overview.accounting_financial_result);
|
const accountingFinancialResult = toRecordObject(overview.accounting_financial_result);
|
||||||
if (accountingFinancialResult) {
|
if (accountingFinancialResult) {
|
||||||
|
|
@ -1178,7 +1305,13 @@ function buildCompactBusinessOverviewReply(
|
||||||
return joinBusinessReplyLines(lines);
|
return joinBusinessReplyLines(lines);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!separateSubject && !crossScopeExecutiveSummary && (actionFamily === "broad_evaluation" || unsupportedFamily === "broad_business_evaluation")) {
|
if (
|
||||||
|
!separateSubject &&
|
||||||
|
!crossScopeExecutiveSummary &&
|
||||||
|
!counterpartyLeaderSurfaceRequested &&
|
||||||
|
!rankingNeed &&
|
||||||
|
(actionFamily === "broad_evaluation" || unsupportedFamily === "broad_business_evaluation")
|
||||||
|
) {
|
||||||
const subject = organizationScope ?? "компания";
|
const subject = organizationScope ?? "компания";
|
||||||
const periodWithoutPrefix = period.replace(/^за\s+/iu, "");
|
const periodWithoutPrefix = period.replace(/^за\s+/iu, "");
|
||||||
lines.push(
|
lines.push(
|
||||||
|
|
@ -1208,6 +1341,10 @@ function buildCompactBusinessOverviewReply(
|
||||||
: `- крупнейший получатель исходящих денег: ${topSupplier};`
|
: `- крупнейший получатель исходящих денег: ${topSupplier};`
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
const taxLine = businessOverviewTaxLine(overview);
|
||||||
|
if (taxLine) {
|
||||||
|
lines.push(`- ${localizeLine(taxLine)}`);
|
||||||
|
}
|
||||||
const inventoryLine = businessOverviewInventoryLine(overview);
|
const inventoryLine = businessOverviewInventoryLine(overview);
|
||||||
if (inventoryLine) {
|
if (inventoryLine) {
|
||||||
lines.push(`- ${localizeLine(inventoryLine)}`);
|
lines.push(`- ${localizeLine(inventoryLine)}`);
|
||||||
|
|
@ -1220,7 +1357,7 @@ function buildCompactBusinessOverviewReply(
|
||||||
"Ограничение: это оценка по денежным потокам и найденным срезам 1С, не аудиторское заключение и не подтвержденная чистая прибыль."
|
"Ограничение: это оценка по денежным потокам и найденным срезам 1С, не аудиторское заключение и не подтвержденная чистая прибыль."
|
||||||
);
|
);
|
||||||
const missingOverviewFamilies: string[] = [];
|
const missingOverviewFamilies: string[] = [];
|
||||||
if (!businessOverviewTaxLine(overview)) {
|
if (!taxLine) {
|
||||||
missingOverviewFamilies.push("НДС/налоговая позиция без отдельного точного расчета");
|
missingOverviewFamilies.push("НДС/налоговая позиция без отдельного точного расчета");
|
||||||
}
|
}
|
||||||
if (!debtLine) {
|
if (!debtLine) {
|
||||||
|
|
@ -1264,9 +1401,26 @@ function buildCompactBusinessOverviewReply(
|
||||||
!/(?:все\s+доступное|все\s+время|all\s+time)/iu.test(period) &&
|
!/(?:все\s+доступное|все\s+время|all\s+time)/iu.test(period) &&
|
||||||
(incomingAmount || outgoingAmount || netAmount);
|
(incomingAmount || outgoingAmount || netAmount);
|
||||||
if (explicitPeriodRankingOverview) {
|
if (explicitPeriodRankingOverview) {
|
||||||
lines.push(
|
if (counterpartyLeaderSurfaceRequested) {
|
||||||
`Коротко: ${organizationPrefix}${period} денежная картина подтверждена по найденным строкам 1С.`
|
const incomingLeaderText =
|
||||||
);
|
customerName && customerAmount
|
||||||
|
? topCustomerLooksFinancial
|
||||||
|
? `${customerName} — ${sentenceAmount(customerAmount) ?? customerAmount}; это банк/финансовый контур, не называю его обычной клиентской выручкой без назначения платежа${nonFinancialCustomer ? `; крупнейший небанковский входящий контрагент: ${nonFinancialCustomer}` : ""}`
|
||||||
|
: `${customerName} — ${sentenceAmount(customerAmount) ?? customerAmount}`
|
||||||
|
: "не распознан";
|
||||||
|
const outgoingLeaderText = topSupplier
|
||||||
|
? topSupplierLooksFinancial
|
||||||
|
? `${topSupplier}; это банк/финансовый контур, не называю его обычным поставщиком без назначения платежа/договора${nonFinancialSupplier ? `; крупнейший небанковский получатель исходящих денег: ${nonFinancialSupplier}` : ""}`
|
||||||
|
: topSupplier
|
||||||
|
: "не распознан";
|
||||||
|
lines.push(
|
||||||
|
`Коротко: ${organizationPrefix}${period} больше всего занес ${incomingLeaderText}; больше всего ушло ${outgoingLeaderText}.`
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
lines.push(
|
||||||
|
`Коротко: ${organizationPrefix}${period} денежная картина подтверждена по найденным строкам 1С.`
|
||||||
|
);
|
||||||
|
}
|
||||||
lines.push(
|
lines.push(
|
||||||
`Деньги: входящие ${incomingAmount ?? "0 руб."}, исходящие ${outgoingAmount ?? "0 руб."}, расчетное операционное нетто ${sentenceAmount(netAmount) ?? netAmount ?? "0 руб."}.`
|
`Деньги: входящие ${incomingAmount ?? "0 руб."}, исходящие ${outgoingAmount ?? "0 руб."}, расчетное операционное нетто ${sentenceAmount(netAmount) ?? netAmount ?? "0 руб."}.`
|
||||||
);
|
);
|
||||||
|
|
@ -1332,7 +1486,7 @@ function buildCompactBusinessOverviewReply(
|
||||||
`Коротко: ${organizationPrefix}${period} по подтвержденным строкам 1С получили ${incomingAmount ?? "0 руб."}; исходящие платежи/списания ${outgoingAmount ?? "0 руб."}; ${netDirection} ${sentenceAmount(netAmount) ?? netAmount ?? "0 руб"}${topCustomerLead}${topSupplierLead}${roleBoundaryLead}${separateSubjectLead}.`
|
`Коротко: ${organizationPrefix}${period} по подтвержденным строкам 1С получили ${incomingAmount ?? "0 руб."}; исходящие платежи/списания ${outgoingAmount ?? "0 руб."}; ${netDirection} ${sentenceAmount(netAmount) ?? netAmount ?? "0 руб"}${topCustomerLead}${topSupplierLead}${roleBoundaryLead}${separateSubjectLead}.`
|
||||||
);
|
);
|
||||||
lines.push('Метод: "заработали" здесь считаю как операционный денежный показатель по 1С; это не чистая прибыль и не финрезультат.');
|
lines.push('Метод: "заработали" здесь считаю как операционный денежный показатель по 1С; это не чистая прибыль и не финрезультат.');
|
||||||
if (!directMoneyAnswer && customerName && customerAmount) {
|
if (shouldIncludeCounterpartyLeaders && customerName && customerAmount) {
|
||||||
lines.push(
|
lines.push(
|
||||||
topCustomerLooksFinancial
|
topCustomerLooksFinancial
|
||||||
? `Крупнейший входящий денежный источник в этом срезе: ${customerName} — ${sentenceAmount(customerAmount) ?? customerAmount}. По названию это банк/финансовая организация, поэтому без назначения платежа не называю это клиентской выручкой.${nonFinancialCustomer ? ` Крупнейший небанковский входящий контрагент: ${nonFinancialCustomer}.` : ""}`
|
? `Крупнейший входящий денежный источник в этом срезе: ${customerName} — ${sentenceAmount(customerAmount) ?? customerAmount}. По названию это банк/финансовая организация, поэтому без назначения платежа не называю это клиентской выручкой.${nonFinancialCustomer ? ` Крупнейший небанковский входящий контрагент: ${nonFinancialCustomer}.` : ""}`
|
||||||
|
|
@ -1353,21 +1507,21 @@ function buildCompactBusinessOverviewReply(
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!directMoneyAnswer && topSupplier) {
|
if (shouldIncludeCounterpartyLeaders && topSupplier) {
|
||||||
lines.push(
|
lines.push(
|
||||||
topSupplierLooksFinancial
|
topSupplierLooksFinancial
|
||||||
? `Крупнейший получатель исходящих денег: ${topSupplier}. По названию это банк/финансовая организация, поэтому без назначения платежа/договора не считаю это обычным поставщиком.${nonFinancialSupplier ? ` Крупнейший небанковский получатель исходящих денег: ${nonFinancialSupplier}.` : ""}`
|
? `Крупнейший получатель исходящих денег: ${topSupplier}. По названию это банк/финансовая организация, поэтому без назначения платежа/договора не считаю это обычным поставщиком.${nonFinancialSupplier ? ` Крупнейший небанковский получатель исходящих денег: ${nonFinancialSupplier}.` : ""}`
|
||||||
: `Крупнейший подтвержденный получатель исходящих денег: ${topSupplier}.`
|
: `Крупнейший подтвержденный получатель исходящих денег: ${topSupplier}.`
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
if (!directMoneyAnswer && (topCustomer || topSupplier)) {
|
if (shouldIncludeCounterpartyLeaders && (topCustomer || topSupplier)) {
|
||||||
lines.push(
|
lines.push(
|
||||||
topCustomerLooksFinancial || topSupplierLooksFinancial
|
topCustomerLooksFinancial || topSupplierLooksFinancial
|
||||||
? "Важно по ролям: текущий денежный срез подтверждает источники и получателей денег, но банковские контрагенты требуют проверки назначения платежа/счетов и не доказывают роль клиента или поставщика."
|
? "Важно по ролям: текущий денежный срез подтверждает источники и получателей денег, но банковские контрагенты требуют проверки назначения платежа/счетов и не доказывают роль клиента или поставщика."
|
||||||
: "Важно по ролям: текущий денежный срез подтверждает денежные источники и получателей, но не доказывает, что это главный клиент или главный поставщик как бизнес-роль."
|
: "Важно по ролям: текущий денежный срез подтверждает денежные источники и получателей, но не доказывает, что это главный клиент или главный поставщик как бизнес-роль."
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
if (!directMoneyAnswer) {
|
if (shouldIncludeOverviewSurface) {
|
||||||
lines.push(
|
lines.push(
|
||||||
`Что подтверждено: денежный срез по компании${organizationScope ? ` ${organizationScope}` : ""}${period ? ` ${period}` : ""}${topCustomer ? ", крупнейший источник входящих денег" : ""}${topSupplier ? ", крупнейший получатель исходящих денег" : ""}.`
|
`Что подтверждено: денежный срез по компании${organizationScope ? ` ${organizationScope}` : ""}${period ? ` ${period}` : ""}${topCustomer ? ", крупнейший источник входящих денег" : ""}${topSupplier ? ", крупнейший получатель исходящих денег" : ""}.`
|
||||||
);
|
);
|
||||||
|
|
@ -1466,6 +1620,11 @@ function buildReplyText(entryPoint: AssistantMcpDiscoveryRuntimeEntryPointContra
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const previousCounterpartyBoundaryReply = buildBoundarySummaryFromPreviousCounterpartyBundles(entryPoint);
|
||||||
|
if (previousCounterpartyBoundaryReply) {
|
||||||
|
return previousCounterpartyBoundaryReply;
|
||||||
|
}
|
||||||
|
|
||||||
const compactBidirectionalValueFlowReply = buildCompactBidirectionalValueFlowReply(entryPoint, draft);
|
const compactBidirectionalValueFlowReply = buildCompactBidirectionalValueFlowReply(entryPoint, draft);
|
||||||
if (compactBidirectionalValueFlowReply) {
|
if (compactBidirectionalValueFlowReply) {
|
||||||
return compactBidirectionalValueFlowReply;
|
return compactBidirectionalValueFlowReply;
|
||||||
|
|
|
||||||
|
|
@ -510,6 +510,13 @@ function hasExactDocumentListAddressReply(
|
||||||
if (source !== "address_query_runtime_v1" && source !== "address_exact" && source !== "address_lane") {
|
if (source !== "address_query_runtime_v1" && source !== "address_exact" && source !== "address_lane") {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
if (
|
||||||
|
hasValueFlowActionConflictWithDiscoveryTurnMeaning(input, entryPoint) ||
|
||||||
|
hasEvidenceLaneConflictWithDiscoveryTurnMeaning(input, entryPoint) ||
|
||||||
|
hasSemanticConflictWithDiscoveryTurnMeaning(input, entryPoint)
|
||||||
|
) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
const detectedIntent = toNonEmptyString(input.addressRuntimeMeta?.detected_intent);
|
const detectedIntent = toNonEmptyString(input.addressRuntimeMeta?.detected_intent);
|
||||||
const selectedRecipe = toNonEmptyString(input.addressRuntimeMeta?.selected_recipe);
|
const selectedRecipe = toNonEmptyString(input.addressRuntimeMeta?.selected_recipe);
|
||||||
const isDocumentIntent =
|
const isDocumentIntent =
|
||||||
|
|
|
||||||
|
|
@ -432,7 +432,12 @@ export async function runAssistantMcpDiscoveryRuntimeBridge(
|
||||||
const reasonCodes = uniqueStrings([...planner.reason_codes, ...pilot.reason_codes, ...answerDraft.reason_codes]);
|
const reasonCodes = uniqueStrings([...planner.reason_codes, ...pilot.reason_codes, ...answerDraft.reason_codes]);
|
||||||
|
|
||||||
pushReason(reasonCodes, `runtime_bridge_status_${bridgeStatus}`);
|
pushReason(reasonCodes, `runtime_bridge_status_${bridgeStatus}`);
|
||||||
pushReason(reasonCodes, "runtime_bridge_not_wired_to_hot_assistant_answer");
|
pushReason(
|
||||||
|
reasonCodes,
|
||||||
|
executionHandoff.can_use_guarded_response
|
||||||
|
? "runtime_bridge_wired_to_guarded_hot_assistant_answer"
|
||||||
|
: "runtime_bridge_not_wired_to_hot_assistant_answer"
|
||||||
|
);
|
||||||
pushReason(reasonCodes, `runtime_bridge_loop_state_${loopState.loop_status}`);
|
pushReason(reasonCodes, `runtime_bridge_loop_state_${loopState.loop_status}`);
|
||||||
pushReason(reasonCodes, "runtime_bridge_route_candidate_built");
|
pushReason(reasonCodes, "runtime_bridge_route_candidate_built");
|
||||||
pushReason(reasonCodes, `runtime_bridge_route_candidate_${routeCandidate.candidate_status}`);
|
pushReason(reasonCodes, `runtime_bridge_route_candidate_${routeCandidate.candidate_status}`);
|
||||||
|
|
|
||||||
|
|
@ -117,6 +117,7 @@ function isReferentialOrganizationPlaceholder(value: string | null): boolean {
|
||||||
"этой компании",
|
"этой компании",
|
||||||
"этой компанией",
|
"этой компанией",
|
||||||
"эту компанию",
|
"эту компанию",
|
||||||
|
"в целом",
|
||||||
"наша организация",
|
"наша организация",
|
||||||
"нашей организации",
|
"нашей организации",
|
||||||
"нашей компанией"
|
"нашей компанией"
|
||||||
|
|
@ -250,7 +251,13 @@ function normalizeFollowupCounterpartyCandidate(value: unknown): string | null {
|
||||||
if (!text || isInvalidEntityCandidate(text)) {
|
if (!text || isInvalidEntityCandidate(text)) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
return text;
|
const cleaned = text
|
||||||
|
.replace(
|
||||||
|
/^(?:\u043f\u043e\s+)?\u043a\u043e\u043d\u0442\u0440\u0430\u0433\u0435\u043d\u0442(?:\u0443|\u0430|\u043e\u043c|\u0435|\u044b|\u0430\u043c|\u0430\u043c\u0438|\u0430\u0445)?\s+/iu,
|
||||||
|
""
|
||||||
|
)
|
||||||
|
.trim();
|
||||||
|
return cleaned && !isInvalidEntityCandidate(cleaned) ? cleaned : text;
|
||||||
}
|
}
|
||||||
|
|
||||||
function pushScopedEntityCandidate(
|
function pushScopedEntityCandidate(
|
||||||
|
|
@ -702,12 +709,13 @@ function collectFollowupDiscoverySeed(followupContext: Record<string, unknown> |
|
||||||
const normalizedDiscoveryEntities = discoveryEntities
|
const normalizedDiscoveryEntities = discoveryEntities
|
||||||
.map((entity) => normalizeFollowupCounterpartyCandidate(entity))
|
.map((entity) => normalizeFollowupCounterpartyCandidate(entity))
|
||||||
.filter((entity): entity is string => Boolean(entity));
|
.filter((entity): entity is string => Boolean(entity));
|
||||||
|
const normalizedLoopMetadataScopeHint = normalizeFollowupCounterpartyCandidate(loopMetadataScopeHint);
|
||||||
const groundedDiscoveryCounterparty =
|
const groundedDiscoveryCounterparty =
|
||||||
ambiguityBlocksImplicitGrounding || metadataPilotCarriesScopeOnly
|
ambiguityBlocksImplicitGrounding || metadataPilotCarriesScopeOnly
|
||||||
? null
|
? null
|
||||||
: normalizedDiscoveryEntities[0] ?? normalizeFollowupCounterpartyCandidate(loopMetadataScopeHint);
|
: normalizedDiscoveryEntities[0] ?? normalizedLoopMetadataScopeHint;
|
||||||
const metadataScopeHint =
|
const metadataScopeHint =
|
||||||
loopMetadataScopeHint ??
|
normalizedLoopMetadataScopeHint ??
|
||||||
(loopSubjectResolutionOptional ? normalizedDiscoveryEntities[0] ?? null : null);
|
(loopSubjectResolutionOptional ? normalizedDiscoveryEntities[0] ?? null : null);
|
||||||
const previousFiltersCounterparty = normalizeFollowupCounterpartyCandidate(previousFilters?.counterparty);
|
const previousFiltersCounterparty = normalizeFollowupCounterpartyCandidate(previousFilters?.counterparty);
|
||||||
const rootFiltersCounterparty = normalizeFollowupCounterpartyCandidate(rootFilters?.counterparty);
|
const rootFiltersCounterparty = normalizeFollowupCounterpartyCandidate(rootFilters?.counterparty);
|
||||||
|
|
@ -724,9 +732,10 @@ function collectFollowupDiscoverySeed(followupContext: Record<string, unknown> |
|
||||||
(toNonEmptyString(followupContext?.previous_anchor_type) === "organization"
|
(toNonEmptyString(followupContext?.previous_anchor_type) === "organization"
|
||||||
? toNonEmptyString(followupContext?.previous_anchor_value)
|
? toNonEmptyString(followupContext?.previous_anchor_value)
|
||||||
: null);
|
: null);
|
||||||
const dateScope =
|
const loopProvidedAllTimeScope = loopProvidedAxes.includes("all_time_scope");
|
||||||
collectDateScopeFromFilters(previousFilters) ??
|
const dateScope = loopProvidedAllTimeScope
|
||||||
collectDateScopeFromFilters(rootFilters);
|
? "all_time_scope"
|
||||||
|
: collectDateScopeFromFilters(previousFilters) ?? collectDateScopeFromFilters(rootFilters);
|
||||||
return {
|
return {
|
||||||
pilotScope: effectivePilotScope,
|
pilotScope: effectivePilotScope,
|
||||||
domain: mapped.domain,
|
domain: mapped.domain,
|
||||||
|
|
@ -986,7 +995,7 @@ function hasOrganizationLevelSupplierQualityOverviewSignal(text: string): boolea
|
||||||
|
|
||||||
function hasCrossScopeExecutiveSummarySignal(text: string): boolean {
|
function hasCrossScopeExecutiveSummarySignal(text: string): boolean {
|
||||||
return (
|
return (
|
||||||
/(?:\u0441\u043e\u0431\u0435\u0440\p{L}*\s+(?:\u043a\u043e\u0440\u043e\u0442\u043a\p{L}*\s+)?\u0438\u0442\u043e\u0433|\u044d\u043a\u0437\u0435\u043a\u044c\u044e\u0442\u0438\u0432\p{L}*\s+\u0441\u0430\u043c\u043c\u0430\u0440\u0438|executive\s+summary|final\s+summary)/iu.test(
|
/(?:\u0441\u043e\u0431\u0435\u0440\p{L}*\s+(?:\u043a\u043e\u0440\u043e\u0442\u043a\p{L}*\s+)?\u0438\u0442\u043e\u0433|(?:\u043a\u043e\u0440\u043e\u0442\u043a\p{L}*\s+)?\u0441\u0440\u0430\u0432\u043d\p{L}*|\u044d\u043a\u0437\u0435\u043a\u044c\u044e\u0442\u0438\u0432\p{L}*\s+\u0441\u0430\u043c\u043c\u0430\u0440\u0438|executive\s+summary|final\s+summary|brief(?:ly)?\s+compare)/iu.test(
|
||||||
text
|
text
|
||||||
) &&
|
) &&
|
||||||
/(?:\u0447\u0442\u043e\s+(?:\u043c\u044b\s+)?\u043f\u043e\u0434\u0442\u0432\u0435\u0440\p{L}*|\u043f\u043e\s+\u043a\u043e\u043c\u043f\u0430\u043d\p{L}*|\u043f\u043e\s+\u043e\u0440\u0433\u0430\u043d\u0438\u0437\u0430\u0446\p{L}*|confirmed|company|organization)/iu.test(
|
/(?:\u0447\u0442\u043e\s+(?:\u043c\u044b\s+)?\u043f\u043e\u0434\u0442\u0432\u0435\u0440\p{L}*|\u043f\u043e\s+\u043a\u043e\u043c\u043f\u0430\u043d\p{L}*|\u043f\u043e\s+\u043e\u0440\u0433\u0430\u043d\u0438\u0437\u0430\u0446\p{L}*|confirmed|company|organization)/iu.test(
|
||||||
|
|
@ -1013,6 +1022,35 @@ function hasPlainBusinessOverviewSignal(text: string): boolean {
|
||||||
return hasPlainOverviewCue && hasCompanyOrOperatingScopeCue;
|
return hasPlainOverviewCue && hasCompanyOrOperatingScopeCue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function countBroadBusinessOverviewAxes(text: string): number {
|
||||||
|
const axisPatterns = [
|
||||||
|
/(?:\u0434\u0435\u043d\p{L}*|\u043e\u0431\u043e\u0440\u043e\u0442|\u0432\u044b\u0440\u0443\u0447\p{L}*|\u043f\u043e\u0441\u0442\u0443\u043f\p{L}*|\u043f\u043b\u0430\u0442\p{L}*|money|cash|revenue|turnover)/iu,
|
||||||
|
/(?:\u043d\u0434\u0441|vat)/iu,
|
||||||
|
/(?:\u0434\u043e\u043b\p{L}*|\u0434\u0435\u0431\u0438\u0442\u043e\u0440\p{L}*|\u043a\u0440\u0435\u0434\u0438\u0442\u043e\u0440\p{L}*|receivable|payable|debt)/iu,
|
||||||
|
/(?:\u0441\u043a\u043b\u0430\u0434|\u043e\u0441\u0442\u0430\u0442|\u0437\u0430\u043f\u0430\u0441|\u0442\u043e\u0432\u0430\u0440|warehouse|stock|inventory)/iu,
|
||||||
|
/(?:\u043a\u043b\u0438\u0435\u043d\u0442|\u043f\u043e\u043a\u0443\u043f\u0430\u0442\u0435\u043b|\u0437\u0430\u043a\u0430\u0437\u0447\u0438\u043a|customer|client|buyer)/iu,
|
||||||
|
/(?:\u043f\u043e\u0441\u0442\u0430\u0432\u0449\u0438\u043a|\u0432\u0435\u043d\u0434\u043e\u0440|\u0437\u0430\u043a\u0443\u043f|supplier|vendor|procurement)/iu,
|
||||||
|
/(?:\u0433\u0434\u0435[\s\S]{0,80}(?:\u043d\u0435\u043b\u044c\u0437\u044f|\u043d\u0435\s+\u0434\u0435\u043b\u0430\p{L}*|\u043d\u0435\s+\u0445\u0432\u0430\p{L}*)|\u0447\u0442\u043e[\s\S]{0,80}(?:\u043d\u0435\u043b\u044c\u0437\u044f|\u043d\u0435\s+\u0445\u0432\u0430\p{L}*)|cannot|unknown|missing|limitation)/iu
|
||||||
|
];
|
||||||
|
return axisPatterns.reduce((count, pattern) => count + (pattern.test(text) ? 1 : 0), 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
function hasBroadBusinessOverviewSurfaceSignal(text: string): boolean {
|
||||||
|
const normalized = compactLower(text);
|
||||||
|
if (!normalized) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const hasBroadCue =
|
||||||
|
/(?:\u043f\u043e[-\s]*\u0447\u0435\u043b\u043e\u0432\u0435\u0447\u0435\u0441\u043a\p{L}*|\u0447\u0442\u043e\s+\u043f\u043e\s+\u0431\u0438\u0437\u043d\u0435\u0441\u0443\s+\u0432\u0438\u0434\u043d\p{L}*|\u043f\u043e\u0441\u043c\u043e\u0442\p{L}*[\s\S]{0,100}(?:\u0431\u0438\u0437\u043d\u0435\u0441|\u0434\u0435\u044f\u0442\u0435\u043b\p{L}*)|\u0431\u0438\u0437\u043d\u0435\u0441[\s\S]{0,80}(?:\u0432\u0438\u0434\u043d\p{L}*|\u0432\u044b\u0432\u043e\u0434|\u0441\u0440\u0435\u0437)|human\s+readable\s+business\s+view)/iu.test(
|
||||||
|
normalized
|
||||||
|
);
|
||||||
|
const hasCompanyScope =
|
||||||
|
/(?:\u043e\u043e\u043e|\u0438\u043f|\u0430\u043e|\u043f\u0430\u043e|\u0437\u0430\u043e|\u043e\u0430\u043e|\u043a\u043e\u043c\u043f\u0430\u043d|\u043e\u0440\u0433\u0430\u043d\u0438\u0437\u0430\u0446|\u0431\u0438\u0437\u043d\u0435\u0441|\u0432\s+1\s?\u0441|1\s?c|company|organization|business|(?:19|20)\d{2})/iu.test(
|
||||||
|
normalized
|
||||||
|
);
|
||||||
|
return hasBroadCue && hasCompanyScope && countBroadBusinessOverviewAxes(normalized) >= 3;
|
||||||
|
}
|
||||||
|
|
||||||
function hasBusinessOverviewSignal(text: string): boolean {
|
function hasBusinessOverviewSignal(text: string): boolean {
|
||||||
if (
|
if (
|
||||||
hasCrossScopeExecutiveSummarySignal(text) ||
|
hasCrossScopeExecutiveSummarySignal(text) ||
|
||||||
|
|
@ -1021,6 +1059,7 @@ function hasBusinessOverviewSignal(text: string): boolean {
|
||||||
hasOrganizationLevelDebtDueDateOverviewSignal(text) ||
|
hasOrganizationLevelDebtDueDateOverviewSignal(text) ||
|
||||||
hasOrganizationLevelInventoryReserveLiquidationOverviewSignal(text) ||
|
hasOrganizationLevelInventoryReserveLiquidationOverviewSignal(text) ||
|
||||||
hasPlainBusinessOverviewSignal(text) ||
|
hasPlainBusinessOverviewSignal(text) ||
|
||||||
|
hasBroadBusinessOverviewSurfaceSignal(text) ||
|
||||||
hasOrganizationLevelSupplierQualityOverviewSignal(text)
|
hasOrganizationLevelSupplierQualityOverviewSignal(text)
|
||||||
) {
|
) {
|
||||||
return true;
|
return true;
|
||||||
|
|
@ -1101,6 +1140,15 @@ function hasBusinessOverviewSeparateCounterpartySignal(text: string): boolean {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function isGenericSelectedCounterpartyReference(value: string | null): boolean {
|
||||||
|
if (!value) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return /^(?:(?:\u0432\u044b\u0431\u0440\u0430\u043d\p{L}*|\u044d\u0442\p{L}*|\u0434\u0430\u043d\p{L}*|\u0442\u0435\u043a\u0443\u0449\p{L}*)\s+)?\u043a\u043e\u043d\u0442\u0440\u0430\u0433\u0435\u043d\u0442\p{L}*$|^(?:selected|chosen|current|this)\s+counterpart(?:y|ies)?$/iu.test(
|
||||||
|
value
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
function businessOverviewSeparateCounterpartyCandidateFromText(text: string): string | null {
|
function businessOverviewSeparateCounterpartyCandidateFromText(text: string): string | null {
|
||||||
const source = repairAddressMojibakeText(String(text ?? ""));
|
const source = repairAddressMojibakeText(String(text ?? ""));
|
||||||
const patterns = [
|
const patterns = [
|
||||||
|
|
@ -1109,7 +1157,7 @@ function businessOverviewSeparateCounterpartyCandidateFromText(text: string): st
|
||||||
];
|
];
|
||||||
for (const pattern of patterns) {
|
for (const pattern of patterns) {
|
||||||
const candidate = normalizeFollowupCounterpartyCandidate(source.match(pattern)?.[1]);
|
const candidate = normalizeFollowupCounterpartyCandidate(source.match(pattern)?.[1]);
|
||||||
if (candidate && !isInvalidEntityCandidate(candidate)) {
|
if (candidate && !isInvalidEntityCandidate(candidate) && !isGenericSelectedCounterpartyReference(candidate)) {
|
||||||
return candidate;
|
return candidate;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1245,6 +1293,9 @@ function normalizeLooseOrganizationAlias(value: string | null): string | null {
|
||||||
if (hasYearOnlyTimeTail) {
|
if (hasYearOnlyTimeTail) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
if (new Set(["в целом", "компания в целом", "организация в целом"]).has(comparable)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
if (/^(?:\u0438|\u0432|\u0432\u043e|\u0437\u0430|\u043d\u0430|\u043f\u043e|\u043a\u0442\u043e|\u0447\u0442\u043e|\u043a\u0430\u043a(?:\u043e\u0439|\u0430\u044f|\u0438\u0435)?|\u0433\u043b\u0430\u0432\u043d\p{L}*)\b/iu.test(comparable)) {
|
if (/^(?:\u0438|\u0432|\u0432\u043e|\u0437\u0430|\u043d\u0430|\u043f\u043e|\u043a\u0442\u043e|\u0447\u0442\u043e|\u043a\u0430\u043a(?:\u043e\u0439|\u0430\u044f|\u0438\u0435)?|\u0433\u043b\u0430\u0432\u043d\p{L}*)\b/iu.test(comparable)) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
@ -1910,11 +1961,21 @@ export function buildAssistantMcpDiscoveryTurnInput(
|
||||||
const businessOverviewSignal =
|
const businessOverviewSignal =
|
||||||
!businessOverviewCounterpartyValueFlowPivot &&
|
!businessOverviewCounterpartyValueFlowPivot &&
|
||||||
(rawBusinessOverviewSignal || seededBusinessOverviewSignal);
|
(rawBusinessOverviewSignal || seededBusinessOverviewSignal);
|
||||||
|
const organizationClarificationBusinessOverviewLoop = Boolean(
|
||||||
|
followupSeed.loopStatus === "awaiting_clarification" &&
|
||||||
|
followupSeed.loopSelectedChainId === "business_overview" &&
|
||||||
|
followupSeed.loopPendingAxes.includes("organization") &&
|
||||||
|
currentTurnOrganizationScope &&
|
||||||
|
!rawLifecycleSignal &&
|
||||||
|
!rawMetadataSignal
|
||||||
|
);
|
||||||
const businessOverviewSeparateCounterpartySignal = Boolean(
|
const businessOverviewSeparateCounterpartySignal = Boolean(
|
||||||
businessOverviewSignal && hasBusinessOverviewSeparateCounterpartySignal(rawText)
|
businessOverviewSignal && hasBusinessOverviewSeparateCounterpartySignal(rawText)
|
||||||
);
|
);
|
||||||
const businessOverviewSeparateCounterpartyCandidate = businessOverviewSeparateCounterpartySignal
|
const businessOverviewSeparateCounterpartyCandidate = businessOverviewSeparateCounterpartySignal
|
||||||
? businessOverviewSeparateCounterpartyCandidateFromText(rawText)
|
? businessOverviewSeparateCounterpartyCandidateFromText(rawText)
|
||||||
|
: organizationClarificationBusinessOverviewLoop
|
||||||
|
? followupSeed.counterparty ?? followupSeed.discoveryEntity ?? followupSeed.metadataScopeHint
|
||||||
: null;
|
: null;
|
||||||
const explicitIntentCandidate = toNonEmptyString(assistantTurnMeaning?.explicit_intent_candidate);
|
const explicitIntentCandidate = toNonEmptyString(assistantTurnMeaning?.explicit_intent_candidate);
|
||||||
const currentTurnDocumentLaneSignal = rawAction === "list_documents";
|
const currentTurnDocumentLaneSignal = rawAction === "list_documents";
|
||||||
|
|
@ -1944,6 +2005,7 @@ export function buildAssistantMcpDiscoveryTurnInput(
|
||||||
const businessOverviewSuppressesFollowupCounterparty = Boolean(
|
const businessOverviewSuppressesFollowupCounterparty = Boolean(
|
||||||
businessOverviewSignal &&
|
businessOverviewSignal &&
|
||||||
!businessOverviewSeparateCounterpartySignal &&
|
!businessOverviewSeparateCounterpartySignal &&
|
||||||
|
!organizationClarificationBusinessOverviewLoop &&
|
||||||
(rawBusinessOverviewSignal ||
|
(rawBusinessOverviewSignal ||
|
||||||
businessOverviewContinuationSignal ||
|
businessOverviewContinuationSignal ||
|
||||||
broadBusinessEvaluationUnsupported ||
|
broadBusinessEvaluationUnsupported ||
|
||||||
|
|
@ -2452,7 +2514,9 @@ export function buildAssistantMcpDiscoveryTurnInput(
|
||||||
}
|
}
|
||||||
pushScopedEntityCandidate(entityCandidates, rawEntityCandidate, groundedFollowupEntity);
|
pushScopedEntityCandidate(entityCandidates, rawEntityCandidate, groundedFollowupEntity);
|
||||||
}
|
}
|
||||||
const businessOverviewSeparateCounterpartyDisplayCandidate = businessOverviewSeparateCounterpartySignal
|
const shouldPreserveBusinessOverviewSeparateCounterparty =
|
||||||
|
businessOverviewSeparateCounterpartySignal || organizationClarificationBusinessOverviewLoop;
|
||||||
|
const businessOverviewSeparateCounterpartyDisplayCandidate = shouldPreserveBusinessOverviewSeparateCounterparty
|
||||||
? preferredScopedDisplayName(businessOverviewSeparateCounterpartyCandidate, [
|
? preferredScopedDisplayName(businessOverviewSeparateCounterpartyCandidate, [
|
||||||
groundedFollowupEntity,
|
groundedFollowupEntity,
|
||||||
effectiveFollowupCounterparty,
|
effectiveFollowupCounterparty,
|
||||||
|
|
@ -2461,7 +2525,24 @@ export function buildAssistantMcpDiscoveryTurnInput(
|
||||||
rawScopedEntityCandidate,
|
rawScopedEntityCandidate,
|
||||||
rawEntityCandidate,
|
rawEntityCandidate,
|
||||||
...entityCandidates
|
...entityCandidates
|
||||||
])
|
]) ??
|
||||||
|
preferredScopedDisplayName(
|
||||||
|
groundedFollowupEntity ??
|
||||||
|
effectiveFollowupCounterparty ??
|
||||||
|
followupSeed.discoveryEntity ??
|
||||||
|
normalizedPredecomposeCounterparty ??
|
||||||
|
rawScopedEntityCandidate ??
|
||||||
|
rawEntityCandidate,
|
||||||
|
[
|
||||||
|
groundedFollowupEntity,
|
||||||
|
effectiveFollowupCounterparty,
|
||||||
|
followupSeed.discoveryEntity,
|
||||||
|
normalizedPredecomposeCounterparty,
|
||||||
|
rawScopedEntityCandidate,
|
||||||
|
rawEntityCandidate,
|
||||||
|
...entityCandidates
|
||||||
|
]
|
||||||
|
)
|
||||||
: null;
|
: null;
|
||||||
const businessOverviewSeparateEntityCandidates = businessOverviewSeparateCounterpartyDisplayCandidate
|
const businessOverviewSeparateEntityCandidates = businessOverviewSeparateCounterpartyDisplayCandidate
|
||||||
? [businessOverviewSeparateCounterpartyDisplayCandidate]
|
? [businessOverviewSeparateCounterpartyDisplayCandidate]
|
||||||
|
|
@ -2586,6 +2667,7 @@ export function buildAssistantMcpDiscoveryTurnInput(
|
||||||
const normalizedAssistantTurnMeaningDateScope =
|
const normalizedAssistantTurnMeaningDateScope =
|
||||||
rawEntitySearchOverridesStaleScope ||
|
rawEntitySearchOverridesStaleScope ||
|
||||||
suppressNegatedTaxOnlyDateScope ||
|
suppressNegatedTaxOnlyDateScope ||
|
||||||
|
(organizationClarificationBusinessOverviewLoop && !currentTurnCarriesExplicitPeriod) ||
|
||||||
(suppressImplicitCurrentDateScope && isImplicitCurrentDateScope(assistantTurnMeaningDateScope))
|
(suppressImplicitCurrentDateScope && isImplicitCurrentDateScope(assistantTurnMeaningDateScope))
|
||||||
? null
|
? null
|
||||||
: assistantTurnMeaningDateScope;
|
: assistantTurnMeaningDateScope;
|
||||||
|
|
@ -2603,8 +2685,9 @@ export function buildAssistantMcpDiscoveryTurnInput(
|
||||||
normalizedPredecomposeDateScope &&
|
normalizedPredecomposeDateScope &&
|
||||||
normalizedPredecomposeDateScope.startsWith(`${rawDateScope}-`)
|
normalizedPredecomposeDateScope.startsWith(`${rawDateScope}-`)
|
||||||
);
|
);
|
||||||
|
const followupAllTimeScopeApplied = normalizedFollowupDateScope === "all_time_scope";
|
||||||
const explicitDateScope =
|
const explicitDateScope =
|
||||||
rawAllTimeScopeSignal
|
rawAllTimeScopeSignal || followupAllTimeScopeApplied
|
||||||
? null
|
? null
|
||||||
: normalizedAssistantTurnMeaningDateScope ??
|
: normalizedAssistantTurnMeaningDateScope ??
|
||||||
(businessOverviewRawYearOverridesPredecomposeAsOf ? rawDateScope : normalizedPredecomposeDateScope) ??
|
(businessOverviewRawYearOverridesPredecomposeAsOf ? rawDateScope : normalizedPredecomposeDateScope) ??
|
||||||
|
|
@ -2615,7 +2698,8 @@ export function buildAssistantMcpDiscoveryTurnInput(
|
||||||
!normalizedAssistantTurnMeaningDateScope &&
|
!normalizedAssistantTurnMeaningDateScope &&
|
||||||
!normalizedPredecomposeDateScope &&
|
!normalizedPredecomposeDateScope &&
|
||||||
!rawDateScope &&
|
!rawDateScope &&
|
||||||
normalizedFollowupDateScope
|
normalizedFollowupDateScope &&
|
||||||
|
normalizedFollowupDateScope !== "all_time_scope"
|
||||||
);
|
);
|
||||||
const clarificationLoopSeedApplied = Boolean(
|
const clarificationLoopSeedApplied = Boolean(
|
||||||
followupSeed.loopStatus === "awaiting_clarification" && followupSeed.loopSelectedChainId
|
followupSeed.loopStatus === "awaiting_clarification" && followupSeed.loopSelectedChainId
|
||||||
|
|
@ -2668,7 +2752,8 @@ export function buildAssistantMcpDiscoveryTurnInput(
|
||||||
valueFlowSignal && followupSeed.rankingNeed && !rawEntitySearchOverridesStaleScope
|
valueFlowSignal && followupSeed.rankingNeed && !rawEntitySearchOverridesStaleScope
|
||||||
? followupSeed.rankingNeed
|
? followupSeed.rankingNeed
|
||||||
: undefined,
|
: undefined,
|
||||||
explicit_entity_candidates: businessOverviewSignal ? [] : entityCandidates,
|
explicit_entity_candidates:
|
||||||
|
businessOverviewSignal || shouldPreserveBusinessOverviewSeparateCounterparty ? [] : entityCandidates,
|
||||||
business_overview_separate_entity_candidates: businessOverviewSeparateEntityCandidates,
|
business_overview_separate_entity_candidates: businessOverviewSeparateEntityCandidates,
|
||||||
previous_counterparty_value_flow_bundle:
|
previous_counterparty_value_flow_bundle:
|
||||||
businessOverviewSignal && followupSeed.previousBidirectionalValueFlow
|
businessOverviewSignal && followupSeed.previousBidirectionalValueFlow
|
||||||
|
|
@ -2897,6 +2982,9 @@ export function buildAssistantMcpDiscoveryTurnInput(
|
||||||
if (rawAllTimeScopeSignal) {
|
if (rawAllTimeScopeSignal) {
|
||||||
pushReason(reasonCodes, "mcp_discovery_all_time_scope_signal_detected");
|
pushReason(reasonCodes, "mcp_discovery_all_time_scope_signal_detected");
|
||||||
}
|
}
|
||||||
|
if (followupAllTimeScopeApplied) {
|
||||||
|
pushReason(reasonCodes, "mcp_discovery_all_time_scope_from_followup_context");
|
||||||
|
}
|
||||||
if (suppressNegatedTaxOnlyDateScope) {
|
if (suppressNegatedTaxOnlyDateScope) {
|
||||||
pushReason(reasonCodes, "mcp_discovery_negated_tax_period_scope_suppressed");
|
pushReason(reasonCodes, "mcp_discovery_negated_tax_period_scope_suppressed");
|
||||||
}
|
}
|
||||||
|
|
@ -2999,7 +3087,7 @@ export function buildAssistantMcpDiscoveryTurnInput(
|
||||||
if (businessOverviewSuppressesFollowupCounterparty) {
|
if (businessOverviewSuppressesFollowupCounterparty) {
|
||||||
pushReason(reasonCodes, "mcp_discovery_business_overview_suppressed_stale_counterparty");
|
pushReason(reasonCodes, "mcp_discovery_business_overview_suppressed_stale_counterparty");
|
||||||
}
|
}
|
||||||
if (businessOverviewSeparateCounterpartySignal) {
|
if (shouldPreserveBusinessOverviewSeparateCounterparty) {
|
||||||
pushReason(reasonCodes, "mcp_discovery_business_overview_preserved_explicit_counterparty_summary_scope");
|
pushReason(reasonCodes, "mcp_discovery_business_overview_preserved_explicit_counterparty_summary_scope");
|
||||||
}
|
}
|
||||||
if (businessOverviewSeparateCounterpartyCandidate) {
|
if (businessOverviewSeparateCounterpartyCandidate) {
|
||||||
|
|
@ -3023,7 +3111,7 @@ export function buildAssistantMcpDiscoveryTurnInput(
|
||||||
) {
|
) {
|
||||||
pushReason(reasonCodes, "mcp_discovery_counterparty_from_predecompose");
|
pushReason(reasonCodes, "mcp_discovery_counterparty_from_predecompose");
|
||||||
}
|
}
|
||||||
if (rawScopedEntityCandidate && !normalizedPredecomposeCounterparty) {
|
if (rawScopedEntityCandidate && !normalizedPredecomposeCounterparty && !businessOverviewSignal) {
|
||||||
pushReason(reasonCodes, "mcp_discovery_counterparty_from_raw_scope");
|
pushReason(reasonCodes, "mcp_discovery_counterparty_from_raw_scope");
|
||||||
}
|
}
|
||||||
if (
|
if (
|
||||||
|
|
|
||||||
|
|
@ -136,6 +136,26 @@ export function createAssistantTransitionPolicy(deps) {
|
||||||
return /(?:документ|счет|счет-фактур|накладн|акт|реализац|document|invoice|receipt)/iu.test(normalized);
|
return /(?:документ|счет|счет-фактур|накладн|акт|реализац|document|invoice|receipt)/iu.test(normalized);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function hasSelectedCounterpartyDocumentFollowupSignal(userMessage, alternateMessage = null) {
|
||||||
|
return [userMessage, alternateMessage]
|
||||||
|
.filter((value) => deps.toNonEmptyString(value))
|
||||||
|
.map((value) => normalizeFollowupText(value).replace(/С‘/g, "Рµ"))
|
||||||
|
.some((normalized) => {
|
||||||
|
if (!normalized) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const hasDocumentCue =
|
||||||
|
/(?:\u0434\u043e\u043a\p{L}*|\u0441\u0447\p{L}*|\u043d\u0430\u043a\u043b\u0430\u0434\p{L}*|\u0430\u043a\u0442|document|docs?|invoice|receipt)/iu.test(
|
||||||
|
normalized
|
||||||
|
) || hasReadableDocumentsPivotCue(normalized);
|
||||||
|
const hasSelectedCounterpartyCue =
|
||||||
|
/(?:\u043f\u043e\s+\u043d(?:\u0435\u043c\u0443|\u0435\u0439)|\u043f\u043e\s+\u044d\u0442(?:\u043e\u043c\u0443|\u043e\u0439)|\u0442\u0435\u043a\u0443\u0449\p{L}*\s+\u043e\u0431\u044a\u0435\u043a\p{L}*|\u0432\u044b\u0431\u0440\u0430\u043d\p{L}*\s+(?:\u043a\u043e\u043d\u0442\u0440\u0430\u0433\u0435\u043d\u0442|\u043e\u0431\u044a\u0435\u043a\u0442)|selected\s+(?:counterparty|object)|current\s+object)/iu.test(
|
||||||
|
normalized
|
||||||
|
);
|
||||||
|
return hasDocumentCue && hasSelectedCounterpartyCue;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
function selectSuggestedIntentByPivotCue(suggestedIntents, userMessage, alternateMessage = null) {
|
function selectSuggestedIntentByPivotCue(suggestedIntents, userMessage, alternateMessage = null) {
|
||||||
if (!Array.isArray(suggestedIntents) || suggestedIntents.length === 0) {
|
if (!Array.isArray(suggestedIntents) || suggestedIntents.length === 0) {
|
||||||
return null;
|
return null;
|
||||||
|
|
@ -425,20 +445,71 @@ export function createAssistantTransitionPolicy(deps) {
|
||||||
return flow;
|
return flow;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function readMcpDiscoveryPreviousCounterpartyValueFlowBundle(debug) {
|
||||||
|
const entryPoint = debug?.assistant_mcp_discovery_entry_point_v1;
|
||||||
|
const bundle = entryPoint?.turn_input?.turn_meaning_ref?.previous_counterparty_value_flow_bundle;
|
||||||
|
if (!bundle || typeof bundle !== "object" || Array.isArray(bundle)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return bundle;
|
||||||
|
}
|
||||||
|
|
||||||
|
function readMcpDiscoveryPreviousCounterpartyDocumentBundle(debug) {
|
||||||
|
const entryPoint = debug?.assistant_mcp_discovery_entry_point_v1;
|
||||||
|
const bundle = entryPoint?.turn_input?.turn_meaning_ref?.previous_counterparty_document_bundle;
|
||||||
|
if (!bundle || typeof bundle !== "object" || Array.isArray(bundle)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return bundle;
|
||||||
|
}
|
||||||
|
|
||||||
function readCounterpartyDocumentSummaryFromItem(item) {
|
function readCounterpartyDocumentSummaryFromItem(item) {
|
||||||
const text = deps.toNonEmptyString(item?.text);
|
const text = deps.toNonEmptyString(item?.text);
|
||||||
if (!text) {
|
if (!text) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
const firstLine = text.split(/\r?\n/).map((line) => line.trim()).find(Boolean) ?? "";
|
const firstLine = text.split(/\r?\n/).map((line) => line.trim()).find(Boolean) ?? "";
|
||||||
const match = firstLine.match(/Контрагент:\s*([^.\n]+)\.\s*Найдено документов:\s*(\d+)/iu);
|
const directMatch = firstLine.match(/Контрагент:\s*([^.\n]+)\.\s*Найдено документов:\s*(\d+)/iu);
|
||||||
if (!match?.[1] || !match?.[2]) {
|
if (directMatch?.[1] && directMatch?.[2]) {
|
||||||
|
return {
|
||||||
|
counterparty: deps.toNonEmptyString(directMatch[1]),
|
||||||
|
document_count: Number(directMatch[2]),
|
||||||
|
direct_answer: firstLine
|
||||||
|
};
|
||||||
|
}
|
||||||
|
const summaryMatch = text.match(/Отдельно\s+по\s+контрагенту\s+([^:\n]+):[\s\S]{0,260}документы\s+по\s+цепочке:\s*найдено\s*(\d+)/iu);
|
||||||
|
if (!summaryMatch?.[1] || !summaryMatch?.[2]) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
counterparty: deps.toNonEmptyString(summaryMatch[1]),
|
||||||
|
document_count: Number(summaryMatch[2]),
|
||||||
|
direct_answer: summaryMatch[0].replace(/\s+/g, " ").trim()
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function readCounterpartyValueFlowSummaryFromItem(item) {
|
||||||
|
const text = deps.toNonEmptyString(item?.text);
|
||||||
|
if (!text) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
const match = text.match(
|
||||||
|
/Отдельно\s+по\s+контрагенту\s+([^:\n]+):\s*подтверждено\s+получили\s+([^,\n]+?руб\.?),\s*заплатили\s+([^,\n]+?руб\.?),\s*расчетное\s+нетто\s+в\s+нашу\s+сторону\s+([^.\n]+?руб\.?)/iu
|
||||||
|
);
|
||||||
|
if (!match?.[1] || !match?.[2] || !match?.[3] || !match?.[4]) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
counterparty: deps.toNonEmptyString(match[1]),
|
counterparty: deps.toNonEmptyString(match[1]),
|
||||||
document_count: Number(match[2]),
|
incoming_customer_revenue: {
|
||||||
direct_answer: firstLine
|
total_amount_human_ru: deps.toNonEmptyString(match[2])
|
||||||
|
},
|
||||||
|
outgoing_supplier_payout: {
|
||||||
|
total_amount_human_ru: deps.toNonEmptyString(match[3])
|
||||||
|
},
|
||||||
|
net_amount_human_ru: deps.toNonEmptyString(match[4]),
|
||||||
|
net_direction: "net_incoming",
|
||||||
|
inference_basis: "parsed_from_previous_confirmed_counterparty_boundary_summary"
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -446,12 +517,51 @@ export function createAssistantTransitionPolicy(deps) {
|
||||||
for (let index = Array.isArray(items) ? items.length - 1 : -1; index >= 0; index -= 1) {
|
for (let index = Array.isArray(items) ? items.length - 1 : -1; index >= 0; index -= 1) {
|
||||||
const item = items[index];
|
const item = items[index];
|
||||||
const debug = item?.debug;
|
const debug = item?.debug;
|
||||||
if (!item || item.role !== "assistant" || !debug || typeof debug !== "object") {
|
const isAssistantItem = item?.role === "assistant" || item?.kind === "assistant";
|
||||||
|
if (!item || !isAssistantItem) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
const flow = readMcpDiscoveryBidirectionalValueFlow(debug);
|
if (debug && typeof debug === "object") {
|
||||||
if (flow) {
|
const flow = readMcpDiscoveryBidirectionalValueFlow(debug);
|
||||||
return flow;
|
if (flow) {
|
||||||
|
return flow;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const parsedFlow = readCounterpartyValueFlowSummaryFromItem(item);
|
||||||
|
if (parsedFlow) {
|
||||||
|
return parsedFlow;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function sameCounterpartyHint(expected, actual) {
|
||||||
|
const left = normalizeFollowupText(expected);
|
||||||
|
const right = normalizeFollowupText(actual);
|
||||||
|
if (!left || !right) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return left === right || left.includes(right) || right.includes(left);
|
||||||
|
}
|
||||||
|
|
||||||
|
function findRecentPreviousCounterpartyValueFlowBundle(items, counterpartyHint = null) {
|
||||||
|
const expectedCounterparty = deps.toNonEmptyString(counterpartyHint);
|
||||||
|
if (!expectedCounterparty) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
for (let index = Array.isArray(items) ? items.length - 1 : -1; index >= 0; index -= 1) {
|
||||||
|
const item = items[index];
|
||||||
|
const debug = item?.debug;
|
||||||
|
const isAssistantItem = item?.role === "assistant" || item?.kind === "assistant";
|
||||||
|
if (!item || !isAssistantItem || !debug || typeof debug !== "object") {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const bundle = readMcpDiscoveryPreviousCounterpartyValueFlowBundle(debug);
|
||||||
|
if (!bundle) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (sameCounterpartyHint(expectedCounterparty, deps.toNonEmptyString(bundle.counterparty))) {
|
||||||
|
return bundle;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
|
|
@ -460,7 +570,8 @@ export function createAssistantTransitionPolicy(deps) {
|
||||||
function findRecentCounterpartyDocumentBundle(items) {
|
function findRecentCounterpartyDocumentBundle(items) {
|
||||||
for (let index = Array.isArray(items) ? items.length - 1 : -1; index >= 0; index -= 1) {
|
for (let index = Array.isArray(items) ? items.length - 1 : -1; index >= 0; index -= 1) {
|
||||||
const item = items[index];
|
const item = items[index];
|
||||||
if (!item || item.role !== "assistant" || !item.debug || typeof item.debug !== "object") {
|
const isAssistantItem = item?.role === "assistant" || item?.kind === "assistant";
|
||||||
|
if (!item || !isAssistantItem) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
const summary = readCounterpartyDocumentSummaryFromItem(item);
|
const summary = readCounterpartyDocumentSummaryFromItem(item);
|
||||||
|
|
@ -471,6 +582,29 @@ export function createAssistantTransitionPolicy(deps) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function findRecentPreviousCounterpartyDocumentBundle(items, counterpartyHint = null) {
|
||||||
|
const expectedCounterparty = deps.toNonEmptyString(counterpartyHint);
|
||||||
|
if (!expectedCounterparty) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
for (let index = Array.isArray(items) ? items.length - 1 : -1; index >= 0; index -= 1) {
|
||||||
|
const item = items[index];
|
||||||
|
const debug = item?.debug;
|
||||||
|
const isAssistantItem = item?.role === "assistant" || item?.kind === "assistant";
|
||||||
|
if (!item || !isAssistantItem || !debug || typeof debug !== "object") {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const bundle = readMcpDiscoveryPreviousCounterpartyDocumentBundle(debug);
|
||||||
|
if (!bundle) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (sameCounterpartyHint(expectedCounterparty, deps.toNonEmptyString(bundle.counterparty))) {
|
||||||
|
return bundle;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
function hasInventoryPurchaseDateVatBridgeSignal(userMessage, alternateMessage, sourceIntentHint, hasInventoryItemFocusHint) {
|
function hasInventoryPurchaseDateVatBridgeSignal(userMessage, alternateMessage, sourceIntentHint, hasInventoryItemFocusHint) {
|
||||||
if (
|
if (
|
||||||
sourceIntentHint !== "inventory_purchase_provenance_for_item" &&
|
sourceIntentHint !== "inventory_purchase_provenance_for_item" &&
|
||||||
|
|
@ -681,10 +815,22 @@ export function createAssistantTransitionPolicy(deps) {
|
||||||
? hasShortValueFlowRetargetCue(String(alternateMessage ?? "")) ||
|
? hasShortValueFlowRetargetCue(String(alternateMessage ?? "")) ||
|
||||||
hasCompactCashflowFollowupCue(String(alternateMessage ?? ""))
|
hasCompactCashflowFollowupCue(String(alternateMessage ?? ""))
|
||||||
: false);
|
: false);
|
||||||
|
const earlyNavigationSessionState = resolveNavigationSessionContextState(
|
||||||
|
addressNavigationState,
|
||||||
|
deps.toNonEmptyString,
|
||||||
|
deps.normalizeOrganizationScopeValue
|
||||||
|
);
|
||||||
|
const earlyNavigationFocusObject = earlyNavigationSessionState.focusObject;
|
||||||
|
const selectedCounterpartyDocumentFollowupSignal = Boolean(
|
||||||
|
deps.toNonEmptyString(earlyNavigationFocusObject?.label) &&
|
||||||
|
deps.toNonEmptyString(earlyNavigationFocusObject?.objectType) === "counterparty" &&
|
||||||
|
hasSelectedCounterpartyDocumentFollowupSignal(userMessage, alternateMessage)
|
||||||
|
);
|
||||||
if (
|
if (
|
||||||
assistantTurnMeaning?.stale_replay_forbidden === true &&
|
assistantTurnMeaning?.stale_replay_forbidden === true &&
|
||||||
!hasExplicitSummaryBundleReuseSignal(userMessage, alternateMessage) &&
|
!hasExplicitSummaryBundleReuseSignal(userMessage, alternateMessage) &&
|
||||||
!compactCashflowFollowupSignal
|
!compactCashflowFollowupSignal &&
|
||||||
|
!selectedCounterpartyDocumentFollowupSignal
|
||||||
) {
|
) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
@ -784,11 +930,7 @@ export function createAssistantTransitionPolicy(deps) {
|
||||||
sourceDiscoveryPilotScopeHint === "business_overview_route_template_v1";
|
sourceDiscoveryPilotScopeHint === "business_overview_route_template_v1";
|
||||||
const hasBusinessOverviewCarryoverSourceHint =
|
const hasBusinessOverviewCarryoverSourceHint =
|
||||||
sourceDiscoveryPilotScopeHint === "business_overview_route_template_v1";
|
sourceDiscoveryPilotScopeHint === "business_overview_route_template_v1";
|
||||||
const navigationSessionState = resolveNavigationSessionContextState(
|
const navigationSessionState = earlyNavigationSessionState;
|
||||||
addressNavigationState,
|
|
||||||
deps.toNonEmptyString,
|
|
||||||
deps.normalizeOrganizationScopeValue
|
|
||||||
);
|
|
||||||
const navigationFocusObjectHint = navigationSessionState.focusObject;
|
const navigationFocusObjectHint = navigationSessionState.focusObject;
|
||||||
const hasNavigationInventoryItemFocusHint = Boolean(
|
const hasNavigationInventoryItemFocusHint = Boolean(
|
||||||
deps.toNonEmptyString(navigationFocusObjectHint?.label) &&
|
deps.toNonEmptyString(navigationFocusObjectHint?.label) &&
|
||||||
|
|
@ -912,6 +1054,7 @@ export function createAssistantTransitionPolicy(deps) {
|
||||||
Boolean(debtRoleSwapIntent) ||
|
Boolean(debtRoleSwapIntent) ||
|
||||||
shortValueFlowRetargetPrimary ||
|
shortValueFlowRetargetPrimary ||
|
||||||
shortValueFlowRetargetAlternate ||
|
shortValueFlowRetargetAlternate ||
|
||||||
|
selectedCounterpartyDocumentFollowupSignal ||
|
||||||
businessOverviewBoundaryFollowupPrimary ||
|
businessOverviewBoundaryFollowupPrimary ||
|
||||||
businessOverviewBoundaryFollowupAlternate ||
|
businessOverviewBoundaryFollowupAlternate ||
|
||||||
inventoryMarginRankingFollowup ||
|
inventoryMarginRankingFollowup ||
|
||||||
|
|
@ -937,6 +1080,7 @@ export function createAssistantTransitionPolicy(deps) {
|
||||||
Boolean(debtRoleSwapIntent) ||
|
Boolean(debtRoleSwapIntent) ||
|
||||||
shortValueFlowRetargetPrimary ||
|
shortValueFlowRetargetPrimary ||
|
||||||
shortValueFlowRetargetAlternate ||
|
shortValueFlowRetargetAlternate ||
|
||||||
|
selectedCounterpartyDocumentFollowupSignal ||
|
||||||
businessOverviewBoundaryFollowupPrimary ||
|
businessOverviewBoundaryFollowupPrimary ||
|
||||||
businessOverviewBoundaryFollowupAlternate ||
|
businessOverviewBoundaryFollowupAlternate ||
|
||||||
inventoryMarginRankingFollowup ||
|
inventoryMarginRankingFollowup ||
|
||||||
|
|
@ -969,6 +1113,7 @@ export function createAssistantTransitionPolicy(deps) {
|
||||||
!hasInventoryRootRestatementAlternate &&
|
!hasInventoryRootRestatementAlternate &&
|
||||||
!shortValueFlowRetargetPrimary &&
|
!shortValueFlowRetargetPrimary &&
|
||||||
!shortValueFlowRetargetAlternate &&
|
!shortValueFlowRetargetAlternate &&
|
||||||
|
!selectedCounterpartyDocumentFollowupSignal &&
|
||||||
!hasImplicitContinuationSignal &&
|
!hasImplicitContinuationSignal &&
|
||||||
!hasSuggestedIntentPivotSignal &&
|
!hasSuggestedIntentPivotSignal &&
|
||||||
!hasOrganizationClarificationContinuation &&
|
!hasOrganizationClarificationContinuation &&
|
||||||
|
|
@ -987,6 +1132,7 @@ export function createAssistantTransitionPolicy(deps) {
|
||||||
!hasInventoryRootRestatementAlternate &&
|
!hasInventoryRootRestatementAlternate &&
|
||||||
!shortValueFlowRetargetPrimary &&
|
!shortValueFlowRetargetPrimary &&
|
||||||
!shortValueFlowRetargetAlternate &&
|
!shortValueFlowRetargetAlternate &&
|
||||||
|
!selectedCounterpartyDocumentFollowupSignal &&
|
||||||
!hasImplicitContinuationSignal &&
|
!hasImplicitContinuationSignal &&
|
||||||
!hasSuggestedIntentPivotSignal &&
|
!hasSuggestedIntentPivotSignal &&
|
||||||
!hasOrganizationClarificationContinuation &&
|
!hasOrganizationClarificationContinuation &&
|
||||||
|
|
@ -1062,9 +1208,22 @@ export function createAssistantTransitionPolicy(deps) {
|
||||||
carryoverSourceDebug,
|
carryoverSourceDebug,
|
||||||
deps.toNonEmptyString
|
deps.toNonEmptyString
|
||||||
);
|
);
|
||||||
|
const sourceDiscoveryCounterpartyHint =
|
||||||
|
sourceDiscoveryLoopMetadataScopeHint ??
|
||||||
|
(deps.toNonEmptyString(earlyNavigationFocusObject?.objectType) === "counterparty"
|
||||||
|
? deps.toNonEmptyString(earlyNavigationFocusObject?.label)
|
||||||
|
: null);
|
||||||
const sourceDiscoveryBidirectionalValueFlow =
|
const sourceDiscoveryBidirectionalValueFlow =
|
||||||
readMcpDiscoveryBidirectionalValueFlow(carryoverSourceDebug) ?? findRecentDiscoveryValueFlowBundle(items);
|
readMcpDiscoveryBidirectionalValueFlow(carryoverSourceDebug) ??
|
||||||
const sourceDiscoveryDocumentSummary = findRecentCounterpartyDocumentBundle(items);
|
readMcpDiscoveryPreviousCounterpartyValueFlowBundle(carryoverSourceDebug) ??
|
||||||
|
findRecentPreviousCounterpartyValueFlowBundle(items, sourceDiscoveryCounterpartyHint) ??
|
||||||
|
readCounterpartyValueFlowSummaryFromItem(previousAddressItem) ??
|
||||||
|
findRecentDiscoveryValueFlowBundle(items);
|
||||||
|
const sourceDiscoveryDocumentSummary =
|
||||||
|
readMcpDiscoveryPreviousCounterpartyDocumentBundle(carryoverSourceDebug) ??
|
||||||
|
findRecentPreviousCounterpartyDocumentBundle(items, sourceDiscoveryCounterpartyHint) ??
|
||||||
|
readCounterpartyDocumentSummaryFromItem(previousAddressItem) ??
|
||||||
|
findRecentCounterpartyDocumentBundle(items);
|
||||||
const llmExplicitIntent = deps.toNonEmptyString(llmPreDecomposeMeta?.predecomposeContract?.intent);
|
const llmExplicitIntent = deps.toNonEmptyString(llmPreDecomposeMeta?.predecomposeContract?.intent);
|
||||||
const llmSelectedObjectScopeDetected =
|
const llmSelectedObjectScopeDetected =
|
||||||
llmPreDecomposeMeta?.predecomposeContract?.semantics?.selected_object_scope_detected === true;
|
llmPreDecomposeMeta?.predecomposeContract?.semantics?.selected_object_scope_detected === true;
|
||||||
|
|
@ -1240,6 +1399,17 @@ export function createAssistantTransitionPolicy(deps) {
|
||||||
let resolvedCounterpartyFromDisplay = false;
|
let resolvedCounterpartyFromDisplay = false;
|
||||||
let displayedEntityTargetIntent = null;
|
let displayedEntityTargetIntent = null;
|
||||||
let previousFilters = resolveAddressDebugCarryoverFilters(carryoverSourceDebug, deps.toNonEmptyString);
|
let previousFilters = resolveAddressDebugCarryoverFilters(carryoverSourceDebug, deps.toNonEmptyString);
|
||||||
|
const navigationCounterpartyFocus =
|
||||||
|
navigationFocusObjectType === "counterparty" ? navigationFocusObjectLabel : null;
|
||||||
|
const hasNavigationCounterpartyFocusCarryover = Boolean(
|
||||||
|
navigationCounterpartyFocus &&
|
||||||
|
(hasValueFlowCarryoverSourceHint ||
|
||||||
|
sourceIntentHint === "list_contracts_by_counterparty" ||
|
||||||
|
sourceIntentHint === "list_documents_by_counterparty" ||
|
||||||
|
sourceIntentHint === "bank_operations_by_counterparty" ||
|
||||||
|
sourceIntentHint === "open_items_by_counterparty_or_contract" ||
|
||||||
|
sourceDiscoveryLoopSelectedChainIdHint === "value_flow_comparison")
|
||||||
|
);
|
||||||
const shouldBackfillHistoricalPartyAnchors =
|
const shouldBackfillHistoricalPartyAnchors =
|
||||||
sourceIntentHint === "list_contracts_by_counterparty" ||
|
sourceIntentHint === "list_contracts_by_counterparty" ||
|
||||||
sourceIntentHint === "list_documents_by_counterparty" ||
|
sourceIntentHint === "list_documents_by_counterparty" ||
|
||||||
|
|
@ -1254,6 +1424,15 @@ export function createAssistantTransitionPolicy(deps) {
|
||||||
deps.findRecentAddressFilterValue(items, "counterparty"),
|
deps.findRecentAddressFilterValue(items, "counterparty"),
|
||||||
deps.toNonEmptyString
|
deps.toNonEmptyString
|
||||||
);
|
);
|
||||||
|
if (hasNavigationCounterpartyFocusCarryover && navigationCounterpartyFocus) {
|
||||||
|
if (!previousAnchor) {
|
||||||
|
previousAnchorType = "counterparty";
|
||||||
|
previousAnchor = navigationCounterpartyFocus;
|
||||||
|
}
|
||||||
|
if (!deps.toNonEmptyString(previousFilters.counterparty)) {
|
||||||
|
previousFilters.counterparty = navigationCounterpartyFocus;
|
||||||
|
}
|
||||||
|
}
|
||||||
const historicalOrganization = deps.findRecentAddressFilterValue(items, "organization");
|
const historicalOrganization = deps.findRecentAddressFilterValue(items, "organization");
|
||||||
const authorityActiveOrganization =
|
const authorityActiveOrganization =
|
||||||
deps.normalizeOrganizationScopeValue(organizationAuthority.activeOrganization) ??
|
deps.normalizeOrganizationScopeValue(organizationAuthority.activeOrganization) ??
|
||||||
|
|
|
||||||
|
|
@ -65,6 +65,14 @@ export interface AddressNavigationEvent {
|
||||||
export interface AddressNavigationSessionContext {
|
export interface AddressNavigationSessionContext {
|
||||||
active_result_set_id: string | null;
|
active_result_set_id: string | null;
|
||||||
active_focus_object: AddressFocusObject | null;
|
active_focus_object: AddressFocusObject | null;
|
||||||
|
comparison_scope: {
|
||||||
|
organization: AddressFocusObject | null;
|
||||||
|
counterparty: AddressFocusObject | null;
|
||||||
|
proof_bundles: {
|
||||||
|
counterparty_value_flow_bundle: Record<string, unknown> | null;
|
||||||
|
counterparty_document_bundle: Record<string, unknown> | null;
|
||||||
|
} | null;
|
||||||
|
} | null;
|
||||||
last_confirmed_route: string | null;
|
last_confirmed_route: string | null;
|
||||||
date_scope: {
|
date_scope: {
|
||||||
as_of_date: string | null;
|
as_of_date: string | null;
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@ import type {
|
||||||
} from "./assistantRuntimeContracts";
|
} from "./assistantRuntimeContracts";
|
||||||
|
|
||||||
export type AddressIntent =
|
export type AddressIntent =
|
||||||
|
| "business_overview"
|
||||||
| "period_coverage_profile"
|
| "period_coverage_profile"
|
||||||
| "document_type_and_account_section_profile"
|
| "document_type_and_account_section_profile"
|
||||||
| "counterparty_population_and_roles"
|
| "counterparty_population_and_roles"
|
||||||
|
|
|
||||||
|
|
@ -529,6 +529,11 @@ export interface AssistantDebugPayload {
|
||||||
fa_live_route_audit?: FaLiveRouteAuditDebug;
|
fa_live_route_audit?: FaLiveRouteAuditDebug;
|
||||||
eligibility_time_basis?: GroundedAnswerEligibilityGuardDebug["eligibility_time_basis"];
|
eligibility_time_basis?: GroundedAnswerEligibilityGuardDebug["eligibility_time_basis"];
|
||||||
grounded_answer_eligibility_guard?: GroundedAnswerEligibilityGuardDebug;
|
grounded_answer_eligibility_guard?: GroundedAnswerEligibilityGuardDebug;
|
||||||
|
mcp_discovery_response_applied?: boolean;
|
||||||
|
mcp_discovery_selected_chain_id?: string | null;
|
||||||
|
mcp_discovery_effective_response_route?: string | null;
|
||||||
|
assistant_mcp_discovery_entry_point_v1?: unknown;
|
||||||
|
mcp_discovery_response_candidate_v1?: unknown;
|
||||||
followup_state_usage?: FollowupStateUsageDebug;
|
followup_state_usage?: FollowupStateUsageDebug;
|
||||||
problem_centric_answer_applied?: boolean;
|
problem_centric_answer_applied?: boolean;
|
||||||
problem_units_used_count?: number;
|
problem_units_used_count?: number;
|
||||||
|
|
|
||||||
|
|
@ -72,6 +72,15 @@ describe("addressIntentResolver regression bridges", () => {
|
||||||
expect(result.reasons).toContain("unicode_business_overview_debt_position_deferred_to_discovery");
|
expect(result.reasons).toContain("unicode_business_overview_debt_position_deferred_to_discovery");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("defers colloquial multi-surface company overview to discovery before exact VAT", () => {
|
||||||
|
const result = resolveAddressIntent(
|
||||||
|
"\u041f\u043e-\u0447\u0435\u043b\u043e\u0432\u0435\u0447\u0435\u0441\u043a\u0438 \u043f\u043e\u0441\u043c\u043e\u0442\u0440\u0438 \u041e\u041e\u041e \u0410\u043b\u044c\u0442\u0435\u0440\u043d\u0430\u0442\u0438\u0432\u0430 \u041f\u043b\u044e\u0441 \u0437\u0430 2020: \u0447\u0442\u043e \u043f\u043e \u0431\u0438\u0437\u043d\u0435\u0441\u0443 \u0432\u0438\u0434\u043d\u043e \u0432 1\u0421 \u043f\u043e \u0434\u0435\u043d\u044c\u0433\u0430\u043c, \u041d\u0414\u0421, \u0434\u043e\u043b\u0433\u0430\u043c, \u0441\u043a\u043b\u0430\u0434\u0443, \u043a\u043b\u0438\u0435\u043d\u0442\u0430\u043c/\u043f\u043e\u0441\u0442\u0430\u0432\u0449\u0438\u043a\u0430\u043c \u0438 \u0433\u0434\u0435 \u043f\u043e\u043a\u0430 \u043d\u0435\u043b\u044c\u0437\u044f \u0434\u0435\u043b\u0430\u0442\u044c \u0432\u044b\u0432\u043e\u0434?"
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(result.intent).toBe("unknown");
|
||||||
|
expect(result.reasons).toContain("unicode_business_overview_multi_surface_deferred_to_discovery");
|
||||||
|
});
|
||||||
|
|
||||||
it("detects specific counterparty turnover wording as revenue profile", () => {
|
it("detects specific counterparty turnover wording as revenue profile", () => {
|
||||||
const result = resolveAddressIntent(
|
const result = resolveAddressIntent(
|
||||||
"\u043a\u0430\u043a\u043e\u0439 \u043e\u0431\u043e\u0440\u043e\u0442 \u0431\u044b\u043b \u0441\u0432\u043a"
|
"\u043a\u0430\u043a\u043e\u0439 \u043e\u0431\u043e\u0440\u043e\u0442 \u0431\u044b\u043b \u0441\u0432\u043a"
|
||||||
|
|
|
||||||
|
|
@ -55,6 +55,231 @@ describe("address navigation state", () => {
|
||||||
expect(evolved.navigation_history[0]?.action).toBe("open");
|
expect(evolved.navigation_history[0]?.action).toBe("open");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("captures organization focus from applied business overview discovery turns", () => {
|
||||||
|
const base = createEmptyAddressNavigationState("asst-bo", "2026-04-12T10:00:00.000Z");
|
||||||
|
const assistantItem = {
|
||||||
|
message_id: "msg-bo1",
|
||||||
|
session_id: "asst-bo",
|
||||||
|
role: "assistant",
|
||||||
|
text: "Коротко: по данным ООО Альтернатива Плюс за 2020 подтвержден бизнес-обзор.",
|
||||||
|
reply_type: "partial_coverage",
|
||||||
|
created_at: "2026-04-12T10:10:00.000Z",
|
||||||
|
trace_id: "address-bo",
|
||||||
|
debug: {
|
||||||
|
detected_mode: "address_query",
|
||||||
|
detected_intent: "unknown",
|
||||||
|
selected_recipe: null,
|
||||||
|
extracted_filters: {},
|
||||||
|
anchor_type: "unknown",
|
||||||
|
mcp_discovery_response_applied: true,
|
||||||
|
mcp_discovery_selected_chain_id: "business_overview",
|
||||||
|
assistant_mcp_discovery_entry_point_v1: {
|
||||||
|
schema_version: "assistant_mcp_discovery_runtime_entry_point_v1",
|
||||||
|
entry_status: "bridge_executed",
|
||||||
|
turn_input: {
|
||||||
|
turn_meaning_ref: {
|
||||||
|
explicit_organization_scope: "ООО Альтернатива Плюс",
|
||||||
|
explicit_date_scope: "2020"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
bridge: {
|
||||||
|
bridge_status: "answer_draft_ready",
|
||||||
|
answer_draft: {
|
||||||
|
answer_mode: "confirmed_with_bounded_inference"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
dialog_continuation_contract_v2: {
|
||||||
|
decision: "new_topic"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} as any;
|
||||||
|
|
||||||
|
const evolved = evolveAddressNavigationStateWithAssistantItem(base, assistantItem, 2);
|
||||||
|
expect(evolved.result_sets[0]?.route_id).toBe("business_overview");
|
||||||
|
expect(evolved.result_sets[0]?.filters.organization).toBe("ООО Альтернатива Плюс");
|
||||||
|
expect(evolved.session_context.active_focus_object?.object_type).toBe("organization");
|
||||||
|
expect(evolved.session_context.active_focus_object?.label).toBe("ООО Альтернатива Плюс");
|
||||||
|
expect(evolved.session_context.organization_scope).toBe("ООО Альтернатива Плюс");
|
||||||
|
expect(evolved.session_context.date_scope.period_from).toBe("2020-01-01");
|
||||||
|
expect(evolved.session_context.date_scope.period_to).toBe("2020-12-31");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("captures counterparty focus from applied discovery turns even when the response stayed in chat mode", () => {
|
||||||
|
const base = createEmptyAddressNavigationState("asst-vf", "2026-04-12T10:00:00.000Z");
|
||||||
|
const assistantItem = {
|
||||||
|
message_id: "msg-vf1",
|
||||||
|
session_id: "asst-vf",
|
||||||
|
role: "assistant",
|
||||||
|
text: "\u041f\u043e \u0413\u0440\u0443\u043f\u043f\u0430 \u0421\u0412\u041a \u0437\u0430 2020 \u043f\u043e\u0434\u0442\u0432\u0435\u0440\u0436\u0434\u0435\u043d\u044b \u0432\u0445\u043e\u0434\u044f\u0449\u0438\u0435 \u0438 \u0438\u0441\u0445\u043e\u0434\u044f\u0449\u0438\u0435 \u0434\u0435\u043d\u044c\u0433\u0438.",
|
||||||
|
reply_type: "partial_coverage",
|
||||||
|
created_at: "2026-04-12T10:12:00.000Z",
|
||||||
|
trace_id: "chat-vf",
|
||||||
|
debug: {
|
||||||
|
detected_mode: "chat",
|
||||||
|
detected_intent: "unknown",
|
||||||
|
selected_recipe: null,
|
||||||
|
extracted_filters: {},
|
||||||
|
anchor_type: "unknown",
|
||||||
|
mcp_discovery_response_applied: true,
|
||||||
|
mcp_discovery_selected_chain_id: "value_flow_comparison",
|
||||||
|
assistant_mcp_discovery_entry_point_v1: {
|
||||||
|
schema_version: "assistant_mcp_discovery_runtime_entry_point_v1",
|
||||||
|
entry_status: "bridge_executed",
|
||||||
|
turn_input: {
|
||||||
|
turn_meaning_ref: {
|
||||||
|
asked_domain_family: "counterparty_value",
|
||||||
|
asked_action_family: "compare_incoming_outgoing",
|
||||||
|
explicit_entity_candidates: ["\u0413\u0440\u0443\u043f\u043f\u0430 \u0421\u0412\u041a"],
|
||||||
|
stale_replay_forbidden: true,
|
||||||
|
explicit_date_scope: "2020"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
bridge: {
|
||||||
|
bridge_status: "answer_draft_ready",
|
||||||
|
answer_draft: {
|
||||||
|
answer_mode: "confirmed_factual"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
dialog_continuation_contract_v2: {
|
||||||
|
decision: "new_topic"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} as any;
|
||||||
|
|
||||||
|
const evolved = evolveAddressNavigationStateWithAssistantItem(base, assistantItem, 2);
|
||||||
|
expect(evolved.result_sets[0]?.route_id).toBe("value_flow_comparison");
|
||||||
|
expect(evolved.result_sets[0]?.intent).toBe("customer_revenue_and_payments");
|
||||||
|
expect(evolved.result_sets[0]?.filters.counterparty).toBe("\u0413\u0440\u0443\u043f\u043f\u0430 \u0421\u0412\u041a");
|
||||||
|
expect(evolved.session_context.active_focus_object?.object_type).toBe("counterparty");
|
||||||
|
expect(evolved.session_context.active_focus_object?.label).toBe("\u0413\u0440\u0443\u043f\u043f\u0430 \u0421\u0412\u041a");
|
||||||
|
expect(evolved.session_context.date_scope.period_from).toBe("2020-01-01");
|
||||||
|
expect(evolved.session_context.date_scope.period_to).toBe("2020-12-31");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("keeps selected counterparty focus for company boundary summaries", () => {
|
||||||
|
const initial = createEmptyAddressNavigationState("asst-boundary", "2026-04-12T10:00:00.000Z");
|
||||||
|
const assistantItem = {
|
||||||
|
message_id: "msg-boundary",
|
||||||
|
session_id: "asst-boundary",
|
||||||
|
role: "assistant",
|
||||||
|
text:
|
||||||
|
"Коротко: по компании Альтернатива Плюс подтвержден company-level денежный срез.\n" +
|
||||||
|
"Отдельно по выбранному контрагенту Группа СВК: суммы компании на него не переношу.",
|
||||||
|
created_at: "2026-04-12T10:05:00.000Z",
|
||||||
|
debug: {
|
||||||
|
detected_intent: "unknown",
|
||||||
|
mcp_discovery_selected_chain_id: "business_overview",
|
||||||
|
mcp_discovery_response_applied: true,
|
||||||
|
extracted_filters: {
|
||||||
|
organization: "Альтернатива Плюс"
|
||||||
|
},
|
||||||
|
assistant_mcp_discovery_entry_point_v1: {
|
||||||
|
turn_input: {
|
||||||
|
turn_meaning_ref: {
|
||||||
|
asked_domain_family: "business_overview",
|
||||||
|
asked_action_family: "broad_evaluation",
|
||||||
|
business_overview_separate_entity_candidates: ["Группа СВК"],
|
||||||
|
explicit_organization_scope: "Альтернатива Плюс"
|
||||||
|
},
|
||||||
|
data_need_graph: {
|
||||||
|
business_fact_family: "business_overview",
|
||||||
|
subject_candidates: []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} as any;
|
||||||
|
|
||||||
|
const evolved = evolveAddressNavigationStateWithAssistantItem(initial, assistantItem, 1);
|
||||||
|
|
||||||
|
expect(evolved.session_context.organization_scope).toBe("Альтернатива Плюс");
|
||||||
|
expect(evolved.session_context.active_focus_object?.object_type).toBe("counterparty");
|
||||||
|
expect(evolved.session_context.active_focus_object?.label).toBe("Группа СВК");
|
||||||
|
expect(evolved.session_context.comparison_scope?.organization?.label).toBe("Альтернатива Плюс");
|
||||||
|
expect(evolved.session_context.comparison_scope?.counterparty?.label).toBe("Группа СВК");
|
||||||
|
expect(evolved.navigation_history[0]?.target_object_id).toBe("counterparty:группа свк");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("carries comparison proof bundles through organization clarification", () => {
|
||||||
|
const org = "\u0410\u043b\u044c\u0442\u0435\u0440\u043d\u0430\u0442\u0438\u0432\u0430 \u041f\u043b\u044e\u0441";
|
||||||
|
const counterparty = "\u0413\u0440\u0443\u043f\u043f\u0430 \u0421\u0412\u041a";
|
||||||
|
const valueFlowBundle = {
|
||||||
|
counterparty,
|
||||||
|
incoming_customer_revenue: { total_amount_human_ru: "20 653 490 руб." },
|
||||||
|
outgoing_supplier_payout: { total_amount_human_ru: "2 129 651 руб." },
|
||||||
|
net_amount_human_ru: "18 523 839 руб."
|
||||||
|
};
|
||||||
|
const documentBundle = { counterparty, document_count: 19 };
|
||||||
|
const initial = createEmptyAddressNavigationState("asst-proof", "2026-04-12T10:00:00.000Z");
|
||||||
|
const boundaryItem = {
|
||||||
|
message_id: "msg-boundary-proof",
|
||||||
|
session_id: "asst-proof",
|
||||||
|
role: "assistant",
|
||||||
|
text: `Коротко: уточните организацию для сравнения ${counterparty}.`,
|
||||||
|
created_at: "2026-04-12T10:05:00.000Z",
|
||||||
|
debug: {
|
||||||
|
detected_intent: "unknown",
|
||||||
|
mcp_discovery_selected_chain_id: "business_overview",
|
||||||
|
mcp_discovery_response_applied: true,
|
||||||
|
extracted_filters: {},
|
||||||
|
assistant_mcp_discovery_entry_point_v1: {
|
||||||
|
turn_input: {
|
||||||
|
turn_meaning_ref: {
|
||||||
|
asked_domain_family: "business_overview",
|
||||||
|
asked_action_family: "broad_evaluation",
|
||||||
|
business_overview_separate_entity_candidates: [counterparty],
|
||||||
|
previous_counterparty_value_flow_bundle: valueFlowBundle,
|
||||||
|
previous_counterparty_document_bundle: documentBundle
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} as any;
|
||||||
|
|
||||||
|
const afterBoundary = evolveAddressNavigationStateWithAssistantItem(initial, boundaryItem, 1);
|
||||||
|
expect(afterBoundary.session_context.comparison_scope?.organization).toBeNull();
|
||||||
|
expect(afterBoundary.session_context.comparison_scope?.counterparty?.label).toBe(counterparty);
|
||||||
|
expect(afterBoundary.session_context.comparison_scope?.proof_bundles?.counterparty_value_flow_bundle).toEqual(
|
||||||
|
valueFlowBundle
|
||||||
|
);
|
||||||
|
|
||||||
|
const clarifiedItem = {
|
||||||
|
message_id: "msg-boundary-org",
|
||||||
|
session_id: "asst-proof",
|
||||||
|
role: "assistant",
|
||||||
|
text: `Коротко: по компании ${org} подтвержден company-level срез. Отдельно по ${counterparty}: границу не теряю.`,
|
||||||
|
created_at: "2026-04-12T10:06:00.000Z",
|
||||||
|
debug: {
|
||||||
|
detected_intent: "business_overview",
|
||||||
|
mcp_discovery_selected_chain_id: "business_overview",
|
||||||
|
mcp_discovery_response_applied: true,
|
||||||
|
extracted_filters: { organization: org },
|
||||||
|
assistant_mcp_discovery_entry_point_v1: {
|
||||||
|
turn_input: {
|
||||||
|
turn_meaning_ref: {
|
||||||
|
asked_domain_family: "business_overview",
|
||||||
|
asked_action_family: "broad_evaluation",
|
||||||
|
business_overview_separate_entity_candidates: [counterparty],
|
||||||
|
explicit_organization_scope: org
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} as any;
|
||||||
|
|
||||||
|
const afterClarification = evolveAddressNavigationStateWithAssistantItem(afterBoundary, clarifiedItem, 2);
|
||||||
|
expect(afterClarification.session_context.comparison_scope?.organization?.label).toBe(org);
|
||||||
|
expect(afterClarification.session_context.comparison_scope?.counterparty?.label).toBe(counterparty);
|
||||||
|
expect(afterClarification.session_context.comparison_scope?.proof_bundles?.counterparty_value_flow_bundle).toEqual(
|
||||||
|
valueFlowBundle
|
||||||
|
);
|
||||||
|
expect(afterClarification.session_context.comparison_scope?.proof_bundles?.counterparty_document_bundle).toEqual(
|
||||||
|
documentBundle
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
it("tracks drilldown event for follow-up continuation turn", () => {
|
it("tracks drilldown event for follow-up continuation turn", () => {
|
||||||
const initial = normalizeAddressNavigationState(
|
const initial = normalizeAddressNavigationState(
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -2145,6 +2145,43 @@ describe("address compose stage utf8 headers", () => {
|
||||||
expect(reply.semantics?.balance_confirmed).toBe(true);
|
expect(reply.semantics?.balance_confirmed).toBe(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("explains purchase-date anchor for confirmed VAT tax-period reply", () => {
|
||||||
|
const reply = composeFactualReply(
|
||||||
|
"vat_liability_confirmed_for_tax_period",
|
||||||
|
[
|
||||||
|
{
|
||||||
|
period: "2015-02-28T23:59:59Z",
|
||||||
|
registrator: "VAT_BOOK_SALES",
|
||||||
|
account_dt: "68.02",
|
||||||
|
account_kt: "",
|
||||||
|
amount: 3500000,
|
||||||
|
analytics: []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
period: "2015-02-28T23:59:59Z",
|
||||||
|
registrator: "VAT_BOOK_PURCHASES",
|
||||||
|
account_dt: "19",
|
||||||
|
account_kt: "",
|
||||||
|
amount: 868612,
|
||||||
|
analytics: []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
{
|
||||||
|
userMessage: "ндс можешь прикинуть на дату покупки рабочей станции?",
|
||||||
|
periodFrom: "2015-02-01",
|
||||||
|
periodTo: "2015-02-28",
|
||||||
|
asOfDate: "2016-03-31",
|
||||||
|
organizationHint: "ООО Альтернатива Плюс",
|
||||||
|
useRubCurrency: true
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(reply.responseType).toBe("FACTUAL_SUMMARY");
|
||||||
|
expect(reply.text).toContain("Якорь периода: дата покупки из вопроса/контекста");
|
||||||
|
expect(reply.text).toContain("01.02.2015..28.02.2015");
|
||||||
|
expect(reply.text).toContain("книг продаж/покупок");
|
||||||
|
});
|
||||||
|
|
||||||
it("formats VAT forecast amounts in rubles and emphasizes numbers when requested", () => {
|
it("formats VAT forecast amounts in rubles and emphasizes numbers when requested", () => {
|
||||||
const reply = composeFactualReply(
|
const reply = composeFactualReply(
|
||||||
"vat_payable_forecast",
|
"vat_payable_forecast",
|
||||||
|
|
|
||||||
|
|
@ -236,6 +236,216 @@ describe("assistant address lane response runtime adapter", () => {
|
||||||
expect(String((runtime.response as any).assistant_reply)).toContain("исходящих платежей/списаний");
|
expect(String((runtime.response as any).assistant_reply)).toContain("исходящих платежей/списаний");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("aligns final route metadata when a business-overview discovery candidate replaces a stale exact reply", () => {
|
||||||
|
const finalizeAddressTurn = vi.fn((input) => ({
|
||||||
|
response: {
|
||||||
|
ok: true,
|
||||||
|
assistant_reply: input.assistantReply,
|
||||||
|
reply_type: input.replyType,
|
||||||
|
debug: input.debug
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
|
const runtime = runAssistantAddressLaneResponseRuntime({
|
||||||
|
sessionId: "asst-business-overview-address",
|
||||||
|
userMessage: "Собери короткий бизнес-итог по компании и отдельно по СВК.",
|
||||||
|
effectiveAddressUserMessage: "Собери короткий бизнес-итог по компании и отдельно по СВК.",
|
||||||
|
addressLane: {
|
||||||
|
handled: true,
|
||||||
|
reply_text: "Контрагент: Группа СВК. Найдено документов: 19.",
|
||||||
|
reply_type: "factual",
|
||||||
|
debug: {
|
||||||
|
detected_intent: "list_documents_by_counterparty",
|
||||||
|
selected_recipe: "address_documents_by_counterparty_v1",
|
||||||
|
response_type: "FACTUAL_LIST"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
llmPreDecomposeMeta: {
|
||||||
|
mcpDiscoveryRuntimeEntryPoint: {
|
||||||
|
schema_version: "assistant_mcp_discovery_runtime_entry_point_v1",
|
||||||
|
policy_owner: "assistantMcpDiscoveryRuntimeEntryPoint",
|
||||||
|
entry_status: "bridge_executed",
|
||||||
|
hot_runtime_wired: false,
|
||||||
|
discovery_attempted: true,
|
||||||
|
turn_input: {
|
||||||
|
adapter_status: "ready",
|
||||||
|
should_run_discovery: true,
|
||||||
|
turn_meaning_ref: {
|
||||||
|
asked_domain_family: "business_overview",
|
||||||
|
asked_action_family: "broad_evaluation",
|
||||||
|
explicit_organization_scope: "ООО Альтернатива Плюс",
|
||||||
|
explicit_date_scope: "2020",
|
||||||
|
raw_message: "Собери короткий бизнес-итог по компании и отдельно по СВК."
|
||||||
|
},
|
||||||
|
data_need_graph: {
|
||||||
|
business_fact_family: "business_overview",
|
||||||
|
reason_codes: ["data_need_graph_family_business_overview"]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
bridge: {
|
||||||
|
bridge_status: "answer_draft_ready",
|
||||||
|
user_facing_response_allowed: true,
|
||||||
|
business_fact_answer_allowed: true,
|
||||||
|
requires_user_clarification: false,
|
||||||
|
planner: {
|
||||||
|
selected_chain_id: "business_overview"
|
||||||
|
},
|
||||||
|
pilot: {
|
||||||
|
pilot_scope: "business_overview_route_template_v1",
|
||||||
|
derived_business_overview: {
|
||||||
|
period_scope: "2020",
|
||||||
|
incoming_customer_revenue: {
|
||||||
|
total_amount_human_ru: "47 628 853,03 руб."
|
||||||
|
},
|
||||||
|
outgoing_supplier_payout: {
|
||||||
|
total_amount_human_ru: "43 763 351,53 руб."
|
||||||
|
},
|
||||||
|
net_amount_human_ru: "3 865 501,50 руб.",
|
||||||
|
net_direction: "net_incoming"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
answer_draft: {
|
||||||
|
answer_mode: "confirmed_with_bounded_inference",
|
||||||
|
headline: "Business overview answer",
|
||||||
|
confirmed_lines: ["Confirmed company business overview facts"],
|
||||||
|
inference_lines: [],
|
||||||
|
unknown_lines: ["Pure profit is not proven"],
|
||||||
|
limitation_lines: [],
|
||||||
|
next_step_line: null
|
||||||
|
}
|
||||||
|
},
|
||||||
|
reason_codes: ["runtime_entry_point_bridge_executed"]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
knownOrganizations: [],
|
||||||
|
activeOrganization: null,
|
||||||
|
sanitizeOutgoingAssistantText: (text) => String(text ?? ""),
|
||||||
|
buildAddressDebugPayload: (debug) => ({ ...(debug as Record<string, unknown>) }),
|
||||||
|
buildAddressFollowupOffer: () => null,
|
||||||
|
mergeKnownOrganizations: (items) => items,
|
||||||
|
toNonEmptyString: (value) => (typeof value === "string" && value.trim() ? value.trim() : null),
|
||||||
|
appendItem: () => {},
|
||||||
|
getSession: () => ({ session_id: "asst-business-overview-address", updated_at: "", items: [], investigation_state: null } as any),
|
||||||
|
persistSession: () => {},
|
||||||
|
cloneConversation: (items) => items,
|
||||||
|
logEvent: () => {},
|
||||||
|
messageIdFactory: () => "msg-business-overview-address",
|
||||||
|
finalizeAddressTurn
|
||||||
|
});
|
||||||
|
|
||||||
|
expect((runtime.response as any).debug).toEqual(
|
||||||
|
expect.objectContaining({
|
||||||
|
mcp_discovery_response_applied: true,
|
||||||
|
detected_intent: "business_overview",
|
||||||
|
selected_recipe: "business_overview",
|
||||||
|
mcp_discovery_effective_response_route: "business_overview"
|
||||||
|
})
|
||||||
|
);
|
||||||
|
expect(String((runtime.response as any).assistant_reply)).toContain("47 628 853,03");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("keeps comparison-scope proof augmentation business-facing for organization clarification", () => {
|
||||||
|
const finalizeAddressTurn = vi.fn((input) => ({
|
||||||
|
response: {
|
||||||
|
ok: true,
|
||||||
|
assistant_reply: input.assistantReply,
|
||||||
|
reply_type: input.replyType,
|
||||||
|
debug: input.debug
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
|
const runtime = runAssistantAddressLaneResponseRuntime({
|
||||||
|
sessionId: "asst-comparison-proof",
|
||||||
|
userMessage: "ООО Альтернатива Плюс",
|
||||||
|
effectiveAddressUserMessage: "ООО Альтернатива Плюс",
|
||||||
|
addressLane: {
|
||||||
|
handled: true,
|
||||||
|
reply_text:
|
||||||
|
"Коротко: по компании Альтернатива Плюс подтвержден company-level денежный срез: входящие 285 819 547,57 руб.",
|
||||||
|
reply_type: "partial_coverage",
|
||||||
|
debug: {}
|
||||||
|
},
|
||||||
|
llmPreDecomposeMeta: {
|
||||||
|
mcpDiscoveryRuntimeEntryPoint: {
|
||||||
|
schema_version: "assistant_mcp_discovery_runtime_entry_point_v1",
|
||||||
|
policy_owner: "assistantMcpDiscoveryRuntimeEntryPoint",
|
||||||
|
entry_status: "bridge_executed",
|
||||||
|
hot_runtime_wired: true,
|
||||||
|
discovery_attempted: true,
|
||||||
|
turn_input: {
|
||||||
|
turn_meaning_ref: {
|
||||||
|
asked_domain_family: "business_overview",
|
||||||
|
business_overview_separate_entity_candidates: ["Группа СВК"],
|
||||||
|
explicit_organization_scope: "Альтернатива Плюс"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
bridge: {
|
||||||
|
bridge_status: "not_applicable",
|
||||||
|
user_facing_response_allowed: false,
|
||||||
|
business_fact_answer_allowed: false,
|
||||||
|
requires_user_clarification: false
|
||||||
|
},
|
||||||
|
reason_codes: []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
knownOrganizations: [],
|
||||||
|
activeOrganization: null,
|
||||||
|
sanitizeOutgoingAssistantText: (text) => String(text ?? ""),
|
||||||
|
buildAddressDebugPayload: () => ({}),
|
||||||
|
buildAddressFollowupOffer: () => null,
|
||||||
|
mergeKnownOrganizations: (items) => items,
|
||||||
|
toNonEmptyString: (value) => (typeof value === "string" && value.trim() ? value.trim() : null),
|
||||||
|
appendItem: () => {},
|
||||||
|
getSession: () =>
|
||||||
|
({
|
||||||
|
session_id: "asst-comparison-proof",
|
||||||
|
updated_at: "",
|
||||||
|
items: [],
|
||||||
|
investigation_state: null,
|
||||||
|
address_navigation_state: {
|
||||||
|
session_context: {
|
||||||
|
comparison_scope: {
|
||||||
|
organization: { label: "Альтернатива Плюс" },
|
||||||
|
counterparty: { label: "Группа СВК" },
|
||||||
|
proof_bundles: {
|
||||||
|
counterparty_value_flow_bundle: {
|
||||||
|
counterparty: "Группа СВК",
|
||||||
|
incoming_customer_revenue: { total_amount_human_ru: "20 653 490 руб." },
|
||||||
|
outgoing_supplier_payout: { total_amount_human_ru: "2 129 651 руб." },
|
||||||
|
net_amount_human_ru: "18 523 839 руб.",
|
||||||
|
net_direction: "net_incoming"
|
||||||
|
},
|
||||||
|
counterparty_document_bundle: {
|
||||||
|
document_count: 19
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}) as any,
|
||||||
|
persistSession: () => {},
|
||||||
|
cloneConversation: (items) => items,
|
||||||
|
logEvent: () => {},
|
||||||
|
messageIdFactory: () => "msg-comparison-proof",
|
||||||
|
finalizeAddressTurn
|
||||||
|
});
|
||||||
|
|
||||||
|
const reply = String((runtime.response as any).assistant_reply);
|
||||||
|
expect(reply).toContain("по компании ООО Альтернатива Плюс");
|
||||||
|
expect(reply).toContain("получили 20 653 490 руб.");
|
||||||
|
expect(reply).toContain("документы: 19");
|
||||||
|
expect(reply).not.toContain("company-level");
|
||||||
|
expect(reply).not.toContain("reusable bundle");
|
||||||
|
expect((runtime.response as any).debug.comparison_scope_response_augmentation_v1).toEqual(
|
||||||
|
expect.objectContaining({
|
||||||
|
applied: true,
|
||||||
|
organization: "ООО Альтернатива Плюс",
|
||||||
|
counterparty: "Группа СВК",
|
||||||
|
document_count: 19
|
||||||
|
})
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
it("keeps debug bounded to shadow contracts when optional enrichment is absent", () => {
|
it("keeps debug bounded to shadow contracts when optional enrichment is absent", () => {
|
||||||
const runtime = runAssistantAddressLaneResponseRuntime({
|
const runtime = runAssistantAddressLaneResponseRuntime({
|
||||||
sessionId: "asst-2",
|
sessionId: "asst-2",
|
||||||
|
|
|
||||||
|
|
@ -307,6 +307,320 @@ describe("assistant address orchestration runtime adapter", () => {
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("recovers business overview proof bundles from session history before MCP discovery", async () => {
|
||||||
|
const valueFlowBundle = {
|
||||||
|
counterparty: "Group SVK",
|
||||||
|
incoming_total: 20653490,
|
||||||
|
outgoing_total: 2129651,
|
||||||
|
net_amount: 18523839
|
||||||
|
};
|
||||||
|
const documentBundle = {
|
||||||
|
counterparty: "Group SVK",
|
||||||
|
document_count: 19
|
||||||
|
};
|
||||||
|
const runMcpDiscoveryRuntimeEntryPoint = vi.fn(async () => ({
|
||||||
|
schema_version: "assistant_mcp_discovery_runtime_entry_point_v1",
|
||||||
|
policy_owner: "assistantMcpDiscoveryRuntimeEntryPoint",
|
||||||
|
entry_status: "bridge_executed",
|
||||||
|
hot_runtime_wired: false,
|
||||||
|
discovery_attempted: true
|
||||||
|
}));
|
||||||
|
const input = buildInput({
|
||||||
|
userMessage: "Alt Plus",
|
||||||
|
sessionItems: [
|
||||||
|
{
|
||||||
|
role: "assistant",
|
||||||
|
debug: {
|
||||||
|
assistant_mcp_discovery_entry_point_v1: {
|
||||||
|
turn_input: {
|
||||||
|
turn_meaning_ref: {
|
||||||
|
asked_domain_family: "business_overview",
|
||||||
|
asked_action_family: "broad_evaluation",
|
||||||
|
business_overview_separate_entity_candidates: ["Group SVK"],
|
||||||
|
previous_counterparty_value_flow_bundle: valueFlowBundle,
|
||||||
|
previous_counterparty_document_bundle: documentBundle,
|
||||||
|
metadata_scope_hint: "Group SVK"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
runAddressLlmPreDecompose: vi.fn(async () => ({
|
||||||
|
attempted: true,
|
||||||
|
applied: false,
|
||||||
|
effectiveMessage: "Alt Plus",
|
||||||
|
reason: "raw_kept",
|
||||||
|
predecomposeContract: {
|
||||||
|
mode: "unsupported",
|
||||||
|
intent: "unknown",
|
||||||
|
entities: { organization: "Alt Plus" },
|
||||||
|
semantics: { anchor_kind: "organization", anchor_value: "Alt Plus" }
|
||||||
|
}
|
||||||
|
})),
|
||||||
|
resolveAddressFollowupCarryoverContext: vi.fn(() => ({
|
||||||
|
followupContext: {
|
||||||
|
previous_intent: "business_overview",
|
||||||
|
target_intent: "business_overview",
|
||||||
|
previous_discovery_pilot_scope: "business_overview_route_template_v1",
|
||||||
|
previous_discovery_loop_status: "awaiting_clarification",
|
||||||
|
previous_discovery_loop_selected_chain_id: "business_overview",
|
||||||
|
previous_discovery_loop_metadata_scope_hint: "Group SVK",
|
||||||
|
previous_anchor_type: "counterparty",
|
||||||
|
previous_anchor_value: "Group SVK",
|
||||||
|
previous_filters: {}
|
||||||
|
}
|
||||||
|
})),
|
||||||
|
resolveAssistantOrchestrationDecision: vi.fn(() => ({
|
||||||
|
runAddressLane: true,
|
||||||
|
livingMode: "address_data",
|
||||||
|
livingReason: "address_lane_triggered",
|
||||||
|
toolGateDecision: "run_address_lane",
|
||||||
|
toolGateReason: "followup_context_detected",
|
||||||
|
orchestrationContract: {
|
||||||
|
schema_version: "assistant_orchestration_contract_v1",
|
||||||
|
assistant_turn_meaning: {
|
||||||
|
schema_version: "assistant_turn_meaning_v1",
|
||||||
|
asked_domain_family: "business_overview",
|
||||||
|
asked_action_family: "broad_evaluation",
|
||||||
|
unsupported_but_understood_family: "broad_business_evaluation"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})),
|
||||||
|
runMcpDiscoveryRuntimeEntryPoint
|
||||||
|
});
|
||||||
|
|
||||||
|
await buildAssistantAddressOrchestrationRuntime(input);
|
||||||
|
|
||||||
|
expect(runMcpDiscoveryRuntimeEntryPoint).toHaveBeenCalledWith(
|
||||||
|
expect.objectContaining({
|
||||||
|
followupContext: expect.objectContaining({
|
||||||
|
previous_discovery_bidirectional_value_flow: valueFlowBundle,
|
||||||
|
previous_discovery_document_summary: documentBundle
|
||||||
|
})
|
||||||
|
})
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("recovers business overview proof bundles from previous assistant summary text", async () => {
|
||||||
|
const runMcpDiscoveryRuntimeEntryPoint = vi.fn(async () => ({
|
||||||
|
schema_version: "assistant_mcp_discovery_runtime_entry_point_v1",
|
||||||
|
policy_owner: "assistantMcpDiscoveryRuntimeEntryPoint",
|
||||||
|
entry_status: "bridge_executed",
|
||||||
|
hot_runtime_wired: false,
|
||||||
|
discovery_attempted: true
|
||||||
|
}));
|
||||||
|
const input = buildInput({
|
||||||
|
userMessage: "Alt Plus",
|
||||||
|
sessionItems: [
|
||||||
|
{
|
||||||
|
role: "assistant",
|
||||||
|
text:
|
||||||
|
"\u041e\u0442\u0434\u0435\u043b\u044c\u043d\u043e \u043f\u043e \u043a\u043e\u043d\u0442\u0440\u0430\u0433\u0435\u043d\u0442\u0443 Group SVK: " +
|
||||||
|
"\u043f\u043e\u0434\u0442\u0432\u0435\u0440\u0436\u0434\u0435\u043d\u043e \u043f\u043e\u043b\u0443\u0447\u0438\u043b\u0438 20 653 490 \u0440\u0443\u0431., " +
|
||||||
|
"\u0437\u0430\u043f\u043b\u0430\u0442\u0438\u043b\u0438 2 129 651 \u0440\u0443\u0431., " +
|
||||||
|
"\u0440\u0430\u0441\u0447\u0435\u0442\u043d\u043e\u0435 \u043d\u0435\u0442\u0442\u043e \u0432 \u043d\u0430\u0448\u0443 \u0441\u0442\u043e\u0440\u043e\u043d\u0443 18 523 839 \u0440\u0443\u0431. " +
|
||||||
|
"\u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442\u044b \u043f\u043e \u0446\u0435\u043f\u043e\u0447\u043a\u0435: \u043d\u0430\u0439\u0434\u0435\u043d\u043e 19."
|
||||||
|
}
|
||||||
|
],
|
||||||
|
runAddressLlmPreDecompose: vi.fn(async () => ({
|
||||||
|
attempted: true,
|
||||||
|
applied: false,
|
||||||
|
effectiveMessage: "Alt Plus",
|
||||||
|
reason: "raw_kept",
|
||||||
|
predecomposeContract: {
|
||||||
|
mode: "unsupported",
|
||||||
|
intent: "unknown",
|
||||||
|
entities: { organization: "Alt Plus" }
|
||||||
|
}
|
||||||
|
})),
|
||||||
|
resolveAddressFollowupCarryoverContext: vi.fn(() => ({
|
||||||
|
followupContext: {
|
||||||
|
previous_intent: "business_overview",
|
||||||
|
target_intent: "business_overview",
|
||||||
|
previous_discovery_pilot_scope: "business_overview_route_template_v1",
|
||||||
|
previous_discovery_loop_selected_chain_id: "business_overview",
|
||||||
|
previous_discovery_loop_metadata_scope_hint: "Group SVK",
|
||||||
|
previous_anchor_type: "counterparty",
|
||||||
|
previous_anchor_value: "Group SVK",
|
||||||
|
previous_filters: {}
|
||||||
|
}
|
||||||
|
})),
|
||||||
|
resolveAssistantOrchestrationDecision: vi.fn(() => ({
|
||||||
|
runAddressLane: true,
|
||||||
|
livingMode: "address_data",
|
||||||
|
livingReason: "address_lane_triggered",
|
||||||
|
toolGateDecision: "run_address_lane",
|
||||||
|
toolGateReason: "followup_context_detected",
|
||||||
|
orchestrationContract: {
|
||||||
|
schema_version: "assistant_orchestration_contract_v1",
|
||||||
|
assistant_turn_meaning: {
|
||||||
|
schema_version: "assistant_turn_meaning_v1",
|
||||||
|
asked_domain_family: "business_overview",
|
||||||
|
asked_action_family: "broad_evaluation"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})),
|
||||||
|
runMcpDiscoveryRuntimeEntryPoint
|
||||||
|
});
|
||||||
|
|
||||||
|
await buildAssistantAddressOrchestrationRuntime(input);
|
||||||
|
|
||||||
|
expect(runMcpDiscoveryRuntimeEntryPoint).toHaveBeenCalledWith(
|
||||||
|
expect.objectContaining({
|
||||||
|
followupContext: expect.objectContaining({
|
||||||
|
previous_discovery_bidirectional_value_flow: expect.objectContaining({
|
||||||
|
counterparty: "Group SVK",
|
||||||
|
incoming_customer_revenue: { total_amount_human_ru: "20 653 490 \u0440\u0443\u0431." },
|
||||||
|
outgoing_supplier_payout: { total_amount_human_ru: "2 129 651 \u0440\u0443\u0431." },
|
||||||
|
net_amount_human_ru: "18 523 839 \u0440\u0443\u0431."
|
||||||
|
}),
|
||||||
|
previous_discovery_document_summary: expect.objectContaining({
|
||||||
|
counterparty: "Group SVK",
|
||||||
|
document_count: 19
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("recovers business overview proof bundles from navigation comparison state", async () => {
|
||||||
|
const valueFlowBundle = {
|
||||||
|
counterparty: "Group SVK",
|
||||||
|
incoming_customer_revenue: { total_amount_human_ru: "20 653 490 руб." },
|
||||||
|
outgoing_supplier_payout: { total_amount_human_ru: "2 129 651 руб." },
|
||||||
|
net_amount_human_ru: "18 523 839 руб."
|
||||||
|
};
|
||||||
|
const documentBundle = { counterparty: "Group SVK", document_count: 19 };
|
||||||
|
const runMcpDiscoveryRuntimeEntryPoint = vi.fn(async () => ({
|
||||||
|
schema_version: "assistant_mcp_discovery_runtime_entry_point_v1",
|
||||||
|
policy_owner: "assistantMcpDiscoveryRuntimeEntryPoint",
|
||||||
|
entry_status: "bridge_executed",
|
||||||
|
hot_runtime_wired: false,
|
||||||
|
discovery_attempted: true
|
||||||
|
}));
|
||||||
|
const input = buildInput({
|
||||||
|
userMessage: "Alt Plus",
|
||||||
|
sessionAddressNavigationState: {
|
||||||
|
session_context: {
|
||||||
|
comparison_scope: {
|
||||||
|
organization: null,
|
||||||
|
counterparty: { label: "Group SVK" },
|
||||||
|
proof_bundles: {
|
||||||
|
counterparty_value_flow_bundle: valueFlowBundle,
|
||||||
|
counterparty_document_bundle: documentBundle
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
runAddressLlmPreDecompose: vi.fn(async () => ({
|
||||||
|
attempted: true,
|
||||||
|
applied: false,
|
||||||
|
effectiveMessage: "Alt Plus",
|
||||||
|
reason: "raw_kept",
|
||||||
|
predecomposeContract: {
|
||||||
|
mode: "unsupported",
|
||||||
|
intent: "unknown",
|
||||||
|
entities: { organization: "Alt Plus" }
|
||||||
|
}
|
||||||
|
})),
|
||||||
|
resolveAddressFollowupCarryoverContext: vi.fn(() => ({ followupContext: null })),
|
||||||
|
resolveAssistantOrchestrationDecision: vi.fn(() => ({
|
||||||
|
runAddressLane: true,
|
||||||
|
livingMode: "address_data",
|
||||||
|
livingReason: "address_lane_triggered",
|
||||||
|
toolGateDecision: "run_address_lane",
|
||||||
|
toolGateReason: "followup_context_detected",
|
||||||
|
orchestrationContract: {
|
||||||
|
schema_version: "assistant_orchestration_contract_v1",
|
||||||
|
assistant_turn_meaning: {
|
||||||
|
schema_version: "assistant_turn_meaning_v1",
|
||||||
|
asked_domain_family: "business_overview",
|
||||||
|
asked_action_family: "broad_evaluation"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})),
|
||||||
|
runMcpDiscoveryRuntimeEntryPoint
|
||||||
|
});
|
||||||
|
|
||||||
|
await buildAssistantAddressOrchestrationRuntime(input);
|
||||||
|
|
||||||
|
expect(runMcpDiscoveryRuntimeEntryPoint).toHaveBeenCalledWith(
|
||||||
|
expect.objectContaining({
|
||||||
|
followupContext: expect.objectContaining({
|
||||||
|
previous_discovery_loop_selected_chain_id: "business_overview",
|
||||||
|
previous_discovery_loop_pending_axes: ["organization"],
|
||||||
|
previous_discovery_loop_metadata_scope_hint: "Group SVK",
|
||||||
|
previous_discovery_bidirectional_value_flow: valueFlowBundle,
|
||||||
|
previous_discovery_document_summary: documentBundle
|
||||||
|
})
|
||||||
|
})
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("does not infer a counterparty proof bundle from company-only overview totals", async () => {
|
||||||
|
const runMcpDiscoveryRuntimeEntryPoint = vi.fn(async () => ({
|
||||||
|
schema_version: "assistant_mcp_discovery_runtime_entry_point_v1",
|
||||||
|
policy_owner: "assistantMcpDiscoveryRuntimeEntryPoint",
|
||||||
|
entry_status: "bridge_executed",
|
||||||
|
hot_runtime_wired: false,
|
||||||
|
discovery_attempted: true
|
||||||
|
}));
|
||||||
|
const input = buildInput({
|
||||||
|
userMessage: "who brought money",
|
||||||
|
sessionItems: [
|
||||||
|
{
|
||||||
|
role: "assistant",
|
||||||
|
text:
|
||||||
|
"Деньги: входящие 47 628 853,03 руб., исходящие 43 763 351,53 руб., расчетное операционное нетто 3 865 501,50 руб.\n" +
|
||||||
|
"НДС: продажи 2 002 138,93 руб., покупки 1 784 850,48 руб., НДС к уплате 217 288,45 руб."
|
||||||
|
}
|
||||||
|
],
|
||||||
|
runAddressLlmPreDecompose: vi.fn(async () => ({
|
||||||
|
attempted: true,
|
||||||
|
applied: false,
|
||||||
|
effectiveMessage: "who brought money",
|
||||||
|
reason: "raw_kept",
|
||||||
|
predecomposeContract: {
|
||||||
|
mode: "unsupported",
|
||||||
|
intent: "unknown",
|
||||||
|
entities: { organization: "Alt Plus" }
|
||||||
|
}
|
||||||
|
})),
|
||||||
|
resolveAddressFollowupCarryoverContext: vi.fn(() => ({
|
||||||
|
followupContext: {
|
||||||
|
previous_intent: "business_overview",
|
||||||
|
target_intent: "business_overview",
|
||||||
|
previous_discovery_pilot_scope: "business_overview_route_template_v1",
|
||||||
|
previous_discovery_loop_selected_chain_id: "business_overview",
|
||||||
|
previous_filters: { organization: "Alt Plus" }
|
||||||
|
}
|
||||||
|
})),
|
||||||
|
resolveAssistantOrchestrationDecision: vi.fn(() => ({
|
||||||
|
runAddressLane: true,
|
||||||
|
livingMode: "address_data",
|
||||||
|
livingReason: "address_lane_triggered",
|
||||||
|
toolGateDecision: "run_address_lane",
|
||||||
|
toolGateReason: "followup_context_detected",
|
||||||
|
orchestrationContract: {
|
||||||
|
schema_version: "assistant_orchestration_contract_v1",
|
||||||
|
assistant_turn_meaning: {
|
||||||
|
schema_version: "assistant_turn_meaning_v1",
|
||||||
|
asked_domain_family: "business_overview",
|
||||||
|
asked_action_family: "broad_evaluation"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})),
|
||||||
|
runMcpDiscoveryRuntimeEntryPoint
|
||||||
|
});
|
||||||
|
|
||||||
|
await buildAssistantAddressOrchestrationRuntime(input);
|
||||||
|
|
||||||
|
const call = runMcpDiscoveryRuntimeEntryPoint.mock.calls[0]?.[0] as any;
|
||||||
|
expect(call.followupContext.previous_discovery_bidirectional_value_flow).toBeUndefined();
|
||||||
|
expect(call.followupContext.previous_discovery_document_summary).toBeUndefined();
|
||||||
|
});
|
||||||
|
|
||||||
it("passes grounded discovery follow-up carryover into MCP discovery entry point for a short year switch", async () => {
|
it("passes grounded discovery follow-up carryover into MCP discovery entry point for a short year switch", async () => {
|
||||||
const runMcpDiscoveryRuntimeEntryPoint = vi.fn(async () => ({
|
const runMcpDiscoveryRuntimeEntryPoint = vi.fn(async () => ({
|
||||||
schema_version: "assistant_mcp_discovery_runtime_entry_point_v1",
|
schema_version: "assistant_mcp_discovery_runtime_entry_point_v1",
|
||||||
|
|
|
||||||
|
|
@ -176,6 +176,31 @@ describe("assistant living chat runtime adapter", () => {
|
||||||
expect(executeLlmChat).toHaveBeenCalledTimes(1);
|
expect(executeLlmChat).toHaveBeenCalledTimes(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("handles first-turn smalltalk deterministically without relying on LLM", async () => {
|
||||||
|
const resolveDataScopeProbe = vi.fn(async () => ({
|
||||||
|
status: "resolved",
|
||||||
|
channel: "default",
|
||||||
|
organizations: [],
|
||||||
|
error: null
|
||||||
|
}));
|
||||||
|
const input = buildRuntimeInput({
|
||||||
|
userMessage: "приветик - че как там дела",
|
||||||
|
resolveDataScopeProbe,
|
||||||
|
buildAssistantProactiveOrganizationOfferReply: () => ""
|
||||||
|
});
|
||||||
|
|
||||||
|
const output = await runAssistantLivingChatRuntime(input);
|
||||||
|
|
||||||
|
expect(output.handled).toBe(true);
|
||||||
|
expect(output.chatText).toContain("Привет! Всё нормально.");
|
||||||
|
expect(output.chatText).not.toContain("llm-text");
|
||||||
|
expect(output.debug?.living_chat_response_source).toBe("deterministic_smalltalk");
|
||||||
|
expect(output.debug?.living_chat_proactive_scope_offer_applied).toBe(false);
|
||||||
|
expect(output.debug?.living_chat_data_scope_probe_org_count).toBe(0);
|
||||||
|
expect(input.__spies.executeLlmChat).not.toHaveBeenCalled();
|
||||||
|
expect(resolveDataScopeProbe).toHaveBeenCalledTimes(1);
|
||||||
|
});
|
||||||
|
|
||||||
it("builds deterministic broad business evaluation summary from grounded continuity instead of replaying lifecycle noise", async () => {
|
it("builds deterministic broad business evaluation summary from grounded continuity instead of replaying lifecycle noise", async () => {
|
||||||
const executeLlmChat = vi.fn(async () => "raw-llm");
|
const executeLlmChat = vi.fn(async () => "raw-llm");
|
||||||
const input = buildRuntimeInput({
|
const input = buildRuntimeInput({
|
||||||
|
|
@ -438,6 +463,7 @@ describe("assistant living chat runtime adapter", () => {
|
||||||
"ООО Лайсвуд",
|
"ООО Лайсвуд",
|
||||||
"РАЙМ"
|
"РАЙМ"
|
||||||
]);
|
]);
|
||||||
|
expect(input.__spies.executeLlmChat).not.toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("does not add proactive organization offer after the session already has assistant context", async () => {
|
it("does not add proactive organization offer after the session already has assistant context", async () => {
|
||||||
|
|
|
||||||
|
|
@ -475,6 +475,7 @@ describe("assistant MCP discovery response candidate", () => {
|
||||||
effective_message:
|
effective_message:
|
||||||
"Дать краткий обзор ООО Альтернатива Плюс за 2020: входящие, исходящие, нетто и банковскую границу.",
|
"Дать краткий обзор ООО Альтернатива Плюс за 2020: входящие, исходящие, нетто и банковскую границу.",
|
||||||
explicit_organization_scope: "ООО Альтернатива Плюс"
|
explicit_organization_scope: "ООО Альтернатива Плюс"
|
||||||
|
,asked_action_family: "broad_evaluation"
|
||||||
},
|
},
|
||||||
data_need_graph: {
|
data_need_graph: {
|
||||||
business_fact_family: "business_overview",
|
business_fact_family: "business_overview",
|
||||||
|
|
@ -778,6 +779,199 @@ describe("assistant MCP discovery response candidate", () => {
|
||||||
expect(candidate.reply_text).not.toContain("wide overview");
|
expect(candidate.reply_text).not.toContain("wide overview");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("keeps broad overview layers when direct-money reason is present", () => {
|
||||||
|
const candidate = buildAssistantMcpDiscoveryResponseCandidate(
|
||||||
|
entryPoint({
|
||||||
|
turn_input: {
|
||||||
|
adapter_status: "ready",
|
||||||
|
turn_meaning_ref: {
|
||||||
|
raw_message:
|
||||||
|
"Дай взрослый бизнес-обзор ООО Альтернатива Плюс за 2020 год: деньги, нетто, НДС, долги, склад, клиенты, поставщики и что нельзя утверждать.",
|
||||||
|
asked_action_family: "broad_evaluation",
|
||||||
|
unsupported_but_understood_family: "broad_business_evaluation",
|
||||||
|
explicit_organization_scope: "ООО Альтернатива Плюс",
|
||||||
|
explicit_date_scope: "2020"
|
||||||
|
},
|
||||||
|
data_need_graph: {
|
||||||
|
business_fact_family: "business_overview",
|
||||||
|
ranking_need: null,
|
||||||
|
reason_codes: [
|
||||||
|
"data_need_graph_family_business_overview",
|
||||||
|
"data_need_graph_business_overview_direct_money_answer"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
bridge: {
|
||||||
|
bridge_status: "answer_draft_ready",
|
||||||
|
user_facing_response_allowed: true,
|
||||||
|
business_fact_answer_allowed: true,
|
||||||
|
requires_user_clarification: false,
|
||||||
|
pilot: {
|
||||||
|
pilot_scope: "business_overview_route_template_v1",
|
||||||
|
derived_business_overview: {
|
||||||
|
period_scope: "2020",
|
||||||
|
incoming_customer_revenue: {
|
||||||
|
total_amount_human_ru: "47 628 853,03 руб."
|
||||||
|
},
|
||||||
|
outgoing_supplier_payout: {
|
||||||
|
total_amount_human_ru: "43 763 351,53 руб."
|
||||||
|
},
|
||||||
|
net_amount_human_ru: "3 865 501,50 руб.",
|
||||||
|
net_direction: "net_incoming",
|
||||||
|
top_customers: [
|
||||||
|
{
|
||||||
|
axis_value: "СБЕРБАНК, ПАО",
|
||||||
|
total_amount_human_ru: "12 792 194,31 руб.",
|
||||||
|
is_likely_financial_institution: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
axis_value: "Группа СВК",
|
||||||
|
total_amount_human_ru: "12 093 465 руб."
|
||||||
|
}
|
||||||
|
],
|
||||||
|
top_suppliers: [
|
||||||
|
{
|
||||||
|
axis_value: "Департамент капитального ремонта города Москвы",
|
||||||
|
total_amount_human_ru: "9 612 904,90 руб."
|
||||||
|
}
|
||||||
|
],
|
||||||
|
tax_position: {
|
||||||
|
sales_vat_amount_human_ru: "2 717 288,45 руб.",
|
||||||
|
purchase_vat_amount_human_ru: "2 500 000 руб.",
|
||||||
|
net_vat_amount_human_ru: "217 288,45 руб.",
|
||||||
|
net_vat_direction: "vat_to_pay"
|
||||||
|
},
|
||||||
|
debt_position: {
|
||||||
|
receivables: { total_amount_human_ru: "25 000 000 руб." },
|
||||||
|
payables: { total_amount_human_ru: "3 318 824,40 руб." },
|
||||||
|
net_debt_position_amount_human_ru: "21 681 175,60 руб.",
|
||||||
|
net_debt_position_direction: "net_receivable"
|
||||||
|
},
|
||||||
|
inventory_position: {
|
||||||
|
total_amount_human_ru: "716 418,33 руб.",
|
||||||
|
rows_matched: 34
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
answer_draft: {
|
||||||
|
answer_mode: "confirmed_with_bounded_inference",
|
||||||
|
headline: "wide overview should not leak",
|
||||||
|
confirmed_lines: [],
|
||||||
|
inference_lines: [],
|
||||||
|
unknown_lines: [],
|
||||||
|
limitation_lines: [],
|
||||||
|
next_step_line: null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(candidate.reply_text).toContain("Что видно:");
|
||||||
|
expect(candidate.reply_text).toContain("НДС:");
|
||||||
|
expect(candidate.reply_text).toContain("Долги:");
|
||||||
|
expect(candidate.reply_text).toContain("Склад:");
|
||||||
|
expect(candidate.reply_text).toContain("Группа СВК");
|
||||||
|
expect(candidate.reply_text).not.toContain("по деньгам плюс");
|
||||||
|
expect(candidate.reply_text).not.toContain("wide overview");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("keeps counterparty leaders for money followups instead of compact cashflow", () => {
|
||||||
|
const candidate = buildAssistantMcpDiscoveryResponseCandidate(
|
||||||
|
entryPoint({
|
||||||
|
turn_input: {
|
||||||
|
adapter_status: "ready",
|
||||||
|
turn_meaning_ref: {
|
||||||
|
raw_message:
|
||||||
|
"Раскрой деньги подробнее: сколько получили, сколько заплатили, какой чистый денежный поток, кто главный клиент и главный поставщик в 2020.",
|
||||||
|
asked_action_family: "broad_evaluation",
|
||||||
|
unsupported_but_understood_family: "broad_business_evaluation",
|
||||||
|
explicit_organization_scope: "ООО Альтернатива Плюс",
|
||||||
|
explicit_date_scope: "2020"
|
||||||
|
},
|
||||||
|
data_need_graph: {
|
||||||
|
business_fact_family: "business_overview",
|
||||||
|
ranking_need: null,
|
||||||
|
reason_codes: [
|
||||||
|
"data_need_graph_family_business_overview",
|
||||||
|
"data_need_graph_business_overview_direct_money_answer"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
bridge: {
|
||||||
|
bridge_status: "answer_draft_ready",
|
||||||
|
user_facing_response_allowed: true,
|
||||||
|
business_fact_answer_allowed: true,
|
||||||
|
requires_user_clarification: false,
|
||||||
|
pilot: {
|
||||||
|
pilot_scope: "business_overview_route_template_v1",
|
||||||
|
derived_business_overview: {
|
||||||
|
period_scope: "2020",
|
||||||
|
incoming_customer_revenue: {
|
||||||
|
total_amount_human_ru: "47 628 853,03 руб."
|
||||||
|
},
|
||||||
|
outgoing_supplier_payout: {
|
||||||
|
total_amount_human_ru: "43 763 351,53 руб."
|
||||||
|
},
|
||||||
|
net_amount_human_ru: "3 865 501,50 руб.",
|
||||||
|
net_direction: "net_incoming",
|
||||||
|
top_customers: [
|
||||||
|
{
|
||||||
|
axis_value: "СБЕРБАНК, ПАО",
|
||||||
|
total_amount_human_ru: "12 792 194,31 руб.",
|
||||||
|
is_likely_financial_institution: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
axis_value: "Группа СВК",
|
||||||
|
total_amount_human_ru: "12 093 465 руб."
|
||||||
|
}
|
||||||
|
],
|
||||||
|
top_suppliers: [
|
||||||
|
{
|
||||||
|
axis_value: "Департамент капитального ремонта города Москвы",
|
||||||
|
total_amount_human_ru: "9 612 904,90 руб."
|
||||||
|
}
|
||||||
|
],
|
||||||
|
tax_position: {
|
||||||
|
net_vat_amount_human_ru: "217 288,45 руб.",
|
||||||
|
net_vat_direction: "vat_to_pay"
|
||||||
|
},
|
||||||
|
debt_position: {
|
||||||
|
receivables: { total_amount_human_ru: "25 000 000 руб." },
|
||||||
|
payables: { total_amount_human_ru: "3 318 824,40 руб." },
|
||||||
|
net_debt_position_amount_human_ru: "21 681 175,60 руб.",
|
||||||
|
net_debt_position_direction: "net_receivable"
|
||||||
|
},
|
||||||
|
inventory_position: {
|
||||||
|
total_amount_human_ru: "716 418,33 руб.",
|
||||||
|
rows_matched: 34
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
answer_draft: {
|
||||||
|
answer_mode: "confirmed_with_bounded_inference",
|
||||||
|
headline: "wide overview should not leak",
|
||||||
|
confirmed_lines: [],
|
||||||
|
inference_lines: [],
|
||||||
|
unknown_lines: [],
|
||||||
|
limitation_lines: [],
|
||||||
|
next_step_line: null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(candidate.reply_text).toContain("47 628 853,03");
|
||||||
|
expect(candidate.reply_text).toContain("43 763 351,53");
|
||||||
|
expect(candidate.reply_text).toContain("3 865 501,50");
|
||||||
|
expect(candidate.reply_text).toContain("СБЕРБАНК, ПАО");
|
||||||
|
expect(candidate.reply_text).toContain("Группа СВК");
|
||||||
|
expect(candidate.reply_text).toContain("Департамент капитального ремонта города Москвы");
|
||||||
|
expect(candidate.reply_text).not.toContain("по деньгам плюс");
|
||||||
|
expect(candidate.reply_text).not.toContain("НДС:");
|
||||||
|
expect(candidate.reply_text).not.toContain("Склад:");
|
||||||
|
expect(candidate.reply_text).not.toContain("wide overview");
|
||||||
|
});
|
||||||
|
|
||||||
it("labels organization-scoped bidirectional value-flow continuations as company scope", () => {
|
it("labels organization-scoped bidirectional value-flow continuations as company scope", () => {
|
||||||
const candidate = buildAssistantMcpDiscoveryResponseCandidate(
|
const candidate = buildAssistantMcpDiscoveryResponseCandidate(
|
||||||
entryPoint({
|
entryPoint({
|
||||||
|
|
@ -902,6 +1096,83 @@ describe("assistant MCP discovery response candidate", () => {
|
||||||
expect(candidate.reply_text).not.toContain("Самый крупный подтвержденный клиент");
|
expect(candidate.reply_text).not.toContain("Самый крупный подтвержденный клиент");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("answers colloquial money leader follow-ups with leaders first", () => {
|
||||||
|
const candidate = buildAssistantMcpDiscoveryResponseCandidate(
|
||||||
|
entryPoint({
|
||||||
|
turn_input: {
|
||||||
|
adapter_status: "ready",
|
||||||
|
turn_meaning_ref: {
|
||||||
|
raw_message:
|
||||||
|
"А по деньгам в этом же году кто больше всего занес и кому больше всего ушло? Банк не называй обычным клиентом без оговорки.",
|
||||||
|
asked_domain_family: "business_overview",
|
||||||
|
asked_action_family: "broad_evaluation",
|
||||||
|
explicit_organization_scope: "ООО Альтернатива Плюс",
|
||||||
|
explicit_date_scope: "2020"
|
||||||
|
},
|
||||||
|
data_need_graph: {
|
||||||
|
business_fact_family: "business_overview",
|
||||||
|
ranking_need: "top_desc",
|
||||||
|
reason_codes: [
|
||||||
|
"data_need_graph_family_business_overview",
|
||||||
|
"data_need_graph_business_overview_direct_money_answer"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
bridge: {
|
||||||
|
bridge_status: "answer_draft_ready",
|
||||||
|
user_facing_response_allowed: true,
|
||||||
|
business_fact_answer_allowed: true,
|
||||||
|
requires_user_clarification: false,
|
||||||
|
pilot: {
|
||||||
|
pilot_scope: "business_overview_route_template_v1",
|
||||||
|
derived_business_overview: {
|
||||||
|
period_scope: "2020",
|
||||||
|
incoming_customer_revenue: { total_amount_human_ru: "47 628 853,03 руб." },
|
||||||
|
outgoing_supplier_payout: { total_amount_human_ru: "43 763 351,53 руб." },
|
||||||
|
net_amount_human_ru: "3 865 501,50 руб.",
|
||||||
|
net_direction: "net_incoming",
|
||||||
|
top_customers: [
|
||||||
|
{
|
||||||
|
axis_value: "СБЕРБАНК, ПАО",
|
||||||
|
total_amount_human_ru: "12 792 194,31 руб.",
|
||||||
|
counterparty_role_hint: "financial_institution"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
axis_value: "Группа СВК",
|
||||||
|
total_amount_human_ru: "12 093 465 руб.",
|
||||||
|
counterparty_role_hint: "ordinary_counterparty"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
top_suppliers: [
|
||||||
|
{
|
||||||
|
axis_value: "Департамент капитального ремонта города Москвы.",
|
||||||
|
total_amount_human_ru: "9 612 904,90 руб."
|
||||||
|
}
|
||||||
|
],
|
||||||
|
yearly_breakdown: []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
answer_draft: {
|
||||||
|
answer_mode: "confirmed_with_bounded_inference",
|
||||||
|
headline: "Company summary.",
|
||||||
|
confirmed_lines: [],
|
||||||
|
inference_lines: [],
|
||||||
|
unknown_lines: [],
|
||||||
|
limitation_lines: [],
|
||||||
|
next_step_line: null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
const firstLine = candidate.reply_text?.split("\n")[0] ?? "";
|
||||||
|
expect(firstLine).toContain("больше всего занес СБЕРБАНК, ПАО");
|
||||||
|
expect(firstLine).toContain("не называю его обычной клиентской выручкой");
|
||||||
|
expect(firstLine).toContain("крупнейший небанковский входящий контрагент: Группа СВК");
|
||||||
|
expect(firstLine).toContain("больше всего ушло Департамент капитального ремонта города Москвы.");
|
||||||
|
expect(firstLine).not.toContain("выглядит как бизнес");
|
||||||
|
});
|
||||||
|
|
||||||
it("mentions separate counterparty scope in company plus counterparty business summaries", () => {
|
it("mentions separate counterparty scope in company plus counterparty business summaries", () => {
|
||||||
const candidate = buildAssistantMcpDiscoveryResponseCandidate(
|
const candidate = buildAssistantMcpDiscoveryResponseCandidate(
|
||||||
entryPoint({
|
entryPoint({
|
||||||
|
|
@ -1107,6 +1378,128 @@ describe("assistant MCP discovery response candidate", () => {
|
||||||
expect(candidate.reply_text).toContain("ранее подтвержденный контрагентский срез");
|
expect(candidate.reply_text).toContain("ранее подтвержденный контрагентский срез");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("answers selected counterparty boundary summaries from previous bundles without asking for organization", () => {
|
||||||
|
const candidate = buildAssistantMcpDiscoveryResponseCandidate(
|
||||||
|
entryPoint({
|
||||||
|
turn_input: {
|
||||||
|
adapter_status: "ready",
|
||||||
|
turn_meaning_ref: {
|
||||||
|
asked_domain_family: "business_overview",
|
||||||
|
asked_action_family: "broad_evaluation",
|
||||||
|
business_overview_separate_entity_candidates: ["Группа СВК"],
|
||||||
|
previous_counterparty_value_flow_bundle: {
|
||||||
|
counterparty: "Группа СВК",
|
||||||
|
incoming_customer_revenue: {
|
||||||
|
total_amount_human_ru: "20 653 490 руб.",
|
||||||
|
rows_with_amount: 26,
|
||||||
|
rows_matched: 26
|
||||||
|
},
|
||||||
|
outgoing_supplier_payout: {
|
||||||
|
total_amount_human_ru: "2 129 651 руб.",
|
||||||
|
rows_with_amount: 1,
|
||||||
|
rows_matched: 1
|
||||||
|
},
|
||||||
|
net_amount_human_ru: "18 523 839 руб.",
|
||||||
|
net_direction: "net_incoming"
|
||||||
|
},
|
||||||
|
previous_counterparty_document_bundle: {
|
||||||
|
counterparty: "Группа СВК",
|
||||||
|
document_count: 19
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data_need_graph: {
|
||||||
|
business_fact_family: "business_overview",
|
||||||
|
subject_candidates: [],
|
||||||
|
clarification_gaps: ["organization"],
|
||||||
|
reason_codes: ["data_need_graph_family_business_overview", "data_need_graph_has_clarification_gaps"]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
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: "Нужно уточнить контекст перед поиском в 1С.",
|
||||||
|
confirmed_lines: [],
|
||||||
|
inference_lines: [],
|
||||||
|
unknown_lines: [],
|
||||||
|
limitation_lines: [],
|
||||||
|
next_step_line: "Уточните организацию."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(candidate.candidate_status).toBe("clarification_candidate");
|
||||||
|
expect(candidate.reply_text).toContain("уточните, по какой компании");
|
||||||
|
expect(candidate.reply_text).toContain("Группа СВК");
|
||||||
|
expect(candidate.reply_text).toContain("20 653 490 руб.");
|
||||||
|
expect(candidate.reply_text).toContain("документы по цепочке: найдено 19");
|
||||||
|
expect(candidate.reply_text).toContain("Нельзя утверждать");
|
||||||
|
expect(candidate.reply_text).not.toContain("перед поиском");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("keeps organization clarification boundary summaries compact", () => {
|
||||||
|
const candidate = buildAssistantMcpDiscoveryResponseCandidate(
|
||||||
|
entryPoint({
|
||||||
|
turn_input: {
|
||||||
|
adapter_status: "ready",
|
||||||
|
turn_meaning_ref: {
|
||||||
|
raw_message: "ООО Альтернатива Плюс",
|
||||||
|
asked_domain_family: "business_overview",
|
||||||
|
asked_action_family: "broad_evaluation",
|
||||||
|
business_overview_separate_entity_candidates: ["Группа СВК"],
|
||||||
|
explicit_organization_scope: "Альтернатива Плюс"
|
||||||
|
},
|
||||||
|
data_need_graph: {
|
||||||
|
business_fact_family: "business_overview",
|
||||||
|
subject_candidates: [],
|
||||||
|
metadata_scope_hint: "Группа СВК",
|
||||||
|
reason_codes: ["data_need_graph_family_business_overview"]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
bridge: {
|
||||||
|
bridge_status: "answer_draft_ready",
|
||||||
|
user_facing_response_allowed: true,
|
||||||
|
business_fact_answer_allowed: true,
|
||||||
|
requires_user_clarification: false,
|
||||||
|
pilot: {
|
||||||
|
pilot_scope: "business_overview_route_template_v1",
|
||||||
|
derived_business_overview: {
|
||||||
|
period_scope: "all_time",
|
||||||
|
incoming_customer_revenue: { total_amount_human_ru: "285 819 547,57 руб." },
|
||||||
|
outgoing_supplier_payout: { total_amount_human_ru: "137 963 720,37 руб." },
|
||||||
|
net_amount_human_ru: "147 855 827,20 руб.",
|
||||||
|
net_direction: "net_incoming",
|
||||||
|
top_customers: [{ axis_value: "Комитет государственных услуг г. Москвы", total_amount_human_ru: "133 839 880,82 руб." }],
|
||||||
|
top_suppliers: [{ axis_value: "ГТК-Интер, ООО", total_amount_human_ru: "44 943 460 руб." }]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
answer_draft: {
|
||||||
|
answer_mode: "confirmed_with_bounded_inference",
|
||||||
|
headline: "Company summary.",
|
||||||
|
confirmed_lines: [],
|
||||||
|
inference_lines: [],
|
||||||
|
unknown_lines: [],
|
||||||
|
limitation_lines: [],
|
||||||
|
next_step_line: null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
const lines = candidate.reply_text?.split("\n").filter(Boolean) ?? [];
|
||||||
|
expect(lines.length).toBeLessThanOrEqual(3);
|
||||||
|
expect(lines[0]).toContain("по компании Альтернатива Плюс");
|
||||||
|
expect(lines[0]).toContain("285 819 547,57");
|
||||||
|
expect(candidate.reply_text).toContain("Отдельно по выбранному контрагенту Группа СВК");
|
||||||
|
expect(candidate.reply_text).toContain("Нельзя утверждать");
|
||||||
|
expect(candidate.reply_text).not.toContain("Метод:");
|
||||||
|
expect(candidate.reply_text).not.toContain("Для ответа именно про чистую прибыль");
|
||||||
|
});
|
||||||
|
|
||||||
it("localizes value-flow evidence without leaking pilot mechanics", () => {
|
it("localizes value-flow evidence without leaking pilot mechanics", () => {
|
||||||
const candidate = buildAssistantMcpDiscoveryResponseCandidate(
|
const candidate = buildAssistantMcpDiscoveryResponseCandidate(
|
||||||
entryPoint({
|
entryPoint({
|
||||||
|
|
|
||||||
|
|
@ -310,6 +310,67 @@ describe("assistant MCP discovery response policy", () => {
|
||||||
expect(result.reason_codes).not.toContain("mcp_discovery_response_policy_keep_exact_matched_factual_address_reply");
|
expect(result.reason_codes).not.toContain("mcp_discovery_response_policy_keep_exact_matched_factual_address_reply");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("overrides exact document-list replies for explicit counterparty received-paid-net questions", () => {
|
||||||
|
const result = applyAssistantMcpDiscoveryResponsePolicy({
|
||||||
|
currentReply: "Контрагент: Группа СВК. Найдено документов: 19.",
|
||||||
|
currentReplySource: "address_query_runtime_v1",
|
||||||
|
currentReplyType: "factual",
|
||||||
|
addressRuntimeMeta: {
|
||||||
|
detected_intent: "list_documents_by_counterparty",
|
||||||
|
selected_recipe: "address_documents_by_counterparty_v1",
|
||||||
|
mcp_call_status: "matched_non_empty",
|
||||||
|
truth_mode: "confirmed",
|
||||||
|
capability_binding_status: "bound",
|
||||||
|
capability_binding_violations: [],
|
||||||
|
answer_shape_contract: {
|
||||||
|
reply_type: "factual",
|
||||||
|
capability_contract_id: "documents_drilldown"
|
||||||
|
},
|
||||||
|
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: ["Группа СВК"],
|
||||||
|
reason_codes: ["data_need_graph_built", "data_need_graph_bidirectional_value_flow"]
|
||||||
|
},
|
||||||
|
turn_meaning_ref: {
|
||||||
|
asked_domain_family: "counterparty_value",
|
||||||
|
asked_action_family: "net_value_flow",
|
||||||
|
explicit_entity_candidates: ["Группа СВК"],
|
||||||
|
unsupported_but_understood_family: "counterparty_bidirectional_value_flow_or_netting"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
bridge: {
|
||||||
|
bridge_status: "answer_draft_ready",
|
||||||
|
user_facing_response_allowed: true,
|
||||||
|
business_fact_answer_allowed: true,
|
||||||
|
requires_user_clarification: false,
|
||||||
|
answer_draft: {
|
||||||
|
answer_mode: "confirmed_with_bounded_inference",
|
||||||
|
headline: "Группа СВК: получили 20 653 490 руб., заплатили 2 129 651 руб.; нетто 18 523 839 руб. в нашу сторону.",
|
||||||
|
confirmed_lines: ["получили 20 653 490 руб.; заплатили 2 129 651 руб.; нетто 18 523 839 руб."],
|
||||||
|
inference_lines: [],
|
||||||
|
unknown_lines: [],
|
||||||
|
limitation_lines: ["Это денежный срез, не бухгалтерская прибыль."],
|
||||||
|
next_step_line: null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(result.applied).toBe(true);
|
||||||
|
expect(result.decision).toBe("apply_candidate");
|
||||||
|
expect(result.reply_source).toBe("mcp_discovery_response_candidate_guarded");
|
||||||
|
expect(result.reply_text).toContain("20 653 490");
|
||||||
|
expect(result.reply_text).toContain("2 129 651");
|
||||||
|
expect(result.reason_codes).toContain("mcp_discovery_response_policy_value_flow_action_conflict_allows_candidate_override");
|
||||||
|
expect(result.reason_codes).toContain("mcp_discovery_response_policy_semantic_conflict_allows_candidate_override");
|
||||||
|
expect(result.reason_codes).not.toContain("mcp_discovery_response_policy_keep_exact_document_list_address_reply");
|
||||||
|
});
|
||||||
|
|
||||||
it("keeps exact matched inventory address replies over stale metadata discovery candidates", () => {
|
it("keeps exact matched inventory address replies over stale metadata discovery candidates", () => {
|
||||||
const result = applyAssistantMcpDiscoveryResponsePolicy({
|
const result = applyAssistantMcpDiscoveryResponsePolicy({
|
||||||
currentReply: "По товару Шкаф картотечный 1000*400*2100 цепочка поставки и продажи подтверждена.",
|
currentReply: "По товару Шкаф картотечный 1000*400*2100 цепочка поставки и продажи подтверждена.",
|
||||||
|
|
|
||||||
|
|
@ -328,8 +328,17 @@ describe("assistant MCP discovery runtime bridge", () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(result.bridge_status).toBe("answer_draft_ready");
|
expect(result.bridge_status).toBe("answer_draft_ready");
|
||||||
|
expect(result.hot_runtime_wired).toBe(true);
|
||||||
expect(result.business_fact_answer_allowed).toBe(true);
|
expect(result.business_fact_answer_allowed).toBe(true);
|
||||||
expect(result.planner.selected_chain_id).toBe("value_flow_comparison");
|
expect(result.planner.selected_chain_id).toBe("value_flow_comparison");
|
||||||
|
expect(result.hot_runtime_wired).toBe(true);
|
||||||
|
expect(result.execution_handoff).toMatchObject({
|
||||||
|
handoff_status: "ready_for_guarded_response",
|
||||||
|
selected_chain_id: "value_flow_comparison",
|
||||||
|
allowed_hot_chain: true,
|
||||||
|
can_use_guarded_response: true
|
||||||
|
});
|
||||||
|
expect(result.reason_codes).toContain("runtime_bridge_wired_to_guarded_hot_assistant_answer");
|
||||||
expect(result.pilot.derived_bidirectional_value_flow).toMatchObject({
|
expect(result.pilot.derived_bidirectional_value_flow).toMatchObject({
|
||||||
period_scope: "2020",
|
period_scope: "2020",
|
||||||
incoming_customer_revenue: {
|
incoming_customer_revenue: {
|
||||||
|
|
@ -389,6 +398,14 @@ describe("assistant MCP discovery runtime bridge", () => {
|
||||||
expect(result.requires_user_clarification).toBe(false);
|
expect(result.requires_user_clarification).toBe(false);
|
||||||
expect(result.business_fact_answer_allowed).toBe(true);
|
expect(result.business_fact_answer_allowed).toBe(true);
|
||||||
expect(result.planner.selected_chain_id).toBe("value_flow_comparison");
|
expect(result.planner.selected_chain_id).toBe("value_flow_comparison");
|
||||||
|
expect(result.hot_runtime_wired).toBe(true);
|
||||||
|
expect(result.execution_handoff).toMatchObject({
|
||||||
|
handoff_status: "ready_for_guarded_response",
|
||||||
|
selected_chain_id: "value_flow_comparison",
|
||||||
|
allowed_hot_chain: true,
|
||||||
|
can_use_guarded_response: true
|
||||||
|
});
|
||||||
|
expect(result.reason_codes).toContain("runtime_bridge_wired_to_guarded_hot_assistant_answer");
|
||||||
expect(result.planner.required_axes).toContain("all_time_scope");
|
expect(result.planner.required_axes).toContain("all_time_scope");
|
||||||
expect(result.pilot.mcp_execution_performed).toBe(true);
|
expect(result.pilot.mcp_execution_performed).toBe(true);
|
||||||
expect(result.pilot.derived_bidirectional_value_flow).toMatchObject({
|
expect(result.pilot.derived_bidirectional_value_flow).toMatchObject({
|
||||||
|
|
@ -581,6 +598,11 @@ describe("assistant MCP discovery runtime bridge", () => {
|
||||||
executable_now: true,
|
executable_now: true,
|
||||||
enablement_reason: null
|
enablement_reason: null
|
||||||
});
|
});
|
||||||
|
expect(result.execution_handoff).toMatchObject({
|
||||||
|
handoff_status: "ready_for_guarded_response",
|
||||||
|
allowed_hot_chain: true,
|
||||||
|
can_use_guarded_response: true
|
||||||
|
});
|
||||||
expect(result.pilot.derived_business_overview?.inventory_quality_events?.evidence_status).toBe(
|
expect(result.pilot.derived_business_overview?.inventory_quality_events?.evidence_status).toBe(
|
||||||
"reviewed_no_quality_events_found"
|
"reviewed_no_quality_events_found"
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -166,6 +166,34 @@ describe("assistant MCP discovery turn input adapter", () => {
|
||||||
expect(result.reason_codes).toContain("mcp_discovery_bidirectional_value_flow_signal_detected");
|
expect(result.reason_codes).toContain("mcp_discovery_bidirectional_value_flow_signal_detected");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("normalizes role-prefixed counterparty carryover before document discovery", () => {
|
||||||
|
const result = buildAssistantMcpDiscoveryTurnInput({
|
||||||
|
userMessage: "Покажи документы по этой цепочке и не смешивай Группа СВК с организацией ООО Альтернатива Плюс.",
|
||||||
|
assistantTurnMeaning: {
|
||||||
|
asked_domain_family: "counterparty",
|
||||||
|
asked_action_family: "list_documents",
|
||||||
|
explicit_intent_candidate: "list_documents_by_counterparty"
|
||||||
|
},
|
||||||
|
predecomposeContract: {
|
||||||
|
entities: { counterparty: null, organization: null },
|
||||||
|
period: { scope: "unspecified", period_from: null, period_to: null }
|
||||||
|
},
|
||||||
|
followupContext: {
|
||||||
|
previous_intent: "customer_revenue_and_payments",
|
||||||
|
target_intent: "list_documents_by_counterparty",
|
||||||
|
previous_discovery_pilot_scope: "counterparty_bidirectional_value_flow_query_movements_v1",
|
||||||
|
previous_discovery_loop_metadata_scope_hint: "контрагенту группа свк",
|
||||||
|
previous_anchor_type: "counterparty",
|
||||||
|
previous_anchor_value: "контрагенту группа свк",
|
||||||
|
previous_filters: { counterparty: "контрагенту группа свк" }
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(result.turn_meaning_ref?.explicit_entity_candidates).toEqual(["группа свк"]);
|
||||||
|
expect(result.turn_meaning_ref?.metadata_scope_hint).toBe("группа свк");
|
||||||
|
expect(result.data_need_graph?.subject_candidates).toEqual(["группа свк"]);
|
||||||
|
});
|
||||||
|
|
||||||
it("extracts compact scoped counterparty from net follow-up wording when LLM entities are empty", () => {
|
it("extracts compact scoped counterparty from net follow-up wording when LLM entities are empty", () => {
|
||||||
const orgName = "ООО Альтернатива Плюс";
|
const orgName = "ООО Альтернатива Плюс";
|
||||||
const result = buildAssistantMcpDiscoveryTurnInput({
|
const result = buildAssistantMcpDiscoveryTurnInput({
|
||||||
|
|
@ -1653,6 +1681,7 @@ describe("assistant MCP discovery turn input adapter", () => {
|
||||||
expect(result.should_run_discovery).toBe(true);
|
expect(result.should_run_discovery).toBe(true);
|
||||||
expect(result.semantic_data_need).toBe("business overview evidence with bounded analyst interpretation");
|
expect(result.semantic_data_need).toBe("business overview evidence with bounded analyst interpretation");
|
||||||
expect(result.data_need_graph?.business_fact_family).toBe("business_overview");
|
expect(result.data_need_graph?.business_fact_family).toBe("business_overview");
|
||||||
|
expect(result.data_need_graph?.subject_candidates).toEqual([]);
|
||||||
expect(result.data_need_graph?.time_scope_need).toBe("all_time_scope");
|
expect(result.data_need_graph?.time_scope_need).toBe("all_time_scope");
|
||||||
expect(result.data_need_graph?.clarification_gaps).toEqual([]);
|
expect(result.data_need_graph?.clarification_gaps).toEqual([]);
|
||||||
expect(result.turn_meaning_ref).toMatchObject({
|
expect(result.turn_meaning_ref).toMatchObject({
|
||||||
|
|
@ -1699,6 +1728,22 @@ describe("assistant MCP discovery turn input adapter", () => {
|
||||||
expect(result.reason_codes).not.toContain("mcp_discovery_value_flow_signal_detected");
|
expect(result.reason_codes).not.toContain("mcp_discovery_value_flow_signal_detected");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("routes colloquial multi-surface company overview into business overview over exact VAT", () => {
|
||||||
|
const result = buildAssistantMcpDiscoveryTurnInput({
|
||||||
|
userMessage:
|
||||||
|
"\u041f\u043e-\u0447\u0435\u043b\u043e\u0432\u0435\u0447\u0435\u0441\u043a\u0438 \u043f\u043e\u0441\u043c\u043e\u0442\u0440\u0438 \u041e\u041e\u041e \u0410\u043b\u044c\u0442\u0435\u0440\u043d\u0430\u0442\u0438\u0432\u0430 \u041f\u043b\u044e\u0441 \u0437\u0430 2020: \u0447\u0442\u043e \u043f\u043e \u0431\u0438\u0437\u043d\u0435\u0441\u0443 \u0432\u0438\u0434\u043d\u043e \u0432 1\u0421 \u043f\u043e \u0434\u0435\u043d\u044c\u0433\u0430\u043c, \u041d\u0414\u0421, \u0434\u043e\u043b\u0433\u0430\u043c, \u0441\u043a\u043b\u0430\u0434\u0443, \u043a\u043b\u0438\u0435\u043d\u0442\u0430\u043c/\u043f\u043e\u0441\u0442\u0430\u0432\u0449\u0438\u043a\u0430\u043c \u0438 \u0433\u0434\u0435 \u043f\u043e\u043a\u0430 \u043d\u0435\u043b\u044c\u0437\u044f \u0434\u0435\u043b\u0430\u0442\u044c \u0432\u044b\u0432\u043e\u0434?",
|
||||||
|
assistantTurnMeaning: {
|
||||||
|
explicit_intent_candidate: "vat_liability_confirmed_for_tax_period",
|
||||||
|
explicit_organization_scope: "\u041e\u041e\u041e \u0410\u043b\u044c\u0442\u0435\u0440\u043d\u0430\u0442\u0438\u0432\u0430 \u041f\u043b\u044e\u0441"
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(result.semantic_data_need).toBe("business overview evidence with bounded analyst interpretation");
|
||||||
|
expect(result.data_need_graph?.business_fact_family).toBe("business_overview");
|
||||||
|
expect(result.turn_meaning_ref?.asked_action_family).toBe("broad_evaluation");
|
||||||
|
expect(result.turn_meaning_ref?.explicit_intent_candidate).toBeUndefined();
|
||||||
|
});
|
||||||
|
|
||||||
it("keeps explicit year out of the organization scope for raw business overview wording", () => {
|
it("keeps explicit year out of the organization scope for raw business overview wording", () => {
|
||||||
const result = buildAssistantMcpDiscoveryTurnInput({
|
const result = buildAssistantMcpDiscoveryTurnInput({
|
||||||
userMessage:
|
userMessage:
|
||||||
|
|
@ -1789,6 +1834,36 @@ describe("assistant MCP discovery turn input adapter", () => {
|
||||||
expect(result.reason_codes).not.toContain("mcp_discovery_counterparty_from_predecompose");
|
expect(result.reason_codes).not.toContain("mcp_discovery_counterparty_from_predecompose");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("keeps previous organization scope when all-time business overview says company in general", () => {
|
||||||
|
const result = buildAssistantMcpDiscoveryTurnInput({
|
||||||
|
userMessage:
|
||||||
|
"Теперь за все доступное время дай обзор компании в целом, но не тащи НДС за 2020 как подтвержденную общую налоговую позицию.",
|
||||||
|
assistantTurnMeaning: {
|
||||||
|
asked_domain_family: "business_overview",
|
||||||
|
asked_action_family: "broad_evaluation",
|
||||||
|
explicit_organization_scope: "в целом",
|
||||||
|
unsupported_but_understood_family: "broad_business_evaluation",
|
||||||
|
stale_replay_forbidden: true
|
||||||
|
},
|
||||||
|
followupContext: {
|
||||||
|
previous_discovery_pilot_scope: "business_overview_route_template_v1",
|
||||||
|
previous_discovery_loop_provided_axes: ["organization"],
|
||||||
|
previous_filters: {
|
||||||
|
organization: "ООО Альтернатива Плюс",
|
||||||
|
period_from: "2020-01-01",
|
||||||
|
period_to: "2020-12-31"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(result.adapter_status).toBe("ready");
|
||||||
|
expect(result.data_need_graph?.business_fact_family).toBe("business_overview");
|
||||||
|
expect(result.data_need_graph?.time_scope_need).toBe("all_time_scope");
|
||||||
|
expect(result.turn_meaning_ref?.explicit_organization_scope).toBe("ООО Альтернатива Плюс");
|
||||||
|
expect(result.reason_codes).toContain("mcp_discovery_all_time_scope_signal_detected");
|
||||||
|
expect(result.reason_codes).not.toContain("mcp_discovery_organization_scope_from_semantic_text");
|
||||||
|
});
|
||||||
|
|
||||||
it("grounds organization aliases from known organizations for clean-session earnings questions", () => {
|
it("grounds organization aliases from known organizations for clean-session earnings questions", () => {
|
||||||
const result = buildAssistantMcpDiscoveryTurnInput({
|
const result = buildAssistantMcpDiscoveryTurnInput({
|
||||||
userMessage: "скока денег альтернатива заработала за 20 год?",
|
userMessage: "скока денег альтернатива заработала за 20 год?",
|
||||||
|
|
@ -3690,6 +3765,135 @@ describe("assistant MCP discovery turn input adapter", () => {
|
||||||
expect(result.reason_codes).not.toContain("mcp_discovery_not_applicable_for_supported_exact_turn");
|
expect(result.reason_codes).not.toContain("mcp_discovery_not_applicable_for_supported_exact_turn");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("routes brief compare boundary wording to business overview without implicit current date", () => {
|
||||||
|
const today = new Date().toISOString().slice(0, 10);
|
||||||
|
const orgName =
|
||||||
|
"\u041e\u041e\u041e \u0410\u043b\u044c\u0442\u0435\u0440\u043d\u0430\u0442\u0438\u0432\u0430 \u041f\u043b\u044e\u0441";
|
||||||
|
const counterpartyName = "\u0413\u0440\u0443\u043f\u043f\u0430 \u0421\u0412\u041a";
|
||||||
|
const valueFlowBundle = {
|
||||||
|
counterparty: counterpartyName,
|
||||||
|
incoming_total: 20653490,
|
||||||
|
outgoing_total: 2129651,
|
||||||
|
net_amount: 18523839
|
||||||
|
};
|
||||||
|
const documentBundle = {
|
||||||
|
counterparty: counterpartyName,
|
||||||
|
document_count: 19
|
||||||
|
};
|
||||||
|
|
||||||
|
const result = buildAssistantMcpDiscoveryTurnInput({
|
||||||
|
userMessage:
|
||||||
|
"\u0418 \u043a\u043e\u0440\u043e\u0442\u043a\u043e \u0441\u0440\u0430\u0432\u043d\u0438: \u0447\u0442\u043e \u0443 \u043d\u0430\u0441 \u043f\u043e\u0434\u0442\u0432\u0435\u0440\u0436\u0434\u0435\u043d\u043e \u043f\u043e \u043a\u043e\u043c\u043f\u0430\u043d\u0438\u0438, \u0447\u0442\u043e \u043e\u0442\u0434\u0435\u043b\u044c\u043d\u043e \u043f\u043e \u0432\u044b\u0431\u0440\u0430\u043d\u043d\u043e\u043c\u0443 \u043a\u043e\u043d\u0442\u0440\u0430\u0433\u0435\u043d\u0442\u0443, \u0438 \u043a\u0430\u043a\u0438\u0435 \u0432\u044b\u0432\u043e\u0434\u044b \u043d\u0435\u043b\u044c\u0437\u044f \u0434\u0435\u043b\u0430\u0442\u044c?",
|
||||||
|
assistantTurnMeaning: {
|
||||||
|
asked_domain_family: "counterparty_value",
|
||||||
|
asked_action_family: "turnover",
|
||||||
|
explicit_intent_candidate: "list_documents_by_counterparty",
|
||||||
|
explicit_entity_candidates: ["\u043d\u0430\u0441 \u043f\u043e\u0434\u0442\u0432\u0435\u0440\u0436\u0434\u0435\u043d\u043e"]
|
||||||
|
},
|
||||||
|
followupContext: {
|
||||||
|
previous_discovery_pilot_scope: "counterparty_document_evidence_query_documents_v1",
|
||||||
|
previous_intent: "list_documents_by_counterparty",
|
||||||
|
target_intent: "list_documents_by_counterparty",
|
||||||
|
previous_filters: {
|
||||||
|
organization: orgName,
|
||||||
|
counterparty: counterpartyName,
|
||||||
|
as_of_date: today
|
||||||
|
},
|
||||||
|
previous_anchor_type: "counterparty",
|
||||||
|
previous_anchor_value: counterpartyName,
|
||||||
|
previous_discovery_bidirectional_value_flow: valueFlowBundle,
|
||||||
|
previous_discovery_document_summary: documentBundle
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(result.adapter_status).toBe("ready");
|
||||||
|
expect(result.should_run_discovery).toBe(true);
|
||||||
|
expect(result.semantic_data_need).toBe("business overview evidence with bounded analyst interpretation");
|
||||||
|
expect(result.data_need_graph?.business_fact_family).toBe("business_overview");
|
||||||
|
expect(result.data_need_graph?.subject_candidates).toEqual([]);
|
||||||
|
expect(result.turn_meaning_ref).toMatchObject({
|
||||||
|
asked_domain_family: "business_overview",
|
||||||
|
asked_action_family: "broad_evaluation",
|
||||||
|
business_overview_separate_entity_candidates: [counterpartyName],
|
||||||
|
previous_counterparty_value_flow_bundle: valueFlowBundle,
|
||||||
|
previous_counterparty_document_bundle: documentBundle,
|
||||||
|
explicit_organization_scope: orgName,
|
||||||
|
unsupported_but_understood_family: "broad_business_evaluation",
|
||||||
|
stale_replay_forbidden: true
|
||||||
|
});
|
||||||
|
expect(result.turn_meaning_ref?.explicit_entity_candidates).toBeUndefined();
|
||||||
|
expect(result.turn_meaning_ref?.explicit_date_scope).toBeUndefined();
|
||||||
|
expect(result.reason_codes).toContain(
|
||||||
|
"mcp_discovery_business_overview_preserved_explicit_counterparty_summary_scope"
|
||||||
|
);
|
||||||
|
expect(result.reason_codes).not.toContain("mcp_discovery_counterparty_from_raw_scope");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("continues business overview organization clarification without current-date reset", () => {
|
||||||
|
const today = new Date().toISOString().slice(0, 10);
|
||||||
|
const orgName = "ООО Альтернатива Плюс";
|
||||||
|
const counterpartyName = "Группа СВК";
|
||||||
|
const valueFlowBundle = {
|
||||||
|
counterparty: counterpartyName,
|
||||||
|
incoming_total: 20653490,
|
||||||
|
outgoing_total: 2129651,
|
||||||
|
net_amount: 18523839
|
||||||
|
};
|
||||||
|
const documentBundle = {
|
||||||
|
counterparty: counterpartyName,
|
||||||
|
document_count: 19
|
||||||
|
};
|
||||||
|
|
||||||
|
const result = buildAssistantMcpDiscoveryTurnInput({
|
||||||
|
userMessage: orgName,
|
||||||
|
assistantTurnMeaning: {
|
||||||
|
asked_domain_family: "business_overview",
|
||||||
|
asked_action_family: "broad_evaluation",
|
||||||
|
explicit_date_scope: today,
|
||||||
|
stale_replay_forbidden: true
|
||||||
|
},
|
||||||
|
predecomposeContract: {
|
||||||
|
entities: { organization: orgName },
|
||||||
|
period: { as_of_date: today }
|
||||||
|
},
|
||||||
|
followupContext: {
|
||||||
|
previous_discovery_loop_status: "awaiting_clarification",
|
||||||
|
previous_discovery_loop_selected_chain_id: "business_overview",
|
||||||
|
previous_discovery_loop_pending_axes: ["organization"],
|
||||||
|
previous_discovery_loop_provided_axes: ["all_time_scope", "metadata_scope"],
|
||||||
|
previous_discovery_loop_asked_domain_family: "business_overview",
|
||||||
|
previous_discovery_loop_asked_action_family: "broad_evaluation",
|
||||||
|
previous_discovery_loop_unsupported_family: "broad_business_evaluation",
|
||||||
|
previous_discovery_loop_metadata_scope_hint: counterpartyName,
|
||||||
|
previous_anchor_type: "counterparty",
|
||||||
|
previous_anchor_value: counterpartyName,
|
||||||
|
previous_discovery_bidirectional_value_flow: valueFlowBundle,
|
||||||
|
previous_discovery_document_summary: documentBundle
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(result.source_signal).toBe("assistant_turn_meaning");
|
||||||
|
expect(result.data_need_graph?.business_fact_family).toBe("business_overview");
|
||||||
|
expect(result.data_need_graph?.time_scope_need).toBe("all_time_scope");
|
||||||
|
expect(result.turn_meaning_ref).toMatchObject({
|
||||||
|
asked_domain_family: "business_overview",
|
||||||
|
asked_action_family: "broad_evaluation",
|
||||||
|
business_overview_separate_entity_candidates: [counterpartyName],
|
||||||
|
previous_counterparty_value_flow_bundle: valueFlowBundle,
|
||||||
|
previous_counterparty_document_bundle: documentBundle,
|
||||||
|
explicit_organization_scope: orgName,
|
||||||
|
unsupported_but_understood_family: "broad_business_evaluation",
|
||||||
|
stale_replay_forbidden: true
|
||||||
|
});
|
||||||
|
expect(result.turn_meaning_ref?.explicit_date_scope).toBeUndefined();
|
||||||
|
expect(result.turn_meaning_ref?.explicit_entity_candidates).toBeUndefined();
|
||||||
|
expect(result.reason_codes).toContain("mcp_discovery_all_time_scope_from_followup_context");
|
||||||
|
expect(result.reason_codes).toContain(
|
||||||
|
"mcp_discovery_business_overview_preserved_explicit_counterparty_summary_scope"
|
||||||
|
);
|
||||||
|
expect(result.reason_codes).not.toContain("mcp_discovery_date_scope_from_followup_context");
|
||||||
|
});
|
||||||
|
|
||||||
it("preserves explicit counterparty scope for company plus counterparty business summaries", () => {
|
it("preserves explicit counterparty scope for company plus counterparty business summaries", () => {
|
||||||
const orgName = "ООО Альтернатива Плюс";
|
const orgName = "ООО Альтернатива Плюс";
|
||||||
const counterpartyName = "Группа СВК";
|
const counterpartyName = "Группа СВК";
|
||||||
|
|
|
||||||
|
|
@ -339,6 +339,71 @@ describe("assistantTransitionPolicy", () => {
|
||||||
expect(carryover?.followupContext?.previous_anchor_value).toBe("Workstation Focus");
|
expect(carryover?.followupContext?.previous_anchor_value).toBe("Workstation Focus");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("keeps selected counterparty document follow-up through stale discovery guard", () => {
|
||||||
|
const policy = buildPolicy({
|
||||||
|
findLastAddressAssistantItem: () => ({
|
||||||
|
text: "Confirmed value-flow profile for SVK Group.",
|
||||||
|
debug: {
|
||||||
|
detected_intent: "customer_revenue_and_payments",
|
||||||
|
extracted_filters: {
|
||||||
|
organization: "ACME",
|
||||||
|
period_from: "2017-01-01",
|
||||||
|
period_to: "2017-12-31"
|
||||||
|
},
|
||||||
|
assistant_mcp_discovery_entry_point_v1: {
|
||||||
|
bridge: {
|
||||||
|
loop_state: {
|
||||||
|
selected_chain_id: "value_flow_comparison"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
resolveAssistantTurnMeaning: () => ({
|
||||||
|
stale_replay_forbidden: true,
|
||||||
|
explicit_intent_candidate: "list_documents_by_counterparty"
|
||||||
|
}),
|
||||||
|
resolveAddressIntent: () => ({ intent: "list_documents_by_counterparty" }),
|
||||||
|
resolveAddressIntentFamily: (intent: unknown) =>
|
||||||
|
["customer_revenue_and_payments", "list_documents_by_counterparty"].includes(String(intent ?? ""))
|
||||||
|
? "counterparty"
|
||||||
|
: toNonEmptyString(intent),
|
||||||
|
hasAddressFollowupContextSignal: () => false,
|
||||||
|
hasReferentialPointer: () => false,
|
||||||
|
findRecentInventoryRootFrame: () => null,
|
||||||
|
findRecentAddressFilterValue: () => null
|
||||||
|
});
|
||||||
|
|
||||||
|
const carryover = policy.resolveAddressFollowupCarryoverContext(
|
||||||
|
"Show docs for selected counterparty",
|
||||||
|
[],
|
||||||
|
null,
|
||||||
|
{
|
||||||
|
predecomposeContract: {
|
||||||
|
intent: "list_documents_by_counterparty"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
session_context: {
|
||||||
|
active_focus_object: {
|
||||||
|
object_type: "counterparty",
|
||||||
|
label: "SVK Group"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(carryover?.followupContext?.target_intent).toBe("list_documents_by_counterparty");
|
||||||
|
expect(carryover?.followupContext?.previous_anchor_type).toBe("counterparty");
|
||||||
|
expect(carryover?.followupContext?.previous_anchor_value).toBe("SVK Group");
|
||||||
|
expect(carryover?.followupContext?.previous_filters).toMatchObject({
|
||||||
|
organization: "ACME",
|
||||||
|
counterparty: "SVK Group",
|
||||||
|
period_from: "2017-01-01",
|
||||||
|
period_to: "2017-12-31"
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
it("hydrates follow-up organization from shared assistant authority when local history filters are empty", () => {
|
it("hydrates follow-up organization from shared assistant authority when local history filters are empty", () => {
|
||||||
const policy = buildPolicy({
|
const policy = buildPolicy({
|
||||||
findLastAddressAssistantItem: () => ({
|
findLastAddressAssistantItem: () => ({
|
||||||
|
|
@ -1628,6 +1693,164 @@ describe("assistantTransitionPolicy", () => {
|
||||||
expect(carryover?.followupContext?.target_intent).toBe("customer_revenue_and_payments");
|
expect(carryover?.followupContext?.target_intent).toBe("customer_revenue_and_payments");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("carries selected counterparty proof bundles through business overview organization clarification", () => {
|
||||||
|
const orgName = "ООО Альтернатива Плюс";
|
||||||
|
const counterpartyName = "Группа СВК";
|
||||||
|
const valueFlowBundle = {
|
||||||
|
counterparty: counterpartyName,
|
||||||
|
incoming_total: 20653490,
|
||||||
|
outgoing_total: 2129651,
|
||||||
|
net_amount: 18523839
|
||||||
|
};
|
||||||
|
const documentBundle = {
|
||||||
|
counterparty: counterpartyName,
|
||||||
|
document_count: 19
|
||||||
|
};
|
||||||
|
const policy = buildPolicy({
|
||||||
|
findLastAddressAssistantItem: () => ({
|
||||||
|
role: "assistant",
|
||||||
|
text: "Коротко: уточните, по какой компании/организации сравнить выбранного контрагента Группа СВК.",
|
||||||
|
debug: {
|
||||||
|
execution_lane: "living_chat",
|
||||||
|
detected_intent: "business_overview",
|
||||||
|
mcp_discovery_response_applied: true,
|
||||||
|
assistant_mcp_discovery_entry_point_v1: {
|
||||||
|
schema_version: "assistant_mcp_discovery_runtime_entry_point_v1",
|
||||||
|
entry_status: "bridge_executed",
|
||||||
|
turn_input: {
|
||||||
|
turn_meaning_ref: {
|
||||||
|
asked_domain_family: "business_overview",
|
||||||
|
asked_action_family: "broad_evaluation",
|
||||||
|
business_overview_separate_entity_candidates: [counterpartyName],
|
||||||
|
previous_counterparty_value_flow_bundle: valueFlowBundle,
|
||||||
|
previous_counterparty_document_bundle: documentBundle,
|
||||||
|
metadata_scope_hint: counterpartyName,
|
||||||
|
unsupported_but_understood_family: "broad_business_evaluation",
|
||||||
|
stale_replay_forbidden: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
bridge: {
|
||||||
|
bridge_status: "needs_clarification",
|
||||||
|
business_fact_answer_allowed: false,
|
||||||
|
pilot: {
|
||||||
|
pilot_scope: "business_overview_route_template_v1"
|
||||||
|
},
|
||||||
|
loop_state: {
|
||||||
|
schema_version: "assistant_mcp_discovery_loop_state_v1",
|
||||||
|
loop_status: "awaiting_clarification",
|
||||||
|
selected_chain_id: "business_overview",
|
||||||
|
pilot_scope: "business_overview_route_template_v1",
|
||||||
|
asked_domain_family: "business_overview",
|
||||||
|
asked_action_family: "broad_evaluation",
|
||||||
|
unsupported_but_understood_family: "broad_business_evaluation",
|
||||||
|
pending_axes: ["organization"],
|
||||||
|
provided_axes: ["all_time_scope", "metadata_scope"],
|
||||||
|
metadata_scope_hint: counterpartyName
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
resolveOrganizationSelectionFromMessage: (message: string) =>
|
||||||
|
String(message ?? "").includes("Альтернатива") ? orgName : null,
|
||||||
|
hasAddressFollowupContextSignal: () => false
|
||||||
|
});
|
||||||
|
|
||||||
|
const carryover = policy.resolveAddressFollowupCarryoverContext(
|
||||||
|
orgName,
|
||||||
|
[],
|
||||||
|
null,
|
||||||
|
{
|
||||||
|
predecomposeContract: {
|
||||||
|
mode: "unsupported",
|
||||||
|
intent: "unknown",
|
||||||
|
entities: { organization: orgName },
|
||||||
|
semantics: { anchor_kind: "organization", anchor_value: orgName }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
null
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(carryover?.followupContext?.previous_discovery_loop_status).toBe("awaiting_clarification");
|
||||||
|
expect(carryover?.followupContext?.previous_discovery_loop_selected_chain_id).toBe("business_overview");
|
||||||
|
expect(carryover?.followupContext?.previous_discovery_loop_provided_axes).toEqual([
|
||||||
|
"all_time_scope",
|
||||||
|
"metadata_scope"
|
||||||
|
]);
|
||||||
|
expect(carryover?.followupContext?.previous_discovery_loop_metadata_scope_hint).toBe(counterpartyName);
|
||||||
|
expect(carryover?.followupContext?.previous_discovery_bidirectional_value_flow).toEqual(valueFlowBundle);
|
||||||
|
expect(carryover?.followupContext?.previous_discovery_document_summary).toEqual(documentBundle);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("recovers selected counterparty proof bundles from prior boundary-summary text", () => {
|
||||||
|
const orgName = "ООО Альтернатива Плюс";
|
||||||
|
const counterpartyName = "Группа СВК";
|
||||||
|
const policy = buildPolicy({
|
||||||
|
findLastAddressAssistantItem: () => ({
|
||||||
|
role: "assistant",
|
||||||
|
text:
|
||||||
|
"Коротко: уточните, по какой компании/организации сравнить выбранного контрагента Группа СВК.\n" +
|
||||||
|
"Отдельно по контрагенту Группа СВК: подтверждено получили 20 653 490 руб., заплатили 2 129 651 руб., расчетное нетто в нашу сторону 18 523 839 руб. Основа: проверенные входящие платежи и исходящие платежи и документы по цепочке: найдено 19.",
|
||||||
|
debug: {
|
||||||
|
execution_lane: "living_chat",
|
||||||
|
detected_intent: "business_overview",
|
||||||
|
mcp_discovery_response_applied: true,
|
||||||
|
assistant_mcp_discovery_entry_point_v1: {
|
||||||
|
schema_version: "assistant_mcp_discovery_runtime_entry_point_v1",
|
||||||
|
entry_status: "bridge_executed",
|
||||||
|
bridge: {
|
||||||
|
bridge_status: "needs_clarification",
|
||||||
|
business_fact_answer_allowed: false,
|
||||||
|
pilot: {
|
||||||
|
pilot_scope: "business_overview_route_template_v1"
|
||||||
|
},
|
||||||
|
loop_state: {
|
||||||
|
schema_version: "assistant_mcp_discovery_loop_state_v1",
|
||||||
|
loop_status: "awaiting_clarification",
|
||||||
|
selected_chain_id: "business_overview",
|
||||||
|
pilot_scope: "business_overview_route_template_v1",
|
||||||
|
asked_domain_family: "business_overview",
|
||||||
|
asked_action_family: "broad_evaluation",
|
||||||
|
pending_axes: ["organization"],
|
||||||
|
provided_axes: ["all_time_scope", "metadata_scope"],
|
||||||
|
metadata_scope_hint: counterpartyName
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
resolveOrganizationSelectionFromMessage: (message: string) =>
|
||||||
|
String(message ?? "").includes("Альтернатива") ? orgName : null,
|
||||||
|
hasAddressFollowupContextSignal: () => false
|
||||||
|
});
|
||||||
|
|
||||||
|
const carryover = policy.resolveAddressFollowupCarryoverContext(
|
||||||
|
orgName,
|
||||||
|
[],
|
||||||
|
null,
|
||||||
|
{
|
||||||
|
predecomposeContract: {
|
||||||
|
mode: "unsupported",
|
||||||
|
intent: "unknown",
|
||||||
|
entities: { organization: orgName },
|
||||||
|
semantics: { anchor_kind: "organization", anchor_value: orgName }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
null
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(carryover?.followupContext?.previous_discovery_bidirectional_value_flow).toMatchObject({
|
||||||
|
counterparty: counterpartyName,
|
||||||
|
incoming_customer_revenue: { total_amount_human_ru: "20 653 490 руб." },
|
||||||
|
outgoing_supplier_payout: { total_amount_human_ru: "2 129 651 руб." },
|
||||||
|
net_amount_human_ru: "18 523 839 руб."
|
||||||
|
});
|
||||||
|
expect(carryover?.followupContext?.previous_discovery_document_summary).toMatchObject({
|
||||||
|
counterparty: counterpartyName,
|
||||||
|
document_count: 19
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
it("carries grounded metadata downstream route hints into followup context", () => {
|
it("carries grounded metadata downstream route hints into followup context", () => {
|
||||||
const policy = buildPolicy({
|
const policy = buildPolicy({
|
||||||
findLastAddressAssistantItem: () => null,
|
findLastAddressAssistantItem: () => null,
|
||||||
|
|
|
||||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
|
@ -1,353 +0,0 @@
|
||||||
{
|
|
||||||
"saved_at": "2026-04-24T14:06:30+00:00",
|
|
||||||
"generation_id": "gen-ag04241406-abe4d8",
|
|
||||||
"mode": "saved_user_sessions",
|
|
||||||
"title": "AGENT | Post-F cross-stage semantic integrity canary",
|
|
||||||
"agent_run": true,
|
|
||||||
"questions": [
|
|
||||||
"Мне нужно понять, где в 1С по НДС вообще лежат данные. Какие объекты стоит смотреть по НДС?",
|
|
||||||
"Хорошо, тогда покажи движения по ООО Альтернатива Плюс.",
|
|
||||||
"За 2020 год.",
|
|
||||||
"А теперь по документам?",
|
|
||||||
"А теперь за 2021 год?",
|
|
||||||
"А теперь за все время?",
|
|
||||||
"С НДС закончили. Новая тема: покажи документы по Жуковке 51.",
|
|
||||||
"Хорошо, а теперь платежи по нему тоже покажи.",
|
|
||||||
"А по нему договоры?",
|
|
||||||
"А по нему документы?",
|
|
||||||
"А за 2021?",
|
|
||||||
"С Жуковкой закончили. Теперь другая задача: быстрый денежный срез по одной организации. Если для ответа нужна организация, просто уточни ее. Сколько вообще входящих денег было за 2020 год?",
|
|
||||||
"По ООО Альтернатива Плюс.",
|
|
||||||
"Понял, тогда за все время.",
|
|
||||||
"Хорошо. А что по ООО Альтернатива Плюс больше в 2020 году: входящие или исходящие деньги?",
|
|
||||||
"И кто больше всего принес денег этой организации в 2020 году?",
|
|
||||||
"А в 2021 году?",
|
|
||||||
"Теперь отдельная тема по конкретному контрагенту. Найди в 1С Группу СВК.",
|
|
||||||
"Сколько получили по нему за 2020 год?",
|
|
||||||
"А теперь сколько заплатили?",
|
|
||||||
"А какое нетто?",
|
|
||||||
"А по документам?",
|
|
||||||
"А по движениям?",
|
|
||||||
"А теперь тот же смысл за 2021 год."
|
|
||||||
],
|
|
||||||
"metadata": {
|
|
||||||
"assistant_prompt_version": null,
|
|
||||||
"decomposition_prompt_version": null,
|
|
||||||
"prompt_fingerprint": null,
|
|
||||||
"agent_focus": "cross-stage canary: VAT metadata, numeric counterparty suffix, open organization value-flow, SVK grounded reset",
|
|
||||||
"architecture_phase": "Post-F Semantic Integrity Hardening",
|
|
||||||
"source_spec_file": "X:\\1C\\NDC_1C\\docs\\orchestration\\address_truth_harness_post_f_cross_stage_canary_agent_20260424.json",
|
|
||||||
"scenario_id": "address_truth_harness_post_f_cross_stage_canary_agent_20260424",
|
|
||||||
"semantic_tags": [
|
|
||||||
"account_injection_guard",
|
|
||||||
"all_time_followup",
|
|
||||||
"bidirectional_value_flow",
|
|
||||||
"clarification_resume",
|
|
||||||
"contracts_followup",
|
|
||||||
"counterparty_pronoun_resolution",
|
|
||||||
"cross_stage_canary",
|
|
||||||
"document_lane_continuity",
|
|
||||||
"document_pivot_after_movement",
|
|
||||||
"documents_followup",
|
|
||||||
"documents_pivot",
|
|
||||||
"grounded_counterparty",
|
|
||||||
"incoming_value_flow",
|
|
||||||
"legacy_phase39",
|
|
||||||
"legacy_phase64",
|
|
||||||
"legacy_phase67",
|
|
||||||
"metadata_surface",
|
|
||||||
"movement_execution",
|
|
||||||
"movement_lane_after_metadata",
|
|
||||||
"movements_pivot",
|
|
||||||
"net_value_flow",
|
|
||||||
"numeric_counterparty_suffix",
|
|
||||||
"open_scope_total",
|
|
||||||
"organization_clarification",
|
|
||||||
"organization_scope",
|
|
||||||
"payments_followup",
|
|
||||||
"payout_value_flow",
|
|
||||||
"period_clarification_resume",
|
|
||||||
"post_f",
|
|
||||||
"repeated_pivot",
|
|
||||||
"scope_reuse",
|
|
||||||
"topic_reset",
|
|
||||||
"value_flow_ranking",
|
|
||||||
"vat_orientation",
|
|
||||||
"year_switch",
|
|
||||||
"year_switch_after_document_pivot",
|
|
||||||
"year_switch_after_repeated_pivot"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"source_session_id": null,
|
|
||||||
"session": {
|
|
||||||
"session_id": null,
|
|
||||||
"mode": "agent_semantic_run",
|
|
||||||
"items": [
|
|
||||||
{
|
|
||||||
"message_id": "agent-user-001",
|
|
||||||
"role": "user",
|
|
||||||
"text": "Мне нужно понять, где в 1С по НДС вообще лежат данные. Какие объекты стоит смотреть по НДС?",
|
|
||||||
"created_at": "2026-04-24T14:06:30+00:00",
|
|
||||||
"reply_type": null,
|
|
||||||
"trace_id": null,
|
|
||||||
"debug": null
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"message_id": "agent-user-002",
|
|
||||||
"role": "user",
|
|
||||||
"text": "Хорошо, тогда покажи движения по ООО Альтернатива Плюс.",
|
|
||||||
"created_at": "2026-04-24T14:06:30+00:00",
|
|
||||||
"reply_type": null,
|
|
||||||
"trace_id": null,
|
|
||||||
"debug": null
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"message_id": "agent-user-003",
|
|
||||||
"role": "user",
|
|
||||||
"text": "За 2020 год.",
|
|
||||||
"created_at": "2026-04-24T14:06:30+00:00",
|
|
||||||
"reply_type": null,
|
|
||||||
"trace_id": null,
|
|
||||||
"debug": null
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"message_id": "agent-user-004",
|
|
||||||
"role": "user",
|
|
||||||
"text": "А теперь по документам?",
|
|
||||||
"created_at": "2026-04-24T14:06:30+00:00",
|
|
||||||
"reply_type": null,
|
|
||||||
"trace_id": null,
|
|
||||||
"debug": null
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"message_id": "agent-user-005",
|
|
||||||
"role": "user",
|
|
||||||
"text": "А теперь за 2021 год?",
|
|
||||||
"created_at": "2026-04-24T14:06:30+00:00",
|
|
||||||
"reply_type": null,
|
|
||||||
"trace_id": null,
|
|
||||||
"debug": null
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"message_id": "agent-user-006",
|
|
||||||
"role": "user",
|
|
||||||
"text": "А теперь за все время?",
|
|
||||||
"created_at": "2026-04-24T14:06:30+00:00",
|
|
||||||
"reply_type": null,
|
|
||||||
"trace_id": null,
|
|
||||||
"debug": null
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"message_id": "agent-user-007",
|
|
||||||
"role": "user",
|
|
||||||
"text": "С НДС закончили. Новая тема: покажи документы по Жуковке 51.",
|
|
||||||
"created_at": "2026-04-24T14:06:30+00:00",
|
|
||||||
"reply_type": null,
|
|
||||||
"trace_id": null,
|
|
||||||
"debug": null
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"message_id": "agent-user-008",
|
|
||||||
"role": "user",
|
|
||||||
"text": "Хорошо, а теперь платежи по нему тоже покажи.",
|
|
||||||
"created_at": "2026-04-24T14:06:30+00:00",
|
|
||||||
"reply_type": null,
|
|
||||||
"trace_id": null,
|
|
||||||
"debug": null
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"message_id": "agent-user-009",
|
|
||||||
"role": "user",
|
|
||||||
"text": "А по нему договоры?",
|
|
||||||
"created_at": "2026-04-24T14:06:30+00:00",
|
|
||||||
"reply_type": null,
|
|
||||||
"trace_id": null,
|
|
||||||
"debug": null
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"message_id": "agent-user-010",
|
|
||||||
"role": "user",
|
|
||||||
"text": "А по нему документы?",
|
|
||||||
"created_at": "2026-04-24T14:06:30+00:00",
|
|
||||||
"reply_type": null,
|
|
||||||
"trace_id": null,
|
|
||||||
"debug": null
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"message_id": "agent-user-011",
|
|
||||||
"role": "user",
|
|
||||||
"text": "А за 2021?",
|
|
||||||
"created_at": "2026-04-24T14:06:30+00:00",
|
|
||||||
"reply_type": null,
|
|
||||||
"trace_id": null,
|
|
||||||
"debug": null
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"message_id": "agent-user-012",
|
|
||||||
"role": "user",
|
|
||||||
"text": "С Жуковкой закончили. Теперь другая задача: быстрый денежный срез по одной организации. Если для ответа нужна организация, просто уточни ее. Сколько вообще входящих денег было за 2020 год?",
|
|
||||||
"created_at": "2026-04-24T14:06:30+00:00",
|
|
||||||
"reply_type": null,
|
|
||||||
"trace_id": null,
|
|
||||||
"debug": null
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"message_id": "agent-user-013",
|
|
||||||
"role": "user",
|
|
||||||
"text": "По ООО Альтернатива Плюс.",
|
|
||||||
"created_at": "2026-04-24T14:06:30+00:00",
|
|
||||||
"reply_type": null,
|
|
||||||
"trace_id": null,
|
|
||||||
"debug": null
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"message_id": "agent-user-014",
|
|
||||||
"role": "user",
|
|
||||||
"text": "Понял, тогда за все время.",
|
|
||||||
"created_at": "2026-04-24T14:06:30+00:00",
|
|
||||||
"reply_type": null,
|
|
||||||
"trace_id": null,
|
|
||||||
"debug": null
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"message_id": "agent-user-015",
|
|
||||||
"role": "user",
|
|
||||||
"text": "Хорошо. А что по ООО Альтернатива Плюс больше в 2020 году: входящие или исходящие деньги?",
|
|
||||||
"created_at": "2026-04-24T14:06:30+00:00",
|
|
||||||
"reply_type": null,
|
|
||||||
"trace_id": null,
|
|
||||||
"debug": null
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"message_id": "agent-user-016",
|
|
||||||
"role": "user",
|
|
||||||
"text": "И кто больше всего принес денег этой организации в 2020 году?",
|
|
||||||
"created_at": "2026-04-24T14:06:30+00:00",
|
|
||||||
"reply_type": null,
|
|
||||||
"trace_id": null,
|
|
||||||
"debug": null
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"message_id": "agent-user-017",
|
|
||||||
"role": "user",
|
|
||||||
"text": "А в 2021 году?",
|
|
||||||
"created_at": "2026-04-24T14:06:30+00:00",
|
|
||||||
"reply_type": null,
|
|
||||||
"trace_id": null,
|
|
||||||
"debug": null
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"message_id": "agent-user-018",
|
|
||||||
"role": "user",
|
|
||||||
"text": "Теперь отдельная тема по конкретному контрагенту. Найди в 1С Группу СВК.",
|
|
||||||
"created_at": "2026-04-24T14:06:30+00:00",
|
|
||||||
"reply_type": null,
|
|
||||||
"trace_id": null,
|
|
||||||
"debug": null
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"message_id": "agent-user-019",
|
|
||||||
"role": "user",
|
|
||||||
"text": "Сколько получили по нему за 2020 год?",
|
|
||||||
"created_at": "2026-04-24T14:06:30+00:00",
|
|
||||||
"reply_type": null,
|
|
||||||
"trace_id": null,
|
|
||||||
"debug": null
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"message_id": "agent-user-020",
|
|
||||||
"role": "user",
|
|
||||||
"text": "А теперь сколько заплатили?",
|
|
||||||
"created_at": "2026-04-24T14:06:30+00:00",
|
|
||||||
"reply_type": null,
|
|
||||||
"trace_id": null,
|
|
||||||
"debug": null
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"message_id": "agent-user-021",
|
|
||||||
"role": "user",
|
|
||||||
"text": "А какое нетто?",
|
|
||||||
"created_at": "2026-04-24T14:06:30+00:00",
|
|
||||||
"reply_type": null,
|
|
||||||
"trace_id": null,
|
|
||||||
"debug": null
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"message_id": "agent-user-022",
|
|
||||||
"role": "user",
|
|
||||||
"text": "А по документам?",
|
|
||||||
"created_at": "2026-04-24T14:06:30+00:00",
|
|
||||||
"reply_type": null,
|
|
||||||
"trace_id": null,
|
|
||||||
"debug": null
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"message_id": "agent-user-023",
|
|
||||||
"role": "user",
|
|
||||||
"text": "А по движениям?",
|
|
||||||
"created_at": "2026-04-24T14:06:30+00:00",
|
|
||||||
"reply_type": null,
|
|
||||||
"trace_id": null,
|
|
||||||
"debug": null
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"message_id": "agent-user-024",
|
|
||||||
"role": "user",
|
|
||||||
"text": "А теперь тот же смысл за 2021 год.",
|
|
||||||
"created_at": "2026-04-24T14:06:30+00:00",
|
|
||||||
"reply_type": null,
|
|
||||||
"trace_id": null,
|
|
||||||
"debug": null
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"agent_run": true,
|
|
||||||
"metadata": {
|
|
||||||
"assistant_prompt_version": null,
|
|
||||||
"decomposition_prompt_version": null,
|
|
||||||
"prompt_fingerprint": null,
|
|
||||||
"agent_focus": "cross-stage canary: VAT metadata, numeric counterparty suffix, open organization value-flow, SVK grounded reset",
|
|
||||||
"architecture_phase": "Post-F Semantic Integrity Hardening",
|
|
||||||
"source_spec_file": "X:\\1C\\NDC_1C\\docs\\orchestration\\address_truth_harness_post_f_cross_stage_canary_agent_20260424.json",
|
|
||||||
"scenario_id": "address_truth_harness_post_f_cross_stage_canary_agent_20260424",
|
|
||||||
"semantic_tags": [
|
|
||||||
"account_injection_guard",
|
|
||||||
"all_time_followup",
|
|
||||||
"bidirectional_value_flow",
|
|
||||||
"clarification_resume",
|
|
||||||
"contracts_followup",
|
|
||||||
"counterparty_pronoun_resolution",
|
|
||||||
"cross_stage_canary",
|
|
||||||
"document_lane_continuity",
|
|
||||||
"document_pivot_after_movement",
|
|
||||||
"documents_followup",
|
|
||||||
"documents_pivot",
|
|
||||||
"grounded_counterparty",
|
|
||||||
"incoming_value_flow",
|
|
||||||
"legacy_phase39",
|
|
||||||
"legacy_phase64",
|
|
||||||
"legacy_phase67",
|
|
||||||
"metadata_surface",
|
|
||||||
"movement_execution",
|
|
||||||
"movement_lane_after_metadata",
|
|
||||||
"movements_pivot",
|
|
||||||
"net_value_flow",
|
|
||||||
"numeric_counterparty_suffix",
|
|
||||||
"open_scope_total",
|
|
||||||
"organization_clarification",
|
|
||||||
"organization_scope",
|
|
||||||
"payments_followup",
|
|
||||||
"payout_value_flow",
|
|
||||||
"period_clarification_resume",
|
|
||||||
"post_f",
|
|
||||||
"repeated_pivot",
|
|
||||||
"scope_reuse",
|
|
||||||
"topic_reset",
|
|
||||||
"value_flow_ranking",
|
|
||||||
"vat_orientation",
|
|
||||||
"year_switch",
|
|
||||||
"year_switch_after_document_pivot",
|
|
||||||
"year_switch_after_repeated_pivot"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,177 +0,0 @@
|
||||||
{
|
|
||||||
"saved_at": "2026-04-24T17:10:31+00:00",
|
|
||||||
"generation_id": "gen-ag04241710-bdb248",
|
|
||||||
"mode": "saved_user_sessions",
|
|
||||||
"title": "AGENT | Post-F ручные провалы VAT revenue item-flow live3",
|
|
||||||
"agent_run": true,
|
|
||||||
"questions": [
|
|
||||||
"кайф - что там на складе по остаткам?",
|
|
||||||
"АЛЬТЕРНАТИВА",
|
|
||||||
"март 2016",
|
|
||||||
"По выбранному объекту \"Рабочая станция универсального специалиста (индивидуальное изготовление)\": где взяли это?",
|
|
||||||
"ндс можешь прикинуть на дату покупки рабочей станции?",
|
|
||||||
"прикинь какой ндс нам надо заплатить на февраль 2017",
|
|
||||||
"кто у нас самый доходный клиент за все время",
|
|
||||||
"по чепурнову покажи все доки",
|
|
||||||
"а по свк",
|
|
||||||
"а сейчас у нас есть что на складе?",
|
|
||||||
"что нам отгружал чепурнов? какой товар или услугу?"
|
|
||||||
],
|
|
||||||
"metadata": {
|
|
||||||
"assistant_prompt_version": null,
|
|
||||||
"decomposition_prompt_version": null,
|
|
||||||
"prompt_fingerprint": null,
|
|
||||||
"agent_focus": "manual failures 11/13/14/26 from assistant-stage1-9liEOh-7JP",
|
|
||||||
"architecture_phase": "Post-F Semantic Integrity Hardening",
|
|
||||||
"source_spec_file": "X:\\1C\\NDC_1C\\docs\\orchestration\\address_truth_harness_post_f_manual_failures_20260424.json",
|
|
||||||
"scenario_id": "address_truth_harness_post_f_manual_failures_20260424",
|
|
||||||
"semantic_tags": [
|
|
||||||
"counterparty_grounding",
|
|
||||||
"counterparty_retarget",
|
|
||||||
"inventory_context",
|
|
||||||
"item_flow_failure_26",
|
|
||||||
"manual_9liEOh",
|
|
||||||
"materialization_gap",
|
|
||||||
"organization_scope",
|
|
||||||
"purchase_date_context",
|
|
||||||
"revenue_ranking_failure_14",
|
|
||||||
"selected_object_purchase",
|
|
||||||
"stale_inventory_scope",
|
|
||||||
"stale_scope_guard",
|
|
||||||
"vat_failure_11",
|
|
||||||
"vat_failure_13"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"source_session_id": null,
|
|
||||||
"session": {
|
|
||||||
"session_id": null,
|
|
||||||
"mode": "agent_semantic_run",
|
|
||||||
"items": [
|
|
||||||
{
|
|
||||||
"message_id": "agent-user-001",
|
|
||||||
"role": "user",
|
|
||||||
"text": "кайф - что там на складе по остаткам?",
|
|
||||||
"created_at": "2026-04-24T17:10:31+00:00",
|
|
||||||
"reply_type": null,
|
|
||||||
"trace_id": null,
|
|
||||||
"debug": null
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"message_id": "agent-user-002",
|
|
||||||
"role": "user",
|
|
||||||
"text": "АЛЬТЕРНАТИВА",
|
|
||||||
"created_at": "2026-04-24T17:10:31+00:00",
|
|
||||||
"reply_type": null,
|
|
||||||
"trace_id": null,
|
|
||||||
"debug": null
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"message_id": "agent-user-003",
|
|
||||||
"role": "user",
|
|
||||||
"text": "март 2016",
|
|
||||||
"created_at": "2026-04-24T17:10:31+00:00",
|
|
||||||
"reply_type": null,
|
|
||||||
"trace_id": null,
|
|
||||||
"debug": null
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"message_id": "agent-user-004",
|
|
||||||
"role": "user",
|
|
||||||
"text": "По выбранному объекту \"Рабочая станция универсального специалиста (индивидуальное изготовление)\": где взяли это?",
|
|
||||||
"created_at": "2026-04-24T17:10:31+00:00",
|
|
||||||
"reply_type": null,
|
|
||||||
"trace_id": null,
|
|
||||||
"debug": null
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"message_id": "agent-user-005",
|
|
||||||
"role": "user",
|
|
||||||
"text": "ндс можешь прикинуть на дату покупки рабочей станции?",
|
|
||||||
"created_at": "2026-04-24T17:10:31+00:00",
|
|
||||||
"reply_type": null,
|
|
||||||
"trace_id": null,
|
|
||||||
"debug": null
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"message_id": "agent-user-006",
|
|
||||||
"role": "user",
|
|
||||||
"text": "прикинь какой ндс нам надо заплатить на февраль 2017",
|
|
||||||
"created_at": "2026-04-24T17:10:31+00:00",
|
|
||||||
"reply_type": null,
|
|
||||||
"trace_id": null,
|
|
||||||
"debug": null
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"message_id": "agent-user-007",
|
|
||||||
"role": "user",
|
|
||||||
"text": "кто у нас самый доходный клиент за все время",
|
|
||||||
"created_at": "2026-04-24T17:10:31+00:00",
|
|
||||||
"reply_type": null,
|
|
||||||
"trace_id": null,
|
|
||||||
"debug": null
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"message_id": "agent-user-008",
|
|
||||||
"role": "user",
|
|
||||||
"text": "по чепурнову покажи все доки",
|
|
||||||
"created_at": "2026-04-24T17:10:31+00:00",
|
|
||||||
"reply_type": null,
|
|
||||||
"trace_id": null,
|
|
||||||
"debug": null
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"message_id": "agent-user-009",
|
|
||||||
"role": "user",
|
|
||||||
"text": "а по свк",
|
|
||||||
"created_at": "2026-04-24T17:10:31+00:00",
|
|
||||||
"reply_type": null,
|
|
||||||
"trace_id": null,
|
|
||||||
"debug": null
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"message_id": "agent-user-010",
|
|
||||||
"role": "user",
|
|
||||||
"text": "а сейчас у нас есть что на складе?",
|
|
||||||
"created_at": "2026-04-24T17:10:31+00:00",
|
|
||||||
"reply_type": null,
|
|
||||||
"trace_id": null,
|
|
||||||
"debug": null
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"message_id": "agent-user-011",
|
|
||||||
"role": "user",
|
|
||||||
"text": "что нам отгружал чепурнов? какой товар или услугу?",
|
|
||||||
"created_at": "2026-04-24T17:10:31+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": "manual failures 11/13/14/26 from assistant-stage1-9liEOh-7JP",
|
|
||||||
"architecture_phase": "Post-F Semantic Integrity Hardening",
|
|
||||||
"source_spec_file": "X:\\1C\\NDC_1C\\docs\\orchestration\\address_truth_harness_post_f_manual_failures_20260424.json",
|
|
||||||
"scenario_id": "address_truth_harness_post_f_manual_failures_20260424",
|
|
||||||
"semantic_tags": [
|
|
||||||
"counterparty_grounding",
|
|
||||||
"counterparty_retarget",
|
|
||||||
"inventory_context",
|
|
||||||
"item_flow_failure_26",
|
|
||||||
"manual_9liEOh",
|
|
||||||
"materialization_gap",
|
|
||||||
"organization_scope",
|
|
||||||
"purchase_date_context",
|
|
||||||
"revenue_ranking_failure_14",
|
|
||||||
"selected_object_purchase",
|
|
||||||
"stale_inventory_scope",
|
|
||||||
"stale_scope_guard",
|
|
||||||
"vat_failure_11",
|
|
||||||
"vat_failure_13"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,153 +0,0 @@
|
||||||
{
|
|
||||||
"saved_at": "2026-05-10T09:49:21+00:00",
|
|
||||||
"generation_id": "gen-ag05100949-2a5ed8",
|
|
||||||
"mode": "saved_user_sessions",
|
|
||||||
"title": "AGENT | Phase 91 route-candidate-driven enablement loop canary",
|
|
||||||
"agent_run": true,
|
|
||||||
"questions": [
|
|
||||||
"какой контрагент принес больше всего денег за 2020 год?",
|
|
||||||
"ООО Альтернатива Плюс",
|
|
||||||
"а теперь сколько лет мы работаем с Группа СВК?",
|
|
||||||
"покажи движения по НДС за 2020 по ООО Альтернатива Плюс",
|
|
||||||
"можно ли по этим данным точно подтвердить резерв под неликвиды на складе?"
|
|
||||||
],
|
|
||||||
"metadata": {
|
|
||||||
"assistant_prompt_version": null,
|
|
||||||
"decomposition_prompt_version": null,
|
|
||||||
"prompt_fingerprint": null,
|
|
||||||
"agent_focus": "route candidate repair-target grouping + handoff canary",
|
|
||||||
"architecture_phase": "Route-Candidate-Driven Enablement Loop",
|
|
||||||
"source_spec_file": "X:\\1C\\NDC_1C\\docs\\orchestration\\address_truth_harness_phase91_route_candidate_driven_enablement_loop.json",
|
|
||||||
"scenario_id": "address_truth_harness_phase91_route_candidate_driven_enablement_loop",
|
|
||||||
"semantic_tags": [
|
|
||||||
"clarification_recovery",
|
|
||||||
"counterparty_lifecycle",
|
|
||||||
"inventory_reserve_boundary",
|
|
||||||
"metadata_scope",
|
|
||||||
"movement_evidence",
|
|
||||||
"needs_user_scope",
|
|
||||||
"no_overclaim",
|
|
||||||
"open_organization_scope",
|
|
||||||
"ready_for_reviewed_execution",
|
|
||||||
"route_candidate_handoff",
|
|
||||||
"stale_scope_guard",
|
|
||||||
"unreviewed_proof_family",
|
|
||||||
"value_flow_ranking",
|
|
||||||
"vat"
|
|
||||||
],
|
|
||||||
"validation_status": "accepted_live_replay",
|
|
||||||
"validated_run_dir": "artifacts\\domain_runs\\address_truth_harness_phase91_route_candidate_driven_enablement_loop_live1_20260510",
|
|
||||||
"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\\address_truth_harness_phase91_route_candidate_driven_enablement_loop_live1_20260510",
|
|
||||||
"final_status": "accepted",
|
|
||||||
"review_overall_status": "pass",
|
|
||||||
"business_overall_status": "pass",
|
|
||||||
"steps_total": 5,
|
|
||||||
"steps_passed": 5,
|
|
||||||
"steps_failed": 0,
|
|
||||||
"steps_with_business_failures": 0,
|
|
||||||
"steps_with_business_warnings": 0,
|
|
||||||
"acceptance_gate_passed": true,
|
|
||||||
"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": "какой контрагент принес больше всего денег за 2020 год?",
|
|
||||||
"created_at": "2026-05-10T09:49:21+00:00",
|
|
||||||
"reply_type": null,
|
|
||||||
"trace_id": null,
|
|
||||||
"debug": null
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"message_id": "agent-user-002",
|
|
||||||
"role": "user",
|
|
||||||
"text": "ООО Альтернатива Плюс",
|
|
||||||
"created_at": "2026-05-10T09:49:21+00:00",
|
|
||||||
"reply_type": null,
|
|
||||||
"trace_id": null,
|
|
||||||
"debug": null
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"message_id": "agent-user-003",
|
|
||||||
"role": "user",
|
|
||||||
"text": "а теперь сколько лет мы работаем с Группа СВК?",
|
|
||||||
"created_at": "2026-05-10T09:49:21+00:00",
|
|
||||||
"reply_type": null,
|
|
||||||
"trace_id": null,
|
|
||||||
"debug": null
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"message_id": "agent-user-004",
|
|
||||||
"role": "user",
|
|
||||||
"text": "покажи движения по НДС за 2020 по ООО Альтернатива Плюс",
|
|
||||||
"created_at": "2026-05-10T09:49:21+00:00",
|
|
||||||
"reply_type": null,
|
|
||||||
"trace_id": null,
|
|
||||||
"debug": null
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"message_id": "agent-user-005",
|
|
||||||
"role": "user",
|
|
||||||
"text": "можно ли по этим данным точно подтвердить резерв под неликвиды на складе?",
|
|
||||||
"created_at": "2026-05-10T09:49:21+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": "route candidate repair-target grouping + handoff canary",
|
|
||||||
"architecture_phase": "Route-Candidate-Driven Enablement Loop",
|
|
||||||
"source_spec_file": "X:\\1C\\NDC_1C\\docs\\orchestration\\address_truth_harness_phase91_route_candidate_driven_enablement_loop.json",
|
|
||||||
"scenario_id": "address_truth_harness_phase91_route_candidate_driven_enablement_loop",
|
|
||||||
"semantic_tags": [
|
|
||||||
"clarification_recovery",
|
|
||||||
"counterparty_lifecycle",
|
|
||||||
"inventory_reserve_boundary",
|
|
||||||
"metadata_scope",
|
|
||||||
"movement_evidence",
|
|
||||||
"needs_user_scope",
|
|
||||||
"no_overclaim",
|
|
||||||
"open_organization_scope",
|
|
||||||
"ready_for_reviewed_execution",
|
|
||||||
"route_candidate_handoff",
|
|
||||||
"stale_scope_guard",
|
|
||||||
"unreviewed_proof_family",
|
|
||||||
"value_flow_ranking",
|
|
||||||
"vat"
|
|
||||||
],
|
|
||||||
"validation_status": "accepted_live_replay",
|
|
||||||
"validated_run_dir": "artifacts\\domain_runs\\address_truth_harness_phase91_route_candidate_driven_enablement_loop_live1_20260510",
|
|
||||||
"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\\address_truth_harness_phase91_route_candidate_driven_enablement_loop_live1_20260510",
|
|
||||||
"final_status": "accepted",
|
|
||||||
"review_overall_status": "pass",
|
|
||||||
"business_overall_status": "pass",
|
|
||||||
"steps_total": 5,
|
|
||||||
"steps_passed": 5,
|
|
||||||
"steps_failed": 0,
|
|
||||||
"steps_with_business_failures": 0,
|
|
||||||
"steps_with_business_warnings": 0,
|
|
||||||
"acceptance_gate_passed": true,
|
|
||||||
"saved_after_validated_replay": true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,157 +0,0 @@
|
||||||
{
|
|
||||||
"saved_at": "2026-05-10T12:13:26+00:00",
|
|
||||||
"generation_id": "gen-ag05101213-596d99",
|
|
||||||
"mode": "saved_user_sessions",
|
|
||||||
"title": "AGENT | Phase 93 accounting profit-margin reviewed route",
|
|
||||||
"agent_run": true,
|
|
||||||
"questions": [
|
|
||||||
"по ООО Альтернатива Плюс за 2020 можно точно сказать чистую прибыль и маржу?",
|
|
||||||
"а это прибыль или убыток, коротко?",
|
|
||||||
"по этой же компании на конец 2020 можно точно понять, какая дебиторка просрочена?",
|
|
||||||
"тогда НДС за 2020 по ООО Альтернатива Плюс какой?",
|
|
||||||
"а какой контрагент принес больше всего денег за 2020?",
|
|
||||||
"по ООО Альтернатива Плюс на конец 2020 можно точно подтвердить резерв под неликвиды на складе?"
|
|
||||||
],
|
|
||||||
"metadata": {
|
|
||||||
"assistant_prompt_version": null,
|
|
||||||
"decomposition_prompt_version": null,
|
|
||||||
"prompt_fingerprint": null,
|
|
||||||
"agent_focus": "accounting profit-margin reviewed route",
|
|
||||||
"architecture_phase": "Route-Candidate-Driven Enablement Loop",
|
|
||||||
"source_spec_file": "X:\\1C\\NDC_1C\\docs\\orchestration\\address_truth_harness_phase93_accounting_profit_margin_reviewed_route.json",
|
|
||||||
"scenario_id": "address_truth_harness_phase93_accounting_profit_margin_reviewed_route",
|
|
||||||
"semantic_tags": [
|
|
||||||
"accounting_profit_margin",
|
|
||||||
"business_overview",
|
|
||||||
"context_carryover",
|
|
||||||
"debt_due_date_boundary",
|
|
||||||
"inventory_reserve_boundary",
|
|
||||||
"missing_proof_families",
|
|
||||||
"needs_route_enablement",
|
|
||||||
"profit_margin_boundary",
|
|
||||||
"ready_for_reviewed_execution",
|
|
||||||
"value_flow_ranking",
|
|
||||||
"vat_continuity"
|
|
||||||
],
|
|
||||||
"validation_status": "accepted_live_replay",
|
|
||||||
"validated_run_dir": "artifacts\\domain_runs\\address_truth_harness_phase93_accounting_profit_margin_reviewed_route_live3_20260510",
|
|
||||||
"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\\address_truth_harness_phase93_accounting_profit_margin_reviewed_route_live3_20260510",
|
|
||||||
"final_status": "accepted",
|
|
||||||
"review_overall_status": "pass",
|
|
||||||
"business_overall_status": "pass",
|
|
||||||
"steps_total": 6,
|
|
||||||
"steps_passed": 6,
|
|
||||||
"steps_failed": 0,
|
|
||||||
"steps_with_business_failures": 0,
|
|
||||||
"steps_with_business_warnings": 0,
|
|
||||||
"acceptance_gate_passed": true,
|
|
||||||
"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": "по ООО Альтернатива Плюс за 2020 можно точно сказать чистую прибыль и маржу?",
|
|
||||||
"created_at": "2026-05-10T12:13:26+00:00",
|
|
||||||
"reply_type": null,
|
|
||||||
"trace_id": null,
|
|
||||||
"debug": null
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"message_id": "agent-user-002",
|
|
||||||
"role": "user",
|
|
||||||
"text": "а это прибыль или убыток, коротко?",
|
|
||||||
"created_at": "2026-05-10T12:13:26+00:00",
|
|
||||||
"reply_type": null,
|
|
||||||
"trace_id": null,
|
|
||||||
"debug": null
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"message_id": "agent-user-003",
|
|
||||||
"role": "user",
|
|
||||||
"text": "по этой же компании на конец 2020 можно точно понять, какая дебиторка просрочена?",
|
|
||||||
"created_at": "2026-05-10T12:13:26+00:00",
|
|
||||||
"reply_type": null,
|
|
||||||
"trace_id": null,
|
|
||||||
"debug": null
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"message_id": "agent-user-004",
|
|
||||||
"role": "user",
|
|
||||||
"text": "тогда НДС за 2020 по ООО Альтернатива Плюс какой?",
|
|
||||||
"created_at": "2026-05-10T12:13:26+00:00",
|
|
||||||
"reply_type": null,
|
|
||||||
"trace_id": null,
|
|
||||||
"debug": null
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"message_id": "agent-user-005",
|
|
||||||
"role": "user",
|
|
||||||
"text": "а какой контрагент принес больше всего денег за 2020?",
|
|
||||||
"created_at": "2026-05-10T12:13:26+00:00",
|
|
||||||
"reply_type": null,
|
|
||||||
"trace_id": null,
|
|
||||||
"debug": null
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"message_id": "agent-user-006",
|
|
||||||
"role": "user",
|
|
||||||
"text": "по ООО Альтернатива Плюс на конец 2020 можно точно подтвердить резерв под неликвиды на складе?",
|
|
||||||
"created_at": "2026-05-10T12:13:26+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": "accounting profit-margin reviewed route",
|
|
||||||
"architecture_phase": "Route-Candidate-Driven Enablement Loop",
|
|
||||||
"source_spec_file": "X:\\1C\\NDC_1C\\docs\\orchestration\\address_truth_harness_phase93_accounting_profit_margin_reviewed_route.json",
|
|
||||||
"scenario_id": "address_truth_harness_phase93_accounting_profit_margin_reviewed_route",
|
|
||||||
"semantic_tags": [
|
|
||||||
"accounting_profit_margin",
|
|
||||||
"business_overview",
|
|
||||||
"context_carryover",
|
|
||||||
"debt_due_date_boundary",
|
|
||||||
"inventory_reserve_boundary",
|
|
||||||
"missing_proof_families",
|
|
||||||
"needs_route_enablement",
|
|
||||||
"profit_margin_boundary",
|
|
||||||
"ready_for_reviewed_execution",
|
|
||||||
"value_flow_ranking",
|
|
||||||
"vat_continuity"
|
|
||||||
],
|
|
||||||
"validation_status": "accepted_live_replay",
|
|
||||||
"validated_run_dir": "artifacts\\domain_runs\\address_truth_harness_phase93_accounting_profit_margin_reviewed_route_live3_20260510",
|
|
||||||
"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\\address_truth_harness_phase93_accounting_profit_margin_reviewed_route_live3_20260510",
|
|
||||||
"final_status": "accepted",
|
|
||||||
"review_overall_status": "pass",
|
|
||||||
"business_overall_status": "pass",
|
|
||||||
"steps_total": 6,
|
|
||||||
"steps_passed": 6,
|
|
||||||
"steps_failed": 0,
|
|
||||||
"steps_with_business_failures": 0,
|
|
||||||
"steps_with_business_warnings": 0,
|
|
||||||
"acceptance_gate_passed": true,
|
|
||||||
"saved_after_validated_replay": true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,173 +0,0 @@
|
||||||
{
|
|
||||||
"saved_at": "2026-05-10T13:19:22+00:00",
|
|
||||||
"generation_id": "gen-ag05101319-c04f79",
|
|
||||||
"mode": "saved_user_sessions",
|
|
||||||
"title": "AGENT | Phase 94 debt due-date aging reviewed route",
|
|
||||||
"agent_run": true,
|
|
||||||
"questions": [
|
|
||||||
"по ООО Альтернатива Плюс на конец 2020 можно точно понять, какая дебиторка просрочена?",
|
|
||||||
"то есть просрочку доказать нельзя, коротко почему?",
|
|
||||||
"а чистая прибыль и маржа за 2020 по этой же компании какие?",
|
|
||||||
"НДС за 2020 по ООО Альтернатива Плюс какой?",
|
|
||||||
"а кто принес больше всего денег за 2020?",
|
|
||||||
"по ООО Альтернатива Плюс на конец 2020 можно точно подтвердить резерв под неликвиды на складе?",
|
|
||||||
"а зависимость от одного поставщика за 2020 можно точно оценить?"
|
|
||||||
],
|
|
||||||
"metadata": {
|
|
||||||
"assistant_prompt_version": null,
|
|
||||||
"decomposition_prompt_version": null,
|
|
||||||
"prompt_fingerprint": null,
|
|
||||||
"agent_focus": "Focused semantic replay for promoting debt_due_date_aging_quality from proxy-only enablement to a reviewed payment-term/open-balance route while preserving profit, VAT, inventory, and vendor-risk boundaries.",
|
|
||||||
"architecture_phase": "turnaround_11",
|
|
||||||
"source_spec_file": "X:\\1C\\NDC_1C\\docs\\orchestration\\address_truth_harness_phase94_debt_due_date_aging_reviewed_route.json",
|
|
||||||
"scenario_id": "address_truth_harness_phase94_debt_due_date_aging_reviewed_route",
|
|
||||||
"semantic_tags": [
|
|
||||||
"accounting_profit_margin",
|
|
||||||
"business_overview",
|
|
||||||
"canary",
|
|
||||||
"checked_negative",
|
|
||||||
"context_carryover",
|
|
||||||
"debt_due_date_aging_quality",
|
|
||||||
"debt_due_date_boundary",
|
|
||||||
"inventory_reserve_boundary",
|
|
||||||
"missing_proof_families",
|
|
||||||
"profit_margin_boundary",
|
|
||||||
"ready_for_reviewed_execution",
|
|
||||||
"value_flow_ranking",
|
|
||||||
"vat_continuity",
|
|
||||||
"vendor_risk_procurement_boundary"
|
|
||||||
],
|
|
||||||
"validation_status": "accepted_live_replay",
|
|
||||||
"validated_run_dir": "artifacts\\domain_runs\\phase94_debt_due_date_aging_reviewed_route_live4",
|
|
||||||
"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\\phase94_debt_due_date_aging_reviewed_route_live4",
|
|
||||||
"final_status": "accepted",
|
|
||||||
"review_overall_status": "pass",
|
|
||||||
"business_overall_status": "pass",
|
|
||||||
"steps_total": 7,
|
|
||||||
"steps_passed": 7,
|
|
||||||
"steps_failed": 0,
|
|
||||||
"steps_with_business_failures": 0,
|
|
||||||
"steps_with_business_warnings": 0,
|
|
||||||
"acceptance_gate_passed": true,
|
|
||||||
"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": "по ООО Альтернатива Плюс на конец 2020 можно точно понять, какая дебиторка просрочена?",
|
|
||||||
"created_at": "2026-05-10T13:19:22+00:00",
|
|
||||||
"reply_type": null,
|
|
||||||
"trace_id": null,
|
|
||||||
"debug": null
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"message_id": "agent-user-002",
|
|
||||||
"role": "user",
|
|
||||||
"text": "то есть просрочку доказать нельзя, коротко почему?",
|
|
||||||
"created_at": "2026-05-10T13:19:22+00:00",
|
|
||||||
"reply_type": null,
|
|
||||||
"trace_id": null,
|
|
||||||
"debug": null
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"message_id": "agent-user-003",
|
|
||||||
"role": "user",
|
|
||||||
"text": "а чистая прибыль и маржа за 2020 по этой же компании какие?",
|
|
||||||
"created_at": "2026-05-10T13:19:22+00:00",
|
|
||||||
"reply_type": null,
|
|
||||||
"trace_id": null,
|
|
||||||
"debug": null
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"message_id": "agent-user-004",
|
|
||||||
"role": "user",
|
|
||||||
"text": "НДС за 2020 по ООО Альтернатива Плюс какой?",
|
|
||||||
"created_at": "2026-05-10T13:19:22+00:00",
|
|
||||||
"reply_type": null,
|
|
||||||
"trace_id": null,
|
|
||||||
"debug": null
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"message_id": "agent-user-005",
|
|
||||||
"role": "user",
|
|
||||||
"text": "а кто принес больше всего денег за 2020?",
|
|
||||||
"created_at": "2026-05-10T13:19:22+00:00",
|
|
||||||
"reply_type": null,
|
|
||||||
"trace_id": null,
|
|
||||||
"debug": null
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"message_id": "agent-user-006",
|
|
||||||
"role": "user",
|
|
||||||
"text": "по ООО Альтернатива Плюс на конец 2020 можно точно подтвердить резерв под неликвиды на складе?",
|
|
||||||
"created_at": "2026-05-10T13:19:22+00:00",
|
|
||||||
"reply_type": null,
|
|
||||||
"trace_id": null,
|
|
||||||
"debug": null
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"message_id": "agent-user-007",
|
|
||||||
"role": "user",
|
|
||||||
"text": "а зависимость от одного поставщика за 2020 можно точно оценить?",
|
|
||||||
"created_at": "2026-05-10T13:19:22+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": "Focused semantic replay for promoting debt_due_date_aging_quality from proxy-only enablement to a reviewed payment-term/open-balance route while preserving profit, VAT, inventory, and vendor-risk boundaries.",
|
|
||||||
"architecture_phase": "turnaround_11",
|
|
||||||
"source_spec_file": "X:\\1C\\NDC_1C\\docs\\orchestration\\address_truth_harness_phase94_debt_due_date_aging_reviewed_route.json",
|
|
||||||
"scenario_id": "address_truth_harness_phase94_debt_due_date_aging_reviewed_route",
|
|
||||||
"semantic_tags": [
|
|
||||||
"accounting_profit_margin",
|
|
||||||
"business_overview",
|
|
||||||
"canary",
|
|
||||||
"checked_negative",
|
|
||||||
"context_carryover",
|
|
||||||
"debt_due_date_aging_quality",
|
|
||||||
"debt_due_date_boundary",
|
|
||||||
"inventory_reserve_boundary",
|
|
||||||
"missing_proof_families",
|
|
||||||
"profit_margin_boundary",
|
|
||||||
"ready_for_reviewed_execution",
|
|
||||||
"value_flow_ranking",
|
|
||||||
"vat_continuity",
|
|
||||||
"vendor_risk_procurement_boundary"
|
|
||||||
],
|
|
||||||
"validation_status": "accepted_live_replay",
|
|
||||||
"validated_run_dir": "artifacts\\domain_runs\\phase94_debt_due_date_aging_reviewed_route_live4",
|
|
||||||
"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\\phase94_debt_due_date_aging_reviewed_route_live4",
|
|
||||||
"final_status": "accepted",
|
|
||||||
"review_overall_status": "pass",
|
|
||||||
"business_overall_status": "pass",
|
|
||||||
"steps_total": 7,
|
|
||||||
"steps_passed": 7,
|
|
||||||
"steps_failed": 0,
|
|
||||||
"steps_with_business_failures": 0,
|
|
||||||
"steps_with_business_warnings": 0,
|
|
||||||
"acceptance_gate_passed": true,
|
|
||||||
"saved_after_validated_replay": true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,173 +0,0 @@
|
||||||
{
|
|
||||||
"saved_at": "2026-05-12T13:57:26+00:00",
|
|
||||||
"generation_id": "gen-ag05121357-9ea5d6",
|
|
||||||
"mode": "saved_user_sessions",
|
|
||||||
"title": "AGENT | Phase 95 vendor/procurement quality reviewed route",
|
|
||||||
"agent_run": true,
|
|
||||||
"questions": [
|
|
||||||
"по ООО Альтернатива Плюс за 2020 есть ли риск, что мы зависим от одного поставщика?",
|
|
||||||
"а банк из этого ответа считать обычным поставщиком?",
|
|
||||||
"по этой же компании на конец 2020 можно точно понять, какая дебиторка просрочена?",
|
|
||||||
"а чистая прибыль и маржа за 2020 какие?",
|
|
||||||
"НДС за 2020 по ООО Альтернатива Плюс какой?",
|
|
||||||
"а кто принес больше всего денег за 2020?",
|
|
||||||
"по ООО Альтернатива Плюс на конец 2020 можно точно подтвердить резерв под неликвиды на складе?"
|
|
||||||
],
|
|
||||||
"metadata": {
|
|
||||||
"assistant_prompt_version": null,
|
|
||||||
"decomposition_prompt_version": null,
|
|
||||||
"prompt_fingerprint": null,
|
|
||||||
"agent_focus": "Focused semantic replay for promoting vendor_risk_procurement_quality from proxy-only enablement to reviewed procurement-concentration evidence while preserving debt, profit, VAT, value-flow, and inventory reserve canaries.",
|
|
||||||
"architecture_phase": "turnaround_11",
|
|
||||||
"source_spec_file": "X:\\1C\\NDC_1C\\docs\\orchestration\\address_truth_harness_phase95_vendor_procurement_quality_reviewed_route.json",
|
|
||||||
"scenario_id": "address_truth_harness_phase95_vendor_procurement_quality_reviewed_route",
|
|
||||||
"semantic_tags": [
|
|
||||||
"accounting_profit_margin",
|
|
||||||
"business_overview",
|
|
||||||
"canary",
|
|
||||||
"context_carryover",
|
|
||||||
"debt_due_date_boundary",
|
|
||||||
"financial_institution_boundary",
|
|
||||||
"inventory_reserve_boundary",
|
|
||||||
"missing_proof_families",
|
|
||||||
"profit_margin_boundary",
|
|
||||||
"ready_for_reviewed_execution",
|
|
||||||
"value_flow_ranking",
|
|
||||||
"vat_continuity",
|
|
||||||
"vendor_risk_procurement_boundary",
|
|
||||||
"vendor_risk_procurement_quality"
|
|
||||||
],
|
|
||||||
"validation_status": "accepted_live_replay",
|
|
||||||
"validated_run_dir": "artifacts\\domain_runs\\phase95_vendor_procurement_quality_reviewed_route_live2",
|
|
||||||
"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\\phase95_vendor_procurement_quality_reviewed_route_live2",
|
|
||||||
"final_status": "accepted",
|
|
||||||
"review_overall_status": "pass",
|
|
||||||
"business_overall_status": "pass",
|
|
||||||
"steps_total": 7,
|
|
||||||
"steps_passed": 7,
|
|
||||||
"steps_failed": 0,
|
|
||||||
"steps_with_business_failures": 0,
|
|
||||||
"steps_with_business_warnings": 0,
|
|
||||||
"acceptance_gate_passed": true,
|
|
||||||
"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": "по ООО Альтернатива Плюс за 2020 есть ли риск, что мы зависим от одного поставщика?",
|
|
||||||
"created_at": "2026-05-12T13:57:26+00:00",
|
|
||||||
"reply_type": null,
|
|
||||||
"trace_id": null,
|
|
||||||
"debug": null
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"message_id": "agent-user-002",
|
|
||||||
"role": "user",
|
|
||||||
"text": "а банк из этого ответа считать обычным поставщиком?",
|
|
||||||
"created_at": "2026-05-12T13:57:26+00:00",
|
|
||||||
"reply_type": null,
|
|
||||||
"trace_id": null,
|
|
||||||
"debug": null
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"message_id": "agent-user-003",
|
|
||||||
"role": "user",
|
|
||||||
"text": "по этой же компании на конец 2020 можно точно понять, какая дебиторка просрочена?",
|
|
||||||
"created_at": "2026-05-12T13:57:26+00:00",
|
|
||||||
"reply_type": null,
|
|
||||||
"trace_id": null,
|
|
||||||
"debug": null
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"message_id": "agent-user-004",
|
|
||||||
"role": "user",
|
|
||||||
"text": "а чистая прибыль и маржа за 2020 какие?",
|
|
||||||
"created_at": "2026-05-12T13:57:26+00:00",
|
|
||||||
"reply_type": null,
|
|
||||||
"trace_id": null,
|
|
||||||
"debug": null
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"message_id": "agent-user-005",
|
|
||||||
"role": "user",
|
|
||||||
"text": "НДС за 2020 по ООО Альтернатива Плюс какой?",
|
|
||||||
"created_at": "2026-05-12T13:57:26+00:00",
|
|
||||||
"reply_type": null,
|
|
||||||
"trace_id": null,
|
|
||||||
"debug": null
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"message_id": "agent-user-006",
|
|
||||||
"role": "user",
|
|
||||||
"text": "а кто принес больше всего денег за 2020?",
|
|
||||||
"created_at": "2026-05-12T13:57:26+00:00",
|
|
||||||
"reply_type": null,
|
|
||||||
"trace_id": null,
|
|
||||||
"debug": null
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"message_id": "agent-user-007",
|
|
||||||
"role": "user",
|
|
||||||
"text": "по ООО Альтернатива Плюс на конец 2020 можно точно подтвердить резерв под неликвиды на складе?",
|
|
||||||
"created_at": "2026-05-12T13:57:26+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": "Focused semantic replay for promoting vendor_risk_procurement_quality from proxy-only enablement to reviewed procurement-concentration evidence while preserving debt, profit, VAT, value-flow, and inventory reserve canaries.",
|
|
||||||
"architecture_phase": "turnaround_11",
|
|
||||||
"source_spec_file": "X:\\1C\\NDC_1C\\docs\\orchestration\\address_truth_harness_phase95_vendor_procurement_quality_reviewed_route.json",
|
|
||||||
"scenario_id": "address_truth_harness_phase95_vendor_procurement_quality_reviewed_route",
|
|
||||||
"semantic_tags": [
|
|
||||||
"accounting_profit_margin",
|
|
||||||
"business_overview",
|
|
||||||
"canary",
|
|
||||||
"context_carryover",
|
|
||||||
"debt_due_date_boundary",
|
|
||||||
"financial_institution_boundary",
|
|
||||||
"inventory_reserve_boundary",
|
|
||||||
"missing_proof_families",
|
|
||||||
"profit_margin_boundary",
|
|
||||||
"ready_for_reviewed_execution",
|
|
||||||
"value_flow_ranking",
|
|
||||||
"vat_continuity",
|
|
||||||
"vendor_risk_procurement_boundary",
|
|
||||||
"vendor_risk_procurement_quality"
|
|
||||||
],
|
|
||||||
"validation_status": "accepted_live_replay",
|
|
||||||
"validated_run_dir": "artifacts\\domain_runs\\phase95_vendor_procurement_quality_reviewed_route_live2",
|
|
||||||
"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\\phase95_vendor_procurement_quality_reviewed_route_live2",
|
|
||||||
"final_status": "accepted",
|
|
||||||
"review_overall_status": "pass",
|
|
||||||
"business_overall_status": "pass",
|
|
||||||
"steps_total": 7,
|
|
||||||
"steps_passed": 7,
|
|
||||||
"steps_failed": 0,
|
|
||||||
"steps_with_business_failures": 0,
|
|
||||||
"steps_with_business_warnings": 0,
|
|
||||||
"acceptance_gate_passed": true,
|
|
||||||
"saved_after_validated_replay": true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,155 +0,0 @@
|
||||||
{
|
|
||||||
"saved_at": "2026-05-12T16:28:41+00:00",
|
|
||||||
"generation_id": "gen-ag05121628-50ea6c",
|
|
||||||
"mode": "saved_user_sessions",
|
|
||||||
"title": "AGENT | Phase20 continuity/ranking/self-scope accepted replay 2026-05-12",
|
|
||||||
"agent_run": true,
|
|
||||||
"questions": [
|
|
||||||
"кто у нас самый доходный клиент за все время?",
|
|
||||||
"какой у нас самый доходный год?",
|
|
||||||
"кто нам должен денег на май 2017?",
|
|
||||||
"а какой ндс мы должны примерно заплатить за этот период?",
|
|
||||||
"мы должны комуто денег на сегодня?",
|
|
||||||
"а нам?"
|
|
||||||
],
|
|
||||||
"metadata": {
|
|
||||||
"assistant_prompt_version": null,
|
|
||||||
"decomposition_prompt_version": null,
|
|
||||||
"prompt_fingerprint": null,
|
|
||||||
"agent_focus": "ranking catalog alignment, self-scope pronoun, compact debt answers",
|
|
||||||
"architecture_phase": "MCP автономка / semantic integrity",
|
|
||||||
"source_spec_file": "X:\\1C\\NDC_1C\\docs\\orchestration\\address_truth_harness_phase20_continuity_stabilization.json",
|
|
||||||
"scenario_id": "address_truth_harness_phase20_continuity_stabilization",
|
|
||||||
"semantic_tags": [
|
|
||||||
"exact_not_overwritten",
|
|
||||||
"garbage_anchor_forbidden",
|
|
||||||
"payables_snapshot",
|
|
||||||
"period_carryover",
|
|
||||||
"pronoun_followup",
|
|
||||||
"receivables_snapshot",
|
|
||||||
"temporal_tail_not_entity",
|
|
||||||
"value_flow_ranking",
|
|
||||||
"vat_followup",
|
|
||||||
"year_tail_not_entity"
|
|
||||||
],
|
|
||||||
"validation_status": "accepted_live_replay",
|
|
||||||
"validated_run_dir": "artifacts\\domain_runs\\phase20_continuity_stabilization_rerun_planner_ranking_fix",
|
|
||||||
"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\\phase20_continuity_stabilization_rerun_planner_ranking_fix",
|
|
||||||
"final_status": "accepted",
|
|
||||||
"review_overall_status": "pass",
|
|
||||||
"business_overall_status": "pass",
|
|
||||||
"steps_total": 6,
|
|
||||||
"steps_passed": 6,
|
|
||||||
"steps_failed": 0,
|
|
||||||
"steps_with_business_failures": 0,
|
|
||||||
"steps_with_business_warnings": 0,
|
|
||||||
"acceptance_gate_passed": true,
|
|
||||||
"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-05-12T16:28:41+00:00",
|
|
||||||
"reply_type": null,
|
|
||||||
"trace_id": null,
|
|
||||||
"debug": null
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"message_id": "agent-user-002",
|
|
||||||
"role": "user",
|
|
||||||
"text": "какой у нас самый доходный год?",
|
|
||||||
"created_at": "2026-05-12T16:28:41+00:00",
|
|
||||||
"reply_type": null,
|
|
||||||
"trace_id": null,
|
|
||||||
"debug": null
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"message_id": "agent-user-003",
|
|
||||||
"role": "user",
|
|
||||||
"text": "кто нам должен денег на май 2017?",
|
|
||||||
"created_at": "2026-05-12T16:28:41+00:00",
|
|
||||||
"reply_type": null,
|
|
||||||
"trace_id": null,
|
|
||||||
"debug": null
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"message_id": "agent-user-004",
|
|
||||||
"role": "user",
|
|
||||||
"text": "а какой ндс мы должны примерно заплатить за этот период?",
|
|
||||||
"created_at": "2026-05-12T16:28:41+00:00",
|
|
||||||
"reply_type": null,
|
|
||||||
"trace_id": null,
|
|
||||||
"debug": null
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"message_id": "agent-user-005",
|
|
||||||
"role": "user",
|
|
||||||
"text": "мы должны комуто денег на сегодня?",
|
|
||||||
"created_at": "2026-05-12T16:28:41+00:00",
|
|
||||||
"reply_type": null,
|
|
||||||
"trace_id": null,
|
|
||||||
"debug": null
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"message_id": "agent-user-006",
|
|
||||||
"role": "user",
|
|
||||||
"text": "а нам?",
|
|
||||||
"created_at": "2026-05-12T16:28:41+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": "ranking catalog alignment, self-scope pronoun, compact debt answers",
|
|
||||||
"architecture_phase": "MCP автономка / semantic integrity",
|
|
||||||
"source_spec_file": "X:\\1C\\NDC_1C\\docs\\orchestration\\address_truth_harness_phase20_continuity_stabilization.json",
|
|
||||||
"scenario_id": "address_truth_harness_phase20_continuity_stabilization",
|
|
||||||
"semantic_tags": [
|
|
||||||
"exact_not_overwritten",
|
|
||||||
"garbage_anchor_forbidden",
|
|
||||||
"payables_snapshot",
|
|
||||||
"period_carryover",
|
|
||||||
"pronoun_followup",
|
|
||||||
"receivables_snapshot",
|
|
||||||
"temporal_tail_not_entity",
|
|
||||||
"value_flow_ranking",
|
|
||||||
"vat_followup",
|
|
||||||
"year_tail_not_entity"
|
|
||||||
],
|
|
||||||
"validation_status": "accepted_live_replay",
|
|
||||||
"validated_run_dir": "artifacts\\domain_runs\\phase20_continuity_stabilization_rerun_planner_ranking_fix",
|
|
||||||
"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\\phase20_continuity_stabilization_rerun_planner_ranking_fix",
|
|
||||||
"final_status": "accepted",
|
|
||||||
"review_overall_status": "pass",
|
|
||||||
"business_overall_status": "pass",
|
|
||||||
"steps_total": 6,
|
|
||||||
"steps_passed": 6,
|
|
||||||
"steps_failed": 0,
|
|
||||||
"steps_with_business_failures": 0,
|
|
||||||
"steps_with_business_warnings": 0,
|
|
||||||
"acceptance_gate_passed": true,
|
|
||||||
"saved_after_validated_replay": true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,109 +0,0 @@
|
||||||
{
|
|
||||||
"saved_at": "2026-05-12T20:57:28+00:00",
|
|
||||||
"generation_id": "gen-ag05122057-c9786e",
|
|
||||||
"mode": "saved_user_sessions",
|
|
||||||
"title": "AGENT | Phase 96 inventory reserve/liquidation quality-events",
|
|
||||||
"agent_run": true,
|
|
||||||
"questions": [
|
|
||||||
"По ООО Альтернатива Плюс за 2020 год проверь склад: были ли списания товаров, оприходования/корректировки, инвентаризации, переоценки, резервы под неликвиды или ликвидационная стоимость? Скажи коротко и честно, что подтверждено 1С, а что нельзя утверждать.",
|
|
||||||
"А по этим же данным можно сказать, что склад ликвидный и неликвидов нет?"
|
|
||||||
],
|
|
||||||
"metadata": {
|
|
||||||
"assistant_prompt_version": null,
|
|
||||||
"decomposition_prompt_version": null,
|
|
||||||
"prompt_fingerprint": null,
|
|
||||||
"agent_focus": "inventory_reserve_liquidation_quality",
|
|
||||||
"architecture_phase": "Route-Candidate-Driven Enablement Loop",
|
|
||||||
"source_spec_file": "X:\\1C\\NDC_1C\\docs\\orchestration\\address_truth_harness_phase96_inventory_reserve_liquidation_quality.json",
|
|
||||||
"scenario_id": "address_truth_harness_phase96_inventory_reserve_liquidation_quality",
|
|
||||||
"semantic_tags": [
|
|
||||||
"direct_answer_first",
|
|
||||||
"followup_context_carryover",
|
|
||||||
"inventory_liquidity_boundary",
|
|
||||||
"inventory_reserve_liquidation_quality",
|
|
||||||
"no_inventory_health_overclaim",
|
|
||||||
"no_market_liquidation_overclaim",
|
|
||||||
"reviewed_inventory_quality_events"
|
|
||||||
],
|
|
||||||
"validation_status": "accepted_live_replay",
|
|
||||||
"validated_run_dir": "artifacts\\domain_runs\\phase96_inventory_reserve_liquidation_quality_rerun",
|
|
||||||
"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\\phase96_inventory_reserve_liquidation_quality_rerun",
|
|
||||||
"final_status": "accepted",
|
|
||||||
"review_overall_status": "pass",
|
|
||||||
"business_overall_status": "pass",
|
|
||||||
"steps_total": 2,
|
|
||||||
"steps_passed": 2,
|
|
||||||
"steps_failed": 0,
|
|
||||||
"steps_with_business_failures": 0,
|
|
||||||
"steps_with_business_warnings": 0,
|
|
||||||
"acceptance_gate_passed": true,
|
|
||||||
"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": "По ООО Альтернатива Плюс за 2020 год проверь склад: были ли списания товаров, оприходования/корректировки, инвентаризации, переоценки, резервы под неликвиды или ликвидационная стоимость? Скажи коротко и честно, что подтверждено 1С, а что нельзя утверждать.",
|
|
||||||
"created_at": "2026-05-12T20:57:28+00:00",
|
|
||||||
"reply_type": null,
|
|
||||||
"trace_id": null,
|
|
||||||
"debug": null
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"message_id": "agent-user-002",
|
|
||||||
"role": "user",
|
|
||||||
"text": "А по этим же данным можно сказать, что склад ликвидный и неликвидов нет?",
|
|
||||||
"created_at": "2026-05-12T20:57: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": "inventory_reserve_liquidation_quality",
|
|
||||||
"architecture_phase": "Route-Candidate-Driven Enablement Loop",
|
|
||||||
"source_spec_file": "X:\\1C\\NDC_1C\\docs\\orchestration\\address_truth_harness_phase96_inventory_reserve_liquidation_quality.json",
|
|
||||||
"scenario_id": "address_truth_harness_phase96_inventory_reserve_liquidation_quality",
|
|
||||||
"semantic_tags": [
|
|
||||||
"direct_answer_first",
|
|
||||||
"followup_context_carryover",
|
|
||||||
"inventory_liquidity_boundary",
|
|
||||||
"inventory_reserve_liquidation_quality",
|
|
||||||
"no_inventory_health_overclaim",
|
|
||||||
"no_market_liquidation_overclaim",
|
|
||||||
"reviewed_inventory_quality_events"
|
|
||||||
],
|
|
||||||
"validation_status": "accepted_live_replay",
|
|
||||||
"validated_run_dir": "artifacts\\domain_runs\\phase96_inventory_reserve_liquidation_quality_rerun",
|
|
||||||
"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\\phase96_inventory_reserve_liquidation_quality_rerun",
|
|
||||||
"final_status": "accepted",
|
|
||||||
"review_overall_status": "pass",
|
|
||||||
"business_overall_status": "pass",
|
|
||||||
"steps_total": 2,
|
|
||||||
"steps_passed": 2,
|
|
||||||
"steps_failed": 0,
|
|
||||||
"steps_with_business_failures": 0,
|
|
||||||
"steps_with_business_warnings": 0,
|
|
||||||
"acceptance_gate_passed": true,
|
|
||||||
"saved_after_validated_replay": true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,135 +0,0 @@
|
||||||
{
|
|
||||||
"saved_at": "2026-05-12T22:50:23+00:00",
|
|
||||||
"generation_id": "gen-ag05122250-4451a8",
|
|
||||||
"mode": "saved_user_sessions",
|
|
||||||
"title": "AGENT | Phase 97 financial counterparty flow hints replay",
|
|
||||||
"agent_run": true,
|
|
||||||
"questions": [
|
|
||||||
"По ООО Альтернатива Плюс за 2020 отдельно посмотри платежи в СБЕРБАНК: это обычный поставщик или банковский/финансовый поток? Дай коротко и по проверенным данным 1С.",
|
|
||||||
"А если по этой же компании СБЕРБАНК встречается во входящих поступлениях, это клиентская выручка или там может быть кредитный/депозитный банковский смысл? Не притягивай, скажи что подтверждено.",
|
|
||||||
"Теперь дай взрослый краткий обзор ООО Альтернатива Плюс за 2020: входящие, исходящие, нетто и отдельно отметь, если в топах есть банк, почему его нельзя читать как обычного клиента или поставщика.",
|
|
||||||
"А теперь отдельно по Группа СВК за 2020: сколько денег получили, сколько заплатили и какое нетто?"
|
|
||||||
],
|
|
||||||
"metadata": {
|
|
||||||
"assistant_prompt_version": null,
|
|
||||||
"decomposition_prompt_version": null,
|
|
||||||
"prompt_fingerprint": null,
|
|
||||||
"agent_focus": "Focused semantic replay for the Open-World Schema/Primitive Discovery slice: bank-like counterparties must not be described as ordinary suppliers/customers when operation, payment purpose, contract, or comment fields indicate banking commission, credit, deposit, tax/budget, or payroll-like flows. The replay also keeps a normal counterparty value-flow canary.",
|
|
||||||
"architecture_phase": "turnaround_11",
|
|
||||||
"source_spec_file": "X:\\1C\\NDC_1C\\docs\\orchestration\\address_truth_harness_phase97_financial_counterparty_flow_hints.json",
|
|
||||||
"scenario_id": "address_truth_harness_phase97_financial_counterparty_flow_hints",
|
|
||||||
"semantic_tags": [
|
|
||||||
"bank_like_customer_boundary",
|
|
||||||
"bank_like_supplier_boundary",
|
|
||||||
"business_overview",
|
|
||||||
"canary",
|
|
||||||
"counterparty_net_cash_flow",
|
|
||||||
"customer_revenue_and_payments",
|
|
||||||
"financial_counterparty_flow_hint",
|
|
||||||
"profit_margin_boundary",
|
|
||||||
"stale_scope_guard",
|
|
||||||
"supplier_payouts_profile"
|
|
||||||
],
|
|
||||||
"validation_status": "accepted_live_replay",
|
|
||||||
"validated_run_dir": "artifacts\\domain_runs\\phase97_financial_counterparty_flow_hints_live4",
|
|
||||||
"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\\phase97_financial_counterparty_flow_hints_live4",
|
|
||||||
"final_status": "accepted",
|
|
||||||
"review_overall_status": "pass",
|
|
||||||
"business_overall_status": "pass",
|
|
||||||
"steps_total": 4,
|
|
||||||
"steps_passed": 4,
|
|
||||||
"steps_failed": 0,
|
|
||||||
"steps_with_business_failures": 0,
|
|
||||||
"steps_with_business_warnings": 0,
|
|
||||||
"acceptance_gate_passed": true,
|
|
||||||
"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": "По ООО Альтернатива Плюс за 2020 отдельно посмотри платежи в СБЕРБАНК: это обычный поставщик или банковский/финансовый поток? Дай коротко и по проверенным данным 1С.",
|
|
||||||
"created_at": "2026-05-12T22:50:23+00:00",
|
|
||||||
"reply_type": null,
|
|
||||||
"trace_id": null,
|
|
||||||
"debug": null
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"message_id": "agent-user-002",
|
|
||||||
"role": "user",
|
|
||||||
"text": "А если по этой же компании СБЕРБАНК встречается во входящих поступлениях, это клиентская выручка или там может быть кредитный/депозитный банковский смысл? Не притягивай, скажи что подтверждено.",
|
|
||||||
"created_at": "2026-05-12T22:50:23+00:00",
|
|
||||||
"reply_type": null,
|
|
||||||
"trace_id": null,
|
|
||||||
"debug": null
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"message_id": "agent-user-003",
|
|
||||||
"role": "user",
|
|
||||||
"text": "Теперь дай взрослый краткий обзор ООО Альтернатива Плюс за 2020: входящие, исходящие, нетто и отдельно отметь, если в топах есть банк, почему его нельзя читать как обычного клиента или поставщика.",
|
|
||||||
"created_at": "2026-05-12T22:50:23+00:00",
|
|
||||||
"reply_type": null,
|
|
||||||
"trace_id": null,
|
|
||||||
"debug": null
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"message_id": "agent-user-004",
|
|
||||||
"role": "user",
|
|
||||||
"text": "А теперь отдельно по Группа СВК за 2020: сколько денег получили, сколько заплатили и какое нетто?",
|
|
||||||
"created_at": "2026-05-12T22:50:23+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": "Focused semantic replay for the Open-World Schema/Primitive Discovery slice: bank-like counterparties must not be described as ordinary suppliers/customers when operation, payment purpose, contract, or comment fields indicate banking commission, credit, deposit, tax/budget, or payroll-like flows. The replay also keeps a normal counterparty value-flow canary.",
|
|
||||||
"architecture_phase": "turnaround_11",
|
|
||||||
"source_spec_file": "X:\\1C\\NDC_1C\\docs\\orchestration\\address_truth_harness_phase97_financial_counterparty_flow_hints.json",
|
|
||||||
"scenario_id": "address_truth_harness_phase97_financial_counterparty_flow_hints",
|
|
||||||
"semantic_tags": [
|
|
||||||
"bank_like_customer_boundary",
|
|
||||||
"bank_like_supplier_boundary",
|
|
||||||
"business_overview",
|
|
||||||
"canary",
|
|
||||||
"counterparty_net_cash_flow",
|
|
||||||
"customer_revenue_and_payments",
|
|
||||||
"financial_counterparty_flow_hint",
|
|
||||||
"profit_margin_boundary",
|
|
||||||
"stale_scope_guard",
|
|
||||||
"supplier_payouts_profile"
|
|
||||||
],
|
|
||||||
"validation_status": "accepted_live_replay",
|
|
||||||
"validated_run_dir": "artifacts\\domain_runs\\phase97_financial_counterparty_flow_hints_live4",
|
|
||||||
"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\\phase97_financial_counterparty_flow_hints_live4",
|
|
||||||
"final_status": "accepted",
|
|
||||||
"review_overall_status": "pass",
|
|
||||||
"business_overall_status": "pass",
|
|
||||||
"steps_total": 4,
|
|
||||||
"steps_passed": 4,
|
|
||||||
"steps_failed": 0,
|
|
||||||
"steps_with_business_failures": 0,
|
|
||||||
"steps_with_business_warnings": 0,
|
|
||||||
"acceptance_gate_passed": true,
|
|
||||||
"saved_after_validated_replay": true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,155 +0,0 @@
|
||||||
{
|
|
||||||
"saved_at": "2026-05-12T23:15:48+00:00",
|
|
||||||
"generation_id": "gen-ag05122315-f1e27c",
|
|
||||||
"mode": "saved_user_sessions",
|
|
||||||
"title": "AGENT | Phase 98 limit honesty and business-language replay",
|
|
||||||
"agent_run": true,
|
|
||||||
"questions": [
|
|
||||||
"По ООО Альтернатива Плюс на конец 2020 можно точно понять, какая дебиторка просрочена?",
|
|
||||||
"То есть просрочку доказать нельзя, коротко почему?",
|
|
||||||
"НДС за 2020 по ООО Альтернатива Плюс какой?",
|
|
||||||
"А кто принес больше всего денег за 2020?",
|
|
||||||
"По ООО Альтернатива Плюс на конец 2020 можно точно подтвердить резерв под неликвиды на складе?",
|
|
||||||
"А зависимость от одного поставщика за 2020 можно точно оценить?"
|
|
||||||
],
|
|
||||||
"metadata": {
|
|
||||||
"assistant_prompt_version": null,
|
|
||||||
"decomposition_prompt_version": null,
|
|
||||||
"prompt_fingerprint": null,
|
|
||||||
"agent_focus": "Focused semantic replay from assistant-stage1-v2qsm_R0fF: answers may be bounded, but they must stay business-readable, direct-first, and must not leak MCP/proxy/route/debug wording when explaining row limits, incomplete coverage, debt due-date proof, inventory reserve proof, supplier dependency, VAT, or bank-like counterparties.",
|
|
||||||
"architecture_phase": "turnaround_11",
|
|
||||||
"source_spec_file": "X:\\1C\\NDC_1C\\docs\\orchestration\\address_truth_harness_phase98_limit_honesty_business_language.json",
|
|
||||||
"scenario_id": "address_truth_harness_phase98_limit_honesty_business_language",
|
|
||||||
"semantic_tags": [
|
|
||||||
"business_language",
|
|
||||||
"customer_revenue_and_payments",
|
|
||||||
"debt_due_date_aging_quality",
|
|
||||||
"debug_leak_guard",
|
|
||||||
"financial_counterparty_flow_hint",
|
|
||||||
"followup_directness",
|
|
||||||
"inventory_reserve_liquidation_quality",
|
|
||||||
"limit_honesty",
|
|
||||||
"vat",
|
|
||||||
"vendor_risk_procurement_quality"
|
|
||||||
],
|
|
||||||
"validation_status": "accepted_live_replay",
|
|
||||||
"validated_run_dir": "artifacts\\domain_runs\\phase98_limit_honesty_business_language_live3",
|
|
||||||
"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\\phase98_limit_honesty_business_language_live3",
|
|
||||||
"final_status": "accepted",
|
|
||||||
"review_overall_status": "pass",
|
|
||||||
"business_overall_status": "pass",
|
|
||||||
"steps_total": 6,
|
|
||||||
"steps_passed": 6,
|
|
||||||
"steps_failed": 0,
|
|
||||||
"steps_with_business_failures": 0,
|
|
||||||
"steps_with_business_warnings": 0,
|
|
||||||
"acceptance_gate_passed": true,
|
|
||||||
"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": "По ООО Альтернатива Плюс на конец 2020 можно точно понять, какая дебиторка просрочена?",
|
|
||||||
"created_at": "2026-05-12T23:15:48+00:00",
|
|
||||||
"reply_type": null,
|
|
||||||
"trace_id": null,
|
|
||||||
"debug": null
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"message_id": "agent-user-002",
|
|
||||||
"role": "user",
|
|
||||||
"text": "То есть просрочку доказать нельзя, коротко почему?",
|
|
||||||
"created_at": "2026-05-12T23:15:48+00:00",
|
|
||||||
"reply_type": null,
|
|
||||||
"trace_id": null,
|
|
||||||
"debug": null
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"message_id": "agent-user-003",
|
|
||||||
"role": "user",
|
|
||||||
"text": "НДС за 2020 по ООО Альтернатива Плюс какой?",
|
|
||||||
"created_at": "2026-05-12T23:15:48+00:00",
|
|
||||||
"reply_type": null,
|
|
||||||
"trace_id": null,
|
|
||||||
"debug": null
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"message_id": "agent-user-004",
|
|
||||||
"role": "user",
|
|
||||||
"text": "А кто принес больше всего денег за 2020?",
|
|
||||||
"created_at": "2026-05-12T23:15:48+00:00",
|
|
||||||
"reply_type": null,
|
|
||||||
"trace_id": null,
|
|
||||||
"debug": null
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"message_id": "agent-user-005",
|
|
||||||
"role": "user",
|
|
||||||
"text": "По ООО Альтернатива Плюс на конец 2020 можно точно подтвердить резерв под неликвиды на складе?",
|
|
||||||
"created_at": "2026-05-12T23:15:48+00:00",
|
|
||||||
"reply_type": null,
|
|
||||||
"trace_id": null,
|
|
||||||
"debug": null
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"message_id": "agent-user-006",
|
|
||||||
"role": "user",
|
|
||||||
"text": "А зависимость от одного поставщика за 2020 можно точно оценить?",
|
|
||||||
"created_at": "2026-05-12T23:15:48+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": "Focused semantic replay from assistant-stage1-v2qsm_R0fF: answers may be bounded, but they must stay business-readable, direct-first, and must not leak MCP/proxy/route/debug wording when explaining row limits, incomplete coverage, debt due-date proof, inventory reserve proof, supplier dependency, VAT, or bank-like counterparties.",
|
|
||||||
"architecture_phase": "turnaround_11",
|
|
||||||
"source_spec_file": "X:\\1C\\NDC_1C\\docs\\orchestration\\address_truth_harness_phase98_limit_honesty_business_language.json",
|
|
||||||
"scenario_id": "address_truth_harness_phase98_limit_honesty_business_language",
|
|
||||||
"semantic_tags": [
|
|
||||||
"business_language",
|
|
||||||
"customer_revenue_and_payments",
|
|
||||||
"debt_due_date_aging_quality",
|
|
||||||
"debug_leak_guard",
|
|
||||||
"financial_counterparty_flow_hint",
|
|
||||||
"followup_directness",
|
|
||||||
"inventory_reserve_liquidation_quality",
|
|
||||||
"limit_honesty",
|
|
||||||
"vat",
|
|
||||||
"vendor_risk_procurement_quality"
|
|
||||||
],
|
|
||||||
"validation_status": "accepted_live_replay",
|
|
||||||
"validated_run_dir": "artifacts\\domain_runs\\phase98_limit_honesty_business_language_live3",
|
|
||||||
"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\\phase98_limit_honesty_business_language_live3",
|
|
||||||
"final_status": "accepted",
|
|
||||||
"review_overall_status": "pass",
|
|
||||||
"business_overall_status": "pass",
|
|
||||||
"steps_total": 6,
|
|
||||||
"steps_passed": 6,
|
|
||||||
"steps_failed": 0,
|
|
||||||
"steps_with_business_failures": 0,
|
|
||||||
"steps_with_business_warnings": 0,
|
|
||||||
"acceptance_gate_passed": true,
|
|
||||||
"saved_after_validated_replay": true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,133 +0,0 @@
|
||||||
{
|
|
||||||
"saved_at": "2026-05-13T10:09:01+00:00",
|
|
||||||
"generation_id": "gen-ag05131009-f08174",
|
|
||||||
"mode": "saved_user_sessions",
|
|
||||||
"title": "AGENT | Phase 99 large-query budget and continuation policy replay",
|
|
||||||
"agent_run": true,
|
|
||||||
"questions": [
|
|
||||||
"Дай взрослый бизнес-обзор ООО Альтернатива Плюс за 2020: входящие, исходящие, нетто, кто основные источники денег и где важные ограничения.",
|
|
||||||
"То есть это можно считать прибылью за 2020 или нет? Коротко.",
|
|
||||||
"А кто за 2020 принес больше всего денег, и если там банк, не называй его обычным клиентом.",
|
|
||||||
"По исходящим за 2020 есть зависимость от одного поставщика или это только денежная концентрация?"
|
|
||||||
],
|
|
||||||
"metadata": {
|
|
||||||
"assistant_prompt_version": null,
|
|
||||||
"decomposition_prompt_version": null,
|
|
||||||
"prompt_fingerprint": null,
|
|
||||||
"agent_focus": "Focused semantic replay for explicit-year large business questions: the assistant should use chunked 1C evidence where available, answer direct-first, avoid fake profit claims, keep bank/counterparty boundaries, and not collapse into row-limit refusal wording when yearly money coverage can be recovered.",
|
|
||||||
"architecture_phase": "turnaround_11",
|
|
||||||
"source_spec_file": "X:\\1C\\NDC_1C\\docs\\orchestration\\address_truth_harness_phase99_large_query_budget_continuation.json",
|
|
||||||
"scenario_id": "address_truth_harness_phase99_large_query_budget_continuation",
|
|
||||||
"semantic_tags": [
|
|
||||||
"business_language",
|
|
||||||
"business_overview",
|
|
||||||
"context_continuity",
|
|
||||||
"direct_answer_first",
|
|
||||||
"financial_counterparty_flow_hint",
|
|
||||||
"followup_directness",
|
|
||||||
"large_query_budget",
|
|
||||||
"profit_boundary",
|
|
||||||
"vendor_risk_procurement_quality"
|
|
||||||
],
|
|
||||||
"validation_status": "accepted_live_replay",
|
|
||||||
"validated_run_dir": "artifacts\\domain_runs\\phase99_large_query_budget_continuation_live2",
|
|
||||||
"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\\phase99_large_query_budget_continuation_live2",
|
|
||||||
"final_status": "accepted",
|
|
||||||
"review_overall_status": "pass",
|
|
||||||
"business_overall_status": "pass",
|
|
||||||
"steps_total": 4,
|
|
||||||
"steps_passed": 4,
|
|
||||||
"steps_failed": 0,
|
|
||||||
"steps_with_business_failures": 0,
|
|
||||||
"steps_with_business_warnings": 0,
|
|
||||||
"acceptance_gate_passed": true,
|
|
||||||
"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": "Дай взрослый бизнес-обзор ООО Альтернатива Плюс за 2020: входящие, исходящие, нетто, кто основные источники денег и где важные ограничения.",
|
|
||||||
"created_at": "2026-05-13T10:09:01+00:00",
|
|
||||||
"reply_type": null,
|
|
||||||
"trace_id": null,
|
|
||||||
"debug": null
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"message_id": "agent-user-002",
|
|
||||||
"role": "user",
|
|
||||||
"text": "То есть это можно считать прибылью за 2020 или нет? Коротко.",
|
|
||||||
"created_at": "2026-05-13T10:09:01+00:00",
|
|
||||||
"reply_type": null,
|
|
||||||
"trace_id": null,
|
|
||||||
"debug": null
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"message_id": "agent-user-003",
|
|
||||||
"role": "user",
|
|
||||||
"text": "А кто за 2020 принес больше всего денег, и если там банк, не называй его обычным клиентом.",
|
|
||||||
"created_at": "2026-05-13T10:09:01+00:00",
|
|
||||||
"reply_type": null,
|
|
||||||
"trace_id": null,
|
|
||||||
"debug": null
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"message_id": "agent-user-004",
|
|
||||||
"role": "user",
|
|
||||||
"text": "По исходящим за 2020 есть зависимость от одного поставщика или это только денежная концентрация?",
|
|
||||||
"created_at": "2026-05-13T10:09:01+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": "Focused semantic replay for explicit-year large business questions: the assistant should use chunked 1C evidence where available, answer direct-first, avoid fake profit claims, keep bank/counterparty boundaries, and not collapse into row-limit refusal wording when yearly money coverage can be recovered.",
|
|
||||||
"architecture_phase": "turnaround_11",
|
|
||||||
"source_spec_file": "X:\\1C\\NDC_1C\\docs\\orchestration\\address_truth_harness_phase99_large_query_budget_continuation.json",
|
|
||||||
"scenario_id": "address_truth_harness_phase99_large_query_budget_continuation",
|
|
||||||
"semantic_tags": [
|
|
||||||
"business_language",
|
|
||||||
"business_overview",
|
|
||||||
"context_continuity",
|
|
||||||
"direct_answer_first",
|
|
||||||
"financial_counterparty_flow_hint",
|
|
||||||
"followup_directness",
|
|
||||||
"large_query_budget",
|
|
||||||
"profit_boundary",
|
|
||||||
"vendor_risk_procurement_quality"
|
|
||||||
],
|
|
||||||
"validation_status": "accepted_live_replay",
|
|
||||||
"validated_run_dir": "artifacts\\domain_runs\\phase99_large_query_budget_continuation_live2",
|
|
||||||
"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\\phase99_large_query_budget_continuation_live2",
|
|
||||||
"final_status": "accepted",
|
|
||||||
"review_overall_status": "pass",
|
|
||||||
"business_overall_status": "pass",
|
|
||||||
"steps_total": 4,
|
|
||||||
"steps_passed": 4,
|
|
||||||
"steps_failed": 0,
|
|
||||||
"steps_with_business_failures": 0,
|
|
||||||
"steps_with_business_warnings": 0,
|
|
||||||
"acceptance_gate_passed": true,
|
|
||||||
"saved_after_validated_replay": true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,121 +0,0 @@
|
||||||
{
|
|
||||||
"saved_at": "2026-05-13T10:28:45+00:00",
|
|
||||||
"generation_id": "gen-ag05131028-234e5e",
|
|
||||||
"mode": "saved_user_sessions",
|
|
||||||
"title": "AGENT | Phase 100 large-query continuation UX replay",
|
|
||||||
"agent_run": true,
|
|
||||||
"questions": [
|
|
||||||
"Дай общий бизнес-обзор ООО Альтернатива Плюс за весь доступный период: входящие, исходящие, нетто, лучший год. Если срез слишком широкий, не выдумывай полный итог, а скажи как безопасно дособрать.",
|
|
||||||
"Ок, тогда дособери конкретно 2020: входящие, исходящие и расчетное денежное нетто.",
|
|
||||||
"Это можно считать прибылью за 2020 или нет? Ответь коротко и по делу."
|
|
||||||
],
|
|
||||||
"metadata": {
|
|
||||||
"assistant_prompt_version": null,
|
|
||||||
"decomposition_prompt_version": null,
|
|
||||||
"prompt_fingerprint": null,
|
|
||||||
"agent_focus": "Focused semantic replay for all-time or very broad business overview questions: the assistant should answer from checked evidence, disclose row-limit coverage honestly, offer a safe period-narrowing continuation path, and then recover the explicit-year follow-up through chunked evidence without leaking technical mechanics.",
|
|
||||||
"architecture_phase": "turnaround_11",
|
|
||||||
"source_spec_file": "X:\\1C\\NDC_1C\\docs\\orchestration\\address_truth_harness_phase100_large_query_continuation_ux.json",
|
|
||||||
"scenario_id": "address_truth_harness_phase100_large_query_continuation_ux",
|
|
||||||
"semantic_tags": [
|
|
||||||
"business_language",
|
|
||||||
"business_overview",
|
|
||||||
"followup_continuation",
|
|
||||||
"followup_directness",
|
|
||||||
"large_query_budget",
|
|
||||||
"large_query_continuation",
|
|
||||||
"limit_honesty",
|
|
||||||
"profit_boundary"
|
|
||||||
],
|
|
||||||
"validation_status": "accepted_live_replay",
|
|
||||||
"validated_run_dir": "artifacts\\domain_runs\\phase100_large_query_continuation_ux_live2",
|
|
||||||
"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\\phase100_large_query_continuation_ux_live2",
|
|
||||||
"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,
|
|
||||||
"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-05-13T10:28:45+00:00",
|
|
||||||
"reply_type": null,
|
|
||||||
"trace_id": null,
|
|
||||||
"debug": null
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"message_id": "agent-user-002",
|
|
||||||
"role": "user",
|
|
||||||
"text": "Ок, тогда дособери конкретно 2020: входящие, исходящие и расчетное денежное нетто.",
|
|
||||||
"created_at": "2026-05-13T10:28:45+00:00",
|
|
||||||
"reply_type": null,
|
|
||||||
"trace_id": null,
|
|
||||||
"debug": null
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"message_id": "agent-user-003",
|
|
||||||
"role": "user",
|
|
||||||
"text": "Это можно считать прибылью за 2020 или нет? Ответь коротко и по делу.",
|
|
||||||
"created_at": "2026-05-13T10:28:45+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": "Focused semantic replay for all-time or very broad business overview questions: the assistant should answer from checked evidence, disclose row-limit coverage honestly, offer a safe period-narrowing continuation path, and then recover the explicit-year follow-up through chunked evidence without leaking technical mechanics.",
|
|
||||||
"architecture_phase": "turnaround_11",
|
|
||||||
"source_spec_file": "X:\\1C\\NDC_1C\\docs\\orchestration\\address_truth_harness_phase100_large_query_continuation_ux.json",
|
|
||||||
"scenario_id": "address_truth_harness_phase100_large_query_continuation_ux",
|
|
||||||
"semantic_tags": [
|
|
||||||
"business_language",
|
|
||||||
"business_overview",
|
|
||||||
"followup_continuation",
|
|
||||||
"followup_directness",
|
|
||||||
"large_query_budget",
|
|
||||||
"large_query_continuation",
|
|
||||||
"limit_honesty",
|
|
||||||
"profit_boundary"
|
|
||||||
],
|
|
||||||
"validation_status": "accepted_live_replay",
|
|
||||||
"validated_run_dir": "artifacts\\domain_runs\\phase100_large_query_continuation_ux_live2",
|
|
||||||
"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\\phase100_large_query_continuation_ux_live2",
|
|
||||||
"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,
|
|
||||||
"saved_after_validated_replay": true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,165 +0,0 @@
|
||||||
{
|
|
||||||
"saved_at": "2026-05-13T10:44:38+00:00",
|
|
||||||
"generation_id": "gen-ag05131044-cbe2ff",
|
|
||||||
"mode": "saved_user_sessions",
|
|
||||||
"title": "AGENT | Phase 101 inventory root scope without warehouse clarification replay",
|
|
||||||
"agent_run": true,
|
|
||||||
"questions": [
|
|
||||||
"приветик - че как там дела",
|
|
||||||
"расскажи что можешь интересного",
|
|
||||||
"кайф - что там на складе по остаткам?",
|
|
||||||
"АЛЬТЕРНАТИВА",
|
|
||||||
"а исторические остатки на другие даты умеешь?",
|
|
||||||
"давай на июнь 2017",
|
|
||||||
"март 2016"
|
|
||||||
],
|
|
||||||
"metadata": {
|
|
||||||
"assistant_prompt_version": null,
|
|
||||||
"decomposition_prompt_version": null,
|
|
||||||
"prompt_fingerprint": null,
|
|
||||||
"agent_focus": "Focused semantic replay for the manual assistant-stage1-hyh1A1WR3j signal: after a broad stock-on-hand question and organization clarification, the assistant must resume the root inventory snapshot for the selected company across available warehouses instead of asking the user to name a warehouse, item, category, or material.",
|
|
||||||
"architecture_phase": "turnaround_11",
|
|
||||||
"source_spec_file": "X:\\1C\\NDC_1C\\docs\\orchestration\\address_truth_harness_phase101_inventory_root_scope_no_warehouse_clarification.json",
|
|
||||||
"scenario_id": "address_truth_harness_phase101_inventory_root_scope_no_warehouse_clarification",
|
|
||||||
"semantic_tags": [
|
|
||||||
"capability_meta",
|
|
||||||
"clarification_required",
|
|
||||||
"clarification_resume",
|
|
||||||
"date_followup",
|
|
||||||
"historical_inventory",
|
|
||||||
"human_answer_quality",
|
|
||||||
"inventory_capability_meta",
|
|
||||||
"inventory_root",
|
|
||||||
"smalltalk_entry",
|
|
||||||
"warehouse_not_required"
|
|
||||||
],
|
|
||||||
"validation_status": "accepted_live_replay",
|
|
||||||
"validated_run_dir": "artifacts\\domain_runs\\phase101_inventory_root_scope_no_warehouse_clarification_live1",
|
|
||||||
"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\\phase101_inventory_root_scope_no_warehouse_clarification_live1",
|
|
||||||
"final_status": "accepted",
|
|
||||||
"review_overall_status": "pass",
|
|
||||||
"business_overall_status": "pass",
|
|
||||||
"steps_total": 7,
|
|
||||||
"steps_passed": 7,
|
|
||||||
"steps_failed": 0,
|
|
||||||
"steps_with_business_failures": 0,
|
|
||||||
"steps_with_business_warnings": 0,
|
|
||||||
"acceptance_gate_passed": true,
|
|
||||||
"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-05-13T10:44:38+00:00",
|
|
||||||
"reply_type": null,
|
|
||||||
"trace_id": null,
|
|
||||||
"debug": null
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"message_id": "agent-user-002",
|
|
||||||
"role": "user",
|
|
||||||
"text": "расскажи что можешь интересного",
|
|
||||||
"created_at": "2026-05-13T10:44:38+00:00",
|
|
||||||
"reply_type": null,
|
|
||||||
"trace_id": null,
|
|
||||||
"debug": null
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"message_id": "agent-user-003",
|
|
||||||
"role": "user",
|
|
||||||
"text": "кайф - что там на складе по остаткам?",
|
|
||||||
"created_at": "2026-05-13T10:44:38+00:00",
|
|
||||||
"reply_type": null,
|
|
||||||
"trace_id": null,
|
|
||||||
"debug": null
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"message_id": "agent-user-004",
|
|
||||||
"role": "user",
|
|
||||||
"text": "АЛЬТЕРНАТИВА",
|
|
||||||
"created_at": "2026-05-13T10:44:38+00:00",
|
|
||||||
"reply_type": null,
|
|
||||||
"trace_id": null,
|
|
||||||
"debug": null
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"message_id": "agent-user-005",
|
|
||||||
"role": "user",
|
|
||||||
"text": "а исторические остатки на другие даты умеешь?",
|
|
||||||
"created_at": "2026-05-13T10:44:38+00:00",
|
|
||||||
"reply_type": null,
|
|
||||||
"trace_id": null,
|
|
||||||
"debug": null
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"message_id": "agent-user-006",
|
|
||||||
"role": "user",
|
|
||||||
"text": "давай на июнь 2017",
|
|
||||||
"created_at": "2026-05-13T10:44:38+00:00",
|
|
||||||
"reply_type": null,
|
|
||||||
"trace_id": null,
|
|
||||||
"debug": null
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"message_id": "agent-user-007",
|
|
||||||
"role": "user",
|
|
||||||
"text": "март 2016",
|
|
||||||
"created_at": "2026-05-13T10:44:38+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": "Focused semantic replay for the manual assistant-stage1-hyh1A1WR3j signal: after a broad stock-on-hand question and organization clarification, the assistant must resume the root inventory snapshot for the selected company across available warehouses instead of asking the user to name a warehouse, item, category, or material.",
|
|
||||||
"architecture_phase": "turnaround_11",
|
|
||||||
"source_spec_file": "X:\\1C\\NDC_1C\\docs\\orchestration\\address_truth_harness_phase101_inventory_root_scope_no_warehouse_clarification.json",
|
|
||||||
"scenario_id": "address_truth_harness_phase101_inventory_root_scope_no_warehouse_clarification",
|
|
||||||
"semantic_tags": [
|
|
||||||
"capability_meta",
|
|
||||||
"clarification_required",
|
|
||||||
"clarification_resume",
|
|
||||||
"date_followup",
|
|
||||||
"historical_inventory",
|
|
||||||
"human_answer_quality",
|
|
||||||
"inventory_capability_meta",
|
|
||||||
"inventory_root",
|
|
||||||
"smalltalk_entry",
|
|
||||||
"warehouse_not_required"
|
|
||||||
],
|
|
||||||
"validation_status": "accepted_live_replay",
|
|
||||||
"validated_run_dir": "artifacts\\domain_runs\\phase101_inventory_root_scope_no_warehouse_clarification_live1",
|
|
||||||
"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\\phase101_inventory_root_scope_no_warehouse_clarification_live1",
|
|
||||||
"final_status": "accepted",
|
|
||||||
"review_overall_status": "pass",
|
|
||||||
"business_overall_status": "pass",
|
|
||||||
"steps_total": 7,
|
|
||||||
"steps_passed": 7,
|
|
||||||
"steps_failed": 0,
|
|
||||||
"steps_with_business_failures": 0,
|
|
||||||
"steps_with_business_warnings": 0,
|
|
||||||
"acceptance_gate_passed": true,
|
|
||||||
"saved_after_validated_replay": true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,155 +0,0 @@
|
||||||
{
|
|
||||||
"saved_at": "2026-05-13T11:21:52+00:00",
|
|
||||||
"generation_id": "gen-ag05131121-8c41ab",
|
|
||||||
"mode": "saved_user_sessions",
|
|
||||||
"title": "AGENT | Phase 102 debt mirror clean-scope polarity replay",
|
|
||||||
"agent_run": true,
|
|
||||||
"questions": [
|
|
||||||
"Альтернатива Плюс",
|
|
||||||
"мы кому реально должны денег на сегодня? коротко, чистый долг к оплате, без встречных обеспечений как основного долга",
|
|
||||||
"а Комитету государственных услуг мы реально должны эти 3,6 млн или это встречное обеспечение/зачет? объясни именно по смыслу долга",
|
|
||||||
"а нам Комитет государственных услуг тоже должен 3,6 млн? это же та же сумма — скажи честно, это дебиторка или встречная часть?",
|
|
||||||
"тогда кто нам реально должен денег на сегодня? именно чистая дебиторка",
|
|
||||||
"а мы кому?"
|
|
||||||
],
|
|
||||||
"metadata": {
|
|
||||||
"assistant_prompt_version": null,
|
|
||||||
"decomposition_prompt_version": null,
|
|
||||||
"prompt_fingerprint": null,
|
|
||||||
"agent_focus": "phase102 debt mirror clean-scope polarity",
|
|
||||||
"architecture_phase": "Open-World Schema/Primitive Discovery",
|
|
||||||
"source_spec_file": "X:\\1C\\NDC_1C\\docs\\orchestration\\address_truth_harness_phase102_debt_mirror_clean_scope_polarity.json",
|
|
||||||
"scenario_id": "address_truth_harness_phase102_debt_mirror_clean_scope_polarity",
|
|
||||||
"semantic_tags": [
|
|
||||||
"company_selected",
|
|
||||||
"debt_mirror_clean_scope",
|
|
||||||
"direct_answer_first",
|
|
||||||
"organization_scope",
|
|
||||||
"payables_counterparty_check",
|
|
||||||
"payables_snapshot",
|
|
||||||
"polarity_honesty",
|
|
||||||
"receivables_counterparty_check",
|
|
||||||
"receivables_snapshot",
|
|
||||||
"settlements_mirror_followup"
|
|
||||||
],
|
|
||||||
"validation_status": "accepted_live_replay",
|
|
||||||
"validated_run_dir": "artifacts\\domain_runs\\phase102_debt_mirror_clean_scope_polarity_live3",
|
|
||||||
"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\\phase102_debt_mirror_clean_scope_polarity_live3",
|
|
||||||
"final_status": "accepted",
|
|
||||||
"review_overall_status": "pass",
|
|
||||||
"business_overall_status": "pass",
|
|
||||||
"steps_total": 6,
|
|
||||||
"steps_passed": 6,
|
|
||||||
"steps_failed": 0,
|
|
||||||
"steps_with_business_failures": 0,
|
|
||||||
"steps_with_business_warnings": 0,
|
|
||||||
"acceptance_gate_passed": true,
|
|
||||||
"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-05-13T11:21:52+00:00",
|
|
||||||
"reply_type": null,
|
|
||||||
"trace_id": null,
|
|
||||||
"debug": null
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"message_id": "agent-user-002",
|
|
||||||
"role": "user",
|
|
||||||
"text": "мы кому реально должны денег на сегодня? коротко, чистый долг к оплате, без встречных обеспечений как основного долга",
|
|
||||||
"created_at": "2026-05-13T11:21:52+00:00",
|
|
||||||
"reply_type": null,
|
|
||||||
"trace_id": null,
|
|
||||||
"debug": null
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"message_id": "agent-user-003",
|
|
||||||
"role": "user",
|
|
||||||
"text": "а Комитету государственных услуг мы реально должны эти 3,6 млн или это встречное обеспечение/зачет? объясни именно по смыслу долга",
|
|
||||||
"created_at": "2026-05-13T11:21:52+00:00",
|
|
||||||
"reply_type": null,
|
|
||||||
"trace_id": null,
|
|
||||||
"debug": null
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"message_id": "agent-user-004",
|
|
||||||
"role": "user",
|
|
||||||
"text": "а нам Комитет государственных услуг тоже должен 3,6 млн? это же та же сумма — скажи честно, это дебиторка или встречная часть?",
|
|
||||||
"created_at": "2026-05-13T11:21:52+00:00",
|
|
||||||
"reply_type": null,
|
|
||||||
"trace_id": null,
|
|
||||||
"debug": null
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"message_id": "agent-user-005",
|
|
||||||
"role": "user",
|
|
||||||
"text": "тогда кто нам реально должен денег на сегодня? именно чистая дебиторка",
|
|
||||||
"created_at": "2026-05-13T11:21:52+00:00",
|
|
||||||
"reply_type": null,
|
|
||||||
"trace_id": null,
|
|
||||||
"debug": null
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"message_id": "agent-user-006",
|
|
||||||
"role": "user",
|
|
||||||
"text": "а мы кому?",
|
|
||||||
"created_at": "2026-05-13T11:21:52+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": "phase102 debt mirror clean-scope polarity",
|
|
||||||
"architecture_phase": "Open-World Schema/Primitive Discovery",
|
|
||||||
"source_spec_file": "X:\\1C\\NDC_1C\\docs\\orchestration\\address_truth_harness_phase102_debt_mirror_clean_scope_polarity.json",
|
|
||||||
"scenario_id": "address_truth_harness_phase102_debt_mirror_clean_scope_polarity",
|
|
||||||
"semantic_tags": [
|
|
||||||
"company_selected",
|
|
||||||
"debt_mirror_clean_scope",
|
|
||||||
"direct_answer_first",
|
|
||||||
"organization_scope",
|
|
||||||
"payables_counterparty_check",
|
|
||||||
"payables_snapshot",
|
|
||||||
"polarity_honesty",
|
|
||||||
"receivables_counterparty_check",
|
|
||||||
"receivables_snapshot",
|
|
||||||
"settlements_mirror_followup"
|
|
||||||
],
|
|
||||||
"validation_status": "accepted_live_replay",
|
|
||||||
"validated_run_dir": "artifacts\\domain_runs\\phase102_debt_mirror_clean_scope_polarity_live3",
|
|
||||||
"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\\phase102_debt_mirror_clean_scope_polarity_live3",
|
|
||||||
"final_status": "accepted",
|
|
||||||
"review_overall_status": "pass",
|
|
||||||
"business_overall_status": "pass",
|
|
||||||
"steps_total": 6,
|
|
||||||
"steps_passed": 6,
|
|
||||||
"steps_failed": 0,
|
|
||||||
"steps_with_business_failures": 0,
|
|
||||||
"steps_with_business_warnings": 0,
|
|
||||||
"acceptance_gate_passed": true,
|
|
||||||
"saved_after_validated_replay": true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,161 +0,0 @@
|
||||||
{
|
|
||||||
"saved_at": "2026-05-13T12:00:47+00:00",
|
|
||||||
"generation_id": "gen-ag05131200-0ed59a",
|
|
||||||
"mode": "saved_user_sessions",
|
|
||||||
"title": "AGENT | Phase 103 financial role and purpose arbitration replay",
|
|
||||||
"agent_run": true,
|
|
||||||
"questions": [
|
|
||||||
"Альтернатива Плюс",
|
|
||||||
"По СБЕРБАНКУ за 2020 покажи коротко: сколько денег входило и уходило, и что это по смыслу в 1С — клиентская выручка, поставщик, комиссия, кредит или другой финансовый поток?",
|
|
||||||
"Если СБЕРБАНК есть во входящих поступлениях, можно ли считать его нашим клиентом и выручкой? Скажи по подтвержденным строкам, без притягивания.",
|
|
||||||
"А если деньги уходили в СБЕРБАНК, это наш поставщик или финансовые списания? Раздели по смыслу и покажи основание.",
|
|
||||||
"Теперь дай взрослый обзор за 2020 по компании: входящие, исходящие, нетто, топы, но банк в топах отдельно объясни как финансовый поток, если по назначению он не обычный клиент или поставщик.",
|
|
||||||
"А теперь отдельно по Группа СВК за 2020: сколько денег получили, сколько заплатили и какое нетто?"
|
|
||||||
],
|
|
||||||
"metadata": {
|
|
||||||
"assistant_prompt_version": null,
|
|
||||||
"decomposition_prompt_version": null,
|
|
||||||
"prompt_fingerprint": null,
|
|
||||||
"agent_focus": "phase103 financial role and purpose arbitration",
|
|
||||||
"architecture_phase": "Open-World Schema/Primitive Discovery",
|
|
||||||
"source_spec_file": "X:\\1C\\NDC_1C\\docs\\orchestration\\address_truth_harness_phase103_financial_role_purpose_arbitration.json",
|
|
||||||
"scenario_id": "address_truth_harness_phase103_financial_role_purpose_arbitration",
|
|
||||||
"semantic_tags": [
|
|
||||||
"bank_like_counterparty",
|
|
||||||
"bank_like_customer_boundary",
|
|
||||||
"bank_like_supplier_boundary",
|
|
||||||
"bank_operations_by_counterparty",
|
|
||||||
"bare_org_scope",
|
|
||||||
"business_overview",
|
|
||||||
"canary",
|
|
||||||
"counterparty_net_cash_flow",
|
|
||||||
"customer_revenue_and_payments",
|
|
||||||
"financial_role_purpose",
|
|
||||||
"phase102_canary",
|
|
||||||
"stale_scope_guard",
|
|
||||||
"supplier_payouts_profile"
|
|
||||||
],
|
|
||||||
"validation_status": "accepted_live_replay",
|
|
||||||
"validated_run_dir": "artifacts\\domain_runs\\phase103_financial_role_purpose_arbitration_live3",
|
|
||||||
"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\\phase103_financial_role_purpose_arbitration_live3",
|
|
||||||
"final_status": "accepted",
|
|
||||||
"review_overall_status": "pass",
|
|
||||||
"business_overall_status": "pass",
|
|
||||||
"steps_total": 6,
|
|
||||||
"steps_passed": 6,
|
|
||||||
"steps_failed": 0,
|
|
||||||
"steps_with_business_failures": 0,
|
|
||||||
"steps_with_business_warnings": 0,
|
|
||||||
"acceptance_gate_passed": true,
|
|
||||||
"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-05-13T12:00:47+00:00",
|
|
||||||
"reply_type": null,
|
|
||||||
"trace_id": null,
|
|
||||||
"debug": null
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"message_id": "agent-user-002",
|
|
||||||
"role": "user",
|
|
||||||
"text": "По СБЕРБАНКУ за 2020 покажи коротко: сколько денег входило и уходило, и что это по смыслу в 1С — клиентская выручка, поставщик, комиссия, кредит или другой финансовый поток?",
|
|
||||||
"created_at": "2026-05-13T12:00:47+00:00",
|
|
||||||
"reply_type": null,
|
|
||||||
"trace_id": null,
|
|
||||||
"debug": null
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"message_id": "agent-user-003",
|
|
||||||
"role": "user",
|
|
||||||
"text": "Если СБЕРБАНК есть во входящих поступлениях, можно ли считать его нашим клиентом и выручкой? Скажи по подтвержденным строкам, без притягивания.",
|
|
||||||
"created_at": "2026-05-13T12:00:47+00:00",
|
|
||||||
"reply_type": null,
|
|
||||||
"trace_id": null,
|
|
||||||
"debug": null
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"message_id": "agent-user-004",
|
|
||||||
"role": "user",
|
|
||||||
"text": "А если деньги уходили в СБЕРБАНК, это наш поставщик или финансовые списания? Раздели по смыслу и покажи основание.",
|
|
||||||
"created_at": "2026-05-13T12:00:47+00:00",
|
|
||||||
"reply_type": null,
|
|
||||||
"trace_id": null,
|
|
||||||
"debug": null
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"message_id": "agent-user-005",
|
|
||||||
"role": "user",
|
|
||||||
"text": "Теперь дай взрослый обзор за 2020 по компании: входящие, исходящие, нетто, топы, но банк в топах отдельно объясни как финансовый поток, если по назначению он не обычный клиент или поставщик.",
|
|
||||||
"created_at": "2026-05-13T12:00:47+00:00",
|
|
||||||
"reply_type": null,
|
|
||||||
"trace_id": null,
|
|
||||||
"debug": null
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"message_id": "agent-user-006",
|
|
||||||
"role": "user",
|
|
||||||
"text": "А теперь отдельно по Группа СВК за 2020: сколько денег получили, сколько заплатили и какое нетто?",
|
|
||||||
"created_at": "2026-05-13T12:00:47+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": "phase103 financial role and purpose arbitration",
|
|
||||||
"architecture_phase": "Open-World Schema/Primitive Discovery",
|
|
||||||
"source_spec_file": "X:\\1C\\NDC_1C\\docs\\orchestration\\address_truth_harness_phase103_financial_role_purpose_arbitration.json",
|
|
||||||
"scenario_id": "address_truth_harness_phase103_financial_role_purpose_arbitration",
|
|
||||||
"semantic_tags": [
|
|
||||||
"bank_like_counterparty",
|
|
||||||
"bank_like_customer_boundary",
|
|
||||||
"bank_like_supplier_boundary",
|
|
||||||
"bank_operations_by_counterparty",
|
|
||||||
"bare_org_scope",
|
|
||||||
"business_overview",
|
|
||||||
"canary",
|
|
||||||
"counterparty_net_cash_flow",
|
|
||||||
"customer_revenue_and_payments",
|
|
||||||
"financial_role_purpose",
|
|
||||||
"phase102_canary",
|
|
||||||
"stale_scope_guard",
|
|
||||||
"supplier_payouts_profile"
|
|
||||||
],
|
|
||||||
"validation_status": "accepted_live_replay",
|
|
||||||
"validated_run_dir": "artifacts\\domain_runs\\phase103_financial_role_purpose_arbitration_live3",
|
|
||||||
"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\\phase103_financial_role_purpose_arbitration_live3",
|
|
||||||
"final_status": "accepted",
|
|
||||||
"review_overall_status": "pass",
|
|
||||||
"business_overall_status": "pass",
|
|
||||||
"steps_total": 6,
|
|
||||||
"steps_passed": 6,
|
|
||||||
"steps_failed": 0,
|
|
||||||
"steps_with_business_failures": 0,
|
|
||||||
"steps_with_business_warnings": 0,
|
|
||||||
"acceptance_gate_passed": true,
|
|
||||||
"saved_after_validated_replay": true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,135 +0,0 @@
|
||||||
{
|
|
||||||
"saved_at": "2026-05-13T12:26:18+00:00",
|
|
||||||
"generation_id": "gen-ag05131226-630ddf",
|
|
||||||
"mode": "saved_user_sessions",
|
|
||||||
"title": "AGENT | Phase 104 generic role-tail anchor hygiene replay",
|
|
||||||
"agent_run": true,
|
|
||||||
"questions": [
|
|
||||||
"Альтернатива Плюс",
|
|
||||||
"Теперь дай взрослый обзор за 2020 по компании: входящие, исходящие, нетто, топы, но банк в топах отдельно объясни как финансовый поток, если по назначению он не обычный клиент или поставщик.",
|
|
||||||
"А отдельно по СБЕРБАНКУ: он для нас клиент, поставщик или финансовый поток? Дай коротко по подтвержденным строкам.",
|
|
||||||
"А теперь по поставщику Группа СВК за 2020: сколько мы ему заплатили и какой общий денежный смысл?"
|
|
||||||
],
|
|
||||||
"metadata": {
|
|
||||||
"assistant_prompt_version": null,
|
|
||||||
"decomposition_prompt_version": null,
|
|
||||||
"prompt_fingerprint": null,
|
|
||||||
"agent_focus": "phase104 generic role-tail anchor hygiene",
|
|
||||||
"architecture_phase": "Open-World Schema/Primitive Discovery",
|
|
||||||
"source_spec_file": "X:\\1C\\NDC_1C\\docs\\orchestration\\address_truth_harness_phase104_generic_role_tail_anchor_hygiene.json",
|
|
||||||
"scenario_id": "address_truth_harness_phase104_generic_role_tail_anchor_hygiene",
|
|
||||||
"semantic_tags": [
|
|
||||||
"bank_like_counterparty",
|
|
||||||
"bare_org_scope",
|
|
||||||
"business_overview",
|
|
||||||
"counterparty_net_cash_flow",
|
|
||||||
"financial_role_purpose",
|
|
||||||
"generic_role_tail_anchor_hygiene",
|
|
||||||
"phase102_canary",
|
|
||||||
"post_overview_anchor_integrity",
|
|
||||||
"stale_scope_guard",
|
|
||||||
"supplier_prefix_canary"
|
|
||||||
],
|
|
||||||
"validation_status": "accepted_live_replay",
|
|
||||||
"validated_run_dir": "artifacts\\domain_runs\\phase104_generic_role_tail_anchor_hygiene_live2",
|
|
||||||
"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\\phase104_generic_role_tail_anchor_hygiene_live2",
|
|
||||||
"final_status": "accepted",
|
|
||||||
"review_overall_status": "pass",
|
|
||||||
"business_overall_status": "pass",
|
|
||||||
"steps_total": 4,
|
|
||||||
"steps_passed": 4,
|
|
||||||
"steps_failed": 0,
|
|
||||||
"steps_with_business_failures": 0,
|
|
||||||
"steps_with_business_warnings": 0,
|
|
||||||
"acceptance_gate_passed": true,
|
|
||||||
"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-05-13T12:26:18+00:00",
|
|
||||||
"reply_type": null,
|
|
||||||
"trace_id": null,
|
|
||||||
"debug": null
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"message_id": "agent-user-002",
|
|
||||||
"role": "user",
|
|
||||||
"text": "Теперь дай взрослый обзор за 2020 по компании: входящие, исходящие, нетто, топы, но банк в топах отдельно объясни как финансовый поток, если по назначению он не обычный клиент или поставщик.",
|
|
||||||
"created_at": "2026-05-13T12:26:18+00:00",
|
|
||||||
"reply_type": null,
|
|
||||||
"trace_id": null,
|
|
||||||
"debug": null
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"message_id": "agent-user-003",
|
|
||||||
"role": "user",
|
|
||||||
"text": "А отдельно по СБЕРБАНКУ: он для нас клиент, поставщик или финансовый поток? Дай коротко по подтвержденным строкам.",
|
|
||||||
"created_at": "2026-05-13T12:26:18+00:00",
|
|
||||||
"reply_type": null,
|
|
||||||
"trace_id": null,
|
|
||||||
"debug": null
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"message_id": "agent-user-004",
|
|
||||||
"role": "user",
|
|
||||||
"text": "А теперь по поставщику Группа СВК за 2020: сколько мы ему заплатили и какой общий денежный смысл?",
|
|
||||||
"created_at": "2026-05-13T12:26:18+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": "phase104 generic role-tail anchor hygiene",
|
|
||||||
"architecture_phase": "Open-World Schema/Primitive Discovery",
|
|
||||||
"source_spec_file": "X:\\1C\\NDC_1C\\docs\\orchestration\\address_truth_harness_phase104_generic_role_tail_anchor_hygiene.json",
|
|
||||||
"scenario_id": "address_truth_harness_phase104_generic_role_tail_anchor_hygiene",
|
|
||||||
"semantic_tags": [
|
|
||||||
"bank_like_counterparty",
|
|
||||||
"bare_org_scope",
|
|
||||||
"business_overview",
|
|
||||||
"counterparty_net_cash_flow",
|
|
||||||
"financial_role_purpose",
|
|
||||||
"generic_role_tail_anchor_hygiene",
|
|
||||||
"phase102_canary",
|
|
||||||
"post_overview_anchor_integrity",
|
|
||||||
"stale_scope_guard",
|
|
||||||
"supplier_prefix_canary"
|
|
||||||
],
|
|
||||||
"validation_status": "accepted_live_replay",
|
|
||||||
"validated_run_dir": "artifacts\\domain_runs\\phase104_generic_role_tail_anchor_hygiene_live2",
|
|
||||||
"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\\phase104_generic_role_tail_anchor_hygiene_live2",
|
|
||||||
"final_status": "accepted",
|
|
||||||
"review_overall_status": "pass",
|
|
||||||
"business_overall_status": "pass",
|
|
||||||
"steps_total": 4,
|
|
||||||
"steps_passed": 4,
|
|
||||||
"steps_failed": 0,
|
|
||||||
"steps_with_business_failures": 0,
|
|
||||||
"steps_with_business_warnings": 0,
|
|
||||||
"acceptance_gate_passed": true,
|
|
||||||
"saved_after_validated_replay": true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,123 +0,0 @@
|
||||||
{
|
|
||||||
"saved_at": "2026-05-22T13:19:31+00:00",
|
|
||||||
"generation_id": "gen-ag05221319-4035f5",
|
|
||||||
"mode": "saved_user_sessions",
|
|
||||||
"title": "AGENT | Inventory margin-ranking accepted replay 2026-05-22",
|
|
||||||
"agent_run": true,
|
|
||||||
"questions": [
|
|
||||||
"Какая номеклатура товара реализована с высокой прибылью какая с низкой",
|
|
||||||
"сентябрь 2017",
|
|
||||||
"покажи найденные строки себестоимостной базы",
|
|
||||||
"расширь до 2017 года",
|
|
||||||
"анализ по 41 счету а не 01"
|
|
||||||
],
|
|
||||||
"metadata": {
|
|
||||||
"assistant_prompt_version": null,
|
|
||||||
"decomposition_prompt_version": null,
|
|
||||||
"prompt_fingerprint": null,
|
|
||||||
"agent_focus": "inventory_margin_ranking_followup_and_business_modes",
|
|
||||||
"architecture_phase": "Business Answer Contract / margin-ranking safety gate",
|
|
||||||
"source_spec_file": "X:\\1C\\NDC_1C\\docs\\orchestration\\inventory_margin_ranking_agent_loop_20260522.json",
|
|
||||||
"scenario_id": "inventory_margin_ranking_agent_loop_20260522",
|
|
||||||
"semantic_tags": [],
|
|
||||||
"validation_status": "accepted_live_replay",
|
|
||||||
"validated_run_dir": "artifacts\\domain_runs\\inventory_margin_ranking_agent_loop_live7",
|
|
||||||
"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_margin_ranking_agent_loop_live7",
|
|
||||||
"final_status": "accepted",
|
|
||||||
"review_overall_status": "pass",
|
|
||||||
"business_overall_status": "pass",
|
|
||||||
"steps_total": 5,
|
|
||||||
"steps_passed": 5,
|
|
||||||
"steps_failed": 0,
|
|
||||||
"steps_with_business_failures": 0,
|
|
||||||
"steps_with_business_warnings": 0,
|
|
||||||
"acceptance_gate_passed": true,
|
|
||||||
"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-05-22T13:19:31+00:00",
|
|
||||||
"reply_type": null,
|
|
||||||
"trace_id": null,
|
|
||||||
"debug": null
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"message_id": "agent-user-002",
|
|
||||||
"role": "user",
|
|
||||||
"text": "сентябрь 2017",
|
|
||||||
"created_at": "2026-05-22T13:19:31+00:00",
|
|
||||||
"reply_type": null,
|
|
||||||
"trace_id": null,
|
|
||||||
"debug": null
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"message_id": "agent-user-003",
|
|
||||||
"role": "user",
|
|
||||||
"text": "покажи найденные строки себестоимостной базы",
|
|
||||||
"created_at": "2026-05-22T13:19:31+00:00",
|
|
||||||
"reply_type": null,
|
|
||||||
"trace_id": null,
|
|
||||||
"debug": null
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"message_id": "agent-user-004",
|
|
||||||
"role": "user",
|
|
||||||
"text": "расширь до 2017 года",
|
|
||||||
"created_at": "2026-05-22T13:19:31+00:00",
|
|
||||||
"reply_type": null,
|
|
||||||
"trace_id": null,
|
|
||||||
"debug": null
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"message_id": "agent-user-005",
|
|
||||||
"role": "user",
|
|
||||||
"text": "анализ по 41 счету а не 01",
|
|
||||||
"created_at": "2026-05-22T13:19:31+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": "inventory_margin_ranking_followup_and_business_modes",
|
|
||||||
"architecture_phase": "Business Answer Contract / margin-ranking safety gate",
|
|
||||||
"source_spec_file": "X:\\1C\\NDC_1C\\docs\\orchestration\\inventory_margin_ranking_agent_loop_20260522.json",
|
|
||||||
"scenario_id": "inventory_margin_ranking_agent_loop_20260522",
|
|
||||||
"semantic_tags": [],
|
|
||||||
"validation_status": "accepted_live_replay",
|
|
||||||
"validated_run_dir": "artifacts\\domain_runs\\inventory_margin_ranking_agent_loop_live7",
|
|
||||||
"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_margin_ranking_agent_loop_live7",
|
|
||||||
"final_status": "accepted",
|
|
||||||
"review_overall_status": "pass",
|
|
||||||
"business_overall_status": "pass",
|
|
||||||
"steps_total": 5,
|
|
||||||
"steps_passed": 5,
|
|
||||||
"steps_failed": 0,
|
|
||||||
"steps_with_business_failures": 0,
|
|
||||||
"steps_with_business_warnings": 0,
|
|
||||||
"acceptance_gate_passed": true,
|
|
||||||
"saved_after_validated_replay": true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,119 +0,0 @@
|
||||||
{
|
|
||||||
"saved_at": "2026-05-22T19:57:37+00:00",
|
|
||||||
"generation_id": "gen-ag05221957-713bbd",
|
|
||||||
"mode": "saved_user_sessions",
|
|
||||||
"title": "AGENT | Hot value-flow discovery handoff",
|
|
||||||
"agent_run": true,
|
|
||||||
"questions": [
|
|
||||||
"Сколько входящих денег за 2020 год по ООО Альтернатива Плюс без разреза по контрагентам?",
|
|
||||||
"Сколько исходящих денег за 2020 год по ООО Альтернатива Плюс без разреза по контрагентам?",
|
|
||||||
"А всего сколько денег пришло в ООО Альтернатива Плюс за 2020, без топов и без контрагентов?"
|
|
||||||
],
|
|
||||||
"metadata": {
|
|
||||||
"assistant_prompt_version": null,
|
|
||||||
"decomposition_prompt_version": null,
|
|
||||||
"prompt_fingerprint": null,
|
|
||||||
"agent_focus": "Targeted AGENT replay for the current Autonomy Core slice: organization-scoped value-flow questions must be answered through guarded MCP discovery response with hot handoff, not through stale exact fallback or a counterparty-only route.",
|
|
||||||
"architecture_phase": "turnaround_11",
|
|
||||||
"source_spec_file": "X:\\1C\\NDC_1C\\docs\\orchestration\\agent_hot_value_flow_handoff_20260522.json",
|
|
||||||
"scenario_id": "agent_hot_value_flow_handoff_20260522",
|
|
||||||
"semantic_tags": [
|
|
||||||
"autonomy_core",
|
|
||||||
"colloquial_total",
|
|
||||||
"guarded_response",
|
|
||||||
"hot_handoff",
|
|
||||||
"incoming_total",
|
|
||||||
"outgoing_total",
|
|
||||||
"value_flow"
|
|
||||||
],
|
|
||||||
"validation_status": "accepted_live_replay",
|
|
||||||
"validated_run_dir": "artifacts\\domain_runs\\agent_hot_value_flow_handoff_live5",
|
|
||||||
"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\\agent_hot_value_flow_handoff_live5",
|
|
||||||
"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,
|
|
||||||
"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": "Сколько входящих денег за 2020 год по ООО Альтернатива Плюс без разреза по контрагентам?",
|
|
||||||
"created_at": "2026-05-22T19:57:37+00:00",
|
|
||||||
"reply_type": null,
|
|
||||||
"trace_id": null,
|
|
||||||
"debug": null
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"message_id": "agent-user-002",
|
|
||||||
"role": "user",
|
|
||||||
"text": "Сколько исходящих денег за 2020 год по ООО Альтернатива Плюс без разреза по контрагентам?",
|
|
||||||
"created_at": "2026-05-22T19:57:37+00:00",
|
|
||||||
"reply_type": null,
|
|
||||||
"trace_id": null,
|
|
||||||
"debug": null
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"message_id": "agent-user-003",
|
|
||||||
"role": "user",
|
|
||||||
"text": "А всего сколько денег пришло в ООО Альтернатива Плюс за 2020, без топов и без контрагентов?",
|
|
||||||
"created_at": "2026-05-22T19:57:37+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 AGENT replay for the current Autonomy Core slice: organization-scoped value-flow questions must be answered through guarded MCP discovery response with hot handoff, not through stale exact fallback or a counterparty-only route.",
|
|
||||||
"architecture_phase": "turnaround_11",
|
|
||||||
"source_spec_file": "X:\\1C\\NDC_1C\\docs\\orchestration\\agent_hot_value_flow_handoff_20260522.json",
|
|
||||||
"scenario_id": "agent_hot_value_flow_handoff_20260522",
|
|
||||||
"semantic_tags": [
|
|
||||||
"autonomy_core",
|
|
||||||
"colloquial_total",
|
|
||||||
"guarded_response",
|
|
||||||
"hot_handoff",
|
|
||||||
"incoming_total",
|
|
||||||
"outgoing_total",
|
|
||||||
"value_flow"
|
|
||||||
],
|
|
||||||
"validation_status": "accepted_live_replay",
|
|
||||||
"validated_run_dir": "artifacts\\domain_runs\\agent_hot_value_flow_handoff_live5",
|
|
||||||
"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\\agent_hot_value_flow_handoff_live5",
|
|
||||||
"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,
|
|
||||||
"saved_after_validated_replay": true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,287 +0,0 @@
|
||||||
{
|
|
||||||
"saved_at": "2026-05-23T18:58:48+00:00",
|
|
||||||
"generation_id": "gen-ag05231858-323f86",
|
|
||||||
"mode": "saved_user_sessions",
|
|
||||||
"title": "AGENT | Autonomy business quality pack",
|
|
||||||
"agent_run": true,
|
|
||||||
"questions": [
|
|
||||||
"Сколько входящих денег за 2020 год по ООО Альтернатива Плюс без разреза по контрагентам?",
|
|
||||||
"Сколько исходящих денег за 2020 год по ООО Альтернатива Плюс без разреза по контрагентам?",
|
|
||||||
"А всего сколько денег пришло в ООО Альтернатива Плюс за 2020, без топов и без контрагентов?",
|
|
||||||
"Теперь дай взрослый обзор за 2020 по компании: входящие, исходящие, нетто, топы, но банк в топах отдельно объясни как финансовый поток.",
|
|
||||||
"скока денег альтернатива заработала за 20 год?",
|
|
||||||
"а это чистая прибыль?",
|
|
||||||
"Какая чистая прибыль по ООО Альтернатива Плюс за 2020?",
|
|
||||||
"А отдельно по СБЕРБАНКУ: он для нас клиент, поставщик или финансовый поток? Дай коротко по подтвержденным строкам.",
|
|
||||||
"какое нетто по деньгам с Группа СВК за 2020 год: сколько получили и сколько заплатили?",
|
|
||||||
"кому мы должны на конец 2020?",
|
|
||||||
"а нам кто должен на конец 2020?",
|
|
||||||
"сколько НДС надо заплатить в налоговую за декабрь 2019?",
|
|
||||||
"Как ты оценишь деятельность компании?",
|
|
||||||
"Какая номенклатура товара реализована с высокой прибылью какая с низкой?",
|
|
||||||
"май 2020"
|
|
||||||
],
|
|
||||||
"metadata": {
|
|
||||||
"assistant_prompt_version": null,
|
|
||||||
"decomposition_prompt_version": null,
|
|
||||||
"prompt_fingerprint": null,
|
|
||||||
"agent_focus": "Expanded targeted AGENT replay for the autonomy milestone: value-flow, business overview, debts, VAT, profit/cashflow distinction, nomenclature margin boundary, and final answer quality must survive realistic business questions.",
|
|
||||||
"architecture_phase": "turnaround_11",
|
|
||||||
"source_spec_file": "X:\\1C\\NDC_1C\\docs\\orchestration\\agent_autonomy_business_quality_20260523.json",
|
|
||||||
"scenario_id": "agent_autonomy_business_quality_20260523",
|
|
||||||
"semantic_tags": [
|
|
||||||
"autonomy_core",
|
|
||||||
"bank_boundary",
|
|
||||||
"bank_classification",
|
|
||||||
"business_answer_quality",
|
|
||||||
"business_evaluation",
|
|
||||||
"business_overview",
|
|
||||||
"cashflow_overview",
|
|
||||||
"cashflow_vs_profit",
|
|
||||||
"clarification",
|
|
||||||
"colloquial_money",
|
|
||||||
"colloquial_total",
|
|
||||||
"counterparty_value_flow",
|
|
||||||
"debt_answer_quality",
|
|
||||||
"direct_profit",
|
|
||||||
"domain_purity",
|
|
||||||
"followup_context",
|
|
||||||
"incoming_total",
|
|
||||||
"limit_honesty",
|
|
||||||
"limited_answer",
|
|
||||||
"net_flow",
|
|
||||||
"next_action",
|
|
||||||
"no_profit_substitution",
|
|
||||||
"no_top_guard",
|
|
||||||
"nomenclature_margin",
|
|
||||||
"outgoing_total",
|
|
||||||
"payables",
|
|
||||||
"profit_vs_cashflow",
|
|
||||||
"receivables",
|
|
||||||
"technical_garbage_guard",
|
|
||||||
"value_flow",
|
|
||||||
"vat"
|
|
||||||
],
|
|
||||||
"validation_status": "accepted_live_replay",
|
|
||||||
"validated_run_dir": "artifacts\\domain_runs\\agent_autonomy_business_quality_live_semantic_gate_accepted_20260523",
|
|
||||||
"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\\agent_autonomy_business_quality_live_semantic_gate_accepted_20260523",
|
|
||||||
"final_status": "accepted",
|
|
||||||
"review_overall_status": "pass",
|
|
||||||
"business_overall_status": "pass",
|
|
||||||
"steps_total": 15,
|
|
||||||
"steps_passed": 15,
|
|
||||||
"steps_failed": 0,
|
|
||||||
"steps_with_business_failures": 0,
|
|
||||||
"steps_with_business_warnings": 0,
|
|
||||||
"acceptance_gate_passed": true,
|
|
||||||
"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": "Сколько входящих денег за 2020 год по ООО Альтернатива Плюс без разреза по контрагентам?",
|
|
||||||
"created_at": "2026-05-23T18:58:48+00:00",
|
|
||||||
"reply_type": null,
|
|
||||||
"trace_id": null,
|
|
||||||
"debug": null
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"message_id": "agent-user-002",
|
|
||||||
"role": "user",
|
|
||||||
"text": "Сколько исходящих денег за 2020 год по ООО Альтернатива Плюс без разреза по контрагентам?",
|
|
||||||
"created_at": "2026-05-23T18:58:48+00:00",
|
|
||||||
"reply_type": null,
|
|
||||||
"trace_id": null,
|
|
||||||
"debug": null
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"message_id": "agent-user-003",
|
|
||||||
"role": "user",
|
|
||||||
"text": "А всего сколько денег пришло в ООО Альтернатива Плюс за 2020, без топов и без контрагентов?",
|
|
||||||
"created_at": "2026-05-23T18:58:48+00:00",
|
|
||||||
"reply_type": null,
|
|
||||||
"trace_id": null,
|
|
||||||
"debug": null
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"message_id": "agent-user-004",
|
|
||||||
"role": "user",
|
|
||||||
"text": "Теперь дай взрослый обзор за 2020 по компании: входящие, исходящие, нетто, топы, но банк в топах отдельно объясни как финансовый поток.",
|
|
||||||
"created_at": "2026-05-23T18:58:48+00:00",
|
|
||||||
"reply_type": null,
|
|
||||||
"trace_id": null,
|
|
||||||
"debug": null
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"message_id": "agent-user-005",
|
|
||||||
"role": "user",
|
|
||||||
"text": "скока денег альтернатива заработала за 20 год?",
|
|
||||||
"created_at": "2026-05-23T18:58:48+00:00",
|
|
||||||
"reply_type": null,
|
|
||||||
"trace_id": null,
|
|
||||||
"debug": null
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"message_id": "agent-user-006",
|
|
||||||
"role": "user",
|
|
||||||
"text": "а это чистая прибыль?",
|
|
||||||
"created_at": "2026-05-23T18:58:48+00:00",
|
|
||||||
"reply_type": null,
|
|
||||||
"trace_id": null,
|
|
||||||
"debug": null
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"message_id": "agent-user-007",
|
|
||||||
"role": "user",
|
|
||||||
"text": "Какая чистая прибыль по ООО Альтернатива Плюс за 2020?",
|
|
||||||
"created_at": "2026-05-23T18:58:48+00:00",
|
|
||||||
"reply_type": null,
|
|
||||||
"trace_id": null,
|
|
||||||
"debug": null
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"message_id": "agent-user-008",
|
|
||||||
"role": "user",
|
|
||||||
"text": "А отдельно по СБЕРБАНКУ: он для нас клиент, поставщик или финансовый поток? Дай коротко по подтвержденным строкам.",
|
|
||||||
"created_at": "2026-05-23T18:58:48+00:00",
|
|
||||||
"reply_type": null,
|
|
||||||
"trace_id": null,
|
|
||||||
"debug": null
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"message_id": "agent-user-009",
|
|
||||||
"role": "user",
|
|
||||||
"text": "какое нетто по деньгам с Группа СВК за 2020 год: сколько получили и сколько заплатили?",
|
|
||||||
"created_at": "2026-05-23T18:58:48+00:00",
|
|
||||||
"reply_type": null,
|
|
||||||
"trace_id": null,
|
|
||||||
"debug": null
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"message_id": "agent-user-010",
|
|
||||||
"role": "user",
|
|
||||||
"text": "кому мы должны на конец 2020?",
|
|
||||||
"created_at": "2026-05-23T18:58:48+00:00",
|
|
||||||
"reply_type": null,
|
|
||||||
"trace_id": null,
|
|
||||||
"debug": null
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"message_id": "agent-user-011",
|
|
||||||
"role": "user",
|
|
||||||
"text": "а нам кто должен на конец 2020?",
|
|
||||||
"created_at": "2026-05-23T18:58:48+00:00",
|
|
||||||
"reply_type": null,
|
|
||||||
"trace_id": null,
|
|
||||||
"debug": null
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"message_id": "agent-user-012",
|
|
||||||
"role": "user",
|
|
||||||
"text": "сколько НДС надо заплатить в налоговую за декабрь 2019?",
|
|
||||||
"created_at": "2026-05-23T18:58:48+00:00",
|
|
||||||
"reply_type": null,
|
|
||||||
"trace_id": null,
|
|
||||||
"debug": null
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"message_id": "agent-user-013",
|
|
||||||
"role": "user",
|
|
||||||
"text": "Как ты оценишь деятельность компании?",
|
|
||||||
"created_at": "2026-05-23T18:58:48+00:00",
|
|
||||||
"reply_type": null,
|
|
||||||
"trace_id": null,
|
|
||||||
"debug": null
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"message_id": "agent-user-014",
|
|
||||||
"role": "user",
|
|
||||||
"text": "Какая номенклатура товара реализована с высокой прибылью какая с низкой?",
|
|
||||||
"created_at": "2026-05-23T18:58:48+00:00",
|
|
||||||
"reply_type": null,
|
|
||||||
"trace_id": null,
|
|
||||||
"debug": null
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"message_id": "agent-user-015",
|
|
||||||
"role": "user",
|
|
||||||
"text": "май 2020",
|
|
||||||
"created_at": "2026-05-23T18:58:48+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": "Expanded targeted AGENT replay for the autonomy milestone: value-flow, business overview, debts, VAT, profit/cashflow distinction, nomenclature margin boundary, and final answer quality must survive realistic business questions.",
|
|
||||||
"architecture_phase": "turnaround_11",
|
|
||||||
"source_spec_file": "X:\\1C\\NDC_1C\\docs\\orchestration\\agent_autonomy_business_quality_20260523.json",
|
|
||||||
"scenario_id": "agent_autonomy_business_quality_20260523",
|
|
||||||
"semantic_tags": [
|
|
||||||
"autonomy_core",
|
|
||||||
"bank_boundary",
|
|
||||||
"bank_classification",
|
|
||||||
"business_answer_quality",
|
|
||||||
"business_evaluation",
|
|
||||||
"business_overview",
|
|
||||||
"cashflow_overview",
|
|
||||||
"cashflow_vs_profit",
|
|
||||||
"clarification",
|
|
||||||
"colloquial_money",
|
|
||||||
"colloquial_total",
|
|
||||||
"counterparty_value_flow",
|
|
||||||
"debt_answer_quality",
|
|
||||||
"direct_profit",
|
|
||||||
"domain_purity",
|
|
||||||
"followup_context",
|
|
||||||
"incoming_total",
|
|
||||||
"limit_honesty",
|
|
||||||
"limited_answer",
|
|
||||||
"net_flow",
|
|
||||||
"next_action",
|
|
||||||
"no_profit_substitution",
|
|
||||||
"no_top_guard",
|
|
||||||
"nomenclature_margin",
|
|
||||||
"outgoing_total",
|
|
||||||
"payables",
|
|
||||||
"profit_vs_cashflow",
|
|
||||||
"receivables",
|
|
||||||
"technical_garbage_guard",
|
|
||||||
"value_flow",
|
|
||||||
"vat"
|
|
||||||
],
|
|
||||||
"validation_status": "accepted_live_replay",
|
|
||||||
"validated_run_dir": "artifacts\\domain_runs\\agent_autonomy_business_quality_live_semantic_gate_accepted_20260523",
|
|
||||||
"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\\agent_autonomy_business_quality_live_semantic_gate_accepted_20260523",
|
|
||||||
"final_status": "accepted",
|
|
||||||
"review_overall_status": "pass",
|
|
||||||
"business_overall_status": "pass",
|
|
||||||
"steps_total": 15,
|
|
||||||
"steps_passed": 15,
|
|
||||||
"steps_failed": 0,
|
|
||||||
"steps_with_business_failures": 0,
|
|
||||||
"steps_with_business_warnings": 0,
|
|
||||||
"acceptance_gate_passed": true,
|
|
||||||
"saved_after_validated_replay": true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,54 +1,58 @@
|
||||||
{
|
{
|
||||||
"saved_at": "2026-05-09T18:30:50+00:00",
|
"saved_at": "2026-05-24T23:06:41+00:00",
|
||||||
"generation_id": "gen-ag05091830-062fc9",
|
"generation_id": "gen-ag05242306-7b49ab",
|
||||||
"mode": "saved_user_sessions",
|
"mode": "saved_user_sessions",
|
||||||
"title": "AGENT | Agentic semantic development loop dogfood gate",
|
"title": "AGENT | Бизнес-обзор компании и СВК accepted replay 2026-05-24",
|
||||||
"agent_run": true,
|
"agent_run": true,
|
||||||
"questions": [
|
"questions": [
|
||||||
"Дай взрослый бизнес-обзор ООО Альтернатива Плюс за 2020 год по данным 1С: обороты, входящие и исходящие деньги, нетто, НДС, долги, склад, клиенты, поставщики и что пока нельзя утверждать.",
|
"Дай взрослый бизнес-обзор ООО Альтернатива Плюс за 2020 год по данным 1С: обороты, входящие и исходящие деньги, нетто, НДС, долги, склад, клиенты, поставщики и что пока нельзя утверждать.",
|
||||||
"Раскрой деньги подробнее: сколько получили, сколько заплатили, какой чистый денежный поток, кто главный клиент и главный поставщик в 2020.",
|
"Раскрой деньги подробнее: сколько получили, сколько заплатили, какой чистый денежный поток, кто главный клиент и главный поставщик в 2020.",
|
||||||
"А если смотреть за все доступное время, какой самый доходный год по подтвержденным оборотам и почему? Не называй это бухгалтерской прибылью, если чистой прибыли нет.",
|
|
||||||
"Что с НДС за 2020 год по ООО Альтернатива Плюс: какая позиция видна, на чем она основана и чего не хватает для налогового вывода?",
|
"Что с НДС за 2020 год по ООО Альтернатива Плюс: какая позиция видна, на чем она основана и чего не хватает для налогового вывода?",
|
||||||
"Теперь за все доступное время дай обзор компании в целом, но не тащи НДС за 2020 как подтвержденную общую налоговую позицию.",
|
"Теперь за все доступное время дай обзор компании в целом, но не тащи НДС за 2020 как подтвержденную общую налоговую позицию.",
|
||||||
"Отдельно по контрагенту Группа СВК, без опоры на прошлый диалог: сколько денег прошло, что входящее, что исходящее и есть ли документы или движения, на которых это основано?",
|
"Отдельно по контрагенту Группа СВК, без опоры на прошлый диалог: сколько денег прошло, что входящее, что исходящее и есть ли документы или движения, на которых это основано?",
|
||||||
"Покажи документы по этой цепочке и не смешивай Группа СВК с организацией ООО Альтернатива Плюс.",
|
"Покажи документы по этой цепочке и не смешивай Группа СВК с организацией ООО Альтернатива Плюс.",
|
||||||
"Собери короткий итог: что мы подтвердили по компании, что отдельно по Группа СВК, какие выводы можно делать и какие нельзя.",
|
"Собери короткий итог: что мы подтвердили по компании, что отдельно по Группа СВК, какие выводы можно делать и какие нельзя.",
|
||||||
"Сделай короткий стартовый чек контекста: есть ли уже выбранная компания или контрагент в текущем диалоге; если нет, скажи честно и не выдумывай память про Группа СВК.",
|
"По-человечески посмотри ООО Альтернатива Плюс за 2020: что по бизнесу видно в 1С по деньгам, НДС, долгам, складу, клиентам/поставщикам и где пока нельзя делать вывод?",
|
||||||
"Покажи хвосты по счету 60 на август 2020 по ООО Альтернатива Плюс; если точных данных нет, скажи это прямо и не подменяй ответ общим обзором.",
|
"А по деньгам в этом же году кто больше всего занес и кому больше всего ушло? Банк не называй обычным клиентом без оговорки.",
|
||||||
"Что было на складе на март 2021 по доступным данным? Дай прямой ответ и не уводи его в контрагента Группа СВК.",
|
"Возьми как выбранного контрагента Группа СВК: сколько по нему входящих денег, исходящих денег и расчетное нетто, на чем это основано?",
|
||||||
"Вернись к ООО Альтернатива Плюс: сколько всего денег получили и заплатили по всем подтвержденным данным, но не смешивай это с отдельной цепочкой Группа СВК и не называй оборот чистой прибылью."
|
"Теперь по нему покажи документы и не переключайся на организацию.",
|
||||||
|
"И коротко сравни: что у нас подтверждено по компании, что отдельно по выбранному контрагенту, и какие выводы нельзя делать?",
|
||||||
|
"ООО Альтернатива Плюс"
|
||||||
],
|
],
|
||||||
"metadata": {
|
"metadata": {
|
||||||
"assistant_prompt_version": null,
|
"assistant_prompt_version": null,
|
||||||
"decomposition_prompt_version": null,
|
"decomposition_prompt_version": null,
|
||||||
"prompt_fingerprint": null,
|
"prompt_fingerprint": null,
|
||||||
"agent_focus": "Automate stage question review, live semantic replay, strong business audit, Lead Codex repair handoff, rerun validation, and final human confirmation.",
|
"agent_focus": "Accepted proof pack for company business overview, SVK counterparty money/documents, selected-object carryover, org clarification, and all-time scope reset",
|
||||||
"architecture_phase": "turnaround_11_agentic_semantic_development_loop",
|
"architecture_phase": "agent_reliability_uplift_business_overview_counterparty",
|
||||||
"source_spec_file": "X:\\1C\\NDC_1C\\docs\\orchestration\\agentic_semantic_development_loop_stage_pack.json",
|
"source_spec_file": "X:\\1C\\NDC_1C\\docs\\orchestration\\agent_business_overview_counterparty_reliability_20260524.json",
|
||||||
"scenario_id": null,
|
"scenario_id": null,
|
||||||
"semantic_tags": [
|
"semantic_tags": [
|
||||||
"business_overview",
|
"business_overview",
|
||||||
|
"colloquial",
|
||||||
"counterparty",
|
"counterparty",
|
||||||
"debt",
|
"debt",
|
||||||
"documents",
|
"documents",
|
||||||
"inventory",
|
"inventory",
|
||||||
"memory",
|
|
||||||
"money",
|
"money",
|
||||||
|
"organization_clarification",
|
||||||
|
"pronoun_followup",
|
||||||
"scope_guard",
|
"scope_guard",
|
||||||
|
"selected_object",
|
||||||
"vat"
|
"vat"
|
||||||
],
|
],
|
||||||
"validation_status": "accepted_domain_pack_loop",
|
"validation_status": "accepted_domain_pack_loop",
|
||||||
"validated_run_dir": "artifacts\\domain_runs\\stage_agent_loops\\agentic_semantic_development_loop\\domain_loops\\asl",
|
"validated_run_dir": "artifacts\\domain_runs\\agent_business_overview_counterparty_proof_20260524_39",
|
||||||
"saved_after_validated_replay": true,
|
"saved_after_validated_replay": true,
|
||||||
"save_gate": {
|
"save_gate": {
|
||||||
"schema_version": "agent_semantic_save_gate_v1",
|
"schema_version": "agent_semantic_save_gate_v1",
|
||||||
"validation_status": "accepted_domain_pack_loop",
|
"validation_status": "accepted_domain_pack_loop",
|
||||||
"validated_run_dir": "artifacts\\domain_runs\\stage_agent_loops\\agentic_semantic_development_loop\\domain_loops\\asl",
|
"validated_run_dir": "artifacts\\domain_runs\\agent_business_overview_counterparty_proof_20260524_39",
|
||||||
"final_status": "accepted",
|
"final_status": "accepted",
|
||||||
"loop_id": "asl",
|
"loop_id": "agent_business_overview_counterparty_proof_20260524_39",
|
||||||
"target_score": 88,
|
"target_score": 90,
|
||||||
"iterations_ran": 1,
|
"iterations_ran": 1,
|
||||||
"quality_score": 91,
|
"quality_score": 92,
|
||||||
"repair_target_count": 0,
|
"repair_target_count": 0,
|
||||||
"repair_target_severity_counts": {
|
"repair_target_severity_counts": {
|
||||||
"P0": 0,
|
"P0": 0,
|
||||||
|
|
@ -56,6 +60,21 @@
|
||||||
"P2": 0
|
"P2": 0
|
||||||
},
|
},
|
||||||
"accepted_gate": true,
|
"accepted_gate": true,
|
||||||
|
"effective_runtime": {
|
||||||
|
"manifest_path": "artifacts\\domain_runs\\agent_business_overview_counterparty_proof_20260524_39\\effective_runtime.json",
|
||||||
|
"runner": "domain_case_loop.run-pack-loop",
|
||||||
|
"git_sha": "7cc65e808eb2430e7d27f2ac786c340aecc7fc50",
|
||||||
|
"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
|
"saved_after_validated_replay": true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
@ -68,7 +87,7 @@
|
||||||
"message_id": "agent-user-001",
|
"message_id": "agent-user-001",
|
||||||
"role": "user",
|
"role": "user",
|
||||||
"text": "Дай взрослый бизнес-обзор ООО Альтернатива Плюс за 2020 год по данным 1С: обороты, входящие и исходящие деньги, нетто, НДС, долги, склад, клиенты, поставщики и что пока нельзя утверждать.",
|
"text": "Дай взрослый бизнес-обзор ООО Альтернатива Плюс за 2020 год по данным 1С: обороты, входящие и исходящие деньги, нетто, НДС, долги, склад, клиенты, поставщики и что пока нельзя утверждать.",
|
||||||
"created_at": "2026-05-09T18:30:50+00:00",
|
"created_at": "2026-05-24T23:06:41+00:00",
|
||||||
"reply_type": null,
|
"reply_type": null,
|
||||||
"trace_id": null,
|
"trace_id": null,
|
||||||
"debug": null
|
"debug": null
|
||||||
|
|
@ -77,7 +96,7 @@
|
||||||
"message_id": "agent-user-002",
|
"message_id": "agent-user-002",
|
||||||
"role": "user",
|
"role": "user",
|
||||||
"text": "Раскрой деньги подробнее: сколько получили, сколько заплатили, какой чистый денежный поток, кто главный клиент и главный поставщик в 2020.",
|
"text": "Раскрой деньги подробнее: сколько получили, сколько заплатили, какой чистый денежный поток, кто главный клиент и главный поставщик в 2020.",
|
||||||
"created_at": "2026-05-09T18:30:50+00:00",
|
"created_at": "2026-05-24T23:06:41+00:00",
|
||||||
"reply_type": null,
|
"reply_type": null,
|
||||||
"trace_id": null,
|
"trace_id": null,
|
||||||
"debug": null
|
"debug": null
|
||||||
|
|
@ -85,8 +104,8 @@
|
||||||
{
|
{
|
||||||
"message_id": "agent-user-003",
|
"message_id": "agent-user-003",
|
||||||
"role": "user",
|
"role": "user",
|
||||||
"text": "А если смотреть за все доступное время, какой самый доходный год по подтвержденным оборотам и почему? Не называй это бухгалтерской прибылью, если чистой прибыли нет.",
|
"text": "Что с НДС за 2020 год по ООО Альтернатива Плюс: какая позиция видна, на чем она основана и чего не хватает для налогового вывода?",
|
||||||
"created_at": "2026-05-09T18:30:50+00:00",
|
"created_at": "2026-05-24T23:06:41+00:00",
|
||||||
"reply_type": null,
|
"reply_type": null,
|
||||||
"trace_id": null,
|
"trace_id": null,
|
||||||
"debug": null
|
"debug": null
|
||||||
|
|
@ -94,8 +113,8 @@
|
||||||
{
|
{
|
||||||
"message_id": "agent-user-004",
|
"message_id": "agent-user-004",
|
||||||
"role": "user",
|
"role": "user",
|
||||||
"text": "Что с НДС за 2020 год по ООО Альтернатива Плюс: какая позиция видна, на чем она основана и чего не хватает для налогового вывода?",
|
"text": "Теперь за все доступное время дай обзор компании в целом, но не тащи НДС за 2020 как подтвержденную общую налоговую позицию.",
|
||||||
"created_at": "2026-05-09T18:30:50+00:00",
|
"created_at": "2026-05-24T23:06:41+00:00",
|
||||||
"reply_type": null,
|
"reply_type": null,
|
||||||
"trace_id": null,
|
"trace_id": null,
|
||||||
"debug": null
|
"debug": null
|
||||||
|
|
@ -103,8 +122,8 @@
|
||||||
{
|
{
|
||||||
"message_id": "agent-user-005",
|
"message_id": "agent-user-005",
|
||||||
"role": "user",
|
"role": "user",
|
||||||
"text": "Теперь за все доступное время дай обзор компании в целом, но не тащи НДС за 2020 как подтвержденную общую налоговую позицию.",
|
"text": "Отдельно по контрагенту Группа СВК, без опоры на прошлый диалог: сколько денег прошло, что входящее, что исходящее и есть ли документы или движения, на которых это основано?",
|
||||||
"created_at": "2026-05-09T18:30:50+00:00",
|
"created_at": "2026-05-24T23:06:41+00:00",
|
||||||
"reply_type": null,
|
"reply_type": null,
|
||||||
"trace_id": null,
|
"trace_id": null,
|
||||||
"debug": null
|
"debug": null
|
||||||
|
|
@ -112,8 +131,8 @@
|
||||||
{
|
{
|
||||||
"message_id": "agent-user-006",
|
"message_id": "agent-user-006",
|
||||||
"role": "user",
|
"role": "user",
|
||||||
"text": "Отдельно по контрагенту Группа СВК, без опоры на прошлый диалог: сколько денег прошло, что входящее, что исходящее и есть ли документы или движения, на которых это основано?",
|
"text": "Покажи документы по этой цепочке и не смешивай Группа СВК с организацией ООО Альтернатива Плюс.",
|
||||||
"created_at": "2026-05-09T18:30:50+00:00",
|
"created_at": "2026-05-24T23:06:41+00:00",
|
||||||
"reply_type": null,
|
"reply_type": null,
|
||||||
"trace_id": null,
|
"trace_id": null,
|
||||||
"debug": null
|
"debug": null
|
||||||
|
|
@ -121,8 +140,8 @@
|
||||||
{
|
{
|
||||||
"message_id": "agent-user-007",
|
"message_id": "agent-user-007",
|
||||||
"role": "user",
|
"role": "user",
|
||||||
"text": "Покажи документы по этой цепочке и не смешивай Группа СВК с организацией ООО Альтернатива Плюс.",
|
"text": "Собери короткий итог: что мы подтвердили по компании, что отдельно по Группа СВК, какие выводы можно делать и какие нельзя.",
|
||||||
"created_at": "2026-05-09T18:30:50+00:00",
|
"created_at": "2026-05-24T23:06:41+00:00",
|
||||||
"reply_type": null,
|
"reply_type": null,
|
||||||
"trace_id": null,
|
"trace_id": null,
|
||||||
"debug": null
|
"debug": null
|
||||||
|
|
@ -130,8 +149,8 @@
|
||||||
{
|
{
|
||||||
"message_id": "agent-user-008",
|
"message_id": "agent-user-008",
|
||||||
"role": "user",
|
"role": "user",
|
||||||
"text": "Собери короткий итог: что мы подтвердили по компании, что отдельно по Группа СВК, какие выводы можно делать и какие нельзя.",
|
"text": "По-человечески посмотри ООО Альтернатива Плюс за 2020: что по бизнесу видно в 1С по деньгам, НДС, долгам, складу, клиентам/поставщикам и где пока нельзя делать вывод?",
|
||||||
"created_at": "2026-05-09T18:30:50+00:00",
|
"created_at": "2026-05-24T23:06:41+00:00",
|
||||||
"reply_type": null,
|
"reply_type": null,
|
||||||
"trace_id": null,
|
"trace_id": null,
|
||||||
"debug": null
|
"debug": null
|
||||||
|
|
@ -139,8 +158,8 @@
|
||||||
{
|
{
|
||||||
"message_id": "agent-user-009",
|
"message_id": "agent-user-009",
|
||||||
"role": "user",
|
"role": "user",
|
||||||
"text": "Сделай короткий стартовый чек контекста: есть ли уже выбранная компания или контрагент в текущем диалоге; если нет, скажи честно и не выдумывай память про Группа СВК.",
|
"text": "А по деньгам в этом же году кто больше всего занес и кому больше всего ушло? Банк не называй обычным клиентом без оговорки.",
|
||||||
"created_at": "2026-05-09T18:30:50+00:00",
|
"created_at": "2026-05-24T23:06:41+00:00",
|
||||||
"reply_type": null,
|
"reply_type": null,
|
||||||
"trace_id": null,
|
"trace_id": null,
|
||||||
"debug": null
|
"debug": null
|
||||||
|
|
@ -148,8 +167,8 @@
|
||||||
{
|
{
|
||||||
"message_id": "agent-user-010",
|
"message_id": "agent-user-010",
|
||||||
"role": "user",
|
"role": "user",
|
||||||
"text": "Покажи хвосты по счету 60 на август 2020 по ООО Альтернатива Плюс; если точных данных нет, скажи это прямо и не подменяй ответ общим обзором.",
|
"text": "Возьми как выбранного контрагента Группа СВК: сколько по нему входящих денег, исходящих денег и расчетное нетто, на чем это основано?",
|
||||||
"created_at": "2026-05-09T18:30:50+00:00",
|
"created_at": "2026-05-24T23:06:41+00:00",
|
||||||
"reply_type": null,
|
"reply_type": null,
|
||||||
"trace_id": null,
|
"trace_id": null,
|
||||||
"debug": null
|
"debug": null
|
||||||
|
|
@ -157,8 +176,8 @@
|
||||||
{
|
{
|
||||||
"message_id": "agent-user-011",
|
"message_id": "agent-user-011",
|
||||||
"role": "user",
|
"role": "user",
|
||||||
"text": "Что было на складе на март 2021 по доступным данным? Дай прямой ответ и не уводи его в контрагента Группа СВК.",
|
"text": "Теперь по нему покажи документы и не переключайся на организацию.",
|
||||||
"created_at": "2026-05-09T18:30:50+00:00",
|
"created_at": "2026-05-24T23:06:41+00:00",
|
||||||
"reply_type": null,
|
"reply_type": null,
|
||||||
"trace_id": null,
|
"trace_id": null,
|
||||||
"debug": null
|
"debug": null
|
||||||
|
|
@ -166,8 +185,17 @@
|
||||||
{
|
{
|
||||||
"message_id": "agent-user-012",
|
"message_id": "agent-user-012",
|
||||||
"role": "user",
|
"role": "user",
|
||||||
"text": "Вернись к ООО Альтернатива Плюс: сколько всего денег получили и заплатили по всем подтвержденным данным, но не смешивай это с отдельной цепочкой Группа СВК и не называй оборот чистой прибылью.",
|
"text": "И коротко сравни: что у нас подтверждено по компании, что отдельно по выбранному контрагенту, и какие выводы нельзя делать?",
|
||||||
"created_at": "2026-05-09T18:30:50+00:00",
|
"created_at": "2026-05-24T23:06:41+00:00",
|
||||||
|
"reply_type": null,
|
||||||
|
"trace_id": null,
|
||||||
|
"debug": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"message_id": "agent-user-013",
|
||||||
|
"role": "user",
|
||||||
|
"text": "ООО Альтернатива Плюс",
|
||||||
|
"created_at": "2026-05-24T23:06:41+00:00",
|
||||||
"reply_type": null,
|
"reply_type": null,
|
||||||
"trace_id": null,
|
"trace_id": null,
|
||||||
"debug": null
|
"debug": null
|
||||||
|
|
@ -178,33 +206,36 @@
|
||||||
"assistant_prompt_version": null,
|
"assistant_prompt_version": null,
|
||||||
"decomposition_prompt_version": null,
|
"decomposition_prompt_version": null,
|
||||||
"prompt_fingerprint": null,
|
"prompt_fingerprint": null,
|
||||||
"agent_focus": "Automate stage question review, live semantic replay, strong business audit, Lead Codex repair handoff, rerun validation, and final human confirmation.",
|
"agent_focus": "Accepted proof pack for company business overview, SVK counterparty money/documents, selected-object carryover, org clarification, and all-time scope reset",
|
||||||
"architecture_phase": "turnaround_11_agentic_semantic_development_loop",
|
"architecture_phase": "agent_reliability_uplift_business_overview_counterparty",
|
||||||
"source_spec_file": "X:\\1C\\NDC_1C\\docs\\orchestration\\agentic_semantic_development_loop_stage_pack.json",
|
"source_spec_file": "X:\\1C\\NDC_1C\\docs\\orchestration\\agent_business_overview_counterparty_reliability_20260524.json",
|
||||||
"scenario_id": null,
|
"scenario_id": null,
|
||||||
"semantic_tags": [
|
"semantic_tags": [
|
||||||
"business_overview",
|
"business_overview",
|
||||||
|
"colloquial",
|
||||||
"counterparty",
|
"counterparty",
|
||||||
"debt",
|
"debt",
|
||||||
"documents",
|
"documents",
|
||||||
"inventory",
|
"inventory",
|
||||||
"memory",
|
|
||||||
"money",
|
"money",
|
||||||
|
"organization_clarification",
|
||||||
|
"pronoun_followup",
|
||||||
"scope_guard",
|
"scope_guard",
|
||||||
|
"selected_object",
|
||||||
"vat"
|
"vat"
|
||||||
],
|
],
|
||||||
"validation_status": "accepted_domain_pack_loop",
|
"validation_status": "accepted_domain_pack_loop",
|
||||||
"validated_run_dir": "artifacts\\domain_runs\\stage_agent_loops\\agentic_semantic_development_loop\\domain_loops\\asl",
|
"validated_run_dir": "artifacts\\domain_runs\\agent_business_overview_counterparty_proof_20260524_39",
|
||||||
"saved_after_validated_replay": true,
|
"saved_after_validated_replay": true,
|
||||||
"save_gate": {
|
"save_gate": {
|
||||||
"schema_version": "agent_semantic_save_gate_v1",
|
"schema_version": "agent_semantic_save_gate_v1",
|
||||||
"validation_status": "accepted_domain_pack_loop",
|
"validation_status": "accepted_domain_pack_loop",
|
||||||
"validated_run_dir": "artifacts\\domain_runs\\stage_agent_loops\\agentic_semantic_development_loop\\domain_loops\\asl",
|
"validated_run_dir": "artifacts\\domain_runs\\agent_business_overview_counterparty_proof_20260524_39",
|
||||||
"final_status": "accepted",
|
"final_status": "accepted",
|
||||||
"loop_id": "asl",
|
"loop_id": "agent_business_overview_counterparty_proof_20260524_39",
|
||||||
"target_score": 88,
|
"target_score": 90,
|
||||||
"iterations_ran": 1,
|
"iterations_ran": 1,
|
||||||
"quality_score": 91,
|
"quality_score": 92,
|
||||||
"repair_target_count": 0,
|
"repair_target_count": 0,
|
||||||
"repair_target_severity_counts": {
|
"repair_target_severity_counts": {
|
||||||
"P0": 0,
|
"P0": 0,
|
||||||
|
|
@ -212,6 +243,21 @@
|
||||||
"P2": 0
|
"P2": 0
|
||||||
},
|
},
|
||||||
"accepted_gate": true,
|
"accepted_gate": true,
|
||||||
|
"effective_runtime": {
|
||||||
|
"manifest_path": "artifacts\\domain_runs\\agent_business_overview_counterparty_proof_20260524_39\\effective_runtime.json",
|
||||||
|
"runner": "domain_case_loop.run-pack-loop",
|
||||||
|
"git_sha": "7cc65e808eb2430e7d27f2ac786c340aecc7fc50",
|
||||||
|
"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
|
"saved_after_validated_replay": true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1,63 +0,0 @@
|
||||||
{
|
|
||||||
"suite_id": "assistant_saved_session_gen-mo2kcds2-tlqmvng",
|
|
||||||
"suite_version": "0.1.0",
|
|
||||||
"schema_version": "assistant_saved_session_suite_v0_1",
|
|
||||||
"generated_at": "2026-04-17T07:04:48.578Z",
|
|
||||||
"generation_id": "gen-mo2kcds2-tlqmvng",
|
|
||||||
"mode": "saved_user_sessions",
|
|
||||||
"title": "Ручная сессия 17.04.2026, 10:04:19 ТЕМП",
|
|
||||||
"scenario_count": 1,
|
|
||||||
"case_ids": [
|
|
||||||
"SAVED-001"
|
|
||||||
],
|
|
||||||
"cases": [
|
|
||||||
{
|
|
||||||
"case_id": "SAVED-001",
|
|
||||||
"scenario_tag": "saved_user_sessions",
|
|
||||||
"title": "Ручная сессия 17.04.2026, 10:04:19 ТЕМП",
|
|
||||||
"question_type": "followup",
|
|
||||||
"broadness_level": "medium",
|
|
||||||
"turns": [
|
|
||||||
{
|
|
||||||
"user_message": "покажи все документы по чепурнову"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"user_message": "что нам отгружал чепурнов, какой товар или услугу?"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"user_message": "какие остатки на складе на сегодня?"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"user_message": "хвосты по счету 60 на август 2022"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"user_message": "какие остатки на складе на март 2021"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"user_message": "По выбранному объекту \"Столешница 600*3050*26 альмандин\": кто нам это поставил?"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"user_message": "По выбранному объекту \"Столешница 600*3050*26 альмандин\": покажи документы по этой позиции"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"user_message": "покажи еще раз остатки на эту же дату"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"user_message": "какие остатки на складе на март 2016"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"user_message": "на июль 2019"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"user_message": "на сентябрь"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"user_message": "а на март"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"user_message": "это по общей базе"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
@ -1,97 +0,0 @@
|
||||||
{
|
|
||||||
"suite_id": "assistant_saved_session_gen-ag04241406-abe4d8",
|
|
||||||
"suite_version": "0.1.0",
|
|
||||||
"schema_version": "assistant_saved_session_suite_v0_1",
|
|
||||||
"generated_at": "2026-04-24T14:06:30+00:00",
|
|
||||||
"generation_id": "gen-ag04241406-abe4d8",
|
|
||||||
"mode": "saved_user_sessions",
|
|
||||||
"title": "AGENT | Post-F cross-stage semantic integrity canary",
|
|
||||||
"domain": "address_post_f_cross_stage_canary_agent",
|
|
||||||
"scenario_count": 1,
|
|
||||||
"case_ids": [
|
|
||||||
"SAVED-001"
|
|
||||||
],
|
|
||||||
"cases": [
|
|
||||||
{
|
|
||||||
"case_id": "SAVED-001",
|
|
||||||
"scenario_tag": "agent_saved_user_sessions",
|
|
||||||
"title": "AGENT | Post-F cross-stage semantic integrity canary",
|
|
||||||
"question_type": "followup",
|
|
||||||
"broadness_level": "medium",
|
|
||||||
"turns": [
|
|
||||||
{
|
|
||||||
"user_message": "Мне нужно понять, где в 1С по НДС вообще лежат данные. Какие объекты стоит смотреть по НДС?"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"user_message": "Хорошо, тогда покажи движения по ООО Альтернатива Плюс."
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"user_message": "За 2020 год."
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"user_message": "А теперь по документам?"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"user_message": "А теперь за 2021 год?"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"user_message": "А теперь за все время?"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"user_message": "С НДС закончили. Новая тема: покажи документы по Жуковке 51."
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"user_message": "Хорошо, а теперь платежи по нему тоже покажи."
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"user_message": "А по нему договоры?"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"user_message": "А по нему документы?"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"user_message": "А за 2021?"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"user_message": "С Жуковкой закончили. Теперь другая задача: быстрый денежный срез по одной организации. Если для ответа нужна организация, просто уточни ее. Сколько вообще входящих денег было за 2020 год?"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"user_message": "По ООО Альтернатива Плюс."
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"user_message": "Понял, тогда за все время."
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"user_message": "Хорошо. А что по ООО Альтернатива Плюс больше в 2020 году: входящие или исходящие деньги?"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"user_message": "И кто больше всего принес денег этой организации в 2020 году?"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"user_message": "А в 2021 году?"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"user_message": "Теперь отдельная тема по конкретному контрагенту. Найди в 1С Группу СВК."
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"user_message": "Сколько получили по нему за 2020 год?"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"user_message": "А теперь сколько заплатили?"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"user_message": "А какое нетто?"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"user_message": "А по документам?"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"user_message": "А по движениям?"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"user_message": "А теперь тот же смысл за 2021 год."
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
@ -1,58 +0,0 @@
|
||||||
{
|
|
||||||
"suite_id": "assistant_saved_session_gen-ag04241710-bdb248",
|
|
||||||
"suite_version": "0.1.0",
|
|
||||||
"schema_version": "assistant_saved_session_suite_v0_1",
|
|
||||||
"generated_at": "2026-04-24T17:10:31+00:00",
|
|
||||||
"generation_id": "gen-ag04241710-bdb248",
|
|
||||||
"mode": "saved_user_sessions",
|
|
||||||
"title": "AGENT | Post-F ручные провалы VAT revenue item-flow live3",
|
|
||||||
"domain": "address_post_f_manual_failures",
|
|
||||||
"scenario_count": 1,
|
|
||||||
"case_ids": [
|
|
||||||
"SAVED-001"
|
|
||||||
],
|
|
||||||
"cases": [
|
|
||||||
{
|
|
||||||
"case_id": "SAVED-001",
|
|
||||||
"scenario_tag": "agent_saved_user_sessions",
|
|
||||||
"title": "AGENT | Post-F ручные провалы VAT revenue item-flow live3",
|
|
||||||
"question_type": "followup",
|
|
||||||
"broadness_level": "medium",
|
|
||||||
"turns": [
|
|
||||||
{
|
|
||||||
"user_message": "кайф - что там на складе по остаткам?"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"user_message": "АЛЬТЕРНАТИВА"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"user_message": "март 2016"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"user_message": "По выбранному объекту \"Рабочая станция универсального специалиста (индивидуальное изготовление)\": где взяли это?"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"user_message": "ндс можешь прикинуть на дату покупки рабочей станции?"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"user_message": "прикинь какой ндс нам надо заплатить на февраль 2017"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"user_message": "кто у нас самый доходный клиент за все время"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"user_message": "по чепурнову покажи все доки"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"user_message": "а по свк"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"user_message": "а сейчас у нас есть что на складе?"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"user_message": "что нам отгружал чепурнов? какой товар или услугу?"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
@ -1,40 +0,0 @@
|
||||||
{
|
|
||||||
"suite_id": "assistant_saved_session_gen-ag05100949-2a5ed8",
|
|
||||||
"suite_version": "0.1.0",
|
|
||||||
"schema_version": "assistant_saved_session_suite_v0_1",
|
|
||||||
"generated_at": "2026-05-10T09:49:21+00:00",
|
|
||||||
"generation_id": "gen-ag05100949-2a5ed8",
|
|
||||||
"mode": "saved_user_sessions",
|
|
||||||
"title": "AGENT | Phase 91 route-candidate-driven enablement loop canary",
|
|
||||||
"domain": "address_phase91_route_candidate_driven_enablement_loop",
|
|
||||||
"scenario_count": 1,
|
|
||||||
"case_ids": [
|
|
||||||
"SAVED-001"
|
|
||||||
],
|
|
||||||
"cases": [
|
|
||||||
{
|
|
||||||
"case_id": "SAVED-001",
|
|
||||||
"scenario_tag": "agent_saved_user_sessions",
|
|
||||||
"title": "AGENT | Phase 91 route-candidate-driven enablement loop canary",
|
|
||||||
"question_type": "followup",
|
|
||||||
"broadness_level": "medium",
|
|
||||||
"turns": [
|
|
||||||
{
|
|
||||||
"user_message": "какой контрагент принес больше всего денег за 2020 год?"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"user_message": "ООО Альтернатива Плюс"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"user_message": "а теперь сколько лет мы работаем с Группа СВК?"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"user_message": "покажи движения по НДС за 2020 по ООО Альтернатива Плюс"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"user_message": "можно ли по этим данным точно подтвердить резерв под неликвиды на складе?"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
@ -1,43 +0,0 @@
|
||||||
{
|
|
||||||
"suite_id": "assistant_saved_session_gen-ag05101213-596d99",
|
|
||||||
"suite_version": "0.1.0",
|
|
||||||
"schema_version": "assistant_saved_session_suite_v0_1",
|
|
||||||
"generated_at": "2026-05-10T12:13:26+00:00",
|
|
||||||
"generation_id": "gen-ag05101213-596d99",
|
|
||||||
"mode": "saved_user_sessions",
|
|
||||||
"title": "AGENT | Phase 93 accounting profit-margin reviewed route",
|
|
||||||
"domain": "address_phase93_accounting_profit_margin_reviewed_route",
|
|
||||||
"scenario_count": 1,
|
|
||||||
"case_ids": [
|
|
||||||
"SAVED-001"
|
|
||||||
],
|
|
||||||
"cases": [
|
|
||||||
{
|
|
||||||
"case_id": "SAVED-001",
|
|
||||||
"scenario_tag": "agent_saved_user_sessions",
|
|
||||||
"title": "AGENT | Phase 93 accounting profit-margin reviewed route",
|
|
||||||
"question_type": "followup",
|
|
||||||
"broadness_level": "medium",
|
|
||||||
"turns": [
|
|
||||||
{
|
|
||||||
"user_message": "по ООО Альтернатива Плюс за 2020 можно точно сказать чистую прибыль и маржу?"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"user_message": "а это прибыль или убыток, коротко?"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"user_message": "по этой же компании на конец 2020 можно точно понять, какая дебиторка просрочена?"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"user_message": "тогда НДС за 2020 по ООО Альтернатива Плюс какой?"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"user_message": "а какой контрагент принес больше всего денег за 2020?"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"user_message": "по ООО Альтернатива Плюс на конец 2020 можно точно подтвердить резерв под неликвиды на складе?"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
@ -1,46 +0,0 @@
|
||||||
{
|
|
||||||
"suite_id": "assistant_saved_session_gen-ag05101319-c04f79",
|
|
||||||
"suite_version": "0.1.0",
|
|
||||||
"schema_version": "assistant_saved_session_suite_v0_1",
|
|
||||||
"generated_at": "2026-05-10T13:19:22+00:00",
|
|
||||||
"generation_id": "gen-ag05101319-c04f79",
|
|
||||||
"mode": "saved_user_sessions",
|
|
||||||
"title": "AGENT | Phase 94 debt due-date aging reviewed route",
|
|
||||||
"domain": "address_phase94_debt_due_date_aging_reviewed_route",
|
|
||||||
"scenario_count": 1,
|
|
||||||
"case_ids": [
|
|
||||||
"SAVED-001"
|
|
||||||
],
|
|
||||||
"cases": [
|
|
||||||
{
|
|
||||||
"case_id": "SAVED-001",
|
|
||||||
"scenario_tag": "agent_saved_user_sessions",
|
|
||||||
"title": "AGENT | Phase 94 debt due-date aging reviewed route",
|
|
||||||
"question_type": "followup",
|
|
||||||
"broadness_level": "medium",
|
|
||||||
"turns": [
|
|
||||||
{
|
|
||||||
"user_message": "по ООО Альтернатива Плюс на конец 2020 можно точно понять, какая дебиторка просрочена?"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"user_message": "то есть просрочку доказать нельзя, коротко почему?"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"user_message": "а чистая прибыль и маржа за 2020 по этой же компании какие?"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"user_message": "НДС за 2020 по ООО Альтернатива Плюс какой?"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"user_message": "а кто принес больше всего денег за 2020?"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"user_message": "по ООО Альтернатива Плюс на конец 2020 можно точно подтвердить резерв под неликвиды на складе?"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"user_message": "а зависимость от одного поставщика за 2020 можно точно оценить?"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
@ -1,46 +0,0 @@
|
||||||
{
|
|
||||||
"suite_id": "assistant_saved_session_gen-ag05121357-9ea5d6",
|
|
||||||
"suite_version": "0.1.0",
|
|
||||||
"schema_version": "assistant_saved_session_suite_v0_1",
|
|
||||||
"generated_at": "2026-05-12T13:57:26+00:00",
|
|
||||||
"generation_id": "gen-ag05121357-9ea5d6",
|
|
||||||
"mode": "saved_user_sessions",
|
|
||||||
"title": "AGENT | Phase 95 vendor/procurement quality reviewed route",
|
|
||||||
"domain": "address_phase95_vendor_procurement_quality_reviewed_route",
|
|
||||||
"scenario_count": 1,
|
|
||||||
"case_ids": [
|
|
||||||
"SAVED-001"
|
|
||||||
],
|
|
||||||
"cases": [
|
|
||||||
{
|
|
||||||
"case_id": "SAVED-001",
|
|
||||||
"scenario_tag": "agent_saved_user_sessions",
|
|
||||||
"title": "AGENT | Phase 95 vendor/procurement quality reviewed route",
|
|
||||||
"question_type": "followup",
|
|
||||||
"broadness_level": "medium",
|
|
||||||
"turns": [
|
|
||||||
{
|
|
||||||
"user_message": "по ООО Альтернатива Плюс за 2020 есть ли риск, что мы зависим от одного поставщика?"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"user_message": "а банк из этого ответа считать обычным поставщиком?"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"user_message": "по этой же компании на конец 2020 можно точно понять, какая дебиторка просрочена?"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"user_message": "а чистая прибыль и маржа за 2020 какие?"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"user_message": "НДС за 2020 по ООО Альтернатива Плюс какой?"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"user_message": "а кто принес больше всего денег за 2020?"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"user_message": "по ООО Альтернатива Плюс на конец 2020 можно точно подтвердить резерв под неликвиды на складе?"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
@ -1,43 +0,0 @@
|
||||||
{
|
|
||||||
"suite_id": "assistant_saved_session_gen-ag05121628-50ea6c",
|
|
||||||
"suite_version": "0.1.0",
|
|
||||||
"schema_version": "assistant_saved_session_suite_v0_1",
|
|
||||||
"generated_at": "2026-05-12T16:28:41+00:00",
|
|
||||||
"generation_id": "gen-ag05121628-50ea6c",
|
|
||||||
"mode": "saved_user_sessions",
|
|
||||||
"title": "AGENT | Phase20 continuity/ranking/self-scope accepted replay 2026-05-12",
|
|
||||||
"domain": "address_phase20_continuity_stabilization",
|
|
||||||
"scenario_count": 1,
|
|
||||||
"case_ids": [
|
|
||||||
"SAVED-001"
|
|
||||||
],
|
|
||||||
"cases": [
|
|
||||||
{
|
|
||||||
"case_id": "SAVED-001",
|
|
||||||
"scenario_tag": "agent_saved_user_sessions",
|
|
||||||
"title": "AGENT | Phase20 continuity/ranking/self-scope accepted replay 2026-05-12",
|
|
||||||
"question_type": "followup",
|
|
||||||
"broadness_level": "medium",
|
|
||||||
"turns": [
|
|
||||||
{
|
|
||||||
"user_message": "кто у нас самый доходный клиент за все время?"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"user_message": "какой у нас самый доходный год?"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"user_message": "кто нам должен денег на май 2017?"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"user_message": "а какой ндс мы должны примерно заплатить за этот период?"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"user_message": "мы должны комуто денег на сегодня?"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"user_message": "а нам?"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
@ -1,31 +0,0 @@
|
||||||
{
|
|
||||||
"suite_id": "assistant_saved_session_gen-ag05122057-c9786e",
|
|
||||||
"suite_version": "0.1.0",
|
|
||||||
"schema_version": "assistant_saved_session_suite_v0_1",
|
|
||||||
"generated_at": "2026-05-12T20:57:28+00:00",
|
|
||||||
"generation_id": "gen-ag05122057-c9786e",
|
|
||||||
"mode": "saved_user_sessions",
|
|
||||||
"title": "AGENT | Phase 96 inventory reserve/liquidation quality-events",
|
|
||||||
"domain": "address_phase96_inventory_reserve_liquidation_quality",
|
|
||||||
"scenario_count": 1,
|
|
||||||
"case_ids": [
|
|
||||||
"SAVED-001"
|
|
||||||
],
|
|
||||||
"cases": [
|
|
||||||
{
|
|
||||||
"case_id": "SAVED-001",
|
|
||||||
"scenario_tag": "agent_saved_user_sessions",
|
|
||||||
"title": "AGENT | Phase 96 inventory reserve/liquidation quality-events",
|
|
||||||
"question_type": "followup",
|
|
||||||
"broadness_level": "medium",
|
|
||||||
"turns": [
|
|
||||||
{
|
|
||||||
"user_message": "По ООО Альтернатива Плюс за 2020 год проверь склад: были ли списания товаров, оприходования/корректировки, инвентаризации, переоценки, резервы под неликвиды или ликвидационная стоимость? Скажи коротко и честно, что подтверждено 1С, а что нельзя утверждать."
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"user_message": "А по этим же данным можно сказать, что склад ликвидный и неликвидов нет?"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
@ -1,37 +0,0 @@
|
||||||
{
|
|
||||||
"suite_id": "assistant_saved_session_gen-ag05122250-4451a8",
|
|
||||||
"suite_version": "0.1.0",
|
|
||||||
"schema_version": "assistant_saved_session_suite_v0_1",
|
|
||||||
"generated_at": "2026-05-12T22:50:23+00:00",
|
|
||||||
"generation_id": "gen-ag05122250-4451a8",
|
|
||||||
"mode": "saved_user_sessions",
|
|
||||||
"title": "AGENT | Phase 97 financial counterparty flow hints replay",
|
|
||||||
"domain": "address_phase97_financial_counterparty_flow_hints",
|
|
||||||
"scenario_count": 1,
|
|
||||||
"case_ids": [
|
|
||||||
"SAVED-001"
|
|
||||||
],
|
|
||||||
"cases": [
|
|
||||||
{
|
|
||||||
"case_id": "SAVED-001",
|
|
||||||
"scenario_tag": "agent_saved_user_sessions",
|
|
||||||
"title": "AGENT | Phase 97 financial counterparty flow hints replay",
|
|
||||||
"question_type": "followup",
|
|
||||||
"broadness_level": "medium",
|
|
||||||
"turns": [
|
|
||||||
{
|
|
||||||
"user_message": "По ООО Альтернатива Плюс за 2020 отдельно посмотри платежи в СБЕРБАНК: это обычный поставщик или банковский/финансовый поток? Дай коротко и по проверенным данным 1С."
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"user_message": "А если по этой же компании СБЕРБАНК встречается во входящих поступлениях, это клиентская выручка или там может быть кредитный/депозитный банковский смысл? Не притягивай, скажи что подтверждено."
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"user_message": "Теперь дай взрослый краткий обзор ООО Альтернатива Плюс за 2020: входящие, исходящие, нетто и отдельно отметь, если в топах есть банк, почему его нельзя читать как обычного клиента или поставщика."
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"user_message": "А теперь отдельно по Группа СВК за 2020: сколько денег получили, сколько заплатили и какое нетто?"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
@ -1,43 +0,0 @@
|
||||||
{
|
|
||||||
"suite_id": "assistant_saved_session_gen-ag05122315-f1e27c",
|
|
||||||
"suite_version": "0.1.0",
|
|
||||||
"schema_version": "assistant_saved_session_suite_v0_1",
|
|
||||||
"generated_at": "2026-05-12T23:15:48+00:00",
|
|
||||||
"generation_id": "gen-ag05122315-f1e27c",
|
|
||||||
"mode": "saved_user_sessions",
|
|
||||||
"title": "AGENT | Phase 98 limit honesty and business-language replay",
|
|
||||||
"domain": "address_phase98_limit_honesty_business_language",
|
|
||||||
"scenario_count": 1,
|
|
||||||
"case_ids": [
|
|
||||||
"SAVED-001"
|
|
||||||
],
|
|
||||||
"cases": [
|
|
||||||
{
|
|
||||||
"case_id": "SAVED-001",
|
|
||||||
"scenario_tag": "agent_saved_user_sessions",
|
|
||||||
"title": "AGENT | Phase 98 limit honesty and business-language replay",
|
|
||||||
"question_type": "followup",
|
|
||||||
"broadness_level": "medium",
|
|
||||||
"turns": [
|
|
||||||
{
|
|
||||||
"user_message": "По ООО Альтернатива Плюс на конец 2020 можно точно понять, какая дебиторка просрочена?"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"user_message": "То есть просрочку доказать нельзя, коротко почему?"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"user_message": "НДС за 2020 по ООО Альтернатива Плюс какой?"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"user_message": "А кто принес больше всего денег за 2020?"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"user_message": "По ООО Альтернатива Плюс на конец 2020 можно точно подтвердить резерв под неликвиды на складе?"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"user_message": "А зависимость от одного поставщика за 2020 можно точно оценить?"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
@ -1,37 +0,0 @@
|
||||||
{
|
|
||||||
"suite_id": "assistant_saved_session_gen-ag05131009-f08174",
|
|
||||||
"suite_version": "0.1.0",
|
|
||||||
"schema_version": "assistant_saved_session_suite_v0_1",
|
|
||||||
"generated_at": "2026-05-13T10:09:01+00:00",
|
|
||||||
"generation_id": "gen-ag05131009-f08174",
|
|
||||||
"mode": "saved_user_sessions",
|
|
||||||
"title": "AGENT | Phase 99 large-query budget and continuation policy replay",
|
|
||||||
"domain": "address_phase99_large_query_budget_continuation",
|
|
||||||
"scenario_count": 1,
|
|
||||||
"case_ids": [
|
|
||||||
"SAVED-001"
|
|
||||||
],
|
|
||||||
"cases": [
|
|
||||||
{
|
|
||||||
"case_id": "SAVED-001",
|
|
||||||
"scenario_tag": "agent_saved_user_sessions",
|
|
||||||
"title": "AGENT | Phase 99 large-query budget and continuation policy replay",
|
|
||||||
"question_type": "followup",
|
|
||||||
"broadness_level": "medium",
|
|
||||||
"turns": [
|
|
||||||
{
|
|
||||||
"user_message": "Дай взрослый бизнес-обзор ООО Альтернатива Плюс за 2020: входящие, исходящие, нетто, кто основные источники денег и где важные ограничения."
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"user_message": "То есть это можно считать прибылью за 2020 или нет? Коротко."
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"user_message": "А кто за 2020 принес больше всего денег, и если там банк, не называй его обычным клиентом."
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"user_message": "По исходящим за 2020 есть зависимость от одного поставщика или это только денежная концентрация?"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
@ -1,34 +0,0 @@
|
||||||
{
|
|
||||||
"suite_id": "assistant_saved_session_gen-ag05131028-234e5e",
|
|
||||||
"suite_version": "0.1.0",
|
|
||||||
"schema_version": "assistant_saved_session_suite_v0_1",
|
|
||||||
"generated_at": "2026-05-13T10:28:45+00:00",
|
|
||||||
"generation_id": "gen-ag05131028-234e5e",
|
|
||||||
"mode": "saved_user_sessions",
|
|
||||||
"title": "AGENT | Phase 100 large-query continuation UX replay",
|
|
||||||
"domain": "address_phase100_large_query_continuation_ux",
|
|
||||||
"scenario_count": 1,
|
|
||||||
"case_ids": [
|
|
||||||
"SAVED-001"
|
|
||||||
],
|
|
||||||
"cases": [
|
|
||||||
{
|
|
||||||
"case_id": "SAVED-001",
|
|
||||||
"scenario_tag": "agent_saved_user_sessions",
|
|
||||||
"title": "AGENT | Phase 100 large-query continuation UX replay",
|
|
||||||
"question_type": "followup",
|
|
||||||
"broadness_level": "medium",
|
|
||||||
"turns": [
|
|
||||||
{
|
|
||||||
"user_message": "Дай общий бизнес-обзор ООО Альтернатива Плюс за весь доступный период: входящие, исходящие, нетто, лучший год. Если срез слишком широкий, не выдумывай полный итог, а скажи как безопасно дособрать."
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"user_message": "Ок, тогда дособери конкретно 2020: входящие, исходящие и расчетное денежное нетто."
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"user_message": "Это можно считать прибылью за 2020 или нет? Ответь коротко и по делу."
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
@ -1,46 +0,0 @@
|
||||||
{
|
|
||||||
"suite_id": "assistant_saved_session_gen-ag05131044-cbe2ff",
|
|
||||||
"suite_version": "0.1.0",
|
|
||||||
"schema_version": "assistant_saved_session_suite_v0_1",
|
|
||||||
"generated_at": "2026-05-13T10:44:38+00:00",
|
|
||||||
"generation_id": "gen-ag05131044-cbe2ff",
|
|
||||||
"mode": "saved_user_sessions",
|
|
||||||
"title": "AGENT | Phase 101 inventory root scope without warehouse clarification replay",
|
|
||||||
"domain": "address_phase101_inventory_root_scope_no_warehouse_clarification",
|
|
||||||
"scenario_count": 1,
|
|
||||||
"case_ids": [
|
|
||||||
"SAVED-001"
|
|
||||||
],
|
|
||||||
"cases": [
|
|
||||||
{
|
|
||||||
"case_id": "SAVED-001",
|
|
||||||
"scenario_tag": "agent_saved_user_sessions",
|
|
||||||
"title": "AGENT | Phase 101 inventory root scope without warehouse clarification replay",
|
|
||||||
"question_type": "followup",
|
|
||||||
"broadness_level": "medium",
|
|
||||||
"turns": [
|
|
||||||
{
|
|
||||||
"user_message": "приветик - че как там дела"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"user_message": "расскажи что можешь интересного"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"user_message": "кайф - что там на складе по остаткам?"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"user_message": "АЛЬТЕРНАТИВА"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"user_message": "а исторические остатки на другие даты умеешь?"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"user_message": "давай на июнь 2017"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"user_message": "март 2016"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
@ -1,43 +0,0 @@
|
||||||
{
|
|
||||||
"suite_id": "assistant_saved_session_gen-ag05131121-8c41ab",
|
|
||||||
"suite_version": "0.1.0",
|
|
||||||
"schema_version": "assistant_saved_session_suite_v0_1",
|
|
||||||
"generated_at": "2026-05-13T11:21:52+00:00",
|
|
||||||
"generation_id": "gen-ag05131121-8c41ab",
|
|
||||||
"mode": "saved_user_sessions",
|
|
||||||
"title": "AGENT | Phase 102 debt mirror clean-scope polarity replay",
|
|
||||||
"domain": "address_phase102_debt_mirror_clean_scope_polarity",
|
|
||||||
"scenario_count": 1,
|
|
||||||
"case_ids": [
|
|
||||||
"SAVED-001"
|
|
||||||
],
|
|
||||||
"cases": [
|
|
||||||
{
|
|
||||||
"case_id": "SAVED-001",
|
|
||||||
"scenario_tag": "agent_saved_user_sessions",
|
|
||||||
"title": "AGENT | Phase 102 debt mirror clean-scope polarity replay",
|
|
||||||
"question_type": "followup",
|
|
||||||
"broadness_level": "medium",
|
|
||||||
"turns": [
|
|
||||||
{
|
|
||||||
"user_message": "Альтернатива Плюс"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"user_message": "мы кому реально должны денег на сегодня? коротко, чистый долг к оплате, без встречных обеспечений как основного долга"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"user_message": "а Комитету государственных услуг мы реально должны эти 3,6 млн или это встречное обеспечение/зачет? объясни именно по смыслу долга"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"user_message": "а нам Комитет государственных услуг тоже должен 3,6 млн? это же та же сумма — скажи честно, это дебиторка или встречная часть?"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"user_message": "тогда кто нам реально должен денег на сегодня? именно чистая дебиторка"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"user_message": "а мы кому?"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
@ -1,43 +0,0 @@
|
||||||
{
|
|
||||||
"suite_id": "assistant_saved_session_gen-ag05131200-0ed59a",
|
|
||||||
"suite_version": "0.1.0",
|
|
||||||
"schema_version": "assistant_saved_session_suite_v0_1",
|
|
||||||
"generated_at": "2026-05-13T12:00:47+00:00",
|
|
||||||
"generation_id": "gen-ag05131200-0ed59a",
|
|
||||||
"mode": "saved_user_sessions",
|
|
||||||
"title": "AGENT | Phase 103 financial role and purpose arbitration replay",
|
|
||||||
"domain": "address_phase103_financial_role_purpose_arbitration",
|
|
||||||
"scenario_count": 1,
|
|
||||||
"case_ids": [
|
|
||||||
"SAVED-001"
|
|
||||||
],
|
|
||||||
"cases": [
|
|
||||||
{
|
|
||||||
"case_id": "SAVED-001",
|
|
||||||
"scenario_tag": "agent_saved_user_sessions",
|
|
||||||
"title": "AGENT | Phase 103 financial role and purpose arbitration replay",
|
|
||||||
"question_type": "followup",
|
|
||||||
"broadness_level": "medium",
|
|
||||||
"turns": [
|
|
||||||
{
|
|
||||||
"user_message": "Альтернатива Плюс"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"user_message": "По СБЕРБАНКУ за 2020 покажи коротко: сколько денег входило и уходило, и что это по смыслу в 1С — клиентская выручка, поставщик, комиссия, кредит или другой финансовый поток?"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"user_message": "Если СБЕРБАНК есть во входящих поступлениях, можно ли считать его нашим клиентом и выручкой? Скажи по подтвержденным строкам, без притягивания."
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"user_message": "А если деньги уходили в СБЕРБАНК, это наш поставщик или финансовые списания? Раздели по смыслу и покажи основание."
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"user_message": "Теперь дай взрослый обзор за 2020 по компании: входящие, исходящие, нетто, топы, но банк в топах отдельно объясни как финансовый поток, если по назначению он не обычный клиент или поставщик."
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"user_message": "А теперь отдельно по Группа СВК за 2020: сколько денег получили, сколько заплатили и какое нетто?"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
@ -1,37 +0,0 @@
|
||||||
{
|
|
||||||
"suite_id": "assistant_saved_session_gen-ag05131226-630ddf",
|
|
||||||
"suite_version": "0.1.0",
|
|
||||||
"schema_version": "assistant_saved_session_suite_v0_1",
|
|
||||||
"generated_at": "2026-05-13T12:26:18+00:00",
|
|
||||||
"generation_id": "gen-ag05131226-630ddf",
|
|
||||||
"mode": "saved_user_sessions",
|
|
||||||
"title": "AGENT | Phase 104 generic role-tail anchor hygiene replay",
|
|
||||||
"domain": "address_phase104_generic_role_tail_anchor_hygiene",
|
|
||||||
"scenario_count": 1,
|
|
||||||
"case_ids": [
|
|
||||||
"SAVED-001"
|
|
||||||
],
|
|
||||||
"cases": [
|
|
||||||
{
|
|
||||||
"case_id": "SAVED-001",
|
|
||||||
"scenario_tag": "agent_saved_user_sessions",
|
|
||||||
"title": "AGENT | Phase 104 generic role-tail anchor hygiene replay",
|
|
||||||
"question_type": "followup",
|
|
||||||
"broadness_level": "medium",
|
|
||||||
"turns": [
|
|
||||||
{
|
|
||||||
"user_message": "Альтернатива Плюс"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"user_message": "Теперь дай взрослый обзор за 2020 по компании: входящие, исходящие, нетто, топы, но банк в топах отдельно объясни как финансовый поток, если по назначению он не обычный клиент или поставщик."
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"user_message": "А отдельно по СБЕРБАНКУ: он для нас клиент, поставщик или финансовый поток? Дай коротко по подтвержденным строкам."
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"user_message": "А теперь по поставщику Группа СВК за 2020: сколько мы ему заплатили и какой общий денежный смысл?"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
@ -1,40 +0,0 @@
|
||||||
{
|
|
||||||
"suite_id": "assistant_saved_session_gen-ag05221319-4035f5",
|
|
||||||
"suite_version": "0.1.0",
|
|
||||||
"schema_version": "assistant_saved_session_suite_v0_1",
|
|
||||||
"generated_at": "2026-05-22T13:19:31+00:00",
|
|
||||||
"generation_id": "gen-ag05221319-4035f5",
|
|
||||||
"mode": "saved_user_sessions",
|
|
||||||
"title": "AGENT | Inventory margin-ranking accepted replay 2026-05-22",
|
|
||||||
"domain": "inventory_margin_ranking",
|
|
||||||
"scenario_count": 1,
|
|
||||||
"case_ids": [
|
|
||||||
"SAVED-001"
|
|
||||||
],
|
|
||||||
"cases": [
|
|
||||||
{
|
|
||||||
"case_id": "SAVED-001",
|
|
||||||
"scenario_tag": "agent_saved_user_sessions",
|
|
||||||
"title": "AGENT | Inventory margin-ranking accepted replay 2026-05-22",
|
|
||||||
"question_type": "followup",
|
|
||||||
"broadness_level": "medium",
|
|
||||||
"turns": [
|
|
||||||
{
|
|
||||||
"user_message": "Какая номеклатура товара реализована с высокой прибылью какая с низкой"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"user_message": "сентябрь 2017"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"user_message": "покажи найденные строки себестоимостной базы"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"user_message": "расширь до 2017 года"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"user_message": "анализ по 41 счету а не 01"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
@ -1,34 +0,0 @@
|
||||||
{
|
|
||||||
"suite_id": "assistant_saved_session_gen-ag05221957-713bbd",
|
|
||||||
"suite_version": "0.1.0",
|
|
||||||
"schema_version": "assistant_saved_session_suite_v0_1",
|
|
||||||
"generated_at": "2026-05-22T19:57:37+00:00",
|
|
||||||
"generation_id": "gen-ag05221957-713bbd",
|
|
||||||
"mode": "saved_user_sessions",
|
|
||||||
"title": "AGENT | Hot value-flow discovery handoff",
|
|
||||||
"domain": "autonomy_hot_value_flow_handoff",
|
|
||||||
"scenario_count": 1,
|
|
||||||
"case_ids": [
|
|
||||||
"SAVED-001"
|
|
||||||
],
|
|
||||||
"cases": [
|
|
||||||
{
|
|
||||||
"case_id": "SAVED-001",
|
|
||||||
"scenario_tag": "agent_saved_user_sessions",
|
|
||||||
"title": "AGENT | Hot value-flow discovery handoff",
|
|
||||||
"question_type": "followup",
|
|
||||||
"broadness_level": "medium",
|
|
||||||
"turns": [
|
|
||||||
{
|
|
||||||
"user_message": "Сколько входящих денег за 2020 год по ООО Альтернатива Плюс без разреза по контрагентам?"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"user_message": "Сколько исходящих денег за 2020 год по ООО Альтернатива Плюс без разреза по контрагентам?"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"user_message": "А всего сколько денег пришло в ООО Альтернатива Плюс за 2020, без топов и без контрагентов?"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
@ -1,70 +0,0 @@
|
||||||
{
|
|
||||||
"suite_id": "assistant_saved_session_gen-ag05231858-323f86",
|
|
||||||
"suite_version": "0.1.0",
|
|
||||||
"schema_version": "assistant_saved_session_suite_v0_1",
|
|
||||||
"generated_at": "2026-05-23T18:58:48+00:00",
|
|
||||||
"generation_id": "gen-ag05231858-323f86",
|
|
||||||
"mode": "saved_user_sessions",
|
|
||||||
"title": "AGENT | Autonomy business quality pack",
|
|
||||||
"domain": "autonomy_business_quality",
|
|
||||||
"scenario_count": 1,
|
|
||||||
"case_ids": [
|
|
||||||
"SAVED-001"
|
|
||||||
],
|
|
||||||
"cases": [
|
|
||||||
{
|
|
||||||
"case_id": "SAVED-001",
|
|
||||||
"scenario_tag": "agent_saved_user_sessions",
|
|
||||||
"title": "AGENT | Autonomy business quality pack",
|
|
||||||
"question_type": "followup",
|
|
||||||
"broadness_level": "medium",
|
|
||||||
"turns": [
|
|
||||||
{
|
|
||||||
"user_message": "Сколько входящих денег за 2020 год по ООО Альтернатива Плюс без разреза по контрагентам?"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"user_message": "Сколько исходящих денег за 2020 год по ООО Альтернатива Плюс без разреза по контрагентам?"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"user_message": "А всего сколько денег пришло в ООО Альтернатива Плюс за 2020, без топов и без контрагентов?"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"user_message": "Теперь дай взрослый обзор за 2020 по компании: входящие, исходящие, нетто, топы, но банк в топах отдельно объясни как финансовый поток."
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"user_message": "скока денег альтернатива заработала за 20 год?"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"user_message": "а это чистая прибыль?"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"user_message": "Какая чистая прибыль по ООО Альтернатива Плюс за 2020?"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"user_message": "А отдельно по СБЕРБАНКУ: он для нас клиент, поставщик или финансовый поток? Дай коротко по подтвержденным строкам."
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"user_message": "какое нетто по деньгам с Группа СВК за 2020 год: сколько получили и сколько заплатили?"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"user_message": "кому мы должны на конец 2020?"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"user_message": "а нам кто должен на конец 2020?"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"user_message": "сколько НДС надо заплатить в налоговую за декабрь 2019?"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"user_message": "Как ты оценишь деятельность компании?"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"user_message": "Какая номенклатура товара реализована с высокой прибылью какая с низкой?"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"user_message": "май 2020"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
@ -1,12 +1,12 @@
|
||||||
{
|
{
|
||||||
"suite_id": "assistant_saved_session_gen-ag05091830-062fc9",
|
"suite_id": "assistant_saved_session_gen-ag05242306-7b49ab",
|
||||||
"suite_version": "0.1.0",
|
"suite_version": "0.1.0",
|
||||||
"schema_version": "assistant_saved_session_suite_v0_1",
|
"schema_version": "assistant_saved_session_suite_v0_1",
|
||||||
"generated_at": "2026-05-09T18:30:50+00:00",
|
"generated_at": "2026-05-24T23:06:41+00:00",
|
||||||
"generation_id": "gen-ag05091830-062fc9",
|
"generation_id": "gen-ag05242306-7b49ab",
|
||||||
"mode": "saved_user_sessions",
|
"mode": "saved_user_sessions",
|
||||||
"title": "AGENT | Agentic semantic development loop dogfood gate",
|
"title": "AGENT | Бизнес-обзор компании и СВК accepted replay 2026-05-24",
|
||||||
"domain": "agentic_semantic_development_loop_control",
|
"domain": "agent_business_overview_counterparty_reliability",
|
||||||
"scenario_count": 1,
|
"scenario_count": 1,
|
||||||
"case_ids": [
|
"case_ids": [
|
||||||
"SAVED-001"
|
"SAVED-001"
|
||||||
|
|
@ -15,7 +15,7 @@
|
||||||
{
|
{
|
||||||
"case_id": "SAVED-001",
|
"case_id": "SAVED-001",
|
||||||
"scenario_tag": "agent_saved_user_sessions",
|
"scenario_tag": "agent_saved_user_sessions",
|
||||||
"title": "AGENT | Agentic semantic development loop dogfood gate",
|
"title": "AGENT | Бизнес-обзор компании и СВК accepted replay 2026-05-24",
|
||||||
"question_type": "followup",
|
"question_type": "followup",
|
||||||
"broadness_level": "medium",
|
"broadness_level": "medium",
|
||||||
"turns": [
|
"turns": [
|
||||||
|
|
@ -25,9 +25,6 @@
|
||||||
{
|
{
|
||||||
"user_message": "Раскрой деньги подробнее: сколько получили, сколько заплатили, какой чистый денежный поток, кто главный клиент и главный поставщик в 2020."
|
"user_message": "Раскрой деньги подробнее: сколько получили, сколько заплатили, какой чистый денежный поток, кто главный клиент и главный поставщик в 2020."
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"user_message": "А если смотреть за все доступное время, какой самый доходный год по подтвержденным оборотам и почему? Не называй это бухгалтерской прибылью, если чистой прибыли нет."
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"user_message": "Что с НДС за 2020 год по ООО Альтернатива Плюс: какая позиция видна, на чем она основана и чего не хватает для налогового вывода?"
|
"user_message": "Что с НДС за 2020 год по ООО Альтернатива Плюс: какая позиция видна, на чем она основана и чего не хватает для налогового вывода?"
|
||||||
},
|
},
|
||||||
|
|
@ -44,16 +41,22 @@
|
||||||
"user_message": "Собери короткий итог: что мы подтвердили по компании, что отдельно по Группа СВК, какие выводы можно делать и какие нельзя."
|
"user_message": "Собери короткий итог: что мы подтвердили по компании, что отдельно по Группа СВК, какие выводы можно делать и какие нельзя."
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"user_message": "Сделай короткий стартовый чек контекста: есть ли уже выбранная компания или контрагент в текущем диалоге; если нет, скажи честно и не выдумывай память про Группа СВК."
|
"user_message": "По-человечески посмотри ООО Альтернатива Плюс за 2020: что по бизнесу видно в 1С по деньгам, НДС, долгам, складу, клиентам/поставщикам и где пока нельзя делать вывод?"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"user_message": "Покажи хвосты по счету 60 на август 2020 по ООО Альтернатива Плюс; если точных данных нет, скажи это прямо и не подменяй ответ общим обзором."
|
"user_message": "А по деньгам в этом же году кто больше всего занес и кому больше всего ушло? Банк не называй обычным клиентом без оговорки."
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"user_message": "Что было на складе на март 2021 по доступным данным? Дай прямой ответ и не уводи его в контрагента Группа СВК."
|
"user_message": "Возьми как выбранного контрагента Группа СВК: сколько по нему входящих денег, исходящих денег и расчетное нетто, на чем это основано?"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"user_message": "Вернись к ООО Альтернатива Плюс: сколько всего денег получили и заплатили по всем подтвержденным данным, но не смешивай это с отдельной цепочкой Группа СВК и не называй оборот чистой прибылью."
|
"user_message": "Теперь по нему покажи документы и не переключайся на организацию."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"user_message": "И коротко сравни: что у нас подтверждено по компании, что отдельно по выбранному контрагенту, и какие выводы нельзя делать?"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"user_message": "ООО Альтернатива Плюс"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
@ -1972,6 +1972,11 @@ def derive_step_execution_status(reply_type: str | None, debug_payload: dict[str
|
||||||
and mcp_discovery_candidate_status == "ready_for_guarded_use"
|
and mcp_discovery_candidate_status == "ready_for_guarded_use"
|
||||||
and reply_type in {"factual", "factual_with_explanation", "partial_coverage"}
|
and reply_type in {"factual", "factual_with_explanation", "partial_coverage"}
|
||||||
):
|
):
|
||||||
|
if (
|
||||||
|
mcp_discovery_candidate.get("hot_runtime_wired") is True
|
||||||
|
and debug_payload.get("mcp_discovery_execution_handoff_can_use_guarded_response") is True
|
||||||
|
):
|
||||||
|
return "exact"
|
||||||
return "partial"
|
return "partial"
|
||||||
capability_route_mode = str(debug_payload.get("capability_route_mode") or "").strip()
|
capability_route_mode = str(debug_payload.get("capability_route_mode") or "").strip()
|
||||||
fallback_type = str(debug_payload.get("fallback_type") or "").strip()
|
fallback_type = str(debug_payload.get("fallback_type") or "").strip()
|
||||||
|
|
@ -2360,6 +2365,8 @@ def is_validated_memory_checkpoint_answer(
|
||||||
business_review: dict[str, Any],
|
business_review: dict[str, Any],
|
||||||
violations: list[str],
|
violations: list[str],
|
||||||
) -> bool:
|
) -> bool:
|
||||||
|
if is_validated_comparison_boundary_memory_checkpoint(state, business_review, violations):
|
||||||
|
return True
|
||||||
tags = set(normalize_string_list(state.get("semantic_tags")))
|
tags = set(normalize_string_list(state.get("semantic_tags")))
|
||||||
if "memory" not in tags:
|
if "memory" not in tags:
|
||||||
return False
|
return False
|
||||||
|
|
@ -2375,6 +2382,46 @@ def is_validated_memory_checkpoint_answer(
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def is_validated_comparison_boundary_memory_checkpoint(
|
||||||
|
state: dict[str, Any],
|
||||||
|
business_review: dict[str, Any],
|
||||||
|
violations: list[str],
|
||||||
|
) -> bool:
|
||||||
|
if violations:
|
||||||
|
return False
|
||||||
|
tags = set(normalize_string_list(state.get("semantic_tags")))
|
||||||
|
if not {"business_overview", "selected_object", "organization_clarification"} <= tags:
|
||||||
|
return False
|
||||||
|
comparison_scope = state.get("comparison_scope") if isinstance(state.get("comparison_scope"), dict) else {}
|
||||||
|
organization = comparison_scope.get("organization") if isinstance(comparison_scope.get("organization"), dict) else {}
|
||||||
|
counterparty = comparison_scope.get("counterparty") if isinstance(comparison_scope.get("counterparty"), dict) else {}
|
||||||
|
proof_bundles = comparison_scope.get("proof_bundles") if isinstance(comparison_scope.get("proof_bundles"), dict) else {}
|
||||||
|
value_flow_bundle = (
|
||||||
|
proof_bundles.get("counterparty_value_flow_bundle")
|
||||||
|
if isinstance(proof_bundles.get("counterparty_value_flow_bundle"), dict)
|
||||||
|
else None
|
||||||
|
)
|
||||||
|
document_bundle = (
|
||||||
|
proof_bundles.get("counterparty_document_bundle")
|
||||||
|
if isinstance(proof_bundles.get("counterparty_document_bundle"), dict)
|
||||||
|
else None
|
||||||
|
)
|
||||||
|
organization_label = str(organization.get("label") or "").strip()
|
||||||
|
counterparty_label = str(counterparty.get("label") or "").strip()
|
||||||
|
focus_object = state.get("focus_object") if isinstance(state.get("focus_object"), dict) else {}
|
||||||
|
focus_label = str(focus_object.get("label") or "").strip()
|
||||||
|
if not organization_label or not counterparty_label or not (value_flow_bundle or document_bundle):
|
||||||
|
return False
|
||||||
|
if focus_label and focus_label.casefold() != counterparty_label.casefold():
|
||||||
|
return False
|
||||||
|
return (
|
||||||
|
business_review.get("direct_answer_first_ok") is True
|
||||||
|
and business_review.get("answer_layering_ok") is True
|
||||||
|
and business_review.get("technical_garbage_present") is False
|
||||||
|
and business_review.get("business_usefulness_ok") is True
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def is_validated_confirmed_runtime_answer(
|
def is_validated_confirmed_runtime_answer(
|
||||||
state: dict[str, Any],
|
state: dict[str, Any],
|
||||||
execution_status: str,
|
execution_status: str,
|
||||||
|
|
@ -2386,7 +2433,17 @@ def is_validated_confirmed_runtime_answer(
|
||||||
if violations:
|
if violations:
|
||||||
return False
|
return False
|
||||||
if state.get("mcp_discovery_response_applied") is True:
|
if state.get("mcp_discovery_response_applied") is True:
|
||||||
return False
|
return (
|
||||||
|
str(state.get("mcp_discovery_response_candidate_status") or "").strip() == "ready_for_guarded_use"
|
||||||
|
and state.get("mcp_discovery_response_candidate_hot_runtime_wired") is True
|
||||||
|
and state.get("mcp_discovery_execution_handoff_can_use_guarded_response") is True
|
||||||
|
and str(state.get("reply_type") or "").strip()
|
||||||
|
in {"factual", "factual_with_explanation", "partial_coverage"}
|
||||||
|
and business_review.get("business_usefulness_ok") is True
|
||||||
|
and business_review.get("direct_answer_first_ok") is True
|
||||||
|
and business_review.get("answer_layering_ok") is True
|
||||||
|
and business_review.get("technical_garbage_present") is False
|
||||||
|
)
|
||||||
if str(state.get("reply_type") or "").strip() not in {"factual", "factual_with_explanation", "empty_but_valid"}:
|
if str(state.get("reply_type") or "").strip() not in {"factual", "factual_with_explanation", "empty_but_valid"}:
|
||||||
return False
|
return False
|
||||||
if str(state.get("fallback_type") or "").strip() not in {"", "none"}:
|
if str(state.get("fallback_type") or "").strip() not in {"", "none"}:
|
||||||
|
|
@ -2489,6 +2546,33 @@ def is_validated_clarification_answer(
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def is_validated_missing_axis_clarification_answer(
|
||||||
|
state: dict[str, Any],
|
||||||
|
execution_status: str,
|
||||||
|
business_review: dict[str, Any],
|
||||||
|
violations: list[str],
|
||||||
|
) -> bool:
|
||||||
|
if execution_status != "partial":
|
||||||
|
return False
|
||||||
|
if violations:
|
||||||
|
return False
|
||||||
|
if str(state.get("mcp_discovery_response_candidate_status") or "").strip() != "clarification_candidate":
|
||||||
|
return False
|
||||||
|
if str(state.get("mcp_discovery_route_candidate_status") or "").strip() != "needs_user_scope":
|
||||||
|
return False
|
||||||
|
if str(state.get("mcp_discovery_response_reply_type") or "").strip() != "clarification_required":
|
||||||
|
return False
|
||||||
|
if not normalize_string_list(state.get("mcp_discovery_route_candidate_missing_axes")):
|
||||||
|
return False
|
||||||
|
return (
|
||||||
|
business_review.get("business_usefulness_ok") is True
|
||||||
|
and business_review.get("direct_answer_first_ok") is True
|
||||||
|
and business_review.get("answer_layering_ok") is True
|
||||||
|
and business_review.get("technical_garbage_present") is False
|
||||||
|
and business_review.get("next_action_present") is True
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def _business_review_is_clean(step_state: dict[str, Any]) -> bool:
|
def _business_review_is_clean(step_state: dict[str, Any]) -> bool:
|
||||||
business_review = step_state.get("business_first_review")
|
business_review = step_state.get("business_first_review")
|
||||||
if not isinstance(business_review, dict):
|
if not isinstance(business_review, dict):
|
||||||
|
|
@ -2736,6 +2820,12 @@ def validate_step_contract(step_state: dict[str, Any]) -> dict[str, Any]:
|
||||||
business_review,
|
business_review,
|
||||||
unique_violations,
|
unique_violations,
|
||||||
)
|
)
|
||||||
|
missing_axis_clarification_validated = is_validated_missing_axis_clarification_answer(
|
||||||
|
state,
|
||||||
|
execution_status,
|
||||||
|
business_review,
|
||||||
|
unique_violations,
|
||||||
|
)
|
||||||
state["violated_invariants"] = unique_violations
|
state["violated_invariants"] = unique_violations
|
||||||
state["warnings"] = list(dict.fromkeys(warnings))
|
state["warnings"] = list(dict.fromkeys(warnings))
|
||||||
state["hard_fail"] = hard_fail
|
state["hard_fail"] = hard_fail
|
||||||
|
|
@ -2744,6 +2834,7 @@ def validate_step_contract(step_state: dict[str, Any]) -> dict[str, Any]:
|
||||||
state["runtime_factual_answer_validated"] = runtime_factual_validated
|
state["runtime_factual_answer_validated"] = runtime_factual_validated
|
||||||
state["guarded_insufficiency_validated"] = guarded_insufficiency_validated
|
state["guarded_insufficiency_validated"] = guarded_insufficiency_validated
|
||||||
state["clarification_answer_validated"] = clarification_validated
|
state["clarification_answer_validated"] = clarification_validated
|
||||||
|
state["missing_axis_clarification_validated"] = missing_axis_clarification_validated
|
||||||
state["acceptance_status"] = acceptance_status_from_execution(
|
state["acceptance_status"] = acceptance_status_from_execution(
|
||||||
execution_status,
|
execution_status,
|
||||||
hard_fail,
|
hard_fail,
|
||||||
|
|
@ -2753,6 +2844,7 @@ def validate_step_contract(step_state: dict[str, Any]) -> dict[str, Any]:
|
||||||
or runtime_factual_validated
|
or runtime_factual_validated
|
||||||
or guarded_insufficiency_validated
|
or guarded_insufficiency_validated
|
||||||
or clarification_validated
|
or clarification_validated
|
||||||
|
or missing_axis_clarification_validated
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
state["status"] = state["acceptance_status"]
|
state["status"] = state["acceptance_status"]
|
||||||
|
|
@ -2913,6 +3005,7 @@ def build_scenario_step_state(
|
||||||
"actual_direct_answer": top_non_empty[0] if top_non_empty else None,
|
"actual_direct_answer": top_non_empty[0] if top_non_empty else None,
|
||||||
"extracted_filters": debug.get("extracted_filters") if isinstance(debug.get("extracted_filters"), dict) else {},
|
"extracted_filters": debug.get("extracted_filters") if isinstance(debug.get("extracted_filters"), dict) else {},
|
||||||
"focus_object": context.get("active_focus_object") if isinstance(context.get("active_focus_object"), dict) else None,
|
"focus_object": context.get("active_focus_object") if isinstance(context.get("active_focus_object"), dict) else None,
|
||||||
|
"comparison_scope": context.get("comparison_scope") if isinstance(context.get("comparison_scope"), dict) else None,
|
||||||
"fallback_type": debug.get("fallback_type"),
|
"fallback_type": debug.get("fallback_type"),
|
||||||
"mcp_call_status": debug.get("mcp_call_status"),
|
"mcp_call_status": debug.get("mcp_call_status"),
|
||||||
"balance_confirmed": debug.get("balance_confirmed"),
|
"balance_confirmed": debug.get("balance_confirmed"),
|
||||||
|
|
@ -2947,6 +3040,20 @@ def save_scenario_step_bundle(
|
||||||
write_text(step_dir / "resolved_question.txt", f"{step_state['question_resolved']}\n")
|
write_text(step_dir / "resolved_question.txt", f"{step_state['question_resolved']}\n")
|
||||||
|
|
||||||
|
|
||||||
|
def is_effectively_complete_partial_step(step_output: dict[str, Any]) -> bool:
|
||||||
|
execution_status = str(step_output.get("execution_status") or step_output.get("status") or "").strip()
|
||||||
|
if execution_status == "exact":
|
||||||
|
return True
|
||||||
|
if execution_status != "partial":
|
||||||
|
return False
|
||||||
|
if str(step_output.get("acceptance_status") or "").strip() != "validated":
|
||||||
|
return False
|
||||||
|
return (
|
||||||
|
step_output.get("missing_axis_clarification_validated") is True
|
||||||
|
or step_output.get("clarification_answer_validated") is True
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def derive_scenario_execution_status(step_outputs: dict[str, dict[str, Any]]) -> str:
|
def derive_scenario_execution_status(step_outputs: dict[str, dict[str, Any]]) -> str:
|
||||||
statuses = [str(item.get("execution_status") or item.get("status") or "") for item in step_outputs.values()]
|
statuses = [str(item.get("execution_status") or item.get("status") or "") for item in step_outputs.values()]
|
||||||
if not statuses:
|
if not statuses:
|
||||||
|
|
@ -2955,7 +3062,7 @@ def derive_scenario_execution_status(step_outputs: dict[str, dict[str, Any]]) ->
|
||||||
return "blocked"
|
return "blocked"
|
||||||
if any(status == "needs_exact_capability" for status in statuses):
|
if any(status == "needs_exact_capability" for status in statuses):
|
||||||
return "needs_exact_capability"
|
return "needs_exact_capability"
|
||||||
if any(status == "partial" for status in statuses):
|
if any(not is_effectively_complete_partial_step(item) for item in step_outputs.values()):
|
||||||
return "partial"
|
return "partial"
|
||||||
return "exact"
|
return "exact"
|
||||||
|
|
||||||
|
|
@ -3419,6 +3526,7 @@ def build_pack_summary(
|
||||||
scenario_results: list[dict[str, Any]],
|
scenario_results: list[dict[str, Any]],
|
||||||
final_status: str,
|
final_status: str,
|
||||||
execution_status: str,
|
execution_status: str,
|
||||||
|
acceptance_status: str,
|
||||||
) -> str:
|
) -> str:
|
||||||
lines = [
|
lines = [
|
||||||
"# Pack summary",
|
"# Pack summary",
|
||||||
|
|
@ -3427,6 +3535,7 @@ def build_pack_summary(
|
||||||
f"- domain: `{pack['domain']}`",
|
f"- domain: `{pack['domain']}`",
|
||||||
f"- title: {pack['title']}",
|
f"- title: {pack['title']}",
|
||||||
f"- execution_status: `{execution_status}`",
|
f"- execution_status: `{execution_status}`",
|
||||||
|
f"- acceptance_status: `{acceptance_status}`",
|
||||||
f"- final_status: `{final_status}`",
|
f"- final_status: `{final_status}`",
|
||||||
"",
|
"",
|
||||||
"## Scenarios",
|
"## Scenarios",
|
||||||
|
|
@ -3450,6 +3559,7 @@ def build_pack_final_status(
|
||||||
scenario_results: list[dict[str, Any]],
|
scenario_results: list[dict[str, Any]],
|
||||||
final_status: str,
|
final_status: str,
|
||||||
execution_status: str,
|
execution_status: str,
|
||||||
|
acceptance_status: str,
|
||||||
) -> str:
|
) -> str:
|
||||||
expected_scenarios = len(pack.get("scenarios") or [])
|
expected_scenarios = len(pack.get("scenarios") or [])
|
||||||
executed_scenarios = len(scenario_results)
|
executed_scenarios = len(scenario_results)
|
||||||
|
|
@ -3471,6 +3581,7 @@ def build_pack_final_status(
|
||||||
|
|
||||||
- status: `{final_status}`
|
- status: `{final_status}`
|
||||||
- execution_status: `{execution_status}`
|
- execution_status: `{execution_status}`
|
||||||
|
- acceptance_status: `{acceptance_status}`
|
||||||
- pack_id: `{pack['pack_id']}`
|
- pack_id: `{pack['pack_id']}`
|
||||||
- domain: `{pack['domain']}`
|
- domain: `{pack['domain']}`
|
||||||
- reason: {reason}
|
- reason: {reason}
|
||||||
|
|
@ -3522,6 +3633,18 @@ def derive_pack_final_status(pack: dict[str, Any], scenario_results: list[dict[s
|
||||||
return "accepted" if len(scenario_results) == len(pack.get("scenarios") or []) else "partial"
|
return "accepted" if len(scenario_results) == len(pack.get("scenarios") or []) else "partial"
|
||||||
|
|
||||||
|
|
||||||
|
def derive_pack_effective_final_status(acceptance_status: str, execution_status: str) -> str:
|
||||||
|
normalized_acceptance = str(acceptance_status or "").strip() or "partial"
|
||||||
|
normalized_execution = str(execution_status or "").strip() or "partial"
|
||||||
|
if normalized_acceptance in {"blocked", "needs_exact_capability"}:
|
||||||
|
return normalized_acceptance
|
||||||
|
if normalized_execution in {"blocked", "needs_exact_capability"}:
|
||||||
|
return normalized_execution
|
||||||
|
if normalized_acceptance == "accepted" and normalized_execution == "exact":
|
||||||
|
return "accepted"
|
||||||
|
return "partial"
|
||||||
|
|
||||||
|
|
||||||
def build_scenario_acceptance_matrix(pack: dict[str, Any], scenario_results: list[dict[str, Any]]) -> str:
|
def build_scenario_acceptance_matrix(pack: dict[str, Any], scenario_results: list[dict[str, Any]]) -> str:
|
||||||
scenario_status_map = {
|
scenario_status_map = {
|
||||||
str(item.get("scenario_id") or ""): str(item.get("final_status") or "unknown")
|
str(item.get("scenario_id") or ""): str(item.get("final_status") or "unknown")
|
||||||
|
|
@ -3906,6 +4029,7 @@ def compact_step_output_for_review(step_output: Any) -> dict[str, Any]:
|
||||||
"required_carryover_invariants": step_output.get("required_carryover_invariants"),
|
"required_carryover_invariants": step_output.get("required_carryover_invariants"),
|
||||||
"extracted_filters": step_output.get("extracted_filters"),
|
"extracted_filters": step_output.get("extracted_filters"),
|
||||||
"focus_object": step_output.get("focus_object"),
|
"focus_object": step_output.get("focus_object"),
|
||||||
|
"comparison_scope": step_output.get("comparison_scope"),
|
||||||
"date_scope": step_output.get("date_scope"),
|
"date_scope": step_output.get("date_scope"),
|
||||||
"result_mode": step_output.get("result_mode"),
|
"result_mode": step_output.get("result_mode"),
|
||||||
"truth_mode": step_output.get("truth_mode"),
|
"truth_mode": step_output.get("truth_mode"),
|
||||||
|
|
@ -4436,6 +4560,8 @@ def build_deterministic_repair_targets(
|
||||||
"schema_version": "domain_pack_repair_targets_v1",
|
"schema_version": "domain_pack_repair_targets_v1",
|
||||||
"pack_id": pack_state.get("pack_id"),
|
"pack_id": pack_state.get("pack_id"),
|
||||||
"domain": pack_state.get("domain"),
|
"domain": pack_state.get("domain"),
|
||||||
|
"execution_status": pack_state.get("execution_status"),
|
||||||
|
"acceptance_status": pack_state.get("acceptance_status"),
|
||||||
"final_status": pack_state.get("final_status"),
|
"final_status": pack_state.get("final_status"),
|
||||||
"target_count": len(targets),
|
"target_count": len(targets),
|
||||||
"severity_counts": severity_counts,
|
"severity_counts": severity_counts,
|
||||||
|
|
@ -4588,6 +4714,18 @@ def analyst_target_contradicts_validated_step(target: dict[str, Any], step_snaps
|
||||||
):
|
):
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
if step_snapshot.get("memory_checkpoint_validated") is True and evidence_problem:
|
||||||
|
memory_checkpoint_markers = (
|
||||||
|
"machine-readable",
|
||||||
|
"comparison boundary",
|
||||||
|
"carryover",
|
||||||
|
"comparison bundle",
|
||||||
|
"reusable comparison",
|
||||||
|
"selected counterparty",
|
||||||
|
)
|
||||||
|
if any(marker in target_text for marker in memory_checkpoint_markers):
|
||||||
|
return True
|
||||||
|
|
||||||
followup_problem = problem_type in {
|
followup_problem = problem_type in {
|
||||||
"followup_action_resolution_gap",
|
"followup_action_resolution_gap",
|
||||||
"object_memory_gap",
|
"object_memory_gap",
|
||||||
|
|
@ -4980,8 +5118,9 @@ def evaluate_deterministic_loop_gate(
|
||||||
repair_targets: dict[str, Any],
|
repair_targets: dict[str, Any],
|
||||||
) -> tuple[bool, str]:
|
) -> tuple[bool, str]:
|
||||||
pack_final_status = str(pack_state.get("final_status") or "").strip() or "partial"
|
pack_final_status = str(pack_state.get("final_status") or "").strip() or "partial"
|
||||||
if pack_final_status != "accepted":
|
pack_acceptance_status = str(pack_state.get("acceptance_status") or pack_final_status).strip() or "partial"
|
||||||
return False, f"pack_final_status={pack_final_status}"
|
if pack_acceptance_status != "accepted":
|
||||||
|
return False, f"pack_acceptance_status={pack_acceptance_status};pack_final_status={pack_final_status}"
|
||||||
severity_counts = repair_targets.get("severity_counts") if isinstance(repair_targets, dict) else {}
|
severity_counts = repair_targets.get("severity_counts") if isinstance(repair_targets, dict) else {}
|
||||||
if isinstance(severity_counts, dict):
|
if isinstance(severity_counts, dict):
|
||||||
p0_count = int(severity_counts.get("P0") or 0)
|
p0_count = int(severity_counts.get("P0") or 0)
|
||||||
|
|
@ -5120,7 +5259,8 @@ def build_analyst_loop_prompt(
|
||||||
|
|
||||||
Rules:
|
Rules:
|
||||||
- `accepted` is allowed only if quality_score >= {target_score}, unresolved_p0_count = 0, and regression_detected = false;
|
- `accepted` is allowed only if quality_score >= {target_score}, unresolved_p0_count = 0, and regression_detected = false;
|
||||||
- `accepted` is forbidden if the evidence bundle shows `pack_state.final_status != accepted` or the deterministic repair targets still contain any `P0` or `P1` items;
|
- `accepted` is forbidden if the evidence bundle shows `pack_state.acceptance_status != accepted` (or, for old artifacts without `acceptance_status`, `pack_state.final_status != accepted`) or the deterministic repair targets still contain any `P0` or `P1` items;
|
||||||
|
- `pack_state.final_status=partial` with `acceptance_status=accepted` means the run still had partial execution evidence; treat it as a proof-strength signal, not as a presentation bug by itself;
|
||||||
- `accepted` also requires `direct_answer_ok = true`, `business_usefulness_ok = true`, `temporal_honesty_ok = true`, and `field_truth_ok = true`;
|
- `accepted` also requires `direct_answer_ok = true`, `business_usefulness_ok = true`, `temporal_honesty_ok = true`, and `field_truth_ok = true`;
|
||||||
- Treat validated bounded MCP discovery as the semantic route when `bounded_mcp_answer_validated = true`, `mcp_discovery_response_applied = true`, `mcp_discovery_response_candidate_status = ready_for_guarded_use`, and `mcp_discovery_effective_intents` matches the business question. The legacy address route may be only a seed lane; do not call that silent heuristic masking unless the selected discovery chain is wrong, not ready, not applied, or the user-facing answer/state is semantically wrong.
|
- Treat validated bounded MCP discovery as the semantic route when `bounded_mcp_answer_validated = true`, `mcp_discovery_response_applied = true`, `mcp_discovery_response_candidate_status = ready_for_guarded_use`, and `mcp_discovery_effective_intents` matches the business question. The legacy address route may be only a seed lane; do not call that silent heuristic masking unless the selected discovery chain is wrong, not ready, not applied, or the user-facing answer/state is semantically wrong.
|
||||||
- Use `mcp_discovery_route_candidate_status`, `mcp_discovery_route_candidate_missing_axes`, and `mcp_discovery_route_candidate_enablement_reason` to distinguish a valid user-scope clarification from a real missing reviewed route. Do not ask the coder to overfit the visible answer when the correct next action is route enablement or missing-axis clarification.
|
- Use `mcp_discovery_route_candidate_status`, `mcp_discovery_route_candidate_missing_axes`, and `mcp_discovery_route_candidate_enablement_reason` to distinguish a valid user-scope clarification from a real missing reviewed route. Do not ask the coder to overfit the visible answer when the correct next action is route enablement or missing-axis clarification.
|
||||||
|
|
@ -5998,7 +6138,8 @@ def handle_run_pack(args: argparse.Namespace) -> int:
|
||||||
)
|
)
|
||||||
|
|
||||||
execution_status = derive_pack_execution_status(scenario_results)
|
execution_status = derive_pack_execution_status(scenario_results)
|
||||||
final_status = derive_pack_final_status(pack, scenario_results)
|
acceptance_status = derive_pack_final_status(pack, scenario_results)
|
||||||
|
final_status = derive_pack_effective_final_status(acceptance_status, execution_status)
|
||||||
|
|
||||||
pack_state = {
|
pack_state = {
|
||||||
"schema_version": SCENARIO_PACK_SCHEMA_VERSION,
|
"schema_version": SCENARIO_PACK_SCHEMA_VERSION,
|
||||||
|
|
@ -6009,6 +6150,7 @@ def handle_run_pack(args: argparse.Namespace) -> int:
|
||||||
"bindings": pack.get("bindings") or {},
|
"bindings": pack.get("bindings") or {},
|
||||||
"scenario_results": scenario_results,
|
"scenario_results": scenario_results,
|
||||||
"execution_status": execution_status,
|
"execution_status": execution_status,
|
||||||
|
"acceptance_status": acceptance_status,
|
||||||
"final_status": final_status,
|
"final_status": final_status,
|
||||||
"updated_at": datetime.now(timezone.utc).replace(microsecond=0).isoformat(),
|
"updated_at": datetime.now(timezone.utc).replace(microsecond=0).isoformat(),
|
||||||
}
|
}
|
||||||
|
|
@ -6018,8 +6160,8 @@ def handle_run_pack(args: argparse.Namespace) -> int:
|
||||||
write_json(pack_dir / "pack_state.json", pack_state)
|
write_json(pack_dir / "pack_state.json", pack_state)
|
||||||
write_json(pack_dir / "repair_targets.json", repair_targets)
|
write_json(pack_dir / "repair_targets.json", repair_targets)
|
||||||
write_text(pack_dir / "repair_targets.md", build_repair_targets_summary(repair_targets))
|
write_text(pack_dir / "repair_targets.md", build_repair_targets_summary(repair_targets))
|
||||||
write_text(pack_dir / "pack_summary.md", build_pack_summary(pack, scenario_results, final_status, execution_status))
|
write_text(pack_dir / "pack_summary.md", build_pack_summary(pack, scenario_results, final_status, execution_status, acceptance_status))
|
||||||
write_text(pack_dir / "final_status.md", build_pack_final_status(pack, scenario_results, final_status, execution_status))
|
write_text(pack_dir / "final_status.md", build_pack_final_status(pack, scenario_results, final_status, execution_status, acceptance_status))
|
||||||
print(f"[domain-case-loop] saved pack artifacts to {pack_dir}")
|
print(f"[domain-case-loop] saved pack artifacts to {pack_dir}")
|
||||||
print(f"[domain-case-loop] execution_status={execution_status} final_status={final_status}")
|
print(f"[domain-case-loop] execution_status={execution_status} final_status={final_status}")
|
||||||
return 0
|
return 0
|
||||||
|
|
|
||||||
|
|
@ -135,6 +135,45 @@ COMPANY_PROFIT_ANSWER_RE = re.compile(
|
||||||
"(?:\u0447\u0438\u0441\u0442\u0430\u044f\\s+\u043f\u0440\u0438\u0431\u044b\u043b\u044c|\u043f\u0440\u0438\u0431\u044b\u043b\u044c\u044e|90/91/99|\u0444\u0438\u043d\u0440\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442|\u0443\u0431\u044b\u0442\u043e\u043a)",
|
"(?:\u0447\u0438\u0441\u0442\u0430\u044f\\s+\u043f\u0440\u0438\u0431\u044b\u043b\u044c|\u043f\u0440\u0438\u0431\u044b\u043b\u044c\u044e|90/91/99|\u0444\u0438\u043d\u0440\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442|\u0443\u0431\u044b\u0442\u043e\u043a)",
|
||||||
re.IGNORECASE,
|
re.IGNORECASE,
|
||||||
)
|
)
|
||||||
|
BACKEND_ERROR_ANSWER_RE = re.compile(
|
||||||
|
"(?:backend_error|internal\\s+error|"
|
||||||
|
"\u0432\u043d\u0443\u0442\u0440\u0435\u043d\u043d\\w*\\s+\u043e\u0448\u0438\u0431\u043a|"
|
||||||
|
"\u043d\u0435\\s+\u0443\u0434\u0430\u043b\u043e\u0441\u044c\\s+\u0437\u0430\u0432\u0435\u0440\u0448\u0438\u0442\u044c\\s+\u0440\u0430\u0437\u0431\u043e\u0440)",
|
||||||
|
re.IGNORECASE,
|
||||||
|
)
|
||||||
|
TOP_YEAR_QUESTION_RE = re.compile(
|
||||||
|
"(?:\u0441\u0430\u043c\\w*\\s+\u0434\u043e\u0445\u043e\u0434\u043d\\w*\\s+\u0433\u043e\u0434|"
|
||||||
|
"\u0434\u043e\u0445\u043e\u0434\u043d\\w*\\s+\u0433\u043e\u0434|"
|
||||||
|
"\u043b\u0443\u0447\u0448\\w*\\s+\u0433\u043e\u0434|top[-\\s]*year|best\\s+year)",
|
||||||
|
re.IGNORECASE,
|
||||||
|
)
|
||||||
|
DIRECT_TOP_YEAR_ANSWER_RE = re.compile(
|
||||||
|
"(?:(?:\u0441\u0430\u043c\\w*\\s+\u0434\u043e\u0445\u043e\u0434\u043d\\w*\\s+\u0433\u043e\u0434|"
|
||||||
|
"\u043b\u0443\u0447\u0448\\w*\\s+\u0433\u043e\u0434|\u043b\u0438\u0434\u0438\u0440\\w*|"
|
||||||
|
"\u0442\u043e\u043f[-\\s]*1|top[-\\s]*year|best\\s+year)[^\\n]{0,160}(?:19|20)\\d{2}|"
|
||||||
|
"(?:19|20)\\d{2}[^\\n]{0,120}(?:\u043b\u0438\u0434\u0438\u0440\\w*|"
|
||||||
|
"\u0441\u0430\u043c\\w*\\s+\u0434\u043e\u0445\u043e\u0434\u043d\\w*|"
|
||||||
|
"\u043b\u0443\u0447\u0448\\w*\\s+\u0433\u043e\u0434))",
|
||||||
|
re.IGNORECASE,
|
||||||
|
)
|
||||||
|
GENERIC_BUSINESS_OVERVIEW_ANSWER_RE = re.compile(
|
||||||
|
"(?:\u0432\u044b\u0433\u043b\u044f\u0434\u0438\u0442\\s+\u043a\u0430\u043a\\s+\u0431\u0438\u0437\u043d\u0435\u0441|"
|
||||||
|
"\u043a\u0440\u0443\u043f\u043d\\w*\\s+\u043a\u043e\u043d\u0442\u0440\u0430\u043a\u0442\\w*\\s+\u0434\u0435\u043d\u0435\u0436\u043d\\w*\\s+\u043f\u043e\u0442\u043e\u043a|"
|
||||||
|
"\u0431\u0438\u0437\u043d\u0435\u0441-\u043e\u0431\u0437\u043e\u0440|\u043e\u0431\u0449\\w*\\s+\u0431\u0438\u0437\u043d\u0435\u0441\\w*\\s+\u043e\u0431\u0437\u043e\u0440)",
|
||||||
|
re.IGNORECASE,
|
||||||
|
)
|
||||||
|
VAT_PURCHASE_DATE_QUESTION_RE = re.compile(
|
||||||
|
"(?:\u043d\u0434\u0441[^\\n]{0,80}\u0434\u0430\u0442[ау]\\s+\u043f\u043e\u043a\u0443\u043f\u043a|"
|
||||||
|
"\u0434\u0430\u0442[ау]\\s+\u043f\u043e\u043a\u0443\u043f\u043a[^\\n]{0,80}\u043d\u0434\u0441)",
|
||||||
|
re.IGNORECASE,
|
||||||
|
)
|
||||||
|
VAT_PURCHASE_DATE_BASIS_RE = re.compile(
|
||||||
|
"(?:\u0434\u0430\u0442[ауы]\\s+\u043f\u043e\u043a\u0443\u043f\u043a|\u043f\u043e\u043a\u0443\u043f\u043a[аи]\\s+\u043e\u0442|"
|
||||||
|
"\u043f\u043e\u0441\u0442\u0443\u043f\u043b\u0435\u043d\u0438[ея]\\s+\u043e\u0442|\u043e\u043f\u043e\u0440\u043d\\w*\\s+\u0434\u0430\u0442|"
|
||||||
|
"\u043f\u0435\u0440\u0432\\w*\\s+(?:\u043d\u0430\u0439\u0434\u0435\u043d\u043d\\w*\\s+)?(?:\u043f\u043e\u043a\u0443\u043f\u043a|\u043f\u043e\u0441\u0442\u0443\u043f\u043b\u0435\u043d)|"
|
||||||
|
"\u0434\u0430\u0442\\w*\\s+\u0437\u0430\u043a\u0443\u043f)",
|
||||||
|
re.IGNORECASE,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def now_iso() -> str:
|
def now_iso() -> str:
|
||||||
|
|
@ -360,6 +399,9 @@ def build_step_for_pair(pair: dict[str, Any]) -> dict[str, Any]:
|
||||||
"business_answer_too_verbose": "P1",
|
"business_answer_too_verbose": "P1",
|
||||||
"bank_counterparty_misclassified_as_business_partner": "P1",
|
"bank_counterparty_misclassified_as_business_partner": "P1",
|
||||||
"counterparty_value_flow_misrouted_to_company_profit": "P0",
|
"counterparty_value_flow_misrouted_to_company_profit": "P0",
|
||||||
|
"backend_error_response": "P0",
|
||||||
|
"year_ranking_direct_answer_missing": "P0",
|
||||||
|
"purchase_date_vat_anchor_ambiguous": "P1",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -374,6 +416,20 @@ def marker_hits(text: str, markers: tuple[str, ...]) -> list[str]:
|
||||||
return [marker for marker in markers if marker and marker.casefold() in lowered]
|
return [marker for marker in markers if marker and marker.casefold() in lowered]
|
||||||
|
|
||||||
|
|
||||||
|
def add_review_issue(
|
||||||
|
*,
|
||||||
|
issue_codes: list[str],
|
||||||
|
root_layers: list[str],
|
||||||
|
issue_code: str,
|
||||||
|
layers: tuple[str, ...],
|
||||||
|
) -> None:
|
||||||
|
if issue_code not in issue_codes:
|
||||||
|
issue_codes.append(issue_code)
|
||||||
|
for layer in layers:
|
||||||
|
if layer and layer not in root_layers:
|
||||||
|
root_layers.append(layer)
|
||||||
|
|
||||||
|
|
||||||
def detect_counterparty_value_flow_profit_mismatch(question: str, assistant_text: str) -> dict[str, Any] | None:
|
def detect_counterparty_value_flow_profit_mismatch(question: str, assistant_text: str) -> dict[str, Any] | None:
|
||||||
question_text = str(question or "")
|
question_text = str(question or "")
|
||||||
answer_text = str(assistant_text or "")
|
answer_text = str(assistant_text or "")
|
||||||
|
|
@ -412,6 +468,51 @@ def augment_gui_business_review(step_state: dict[str, Any]) -> dict[str, Any]:
|
||||||
assistant_text = str(step_state.get("assistant_text") or "")
|
assistant_text = str(step_state.get("assistant_text") or "")
|
||||||
issue_codes = [str(item) for item in review.get("issue_codes", []) if str(item).strip()]
|
issue_codes = [str(item) for item in review.get("issue_codes", []) if str(item).strip()]
|
||||||
root_layers = [str(item) for item in review.get("suggested_root_cause_layers", []) if str(item).strip()]
|
root_layers = [str(item) for item in review.get("suggested_root_cause_layers", []) if str(item).strip()]
|
||||||
|
semantic_details = (
|
||||||
|
dict(review.get("semantic_mismatch_details"))
|
||||||
|
if isinstance(review.get("semantic_mismatch_details"), dict)
|
||||||
|
else {}
|
||||||
|
)
|
||||||
|
first_answer_line = next((line.strip() for line in assistant_text.splitlines() if line.strip()), "")
|
||||||
|
|
||||||
|
if str(step_state.get("reply_type") or "").strip() == "backend_error" or BACKEND_ERROR_ANSWER_RE.search(assistant_text):
|
||||||
|
add_review_issue(
|
||||||
|
issue_codes=issue_codes,
|
||||||
|
root_layers=root_layers,
|
||||||
|
issue_code="backend_error_response",
|
||||||
|
layers=("runtime_error", "business_utility_gap"),
|
||||||
|
)
|
||||||
|
semantic_details["backend_error_response"] = {
|
||||||
|
"reply_type": step_state.get("reply_type"),
|
||||||
|
"first_answer_line": first_answer_line,
|
||||||
|
}
|
||||||
|
|
||||||
|
if TOP_YEAR_QUESTION_RE.search(question):
|
||||||
|
has_direct_top_year = bool(DIRECT_TOP_YEAR_ANSWER_RE.search(assistant_text))
|
||||||
|
generic_overview_first = bool(GENERIC_BUSINESS_OVERVIEW_ANSWER_RE.search(first_answer_line))
|
||||||
|
if not has_direct_top_year or generic_overview_first:
|
||||||
|
add_review_issue(
|
||||||
|
issue_codes=issue_codes,
|
||||||
|
root_layers=root_layers,
|
||||||
|
issue_code="year_ranking_direct_answer_missing",
|
||||||
|
layers=("answer_shape_mismatch", "business_utility_gap"),
|
||||||
|
)
|
||||||
|
semantic_details["year_ranking_direct_answer_missing"] = {
|
||||||
|
"has_direct_top_year": has_direct_top_year,
|
||||||
|
"generic_overview_first": generic_overview_first,
|
||||||
|
"first_answer_line": first_answer_line,
|
||||||
|
}
|
||||||
|
|
||||||
|
if VAT_PURCHASE_DATE_QUESTION_RE.search(question) and not VAT_PURCHASE_DATE_BASIS_RE.search(assistant_text):
|
||||||
|
add_review_issue(
|
||||||
|
issue_codes=issue_codes,
|
||||||
|
root_layers=root_layers,
|
||||||
|
issue_code="purchase_date_vat_anchor_ambiguous",
|
||||||
|
layers=("domain_anchor_gap", "field_mapping_gap"),
|
||||||
|
)
|
||||||
|
semantic_details["purchase_date_vat_anchor_ambiguous"] = {
|
||||||
|
"first_answer_line": first_answer_line,
|
||||||
|
}
|
||||||
|
|
||||||
technical_hits = [str(item) for item in review.get("technical_garbage_hits", []) if str(item).strip()]
|
technical_hits = [str(item) for item in review.get("technical_garbage_hits", []) if str(item).strip()]
|
||||||
for hit in marker_hits(assistant_text, GUI_TECHNICAL_LEAK_MARKERS):
|
for hit in marker_hits(assistant_text, GUI_TECHNICAL_LEAK_MARKERS):
|
||||||
|
|
@ -434,17 +535,18 @@ def augment_gui_business_review(step_state: dict[str, Any]) -> dict[str, Any]:
|
||||||
|
|
||||||
mismatch_details = detect_counterparty_value_flow_profit_mismatch(question, assistant_text)
|
mismatch_details = detect_counterparty_value_flow_profit_mismatch(question, assistant_text)
|
||||||
if mismatch_details:
|
if mismatch_details:
|
||||||
issue_code = "counterparty_value_flow_misrouted_to_company_profit"
|
add_review_issue(
|
||||||
if issue_code not in issue_codes:
|
issue_codes=issue_codes,
|
||||||
issue_codes.append(issue_code)
|
root_layers=root_layers,
|
||||||
if "followup_action_resolution_gap" not in root_layers:
|
issue_code="counterparty_value_flow_misrouted_to_company_profit",
|
||||||
root_layers.append("followup_action_resolution_gap")
|
layers=("followup_action_resolution_gap", "answer_shape_mismatch"),
|
||||||
if "answer_shape_mismatch" not in root_layers:
|
)
|
||||||
root_layers.append("answer_shape_mismatch")
|
semantic_details["counterparty_value_flow_misrouted_to_company_profit"] = mismatch_details
|
||||||
review["semantic_mismatch_details"] = mismatch_details
|
|
||||||
|
|
||||||
review["technical_garbage_present"] = bool(technical_hits)
|
review["technical_garbage_present"] = bool(technical_hits)
|
||||||
review["technical_garbage_hits"] = technical_hits
|
review["technical_garbage_hits"] = technical_hits
|
||||||
|
if semantic_details:
|
||||||
|
review["semantic_mismatch_details"] = semantic_details
|
||||||
review["issue_codes"] = issue_codes
|
review["issue_codes"] = issue_codes
|
||||||
review["suggested_root_cause_layers"] = list(dict.fromkeys(root_layers))
|
review["suggested_root_cause_layers"] = list(dict.fromkeys(root_layers))
|
||||||
review["business_usefulness_ok"] = not issue_codes
|
review["business_usefulness_ok"] = not issue_codes
|
||||||
|
|
|
||||||
|
|
@ -104,6 +104,117 @@ class AssistantStage1RunReviewTests(unittest.TestCase):
|
||||||
self.assertEqual(review["question_quality_review"]["turns_total"], 2)
|
self.assertEqual(review["question_quality_review"]["turns_total"], 2)
|
||||||
self.assertIn("contextual_followup", review["question_quality_review"]["tag_counts"])
|
self.assertIn("contextual_followup", review["question_quality_review"]["tag_counts"])
|
||||||
|
|
||||||
|
def test_review_flags_backend_error_as_business_blocker(self) -> None:
|
||||||
|
with tempfile.TemporaryDirectory() as tmp:
|
||||||
|
root = Path(tmp)
|
||||||
|
sessions_dir = root / "sessions"
|
||||||
|
reports_dir = root / "reports"
|
||||||
|
run_id = "assistant-stage1-backend-error"
|
||||||
|
session_file = sessions_dir / f"{run_id}-SAVED-001.json"
|
||||||
|
report_file = reports_dir / f"{run_id}.md"
|
||||||
|
write_json(
|
||||||
|
session_file,
|
||||||
|
session_payload(
|
||||||
|
[
|
||||||
|
{"role": "user", "text": "приветик - че как там дела"},
|
||||||
|
{
|
||||||
|
"role": "assistant",
|
||||||
|
"text": "Сейчас не удалось завершить разбор из-за внутренней ошибки контуров LLM.",
|
||||||
|
"reply_type": "backend_error",
|
||||||
|
"message_id": "a-backend-error",
|
||||||
|
"trace_id": "trace-backend-error",
|
||||||
|
"debug": {},
|
||||||
|
},
|
||||||
|
]
|
||||||
|
),
|
||||||
|
)
|
||||||
|
report_file.parent.mkdir(parents=True, exist_ok=True)
|
||||||
|
report_file.write_text(f"# Assistant Stage 1 Eval Run\n\n- run_id: {run_id}\n", encoding="utf-8")
|
||||||
|
|
||||||
|
review = reviewer.build_run_review(
|
||||||
|
run_id=run_id,
|
||||||
|
session_files=[session_file],
|
||||||
|
report_path=report_file,
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertEqual(review["summary"]["overall_business_status"], "fail")
|
||||||
|
self.assertIn("backend_error_response", review["summary"]["issue_counts"])
|
||||||
|
target_by_issue = {item["issue_code"]: item for item in review["repair_targets"]}
|
||||||
|
self.assertEqual(target_by_issue["backend_error_response"]["severity"], "P0")
|
||||||
|
|
||||||
|
def test_review_flags_top_year_generic_overview_without_direct_year(self) -> None:
|
||||||
|
with tempfile.TemporaryDirectory() as tmp:
|
||||||
|
root = Path(tmp)
|
||||||
|
sessions_dir = root / "sessions"
|
||||||
|
reports_dir = root / "reports"
|
||||||
|
run_id = "assistant-stage1-top-year-generic"
|
||||||
|
session_file = sessions_dir / f"{run_id}-SAVED-001.json"
|
||||||
|
report_file = reports_dir / f"{run_id}.md"
|
||||||
|
write_json(
|
||||||
|
session_file,
|
||||||
|
session_payload(
|
||||||
|
[
|
||||||
|
{"role": "user", "text": "какой у нас самый доходный год"},
|
||||||
|
{
|
||||||
|
"role": "assistant",
|
||||||
|
"text": "Коротко: по доступным данным компания выглядит как бизнес с крупными контрактными денежными потоками. Входящие деньги за доступное время: 10 000 руб.",
|
||||||
|
"reply_type": "partial_coverage",
|
||||||
|
"message_id": "a-top-year-generic",
|
||||||
|
"trace_id": "trace-top-year-generic",
|
||||||
|
"debug": {"capability_id": "address_customer_revenue_and_payments"},
|
||||||
|
},
|
||||||
|
]
|
||||||
|
),
|
||||||
|
)
|
||||||
|
report_file.parent.mkdir(parents=True, exist_ok=True)
|
||||||
|
report_file.write_text(f"# Assistant Stage 1 Eval Run\n\n- run_id: {run_id}\n", encoding="utf-8")
|
||||||
|
|
||||||
|
review = reviewer.build_run_review(
|
||||||
|
run_id=run_id,
|
||||||
|
session_files=[session_file],
|
||||||
|
report_path=report_file,
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertEqual(review["summary"]["overall_business_status"], "fail")
|
||||||
|
self.assertIn("year_ranking_direct_answer_missing", review["summary"]["issue_counts"])
|
||||||
|
target_by_issue = {item["issue_code"]: item for item in review["repair_targets"]}
|
||||||
|
self.assertEqual(target_by_issue["year_ranking_direct_answer_missing"]["severity"], "P0")
|
||||||
|
|
||||||
|
def test_review_accepts_direct_top_year_answer(self) -> None:
|
||||||
|
with tempfile.TemporaryDirectory() as tmp:
|
||||||
|
root = Path(tmp)
|
||||||
|
sessions_dir = root / "sessions"
|
||||||
|
reports_dir = root / "reports"
|
||||||
|
run_id = "assistant-stage1-top-year-clean"
|
||||||
|
session_file = sessions_dir / f"{run_id}-SAVED-001.json"
|
||||||
|
report_file = reports_dir / f"{run_id}.md"
|
||||||
|
write_json(
|
||||||
|
session_file,
|
||||||
|
session_payload(
|
||||||
|
[
|
||||||
|
{"role": "user", "text": "какой у нас самый доходный год"},
|
||||||
|
{
|
||||||
|
"role": "assistant",
|
||||||
|
"text": "Самый доходный год по подтвержденным поступлениям: 2021 (320 000 руб. по 1 операции). Это денежный поток, а не чистая прибыль.",
|
||||||
|
"reply_type": "factual",
|
||||||
|
"message_id": "a-top-year-clean",
|
||||||
|
"trace_id": "trace-top-year-clean",
|
||||||
|
"debug": {"capability_id": "address_customer_revenue_and_payments"},
|
||||||
|
},
|
||||||
|
]
|
||||||
|
),
|
||||||
|
)
|
||||||
|
report_file.parent.mkdir(parents=True, exist_ok=True)
|
||||||
|
report_file.write_text(f"# Assistant Stage 1 Eval Run\n\n- run_id: {run_id}\n", encoding="utf-8")
|
||||||
|
|
||||||
|
review = reviewer.build_run_review(
|
||||||
|
run_id=run_id,
|
||||||
|
session_files=[session_file],
|
||||||
|
report_path=report_file,
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertNotIn("year_ranking_direct_answer_missing", review["summary"]["issue_counts"])
|
||||||
|
|
||||||
def test_save_run_review_materializes_machine_and_markdown_artifacts(self) -> None:
|
def test_save_run_review_materializes_machine_and_markdown_artifacts(self) -> None:
|
||||||
with tempfile.TemporaryDirectory() as tmp:
|
with tempfile.TemporaryDirectory() as tmp:
|
||||||
output_dir = Path(tmp) / "review"
|
output_dir = Path(tmp) / "review"
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue